From 4d1a70932a158ab05b84b20a2c135a288aa0ba48 Mon Sep 17 00:00:00 2001 From: zmj <1493694146@qq.com> Date: Fri, 8 Dec 2023 18:51:02 +0800 Subject: [PATCH] shit --- package-lock.json | 1645 +- package.json | 3 + src/App.vue | 5 - src/api.js | 57 +- src/components/header.vue | 4 +- src/components/index/bottomCenter.vue | 86 +- src/components/townDetail/bottomleft.vue | 579 +- src/components/townDetail/test.js | 26408 --------------------- src/components/townDetail/topCenter.vue | 14 + src/components/townDetail/topLeft.vue | 147 + src/components/townDetail/topRight.vue | 140 + src/components/townDetail/version.js | 1 - src/main.js | 11 +- src/router/index.js | 77 +- src/store/index.js | 19 + src/view/Businesses.vue | 52 +- src/view/commodity.vue | 103 +- src/view/finance.vue | 348 +- src/view/home.vue | 13 + src/view/index.vue | 19 +- src/view/login.vue | 158 + src/view/order.vue | 146 +- src/view/townDetail.vue | 224 +- static/login/BKH.png | Bin 0 -> 1206 bytes static/login/DL.png | Bin 0 -> 1967 bytes static/login/DLBG.png | Bin 0 -> 387311 bytes static/login/KJ.png | Bin 0 -> 1048 bytes static/login/MM.png | Bin 0 -> 2027 bytes static/login/ZH.png | Bin 0 -> 2326 bytes static/login/bg.png | Bin 0 -> 1179489 bytes 30 files changed, 2843 insertions(+), 27416 deletions(-) delete mode 100644 src/components/townDetail/test.js create mode 100644 src/components/townDetail/topLeft.vue create mode 100644 src/components/townDetail/topRight.vue delete mode 100644 src/components/townDetail/version.js create mode 100644 src/store/index.js create mode 100644 src/view/home.vue create mode 100644 src/view/login.vue create mode 100644 static/login/BKH.png create mode 100644 static/login/DL.png create mode 100644 static/login/DLBG.png create mode 100644 static/login/KJ.png create mode 100644 static/login/MM.png create mode 100644 static/login/ZH.png create mode 100644 static/login/bg.png diff --git a/package-lock.json b/package-lock.json index dbd2000..4460a20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,22 +13,358 @@ "amfe-flexible": "^2.2.1", "axios": "^1.6.2", "echarts": "^5.4.3", + "element-plus": "^2.4.3", + "pinia": "^2.1.7", "postcss-pxtorem": "^5.1.1", "vue": "^3.3.8", "vue-router": "^4.2.5" }, "devDependencies": { "@vitejs/plugin-vue": "^4.5.0", + "@vitejs/plugin-vue-jsx": "^3.1.0", "postcss-pxtorem": "^6.0.0", "sass": "^1.69.5", "sass-loader": "^13.3.2", "vite": "^5.0.0" } }, - "node_modules/@babel/parser": { + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.23.5.tgz", + "integrity": "sha512-Cwc2XjUrG4ilcfOw4wBAK+enbdgwAcAJCfGUItPBKR7Mjw4aEfAFYrLxeRp4jWgtNIKn3n2AlBOfwwafl+42/g==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.5", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.5", + "@babel/parser": "^7.23.5", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.5", + "@babel/types": "^7.23.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.23.5.tgz", + "integrity": "sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.15", + "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.15", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.5.tgz", + "integrity": "sha512-QELlRWxSpgdwdJzSJn4WAhKC+hvw/AtHbbrIoncKHkhKKR/luAlKkgBDcri1EzWAo8f8VvYVryEHN4tax/V67A==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmmirror.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.23.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.22.20", + "resolved": "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", + "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { "version": "7.23.4", - "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.23.4.tgz", - "integrity": "sha512-vf3Xna6UEprW+7t6EtOmFpHNAuxw3xqPZghy+brsnusscJRW5BMUzzHZc5ICjULee81WeUV2jjakG09MDglJXQ==", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.23.5.tgz", + "integrity": "sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.5", + "@babel/types": "^7.23.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmmirror.com/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.23.5.tgz", + "integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==", "bin": { "parser": "bin/babel-parser.js" }, @@ -36,6 +372,54 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", + "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", + "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.5.tgz", + "integrity": "sha512-2fMkXEJkrmwgu2Bsv1Saxgj30IXZdJ+84lQcKKI7sm719oXs0BBw2ZENKdJdR1PjWndgLCEBNXJOri0fk7RYQA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.23.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-typescript": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/runtime": { "version": "7.23.4", "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.23.4.tgz", @@ -47,6 +431,63 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.23.5.tgz", + "integrity": "sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.5", + "@babel/types": "^7.23.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.23.5.tgz", + "integrity": "sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "engines": { + "node": ">=10" + } + }, "node_modules/@dataview/datav-vue3": { "version": "0.0.0-test.1672506674342", "resolved": "https://registry.npmmirror.com/@dataview/datav-vue3/-/datav-vue3-0.0.0-test.1672506674342.tgz", @@ -60,6 +501,14 @@ "vue": ">=3.2.0" } }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz", + "integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==", + "peerDependencies": { + "vue": "^3.2.0" + } + }, "node_modules/@esbuild/android-arm": { "version": "0.19.7", "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.19.7.tgz", @@ -412,6 +861,28 @@ "node": ">=12" } }, + "node_modules/@floating-ui/core": { + "version": "1.5.2", + "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.5.2.tgz", + "integrity": "sha512-Ii3MrfY/GAIN3OhXNzpCKaLxHQfJF9qvwq/kEJYdqDxeIHa01K8sldugal6TmeeXl+WMvhv9cnVzUTaFFJF09A==", + "dependencies": { + "@floating-ui/utils": "^0.1.3" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.5.3", + "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.5.3.tgz", + "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", + "dependencies": { + "@floating-ui/core": "^1.4.2", + "@floating-ui/utils": "^0.1.3" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.1.6", + "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.1.6.tgz", + "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" + }, "node_modules/@jiaminghi/bezier-curve": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/@jiaminghi/bezier-curve/-/bezier-curve-0.0.9.tgz", @@ -470,7 +941,6 @@ "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "dev": true, - "peer": true, "dependencies": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -485,7 +955,6 @@ "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "dev": true, - "peer": true, "engines": { "node": ">=6.0.0" } @@ -495,7 +964,6 @@ "resolved": "https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.1.2.tgz", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", "dev": true, - "peer": true, "engines": { "node": ">=6.0.0" } @@ -521,12 +989,17 @@ "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", "dev": true, - "peer": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.7", + "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz", + "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==" + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.5.1", "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.5.1.tgz", @@ -719,6 +1192,19 @@ "dev": true, "peer": true }, + "node_modules/@types/lodash": { + "version": "4.14.202", + "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.202.tgz", + "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/node": { "version": "20.9.4", "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.9.4.tgz", @@ -729,6 +1215,11 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.16", + "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz", + "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==" + }, "node_modules/@vitejs/plugin-vue": { "version": "4.5.0", "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-4.5.0.tgz", @@ -742,6 +1233,50 @@ "vue": "^3.2.25" } }, + "node_modules/@vitejs/plugin-vue-jsx": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue-jsx/-/plugin-vue-jsx-3.1.0.tgz", + "integrity": "sha512-w9M6F3LSEU5kszVb9An2/MmXNxocAnUb3WhRr8bHlimhDrXNt6n6D2nJQR3UXpGlZHh/EsgouOHCsM8V3Ln+WA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.3", + "@babel/plugin-transform-typescript": "^7.23.3", + "@vue/babel-plugin-jsx": "^1.1.5" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.0.0 || ^5.0.0", + "vue": "^3.0.0" + } + }, + "node_modules/@vue/babel-helper-vue-transform-on": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.1.5.tgz", + "integrity": "sha512-SgUymFpMoAyWeYWLAY+MkCK3QEROsiUnfaw5zxOVD/M64KQs8D/4oK6Q5omVA2hnvEOE0SCkH2TZxs/jnnUj7w==", + "dev": true + }, + "node_modules/@vue/babel-plugin-jsx": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.1.5.tgz", + "integrity": "sha512-nKs1/Bg9U1n3qSWnsHhCVQtAzI6aQXqua8j/bZrau8ywT1ilXQbK4FwEJGmU8fV7tcpuFvWmmN7TMmV1OBma1g==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5", + "@vue/babel-helper-vue-transform-on": "^1.1.5", + "camelcase": "^6.3.0", + "html-tags": "^3.3.1", + "svg-tags": "^1.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@vue/compiler-core": { "version": "3.3.8", "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.3.8.tgz", @@ -849,6 +1384,74 @@ "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.3.8.tgz", "integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==" }, + "node_modules/@vueuse/core": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz", + "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==", + "dependencies": { + "@types/web-bluetooth": "^0.0.16", + "@vueuse/metadata": "9.13.0", + "@vueuse/shared": "9.13.0", + "vue-demi": "*" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.6", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.6.tgz", + "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz", + "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==" + }, + "node_modules/@vueuse/shared": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz", + "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==", + "dependencies": { + "vue-demi": "*" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.6", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.6.tgz", + "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.6", "resolved": "https://registry.npmmirror.com/@webassemblyjs/ast/-/ast-1.11.6.tgz", @@ -1075,6 +1678,18 @@ "resolved": "https://registry.npmmirror.com/amfe-flexible/-/amfe-flexible-2.2.1.tgz", "integrity": "sha512-L2VfvDzoETBjhRptg5u/IUuzHSuxm22JpSRb404p/TBGeRfwWmmNEbB+TFPIP/sS/+pbM18bCFH9QnMojLuPNw==" }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", @@ -1088,6 +1703,11 @@ "node": ">= 8" } }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", @@ -1129,7 +1749,6 @@ "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.22.1.tgz", "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", "dev": true, - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001541", "electron-to-chromium": "^1.4.535", @@ -1150,12 +1769,55 @@ "dev": true, "peer": true }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001564", "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001564.tgz", "integrity": "sha512-DqAOf+rhof+6GVx1y+xzbFPeOumfQnhYzVnZD6LAXijR77yPtm9mfOcqOnT3mpnJiZVT+kwLAFnRlZcIz+c6bg==", + "dev": true + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, - "peer": true + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } }, "node_modules/chokidar": { "version": "3.5.3", @@ -1193,6 +1855,21 @@ "resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.3.2.tgz", "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1211,11 +1888,39 @@ "dev": true, "peer": true }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, "node_modules/csstype": { "version": "3.1.2", "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.2.tgz", "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1237,8 +1942,32 @@ "version": "1.4.590", "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.590.tgz", "integrity": "sha512-hohItzsQcG7/FBsviCYMtQwUSWvVF7NVqPOnJCErWsAshsP/CR2LAXdmq276RbESNdhxiAq5/vRo1g2pxGXVww==", - "dev": true, - "peer": true + "dev": true + }, + "node_modules/element-plus": { + "version": "2.4.3", + "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.4.3.tgz", + "integrity": "sha512-b3q26j+lM4SBqiyzw8HybybGnP2pk4MWgrnzzzYW5qKQUgV6EG1Zg7nMCfgCVccI8tNvZoTiUHb2mFaiB9qT8w==", + "dependencies": { + "@ctrl/tinycolor": "^3.4.1", + "@element-plus/icons-vue": "^2.3.1", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", + "@types/lodash": "^4.14.182", + "@types/lodash-es": "^4.17.6", + "@vueuse/core": "^9.1.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.3", + "escape-html": "^1.0.3", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "lodash-unified": "^1.0.2", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0" + }, + "peerDependencies": { + "vue": "^3.2.0" + } }, "node_modules/enhanced-resolve": { "version": "5.15.0", @@ -1303,11 +2032,24 @@ "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true, - "peer": true, "engines": { "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -1436,6 +2178,15 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", @@ -1455,6 +2206,15 @@ "dev": true, "peer": true }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -1472,6 +2232,15 @@ "node": ">=8" } }, + "node_modules/html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/immutable": { "version": "4.3.4", "resolved": "https://registry.npmmirror.com/immutable/-/immutable-4.3.4.tgz", @@ -1535,6 +2304,24 @@ "node": ">= 10.13.0" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -1549,6 +2336,18 @@ "dev": true, "peer": true }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmmirror.com/loader-runner/-/loader-runner-4.3.0.tgz", @@ -1559,11 +2358,35 @@ "node": ">=6.11.5" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash-es": { "version": "4.17.21", "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/magic-string": { "version": "0.30.5", "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.5.tgz", @@ -1575,6 +2398,11 @@ "node": ">=12" } }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz", @@ -1601,6 +2429,12 @@ "node": ">= 0.6" } }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz", @@ -1622,8 +2456,7 @@ "version": "2.0.13", "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.13.tgz", "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/normalize-path": { "version": "3.0.0", @@ -1634,6 +2467,11 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==" + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz", @@ -1648,6 +2486,50 @@ "node": ">=8.6" } }, + "node_modules/pinia": { + "version": "2.1.7", + "resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.1.7.tgz", + "integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==", + "dependencies": { + "@vue/devtools-api": "^6.5.0", + "vue-demi": ">=0.14.5" + }, + "peerDependencies": { + "@vue/composition-api": "^1.4.0", + "typescript": ">=4.4.4", + "vue": "^2.6.14 || ^3.3.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/pinia/node_modules/vue-demi": { + "version": "0.14.6", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.6.tgz", + "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.31.tgz", @@ -1812,6 +2694,15 @@ "node": ">= 10.13.0" } }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/serialize-javascript": { "version": "6.0.1", "resolved": "https://registry.npmmirror.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz", @@ -1864,6 +2755,12 @@ "node": ">=10" } }, + "node_modules/svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "dev": true + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmmirror.com/tapable/-/tapable-2.2.1.tgz", @@ -1924,6 +2821,15 @@ } } }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -1953,7 +2859,6 @@ "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", "dev": true, - "peer": true, "dependencies": { "escalade": "^3.1.1", "picocolors": "^1.0.0" @@ -2126,6 +3031,12 @@ "node": ">=10.13.0" } }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, "node_modules/zrender": { "version": "5.4.4", "resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.4.4.tgz", @@ -2136,10 +3047,289 @@ } }, "dependencies": { - "@babel/parser": { + "@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + } + }, + "@babel/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "dev": true + }, + "@babel/core": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.23.5.tgz", + "integrity": "sha512-Cwc2XjUrG4ilcfOw4wBAK+enbdgwAcAJCfGUItPBKR7Mjw4aEfAFYrLxeRp4jWgtNIKn3n2AlBOfwwafl+42/g==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.5", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.5", + "@babel/parser": "^7.23.5", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.5", + "@babel/types": "^7.23.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + } + }, + "@babel/generator": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.23.5.tgz", + "integrity": "sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==", + "dev": true, + "requires": { + "@babel/types": "^7.23.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.22.15", + "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.15", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.5.tgz", + "integrity": "sha512-QELlRWxSpgdwdJzSJn4WAhKC+hvw/AtHbbrIoncKHkhKKR/luAlKkgBDcri1EzWAo8f8VvYVryEHN4tax/V67A==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" + } + }, + "@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmmirror.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true + }, + "@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "requires": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.23.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "dev": true, + "requires": { + "@babel/types": "^7.23.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "requires": { + "@babel/types": "^7.22.15" + } + }, + "@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true + }, + "@babel/helper-replace-supers": { + "version": "7.22.20", + "resolved": "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", + "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5" + } + }, + "@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-string-parser": { "version": "7.23.4", - "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.23.4.tgz", - "integrity": "sha512-vf3Xna6UEprW+7t6EtOmFpHNAuxw3xqPZghy+brsnusscJRW5BMUzzHZc5ICjULee81WeUV2jjakG09MDglJXQ==" + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true + }, + "@babel/helpers": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.23.5.tgz", + "integrity": "sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg==", + "dev": true, + "requires": { + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.5", + "@babel/types": "^7.23.5" + } + }, + "@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmmirror.com/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.23.5.tgz", + "integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==" + }, + "@babel/plugin-syntax-jsx": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", + "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.23.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", + "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-typescript": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.5.tgz", + "integrity": "sha512-2fMkXEJkrmwgu2Bsv1Saxgj30IXZdJ+84lQcKKI7sm719oXs0BBw2ZENKdJdR1PjWndgLCEBNXJOri0fk7RYQA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.23.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-typescript": "^7.23.3" + } }, "@babel/runtime": { "version": "7.23.4", @@ -2149,6 +3339,51 @@ "regenerator-runtime": "^0.14.0" } }, + "@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + } + }, + "@babel/traverse": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.23.5.tgz", + "integrity": "sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.5", + "@babel/types": "^7.23.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.23.5", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.23.5.tgz", + "integrity": "sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + } + }, + "@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==" + }, "@dataview/datav-vue3": { "version": "0.0.0-test.1672506674342", "resolved": "https://registry.npmmirror.com/@dataview/datav-vue3/-/datav-vue3-0.0.0-test.1672506674342.tgz", @@ -2159,6 +3394,12 @@ "lodash-es": "^4.17.21" } }, + "@element-plus/icons-vue": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz", + "integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==", + "requires": {} + }, "@esbuild/android-arm": { "version": "0.19.7", "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.19.7.tgz", @@ -2313,6 +3554,28 @@ "dev": true, "optional": true }, + "@floating-ui/core": { + "version": "1.5.2", + "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.5.2.tgz", + "integrity": "sha512-Ii3MrfY/GAIN3OhXNzpCKaLxHQfJF9qvwq/kEJYdqDxeIHa01K8sldugal6TmeeXl+WMvhv9cnVzUTaFFJF09A==", + "requires": { + "@floating-ui/utils": "^0.1.3" + } + }, + "@floating-ui/dom": { + "version": "1.5.3", + "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.5.3.tgz", + "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", + "requires": { + "@floating-ui/core": "^1.4.2", + "@floating-ui/utils": "^0.1.3" + } + }, + "@floating-ui/utils": { + "version": "0.1.6", + "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.1.6.tgz", + "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" + }, "@jiaminghi/bezier-curve": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/@jiaminghi/bezier-curve/-/bezier-curve-0.0.9.tgz", @@ -2371,7 +3634,6 @@ "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "dev": true, - "peer": true, "requires": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -2382,15 +3644,13 @@ "version": "3.1.1", "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true, - "peer": true + "dev": true }, "@jridgewell/set-array": { "version": "1.1.2", "resolved": "https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.1.2.tgz", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "peer": true + "dev": true }, "@jridgewell/source-map": { "version": "0.3.5", @@ -2413,12 +3673,16 @@ "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", "dev": true, - "peer": true, "requires": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "@popperjs/core": { + "version": "npm:@sxzz/popperjs-es@2.11.7", + "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz", + "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==" + }, "@rollup/rollup-android-arm-eabi": { "version": "4.5.1", "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.5.1.tgz", @@ -2539,6 +3803,19 @@ "dev": true, "peer": true }, + "@types/lodash": { + "version": "4.14.202", + "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.202.tgz", + "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==" + }, + "@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "requires": { + "@types/lodash": "*" + } + }, "@types/node": { "version": "20.9.4", "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.9.4.tgz", @@ -2549,6 +3826,11 @@ "undici-types": "~5.26.4" } }, + "@types/web-bluetooth": { + "version": "0.0.16", + "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz", + "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==" + }, "@vitejs/plugin-vue": { "version": "4.5.0", "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-4.5.0.tgz", @@ -2556,6 +3838,40 @@ "dev": true, "requires": {} }, + "@vitejs/plugin-vue-jsx": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue-jsx/-/plugin-vue-jsx-3.1.0.tgz", + "integrity": "sha512-w9M6F3LSEU5kszVb9An2/MmXNxocAnUb3WhRr8bHlimhDrXNt6n6D2nJQR3UXpGlZHh/EsgouOHCsM8V3Ln+WA==", + "dev": true, + "requires": { + "@babel/core": "^7.23.3", + "@babel/plugin-transform-typescript": "^7.23.3", + "@vue/babel-plugin-jsx": "^1.1.5" + } + }, + "@vue/babel-helper-vue-transform-on": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.1.5.tgz", + "integrity": "sha512-SgUymFpMoAyWeYWLAY+MkCK3QEROsiUnfaw5zxOVD/M64KQs8D/4oK6Q5omVA2hnvEOE0SCkH2TZxs/jnnUj7w==", + "dev": true + }, + "@vue/babel-plugin-jsx": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.1.5.tgz", + "integrity": "sha512-nKs1/Bg9U1n3qSWnsHhCVQtAzI6aQXqua8j/bZrau8ywT1ilXQbK4FwEJGmU8fV7tcpuFvWmmN7TMmV1OBma1g==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5", + "@vue/babel-helper-vue-transform-on": "^1.1.5", + "camelcase": "^6.3.0", + "html-tags": "^3.3.1", + "svg-tags": "^1.0.0" + } + }, "@vue/compiler-core": { "version": "3.3.8", "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.3.8.tgz", @@ -2660,6 +3976,46 @@ "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.3.8.tgz", "integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==" }, + "@vueuse/core": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz", + "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==", + "requires": { + "@types/web-bluetooth": "^0.0.16", + "@vueuse/metadata": "9.13.0", + "@vueuse/shared": "9.13.0", + "vue-demi": "*" + }, + "dependencies": { + "vue-demi": { + "version": "0.14.6", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.6.tgz", + "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "requires": {} + } + } + }, + "@vueuse/metadata": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz", + "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==" + }, + "@vueuse/shared": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz", + "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==", + "requires": { + "vue-demi": "*" + }, + "dependencies": { + "vue-demi": { + "version": "0.14.6", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.6.tgz", + "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "requires": {} + } + } + }, "@webassemblyjs/ast": { "version": "1.11.6", "resolved": "https://registry.npmmirror.com/@webassemblyjs/ast/-/ast-1.11.6.tgz", @@ -2876,6 +4232,15 @@ "resolved": "https://registry.npmmirror.com/amfe-flexible/-/amfe-flexible-2.2.1.tgz", "integrity": "sha512-L2VfvDzoETBjhRptg5u/IUuzHSuxm22JpSRb404p/TBGeRfwWmmNEbB+TFPIP/sS/+pbM18bCFH9QnMojLuPNw==" }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, "anymatch": { "version": "3.1.3", "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", @@ -2886,6 +4251,11 @@ "picomatch": "^2.0.4" } }, + "async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==" + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", @@ -2921,7 +4291,6 @@ "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.22.1.tgz", "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", "dev": true, - "peer": true, "requires": { "caniuse-lite": "^1.0.30001541", "electron-to-chromium": "^1.4.535", @@ -2936,12 +4305,45 @@ "dev": true, "peer": true }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, "caniuse-lite": { "version": "1.0.30001564", "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001564.tgz", "integrity": "sha512-DqAOf+rhof+6GVx1y+xzbFPeOumfQnhYzVnZD6LAXijR77yPtm9mfOcqOnT3mpnJiZVT+kwLAFnRlZcIz+c6bg==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, - "peer": true + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } }, "chokidar": { "version": "3.5.3", @@ -2971,6 +4373,21 @@ "resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.3.2.tgz", "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2986,11 +4403,31 @@ "dev": true, "peer": true }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, "csstype": { "version": "3.1.2", "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.2.tgz", "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, + "dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -3009,8 +4446,29 @@ "version": "1.4.590", "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.590.tgz", "integrity": "sha512-hohItzsQcG7/FBsviCYMtQwUSWvVF7NVqPOnJCErWsAshsP/CR2LAXdmq276RbESNdhxiAq5/vRo1g2pxGXVww==", - "dev": true, - "peer": true + "dev": true + }, + "element-plus": { + "version": "2.4.3", + "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.4.3.tgz", + "integrity": "sha512-b3q26j+lM4SBqiyzw8HybybGnP2pk4MWgrnzzzYW5qKQUgV6EG1Zg7nMCfgCVccI8tNvZoTiUHb2mFaiB9qT8w==", + "requires": { + "@ctrl/tinycolor": "^3.4.1", + "@element-plus/icons-vue": "^2.3.1", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", + "@types/lodash": "^4.14.182", + "@types/lodash-es": "^4.17.6", + "@vueuse/core": "^9.1.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.3", + "escape-html": "^1.0.3", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "lodash-unified": "^1.0.2", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0" + } }, "enhanced-resolve": { "version": "5.15.0", @@ -3064,8 +4522,18 @@ "version": "3.1.1", "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "peer": true + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true }, "eslint-scope": { "version": "5.1.1", @@ -3161,6 +4629,12 @@ "dev": true, "optional": true }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, "glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", @@ -3177,6 +4651,12 @@ "dev": true, "peer": true }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, "graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -3191,6 +4671,12 @@ "dev": true, "peer": true }, + "html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "dev": true + }, "immutable": { "version": "4.3.4", "resolved": "https://registry.npmmirror.com/immutable/-/immutable-4.3.4.tgz", @@ -3239,6 +4725,18 @@ "supports-color": "^8.0.0" } }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -3253,6 +4751,12 @@ "dev": true, "peer": true }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, "loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmmirror.com/loader-runner/-/loader-runner-4.3.0.tgz", @@ -3260,11 +4764,31 @@ "dev": true, "peer": true }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "lodash-es": { "version": "4.17.21", "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, + "lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "requires": {} + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, "magic-string": { "version": "0.30.5", "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.5.tgz", @@ -3273,6 +4797,11 @@ "@jridgewell/sourcemap-codec": "^1.4.15" } }, + "memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz", @@ -3293,6 +4822,12 @@ "mime-db": "1.52.0" } }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "nanoid": { "version": "3.3.7", "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz", @@ -3308,8 +4843,7 @@ "version": "2.0.13", "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.13.tgz", "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", - "dev": true, - "peer": true + "dev": true }, "normalize-path": { "version": "3.0.0", @@ -3317,6 +4851,11 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==" + }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz", @@ -3328,6 +4867,23 @@ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, + "pinia": { + "version": "2.1.7", + "resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.1.7.tgz", + "integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==", + "requires": { + "@vue/devtools-api": "^6.5.0", + "vue-demi": ">=0.14.5" + }, + "dependencies": { + "vue-demi": { + "version": "0.14.6", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.6.tgz", + "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "requires": {} + } + } + }, "postcss": { "version": "8.4.31", "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.31.tgz", @@ -3441,6 +4997,12 @@ "ajv-keywords": "^3.5.2" } }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + }, "serialize-javascript": { "version": "6.0.1", "resolved": "https://registry.npmmirror.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz", @@ -3484,6 +5046,12 @@ "has-flag": "^4.0.0" } }, + "svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "dev": true + }, "tapable": { "version": "2.2.1", "resolved": "https://registry.npmmirror.com/tapable/-/tapable-2.2.1.tgz", @@ -3518,6 +5086,12 @@ "terser": "^5.16.8" } }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true + }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3544,7 +5118,6 @@ "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", "dev": true, - "peer": true, "requires": { "escalade": "^3.1.1", "picocolors": "^1.0.0" @@ -3643,6 +5216,12 @@ "dev": true, "peer": true }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, "zrender": { "version": "5.4.4", "resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.4.4.tgz", diff --git a/package.json b/package.json index 4f506d6..0a1f430 100644 --- a/package.json +++ b/package.json @@ -14,12 +14,15 @@ "amfe-flexible": "^2.2.1", "axios": "^1.6.2", "echarts": "^5.4.3", + "element-plus": "^2.4.3", + "pinia": "^2.1.7", "postcss-pxtorem": "^5.1.1", "vue": "^3.3.8", "vue-router": "^4.2.5" }, "devDependencies": { "@vitejs/plugin-vue": "^4.5.0", + "@vitejs/plugin-vue-jsx": "^3.1.0", "postcss-pxtorem": "^6.0.0", "sass": "^1.69.5", "sass-loader": "^13.3.2", diff --git a/src/App.vue b/src/App.vue index a962a49..4025011 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,15 +1,10 @@ diff --git a/src/api.js b/src/api.js index c5e9d9b..166065d 100644 --- a/src/api.js +++ b/src/api.js @@ -1,5 +1,6 @@ import axios from "axios"; +import { ElMessage } from 'element-plus' // 创建axios 实例 const instacne = axios.create({ baseURL: "https://crmeb-test.shop.lihaink.cn/", @@ -21,6 +22,16 @@ instacne.interceptors.request.use( // 响应拦截 instacne.interceptors.response.use( (res) => { + + console.log(res.data) + if (res.data.status != 200) { + ElMessage({ + message: res.data.message, + type: 'warning', + }) + return Promise.reject(res.data.message); + + } // 对响应的数据做什么 return res.data; }, @@ -92,6 +103,48 @@ export function take_order_listApi(params) { export function take_order_count_titleApi(params) { return instacne.get('/api/dataview/take_order_count_title', { params }) } -// -// /api/dataview/take_order_count_title?areaCode=510524&streetCode=510524100&status&page=1&limit=20 + +export function financial_record_titleApi(params) { + return instacne.get('/api/dataview/financial_record_title', { params }) +} + + + +export function financial_recordApi(params) { + return instacne.get('/api/dataview/financial_record', { params }) +} + +export function product_status_filterApi(params) { + return instacne.get('/api/dataview/product_status_filter', { params }) +} + + +export function financial_record_detailApi(params) { + return instacne.get('/api/dataview/financial_record_detail/1', { params }) +} +export function financial_record_detailApi2(params) { + return instacne.get('/api/dataview/financial_record_detail/2', { params }) +} + + + +export function bill_listApi(params) { + return instacne.get('/api/dataview/bill_list', { params }) +} + +export function withdraw_listApi(params) { + return instacne.get('/api/dataview/withdraw_list', { params }) +} + +export function street_currday_order_countApi(params) { + return instacne.get('/api/dataview/street_currday_order_count', { params }) +} +export function loginApi(params) { + return instacne.post('/api/dataview/login', params) +} +// +////api/dataview/login { +//     "account": "泸县", +//     "password": "luxian" +// } diff --git a/src/components/header.vue b/src/components/header.vue index cf9d444..9343fa0 100644 --- a/src/components/header.vue +++ b/src/components/header.vue @@ -82,6 +82,7 @@ import MapLoader from "@/view/utils/position.js" + let area = ref('') const u = (name) => { @@ -154,7 +155,8 @@ const offAreaList = (name) => { } const out = () => { //@ts-ignore - window.open("about:blank", "_top").close() + // window.open("about:blank", "_top").close() + router.replace('/login') } const updateTime = () => { var date = new Date(); diff --git a/src/components/index/bottomCenter.vue b/src/components/index/bottomCenter.vue index 55e0958..de6536c 100644 --- a/src/components/index/bottomCenter.vue +++ b/src/components/index/bottomCenter.vue @@ -4,44 +4,80 @@
{{ area.area_name }}
-
{{ item.street_name }}
-
+ +
{{ item.street_name }} +
+ + +
+ + +
+ - - + + + \ No newline at end of file diff --git a/src/components/townDetail/test.js b/src/components/townDetail/test.js deleted file mode 100644 index 88257bb..0000000 --- a/src/components/townDetail/test.js +++ /dev/null @@ -1,26408 +0,0 @@ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.jessibuca = factory()); -})(this, (function () { - - - var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; - - function unwrapExports(x) { - return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; - } - - function createCommonjsModule(fn, module) { - return module = { exports: {} }, fn(module, module.exports), module.exports; - } - - var defineProperty = createCommonjsModule(function (module) { - function _defineProperty(obj, key, value) { - if (key in obj) { - Object.defineProperty(obj, key, { - value: value, - enumerable: true, - configurable: true, - writable: true - }); - } else { - obj[key] = value; - } - - return obj; - } - - module.exports = _defineProperty, module.exports.__esModule = true, module.exports["default"] = module.exports; - }); - - var _defineProperty = unwrapExports(defineProperty); - - // 播放协议 - const PLAYER_PLAY_PROTOCOL = { - websocket: 0, - fetch: 1, - webrtc: 2 - }; - const DEMUX_TYPE = { - flv: 'flv', - m7s: 'm7s' - }; - const FILE_SUFFIX = { - mp4: 'mp4', - webm: 'webm' - }; - - const DEFAULT_PLAYER_OPTIONS = { - videoBuffer: 1000, - //1000ms 1 second - videoBufferDelay: 1000, - // 1000ms - isResize: true, - isFullResize: false, - // - isFlv: false, - debug: false, - hotKey: false, - // 快捷键 - loadingTimeout: 10, - // loading timeout - heartTimeout: 5, - // heart timeout - timeout: 10, - // second - loadingTimeoutReplay: true, - // loading timeout replay. default is true - heartTimeoutReplay: true, - // heart timeout replay. - loadingTimeoutReplayTimes: 3, - // loading timeout replay fail times - heartTimeoutReplayTimes: 3, - // heart timeout replay fail times - supportDblclickFullscreen: false, - // support double click toggle fullscreen - showBandwidth: false, - // show band width - keepScreenOn: false, - // - isNotMute: false, - // - hasAudio: true, - // has audio - hasVideo: true, - // has video - operateBtns: { - fullscreen: false, - screenshot: false, - play: false, - audio: false, - record: false - }, - controlAutoHide: false, - // control auto hide - hasControl: false, - loadingText: '', - // loading Text - background: '', - decoder: 'decoder.js', - url: '', - // play url - rotate: 0, - // - // text: '', - forceNoOffscreen: true, - // 默认是不采用 - hiddenAutoPause: false, - // - protocol: PLAYER_PLAY_PROTOCOL.fetch, - demuxType: DEMUX_TYPE.flv, - // demux type - useWCS: false, - // - wcsUseVideoRender: true, - // 默认设置为true - useMSE: false, - // - useOffscreen: false, - // - autoWasm: true, - // 自动降级到 wasm 模式 - wasmDecodeErrorReplay: true, - // 解码失败重新播放。 - openWebglAlignment: false, - // https://github.com/langhuihui/jessibuca/issues/152 - wasmDecodeAudioSyncVideo: false, - // wasm 解码之后音视频同步 - recordType: FILE_SUFFIX.webm, - useWebFullScreen: false // use web full screen - - }; - const WORKER_CMD_TYPE = { - init: 'init', - initVideo: 'initVideo', - render: 'render', - playAudio: 'playAudio', - initAudio: 'initAudio', - kBps: 'kBps', - decode: 'decode', - audioCode: 'audioCode', - videoCode: 'videoCode', - wasmError: 'wasmError' - }; - const WASM_ERROR = { - invalidNalUnitSize: 'Invalid NAL unit size' // errorSplittingTheInputIntoNALUnits: 'Error splitting the input into NAL units' - - }; - const MEDIA_TYPE = { - audio: 1, - video: 2 - }; - const FLV_MEDIA_TYPE = { - audio: 8, - video: 9 - }; - const WORKER_SEND_TYPE = { - init: 'init', - decode: 'decode', - audioDecode: 'audioDecode', - videoDecode: 'videoDecode', - close: 'close', - updateConfig: 'updateConfig' - }; // - - const EVENTS = { - fullscreen: 'fullscreen$2', - webFullscreen: 'webFullscreen', - decoderWorkerInit: 'decoderWorkerInit', - play: 'play', - playing: 'playing', - pause: 'pause', - mute: 'mute', - load: 'load', - loading: 'loading', - videoInfo: 'videoInfo', - timeUpdate: 'timeUpdate', - audioInfo: "audioInfo", - log: 'log', - error: "error", - kBps: 'kBps', - timeout: 'timeout', - delayTimeout: 'delayTimeout', - loadingTimeout: 'loadingTimeout', - stats: 'stats', - performance: "performance", - record: 'record', - recording: 'recording', - recordingTimestamp: 'recordingTimestamp', - recordStart: 'recordStart', - recordEnd: 'recordEnd', - recordCreateError: 'recordCreateError', - buffer: 'buffer', - videoFrame: 'videoFrame', - start: 'start', - metadata: 'metadata', - resize: 'resize', - streamEnd: 'streamEnd', - streamSuccess: 'streamSuccess', - streamMessage: 'streamMessage', - streamError: 'streamError', - volumechange: 'volumechange', - destroy: 'destroy', - mseSourceOpen: 'mseSourceOpen', - mseSourceClose: 'mseSourceClose', - mseSourceBufferError: 'mseSourceBufferError', - mseSourceBufferBusy: 'mseSourceBufferBusy', - mseSourceBufferFull: 'mseSourceBufferFull', - videoWaiting: 'videoWaiting', - videoTimeUpdate: 'videoTimeUpdate', - videoSyncAudio: 'videoSyncAudio', - playToRenderTimes: 'playToRenderTimes' - }; - const JESSIBUCA_EVENTS = { - load: EVENTS.load, - timeUpdate: EVENTS.timeUpdate, - videoInfo: EVENTS.videoInfo, - audioInfo: EVENTS.audioInfo, - error: EVENTS.error, - kBps: EVENTS.kBps, - log: EVENTS.log, - start: EVENTS.start, - timeout: EVENTS.timeout, - loadingTimeout: EVENTS.loadingTimeout, - delayTimeout: EVENTS.delayTimeout, - fullscreen: 'fullscreen', - webFullscreen: EVENTS.webFullscreen, - play: EVENTS.play, - pause: EVENTS.pause, - mute: EVENTS.mute, - stats: EVENTS.stats, - volumechange: EVENTS.volumechange, - performance: EVENTS.performance, - recordingTimestamp: EVENTS.recordingTimestamp, - recordStart: EVENTS.recordStart, - recordEnd: EVENTS.recordEnd, - playToRenderTimes: EVENTS.playToRenderTimes - }; - const EVENTS_ERROR = { - playError: 'playIsNotPauseOrUrlIsNull', - fetchError: "fetchError", - websocketError: 'websocketError', - webcodecsH265NotSupport: 'webcodecsH265NotSupport', - webcodecsDecodeError: 'webcodecsDecodeError', - webcodecsWidthOrHeightChange: 'webcodecsWidthOrHeightChange', - mediaSourceH265NotSupport: 'mediaSourceH265NotSupport', - mediaSourceFull: EVENTS.mseSourceBufferFull, - mseSourceBufferError: EVENTS.mseSourceBufferError, - mediaSourceAppendBufferError: 'mediaSourceAppendBufferError', - mediaSourceBufferListLarge: 'mediaSourceBufferListLarge', - mediaSourceAppendBufferEndTimeout: 'mediaSourceAppendBufferEndTimeout', - wasmDecodeError: 'wasmDecodeError', - webglAlignmentError: 'webglAlignmentError' - }; - const WEBSOCKET_STATUS = { - notConnect: 'notConnect', - open: 'open', - close: 'close', - error: 'error' - }; - const SCREENSHOT_TYPE = { - download: 'download', - base64: 'base64', - blob: 'blob' - }; - const VIDEO_ENC_TYPE = { - 7: 'H264(AVC)', - // - 12: 'H265(HEVC)' // - - }; - const VIDEO_ENC_CODE = { - h264: 7, - h265: 12 - }; - const AUDIO_ENC_TYPE = { - 10: 'AAC', - 7: 'ALAW', - 8: 'MULAW' - }; - const CONTROL_HEIGHT = 38; - const SCALE_MODE_TYPE = { - full: 0, - // 视频画面完全填充canvas区域,画面会被拉伸 - auto: 1, - // 视频画面做等比缩放后,高或宽对齐canvas区域,画面不被拉伸,但有黑边 - fullAuto: 2 // 视频画面做等比缩放后,完全填充canvas区域,画面不被拉伸,没有黑边,但画面显示不全 - - }; - const CANVAS_RENDER_TYPE = { - webcodecs: 'webcodecs', - webgl: 'webgl', - offscreen: 'offscreen' - }; - const ENCODED_VIDEO_TYPE = { - key: 'key', - delta: 'delta' - }; - const MP4_CODECS = { - avc: 'video/mp4; codecs="avc1.64002A"', - hev: 'video/mp4; codecs="hev1.1.6.L123.b0"' - }; - const MEDIA_SOURCE_STATE = { - ended: 'ended', - open: 'open', - closed: 'closed' - }; // frag duration - const AUDIO_SYNC_VIDEO_DIFF = 1000; - const HOT_KEY = { - esc: 27, - // - arrowUp: 38, - // - arrowDown: 40 // - - }; - const WCS_ERROR = { - keyframeIsRequiredError: 'A key frame is required after configure() or flush()', - canNotDecodeClosedCodec: "Cannot call 'decode' on a closed codec" - }; - const FETCH_ERROR = { - abortError1: 'The user aborted a request', - abortError2: 'AbortError', - abort: 'AbortError' - }; - - class Debug { - constructor(master) { - this.log = function (name) { - if (master._opt.debug) { - for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - args[_key - 1] = arguments[_key]; - } - - console.log(`Jessibuca: [${name}]`, ...args); - } - }; - - this.warn = function (name) { - if (master._opt.debug) { - for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { - args[_key2 - 1] = arguments[_key2]; - } - - console.warn(`Jessibuca: [${name}]`, ...args); - } - }; - - this.error = function (name) { - for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) { - args[_key3 - 1] = arguments[_key3]; - } - - console.error(`Jessibuca: [${name}]`, ...args); - }; - } - - } - - class Events { - constructor(master) { - this.destroys = []; - this.proxy = this.proxy.bind(this); - this.master = master; - } - - proxy(target, name, callback) { - let option = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; - - if (!target) { - return; - } - - if (Array.isArray(name)) { - return name.map(item => this.proxy(target, item, callback, option)); - } - - target.addEventListener(name, callback, option); - - const destroy = () => target.removeEventListener(name, callback, option); - - this.destroys.push(destroy); - return destroy; - } - - destroy() { - this.master.debug && this.master.debug.log(`Events`, 'destroy'); - this.destroys.forEach(event => event()); - } - - } - - var property$1 = (player => { - Object.defineProperty(player, 'rect', { - get: () => { - const clientRect = player.$container.getBoundingClientRect(); - clientRect.width = Math.max(clientRect.width, player.$container.clientWidth); - clientRect.height = Math.max(clientRect.height, player.$container.clientHeight); - return clientRect; - } - }); - ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(key => { - Object.defineProperty(player, key, { - get: () => { - return player.rect[key]; - } - }); - }); - }); - - var screenfull = createCommonjsModule(function (module) { - /*! - * screenfull - * v5.1.0 - 2020-12-24 - * (c) Sindre Sorhus; MIT License - */ - (function () { - - var document = typeof window !== 'undefined' && typeof window.document !== 'undefined' ? window.document : {}; - var isCommonjs = module.exports; - - var fn = (function () { - var val; - - var fnMap = [ - [ - 'requestFullscreen', - 'exitFullscreen', - 'fullscreenElement', - 'fullscreenEnabled', - 'fullscreenchange', - 'fullscreenerror' - ], - // New WebKit - [ - 'webkitRequestFullscreen', - 'webkitExitFullscreen', - 'webkitFullscreenElement', - 'webkitFullscreenEnabled', - 'webkitfullscreenchange', - 'webkitfullscreenerror' - - ], - // Old WebKit - [ - 'webkitRequestFullScreen', - 'webkitCancelFullScreen', - 'webkitCurrentFullScreenElement', - 'webkitCancelFullScreen', - 'webkitfullscreenchange', - 'webkitfullscreenerror' - - ], - [ - 'mozRequestFullScreen', - 'mozCancelFullScreen', - 'mozFullScreenElement', - 'mozFullScreenEnabled', - 'mozfullscreenchange', - 'mozfullscreenerror' - ], - [ - 'msRequestFullscreen', - 'msExitFullscreen', - 'msFullscreenElement', - 'msFullscreenEnabled', - 'MSFullscreenChange', - 'MSFullscreenError' - ] - ]; - - var i = 0; - var l = fnMap.length; - var ret = {}; - - for (; i < l; i++) { - val = fnMap[i]; - if (val && val[1] in document) { - for (i = 0; i < val.length; i++) { - ret[fnMap[0][i]] = val[i]; - } - return ret; - } - } - - return false; - })(); - - var eventNameMap = { - change: fn.fullscreenchange, - error: fn.fullscreenerror - }; - - var screenfull = { - request: function (element, options) { - return new Promise(function (resolve, reject) { - var onFullScreenEntered = function () { - this.off('change', onFullScreenEntered); - resolve(); - }.bind(this); - - this.on('change', onFullScreenEntered); - - element = element || document.documentElement; - - var returnPromise = element[fn.requestFullscreen](options); - - if (returnPromise instanceof Promise) { - returnPromise.then(onFullScreenEntered).catch(reject); - } - }.bind(this)); - }, - exit: function () { - return new Promise(function (resolve, reject) { - if (!this.isFullscreen) { - resolve(); - return; - } - - var onFullScreenExit = function () { - this.off('change', onFullScreenExit); - resolve(); - }.bind(this); - - this.on('change', onFullScreenExit); - - var returnPromise = document[fn.exitFullscreen](); - - if (returnPromise instanceof Promise) { - returnPromise.then(onFullScreenExit).catch(reject); - } - }.bind(this)); - }, - toggle: function (element, options) { - return this.isFullscreen ? this.exit() : this.request(element, options); - }, - onchange: function (callback) { - this.on('change', callback); - }, - onerror: function (callback) { - this.on('error', callback); - }, - on: function (event, callback) { - var eventName = eventNameMap[event]; - if (eventName) { - document.addEventListener(eventName, callback, false); - } - }, - off: function (event, callback) { - var eventName = eventNameMap[event]; - if (eventName) { - document.removeEventListener(eventName, callback, false); - } - }, - raw: fn - }; - - if (!fn) { - if (isCommonjs) { - module.exports = { isEnabled: false }; - } else { - window.screenfull = { isEnabled: false }; - } - - return; - } - - Object.defineProperties(screenfull, { - isFullscreen: { - get: function () { - return Boolean(document[fn.fullscreenElement]); - } - }, - element: { - enumerable: true, - get: function () { - return document[fn.fullscreenElement]; - } - }, - isEnabled: { - enumerable: true, - get: function () { - // Coerce to boolean in case of old WebKit - return Boolean(document[fn.fullscreenEnabled]); - } - } - }); - - if (isCommonjs) { - module.exports = screenfull; - } else { - window.screenfull = screenfull; - } - })(); - }); - screenfull.isEnabled; - - function noop() { } - function supportOffscreen($canvas) { - return typeof $canvas.transferControlToOffscreen === 'function'; - } - function supportOffscreenV2() { - return typeof OffscreenCanvas !== "undefined"; - } - function createContextGL($canvas) { - let gl = null; - const validContextNames = ["webgl", "experimental-webgl", "moz-webgl", "webkit-3d"]; - let nameIndex = 0; - - while (!gl && nameIndex < validContextNames.length) { - const contextName = validContextNames[nameIndex]; - - try { - let contextOptions = { - preserveDrawingBuffer: true - }; - gl = $canvas.getContext(contextName, contextOptions); - } catch (e) { - gl = null; - } - - if (!gl || typeof gl.getParameter !== "function") { - gl = null; - } - - ++nameIndex; - } - - return gl; - } - function dataURLToFile() { - let dataURL = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; - const arr = dataURL.split(","); - const bstr = atob(arr[1]); - const type = arr[0].replace("data:", "").replace(";base64", ""); - let n = bstr.length, - u8arr = new Uint8Array(n); - - while (n--) { - u8arr[n] = bstr.charCodeAt(n); - } - - return new File([u8arr], 'file', { - type - }); - } - function now() { - return new Date().getTime(); - } - (() => { - try { - if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") { - const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00)); - if (module instanceof WebAssembly.Module) return new WebAssembly.Instance(module) instanceof WebAssembly.Instance; - } - } catch (e) { } - - return false; - })(); - function clamp(num, a, b) { - return Math.max(Math.min(num, Math.max(a, b)), Math.min(a, b)); - } - function setStyle(element, key, value) { - if (!element) { - return; - } - - if (typeof key === 'object') { - Object.keys(key).forEach(item => { - setStyle(element, item, key[item]); - }); - } - - element.style[key] = value; - return element; - } - function getStyle(element, key) { - let numberType = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; - - if (!element) { - return 0; - } - - const value = getComputedStyle(element, null).getPropertyValue(key); - return numberType ? parseFloat(value) : value; - } - function getNowTime() { - if (performance && typeof performance.now === 'function') { - return performance.now(); - } - - return Date.now(); - } - function calculationRate(callback) { - let totalSize = 0; - let lastTime = getNowTime(); - return size => { - totalSize += size; - const thisTime = getNowTime(); - const diffTime = thisTime - lastTime; - - if (diffTime >= 1000) { - callback(totalSize / diffTime * 1000); - lastTime = thisTime; - totalSize = 0; - } - }; - } - function isMobile() { - return /iphone|ipod|android.*mobile|windows.*phone|blackberry.*mobile/i.test(window.navigator.userAgent.toLowerCase()); - } - - function supportWCS() { - return "VideoEncoder" in window; - } - function formatVideoDecoderConfigure(avcC) { - let codecArray = avcC.subarray(1, 4); - let codecString = "avc1."; - - for (let j = 0; j < 3; j++) { - let h = codecArray[j].toString(16); - - if (h.length < 2) { - h = "0" + h; - } - - codecString += h; - } - - return { - codec: codecString, - description: avcC - }; - } - function isFullScreen() { - return screenfull.isFullscreen; - } - function bpsSize(value) { - if (null == value || value === '' || parseInt(value) === 0 || isNaN(parseInt(value))) { - return "0KB/s"; - } - - let size = parseFloat(value); - size = size.toFixed(2); - return size + 'KB/s'; - } - function fpsStatus(fps) { - let result = 0; - - if (fps >= 24) { - result = 2; - } else if (fps >= 15) { - result = 1; - } - - return result; - } - function createEmptyImageBitmap(width, height) { - const $canvasElement = document.createElement("canvas"); - $canvasElement.width = width; - $canvasElement.height = height; - return window.createImageBitmap($canvasElement, 0, 0, width, height); - } - function supportMSE() { - return window.MediaSource && window.MediaSource.isTypeSupported(MP4_CODECS.avc); - } - function supportMediaStreamTrack() { - return window.MediaStreamTrackGenerator && typeof window.MediaStreamTrackGenerator === 'function'; - } - function isEmpty(value) { - return value === null || value === undefined; - } - function isBoolean(value) { - return value === true || value === false; - } - function isNotEmpty(value) { - return !isEmpty(value); - } - function initPlayTimes() { - return { - playInitStart: '', - //1 - playStart: '', - // 2 - streamStart: '', - //3 - streamResponse: '', - // 4 - demuxStart: '', - // 5 - decodeStart: '', - // 6 - videoStart: '', - // 7 - playTimestamp: '', - // playStart- playInitStart - streamTimestamp: '', - // streamStart - playStart - streamResponseTimestamp: '', - // streamResponse - streamStart - demuxTimestamp: '', - // demuxStart - streamResponse - decodeTimestamp: '', - // decodeStart - demuxStart - videoTimestamp: '', - // videoStart - decodeStart - allTimestamp: '' // videoStart - playInitStart - - }; - } // create watermark - function formatTimeTips(time) { - var result; // - - if (time > -1) { - var hour = Math.floor(time / 3600); - var min = Math.floor(time / 60) % 60; - var sec = time % 60; - sec = Math.round(sec); - - if (hour < 10) { - result = '0' + hour + ":"; - } else { - result = hour + ":"; - } - - if (min < 10) { - result += "0"; - } - - result += min + ":"; - - if (sec < 10) { - result += "0"; - } - - result += sec.toFixed(0); - } - - return result; - } - function getTarget(e) { - const event = e || window.event; - const target = event.target || event.srcElement; - return target; - } - function isWebglRenderSupport(width) { - return width / 2 % 4 === 0; - } - function getBrowser() { - const UserAgent = navigator.userAgent.toLowerCase(); - const browserInfo = {}; - const browserArray = { - IE: window.ActiveXObject || "ActiveXObject" in window, - // IE - Chrome: UserAgent.indexOf('chrome') > -1 && UserAgent.indexOf('safari') > -1, - // Chrome浏览器 - Firefox: UserAgent.indexOf('firefox') > -1, - // 火狐浏览器 - Opera: UserAgent.indexOf('opera') > -1, - // Opera浏览器 - Safari: UserAgent.indexOf('safari') > -1 && UserAgent.indexOf('chrome') == -1, - // safari浏览器 - Edge: UserAgent.indexOf('edge') > -1, - // Edge浏览器 - QQBrowser: /qqbrowser/.test(UserAgent), - // qq浏览器 - WeixinBrowser: /MicroMessenger/i.test(UserAgent) // 微信浏览器 - - }; // console.log(browserArray) - - for (let i in browserArray) { - if (browserArray[i]) { - let versions = ''; - - if (i === 'IE') { - versions = UserAgent.match(/(msie\s|trident.*rv:)([\w.]+)/)[2]; - } else if (i === 'Chrome') { - for (let mt in navigator.mimeTypes) { - //检测是否是360浏览器(测试只有pc端的360才起作用) - if (navigator.mimeTypes[mt]['type'] === 'application/360softmgrplugin') { - i = '360'; - } - } - - versions = UserAgent.match(/chrome\/([\d.]+)/)[1]; - } else if (i === 'Firefox') { - versions = UserAgent.match(/firefox\/([\d.]+)/)[1]; - } else if (i === 'Opera') { - versions = UserAgent.match(/opera\/([\d.]+)/)[1]; - } else if (i === 'Safari') { - versions = UserAgent.match(/version\/([\d.]+)/)[1]; - } else if (i === 'Edge') { - versions = UserAgent.match(/edge\/([\d.]+)/)[1]; - } else if (i === 'QQBrowser') { - versions = UserAgent.match(/qqbrowser\/([\d.]+)/)[1]; - } - - browserInfo.type = i; - browserInfo.version = parseInt(versions); - } - } - - return browserInfo; - } - function closeVideoFrame(videoFrame) { - if (videoFrame.close) { - videoFrame.close(); - } else if (videoFrame.destroy) { - videoFrame.destroy(); - } - } - function removeElement(element) { - let result = false; - - if (element) { - if (element.parentNode) { - element.parentNode.removeChild(element); - result = true; - } - } - - return result; - } - - var events$1 = (player => { - try { - const screenfullChange = e => { - if (getTarget(e) === player.$container) { - player.emit(JESSIBUCA_EVENTS.fullscreen, player.fullscreen); // 如果不是fullscreen,则触发下 resize 方法 - - if (!player.fullscreen) { - player.resize(); - } else { - if (player._opt.useMSE) { - player.resize(); - } - } - } - }; - - screenfull.on('change', screenfullChange); - player.events.destroys.push(() => { - screenfull.off('change', screenfullChange); - }); - } catch (error) {// - } // - - - player.on(EVENTS.decoderWorkerInit, () => { - player.debug.log('player', 'has loaded'); - player.loaded = true; - }); // - - player.on(EVENTS.play, () => { - player.loading = false; - }); // - - player.on(EVENTS.fullscreen, value => { - if (value) { - try { - screenfull.request(player.$container).then(() => { }).catch(e => { - if (isMobile() && player._opt.useWebFullScreen) { - player.webFullscreen = true; - } - }); - } catch (e) { - if (isMobile() && player._opt.useWebFullScreen) { - player.webFullscreen = true; - } - } - } else { - try { - screenfull.exit().then(() => { - if (player.webFullscreen) { - player.webFullscreen = false; - } - }).catch(() => { - player.webFullscreen = false; - }); - } catch (e) { - player.webFullscreen = false; - } - } - }); - - if (isMobile()) { - player.on(EVENTS.webFullscreen, value => { - if (value) { - player.$container.classList.add('jessibuca-fullscreen-web'); - } else { - player.$container.classList.remove('jessibuca-fullscreen-web'); - } // - - - player.emit(JESSIBUCA_EVENTS.fullscreen, player.fullscreen); - }); - } // - - - player.on(EVENTS.resize, () => { - player.video && player.video.resize(); - }); - - if (player._opt.debug) { - const ignoreList = [EVENTS.timeUpdate]; - Object.keys(EVENTS).forEach(key => { - player.on(EVENTS[key], value => { - if (ignoreList.includes(key)) { - return; - } - - player.debug.log('player events', EVENTS[key], value); - }); - }); - Object.keys(EVENTS_ERROR).forEach(key => { - player.on(EVENTS_ERROR[key], value => { - player.debug.log('player event error', EVENTS_ERROR[key], value); - }); - }); - } - }); - - class Emitter { - on(name, fn, ctx) { - const e = this.e || (this.e = {}); - (e[name] || (e[name] = [])).push({ - fn, - ctx - }); - return this; - } - - once(name, fn, ctx) { - const self = this; - - function listener() { - self.off(name, listener); - - for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { - args[_key] = arguments[_key]; - } - - fn.apply(ctx, args); - } - - listener._ = fn; - return this.on(name, listener, ctx); - } - - emit(name) { - const evtArr = ((this.e || (this.e = {}))[name] || []).slice(); - - for (var _len2 = arguments.length, data = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { - data[_key2 - 1] = arguments[_key2]; - } - - for (let i = 0; i < evtArr.length; i += 1) { - evtArr[i].fn.apply(evtArr[i].ctx, data); - } - - return this; - } - - off(name, callback) { - const e = this.e || (this.e = {}); - - if (!name) { - Object.keys(e).forEach(key => { - delete e[key]; - }); - delete this.e; - return; - } - - const evts = e[name]; - const liveEvents = []; - - if (evts && callback) { - for (let i = 0, len = evts.length; i < len; i += 1) { - if (evts[i].fn !== callback && evts[i].fn._ !== callback) liveEvents.push(evts[i]); - } - } - - if (liveEvents.length) { - e[name] = liveEvents; - } else { - delete e[name]; - } - - return this; - } - - } - - var createWebGL = ((gl, openWebglAlignment) => { - var vertexShaderScript = ['attribute vec4 vertexPos;', 'attribute vec4 texturePos;', 'varying vec2 textureCoord;', 'void main()', '{', 'gl_Position = vertexPos;', 'textureCoord = texturePos.xy;', '}'].join('\n'); - var fragmentShaderScript = ['precision highp float;', 'varying highp vec2 textureCoord;', 'uniform sampler2D ySampler;', 'uniform sampler2D uSampler;', 'uniform sampler2D vSampler;', 'const mat4 YUV2RGB = mat4', '(', '1.1643828125, 0, 1.59602734375, -.87078515625,', '1.1643828125, -.39176171875, -.81296875, .52959375,', '1.1643828125, 2.017234375, 0, -1.081390625,', '0, 0, 0, 1', ');', 'void main(void) {', 'highp float y = texture2D(ySampler, textureCoord).r;', 'highp float u = texture2D(uSampler, textureCoord).r;', 'highp float v = texture2D(vSampler, textureCoord).r;', 'gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;', '}'].join('\n'); - - if (openWebglAlignment) { - gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); - } - - var vertexShader = gl.createShader(gl.VERTEX_SHADER); - gl.shaderSource(vertexShader, vertexShaderScript); - gl.compileShader(vertexShader); - - if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) { - console.log('Vertex shader failed to compile: ' + gl.getShaderInfoLog(vertexShader)); - } - - var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); - gl.shaderSource(fragmentShader, fragmentShaderScript); - gl.compileShader(fragmentShader); - - if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) { - console.log('Fragment shader failed to compile: ' + gl.getShaderInfoLog(fragmentShader)); - } - - var program = gl.createProgram(); - gl.attachShader(program, vertexShader); - gl.attachShader(program, fragmentShader); - gl.linkProgram(program); - - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { - console.log('Program failed to compile: ' + gl.getProgramInfoLog(program)); - } - - gl.useProgram(program); // initBuffers - - var vertexPosBuffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer); - gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 1, -1, 1, 1, -1, -1, -1]), gl.STATIC_DRAW); - var vertexPosRef = gl.getAttribLocation(program, 'vertexPos'); - gl.enableVertexAttribArray(vertexPosRef); - gl.vertexAttribPointer(vertexPosRef, 2, gl.FLOAT, false, 0, 0); - var texturePosBuffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer); - gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 0, 0, 0, 1, 1, 0, 1]), gl.STATIC_DRAW); - var texturePosRef = gl.getAttribLocation(program, 'texturePos'); - gl.enableVertexAttribArray(texturePosRef); - gl.vertexAttribPointer(texturePosRef, 2, gl.FLOAT, false, 0, 0); - - function _initTexture(name, index) { - var textureRef = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, textureRef); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.bindTexture(gl.TEXTURE_2D, null); - gl.uniform1i(gl.getUniformLocation(program, name), index); - return textureRef; - } - - var yTextureRef = _initTexture('ySampler', 0); - - var uTextureRef = _initTexture('uSampler', 1); - - var vTextureRef = _initTexture('vSampler', 2); - - return { - render: function (w, h, y, u, v) { - gl.viewport(0, 0, w, h); - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, yTextureRef); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, w, h, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, y); - gl.activeTexture(gl.TEXTURE1); - gl.bindTexture(gl.TEXTURE_2D, uTextureRef); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, w / 2, h / 2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, u); - gl.activeTexture(gl.TEXTURE2); - gl.bindTexture(gl.TEXTURE_2D, vTextureRef); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, w / 2, h / 2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, v); - gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); - }, - destroy: function () { - try { - gl.deleteProgram(program); - gl.deleteBuffer(vertexPosBuffer); - gl.deleteBuffer(texturePosBuffer); - gl.deleteTexture(yTextureRef); - gl.deleteTexture(uTextureRef); - gl.deleteTexture(vTextureRef); - } catch (e) {// console.error(e); - } - } - }; - }); - - class CommonLoader$1 extends Emitter { - constructor() { - super(); - this.init = false; - } - - resetInit() { - this.init = false; - this.videoInfo = { - width: '', - height: '', - encType: '', - encTypeCode: '' - }; - } - - destroy() { - this.resetInit(); - this.player.$container.removeChild(this.$videoElement); - this.off(); - } // - - - updateVideoInfo(data) { - if (data.encTypeCode) { - this.videoInfo.encType = VIDEO_ENC_TYPE[data.encTypeCode]; - } - - if (data.width) { - this.videoInfo.width = data.width; - } - - if (data.height) { - this.videoInfo.height = data.height; - } // video 基本信息 - - - if (this.videoInfo.encType && this.videoInfo.height && this.videoInfo.width && !this.init) { - this.player.emit(EVENTS.videoInfo, this.videoInfo); - this.init = true; - } - } - - play() { } - - pause() { } - - clearView() { } - - } - - /* - * FileSaver.js - * A saveAs() FileSaver implementation. - * - * By Eli Grey, http://eligrey.com - * - * License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT) - * source : http://purl.eligrey.com/github/FileSaver.js - */ - // The one and only way of getting global scope in all environments - // https://stackoverflow.com/q/3277182/1008999 - var _global = typeof window === 'object' && window.window === window ? window : typeof self === 'object' && self.self === self ? self : typeof global === 'object' && global.global === global ? global : undefined; - - function bom(blob, opts) { - if (typeof opts === 'undefined') opts = { - autoBom: false - }; else if (typeof opts !== 'object') { - console.warn('Deprecated: Expected third argument to be a object'); - opts = { - autoBom: !opts - }; - } // prepend BOM for UTF-8 XML and text/* types (including HTML) - // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF - - if (opts.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { - return new Blob([String.fromCharCode(0xFEFF), blob], { - type: blob.type - }); - } - - return blob; - } - - function download(url, name, opts) { - var xhr = new XMLHttpRequest(); - xhr.open('GET', url); - xhr.responseType = 'blob'; - - xhr.onload = function () { - saveAs(xhr.response, name, opts); - }; - - xhr.onerror = function () { - console.error('could not download file'); - }; - - xhr.send(); - } - - function corsEnabled(url) { - var xhr = new XMLHttpRequest(); // use sync to avoid popup blocker - - xhr.open('HEAD', url, false); - - try { - xhr.send(); - } catch (e) { } - - return xhr.status >= 200 && xhr.status <= 299; - } // `a.click()` doesn't work for all browsers (#465) - - - function click(node) { - try { - node.dispatchEvent(new MouseEvent('click')); - } catch (e) { - var evt = document.createEvent('MouseEvents'); - evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null); - node.dispatchEvent(evt); - } - } // Detect WebView inside a native macOS app by ruling out all browsers - // We just need to check for 'Safari' because all other browsers (besides Firefox) include that too - // https://www.whatismybrowser.com/guides/the-latest-user-agent/macos - - - var isMacOSWebView = _global.navigator && /Macintosh/.test(navigator.userAgent) && /AppleWebKit/.test(navigator.userAgent) && !/Safari/.test(navigator.userAgent); - var saveAs = // probably in some web worker - typeof window !== 'object' || window !== _global ? function saveAs() { - /* noop */ - } // Use download attribute first if possible (#193 Lumia mobile) unless this is a macOS WebView - : 'download' in HTMLAnchorElement.prototype && !isMacOSWebView ? function saveAs(blob, name, opts) { - var URL = _global.URL || _global.webkitURL; // Namespace is used to prevent conflict w/ Chrome Poper Blocker extension (Issue #561) - - var a = document.createElementNS('http://www.w3.org/1999/xhtml', 'a'); - name = name || blob.name || 'download'; - a.download = name; - a.rel = 'noopener'; // tabnabbing - // TODO: detect chrome extensions & packaged apps - // a.target = '_blank' - - if (typeof blob === 'string') { - // Support regular links - a.href = blob; - - if (a.origin !== location.origin) { - corsEnabled(a.href) ? download(blob, name, opts) : click(a, a.target = '_blank'); - } else { - click(a); - } - } else { - // Support blobs - a.href = URL.createObjectURL(blob); - setTimeout(function () { - URL.revokeObjectURL(a.href); - }, 4E4); // 40s - - setTimeout(function () { - click(a); - }, 0); - } - } // Use msSaveOrOpenBlob as a second approach - : 'msSaveOrOpenBlob' in navigator ? function saveAs(blob, name, opts) { - name = name || blob.name || 'download'; - - if (typeof blob === 'string') { - if (corsEnabled(blob)) { - download(blob, name, opts); - } else { - var a = document.createElement('a'); - a.href = blob; - a.target = '_blank'; - setTimeout(function () { - click(a); - }); - } - } else { - navigator.msSaveOrOpenBlob(bom(blob, opts), name); - } - } // Fallback to using FileReader and a popup - : function saveAs(blob, name, opts, popup) { - // Open a popup immediately do go around popup blocker - // Mostly only available on user interaction and the fileReader is async so... - popup = popup || open('', '_blank'); - - if (popup) { - popup.document.title = popup.document.body.innerText = 'downloading...'; - } - - if (typeof blob === 'string') return download(blob, name, opts); - var force = blob.type === 'application/octet-stream'; - - var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari; - - var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent); - - if ((isChromeIOS || force && isSafari || isMacOSWebView) && typeof FileReader !== 'undefined') { - // Safari doesn't allow downloading of blob URLs - var reader = new FileReader(); - - reader.onloadend = function () { - var url = reader.result; - url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;'); - if (popup) popup.location.href = url; else location = url; - popup = null; // reverse-tabnabbing #460 - }; - - reader.readAsDataURL(blob); - } else { - var URL = _global.URL || _global.webkitURL; - var url = URL.createObjectURL(blob); - if (popup) popup.location = url; else location.href = url; - popup = null; // reverse-tabnabbing #460 - - setTimeout(function () { - URL.revokeObjectURL(url); - }, 4E4); // 40s - } - }; - - class CanvasVideoLoader extends CommonLoader$1 { - constructor(player) { - super(); - this.player = player; - const $canvasElement = document.createElement("canvas"); - $canvasElement.style.position = "absolute"; - $canvasElement.style.top = 0; - $canvasElement.style.left = 0; - this.$videoElement = $canvasElement; - player.$container.appendChild(this.$videoElement); - this.context2D = null; - this.contextGl = null; - this.contextGlRender = null; - this.contextGlDestroy = null; - this.bitmaprenderer = null; - this.renderType = null; - this.videoInfo = { - width: '', - height: '', - encType: '' - }; // - - this._initCanvasRender(); - - this.player.debug.log('CanvasVideo', 'init'); - } - - destroy() { - super.destroy(); - - if (this.contextGl) { - this.contextGl = null; - } - - if (this.context2D) { - this.context2D = null; - } - - if (this.contextGlRender) { - this.contextGlDestroy && this.contextGlDestroy(); - this.contextGlDestroy = null; - this.contextGlRender = null; - } - - if (this.bitmaprenderer) { - this.bitmaprenderer = null; - } - - this.renderType = null; - this.player.debug.log(`CanvasVideoLoader`, 'destroy'); - } - - _initContextGl() { - this.contextGl = createContextGL(this.$videoElement); - - if (this.contextGl) { - const webgl = createWebGL(this.contextGl, this.player._opt.openWebglAlignment); - this.contextGlRender = webgl.render; - this.contextGlDestroy = webgl.destroy; - } else { - this.player.debug.error(`CanvasVideoLoader`, 'init webgl fail'); - } - } - - _initContext2D() { - this.context2D = this.$videoElement.getContext('2d'); - } // 渲染类型 - - - _initCanvasRender() { - if (this.player._opt.useWCS && !this._supportOffscreen()) { - this.renderType = CANVAS_RENDER_TYPE.webcodecs; - - this._initContext2D(); - } else if (this._supportOffscreen()) { - this.renderType = CANVAS_RENDER_TYPE.offscreen; - - this._bindOffscreen(); - } else { - this.renderType = CANVAS_RENDER_TYPE.webgl; - - this._initContextGl(); - } - } - - _supportOffscreen() { - return supportOffscreen(this.$videoElement) && this.player._opt.useOffscreen; - } // - - - _bindOffscreen() { - this.bitmaprenderer = this.$videoElement.getContext('bitmaprenderer'); - } - - initCanvasViewSize() { - this.$videoElement.width = this.videoInfo.width; - this.$videoElement.height = this.videoInfo.height; - this.resize(); - } // - - - render(msg) { - this.player.videoTimestamp = msg.ts; - - switch (this.renderType) { - case CANVAS_RENDER_TYPE.offscreen: - this.bitmaprenderer.transferFromImageBitmap(msg.buffer); - break; - - case CANVAS_RENDER_TYPE.webgl: - this.contextGlRender(this.$videoElement.width, this.$videoElement.height, msg.output[0], msg.output[1], msg.output[2]); - break; - - case CANVAS_RENDER_TYPE.webcodecs: - // can use createImageBitmap in wexin - this.context2D.drawImage(msg.videoFrame, 0, 0, this.$videoElement.width, this.$videoElement.height); - closeVideoFrame(msg.videoFrame); - break; - } - } - - screenshot(filename, format, quality, type) { - filename = filename || now(); - type = type || SCREENSHOT_TYPE.download; - const formatType = { - png: 'image/png', - jpeg: 'image/jpeg', - webp: 'image/webp' - }; - let encoderOptions = 0.92; - - if (!formatType[format] && SCREENSHOT_TYPE[format]) { - type = format; - format = 'png'; - quality = undefined; - } - - if (typeof quality === "string") { - type = quality; - quality = undefined; - } - - if (typeof quality !== 'undefined') { - encoderOptions = Number(quality); - } - - const dataURL = this.$videoElement.toDataURL(formatType[format] || formatType.png, encoderOptions); - - if (type === SCREENSHOT_TYPE.base64) { - return dataURL; - } else { - const file = dataURLToFile(dataURL); - - if (type === SCREENSHOT_TYPE.blob) { - return file; - } else if (type === SCREENSHOT_TYPE.download) { - // downloadImg(file, filename); - saveAs(file, filename); - } - } - } // - - - clearView() { - switch (this.renderType) { - case CANVAS_RENDER_TYPE.offscreen: - createEmptyImageBitmap(this.$videoElement.width, this.$videoElement.height).then(imageBitMap => { - this.bitmaprenderer.transferFromImageBitmap(imageBitMap); - }); - break; - - case CANVAS_RENDER_TYPE.webgl: - this.contextGl.clear(this.contextGl.COLOR_BUFFER_BIT); - break; - - case CANVAS_RENDER_TYPE.webcodecs: - this.context2D.clearRect(0, 0, this.$videoElement.width, this.$videoElement.height); - break; - } - } - - resize() { - this.player.debug.log('canvasVideo', 'resize'); - const option = this.player._opt; - let width = this.player.width; - let height = this.player.height; - - if (option.hasControl && !option.controlAutoHide) { - if (isMobile() && this.player.fullscreen && option.useWebFullScreen) { - width -= CONTROL_HEIGHT; - } else { - height -= CONTROL_HEIGHT; - } - } - - let resizeWidth = this.$videoElement.width; - let resizeHeight = this.$videoElement.height; - const rotate = option.rotate; - let left = (width - resizeWidth) / 2; - let top = (height - resizeHeight) / 2; - - if (rotate === 270 || rotate === 90) { - resizeWidth = this.$videoElement.height; - resizeHeight = this.$videoElement.width; - } - - const wScale = width / resizeWidth; - const hScale = height / resizeHeight; - let scale = wScale > hScale ? hScale : wScale; // - - if (!option.isResize) { - if (wScale !== hScale) { - scale = wScale + ',' + hScale; - } - } // - - - if (option.isFullResize) { - scale = wScale > hScale ? wScale : hScale; - } - - let transform = "scale(" + scale + ")"; - - if (rotate) { - transform += ' rotate(' + rotate + 'deg)'; - } - - this.$videoElement.style.transform = transform; - this.$videoElement.style.left = left + "px"; - this.$videoElement.style.top = top + "px"; - } - - } - - class VideoLoader extends CommonLoader$1 { - constructor(player) { - super(); - this.player = player; - const $videoElement = document.createElement('video'); - const $canvasElement = document.createElement('canvas'); - $videoElement.muted = true; - $videoElement.style.position = "absolute"; - $videoElement.style.top = 0; - $videoElement.style.left = 0; - this._delayPlay = false; - player.$container.appendChild($videoElement); - this.videoInfo = { - width: '', - height: '', - encType: '' - }; - const _opt = this.player._opt; - - if (_opt.useWCS && _opt.wcsUseVideoRender) { - this.trackGenerator = new MediaStreamTrackGenerator({ - kind: 'video' - }); - $videoElement.srcObject = new MediaStream([this.trackGenerator]); - this.vwriter = this.trackGenerator.writable.getWriter(); - } - - this.$videoElement = $videoElement; - this.$canvasElement = $canvasElement; - this.canvasContext = $canvasElement.getContext('2d'); - this.fixChromeVideoFlashBug(); - this.resize(); - const { - proxy - } = this.player.events; - proxy(this.$videoElement, 'canplay', () => { - this.player.debug.log('Video', `canplay and _delayPlay is ${this._delayPlay}`); - - if (this._delayPlay) { - this._play(); - } - }); - proxy(this.$videoElement, 'waiting', () => { - this.player.emit(EVENTS.videoWaiting); - }); - proxy(this.$videoElement, 'timeupdate', event => { - // this.player.emit(EVENTS.videoTimeUpdate, event.timeStamp); - const timeStamp = parseInt(event.timeStamp, 10); - this.player.emit(EVENTS.timeUpdate, timeStamp); // check is pause; - - if (!this.isPlaying() && this.init) { - this.player.debug.log('Video', `timeupdate and this.isPlaying is false and retry play`); - this.$videoElement.play(); - } - }); - this.player.debug.log('Video', 'init'); - } - - destroy() { - super.destroy(); - this.$canvasElement = null; - this.canvasContext = null; - - if (this.$videoElement) { - this.$videoElement.pause(); - this.$videoElement.currentTime = 0; - this.$videoElement.src = ''; - this.$videoElement.removeAttribute('src'); - this.$videoElement = null; - } - - if (this.trackGenerator) { - this.trackGenerator.stop(); - this.trackGenerator = null; - } - - if (this.vwriter) { - this.vwriter.close(); - this.vwriter = null; - } - - this.player.debug.log('Video', 'destroy'); - } - - fixChromeVideoFlashBug() { - const browser = getBrowser(); - const type = browser.type.toLowerCase(); - - if (type === 'chrome' || type === 'edge') { - const $container = this.player.$container; - $container.style.backdropFilter = 'blur(0px)'; - $container.style.translateZ = '0'; - } - } - - play() { - if (this.$videoElement) { - const readyState = this._getVideoReadyState(); - - this.player.debug.log('Video', `play and readyState: ${readyState}`); - - if (readyState === 0) { - this.player.debug.warn('Video', 'readyState is 0 and set _delayPlay to true'); - this._delayPlay = true; - return; - } - - this._play(); - } - } - - _getVideoReadyState() { - let result = 0; - - if (this.$videoElement) { - result = this.$videoElement.readyState; - } - - return result; - } - - _play() { - this.$videoElement && this.$videoElement.play().then(() => { - this._delayPlay = false; - this.player.debug.log('Video', '_play success'); - setTimeout(() => { - if (!this.isPlaying()) { - this.player.debug.warn('Video', `play failed and retry play`); - - this._play(); - } - }, 100); - }).catch(e => { - this.player.debug.error('Video', '_play error', e); - }); - } - - pause(isNow) { - // 预防 - // https://developer.chrome.com/blog/play-request-was-interrupted/ - // http://alonesuperman.com/?p=23 - if (isNow) { - this.$videoElement && this.$videoElement.pause(); - } else { - setTimeout(() => { - this.$videoElement && this.$videoElement.pause(); - }, 100); - } - } - - clearView() { } - - screenshot(filename, format, quality, type) { - filename = filename || now(); - type = type || SCREENSHOT_TYPE.download; - const formatType = { - png: 'image/png', - jpeg: 'image/jpeg', - webp: 'image/webp' - }; - let encoderOptions = 0.92; - - if (!formatType[format] && SCREENSHOT_TYPE[format]) { - type = format; - format = 'png'; - quality = undefined; - } - - if (typeof quality === "string") { - type = quality; - quality = undefined; - } - - if (typeof quality !== 'undefined') { - encoderOptions = Number(quality); - } - - const $video = this.$videoElement; - let canvas = this.$canvasElement; - canvas.width = $video.videoWidth; - canvas.height = $video.videoHeight; - this.canvasContext.drawImage($video, 0, 0, canvas.width, canvas.height); - const dataURL = canvas.toDataURL(formatType[format] || formatType.png, encoderOptions); // release memory - - this.canvasContext.clearRect(0, 0, canvas.width, canvas.height); - canvas.width = 0; - canvas.height = 0; - - if (type === SCREENSHOT_TYPE.base64) { - return dataURL; - } else { - const file = dataURLToFile(dataURL); - - if (type === SCREENSHOT_TYPE.blob) { - return file; - } else if (type === SCREENSHOT_TYPE.download) { - // downloadImg(file, filename); - saveAs(file, filename); - } - } - } - - initCanvasViewSize() { - this.resize(); - } // - - - render(msg) { - if (this.vwriter) { - this.vwriter.write(msg.videoFrame); - } - } - - resize() { - let width = this.player.width; - let height = this.player.height; - const option = this.player._opt; - const rotate = option.rotate; - - if (option.hasControl && !option.controlAutoHide) { - if (isMobile() && this.player.fullscreen && option.useWebFullScreen) { - width -= CONTROL_HEIGHT; - } else { - height -= CONTROL_HEIGHT; - } - } - - this.$videoElement.width = width; - this.$videoElement.height = height; - - if (rotate === 270 || rotate === 90) { - this.$videoElement.width = height; - this.$videoElement.height = width; - } - - let resizeWidth = this.$videoElement.width; - let resizeHeight = this.$videoElement.height; - let left = (width - resizeWidth) / 2; - let top = (height - resizeHeight) / 2; - let objectFill = 'contain'; // 默认是true - // 视频画面做等比缩放后,高或宽对齐canvas区域,画面不被拉伸,但有黑边 - // 视频画面完全填充canvas区域,画面会被拉伸 - - if (!option.isResize) { - objectFill = 'fill'; - } // 视频画面做等比缩放后,完全填充canvas区域,画面不被拉伸,没有黑边,但画面显示不全 - - - if (option.isFullResize) { - objectFill = 'none'; - } - - this.$videoElement.style.objectFit = objectFill; - this.$videoElement.style.transform = 'rotate(' + rotate + 'deg)'; - this.$videoElement.style.left = left + "px"; - this.$videoElement.style.top = top + "px"; - } - - isPlaying() { - return this.$videoElement && !this.$videoElement.paused; - } - - } - - class Video { - constructor(player) { - const Loader = Video.getLoaderFactory(player._opt); - return new Loader(player); - } - - static getLoaderFactory(opt) { - if (opt.useMSE || opt.useWCS && !opt.useOffscreen && opt.wcsUseVideoRender) { - return VideoLoader; - } else { - return CanvasVideoLoader; - } - } - - } - - class AudioContextLoader extends Emitter { - constructor(player) { - super(); - this.bufferList = []; - this.player = player; - this.scriptNode = null; - this.hasInitScriptNode = false; - this.audioContextChannel = null; - this.audioContext = new (window.AudioContext || window.webkitAudioContext)(); // - - this.gainNode = this.audioContext.createGain(); // Get an AudioBufferSourceNode. - // This is the AudioNode to use when we want to play an AudioBuffer - - const source = this.audioContext.createBufferSource(); // set the buffer in the AudioBufferSourceNode - - source.buffer = this.audioContext.createBuffer(1, 1, 22050); // connect the AudioBufferSourceNode to the - // destination so we can hear the sound - - source.connect(this.audioContext.destination); // noteOn as start - // start the source playing - - if (source.noteOn) { - source.noteOn(0); - } else { - source.start(0); - } - - this.audioBufferSourceNode = source; // - - this.mediaStreamAudioDestinationNode = this.audioContext.createMediaStreamDestination(); // - - this.audioEnabled(true); // default setting 0 - - this.gainNode.gain.value = 0; - this.playing = false; // - - this.audioSyncVideoOption = { - diff: null - }; - this.audioInfo = { - encType: '', - channels: '', - sampleRate: '' - }; - this.init = false; - this.hasAudio = false; // update - - this.on(EVENTS.videoSyncAudio, options => { - // this.player.debug.log('AudioContext', `videoSyncAudio , audioTimestamp: ${options.audioTimestamp},videoTimestamp: ${options.videoTimestamp},diff:${options.diff}`) - this.audioSyncVideoOption = options; - }); - this.player.debug.log('AudioContext', 'init'); - } - - resetInit() { - this.init = false; - this.audioInfo = { - encType: '', - channels: '', - sampleRate: '' - }; - } - - destroy() { - this.closeAudio(); - this.resetInit(); - this.audioContext.close(); - this.audioContext = null; - this.gainNode = null; - this.hasAudio = false; - this.playing = false; - - if (this.scriptNode) { - this.scriptNode.onaudioprocess = noop; - this.scriptNode = null; - } - - this.audioBufferSourceNode = null; - this.mediaStreamAudioDestinationNode = null; - this.hasInitScriptNode = false; - this.audioSyncVideoOption = { - diff: null - }; - this.off(); - this.player.debug.log('AudioContext', 'destroy'); - } - - updateAudioInfo(data) { - if (data.encTypeCode) { - this.audioInfo.encType = AUDIO_ENC_TYPE[data.encTypeCode]; - } - - if (data.channels) { - this.audioInfo.channels = data.channels; - } - - if (data.sampleRate) { - this.audioInfo.sampleRate = data.sampleRate; - } // audio 基本信息 - - - if (this.audioInfo.sampleRate && this.audioInfo.channels && this.audioInfo.encType && !this.init) { - this.player.emit(EVENTS.audioInfo, this.audioInfo); - this.init = true; - } - } // - - - get isPlaying() { - return this.playing; - } - - get isMute() { - return this.gainNode.gain.value === 0; - } - - get volume() { - return this.gainNode.gain.value; - } - - get bufferSize() { - return this.bufferList.length; - } - - initScriptNode() { - this.playing = true; - - if (this.hasInitScriptNode) { - return; - } - - const channels = this.audioInfo.channels; - const scriptNode = this.audioContext.createScriptProcessor(1024, 0, channels); // tips: if audio isStateSuspended onaudioprocess method not working - - scriptNode.onaudioprocess = audioProcessingEvent => { - const outputBuffer = audioProcessingEvent.outputBuffer; - - if (this.bufferList.length && this.playing) { - // just for wasm - if (!this.player._opt.useWCS && !this.player._opt.useMSE && this.player._opt.wasmDecodeAudioSyncVideo) { - // audio > video - // wait - if (this.audioSyncVideoOption.diff > AUDIO_SYNC_VIDEO_DIFF) { - this.player.debug.warn('AudioContext', `audioSyncVideoOption more than diff :${this.audioSyncVideoOption.diff}, waiting`); // wait - - return; - } // audio < video - // throw away then chase video - else if (this.audioSyncVideoOption.diff < -AUDIO_SYNC_VIDEO_DIFF) { - this.player.debug.warn('AudioContext', `audioSyncVideoOption less than diff :${this.audioSyncVideoOption.diff}, dropping`); // - - let bufferItem = this.bufferList.shift(); // - - while (bufferItem.ts - this.player.videoTimestamp < -AUDIO_SYNC_VIDEO_DIFF && this.bufferList.length > 0) { - // this.player.debug.warn('AudioContext', `audioSyncVideoOption less than inner ts is:${bufferItem.ts}, videoTimestamp is ${this.player.videoTimestamp},diff:${bufferItem.ts - this.player.videoTimestamp}`) - bufferItem = this.bufferList.shift(); - } - - if (this.bufferList.length === 0) { - return; - } - } - } - - if (this.bufferList.length === 0) { - return; - } - - const bufferItem = this.bufferList.shift(); // update audio time stamp - - if (bufferItem && bufferItem.ts) { - this.player.audioTimestamp = bufferItem.ts; - } - - for (let channel = 0; channel < channels; channel++) { - const b = bufferItem.buffer[channel]; - const nowBuffering = outputBuffer.getChannelData(channel); - - for (let i = 0; i < 1024; i++) { - nowBuffering[i] = b[i] || 0; - } - } - } - }; - - scriptNode.connect(this.gainNode); - this.scriptNode = scriptNode; - this.gainNode.connect(this.audioContext.destination); - this.gainNode.connect(this.mediaStreamAudioDestinationNode); - this.hasInitScriptNode = true; - } - - mute(flag) { - if (flag) { - if (!this.isMute) { - this.player.emit(EVENTS.mute, flag); - } - - this.setVolume(0); - this.clear(); - } else { - if (this.isMute) { - this.player.emit(EVENTS.mute, flag); - } - - this.setVolume(0.5); - } - } - - setVolume(volume) { - volume = parseFloat(volume).toFixed(2); - - if (isNaN(volume)) { - return; - } - - this.audioEnabled(true); - volume = clamp(volume, 0, 1); - this.gainNode.gain.value = volume; - this.gainNode.gain.setValueAtTime(volume, this.audioContext.currentTime); - this.player.emit(EVENTS.volumechange, this.player.volume); - } - - closeAudio() { - if (this.hasInitScriptNode) { - this.scriptNode && this.scriptNode.disconnect(this.gainNode); - this.gainNode && this.gainNode.disconnect(this.audioContext.destination); - this.gainNode && this.gainNode.disconnect(this.mediaStreamAudioDestinationNode); - } - - this.clear(); - } // 是否播放。。。 - - - audioEnabled(flag) { - if (flag) { - if (this.audioContext.state === 'suspended') { - // resume - this.audioContext.resume(); - } - } else { - if (this.audioContext.state === 'running') { - // suspend - this.audioContext.suspend(); - } - } - } - - isStateRunning() { - return this.audioContext.state === 'running'; - } - - isStateSuspended() { - return this.audioContext.state === 'suspended'; - } - - clear() { - this.bufferList = []; - } - - play(buffer, ts) { - // if is mute - if (this.isMute) { - return; - } - - this.hasAudio = true; - this.bufferList.push({ - buffer, - ts - }); - - if (this.bufferList.length > 20) { - this.player.debug.warn('AudioContext', `bufferList is large: ${this.bufferList.length}`); // out of memory - - if (this.bufferList.length > 50) { - this.bufferList.shift(); - } - } // this.player.debug.log('AudioContext', `bufferList is ${this.bufferList.length}`) - - } - - pause() { - this.audioSyncVideoOption = { - diff: null - }; - this.playing = false; - this.clear(); - } - - resume() { - this.playing = true; - } - - } - - class Audio { - constructor(player) { - const Loader = Audio.getLoaderFactory(); - return new Loader(player); - } - - static getLoaderFactory() { - return AudioContextLoader; - } - - } - - class FetchLoader extends Emitter { - constructor(player) { - super(); - this.player = player; - this.playing = false; - this.abortController = new AbortController(); // - - this.streamRate = calculationRate(rate => { - player.emit(EVENTS.kBps, (rate / 1024).toFixed(2)); - }); - player.debug.log('FetchStream', 'init'); - } - - destroy() { - this.abort(); - this.off(); - this.streamRate = null; - this.player.debug.log('FetchStream', 'destroy'); - } - /** - * - * @param url - * @param options - */ - - - fetchStream(url) { - let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - const { - demux - } = this.player; - this.player.debug.log('FetchStream', 'fetchStream', url, JSON.stringify(options)); - this.player._times.streamStart = now(); - const fetchOptions = Object.assign({ - signal: this.abortController.signal - }, { - headers: options.headers || {} - }); - fetch(url, fetchOptions).then(res => { - const reader = res.body.getReader(); - this.emit(EVENTS.streamSuccess); - - const fetchNext = () => { - reader.read().then(_ref => { - let { - done, - value - } = _ref; - - if (done) { - demux.close(); - } else { - this.streamRate && this.streamRate(value.byteLength); - demux.dispatch(value); - fetchNext(); - } - }).catch(e => { - demux.close(); - const errorString = e.toString(); // aborted a request 。 - - if (errorString.indexOf(FETCH_ERROR.abortError1) !== -1) { - return; - } - - if (errorString.indexOf(FETCH_ERROR.abortError2) !== -1) { - return; - } - - if (e.name === FETCH_ERROR.abort) { - return; - } - - this.abort(); - this.emit(EVENTS_ERROR.fetchError, e); - this.player.emit(EVENTS.error, EVENTS_ERROR.fetchError); - }); - }; - - fetchNext(); - }).catch(e => { - if (e.name === 'AbortError') { - return; - } - - demux.close(); - this.abort(); - this.emit(EVENTS_ERROR.fetchError, e); - this.player.emit(EVENTS.error, EVENTS_ERROR.fetchError); - }); - } - - abort() { - if (this.abortController) { - this.abortController.abort(); - this.abortController = null; - } - } - - } - - class WebsocketLoader extends Emitter { - constructor(player) { - super(); - this.player = player; - this.socket = null; - this.socketStatus = WEBSOCKET_STATUS.notConnect; - this.wsUrl = null; // - - this.streamRate = calculationRate(rate => { - player.emit(EVENTS.kBps, (rate / 1024).toFixed(2)); - }); - player.debug.log('WebsocketLoader', 'init'); - } - - destroy() { - if (this.socket) { - this.socket.close(1000, 'Client disconnecting'); - this.socket = null; - } - - this.socketStatus = WEBSOCKET_STATUS.notConnect; - this.streamRate = null; - this.wsUrl = null; - this.off(); - this.player.debug.log('websocketLoader', 'destroy'); - } - - _createWebSocket() { - const player = this.player; - const { - debug, - events: { - proxy - }, - demux - } = player; - this.socket = new WebSocket(this.wsUrl); - this.socket.binaryType = 'arraybuffer'; - proxy(this.socket, 'open', () => { - this.emit(EVENTS.streamSuccess); - debug.log('websocketLoader', 'socket open'); - this.socketStatus = WEBSOCKET_STATUS.open; - }); - proxy(this.socket, 'message', event => { - this.streamRate && this.streamRate(event.data.byteLength); - - this._handleMessage(event.data); - }); - proxy(this.socket, 'close', () => { - debug.log('websocketLoader', 'socket close'); - this.emit(EVENTS.streamEnd); - this.socketStatus = WEBSOCKET_STATUS.close; - }); - proxy(this.socket, 'error', error => { - debug.log('websocketLoader', 'socket error'); - this.emit(EVENTS_ERROR.websocketError, error); - this.player.emit(EVENTS.error, EVENTS_ERROR.websocketError); - this.socketStatus = WEBSOCKET_STATUS.error; - demux.close(); - debug.log('websocketLoader', `socket error:`, error); - }); - } // - - - _handleMessage(message) { - const { - demux - } = this.player; - - if (!demux) { - this.player.debug.warn('websocketLoader', 'websocket handle message demux is null'); - return; - } - - demux.dispatch(message); - } - /** - * - * @param url - * @param options - */ - - - fetchStream(url, options) { - this.player._times.streamStart = now(); - this.wsUrl = url; - - this._createWebSocket(); - } - - } - - class Stream { - constructor(player) { - const Loader = Stream.getLoaderFactory(player._opt.protocol); - return new Loader(player); - } - - static getLoaderFactory(protocol) { - if (protocol === PLAYER_PLAY_PROTOCOL.fetch) { - return FetchLoader; - } else if (protocol === PLAYER_PLAY_PROTOCOL.websocket) { - return WebsocketLoader; - } - } - - } - - var RecordRTC_1 = createCommonjsModule(function (module) { - - // Last time updated: 2021-03-09 3:20:22 AM UTC - - // ________________ - // RecordRTC v5.6.2 - - // Open-Sourced: https://github.com/muaz-khan/RecordRTC - - // -------------------------------------------------- - // Muaz Khan - www.MuazKhan.com - // MIT License - www.WebRTC-Experiment.com/licence - // -------------------------------------------------- - - // ____________ - // RecordRTC.js - - /** - * {@link https://github.com/muaz-khan/RecordRTC|RecordRTC} is a WebRTC JavaScript library for audio/video as well as screen activity recording. It supports Chrome, Firefox, Opera, Android, and Microsoft Edge. Platforms: Linux, Mac and Windows. - * @summary Record audio, video or screen inside the browser. - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @typedef RecordRTC - * @class - * @example - * var recorder = RecordRTC(mediaStream or [arrayOfMediaStream], { - * type: 'video', // audio or video or gif or canvas - * recorderType: MediaStreamRecorder || CanvasRecorder || StereoAudioRecorder || Etc - * }); - * recorder.startRecording(); - * @see For further information: - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - * @param {MediaStream} mediaStream - Single media-stream object, array of media-streams, html-canvas-element, etc. - * @param {object} config - {type:"video", recorderType: MediaStreamRecorder, disableLogs: true, numberOfAudioChannels: 1, bufferSize: 0, sampleRate: 0, desiredSampRate: 16000, video: HTMLVideoElement, etc.} - */ - - function RecordRTC(mediaStream, config) { - if (!mediaStream) { - throw 'First parameter is required.'; - } - - config = config || { - type: 'video' - }; - - config = new RecordRTCConfiguration(mediaStream, config); - - // a reference to user's recordRTC object - var self = this; - - function startRecording(config2) { - if (!config.disableLogs) { - console.log('RecordRTC version: ', self.version); - } - - if (!!config2) { - // allow users to set options using startRecording method - // config2 is similar to main "config" object (second parameter over RecordRTC constructor) - config = new RecordRTCConfiguration(mediaStream, config2); - } - - if (!config.disableLogs) { - console.log('started recording ' + config.type + ' stream.'); - } - - if (mediaRecorder) { - mediaRecorder.clearRecordedData(); - mediaRecorder.record(); - - setState('recording'); - - if (self.recordingDuration) { - handleRecordingDuration(); - } - return self; - } - - initRecorder(function () { - if (self.recordingDuration) { - handleRecordingDuration(); - } - }); - - return self; - } - - function initRecorder(initCallback) { - if (initCallback) { - config.initCallback = function () { - initCallback(); - initCallback = config.initCallback = null; // recorder.initRecorder should be call-backed once. - }; - } - - var Recorder = new GetRecorderType(mediaStream, config); - - mediaRecorder = new Recorder(mediaStream, config); - mediaRecorder.record(); - - setState('recording'); - - if (!config.disableLogs) { - console.log('Initialized recorderType:', mediaRecorder.constructor.name, 'for output-type:', config.type); - } - } - - function stopRecording(callback) { - callback = callback || function () { }; - - if (!mediaRecorder) { - warningLog(); - return; - } - - if (self.state === 'paused') { - self.resumeRecording(); - - setTimeout(function () { - stopRecording(callback); - }, 1); - return; - } - - if (self.state !== 'recording' && !config.disableLogs) { - console.warn('Recording state should be: "recording", however current state is: ', self.state); - } - - if (!config.disableLogs) { - console.log('Stopped recording ' + config.type + ' stream.'); - } - - if (config.type !== 'gif') { - mediaRecorder.stop(_callback); - } else { - mediaRecorder.stop(); - _callback(); - } - - setState('stopped'); - - function _callback(__blob) { - if (!mediaRecorder) { - if (typeof callback.call === 'function') { - callback.call(self, ''); - } else { - callback(''); - } - return; - } - - Object.keys(mediaRecorder).forEach(function (key) { - if (typeof mediaRecorder[key] === 'function') { - return; - } - - self[key] = mediaRecorder[key]; - }); - - var blob = mediaRecorder.blob; - - if (!blob) { - if (__blob) { - mediaRecorder.blob = blob = __blob; - } else { - throw 'Recording failed.'; - } - } - - if (blob && !config.disableLogs) { - console.log(blob.type, '->', bytesToSize(blob.size)); - } - - if (callback) { - var url; - - try { - url = URL.createObjectURL(blob); - } catch (e) { } - - if (typeof callback.call === 'function') { - callback.call(self, url); - } else { - callback(url); - } - } - - if (!config.autoWriteToDisk) { - return; - } - - getDataURL(function (dataURL) { - var parameter = {}; - parameter[config.type + 'Blob'] = dataURL; - DiskStorage.Store(parameter); - }); - } - } - - function pauseRecording() { - if (!mediaRecorder) { - warningLog(); - return; - } - - if (self.state !== 'recording') { - if (!config.disableLogs) { - console.warn('Unable to pause the recording. Recording state: ', self.state); - } - return; - } - - setState('paused'); - - mediaRecorder.pause(); - - if (!config.disableLogs) { - console.log('Paused recording.'); - } - } - - function resumeRecording() { - if (!mediaRecorder) { - warningLog(); - return; - } - - if (self.state !== 'paused') { - if (!config.disableLogs) { - console.warn('Unable to resume the recording. Recording state: ', self.state); - } - return; - } - - setState('recording'); - - // not all libs have this method yet - mediaRecorder.resume(); - - if (!config.disableLogs) { - console.log('Resumed recording.'); - } - } - - function readFile(_blob) { - postMessage(new FileReaderSync().readAsDataURL(_blob)); - } - - function getDataURL(callback, _mediaRecorder) { - if (!callback) { - throw 'Pass a callback function over getDataURL.'; - } - - var blob = _mediaRecorder ? _mediaRecorder.blob : (mediaRecorder || {}).blob; - - if (!blob) { - if (!config.disableLogs) { - console.warn('Blob encoder did not finish its job yet.'); - } - - setTimeout(function () { - getDataURL(callback, _mediaRecorder); - }, 1000); - return; - } - - if (typeof Worker !== 'undefined' && !navigator.mozGetUserMedia) { - var webWorker = processInWebWorker(readFile); - - webWorker.onmessage = function (event) { - callback(event.data); - }; - - webWorker.postMessage(blob); - } else { - var reader = new FileReader(); - reader.readAsDataURL(blob); - reader.onload = function (event) { - callback(event.target.result); - }; - } - - function processInWebWorker(_function) { - try { - var blob = URL.createObjectURL(new Blob([_function.toString(), - 'this.onmessage = function (eee) {' + _function.name + '(eee.data);}' - ], { - type: 'application/javascript' - })); - - var worker = new Worker(blob); - URL.revokeObjectURL(blob); - return worker; - } catch (e) { } - } - } - - function handleRecordingDuration(counter) { - counter = counter || 0; - - if (self.state === 'paused') { - setTimeout(function () { - handleRecordingDuration(counter); - }, 1000); - return; - } - - if (self.state === 'stopped') { - return; - } - - if (counter >= self.recordingDuration) { - stopRecording(self.onRecordingStopped); - return; - } - - counter += 1000; // 1-second - - setTimeout(function () { - handleRecordingDuration(counter); - }, 1000); - } - - function setState(state) { - if (!self) { - return; - } - - self.state = state; - - if (typeof self.onStateChanged.call === 'function') { - self.onStateChanged.call(self, state); - } else { - self.onStateChanged(state); - } - } - - var WARNING = 'It seems that recorder is destroyed or "startRecording" is not invoked for ' + config.type + ' recorder.'; - - function warningLog() { - if (config.disableLogs === true) { - return; - } - - console.warn(WARNING); - } - - var mediaRecorder; - - var returnObject = { - /** - * This method starts the recording. - * @method - * @memberof RecordRTC - * @instance - * @example - * var recorder = RecordRTC(mediaStream, { - * type: 'video' - * }); - * recorder.startRecording(); - */ - startRecording: startRecording, - - /** - * This method stops the recording. It is strongly recommended to get "blob" or "URI" inside the callback to make sure all recorders finished their job. - * @param {function} callback - Callback to get the recorded blob. - * @method - * @memberof RecordRTC - * @instance - * @example - * recorder.stopRecording(function() { - * // use either "this" or "recorder" object; both are identical - * video.src = this.toURL(); - * var blob = this.getBlob(); - * }); - */ - stopRecording: stopRecording, - - /** - * This method pauses the recording. You can resume recording using "resumeRecording" method. - * @method - * @memberof RecordRTC - * @instance - * @todo Firefox is unable to pause the recording. Fix it. - * @example - * recorder.pauseRecording(); // pause the recording - * recorder.resumeRecording(); // resume again - */ - pauseRecording: pauseRecording, - - /** - * This method resumes the recording. - * @method - * @memberof RecordRTC - * @instance - * @example - * recorder.pauseRecording(); // first of all, pause the recording - * recorder.resumeRecording(); // now resume it - */ - resumeRecording: resumeRecording, - - /** - * This method initializes the recording. - * @method - * @memberof RecordRTC - * @instance - * @todo This method should be deprecated. - * @example - * recorder.initRecorder(); - */ - initRecorder: initRecorder, - - /** - * Ask RecordRTC to auto-stop the recording after 5 minutes. - * @method - * @memberof RecordRTC - * @instance - * @example - * var fiveMinutes = 5 * 1000 * 60; - * recorder.setRecordingDuration(fiveMinutes, function() { - * var blob = this.getBlob(); - * video.src = this.toURL(); - * }); - * - * // or otherwise - * recorder.setRecordingDuration(fiveMinutes).onRecordingStopped(function() { - * var blob = this.getBlob(); - * video.src = this.toURL(); - * }); - */ - setRecordingDuration: function (recordingDuration, callback) { - if (typeof recordingDuration === 'undefined') { - throw 'recordingDuration is required.'; - } - - if (typeof recordingDuration !== 'number') { - throw 'recordingDuration must be a number.'; - } - - self.recordingDuration = recordingDuration; - self.onRecordingStopped = callback || function () { }; - - return { - onRecordingStopped: function (callback) { - self.onRecordingStopped = callback; - } - }; - }, - - /** - * This method can be used to clear/reset all the recorded data. - * @method - * @memberof RecordRTC - * @instance - * @todo Figure out the difference between "reset" and "clearRecordedData" methods. - * @example - * recorder.clearRecordedData(); - */ - clearRecordedData: function () { - if (!mediaRecorder) { - warningLog(); - return; - } - - mediaRecorder.clearRecordedData(); - - if (!config.disableLogs) { - console.log('Cleared old recorded data.'); - } - }, - - /** - * Get the recorded blob. Use this method inside the "stopRecording" callback. - * @method - * @memberof RecordRTC - * @instance - * @example - * recorder.stopRecording(function() { - * var blob = this.getBlob(); - * - * var file = new File([blob], 'filename.webm', { - * type: 'video/webm' - * }); - * - * var formData = new FormData(); - * formData.append('file', file); // upload "File" object rather than a "Blob" - * uploadToServer(formData); - * }); - * @returns {Blob} Returns recorded data as "Blob" object. - */ - getBlob: function () { - if (!mediaRecorder) { - warningLog(); - return; - } - - return mediaRecorder.blob; - }, - - /** - * Get data-URI instead of Blob. - * @param {function} callback - Callback to get the Data-URI. - * @method - * @memberof RecordRTC - * @instance - * @example - * recorder.stopRecording(function() { - * recorder.getDataURL(function(dataURI) { - * video.src = dataURI; - * }); - * }); - */ - getDataURL: getDataURL, - - /** - * Get virtual/temporary URL. Usage of this URL is limited to current tab. - * @method - * @memberof RecordRTC - * @instance - * @example - * recorder.stopRecording(function() { - * video.src = this.toURL(); - * }); - * @returns {String} Returns a virtual/temporary URL for the recorded "Blob". - */ - toURL: function () { - if (!mediaRecorder) { - warningLog(); - return; - } - - return URL.createObjectURL(mediaRecorder.blob); - }, - - /** - * Get internal recording object (i.e. internal module) e.g. MutliStreamRecorder, MediaStreamRecorder, StereoAudioRecorder or WhammyRecorder etc. - * @method - * @memberof RecordRTC - * @instance - * @example - * var internalRecorder = recorder.getInternalRecorder(); - * if(internalRecorder instanceof MultiStreamRecorder) { - * internalRecorder.addStreams([newAudioStream]); - * internalRecorder.resetVideoStreams([screenStream]); - * } - * @returns {Object} Returns internal recording object. - */ - getInternalRecorder: function () { - return mediaRecorder; - }, - - /** - * Invoke save-as dialog to save the recorded blob into your disk. - * @param {string} fileName - Set your own file name. - * @method - * @memberof RecordRTC - * @instance - * @example - * recorder.stopRecording(function() { - * this.save('file-name'); - * - * // or manually: - * invokeSaveAsDialog(this.getBlob(), 'filename.webm'); - * }); - */ - save: function (fileName) { - if (!mediaRecorder) { - warningLog(); - return; - } - - invokeSaveAsDialog(mediaRecorder.blob, fileName); - }, - - /** - * This method gets a blob from indexed-DB storage. - * @param {function} callback - Callback to get the recorded blob. - * @method - * @memberof RecordRTC - * @instance - * @example - * recorder.getFromDisk(function(dataURL) { - * video.src = dataURL; - * }); - */ - getFromDisk: function (callback) { - if (!mediaRecorder) { - warningLog(); - return; - } - - RecordRTC.getFromDisk(config.type, callback); - }, - - /** - * This method appends an array of webp images to the recorded video-blob. It takes an "array" object. - * @type {Array.} - * @param {Array} arrayOfWebPImages - Array of webp images. - * @method - * @memberof RecordRTC - * @instance - * @todo This method should be deprecated. - * @example - * var arrayOfWebPImages = []; - * arrayOfWebPImages.push({ - * duration: index, - * image: 'data:image/webp;base64,...' - * }); - * recorder.setAdvertisementArray(arrayOfWebPImages); - */ - setAdvertisementArray: function (arrayOfWebPImages) { - config.advertisement = []; - - var length = arrayOfWebPImages.length; - for (var i = 0; i < length; i++) { - config.advertisement.push({ - duration: i, - image: arrayOfWebPImages[i] - }); - } - }, - - /** - * It is equivalent to "recorder.getBlob()" method. Usage of "getBlob" is recommended, though. - * @property {Blob} blob - Recorded Blob can be accessed using this property. - * @memberof RecordRTC - * @instance - * @readonly - * @example - * recorder.stopRecording(function() { - * var blob = this.blob; - * - * // below one is recommended - * var blob = this.getBlob(); - * }); - */ - blob: null, - - /** - * This works only with {recorderType:StereoAudioRecorder}. Use this property on "stopRecording" to verify the encoder's sample-rates. - * @property {number} bufferSize - Buffer-size used to encode the WAV container - * @memberof RecordRTC - * @instance - * @readonly - * @example - * recorder.stopRecording(function() { - * alert('Recorder used this buffer-size: ' + this.bufferSize); - * }); - */ - bufferSize: 0, - - /** - * This works only with {recorderType:StereoAudioRecorder}. Use this property on "stopRecording" to verify the encoder's sample-rates. - * @property {number} sampleRate - Sample-rates used to encode the WAV container - * @memberof RecordRTC - * @instance - * @readonly - * @example - * recorder.stopRecording(function() { - * alert('Recorder used these sample-rates: ' + this.sampleRate); - * }); - */ - sampleRate: 0, - - /** - * {recorderType:StereoAudioRecorder} returns ArrayBuffer object. - * @property {ArrayBuffer} buffer - Audio ArrayBuffer, supported only in Chrome. - * @memberof RecordRTC - * @instance - * @readonly - * @example - * recorder.stopRecording(function() { - * var arrayBuffer = this.buffer; - * alert(arrayBuffer.byteLength); - * }); - */ - buffer: null, - - /** - * This method resets the recorder. So that you can reuse single recorder instance many times. - * @method - * @memberof RecordRTC - * @instance - * @example - * recorder.reset(); - * recorder.startRecording(); - */ - reset: function () { - if (self.state === 'recording' && !config.disableLogs) { - console.warn('Stop an active recorder.'); - } - - if (mediaRecorder && typeof mediaRecorder.clearRecordedData === 'function') { - mediaRecorder.clearRecordedData(); - } - mediaRecorder = null; - setState('inactive'); - self.blob = null; - }, - - /** - * This method is called whenever recorder's state changes. Use this as an "event". - * @property {String} state - A recorder's state can be: recording, paused, stopped or inactive. - * @method - * @memberof RecordRTC - * @instance - * @example - * recorder.onStateChanged = function(state) { - * console.log('Recorder state: ', state); - * }; - */ - onStateChanged: function (state) { - if (!config.disableLogs) { - console.log('Recorder state changed:', state); - } - }, - - /** - * A recorder can have inactive, recording, paused or stopped states. - * @property {String} state - A recorder's state can be: recording, paused, stopped or inactive. - * @memberof RecordRTC - * @static - * @readonly - * @example - * // this looper function will keep you updated about the recorder's states. - * (function looper() { - * document.querySelector('h1').innerHTML = 'Recorder\'s state is: ' + recorder.state; - * if(recorder.state === 'stopped') return; // ignore+stop - * setTimeout(looper, 1000); // update after every 3-seconds - * })(); - * recorder.startRecording(); - */ - state: 'inactive', - - /** - * Get recorder's readonly state. - * @method - * @memberof RecordRTC - * @example - * var state = recorder.getState(); - * @returns {String} Returns recording state. - */ - getState: function () { - return self.state; - }, - - /** - * Destroy RecordRTC instance. Clear all recorders and objects. - * @method - * @memberof RecordRTC - * @example - * recorder.destroy(); - */ - destroy: function () { - var disableLogsCache = config.disableLogs; - - config = { - disableLogs: true - }; - self.reset(); - setState('destroyed'); - returnObject = self = null; - - if (Storage.AudioContextConstructor) { - Storage.AudioContextConstructor.close(); - Storage.AudioContextConstructor = null; - } - - config.disableLogs = disableLogsCache; - - if (!config.disableLogs) { - console.log('RecordRTC is destroyed.'); - } - }, - - /** - * RecordRTC version number - * @property {String} version - Release version number. - * @memberof RecordRTC - * @static - * @readonly - * @example - * alert(recorder.version); - */ - version: '5.6.2' - }; - - if (!this) { - self = returnObject; - return returnObject; - } - - // if someone wants to use RecordRTC with the "new" keyword. - for (var prop in returnObject) { - this[prop] = returnObject[prop]; - } - - self = this; - - return returnObject; - } - - RecordRTC.version = '5.6.2'; - - { - module.exports = RecordRTC; - } - - RecordRTC.getFromDisk = function (type, callback) { - if (!callback) { - throw 'callback is mandatory.'; - } - - console.log('Getting recorded ' + (type === 'all' ? 'blobs' : type + ' blob ') + ' from disk!'); - DiskStorage.Fetch(function (dataURL, _type) { - if (type !== 'all' && _type === type + 'Blob' && callback) { - callback(dataURL); - } - - if (type === 'all' && callback) { - callback(dataURL, _type.replace('Blob', '')); - } - }); - }; - - /** - * This method can be used to store recorded blobs into IndexedDB storage. - * @param {object} options - {audio: Blob, video: Blob, gif: Blob} - * @method - * @memberof RecordRTC - * @example - * RecordRTC.writeToDisk({ - * audio: audioBlob, - * video: videoBlob, - * gif : gifBlob - * }); - */ - RecordRTC.writeToDisk = function (options) { - console.log('Writing recorded blob(s) to disk!'); - options = options || {}; - if (options.audio && options.video && options.gif) { - options.audio.getDataURL(function (audioDataURL) { - options.video.getDataURL(function (videoDataURL) { - options.gif.getDataURL(function (gifDataURL) { - DiskStorage.Store({ - audioBlob: audioDataURL, - videoBlob: videoDataURL, - gifBlob: gifDataURL - }); - }); - }); - }); - } else if (options.audio && options.video) { - options.audio.getDataURL(function (audioDataURL) { - options.video.getDataURL(function (videoDataURL) { - DiskStorage.Store({ - audioBlob: audioDataURL, - videoBlob: videoDataURL - }); - }); - }); - } else if (options.audio && options.gif) { - options.audio.getDataURL(function (audioDataURL) { - options.gif.getDataURL(function (gifDataURL) { - DiskStorage.Store({ - audioBlob: audioDataURL, - gifBlob: gifDataURL - }); - }); - }); - } else if (options.video && options.gif) { - options.video.getDataURL(function (videoDataURL) { - options.gif.getDataURL(function (gifDataURL) { - DiskStorage.Store({ - videoBlob: videoDataURL, - gifBlob: gifDataURL - }); - }); - }); - } else if (options.audio) { - options.audio.getDataURL(function (audioDataURL) { - DiskStorage.Store({ - audioBlob: audioDataURL - }); - }); - } else if (options.video) { - options.video.getDataURL(function (videoDataURL) { - DiskStorage.Store({ - videoBlob: videoDataURL - }); - }); - } else if (options.gif) { - options.gif.getDataURL(function (gifDataURL) { - DiskStorage.Store({ - gifBlob: gifDataURL - }); - }); - } - }; - - // __________________________ - // RecordRTC-Configuration.js - - /** - * {@link RecordRTCConfiguration} is an inner/private helper for {@link RecordRTC}. - * @summary It configures the 2nd parameter passed over {@link RecordRTC} and returns a valid "config" object. - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @typedef RecordRTCConfiguration - * @class - * @example - * var options = RecordRTCConfiguration(mediaStream, options); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. - * @param {object} config - {type:"video", disableLogs: true, numberOfAudioChannels: 1, bufferSize: 0, sampleRate: 0, video: HTMLVideoElement, getNativeBlob:true, etc.} - */ - - function RecordRTCConfiguration(mediaStream, config) { - if (!config.recorderType && !config.type) { - if (!!config.audio && !!config.video) { - config.type = 'video'; - } else if (!!config.audio && !config.video) { - config.type = 'audio'; - } - } - - if (config.recorderType && !config.type) { - if (config.recorderType === WhammyRecorder || config.recorderType === CanvasRecorder || (typeof WebAssemblyRecorder !== 'undefined' && config.recorderType === WebAssemblyRecorder)) { - config.type = 'video'; - } else if (config.recorderType === GifRecorder) { - config.type = 'gif'; - } else if (config.recorderType === StereoAudioRecorder) { - config.type = 'audio'; - } else if (config.recorderType === MediaStreamRecorder) { - if (getTracks(mediaStream, 'audio').length && getTracks(mediaStream, 'video').length) { - config.type = 'video'; - } else if (!getTracks(mediaStream, 'audio').length && getTracks(mediaStream, 'video').length) { - config.type = 'video'; - } else if (getTracks(mediaStream, 'audio').length && !getTracks(mediaStream, 'video').length) { - config.type = 'audio'; - } else; - } - } - - if (typeof MediaStreamRecorder !== 'undefined' && typeof MediaRecorder !== 'undefined' && 'requestData' in MediaRecorder.prototype) { - if (!config.mimeType) { - config.mimeType = 'video/webm'; - } - - if (!config.type) { - config.type = config.mimeType.split('/')[0]; - } - - if (!config.bitsPerSecond); - } - - // consider default type=audio - if (!config.type) { - if (config.mimeType) { - config.type = config.mimeType.split('/')[0]; - } - if (!config.type) { - config.type = 'audio'; - } - } - - return config; - } - - // __________________ - // GetRecorderType.js - - /** - * {@link GetRecorderType} is an inner/private helper for {@link RecordRTC}. - * @summary It returns best recorder-type available for your browser. - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @typedef GetRecorderType - * @class - * @example - * var RecorderType = GetRecorderType(options); - * var recorder = new RecorderType(options); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. - * @param {object} config - {type:"video", disableLogs: true, numberOfAudioChannels: 1, bufferSize: 0, sampleRate: 0, video: HTMLVideoElement, etc.} - */ - - function GetRecorderType(mediaStream, config) { - var recorder; - - // StereoAudioRecorder can work with all three: Edge, Firefox and Chrome - // todo: detect if it is Edge, then auto use: StereoAudioRecorder - if (isChrome || isEdge || isOpera) { - // Media Stream Recording API has not been implemented in chrome yet; - // That's why using WebAudio API to record stereo audio in WAV format - recorder = StereoAudioRecorder; - } - - if (typeof MediaRecorder !== 'undefined' && 'requestData' in MediaRecorder.prototype && !isChrome) { - recorder = MediaStreamRecorder; - } - - // video recorder (in WebM format) - if (config.type === 'video' && (isChrome || isOpera)) { - recorder = WhammyRecorder; - - if (typeof WebAssemblyRecorder !== 'undefined' && typeof ReadableStream !== 'undefined') { - recorder = WebAssemblyRecorder; - } - } - - // video recorder (in Gif format) - if (config.type === 'gif') { - recorder = GifRecorder; - } - - // html2canvas recording! - if (config.type === 'canvas') { - recorder = CanvasRecorder; - } - - if (isMediaRecorderCompatible() && recorder !== CanvasRecorder && recorder !== GifRecorder && typeof MediaRecorder !== 'undefined' && 'requestData' in MediaRecorder.prototype) { - if (getTracks(mediaStream, 'video').length || getTracks(mediaStream, 'audio').length) { - // audio-only recording - if (config.type === 'audio') { - if (typeof MediaRecorder.isTypeSupported === 'function' && MediaRecorder.isTypeSupported('audio/webm')) { - recorder = MediaStreamRecorder; - } - // else recorder = StereoAudioRecorder; - } else { - // video or screen tracks - if (typeof MediaRecorder.isTypeSupported === 'function' && MediaRecorder.isTypeSupported('video/webm')) { - recorder = MediaStreamRecorder; - } - } - } - } - - if (mediaStream instanceof Array && mediaStream.length) { - recorder = MultiStreamRecorder; - } - - if (config.recorderType) { - recorder = config.recorderType; - } - - if (!config.disableLogs && !!recorder && !!recorder.name) { - console.log('Using recorderType:', recorder.name || recorder.constructor.name); - } - - if (!recorder && isSafari) { - recorder = MediaStreamRecorder; - } - - return recorder; - } - - // _____________ - // MRecordRTC.js - - /** - * MRecordRTC runs on top of {@link RecordRTC} to bring multiple recordings in a single place, by providing simple API. - * @summary MRecordRTC stands for "Multiple-RecordRTC". - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @typedef MRecordRTC - * @class - * @example - * var recorder = new MRecordRTC(); - * recorder.addStream(MediaStream); - * recorder.mediaType = { - * audio: true, // or StereoAudioRecorder or MediaStreamRecorder - * video: true, // or WhammyRecorder or MediaStreamRecorder or WebAssemblyRecorder or CanvasRecorder - * gif: true // or GifRecorder - * }; - * // mimeType is optional and should be set only in advance cases. - * recorder.mimeType = { - * audio: 'audio/wav', - * video: 'video/webm', - * gif: 'image/gif' - * }; - * recorder.startRecording(); - * @see For further information: - * @see {@link https://github.com/muaz-khan/RecordRTC/tree/master/MRecordRTC|MRecordRTC Source Code} - * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. - * @requires {@link RecordRTC} - */ - - function MRecordRTC(mediaStream) { - - /** - * This method attaches MediaStream object to {@link MRecordRTC}. - * @param {MediaStream} mediaStream - A MediaStream object, either fetched using getUserMedia API, or generated using captureStreamUntilEnded or WebAudio API. - * @method - * @memberof MRecordRTC - * @example - * recorder.addStream(MediaStream); - */ - this.addStream = function (_mediaStream) { - if (_mediaStream) { - mediaStream = _mediaStream; - } - }; - - /** - * This property can be used to set the recording type e.g. audio, or video, or gif, or canvas. - * @property {object} mediaType - {audio: true, video: true, gif: true} - * @memberof MRecordRTC - * @example - * var recorder = new MRecordRTC(); - * recorder.mediaType = { - * audio: true, // TRUE or StereoAudioRecorder or MediaStreamRecorder - * video: true, // TRUE or WhammyRecorder or MediaStreamRecorder or WebAssemblyRecorder or CanvasRecorder - * gif : true // TRUE or GifRecorder - * }; - */ - this.mediaType = { - audio: true, - video: true - }; - - /** - * This method starts recording. - * @method - * @memberof MRecordRTC - * @example - * recorder.startRecording(); - */ - this.startRecording = function () { - var mediaType = this.mediaType; - var recorderType; - var mimeType = this.mimeType || { - audio: null, - video: null, - gif: null - }; - - if (typeof mediaType.audio !== 'function' && isMediaRecorderCompatible() && !getTracks(mediaStream, 'audio').length) { - mediaType.audio = false; - } - - if (typeof mediaType.video !== 'function' && isMediaRecorderCompatible() && !getTracks(mediaStream, 'video').length) { - mediaType.video = false; - } - - if (typeof mediaType.gif !== 'function' && isMediaRecorderCompatible() && !getTracks(mediaStream, 'video').length) { - mediaType.gif = false; - } - - if (!mediaType.audio && !mediaType.video && !mediaType.gif) { - throw 'MediaStream must have either audio or video tracks.'; - } - - if (!!mediaType.audio) { - recorderType = null; - if (typeof mediaType.audio === 'function') { - recorderType = mediaType.audio; - } - - this.audioRecorder = new RecordRTC(mediaStream, { - type: 'audio', - bufferSize: this.bufferSize, - sampleRate: this.sampleRate, - numberOfAudioChannels: this.numberOfAudioChannels || 2, - disableLogs: this.disableLogs, - recorderType: recorderType, - mimeType: mimeType.audio, - timeSlice: this.timeSlice, - onTimeStamp: this.onTimeStamp - }); - - if (!mediaType.video) { - this.audioRecorder.startRecording(); - } - } - - if (!!mediaType.video) { - recorderType = null; - if (typeof mediaType.video === 'function') { - recorderType = mediaType.video; - } - - var newStream = mediaStream; - - if (isMediaRecorderCompatible() && !!mediaType.audio && typeof mediaType.audio === 'function') { - var videoTrack = getTracks(mediaStream, 'video')[0]; - - if (isFirefox) { - newStream = new MediaStream(); - newStream.addTrack(videoTrack); - - if (recorderType && recorderType === WhammyRecorder) { - // Firefox does NOT supports webp-encoding yet - // But Firefox do supports WebAssemblyRecorder - recorderType = MediaStreamRecorder; - } - } else { - newStream = new MediaStream(); - newStream.addTrack(videoTrack); - } - } - - this.videoRecorder = new RecordRTC(newStream, { - type: 'video', - video: this.video, - canvas: this.canvas, - frameInterval: this.frameInterval || 10, - disableLogs: this.disableLogs, - recorderType: recorderType, - mimeType: mimeType.video, - timeSlice: this.timeSlice, - onTimeStamp: this.onTimeStamp, - workerPath: this.workerPath, - webAssemblyPath: this.webAssemblyPath, - frameRate: this.frameRate, // used by WebAssemblyRecorder; values: usually 30; accepts any. - bitrate: this.bitrate // used by WebAssemblyRecorder; values: 0 to 1000+ - }); - - if (!mediaType.audio) { - this.videoRecorder.startRecording(); - } - } - - if (!!mediaType.audio && !!mediaType.video) { - var self = this; - - var isSingleRecorder = isMediaRecorderCompatible() === true; - - if (mediaType.audio instanceof StereoAudioRecorder && !!mediaType.video) { - isSingleRecorder = false; - } else if (mediaType.audio !== true && mediaType.video !== true && mediaType.audio !== mediaType.video) { - isSingleRecorder = false; - } - - if (isSingleRecorder === true) { - self.audioRecorder = null; - self.videoRecorder.startRecording(); - } else { - self.videoRecorder.initRecorder(function () { - self.audioRecorder.initRecorder(function () { - // Both recorders are ready to record things accurately - self.videoRecorder.startRecording(); - self.audioRecorder.startRecording(); - }); - }); - } - } - - if (!!mediaType.gif) { - recorderType = null; - if (typeof mediaType.gif === 'function') { - recorderType = mediaType.gif; - } - this.gifRecorder = new RecordRTC(mediaStream, { - type: 'gif', - frameRate: this.frameRate || 200, - quality: this.quality || 10, - disableLogs: this.disableLogs, - recorderType: recorderType, - mimeType: mimeType.gif - }); - this.gifRecorder.startRecording(); - } - }; - - /** - * This method stops recording. - * @param {function} callback - Callback function is invoked when all encoders finished their jobs. - * @method - * @memberof MRecordRTC - * @example - * recorder.stopRecording(function(recording){ - * var audioBlob = recording.audio; - * var videoBlob = recording.video; - * var gifBlob = recording.gif; - * }); - */ - this.stopRecording = function (callback) { - callback = callback || function () { }; - - if (this.audioRecorder) { - this.audioRecorder.stopRecording(function (blobURL) { - callback(blobURL, 'audio'); - }); - } - - if (this.videoRecorder) { - this.videoRecorder.stopRecording(function (blobURL) { - callback(blobURL, 'video'); - }); - } - - if (this.gifRecorder) { - this.gifRecorder.stopRecording(function (blobURL) { - callback(blobURL, 'gif'); - }); - } - }; - - /** - * This method pauses recording. - * @method - * @memberof MRecordRTC - * @example - * recorder.pauseRecording(); - */ - this.pauseRecording = function () { - if (this.audioRecorder) { - this.audioRecorder.pauseRecording(); - } - - if (this.videoRecorder) { - this.videoRecorder.pauseRecording(); - } - - if (this.gifRecorder) { - this.gifRecorder.pauseRecording(); - } - }; - - /** - * This method resumes recording. - * @method - * @memberof MRecordRTC - * @example - * recorder.resumeRecording(); - */ - this.resumeRecording = function () { - if (this.audioRecorder) { - this.audioRecorder.resumeRecording(); - } - - if (this.videoRecorder) { - this.videoRecorder.resumeRecording(); - } - - if (this.gifRecorder) { - this.gifRecorder.resumeRecording(); - } - }; - - /** - * This method can be used to manually get all recorded blobs. - * @param {function} callback - All recorded blobs are passed back to the "callback" function. - * @method - * @memberof MRecordRTC - * @example - * recorder.getBlob(function(recording){ - * var audioBlob = recording.audio; - * var videoBlob = recording.video; - * var gifBlob = recording.gif; - * }); - * // or - * var audioBlob = recorder.getBlob().audio; - * var videoBlob = recorder.getBlob().video; - */ - this.getBlob = function (callback) { - var output = {}; - - if (this.audioRecorder) { - output.audio = this.audioRecorder.getBlob(); - } - - if (this.videoRecorder) { - output.video = this.videoRecorder.getBlob(); - } - - if (this.gifRecorder) { - output.gif = this.gifRecorder.getBlob(); - } - - if (callback) { - callback(output); - } - - return output; - }; - - /** - * Destroy all recorder instances. - * @method - * @memberof MRecordRTC - * @example - * recorder.destroy(); - */ - this.destroy = function () { - if (this.audioRecorder) { - this.audioRecorder.destroy(); - this.audioRecorder = null; - } - - if (this.videoRecorder) { - this.videoRecorder.destroy(); - this.videoRecorder = null; - } - - if (this.gifRecorder) { - this.gifRecorder.destroy(); - this.gifRecorder = null; - } - }; - - /** - * This method can be used to manually get all recorded blobs' DataURLs. - * @param {function} callback - All recorded blobs' DataURLs are passed back to the "callback" function. - * @method - * @memberof MRecordRTC - * @example - * recorder.getDataURL(function(recording){ - * var audioDataURL = recording.audio; - * var videoDataURL = recording.video; - * var gifDataURL = recording.gif; - * }); - */ - this.getDataURL = function (callback) { - this.getBlob(function (blob) { - if (blob.audio && blob.video) { - getDataURL(blob.audio, function (_audioDataURL) { - getDataURL(blob.video, function (_videoDataURL) { - callback({ - audio: _audioDataURL, - video: _videoDataURL - }); - }); - }); - } else if (blob.audio) { - getDataURL(blob.audio, function (_audioDataURL) { - callback({ - audio: _audioDataURL - }); - }); - } else if (blob.video) { - getDataURL(blob.video, function (_videoDataURL) { - callback({ - video: _videoDataURL - }); - }); - } - }); - - function getDataURL(blob, callback00) { - if (typeof Worker !== 'undefined') { - var webWorker = processInWebWorker(function readFile(_blob) { - postMessage(new FileReaderSync().readAsDataURL(_blob)); - }); - - webWorker.onmessage = function (event) { - callback00(event.data); - }; - - webWorker.postMessage(blob); - } else { - var reader = new FileReader(); - reader.readAsDataURL(blob); - reader.onload = function (event) { - callback00(event.target.result); - }; - } - } - - function processInWebWorker(_function) { - var blob = URL.createObjectURL(new Blob([_function.toString(), - 'this.onmessage = function (eee) {' + _function.name + '(eee.data);}' - ], { - type: 'application/javascript' - })); - - var worker = new Worker(blob); - var url; - if (typeof URL !== 'undefined') { - url = URL; - } else if (typeof webkitURL !== 'undefined') { - url = webkitURL; - } else { - throw 'Neither URL nor webkitURL detected.'; - } - url.revokeObjectURL(blob); - return worker; - } - }; - - /** - * This method can be used to ask {@link MRecordRTC} to write all recorded blobs into IndexedDB storage. - * @method - * @memberof MRecordRTC - * @example - * recorder.writeToDisk(); - */ - this.writeToDisk = function () { - RecordRTC.writeToDisk({ - audio: this.audioRecorder, - video: this.videoRecorder, - gif: this.gifRecorder - }); - }; - - /** - * This method can be used to invoke a save-as dialog for all recorded blobs. - * @param {object} args - {audio: 'audio-name', video: 'video-name', gif: 'gif-name'} - * @method - * @memberof MRecordRTC - * @example - * recorder.save({ - * audio: 'audio-file-name', - * video: 'video-file-name', - * gif : 'gif-file-name' - * }); - */ - this.save = function (args) { - args = args || { - audio: true, - video: true, - gif: true - }; - - if (!!args.audio && this.audioRecorder) { - this.audioRecorder.save(typeof args.audio === 'string' ? args.audio : ''); - } - - if (!!args.video && this.videoRecorder) { - this.videoRecorder.save(typeof args.video === 'string' ? args.video : ''); - } - if (!!args.gif && this.gifRecorder) { - this.gifRecorder.save(typeof args.gif === 'string' ? args.gif : ''); - } - }; - } - - /** - * This method can be used to get all recorded blobs from IndexedDB storage. - * @param {string} type - 'all' or 'audio' or 'video' or 'gif' - * @param {function} callback - Callback function to get all stored blobs. - * @method - * @memberof MRecordRTC - * @example - * MRecordRTC.getFromDisk('all', function(dataURL, type){ - * if(type === 'audio') { } - * if(type === 'video') { } - * if(type === 'gif') { } - * }); - */ - MRecordRTC.getFromDisk = RecordRTC.getFromDisk; - - /** - * This method can be used to store recorded blobs into IndexedDB storage. - * @param {object} options - {audio: Blob, video: Blob, gif: Blob} - * @method - * @memberof MRecordRTC - * @example - * MRecordRTC.writeToDisk({ - * audio: audioBlob, - * video: videoBlob, - * gif : gifBlob - * }); - */ - MRecordRTC.writeToDisk = RecordRTC.writeToDisk; - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.MRecordRTC = MRecordRTC; - } - - var browserFakeUserAgent = 'Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45'; - - (function (that) { - if (!that) { - return; - } - - if (typeof window !== 'undefined') { - return; - } - - if (typeof commonjsGlobal === 'undefined') { - return; - } - - commonjsGlobal.navigator = { - userAgent: browserFakeUserAgent, - getUserMedia: function () { } - }; - - if (!commonjsGlobal.console) { - commonjsGlobal.console = {}; - } - - if (typeof commonjsGlobal.console.log === 'undefined' || typeof commonjsGlobal.console.error === 'undefined') { - commonjsGlobal.console.error = commonjsGlobal.console.log = commonjsGlobal.console.log || function () { - console.log(arguments); - }; - } - - if (typeof document === 'undefined') { - /*global document:true */ - that.document = { - documentElement: { - appendChild: function () { - return ''; - } - } - }; - - document.createElement = document.captureStream = document.mozCaptureStream = function () { - var obj = { - getContext: function () { - return obj; - }, - play: function () { }, - pause: function () { }, - drawImage: function () { }, - toDataURL: function () { - return ''; - }, - style: {} - }; - return obj; - }; - - that.HTMLVideoElement = function () { }; - } - - if (typeof location === 'undefined') { - /*global location:true */ - that.location = { - protocol: 'file:', - href: '', - hash: '' - }; - } - - if (typeof screen === 'undefined') { - /*global screen:true */ - that.screen = { - width: 0, - height: 0 - }; - } - - if (typeof URL === 'undefined') { - /*global screen:true */ - that.URL = { - createObjectURL: function () { - return ''; - }, - revokeObjectURL: function () { - return ''; - } - }; - } - - /*global window:true */ - that.window = commonjsGlobal; - })(typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : null); - - // _____________________________ - // Cross-Browser-Declarations.js - - // animation-frame used in WebM recording - - /*jshint -W079 */ - var requestAnimationFrame = window.requestAnimationFrame; - if (typeof requestAnimationFrame === 'undefined') { - if (typeof webkitRequestAnimationFrame !== 'undefined') { - /*global requestAnimationFrame:true */ - requestAnimationFrame = webkitRequestAnimationFrame; - } else if (typeof mozRequestAnimationFrame !== 'undefined') { - /*global requestAnimationFrame:true */ - requestAnimationFrame = mozRequestAnimationFrame; - } else if (typeof msRequestAnimationFrame !== 'undefined') { - /*global requestAnimationFrame:true */ - requestAnimationFrame = msRequestAnimationFrame; - } else if (typeof requestAnimationFrame === 'undefined') { - // via: https://gist.github.com/paulirish/1579671 - var lastTime = 0; - - /*global requestAnimationFrame:true */ - requestAnimationFrame = function (callback, element) { - var currTime = new Date().getTime(); - var timeToCall = Math.max(0, 16 - (currTime - lastTime)); - var id = setTimeout(function () { - callback(currTime + timeToCall); - }, timeToCall); - lastTime = currTime + timeToCall; - return id; - }; - } - } - - /*jshint -W079 */ - var cancelAnimationFrame = window.cancelAnimationFrame; - if (typeof cancelAnimationFrame === 'undefined') { - if (typeof webkitCancelAnimationFrame !== 'undefined') { - /*global cancelAnimationFrame:true */ - cancelAnimationFrame = webkitCancelAnimationFrame; - } else if (typeof mozCancelAnimationFrame !== 'undefined') { - /*global cancelAnimationFrame:true */ - cancelAnimationFrame = mozCancelAnimationFrame; - } else if (typeof msCancelAnimationFrame !== 'undefined') { - /*global cancelAnimationFrame:true */ - cancelAnimationFrame = msCancelAnimationFrame; - } else if (typeof cancelAnimationFrame === 'undefined') { - /*global cancelAnimationFrame:true */ - cancelAnimationFrame = function (id) { - clearTimeout(id); - }; - } - } - - // WebAudio API representer - var AudioContext = window.AudioContext; - - if (typeof AudioContext === 'undefined') { - if (typeof webkitAudioContext !== 'undefined') { - /*global AudioContext:true */ - AudioContext = webkitAudioContext; - } - - if (typeof mozAudioContext !== 'undefined') { - /*global AudioContext:true */ - AudioContext = mozAudioContext; - } - } - - /*jshint -W079 */ - var URL = window.URL; - - if (typeof URL === 'undefined' && typeof webkitURL !== 'undefined') { - /*global URL:true */ - URL = webkitURL; - } - - if (typeof navigator !== 'undefined' && typeof navigator.getUserMedia === 'undefined') { // maybe window.navigator? - if (typeof navigator.webkitGetUserMedia !== 'undefined') { - navigator.getUserMedia = navigator.webkitGetUserMedia; - } - - if (typeof navigator.mozGetUserMedia !== 'undefined') { - navigator.getUserMedia = navigator.mozGetUserMedia; - } - } - - var isEdge = navigator.userAgent.indexOf('Edge') !== -1 && (!!navigator.msSaveBlob || !!navigator.msSaveOrOpenBlob); - var isOpera = !!window.opera || navigator.userAgent.indexOf('OPR/') !== -1; - var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1 && ('netscape' in window) && / rv:/.test(navigator.userAgent); - var isChrome = (!isOpera && !isEdge && !!navigator.webkitGetUserMedia) || isElectron() || navigator.userAgent.toLowerCase().indexOf('chrome/') !== -1; - - var isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); - - if (isSafari && !isChrome && navigator.userAgent.indexOf('CriOS') !== -1) { - isSafari = false; - isChrome = true; - } - - var MediaStream = window.MediaStream; - - if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefined') { - MediaStream = webkitMediaStream; - } - - /*global MediaStream:true */ - if (typeof MediaStream !== 'undefined') { - // override "stop" method for all browsers - if (typeof MediaStream.prototype.stop === 'undefined') { - MediaStream.prototype.stop = function () { - this.getTracks().forEach(function (track) { - track.stop(); - }); - }; - } - } - - // below function via: http://goo.gl/B3ae8c - /** - * Return human-readable file size. - * @param {number} bytes - Pass bytes and get formatted string. - * @returns {string} - formatted string - * @example - * bytesToSize(1024*1024*5) === '5 GB' - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - */ - function bytesToSize(bytes) { - var k = 1000; - var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; - if (bytes === 0) { - return '0 Bytes'; - } - var i = parseInt(Math.floor(Math.log(bytes) / Math.log(k)), 10); - return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i]; - } - - /** - * @param {Blob} file - File or Blob object. This parameter is required. - * @param {string} fileName - Optional file name e.g. "Recorded-Video.webm" - * @example - * invokeSaveAsDialog(blob or file, [optional] fileName); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - */ - function invokeSaveAsDialog(file, fileName) { - if (!file) { - throw 'Blob object is required.'; - } - - if (!file.type) { - try { - file.type = 'video/webm'; - } catch (e) { } - } - - var fileExtension = (file.type || 'video/webm').split('/')[1]; - if (fileExtension.indexOf(';') !== -1) { - // extended mimetype, e.g. 'video/webm;codecs=vp8,opus' - fileExtension = fileExtension.split(';')[0]; - } - if (fileName && fileName.indexOf('.') !== -1) { - var splitted = fileName.split('.'); - fileName = splitted[0]; - fileExtension = splitted[1]; - } - - var fileFullName = (fileName || (Math.round(Math.random() * 9999999999) + 888888888)) + '.' + fileExtension; - - if (typeof navigator.msSaveOrOpenBlob !== 'undefined') { - return navigator.msSaveOrOpenBlob(file, fileFullName); - } else if (typeof navigator.msSaveBlob !== 'undefined') { - return navigator.msSaveBlob(file, fileFullName); - } - - var hyperlink = document.createElement('a'); - hyperlink.href = URL.createObjectURL(file); - hyperlink.download = fileFullName; - - hyperlink.style = 'display:none;opacity:0;color:transparent;'; - (document.body || document.documentElement).appendChild(hyperlink); - - if (typeof hyperlink.click === 'function') { - hyperlink.click(); - } else { - hyperlink.target = '_blank'; - hyperlink.dispatchEvent(new MouseEvent('click', { - view: window, - bubbles: true, - cancelable: true - })); - } - - URL.revokeObjectURL(hyperlink.href); - } - - /** - * from: https://github.com/cheton/is-electron/blob/master/index.js - **/ - function isElectron() { - // Renderer process - if (typeof window !== 'undefined' && typeof window.process === 'object' && window.process.type === 'renderer') { - return true; - } - - // Main process - if (typeof process !== 'undefined' && typeof process.versions === 'object' && !!process.versions.electron) { - return true; - } - - // Detect the user agent when the `nodeIntegration` option is set to true - if (typeof navigator === 'object' && typeof navigator.userAgent === 'string' && navigator.userAgent.indexOf('Electron') >= 0) { - return true; - } - - return false; - } - - function getTracks(stream, kind) { - if (!stream || !stream.getTracks) { - return []; - } - - return stream.getTracks().filter(function (t) { - return t.kind === (kind || 'audio'); - }); - } - - function setSrcObject(stream, element) { - if ('srcObject' in element) { - element.srcObject = stream; - } else if ('mozSrcObject' in element) { - element.mozSrcObject = stream; - } else { - element.srcObject = stream; - } - } - - /** - * @param {Blob} file - File or Blob object. - * @param {function} callback - Callback function. - * @example - * getSeekableBlob(blob or file, callback); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - */ - function getSeekableBlob(inputBlob, callback) { - // EBML.js copyrights goes to: https://github.com/legokichi/ts-ebml - if (typeof EBML === 'undefined') { - throw new Error('Please link: https://www.webrtc-experiment.com/EBML.js'); - } - - var reader = new EBML.Reader(); - var decoder = new EBML.Decoder(); - var tools = EBML.tools; - - var fileReader = new FileReader(); - fileReader.onload = function (e) { - var ebmlElms = decoder.decode(this.result); - ebmlElms.forEach(function (element) { - reader.read(element); - }); - reader.stop(); - var refinedMetadataBuf = tools.makeMetadataSeekable(reader.metadatas, reader.duration, reader.cues); - var body = this.result.slice(reader.metadataSize); - var newBlob = new Blob([refinedMetadataBuf, body], { - type: 'video/webm' - }); - - callback(newBlob); - }; - fileReader.readAsArrayBuffer(inputBlob); - } - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.invokeSaveAsDialog = invokeSaveAsDialog; - RecordRTC.getTracks = getTracks; - RecordRTC.getSeekableBlob = getSeekableBlob; - RecordRTC.bytesToSize = bytesToSize; - RecordRTC.isElectron = isElectron; - } - - // __________ (used to handle stuff like http://goo.gl/xmE5eg) issue #129 - // Storage.js - - /** - * Storage is a standalone object used by {@link RecordRTC} to store reusable objects e.g. "new AudioContext". - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @example - * Storage.AudioContext === webkitAudioContext - * @property {webkitAudioContext} AudioContext - Keeps a reference to AudioContext object. - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - */ - - var Storage = {}; - - if (typeof AudioContext !== 'undefined') { - Storage.AudioContext = AudioContext; - } else if (typeof webkitAudioContext !== 'undefined') { - Storage.AudioContext = webkitAudioContext; - } - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.Storage = Storage; - } - - function isMediaRecorderCompatible() { - if (isFirefox || isSafari || isEdge) { - return true; - } - var nAgt = navigator.userAgent; - var fullVersion = '' + parseFloat(navigator.appVersion); - var majorVersion = parseInt(navigator.appVersion, 10); - var verOffset, ix; - - if (isChrome || isOpera) { - verOffset = nAgt.indexOf('Chrome'); - fullVersion = nAgt.substring(verOffset + 7); - } - - // trim the fullVersion string at semicolon/space if present - if ((ix = fullVersion.indexOf(';')) !== -1) { - fullVersion = fullVersion.substring(0, ix); - } - - if ((ix = fullVersion.indexOf(' ')) !== -1) { - fullVersion = fullVersion.substring(0, ix); - } - - majorVersion = parseInt('' + fullVersion, 10); - - if (isNaN(majorVersion)) { - fullVersion = '' + parseFloat(navigator.appVersion); - majorVersion = parseInt(navigator.appVersion, 10); - } - - return majorVersion >= 49; - } - - // ______________________ - // MediaStreamRecorder.js - - /** - * MediaStreamRecorder is an abstraction layer for {@link https://w3c.github.io/mediacapture-record/MediaRecorder.html|MediaRecorder API}. It is used by {@link RecordRTC} to record MediaStream(s) in both Chrome and Firefox. - * @summary Runs top over {@link https://w3c.github.io/mediacapture-record/MediaRecorder.html|MediaRecorder API}. - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://github.com/muaz-khan|Muaz Khan} - * @typedef MediaStreamRecorder - * @class - * @example - * var config = { - * mimeType: 'video/webm', // vp8, vp9, h264, mkv, opus/vorbis - * audioBitsPerSecond : 256 * 8 * 1024, - * videoBitsPerSecond : 256 * 8 * 1024, - * bitsPerSecond: 256 * 8 * 1024, // if this is provided, skip above two - * checkForInactiveTracks: true, - * timeSlice: 1000, // concatenate intervals based blobs - * ondataavailable: function() {} // get intervals based blobs - * } - * var recorder = new MediaStreamRecorder(mediaStream, config); - * recorder.record(); - * recorder.stop(function(blob) { - * video.src = URL.createObjectURL(blob); - * - * // or - * var blob = recorder.blob; - * }); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. - * @param {object} config - {disableLogs:true, initCallback: function, mimeType: "video/webm", timeSlice: 1000} - * @throws Will throw an error if first argument "MediaStream" is missing. Also throws error if "MediaRecorder API" are not supported by the browser. - */ - - function MediaStreamRecorder(mediaStream, config) { - var self = this; - - if (typeof mediaStream === 'undefined') { - throw 'First argument "MediaStream" is required.'; - } - - if (typeof MediaRecorder === 'undefined') { - throw 'Your browser does not support the Media Recorder API. Please try other modules e.g. WhammyRecorder or StereoAudioRecorder.'; - } - - config = config || { - // bitsPerSecond: 256 * 8 * 1024, - mimeType: 'video/webm' - }; - - if (config.type === 'audio') { - if (getTracks(mediaStream, 'video').length && getTracks(mediaStream, 'audio').length) { - var stream; - if (!!navigator.mozGetUserMedia) { - stream = new MediaStream(); - stream.addTrack(getTracks(mediaStream, 'audio')[0]); - } else { - // webkitMediaStream - stream = new MediaStream(getTracks(mediaStream, 'audio')); - } - mediaStream = stream; - } - - if (!config.mimeType || config.mimeType.toString().toLowerCase().indexOf('audio') === -1) { - config.mimeType = isChrome ? 'audio/webm' : 'audio/ogg'; - } - - if (config.mimeType && config.mimeType.toString().toLowerCase() !== 'audio/ogg' && !!navigator.mozGetUserMedia) { - // forcing better codecs on Firefox (via #166) - config.mimeType = 'audio/ogg'; - } - } - - var arrayOfBlobs = []; - - /** - * This method returns array of blobs. Use only with "timeSlice". Its useful to preview recording anytime, without using the "stop" method. - * @method - * @memberof MediaStreamRecorder - * @example - * var arrayOfBlobs = recorder.getArrayOfBlobs(); - * @returns {Array} Returns array of recorded blobs. - */ - this.getArrayOfBlobs = function () { - return arrayOfBlobs; - }; - - /** - * This method records MediaStream. - * @method - * @memberof MediaStreamRecorder - * @example - * recorder.record(); - */ - this.record = function () { - // set defaults - self.blob = null; - self.clearRecordedData(); - self.timestamps = []; - allStates = []; - arrayOfBlobs = []; - - var recorderHints = config; - - if (!config.disableLogs) { - console.log('Passing following config over MediaRecorder API.', recorderHints); - } - - if (mediaRecorder) { - // mandatory to make sure Firefox doesn't fails to record streams 3-4 times without reloading the page. - mediaRecorder = null; - } - - if (isChrome && !isMediaRecorderCompatible()) { - // to support video-only recording on stable - recorderHints = 'video/vp8'; - } - - if (typeof MediaRecorder.isTypeSupported === 'function' && recorderHints.mimeType) { - if (!MediaRecorder.isTypeSupported(recorderHints.mimeType)) { - if (!config.disableLogs) { - console.warn('MediaRecorder API seems unable to record mimeType:', recorderHints.mimeType); - } - - recorderHints.mimeType = config.type === 'audio' ? 'audio/webm' : 'video/webm'; - } - } - - // using MediaRecorder API here - try { - mediaRecorder = new MediaRecorder(mediaStream, recorderHints); - - // reset - config.mimeType = recorderHints.mimeType; - } catch (e) { - // chrome-based fallback - mediaRecorder = new MediaRecorder(mediaStream); - } - - // old hack? - if (recorderHints.mimeType && !MediaRecorder.isTypeSupported && 'canRecordMimeType' in mediaRecorder && mediaRecorder.canRecordMimeType(recorderHints.mimeType) === false) { - if (!config.disableLogs) { - console.warn('MediaRecorder API seems unable to record mimeType:', recorderHints.mimeType); - } - } - - // Dispatching OnDataAvailable Handler - mediaRecorder.ondataavailable = function (e) { - if (e.data) { - allStates.push('ondataavailable: ' + bytesToSize(e.data.size)); - } - - if (typeof config.timeSlice === 'number') { - if (e.data && e.data.size) { - arrayOfBlobs.push(e.data); - updateTimeStamp(); - - if (typeof config.ondataavailable === 'function') { - // intervals based blobs - var blob = config.getNativeBlob ? e.data : new Blob([e.data], { - type: getMimeType(recorderHints) - }); - config.ondataavailable(blob); - } - } - return; - } - - if (!e.data || !e.data.size || e.data.size < 100 || self.blob) { - // make sure that stopRecording always getting fired - // even if there is invalid data - if (self.recordingCallback) { - self.recordingCallback(new Blob([], { - type: getMimeType(recorderHints) - })); - self.recordingCallback = null; - } - return; - } - - self.blob = config.getNativeBlob ? e.data : new Blob([e.data], { - type: getMimeType(recorderHints) - }); - - if (self.recordingCallback) { - self.recordingCallback(self.blob); - self.recordingCallback = null; - } - }; - - mediaRecorder.onstart = function () { - allStates.push('started'); - }; - - mediaRecorder.onpause = function () { - allStates.push('paused'); - }; - - mediaRecorder.onresume = function () { - allStates.push('resumed'); - }; - - mediaRecorder.onstop = function () { - allStates.push('stopped'); - }; - - mediaRecorder.onerror = function (error) { - if (!error) { - return; - } - - if (!error.name) { - error.name = 'UnknownError'; - } - - allStates.push('error: ' + error); - - if (!config.disableLogs) { - // via: https://w3c.github.io/mediacapture-record/MediaRecorder.html#exception-summary - if (error.name.toString().toLowerCase().indexOf('invalidstate') !== -1) { - console.error('The MediaRecorder is not in a state in which the proposed operation is allowed to be executed.', error); - } else if (error.name.toString().toLowerCase().indexOf('notsupported') !== -1) { - console.error('MIME type (', recorderHints.mimeType, ') is not supported.', error); - } else if (error.name.toString().toLowerCase().indexOf('security') !== -1) { - console.error('MediaRecorder security error', error); - } - - // older code below - else if (error.name === 'OutOfMemory') { - console.error('The UA has exhaused the available memory. User agents SHOULD provide as much additional information as possible in the message attribute.', error); - } else if (error.name === 'IllegalStreamModification') { - console.error('A modification to the stream has occurred that makes it impossible to continue recording. An example would be the addition of a Track while recording is occurring. User agents SHOULD provide as much additional information as possible in the message attribute.', error); - } else if (error.name === 'OtherRecordingError') { - console.error('Used for an fatal error other than those listed above. User agents SHOULD provide as much additional information as possible in the message attribute.', error); - } else if (error.name === 'GenericError') { - console.error('The UA cannot provide the codec or recording option that has been requested.', error); - } else { - console.error('MediaRecorder Error', error); - } - } - - (function (looper) { - if (!self.manuallyStopped && mediaRecorder && mediaRecorder.state === 'inactive') { - delete config.timeslice; - - // 10 minutes, enough? - mediaRecorder.start(10 * 60 * 1000); - return; - } - - setTimeout(looper, 1000); - })(); - - if (mediaRecorder.state !== 'inactive' && mediaRecorder.state !== 'stopped') { - mediaRecorder.stop(); - } - }; - - if (typeof config.timeSlice === 'number') { - updateTimeStamp(); - mediaRecorder.start(config.timeSlice); - } else { - // default is 60 minutes; enough? - // use config => {timeSlice: 1000} otherwise - - mediaRecorder.start(3.6e+6); - } - - if (config.initCallback) { - config.initCallback(); // old code - } - }; - - /** - * @property {Array} timestamps - Array of time stamps - * @memberof MediaStreamRecorder - * @example - * console.log(recorder.timestamps); - */ - this.timestamps = []; - - function updateTimeStamp() { - self.timestamps.push(new Date().getTime()); - - if (typeof config.onTimeStamp === 'function') { - config.onTimeStamp(self.timestamps[self.timestamps.length - 1], self.timestamps); - } - } - - function getMimeType(secondObject) { - if (mediaRecorder && mediaRecorder.mimeType) { - return mediaRecorder.mimeType; - } - - return secondObject.mimeType || 'video/webm'; - } - - /** - * This method stops recording MediaStream. - * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. - * @method - * @memberof MediaStreamRecorder - * @example - * recorder.stop(function(blob) { - * video.src = URL.createObjectURL(blob); - * }); - */ - this.stop = function (callback) { - callback = callback || function () { }; - - self.manuallyStopped = true; // used inside the mediaRecorder.onerror - - if (!mediaRecorder) { - return; - } - - this.recordingCallback = callback; - - if (mediaRecorder.state === 'recording') { - mediaRecorder.stop(); - } - - if (typeof config.timeSlice === 'number') { - setTimeout(function () { - self.blob = new Blob(arrayOfBlobs, { - type: getMimeType(config) - }); - - self.recordingCallback(self.blob); - }, 100); - } - }; - - /** - * This method pauses the recording process. - * @method - * @memberof MediaStreamRecorder - * @example - * recorder.pause(); - */ - this.pause = function () { - if (!mediaRecorder) { - return; - } - - if (mediaRecorder.state === 'recording') { - mediaRecorder.pause(); - } - }; - - /** - * This method resumes the recording process. - * @method - * @memberof MediaStreamRecorder - * @example - * recorder.resume(); - */ - this.resume = function () { - if (!mediaRecorder) { - return; - } - - if (mediaRecorder.state === 'paused') { - mediaRecorder.resume(); - } - }; - - /** - * This method resets currently recorded data. - * @method - * @memberof MediaStreamRecorder - * @example - * recorder.clearRecordedData(); - */ - this.clearRecordedData = function () { - if (mediaRecorder && mediaRecorder.state === 'recording') { - self.stop(clearRecordedDataCB); - } - - clearRecordedDataCB(); - }; - - function clearRecordedDataCB() { - arrayOfBlobs = []; - mediaRecorder = null; - self.timestamps = []; - } - - // Reference to "MediaRecorder" object - var mediaRecorder; - - /** - * Access to native MediaRecorder API - * @method - * @memberof MediaStreamRecorder - * @instance - * @example - * var internal = recorder.getInternalRecorder(); - * internal.ondataavailable = function() {}; // override - * internal.stream, internal.onpause, internal.onstop, etc. - * @returns {Object} Returns internal recording object. - */ - this.getInternalRecorder = function () { - return mediaRecorder; - }; - - function isMediaStreamActive() { - if ('active' in mediaStream) { - if (!mediaStream.active) { - return false; - } - } else if ('ended' in mediaStream) { // old hack - if (mediaStream.ended) { - return false; - } - } - return true; - } - - /** - * @property {Blob} blob - Recorded data as "Blob" object. - * @memberof MediaStreamRecorder - * @example - * recorder.stop(function() { - * var blob = recorder.blob; - * }); - */ - this.blob = null; - - - /** - * Get MediaRecorder readonly state. - * @method - * @memberof MediaStreamRecorder - * @example - * var state = recorder.getState(); - * @returns {String} Returns recording state. - */ - this.getState = function () { - if (!mediaRecorder) { - return 'inactive'; - } - - return mediaRecorder.state || 'inactive'; - }; - - // list of all recording states - var allStates = []; - - /** - * Get MediaRecorder all recording states. - * @method - * @memberof MediaStreamRecorder - * @example - * var state = recorder.getAllStates(); - * @returns {Array} Returns all recording states - */ - this.getAllStates = function () { - return allStates; - }; - - // if any Track within the MediaStream is muted or not enabled at any time, - // the browser will only record black frames - // or silence since that is the content produced by the Track - // so we need to stopRecording as soon as any single track ends. - if (typeof config.checkForInactiveTracks === 'undefined') { - config.checkForInactiveTracks = false; // disable to minimize CPU usage - } - - var self = this; - - // this method checks if media stream is stopped - // or if any track is ended. - (function looper() { - if (!mediaRecorder || config.checkForInactiveTracks === false) { - return; - } - - if (isMediaStreamActive() === false) { - if (!config.disableLogs) { - console.log('MediaStream seems stopped.'); - } - self.stop(); - return; - } - - setTimeout(looper, 1000); // check every second - })(); - - // for debugging - this.name = 'MediaStreamRecorder'; - this.toString = function () { - return this.name; - }; - } - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.MediaStreamRecorder = MediaStreamRecorder; - } - - // source code from: http://typedarray.org/wp-content/projects/WebAudioRecorder/script.js - // https://github.com/mattdiamond/Recorderjs#license-mit - // ______________________ - // StereoAudioRecorder.js - - /** - * StereoAudioRecorder is a standalone class used by {@link RecordRTC} to bring "stereo" audio-recording in chrome. - * @summary JavaScript standalone object for stereo audio recording. - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @typedef StereoAudioRecorder - * @class - * @example - * var recorder = new StereoAudioRecorder(MediaStream, { - * sampleRate: 44100, - * bufferSize: 4096 - * }); - * recorder.record(); - * recorder.stop(function(blob) { - * video.src = URL.createObjectURL(blob); - * }); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. - * @param {object} config - {sampleRate: 44100, bufferSize: 4096, numberOfAudioChannels: 1, etc.} - */ - - function StereoAudioRecorder(mediaStream, config) { - if (!getTracks(mediaStream, 'audio').length) { - throw 'Your stream has no audio tracks.'; - } - - config = config || {}; - - var self = this; - - // variables - var leftchannel = []; - var rightchannel = []; - var recording = false; - var recordingLength = 0; - var jsAudioNode; - - var numberOfAudioChannels = 2; - - /** - * Set sample rates such as 8K or 16K. Reference: http://stackoverflow.com/a/28977136/552182 - * @property {number} desiredSampRate - Desired Bits per sample * 1000 - * @memberof StereoAudioRecorder - * @instance - * @example - * var recorder = StereoAudioRecorder(mediaStream, { - * desiredSampRate: 16 * 1000 // bits-per-sample * 1000 - * }); - */ - var desiredSampRate = config.desiredSampRate; - - // backward compatibility - if (config.leftChannel === true) { - numberOfAudioChannels = 1; - } - - if (config.numberOfAudioChannels === 1) { - numberOfAudioChannels = 1; - } - - if (!numberOfAudioChannels || numberOfAudioChannels < 1) { - numberOfAudioChannels = 2; - } - - if (!config.disableLogs) { - console.log('StereoAudioRecorder is set to record number of channels: ' + numberOfAudioChannels); - } - - // if any Track within the MediaStream is muted or not enabled at any time, - // the browser will only record black frames - // or silence since that is the content produced by the Track - // so we need to stopRecording as soon as any single track ends. - if (typeof config.checkForInactiveTracks === 'undefined') { - config.checkForInactiveTracks = true; - } - - function isMediaStreamActive() { - if (config.checkForInactiveTracks === false) { - // always return "true" - return true; - } - - if ('active' in mediaStream) { - if (!mediaStream.active) { - return false; - } - } else if ('ended' in mediaStream) { // old hack - if (mediaStream.ended) { - return false; - } - } - return true; - } - - /** - * This method records MediaStream. - * @method - * @memberof StereoAudioRecorder - * @example - * recorder.record(); - */ - this.record = function () { - if (isMediaStreamActive() === false) { - throw 'Please make sure MediaStream is active.'; - } - - resetVariables(); - - isAudioProcessStarted = isPaused = false; - recording = true; - - if (typeof config.timeSlice !== 'undefined') { - looper(); - } - }; - - function mergeLeftRightBuffers(config, callback) { - function mergeAudioBuffers(config, cb) { - var numberOfAudioChannels = config.numberOfAudioChannels; - - // todo: "slice(0)" --- is it causes loop? Should be removed? - var leftBuffers = config.leftBuffers.slice(0); - var rightBuffers = config.rightBuffers.slice(0); - var sampleRate = config.sampleRate; - var internalInterleavedLength = config.internalInterleavedLength; - var desiredSampRate = config.desiredSampRate; - - if (numberOfAudioChannels === 2) { - leftBuffers = mergeBuffers(leftBuffers, internalInterleavedLength); - rightBuffers = mergeBuffers(rightBuffers, internalInterleavedLength); - - if (desiredSampRate) { - leftBuffers = interpolateArray(leftBuffers, desiredSampRate, sampleRate); - rightBuffers = interpolateArray(rightBuffers, desiredSampRate, sampleRate); - } - } - - if (numberOfAudioChannels === 1) { - leftBuffers = mergeBuffers(leftBuffers, internalInterleavedLength); - - if (desiredSampRate) { - leftBuffers = interpolateArray(leftBuffers, desiredSampRate, sampleRate); - } - } - - // set sample rate as desired sample rate - if (desiredSampRate) { - sampleRate = desiredSampRate; - } - - // for changing the sampling rate, reference: - // http://stackoverflow.com/a/28977136/552182 - function interpolateArray(data, newSampleRate, oldSampleRate) { - var fitCount = Math.round(data.length * (newSampleRate / oldSampleRate)); - var newData = []; - var springFactor = Number((data.length - 1) / (fitCount - 1)); - newData[0] = data[0]; - for (var i = 1; i < fitCount - 1; i++) { - var tmp = i * springFactor; - var before = Number(Math.floor(tmp)).toFixed(); - var after = Number(Math.ceil(tmp)).toFixed(); - var atPoint = tmp - before; - newData[i] = linearInterpolate(data[before], data[after], atPoint); - } - newData[fitCount - 1] = data[data.length - 1]; - return newData; - } - - function linearInterpolate(before, after, atPoint) { - return before + (after - before) * atPoint; - } - - function mergeBuffers(channelBuffer, rLength) { - var result = new Float64Array(rLength); - var offset = 0; - var lng = channelBuffer.length; - - for (var i = 0; i < lng; i++) { - var buffer = channelBuffer[i]; - result.set(buffer, offset); - offset += buffer.length; - } - - return result; - } - - function interleave(leftChannel, rightChannel) { - var length = leftChannel.length + rightChannel.length; - - var result = new Float64Array(length); - - var inputIndex = 0; - - for (var index = 0; index < length;) { - result[index++] = leftChannel[inputIndex]; - result[index++] = rightChannel[inputIndex]; - inputIndex++; - } - return result; - } - - function writeUTFBytes(view, offset, string) { - var lng = string.length; - for (var i = 0; i < lng; i++) { - view.setUint8(offset + i, string.charCodeAt(i)); - } - } - - // interleave both channels together - var interleaved; - - if (numberOfAudioChannels === 2) { - interleaved = interleave(leftBuffers, rightBuffers); - } - - if (numberOfAudioChannels === 1) { - interleaved = leftBuffers; - } - - var interleavedLength = interleaved.length; - - // create wav file - var resultingBufferLength = 44 + interleavedLength * 2; - - var buffer = new ArrayBuffer(resultingBufferLength); - - var view = new DataView(buffer); - - // RIFF chunk descriptor/identifier - writeUTFBytes(view, 0, 'RIFF'); - - // RIFF chunk length - // changed "44" to "36" via #401 - view.setUint32(4, 36 + interleavedLength * 2, true); - - // RIFF type - writeUTFBytes(view, 8, 'WAVE'); - - // format chunk identifier - // FMT sub-chunk - writeUTFBytes(view, 12, 'fmt '); - - // format chunk length - view.setUint32(16, 16, true); - - // sample format (raw) - view.setUint16(20, 1, true); - - // stereo (2 channels) - view.setUint16(22, numberOfAudioChannels, true); - - // sample rate - view.setUint32(24, sampleRate, true); - - // byte rate (sample rate * block align) - view.setUint32(28, sampleRate * numberOfAudioChannels * 2, true); - - // block align (channel count * bytes per sample) - view.setUint16(32, numberOfAudioChannels * 2, true); - - // bits per sample - view.setUint16(34, 16, true); - - // data sub-chunk - // data chunk identifier - writeUTFBytes(view, 36, 'data'); - - // data chunk length - view.setUint32(40, interleavedLength * 2, true); - - // write the PCM samples - var lng = interleavedLength; - var index = 44; - var volume = 1; - for (var i = 0; i < lng; i++) { - view.setInt16(index, interleaved[i] * (0x7FFF * volume), true); - index += 2; - } - - if (cb) { - return cb({ - buffer: buffer, - view: view - }); - } - - postMessage({ - buffer: buffer, - view: view - }); - } - - if (config.noWorker) { - mergeAudioBuffers(config, function (data) { - callback(data.buffer, data.view); - }); - return; - } - - - var webWorker = processInWebWorker(mergeAudioBuffers); - - webWorker.onmessage = function (event) { - callback(event.data.buffer, event.data.view); - - // release memory - URL.revokeObjectURL(webWorker.workerURL); - - // kill webworker (or Chrome will kill your page after ~25 calls) - webWorker.terminate(); - }; - - webWorker.postMessage(config); - } - - function processInWebWorker(_function) { - var workerURL = URL.createObjectURL(new Blob([_function.toString(), - ';this.onmessage = function (eee) {' + _function.name + '(eee.data);}' - ], { - type: 'application/javascript' - })); - - var worker = new Worker(workerURL); - worker.workerURL = workerURL; - return worker; - } - - /** - * This method stops recording MediaStream. - * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. - * @method - * @memberof StereoAudioRecorder - * @example - * recorder.stop(function(blob) { - * video.src = URL.createObjectURL(blob); - * }); - */ - this.stop = function (callback) { - callback = callback || function () { }; - - // stop recording - recording = false; - - mergeLeftRightBuffers({ - desiredSampRate: desiredSampRate, - sampleRate: sampleRate, - numberOfAudioChannels: numberOfAudioChannels, - internalInterleavedLength: recordingLength, - leftBuffers: leftchannel, - rightBuffers: numberOfAudioChannels === 1 ? [] : rightchannel, - noWorker: config.noWorker - }, function (buffer, view) { - /** - * @property {Blob} blob - The recorded blob object. - * @memberof StereoAudioRecorder - * @example - * recorder.stop(function(){ - * var blob = recorder.blob; - * }); - */ - self.blob = new Blob([view], { - type: 'audio/wav' - }); - - /** - * @property {ArrayBuffer} buffer - The recorded buffer object. - * @memberof StereoAudioRecorder - * @example - * recorder.stop(function(){ - * var buffer = recorder.buffer; - * }); - */ - self.buffer = new ArrayBuffer(view.buffer.byteLength); - - /** - * @property {DataView} view - The recorded data-view object. - * @memberof StereoAudioRecorder - * @example - * recorder.stop(function(){ - * var view = recorder.view; - * }); - */ - self.view = view; - - self.sampleRate = desiredSampRate || sampleRate; - self.bufferSize = bufferSize; - - // recorded audio length - self.length = recordingLength; - - isAudioProcessStarted = false; - - if (callback) { - callback(self.blob); - } - }); - }; - - if (typeof RecordRTC.Storage === 'undefined') { - RecordRTC.Storage = { - AudioContextConstructor: null, - AudioContext: window.AudioContext || window.webkitAudioContext - }; - } - - if (!RecordRTC.Storage.AudioContextConstructor || RecordRTC.Storage.AudioContextConstructor.state === 'closed') { - RecordRTC.Storage.AudioContextConstructor = new RecordRTC.Storage.AudioContext(); - } - - var context = RecordRTC.Storage.AudioContextConstructor; - - // creates an audio node from the microphone incoming stream - var audioInput = context.createMediaStreamSource(mediaStream); - - var legalBufferValues = [0, 256, 512, 1024, 2048, 4096, 8192, 16384]; - - /** - * From the spec: This value controls how frequently the audioprocess event is - * dispatched and how many sample-frames need to be processed each call. - * Lower values for buffer size will result in a lower (better) latency. - * Higher values will be necessary to avoid audio breakup and glitches - * The size of the buffer (in sample-frames) which needs to - * be processed each time onprocessaudio is called. - * Legal values are (256, 512, 1024, 2048, 4096, 8192, 16384). - * @property {number} bufferSize - Buffer-size for how frequently the audioprocess event is dispatched. - * @memberof StereoAudioRecorder - * @example - * recorder = new StereoAudioRecorder(mediaStream, { - * bufferSize: 4096 - * }); - */ - - // "0" means, let chrome decide the most accurate buffer-size for current platform. - var bufferSize = typeof config.bufferSize === 'undefined' ? 4096 : config.bufferSize; - - if (legalBufferValues.indexOf(bufferSize) === -1) { - if (!config.disableLogs) { - console.log('Legal values for buffer-size are ' + JSON.stringify(legalBufferValues, null, '\t')); - } - } - - if (context.createJavaScriptNode) { - jsAudioNode = context.createJavaScriptNode(bufferSize, numberOfAudioChannels, numberOfAudioChannels); - } else if (context.createScriptProcessor) { - jsAudioNode = context.createScriptProcessor(bufferSize, numberOfAudioChannels, numberOfAudioChannels); - } else { - throw 'WebAudio API has no support on this browser.'; - } - - // connect the stream to the script processor - audioInput.connect(jsAudioNode); - - if (!config.bufferSize) { - bufferSize = jsAudioNode.bufferSize; // device buffer-size - } - - /** - * The sample rate (in sample-frames per second) at which the - * AudioContext handles audio. It is assumed that all AudioNodes - * in the context run at this rate. In making this assumption, - * sample-rate converters or "varispeed" processors are not supported - * in real-time processing. - * The sampleRate parameter describes the sample-rate of the - * linear PCM audio data in the buffer in sample-frames per second. - * An implementation must support sample-rates in at least - * the range 22050 to 96000. - * @property {number} sampleRate - Buffer-size for how frequently the audioprocess event is dispatched. - * @memberof StereoAudioRecorder - * @example - * recorder = new StereoAudioRecorder(mediaStream, { - * sampleRate: 44100 - * }); - */ - var sampleRate = typeof config.sampleRate !== 'undefined' ? config.sampleRate : context.sampleRate || 44100; - - if (sampleRate < 22050 || sampleRate > 96000) { - // Ref: http://stackoverflow.com/a/26303918/552182 - if (!config.disableLogs) { - console.log('sample-rate must be under range 22050 and 96000.'); - } - } - - if (!config.disableLogs) { - if (config.desiredSampRate) { - console.log('Desired sample-rate: ' + config.desiredSampRate); - } - } - - var isPaused = false; - /** - * This method pauses the recording process. - * @method - * @memberof StereoAudioRecorder - * @example - * recorder.pause(); - */ - this.pause = function () { - isPaused = true; - }; - - /** - * This method resumes the recording process. - * @method - * @memberof StereoAudioRecorder - * @example - * recorder.resume(); - */ - this.resume = function () { - if (isMediaStreamActive() === false) { - throw 'Please make sure MediaStream is active.'; - } - - if (!recording) { - if (!config.disableLogs) { - console.log('Seems recording has been restarted.'); - } - this.record(); - return; - } - - isPaused = false; - }; - - /** - * This method resets currently recorded data. - * @method - * @memberof StereoAudioRecorder - * @example - * recorder.clearRecordedData(); - */ - this.clearRecordedData = function () { - config.checkForInactiveTracks = false; - - if (recording) { - this.stop(clearRecordedDataCB); - } - - clearRecordedDataCB(); - }; - - function resetVariables() { - leftchannel = []; - rightchannel = []; - recordingLength = 0; - isAudioProcessStarted = false; - recording = false; - isPaused = false; - context = null; - - self.leftchannel = leftchannel; - self.rightchannel = rightchannel; - self.numberOfAudioChannels = numberOfAudioChannels; - self.desiredSampRate = desiredSampRate; - self.sampleRate = sampleRate; - self.recordingLength = recordingLength; - - intervalsBasedBuffers = { - left: [], - right: [], - recordingLength: 0 - }; - } - - function clearRecordedDataCB() { - if (jsAudioNode) { - jsAudioNode.onaudioprocess = null; - jsAudioNode.disconnect(); - jsAudioNode = null; - } - - if (audioInput) { - audioInput.disconnect(); - audioInput = null; - } - - resetVariables(); - } - - // for debugging - this.name = 'StereoAudioRecorder'; - this.toString = function () { - return this.name; - }; - - var isAudioProcessStarted = false; - - function onAudioProcessDataAvailable(e) { - if (isPaused) { - return; - } - - if (isMediaStreamActive() === false) { - if (!config.disableLogs) { - console.log('MediaStream seems stopped.'); - } - jsAudioNode.disconnect(); - recording = false; - } - - if (!recording) { - if (audioInput) { - audioInput.disconnect(); - audioInput = null; - } - return; - } - - /** - * This method is called on "onaudioprocess" event's first invocation. - * @method {function} onAudioProcessStarted - * @memberof StereoAudioRecorder - * @example - * recorder.onAudioProcessStarted: function() { }; - */ - if (!isAudioProcessStarted) { - isAudioProcessStarted = true; - if (config.onAudioProcessStarted) { - config.onAudioProcessStarted(); - } - - if (config.initCallback) { - config.initCallback(); - } - } - - var left = e.inputBuffer.getChannelData(0); - - // we clone the samples - var chLeft = new Float32Array(left); - leftchannel.push(chLeft); - - if (numberOfAudioChannels === 2) { - var right = e.inputBuffer.getChannelData(1); - var chRight = new Float32Array(right); - rightchannel.push(chRight); - } - - recordingLength += bufferSize; - - // export raw PCM - self.recordingLength = recordingLength; - - if (typeof config.timeSlice !== 'undefined') { - intervalsBasedBuffers.recordingLength += bufferSize; - intervalsBasedBuffers.left.push(chLeft); - - if (numberOfAudioChannels === 2) { - intervalsBasedBuffers.right.push(chRight); - } - } - } - - jsAudioNode.onaudioprocess = onAudioProcessDataAvailable; - - // to prevent self audio to be connected with speakers - if (context.createMediaStreamDestination) { - jsAudioNode.connect(context.createMediaStreamDestination()); - } else { - jsAudioNode.connect(context.destination); - } - - // export raw PCM - this.leftchannel = leftchannel; - this.rightchannel = rightchannel; - this.numberOfAudioChannels = numberOfAudioChannels; - this.desiredSampRate = desiredSampRate; - this.sampleRate = sampleRate; - self.recordingLength = recordingLength; - - // helper for intervals based blobs - var intervalsBasedBuffers = { - left: [], - right: [], - recordingLength: 0 - }; - - // this looper is used to support intervals based blobs (via timeSlice+ondataavailable) - function looper() { - if (!recording || typeof config.ondataavailable !== 'function' || typeof config.timeSlice === 'undefined') { - return; - } - - if (intervalsBasedBuffers.left.length) { - mergeLeftRightBuffers({ - desiredSampRate: desiredSampRate, - sampleRate: sampleRate, - numberOfAudioChannels: numberOfAudioChannels, - internalInterleavedLength: intervalsBasedBuffers.recordingLength, - leftBuffers: intervalsBasedBuffers.left, - rightBuffers: numberOfAudioChannels === 1 ? [] : intervalsBasedBuffers.right - }, function (buffer, view) { - var blob = new Blob([view], { - type: 'audio/wav' - }); - config.ondataavailable(blob); - - setTimeout(looper, config.timeSlice); - }); - - intervalsBasedBuffers = { - left: [], - right: [], - recordingLength: 0 - }; - } else { - setTimeout(looper, config.timeSlice); - } - } - } - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.StereoAudioRecorder = StereoAudioRecorder; - } - - // _________________ - // CanvasRecorder.js - - /** - * CanvasRecorder is a standalone class used by {@link RecordRTC} to bring HTML5-Canvas recording into video WebM. It uses HTML2Canvas library and runs top over {@link Whammy}. - * @summary HTML2Canvas recording into video WebM. - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @typedef CanvasRecorder - * @class - * @example - * var recorder = new CanvasRecorder(htmlElement, { disableLogs: true, useWhammyRecorder: true }); - * recorder.record(); - * recorder.stop(function(blob) { - * video.src = URL.createObjectURL(blob); - * }); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - * @param {HTMLElement} htmlElement - querySelector/getElementById/getElementsByTagName[0]/etc. - * @param {object} config - {disableLogs:true, initCallback: function} - */ - - function CanvasRecorder(htmlElement, config) { - if (typeof html2canvas === 'undefined') { - throw 'Please link: https://www.webrtc-experiment.com/screenshot.js'; - } - - config = config || {}; - if (!config.frameInterval) { - config.frameInterval = 10; - } - - // via DetectRTC.js - var isCanvasSupportsStreamCapturing = false; - ['captureStream', 'mozCaptureStream', 'webkitCaptureStream'].forEach(function (item) { - if (item in document.createElement('canvas')) { - isCanvasSupportsStreamCapturing = true; - } - }); - - var _isChrome = (!!window.webkitRTCPeerConnection || !!window.webkitGetUserMedia) && !!window.chrome; - - var chromeVersion = 50; - var matchArray = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./); - if (_isChrome && matchArray && matchArray[2]) { - chromeVersion = parseInt(matchArray[2], 10); - } - - if (_isChrome && chromeVersion < 52) { - isCanvasSupportsStreamCapturing = false; - } - - if (config.useWhammyRecorder) { - isCanvasSupportsStreamCapturing = false; - } - - var globalCanvas, mediaStreamRecorder; - - if (isCanvasSupportsStreamCapturing) { - if (!config.disableLogs) { - console.log('Your browser supports both MediRecorder API and canvas.captureStream!'); - } - - if (htmlElement instanceof HTMLCanvasElement) { - globalCanvas = htmlElement; - } else if (htmlElement instanceof CanvasRenderingContext2D) { - globalCanvas = htmlElement.canvas; - } else { - throw 'Please pass either HTMLCanvasElement or CanvasRenderingContext2D.'; - } - } else if (!!navigator.mozGetUserMedia) { - if (!config.disableLogs) { - console.error('Canvas recording is NOT supported in Firefox.'); - } - } - - var isRecording; - - /** - * This method records Canvas. - * @method - * @memberof CanvasRecorder - * @example - * recorder.record(); - */ - this.record = function () { - isRecording = true; - - if (isCanvasSupportsStreamCapturing && !config.useWhammyRecorder) { - // CanvasCaptureMediaStream - var canvasMediaStream; - if ('captureStream' in globalCanvas) { - canvasMediaStream = globalCanvas.captureStream(25); // 25 FPS - } else if ('mozCaptureStream' in globalCanvas) { - canvasMediaStream = globalCanvas.mozCaptureStream(25); - } else if ('webkitCaptureStream' in globalCanvas) { - canvasMediaStream = globalCanvas.webkitCaptureStream(25); - } - - try { - var mdStream = new MediaStream(); - mdStream.addTrack(getTracks(canvasMediaStream, 'video')[0]); - canvasMediaStream = mdStream; - } catch (e) { } - - if (!canvasMediaStream) { - throw 'captureStream API are NOT available.'; - } - - // Note: Jan 18, 2016 status is that, - // Firefox MediaRecorder API can't record CanvasCaptureMediaStream object. - mediaStreamRecorder = new MediaStreamRecorder(canvasMediaStream, { - mimeType: config.mimeType || 'video/webm' - }); - mediaStreamRecorder.record(); - } else { - whammy.frames = []; - lastTime = new Date().getTime(); - drawCanvasFrame(); - } - - if (config.initCallback) { - config.initCallback(); - } - }; - - this.getWebPImages = function (callback) { - if (htmlElement.nodeName.toLowerCase() !== 'canvas') { - callback(); - return; - } - - var framesLength = whammy.frames.length; - whammy.frames.forEach(function (frame, idx) { - var framesRemaining = framesLength - idx; - if (!config.disableLogs) { - console.log(framesRemaining + '/' + framesLength + ' frames remaining'); - } - - if (config.onEncodingCallback) { - config.onEncodingCallback(framesRemaining, framesLength); - } - - var webp = frame.image.toDataURL('image/webp', 1); - whammy.frames[idx].image = webp; - }); - - if (!config.disableLogs) { - console.log('Generating WebM'); - } - - callback(); - }; - - /** - * This method stops recording Canvas. - * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. - * @method - * @memberof CanvasRecorder - * @example - * recorder.stop(function(blob) { - * video.src = URL.createObjectURL(blob); - * }); - */ - this.stop = function (callback) { - isRecording = false; - - var that = this; - - if (isCanvasSupportsStreamCapturing && mediaStreamRecorder) { - mediaStreamRecorder.stop(callback); - return; - } - - this.getWebPImages(function () { - /** - * @property {Blob} blob - Recorded frames in video/webm blob. - * @memberof CanvasRecorder - * @example - * recorder.stop(function() { - * var blob = recorder.blob; - * }); - */ - whammy.compile(function (blob) { - if (!config.disableLogs) { - console.log('Recording finished!'); - } - - that.blob = blob; - - if (that.blob.forEach) { - that.blob = new Blob([], { - type: 'video/webm' - }); - } - - if (callback) { - callback(that.blob); - } - - whammy.frames = []; - }); - }); - }; - - var isPausedRecording = false; - - /** - * This method pauses the recording process. - * @method - * @memberof CanvasRecorder - * @example - * recorder.pause(); - */ - this.pause = function () { - isPausedRecording = true; - - if (mediaStreamRecorder instanceof MediaStreamRecorder) { - mediaStreamRecorder.pause(); - return; - } - }; - - /** - * This method resumes the recording process. - * @method - * @memberof CanvasRecorder - * @example - * recorder.resume(); - */ - this.resume = function () { - isPausedRecording = false; - - if (mediaStreamRecorder instanceof MediaStreamRecorder) { - mediaStreamRecorder.resume(); - return; - } - - if (!isRecording) { - this.record(); - } - }; - - /** - * This method resets currently recorded data. - * @method - * @memberof CanvasRecorder - * @example - * recorder.clearRecordedData(); - */ - this.clearRecordedData = function () { - if (isRecording) { - this.stop(clearRecordedDataCB); - } - clearRecordedDataCB(); - }; - - function clearRecordedDataCB() { - whammy.frames = []; - isRecording = false; - isPausedRecording = false; - } - - // for debugging - this.name = 'CanvasRecorder'; - this.toString = function () { - return this.name; - }; - - function cloneCanvas() { - //create a new canvas - var newCanvas = document.createElement('canvas'); - var context = newCanvas.getContext('2d'); - - //set dimensions - newCanvas.width = htmlElement.width; - newCanvas.height = htmlElement.height; - - //apply the old canvas to the new one - context.drawImage(htmlElement, 0, 0); - - //return the new canvas - return newCanvas; - } - - function drawCanvasFrame() { - if (isPausedRecording) { - lastTime = new Date().getTime(); - return setTimeout(drawCanvasFrame, 500); - } - - if (htmlElement.nodeName.toLowerCase() === 'canvas') { - var duration = new Date().getTime() - lastTime; - // via #206, by Jack i.e. @Seymourr - lastTime = new Date().getTime(); - - whammy.frames.push({ - image: cloneCanvas(), - duration: duration - }); - - if (isRecording) { - setTimeout(drawCanvasFrame, config.frameInterval); - } - return; - } - - html2canvas(htmlElement, { - grabMouse: typeof config.showMousePointer === 'undefined' || config.showMousePointer, - onrendered: function (canvas) { - var duration = new Date().getTime() - lastTime; - if (!duration) { - return setTimeout(drawCanvasFrame, config.frameInterval); - } - - // via #206, by Jack i.e. @Seymourr - lastTime = new Date().getTime(); - - whammy.frames.push({ - image: canvas.toDataURL('image/webp', 1), - duration: duration - }); - - if (isRecording) { - setTimeout(drawCanvasFrame, config.frameInterval); - } - } - }); - } - - var lastTime = new Date().getTime(); - - var whammy = new Whammy.Video(100); - } - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.CanvasRecorder = CanvasRecorder; - } - - // _________________ - // WhammyRecorder.js - - /** - * WhammyRecorder is a standalone class used by {@link RecordRTC} to bring video recording in Chrome. It runs top over {@link Whammy}. - * @summary Video recording feature in Chrome. - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @typedef WhammyRecorder - * @class - * @example - * var recorder = new WhammyRecorder(mediaStream); - * recorder.record(); - * recorder.stop(function(blob) { - * video.src = URL.createObjectURL(blob); - * }); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. - * @param {object} config - {disableLogs: true, initCallback: function, video: HTMLVideoElement, etc.} - */ - - function WhammyRecorder(mediaStream, config) { - - config = config || {}; - - if (!config.frameInterval) { - config.frameInterval = 10; - } - - if (!config.disableLogs) { - console.log('Using frames-interval:', config.frameInterval); - } - - /** - * This method records video. - * @method - * @memberof WhammyRecorder - * @example - * recorder.record(); - */ - this.record = function () { - if (!config.width) { - config.width = 320; - } - - if (!config.height) { - config.height = 240; - } - - if (!config.video) { - config.video = { - width: config.width, - height: config.height - }; - } - - if (!config.canvas) { - config.canvas = { - width: config.width, - height: config.height - }; - } - - canvas.width = config.canvas.width || 320; - canvas.height = config.canvas.height || 240; - - context = canvas.getContext('2d'); - - // setting defaults - if (config.video && config.video instanceof HTMLVideoElement) { - video = config.video.cloneNode(); - - if (config.initCallback) { - config.initCallback(); - } - } else { - video = document.createElement('video'); - - setSrcObject(mediaStream, video); - - video.onloadedmetadata = function () { // "onloadedmetadata" may NOT work in FF? - if (config.initCallback) { - config.initCallback(); - } - }; - - video.width = config.video.width; - video.height = config.video.height; - } - - video.muted = true; - video.play(); - - lastTime = new Date().getTime(); - whammy = new Whammy.Video(); - - if (!config.disableLogs) { - console.log('canvas resolutions', canvas.width, '*', canvas.height); - console.log('video width/height', video.width || canvas.width, '*', video.height || canvas.height); - } - - drawFrames(config.frameInterval); - }; - - /** - * Draw and push frames to Whammy - * @param {integer} frameInterval - set minimum interval (in milliseconds) between each time we push a frame to Whammy - */ - function drawFrames(frameInterval) { - frameInterval = typeof frameInterval !== 'undefined' ? frameInterval : 10; - - var duration = new Date().getTime() - lastTime; - if (!duration) { - return setTimeout(drawFrames, frameInterval, frameInterval); - } - - if (isPausedRecording) { - lastTime = new Date().getTime(); - return setTimeout(drawFrames, 100); - } - - // via #206, by Jack i.e. @Seymourr - lastTime = new Date().getTime(); - - if (video.paused) { - // via: https://github.com/muaz-khan/WebRTC-Experiment/pull/316 - // Tweak for Android Chrome - video.play(); - } - - context.drawImage(video, 0, 0, canvas.width, canvas.height); - whammy.frames.push({ - duration: duration, - image: canvas.toDataURL('image/webp') - }); - - if (!isStopDrawing) { - setTimeout(drawFrames, frameInterval, frameInterval); - } - } - - function asyncLoop(o) { - var i = -1, - length = o.length; - - (function loop() { - i++; - if (i === length) { - o.callback(); - return; - } - - // "setTimeout" added by Jim McLeod - setTimeout(function () { - o.functionToLoop(loop, i); - }, 1); - })(); - } - - - /** - * remove black frames from the beginning to the specified frame - * @param {Array} _frames - array of frames to be checked - * @param {number} _framesToCheck - number of frame until check will be executed (-1 - will drop all frames until frame not matched will be found) - * @param {number} _pixTolerance - 0 - very strict (only black pixel color) ; 1 - all - * @param {number} _frameTolerance - 0 - very strict (only black frame color) ; 1 - all - * @returns {Array} - array of frames - */ - // pull#293 by @volodalexey - function dropBlackFrames(_frames, _framesToCheck, _pixTolerance, _frameTolerance, callback) { - var localCanvas = document.createElement('canvas'); - localCanvas.width = canvas.width; - localCanvas.height = canvas.height; - var context2d = localCanvas.getContext('2d'); - var resultFrames = []; - - var checkUntilNotBlack = _framesToCheck === -1; - var endCheckFrame = (_framesToCheck && _framesToCheck > 0 && _framesToCheck <= _frames.length) ? - _framesToCheck : _frames.length; - var sampleColor = { - r: 0, - g: 0, - b: 0 - }; - var maxColorDifference = Math.sqrt( - Math.pow(255, 2) + - Math.pow(255, 2) + - Math.pow(255, 2) - ); - var pixTolerance = _pixTolerance && _pixTolerance >= 0 && _pixTolerance <= 1 ? _pixTolerance : 0; - var frameTolerance = _frameTolerance && _frameTolerance >= 0 && _frameTolerance <= 1 ? _frameTolerance : 0; - var doNotCheckNext = false; - - asyncLoop({ - length: endCheckFrame, - functionToLoop: function (loop, f) { - var matchPixCount, endPixCheck, maxPixCount; - - var finishImage = function () { - if (!doNotCheckNext && maxPixCount - matchPixCount <= maxPixCount * frameTolerance); else { - // console.log('frame is passed : ' + f); - if (checkUntilNotBlack) { - doNotCheckNext = true; - } - resultFrames.push(_frames[f]); - } - loop(); - }; - - if (!doNotCheckNext) { - var image = new Image(); - image.onload = function () { - context2d.drawImage(image, 0, 0, canvas.width, canvas.height); - var imageData = context2d.getImageData(0, 0, canvas.width, canvas.height); - matchPixCount = 0; - endPixCheck = imageData.data.length; - maxPixCount = imageData.data.length / 4; - - for (var pix = 0; pix < endPixCheck; pix += 4) { - var currentColor = { - r: imageData.data[pix], - g: imageData.data[pix + 1], - b: imageData.data[pix + 2] - }; - var colorDifference = Math.sqrt( - Math.pow(currentColor.r - sampleColor.r, 2) + - Math.pow(currentColor.g - sampleColor.g, 2) + - Math.pow(currentColor.b - sampleColor.b, 2) - ); - // difference in color it is difference in color vectors (r1,g1,b1) <=> (r2,g2,b2) - if (colorDifference <= maxColorDifference * pixTolerance) { - matchPixCount++; - } - } - finishImage(); - }; - image.src = _frames[f].image; - } else { - finishImage(); - } - }, - callback: function () { - resultFrames = resultFrames.concat(_frames.slice(endCheckFrame)); - - if (resultFrames.length <= 0) { - // at least one last frame should be available for next manipulation - // if total duration of all frames will be < 1000 than ffmpeg doesn't work well... - resultFrames.push(_frames[_frames.length - 1]); - } - callback(resultFrames); - } - }); - } - - var isStopDrawing = false; - - /** - * This method stops recording video. - * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. - * @method - * @memberof WhammyRecorder - * @example - * recorder.stop(function(blob) { - * video.src = URL.createObjectURL(blob); - * }); - */ - this.stop = function (callback) { - callback = callback || function () { }; - - isStopDrawing = true; - - var _this = this; - // analyse of all frames takes some time! - setTimeout(function () { - // e.g. dropBlackFrames(frames, 10, 1, 1) - will cut all 10 frames - // e.g. dropBlackFrames(frames, 10, 0.5, 0.5) - will analyse 10 frames - // e.g. dropBlackFrames(frames, 10) === dropBlackFrames(frames, 10, 0, 0) - will analyse 10 frames with strict black color - dropBlackFrames(whammy.frames, -1, null, null, function (frames) { - whammy.frames = frames; - - // to display advertisement images! - if (config.advertisement && config.advertisement.length) { - whammy.frames = config.advertisement.concat(whammy.frames); - } - - /** - * @property {Blob} blob - Recorded frames in video/webm blob. - * @memberof WhammyRecorder - * @example - * recorder.stop(function() { - * var blob = recorder.blob; - * }); - */ - whammy.compile(function (blob) { - _this.blob = blob; - - if (_this.blob.forEach) { - _this.blob = new Blob([], { - type: 'video/webm' - }); - } - - if (callback) { - callback(_this.blob); - } - }); - }); - }, 10); - }; - - var isPausedRecording = false; - - /** - * This method pauses the recording process. - * @method - * @memberof WhammyRecorder - * @example - * recorder.pause(); - */ - this.pause = function () { - isPausedRecording = true; - }; - - /** - * This method resumes the recording process. - * @method - * @memberof WhammyRecorder - * @example - * recorder.resume(); - */ - this.resume = function () { - isPausedRecording = false; - - if (isStopDrawing) { - this.record(); - } - }; - - /** - * This method resets currently recorded data. - * @method - * @memberof WhammyRecorder - * @example - * recorder.clearRecordedData(); - */ - this.clearRecordedData = function () { - if (!isStopDrawing) { - this.stop(clearRecordedDataCB); - } - clearRecordedDataCB(); - }; - - function clearRecordedDataCB() { - whammy.frames = []; - isStopDrawing = true; - isPausedRecording = false; - } - - // for debugging - this.name = 'WhammyRecorder'; - this.toString = function () { - return this.name; - }; - - var canvas = document.createElement('canvas'); - var context = canvas.getContext('2d'); - - var video; - var lastTime; - var whammy; - } - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.WhammyRecorder = WhammyRecorder; - } - - // https://github.com/antimatter15/whammy/blob/master/LICENSE - // _________ - // Whammy.js - - // todo: Firefox now supports webp for webm containers! - // their MediaRecorder implementation works well! - // should we provide an option to record via Whammy.js or MediaRecorder API is a better solution? - - /** - * Whammy is a standalone class used by {@link RecordRTC} to bring video recording in Chrome. It is written by {@link https://github.com/antimatter15|antimatter15} - * @summary A real time javascript webm encoder based on a canvas hack. - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @typedef Whammy - * @class - * @example - * var recorder = new Whammy().Video(15); - * recorder.add(context || canvas || dataURL); - * var output = recorder.compile(); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - */ - - var Whammy = (function () { - // a more abstract-ish API - - function WhammyVideo(duration) { - this.frames = []; - this.duration = duration || 1; - this.quality = 0.8; - } - - /** - * Pass Canvas or Context or image/webp(string) to {@link Whammy} encoder. - * @method - * @memberof Whammy - * @example - * recorder = new Whammy().Video(0.8, 100); - * recorder.add(canvas || context || 'image/webp'); - * @param {string} frame - Canvas || Context || image/webp - * @param {number} duration - Stick a duration (in milliseconds) - */ - WhammyVideo.prototype.add = function (frame, duration) { - if ('canvas' in frame) { //CanvasRenderingContext2D - frame = frame.canvas; - } - - if ('toDataURL' in frame) { - frame = frame.toDataURL('image/webp', this.quality); - } - - if (!(/^data:image\/webp;base64,/ig).test(frame)) { - throw 'Input must be formatted properly as a base64 encoded DataURI of type image/webp'; - } - this.frames.push({ - image: frame, - duration: duration || this.duration - }); - }; - - function processInWebWorker(_function) { - var blob = URL.createObjectURL(new Blob([_function.toString(), - 'this.onmessage = function (eee) {' + _function.name + '(eee.data);}' - ], { - type: 'application/javascript' - })); - - var worker = new Worker(blob); - URL.revokeObjectURL(blob); - return worker; - } - - function whammyInWebWorker(frames) { - function ArrayToWebM(frames) { - var info = checkFrames(frames); - if (!info) { - return []; - } - - var clusterMaxDuration = 30000; - - var EBML = [{ - 'id': 0x1a45dfa3, // EBML - 'data': [{ - 'data': 1, - 'id': 0x4286 // EBMLVersion - }, { - 'data': 1, - 'id': 0x42f7 // EBMLReadVersion - }, { - 'data': 4, - 'id': 0x42f2 // EBMLMaxIDLength - }, { - 'data': 8, - 'id': 0x42f3 // EBMLMaxSizeLength - }, { - 'data': 'webm', - 'id': 0x4282 // DocType - }, { - 'data': 2, - 'id': 0x4287 // DocTypeVersion - }, { - 'data': 2, - 'id': 0x4285 // DocTypeReadVersion - }] - }, { - 'id': 0x18538067, // Segment - 'data': [{ - 'id': 0x1549a966, // Info - 'data': [{ - 'data': 1e6, //do things in millisecs (num of nanosecs for duration scale) - 'id': 0x2ad7b1 // TimecodeScale - }, { - 'data': 'whammy', - 'id': 0x4d80 // MuxingApp - }, { - 'data': 'whammy', - 'id': 0x5741 // WritingApp - }, { - 'data': doubleToString(info.duration), - 'id': 0x4489 // Duration - }] - }, { - 'id': 0x1654ae6b, // Tracks - 'data': [{ - 'id': 0xae, // TrackEntry - 'data': [{ - 'data': 1, - 'id': 0xd7 // TrackNumber - }, { - 'data': 1, - 'id': 0x73c5 // TrackUID - }, { - 'data': 0, - 'id': 0x9c // FlagLacing - }, { - 'data': 'und', - 'id': 0x22b59c // Language - }, { - 'data': 'V_VP8', - 'id': 0x86 // CodecID - }, { - 'data': 'VP8', - 'id': 0x258688 // CodecName - }, { - 'data': 1, - 'id': 0x83 // TrackType - }, { - 'id': 0xe0, // Video - 'data': [{ - 'data': info.width, - 'id': 0xb0 // PixelWidth - }, { - 'data': info.height, - 'id': 0xba // PixelHeight - }] - }] - }] - }] - }]; - - //Generate clusters (max duration) - var frameNumber = 0; - var clusterTimecode = 0; - while (frameNumber < frames.length) { - - var clusterFrames = []; - var clusterDuration = 0; - do { - clusterFrames.push(frames[frameNumber]); - clusterDuration += frames[frameNumber].duration; - frameNumber++; - } while (frameNumber < frames.length && clusterDuration < clusterMaxDuration); - - var clusterCounter = 0; - var cluster = { - 'id': 0x1f43b675, // Cluster - 'data': getClusterData(clusterTimecode, clusterCounter, clusterFrames) - }; //Add cluster to segment - EBML[1].data.push(cluster); - clusterTimecode += clusterDuration; - } - - return generateEBML(EBML); - } - - function getClusterData(clusterTimecode, clusterCounter, clusterFrames) { - return [{ - 'data': clusterTimecode, - 'id': 0xe7 // Timecode - }].concat(clusterFrames.map(function (webp) { - var block = makeSimpleBlock({ - discardable: 0, - frame: webp.data.slice(4), - invisible: 0, - keyframe: 1, - lacing: 0, - trackNum: 1, - timecode: Math.round(clusterCounter) - }); - clusterCounter += webp.duration; - return { - data: block, - id: 0xa3 - }; - })); - } - - // sums the lengths of all the frames and gets the duration - - function checkFrames(frames) { - if (!frames[0]) { - postMessage({ - error: 'Something went wrong. Maybe WebP format is not supported in the current browser.' - }); - return; - } - - var width = frames[0].width, - height = frames[0].height, - duration = frames[0].duration; - - for (var i = 1; i < frames.length; i++) { - duration += frames[i].duration; - } - return { - duration: duration, - width: width, - height: height - }; - } - - function numToBuffer(num) { - var parts = []; - while (num > 0) { - parts.push(num & 0xff); - num = num >> 8; - } - return new Uint8Array(parts.reverse()); - } - - function strToBuffer(str) { - return new Uint8Array(str.split('').map(function (e) { - return e.charCodeAt(0); - })); - } - - function bitsToBuffer(bits) { - var data = []; - var pad = (bits.length % 8) ? (new Array(1 + 8 - (bits.length % 8))).join('0') : ''; - bits = pad + bits; - for (var i = 0; i < bits.length; i += 8) { - data.push(parseInt(bits.substr(i, 8), 2)); - } - return new Uint8Array(data); - } - - function generateEBML(json) { - var ebml = []; - for (var i = 0; i < json.length; i++) { - var data = json[i].data; - - if (typeof data === 'object') { - data = generateEBML(data); - } - - if (typeof data === 'number') { - data = bitsToBuffer(data.toString(2)); - } - - if (typeof data === 'string') { - data = strToBuffer(data); - } - - var len = data.size || data.byteLength || data.length; - var zeroes = Math.ceil(Math.ceil(Math.log(len) / Math.log(2)) / 8); - var sizeToString = len.toString(2); - var padded = (new Array((zeroes * 7 + 7 + 1) - sizeToString.length)).join('0') + sizeToString; - var size = (new Array(zeroes)).join('0') + '1' + padded; - - ebml.push(numToBuffer(json[i].id)); - ebml.push(bitsToBuffer(size)); - ebml.push(data); - } - - return new Blob(ebml, { - type: 'video/webm' - }); - } - - function makeSimpleBlock(data) { - var flags = 0; - - if (data.keyframe) { - flags |= 128; - } - - if (data.invisible) { - flags |= 8; - } - - if (data.lacing) { - flags |= (data.lacing << 1); - } - - if (data.discardable) { - flags |= 1; - } - - if (data.trackNum > 127) { - throw 'TrackNumber > 127 not supported'; - } - - var out = [data.trackNum | 0x80, data.timecode >> 8, data.timecode & 0xff, flags].map(function (e) { - return String.fromCharCode(e); - }).join('') + data.frame; - - return out; - } - - function parseWebP(riff) { - var VP8 = riff.RIFF[0].WEBP[0]; - - var frameStart = VP8.indexOf('\x9d\x01\x2a'); // A VP8 keyframe starts with the 0x9d012a header - for (var i = 0, c = []; i < 4; i++) { - c[i] = VP8.charCodeAt(frameStart + 3 + i); - } - - var width, height, tmp; - - //the code below is literally copied verbatim from the bitstream spec - tmp = (c[1] << 8) | c[0]; - width = tmp & 0x3FFF; - tmp = (c[3] << 8) | c[2]; - height = tmp & 0x3FFF; - return { - width: width, - height: height, - data: VP8, - riff: riff - }; - } - - function getStrLength(string, offset) { - return parseInt(string.substr(offset + 4, 4).split('').map(function (i) { - var unpadded = i.charCodeAt(0).toString(2); - return (new Array(8 - unpadded.length + 1)).join('0') + unpadded; - }).join(''), 2); - } - - function parseRIFF(string) { - var offset = 0; - var chunks = {}; - - while (offset < string.length) { - var id = string.substr(offset, 4); - var len = getStrLength(string, offset); - var data = string.substr(offset + 4 + 4, len); - offset += 4 + 4 + len; - chunks[id] = chunks[id] || []; - - if (id === 'RIFF' || id === 'LIST') { - chunks[id].push(parseRIFF(data)); - } else { - chunks[id].push(data); - } - } - return chunks; - } - - function doubleToString(num) { - return [].slice.call( - new Uint8Array((new Float64Array([num])).buffer), 0).map(function (e) { - return String.fromCharCode(e); - }).reverse().join(''); - } - - var webm = new ArrayToWebM(frames.map(function (frame) { - var webp = parseWebP(parseRIFF(atob(frame.image.slice(23)))); - webp.duration = frame.duration; - return webp; - })); - - postMessage(webm); - } - - /** - * Encodes frames in WebM container. It uses WebWorkinvoke to invoke 'ArrayToWebM' method. - * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. - * @method - * @memberof Whammy - * @example - * recorder = new Whammy().Video(0.8, 100); - * recorder.compile(function(blob) { - * // blob.size - blob.type - * }); - */ - WhammyVideo.prototype.compile = function (callback) { - var webWorker = processInWebWorker(whammyInWebWorker); - - webWorker.onmessage = function (event) { - if (event.data.error) { - console.error(event.data.error); - return; - } - callback(event.data); - }; - - webWorker.postMessage(this.frames); - }; - - return { - /** - * A more abstract-ish API. - * @method - * @memberof Whammy - * @example - * recorder = new Whammy().Video(0.8, 100); - * @param {?number} speed - 0.8 - * @param {?number} quality - 100 - */ - Video: WhammyVideo - }; - })(); - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.Whammy = Whammy; - } - - // ______________ (indexed-db) - // DiskStorage.js - - /** - * DiskStorage is a standalone object used by {@link RecordRTC} to store recorded blobs in IndexedDB storage. - * @summary Writing blobs into IndexedDB. - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @example - * DiskStorage.Store({ - * audioBlob: yourAudioBlob, - * videoBlob: yourVideoBlob, - * gifBlob : yourGifBlob - * }); - * DiskStorage.Fetch(function(dataURL, type) { - * if(type === 'audioBlob') { } - * if(type === 'videoBlob') { } - * if(type === 'gifBlob') { } - * }); - * // DiskStorage.dataStoreName = 'recordRTC'; - * // DiskStorage.onError = function(error) { }; - * @property {function} init - This method must be called once to initialize IndexedDB ObjectStore. Though, it is auto-used internally. - * @property {function} Fetch - This method fetches stored blobs from IndexedDB. - * @property {function} Store - This method stores blobs in IndexedDB. - * @property {function} onError - This function is invoked for any known/unknown error. - * @property {string} dataStoreName - Name of the ObjectStore created in IndexedDB storage. - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - */ - - - var DiskStorage = { - /** - * This method must be called once to initialize IndexedDB ObjectStore. Though, it is auto-used internally. - * @method - * @memberof DiskStorage - * @internal - * @example - * DiskStorage.init(); - */ - init: function () { - var self = this; - - if (typeof indexedDB === 'undefined' || typeof indexedDB.open === 'undefined') { - console.error('IndexedDB API are not available in this browser.'); - return; - } - - var dbVersion = 1; - var dbName = this.dbName || location.href.replace(/\/|:|#|%|\.|\[|\]/g, ''), - db; - var request = indexedDB.open(dbName, dbVersion); - - function createObjectStore(dataBase) { - dataBase.createObjectStore(self.dataStoreName); - } - - function putInDB() { - var transaction = db.transaction([self.dataStoreName], 'readwrite'); - - if (self.videoBlob) { - transaction.objectStore(self.dataStoreName).put(self.videoBlob, 'videoBlob'); - } - - if (self.gifBlob) { - transaction.objectStore(self.dataStoreName).put(self.gifBlob, 'gifBlob'); - } - - if (self.audioBlob) { - transaction.objectStore(self.dataStoreName).put(self.audioBlob, 'audioBlob'); - } - - function getFromStore(portionName) { - transaction.objectStore(self.dataStoreName).get(portionName).onsuccess = function (event) { - if (self.callback) { - self.callback(event.target.result, portionName); - } - }; - } - - getFromStore('audioBlob'); - getFromStore('videoBlob'); - getFromStore('gifBlob'); - } - - request.onerror = self.onError; - - request.onsuccess = function () { - db = request.result; - db.onerror = self.onError; - - if (db.setVersion) { - if (db.version !== dbVersion) { - var setVersion = db.setVersion(dbVersion); - setVersion.onsuccess = function () { - createObjectStore(db); - putInDB(); - }; - } else { - putInDB(); - } - } else { - putInDB(); - } - }; - request.onupgradeneeded = function (event) { - createObjectStore(event.target.result); - }; - }, - /** - * This method fetches stored blobs from IndexedDB. - * @method - * @memberof DiskStorage - * @internal - * @example - * DiskStorage.Fetch(function(dataURL, type) { - * if(type === 'audioBlob') { } - * if(type === 'videoBlob') { } - * if(type === 'gifBlob') { } - * }); - */ - Fetch: function (callback) { - this.callback = callback; - this.init(); - - return this; - }, - /** - * This method stores blobs in IndexedDB. - * @method - * @memberof DiskStorage - * @internal - * @example - * DiskStorage.Store({ - * audioBlob: yourAudioBlob, - * videoBlob: yourVideoBlob, - * gifBlob : yourGifBlob - * }); - */ - Store: function (config) { - this.audioBlob = config.audioBlob; - this.videoBlob = config.videoBlob; - this.gifBlob = config.gifBlob; - - this.init(); - - return this; - }, - /** - * This function is invoked for any known/unknown error. - * @method - * @memberof DiskStorage - * @internal - * @example - * DiskStorage.onError = function(error){ - * alerot( JSON.stringify(error) ); - * }; - */ - onError: function (error) { - console.error(JSON.stringify(error, null, '\t')); - }, - - /** - * @property {string} dataStoreName - Name of the ObjectStore created in IndexedDB storage. - * @memberof DiskStorage - * @internal - * @example - * DiskStorage.dataStoreName = 'recordRTC'; - */ - dataStoreName: 'recordRTC', - dbName: null - }; - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.DiskStorage = DiskStorage; - } - - // ______________ - // GifRecorder.js - - /** - * GifRecorder is standalone calss used by {@link RecordRTC} to record video or canvas into animated gif. - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @typedef GifRecorder - * @class - * @example - * var recorder = new GifRecorder(mediaStream || canvas || context, { onGifPreview: function, onGifRecordingStarted: function, width: 1280, height: 720, frameRate: 200, quality: 10 }); - * recorder.record(); - * recorder.stop(function(blob) { - * img.src = URL.createObjectURL(blob); - * }); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - * @param {MediaStream} mediaStream - MediaStream object or HTMLCanvasElement or CanvasRenderingContext2D. - * @param {object} config - {disableLogs:true, initCallback: function, width: 320, height: 240, frameRate: 200, quality: 10} - */ - - function GifRecorder(mediaStream, config) { - if (typeof GIFEncoder === 'undefined') { - var script = document.createElement('script'); - script.src = 'https://www.webrtc-experiment.com/gif-recorder.js'; - (document.body || document.documentElement).appendChild(script); - } - - config = config || {}; - - var isHTMLObject = mediaStream instanceof CanvasRenderingContext2D || mediaStream instanceof HTMLCanvasElement; - - /** - * This method records MediaStream. - * @method - * @memberof GifRecorder - * @example - * recorder.record(); - */ - this.record = function () { - if (typeof GIFEncoder === 'undefined') { - setTimeout(self.record, 1000); - return; - } - - if (!isLoadedMetaData) { - setTimeout(self.record, 1000); - return; - } - - if (!isHTMLObject) { - if (!config.width) { - config.width = video.offsetWidth || 320; - } - - if (!config.height) { - config.height = video.offsetHeight || 240; - } - - if (!config.video) { - config.video = { - width: config.width, - height: config.height - }; - } - - if (!config.canvas) { - config.canvas = { - width: config.width, - height: config.height - }; - } - - canvas.width = config.canvas.width || 320; - canvas.height = config.canvas.height || 240; - - video.width = config.video.width || 320; - video.height = config.video.height || 240; - } - - // external library to record as GIF images - gifEncoder = new GIFEncoder(); - - // void setRepeat(int iter) - // Sets the number of times the set of GIF frames should be played. - // Default is 1; 0 means play indefinitely. - gifEncoder.setRepeat(0); - - // void setFrameRate(Number fps) - // Sets frame rate in frames per second. - // Equivalent to setDelay(1000/fps). - // Using "setDelay" instead of "setFrameRate" - gifEncoder.setDelay(config.frameRate || 200); - - // void setQuality(int quality) - // Sets quality of color quantization (conversion of images to the - // maximum 256 colors allowed by the GIF specification). - // Lower values (minimum = 1) produce better colors, - // but slow processing significantly. 10 is the default, - // and produces good color mapping at reasonable speeds. - // Values greater than 20 do not yield significant improvements in speed. - gifEncoder.setQuality(config.quality || 10); - - // Boolean start() - // This writes the GIF Header and returns false if it fails. - gifEncoder.start(); - - if (typeof config.onGifRecordingStarted === 'function') { - config.onGifRecordingStarted(); - } - - function drawVideoFrame(time) { - if (self.clearedRecordedData === true) { - return; - } - - if (isPausedRecording) { - return setTimeout(function () { - drawVideoFrame(time); - }, 100); - } - - lastAnimationFrame = requestAnimationFrame(drawVideoFrame); - - if (typeof lastFrameTime === undefined) { - lastFrameTime = time; - } - - // ~10 fps - if (time - lastFrameTime < 90) { - return; - } - - if (!isHTMLObject && video.paused) { - // via: https://github.com/muaz-khan/WebRTC-Experiment/pull/316 - // Tweak for Android Chrome - video.play(); - } - - if (!isHTMLObject) { - context.drawImage(video, 0, 0, canvas.width, canvas.height); - } - - if (config.onGifPreview) { - config.onGifPreview(canvas.toDataURL('image/png')); - } - - gifEncoder.addFrame(context); - lastFrameTime = time; - } - - lastAnimationFrame = requestAnimationFrame(drawVideoFrame); - - if (config.initCallback) { - config.initCallback(); - } - }; - - /** - * This method stops recording MediaStream. - * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. - * @method - * @memberof GifRecorder - * @example - * recorder.stop(function(blob) { - * img.src = URL.createObjectURL(blob); - * }); - */ - this.stop = function (callback) { - callback = callback || function () { }; - - if (lastAnimationFrame) { - cancelAnimationFrame(lastAnimationFrame); - } - - /** - * @property {Blob} blob - The recorded blob object. - * @memberof GifRecorder - * @example - * recorder.stop(function(){ - * var blob = recorder.blob; - * }); - */ - this.blob = new Blob([new Uint8Array(gifEncoder.stream().bin)], { - type: 'image/gif' - }); - - callback(this.blob); - - // bug: find a way to clear old recorded blobs - gifEncoder.stream().bin = []; - }; - - var isPausedRecording = false; - - /** - * This method pauses the recording process. - * @method - * @memberof GifRecorder - * @example - * recorder.pause(); - */ - this.pause = function () { - isPausedRecording = true; - }; - - /** - * This method resumes the recording process. - * @method - * @memberof GifRecorder - * @example - * recorder.resume(); - */ - this.resume = function () { - isPausedRecording = false; - }; - - /** - * This method resets currently recorded data. - * @method - * @memberof GifRecorder - * @example - * recorder.clearRecordedData(); - */ - this.clearRecordedData = function () { - self.clearedRecordedData = true; - clearRecordedDataCB(); - }; - - function clearRecordedDataCB() { - if (gifEncoder) { - gifEncoder.stream().bin = []; - } - } - - // for debugging - this.name = 'GifRecorder'; - this.toString = function () { - return this.name; - }; - - var canvas = document.createElement('canvas'); - var context = canvas.getContext('2d'); - - if (isHTMLObject) { - if (mediaStream instanceof CanvasRenderingContext2D) { - context = mediaStream; - canvas = context.canvas; - } else if (mediaStream instanceof HTMLCanvasElement) { - context = mediaStream.getContext('2d'); - canvas = mediaStream; - } - } - - var isLoadedMetaData = true; - - if (!isHTMLObject) { - var video = document.createElement('video'); - video.muted = true; - video.autoplay = true; - video.playsInline = true; - - isLoadedMetaData = false; - video.onloadedmetadata = function () { - isLoadedMetaData = true; - }; - - setSrcObject(mediaStream, video); - - video.play(); - } - - var lastAnimationFrame = null; - var lastFrameTime; - - var gifEncoder; - - var self = this; - } - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.GifRecorder = GifRecorder; - } - - // Last time updated: 2019-06-21 4:09:42 AM UTC - - // ________________________ - // MultiStreamsMixer v1.2.2 - - // Open-Sourced: https://github.com/muaz-khan/MultiStreamsMixer - - // -------------------------------------------------- - // Muaz Khan - www.MuazKhan.com - // MIT License - www.WebRTC-Experiment.com/licence - // -------------------------------------------------- - - function MultiStreamsMixer(arrayOfMediaStreams, elementClass) { - - var browserFakeUserAgent = 'Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45'; - - (function (that) { - if (typeof RecordRTC !== 'undefined') { - return; - } - - if (!that) { - return; - } - - if (typeof window !== 'undefined') { - return; - } - - if (typeof commonjsGlobal === 'undefined') { - return; - } - - commonjsGlobal.navigator = { - userAgent: browserFakeUserAgent, - getUserMedia: function () { } - }; - - if (!commonjsGlobal.console) { - commonjsGlobal.console = {}; - } - - if (typeof commonjsGlobal.console.log === 'undefined' || typeof commonjsGlobal.console.error === 'undefined') { - commonjsGlobal.console.error = commonjsGlobal.console.log = commonjsGlobal.console.log || function () { - console.log(arguments); - }; - } - - if (typeof document === 'undefined') { - /*global document:true */ - that.document = { - documentElement: { - appendChild: function () { - return ''; - } - } - }; - - document.createElement = document.captureStream = document.mozCaptureStream = function () { - var obj = { - getContext: function () { - return obj; - }, - play: function () { }, - pause: function () { }, - drawImage: function () { }, - toDataURL: function () { - return ''; - }, - style: {} - }; - return obj; - }; - - that.HTMLVideoElement = function () { }; - } - - if (typeof location === 'undefined') { - /*global location:true */ - that.location = { - protocol: 'file:', - href: '', - hash: '' - }; - } - - if (typeof screen === 'undefined') { - /*global screen:true */ - that.screen = { - width: 0, - height: 0 - }; - } - - if (typeof URL === 'undefined') { - /*global screen:true */ - that.URL = { - createObjectURL: function () { - return ''; - }, - revokeObjectURL: function () { - return ''; - } - }; - } - - /*global window:true */ - that.window = commonjsGlobal; - })(typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : null); - - // requires: chrome://flags/#enable-experimental-web-platform-features - - elementClass = elementClass || 'multi-streams-mixer'; - - var videos = []; - var isStopDrawingFrames = false; - - var canvas = document.createElement('canvas'); - var context = canvas.getContext('2d'); - canvas.style.opacity = 0; - canvas.style.position = 'absolute'; - canvas.style.zIndex = -1; - canvas.style.top = '-1000em'; - canvas.style.left = '-1000em'; - canvas.className = elementClass; - (document.body || document.documentElement).appendChild(canvas); - - this.disableLogs = false; - this.frameInterval = 10; - - this.width = 360; - this.height = 240; - - // use gain node to prevent echo - this.useGainNode = true; - - var self = this; - - // _____________________________ - // Cross-Browser-Declarations.js - - // WebAudio API representer - var AudioContext = window.AudioContext; - - if (typeof AudioContext === 'undefined') { - if (typeof webkitAudioContext !== 'undefined') { - /*global AudioContext:true */ - AudioContext = webkitAudioContext; - } - - if (typeof mozAudioContext !== 'undefined') { - /*global AudioContext:true */ - AudioContext = mozAudioContext; - } - } - - /*jshint -W079 */ - var URL = window.URL; - - if (typeof URL === 'undefined' && typeof webkitURL !== 'undefined') { - /*global URL:true */ - URL = webkitURL; - } - - if (typeof navigator !== 'undefined' && typeof navigator.getUserMedia === 'undefined') { // maybe window.navigator? - if (typeof navigator.webkitGetUserMedia !== 'undefined') { - navigator.getUserMedia = navigator.webkitGetUserMedia; - } - - if (typeof navigator.mozGetUserMedia !== 'undefined') { - navigator.getUserMedia = navigator.mozGetUserMedia; - } - } - - var MediaStream = window.MediaStream; - - if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefined') { - MediaStream = webkitMediaStream; - } - - /*global MediaStream:true */ - if (typeof MediaStream !== 'undefined') { - // override "stop" method for all browsers - if (typeof MediaStream.prototype.stop === 'undefined') { - MediaStream.prototype.stop = function () { - this.getTracks().forEach(function (track) { - track.stop(); - }); - }; - } - } - - var Storage = {}; - - if (typeof AudioContext !== 'undefined') { - Storage.AudioContext = AudioContext; - } else if (typeof webkitAudioContext !== 'undefined') { - Storage.AudioContext = webkitAudioContext; - } - - function setSrcObject(stream, element) { - if ('srcObject' in element) { - element.srcObject = stream; - } else if ('mozSrcObject' in element) { - element.mozSrcObject = stream; - } else { - element.srcObject = stream; - } - } - - this.startDrawingFrames = function () { - drawVideosToCanvas(); - }; - - function drawVideosToCanvas() { - if (isStopDrawingFrames) { - return; - } - - var videosLength = videos.length; - - var fullcanvas = false; - var remaining = []; - videos.forEach(function (video) { - if (!video.stream) { - video.stream = {}; - } - - if (video.stream.fullcanvas) { - fullcanvas = video; - } else { - // todo: video.stream.active or video.stream.live to fix blank frames issues? - remaining.push(video); - } - }); - - if (fullcanvas) { - canvas.width = fullcanvas.stream.width; - canvas.height = fullcanvas.stream.height; - } else if (remaining.length) { - canvas.width = videosLength > 1 ? remaining[0].width * 2 : remaining[0].width; - - var height = 1; - if (videosLength === 3 || videosLength === 4) { - height = 2; - } - if (videosLength === 5 || videosLength === 6) { - height = 3; - } - if (videosLength === 7 || videosLength === 8) { - height = 4; - } - if (videosLength === 9 || videosLength === 10) { - height = 5; - } - canvas.height = remaining[0].height * height; - } else { - canvas.width = self.width || 360; - canvas.height = self.height || 240; - } - - if (fullcanvas && fullcanvas instanceof HTMLVideoElement) { - drawImage(fullcanvas); - } - - remaining.forEach(function (video, idx) { - drawImage(video, idx); - }); - - setTimeout(drawVideosToCanvas, self.frameInterval); - } - - function drawImage(video, idx) { - if (isStopDrawingFrames) { - return; - } - - var x = 0; - var y = 0; - var width = video.width; - var height = video.height; - - if (idx === 1) { - x = video.width; - } - - if (idx === 2) { - y = video.height; - } - - if (idx === 3) { - x = video.width; - y = video.height; - } - - if (idx === 4) { - y = video.height * 2; - } - - if (idx === 5) { - x = video.width; - y = video.height * 2; - } - - if (idx === 6) { - y = video.height * 3; - } - - if (idx === 7) { - x = video.width; - y = video.height * 3; - } - - if (typeof video.stream.left !== 'undefined') { - x = video.stream.left; - } - - if (typeof video.stream.top !== 'undefined') { - y = video.stream.top; - } - - if (typeof video.stream.width !== 'undefined') { - width = video.stream.width; - } - - if (typeof video.stream.height !== 'undefined') { - height = video.stream.height; - } - - context.drawImage(video, x, y, width, height); - - if (typeof video.stream.onRender === 'function') { - video.stream.onRender(context, x, y, width, height, idx); - } - } - - function getMixedStream() { - isStopDrawingFrames = false; - var mixedVideoStream = getMixedVideoStream(); - - var mixedAudioStream = getMixedAudioStream(); - if (mixedAudioStream) { - mixedAudioStream.getTracks().filter(function (t) { - return t.kind === 'audio'; - }).forEach(function (track) { - mixedVideoStream.addTrack(track); - }); - } - arrayOfMediaStreams.forEach(function (stream) { - if (stream.fullcanvas); - }); - - // mixedVideoStream.prototype.appendStreams = appendStreams; - // mixedVideoStream.prototype.resetVideoStreams = resetVideoStreams; - // mixedVideoStream.prototype.clearRecordedData = clearRecordedData; - - return mixedVideoStream; - } - - function getMixedVideoStream() { - resetVideoStreams(); - - var capturedStream; - - if ('captureStream' in canvas) { - capturedStream = canvas.captureStream(); - } else if ('mozCaptureStream' in canvas) { - capturedStream = canvas.mozCaptureStream(); - } else if (!self.disableLogs) { - console.error('Upgrade to latest Chrome or otherwise enable this flag: chrome://flags/#enable-experimental-web-platform-features'); - } - - var videoStream = new MediaStream(); - - capturedStream.getTracks().filter(function (t) { - return t.kind === 'video'; - }).forEach(function (track) { - videoStream.addTrack(track); - }); - - canvas.stream = videoStream; - - return videoStream; - } - - function getMixedAudioStream() { - // via: @pehrsons - if (!Storage.AudioContextConstructor) { - Storage.AudioContextConstructor = new Storage.AudioContext(); - } - - self.audioContext = Storage.AudioContextConstructor; - - self.audioSources = []; - - if (self.useGainNode === true) { - self.gainNode = self.audioContext.createGain(); - self.gainNode.connect(self.audioContext.destination); - self.gainNode.gain.value = 0; // don't hear self - } - - var audioTracksLength = 0; - arrayOfMediaStreams.forEach(function (stream) { - if (!stream.getTracks().filter(function (t) { - return t.kind === 'audio'; - }).length) { - return; - } - - audioTracksLength++; - - var audioSource = self.audioContext.createMediaStreamSource(stream); - - if (self.useGainNode === true) { - audioSource.connect(self.gainNode); - } - - self.audioSources.push(audioSource); - }); - - if (!audioTracksLength) { - // because "self.audioContext" is not initialized - // that's why we've to ignore rest of the code - return; - } - - self.audioDestination = self.audioContext.createMediaStreamDestination(); - self.audioSources.forEach(function (audioSource) { - audioSource.connect(self.audioDestination); - }); - return self.audioDestination.stream; - } - - function getVideo(stream) { - var video = document.createElement('video'); - - setSrcObject(stream, video); - - video.className = elementClass; - - video.muted = true; - video.volume = 0; - - video.width = stream.width || self.width || 360; - video.height = stream.height || self.height || 240; - - video.play(); - - return video; - } - - this.appendStreams = function (streams) { - if (!streams) { - throw 'First parameter is required.'; - } - - if (!(streams instanceof Array)) { - streams = [streams]; - } - - streams.forEach(function (stream) { - var newStream = new MediaStream(); - - if (stream.getTracks().filter(function (t) { - return t.kind === 'video'; - }).length) { - var video = getVideo(stream); - video.stream = stream; - videos.push(video); - - newStream.addTrack(stream.getTracks().filter(function (t) { - return t.kind === 'video'; - })[0]); - } - - if (stream.getTracks().filter(function (t) { - return t.kind === 'audio'; - }).length) { - var audioSource = self.audioContext.createMediaStreamSource(stream); - self.audioDestination = self.audioContext.createMediaStreamDestination(); - audioSource.connect(self.audioDestination); - - newStream.addTrack(self.audioDestination.stream.getTracks().filter(function (t) { - return t.kind === 'audio'; - })[0]); - } - - arrayOfMediaStreams.push(newStream); - }); - }; - - this.releaseStreams = function () { - videos = []; - isStopDrawingFrames = true; - - if (self.gainNode) { - self.gainNode.disconnect(); - self.gainNode = null; - } - - if (self.audioSources.length) { - self.audioSources.forEach(function (source) { - source.disconnect(); - }); - self.audioSources = []; - } - - if (self.audioDestination) { - self.audioDestination.disconnect(); - self.audioDestination = null; - } - - if (self.audioContext) { - self.audioContext.close(); - } - - self.audioContext = null; - - context.clearRect(0, 0, canvas.width, canvas.height); - - if (canvas.stream) { - canvas.stream.stop(); - canvas.stream = null; - } - }; - - this.resetVideoStreams = function (streams) { - if (streams && !(streams instanceof Array)) { - streams = [streams]; - } - - resetVideoStreams(streams); - }; - - function resetVideoStreams(streams) { - videos = []; - streams = streams || arrayOfMediaStreams; - - // via: @adrian-ber - streams.forEach(function (stream) { - if (!stream.getTracks().filter(function (t) { - return t.kind === 'video'; - }).length) { - return; - } - - var video = getVideo(stream); - video.stream = stream; - videos.push(video); - }); - } - - // for debugging - this.name = 'MultiStreamsMixer'; - this.toString = function () { - return this.name; - }; - - this.getMixedStream = getMixedStream; - - } - - if (typeof RecordRTC === 'undefined') { - { - module.exports = MultiStreamsMixer; - } - } - - // ______________________ - // MultiStreamRecorder.js - - /* - * Video conference recording, using captureStream API along with WebAudio and Canvas2D API. - */ - - /** - * MultiStreamRecorder can record multiple videos in single container. - * @summary Multi-videos recorder. - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @typedef MultiStreamRecorder - * @class - * @example - * var options = { - * mimeType: 'video/webm' - * } - * var recorder = new MultiStreamRecorder(ArrayOfMediaStreams, options); - * recorder.record(); - * recorder.stop(function(blob) { - * video.src = URL.createObjectURL(blob); - * - * // or - * var blob = recorder.blob; - * }); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - * @param {MediaStreams} mediaStreams - Array of MediaStreams. - * @param {object} config - {disableLogs:true, frameInterval: 1, mimeType: "video/webm"} - */ - - function MultiStreamRecorder(arrayOfMediaStreams, options) { - arrayOfMediaStreams = arrayOfMediaStreams || []; - var self = this; - - var mixer; - var mediaRecorder; - - options = options || { - elementClass: 'multi-streams-mixer', - mimeType: 'video/webm', - video: { - width: 360, - height: 240 - } - }; - - if (!options.frameInterval) { - options.frameInterval = 10; - } - - if (!options.video) { - options.video = {}; - } - - if (!options.video.width) { - options.video.width = 360; - } - - if (!options.video.height) { - options.video.height = 240; - } - - /** - * This method records all MediaStreams. - * @method - * @memberof MultiStreamRecorder - * @example - * recorder.record(); - */ - this.record = function () { - // github/muaz-khan/MultiStreamsMixer - mixer = new MultiStreamsMixer(arrayOfMediaStreams, options.elementClass || 'multi-streams-mixer'); - - if (getAllVideoTracks().length) { - mixer.frameInterval = options.frameInterval || 10; - mixer.width = options.video.width || 360; - mixer.height = options.video.height || 240; - mixer.startDrawingFrames(); - } - - if (options.previewStream && typeof options.previewStream === 'function') { - options.previewStream(mixer.getMixedStream()); - } - - // record using MediaRecorder API - mediaRecorder = new MediaStreamRecorder(mixer.getMixedStream(), options); - mediaRecorder.record(); - }; - - function getAllVideoTracks() { - var tracks = []; - arrayOfMediaStreams.forEach(function (stream) { - getTracks(stream, 'video').forEach(function (track) { - tracks.push(track); - }); - }); - return tracks; - } - - /** - * This method stops recording MediaStream. - * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. - * @method - * @memberof MultiStreamRecorder - * @example - * recorder.stop(function(blob) { - * video.src = URL.createObjectURL(blob); - * }); - */ - this.stop = function (callback) { - if (!mediaRecorder) { - return; - } - - mediaRecorder.stop(function (blob) { - self.blob = blob; - - callback(blob); - - self.clearRecordedData(); - }); - }; - - /** - * This method pauses the recording process. - * @method - * @memberof MultiStreamRecorder - * @example - * recorder.pause(); - */ - this.pause = function () { - if (mediaRecorder) { - mediaRecorder.pause(); - } - }; - - /** - * This method resumes the recording process. - * @method - * @memberof MultiStreamRecorder - * @example - * recorder.resume(); - */ - this.resume = function () { - if (mediaRecorder) { - mediaRecorder.resume(); - } - }; - - /** - * This method resets currently recorded data. - * @method - * @memberof MultiStreamRecorder - * @example - * recorder.clearRecordedData(); - */ - this.clearRecordedData = function () { - if (mediaRecorder) { - mediaRecorder.clearRecordedData(); - mediaRecorder = null; - } - - if (mixer) { - mixer.releaseStreams(); - mixer = null; - } - }; - - /** - * Add extra media-streams to existing recordings. - * @method - * @memberof MultiStreamRecorder - * @param {MediaStreams} mediaStreams - Array of MediaStreams - * @example - * recorder.addStreams([newAudioStream, newVideoStream]); - */ - this.addStreams = function (streams) { - if (!streams) { - throw 'First parameter is required.'; - } - - if (!(streams instanceof Array)) { - streams = [streams]; - } - - arrayOfMediaStreams.concat(streams); - - if (!mediaRecorder || !mixer) { - return; - } - - mixer.appendStreams(streams); - - if (options.previewStream && typeof options.previewStream === 'function') { - options.previewStream(mixer.getMixedStream()); - } - }; - - /** - * Reset videos during live recording. Replace old videos e.g. replace cameras with full-screen. - * @method - * @memberof MultiStreamRecorder - * @param {MediaStreams} mediaStreams - Array of MediaStreams - * @example - * recorder.resetVideoStreams([newVideo1, newVideo2]); - */ - this.resetVideoStreams = function (streams) { - if (!mixer) { - return; - } - - if (streams && !(streams instanceof Array)) { - streams = [streams]; - } - - mixer.resetVideoStreams(streams); - }; - - /** - * Returns MultiStreamsMixer - * @method - * @memberof MultiStreamRecorder - * @example - * let mixer = recorder.getMixer(); - * mixer.appendStreams([newStream]); - */ - this.getMixer = function () { - return mixer; - }; - - // for debugging - this.name = 'MultiStreamRecorder'; - this.toString = function () { - return this.name; - }; - } - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.MultiStreamRecorder = MultiStreamRecorder; - } - - // _____________________ - // RecordRTC.promises.js - - /** - * RecordRTCPromisesHandler adds promises support in {@link RecordRTC}. Try a {@link https://github.com/muaz-khan/RecordRTC/blob/master/simple-demos/RecordRTCPromisesHandler.html|demo here} - * @summary Promises for {@link RecordRTC} - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @typedef RecordRTCPromisesHandler - * @class - * @example - * var recorder = new RecordRTCPromisesHandler(mediaStream, options); - * recorder.startRecording() - * .then(successCB) - * .catch(errorCB); - * // Note: You can access all RecordRTC API using "recorder.recordRTC" e.g. - * recorder.recordRTC.onStateChanged = function(state) {}; - * recorder.recordRTC.setRecordingDuration(5000); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - * @param {MediaStream} mediaStream - Single media-stream object, array of media-streams, html-canvas-element, etc. - * @param {object} config - {type:"video", recorderType: MediaStreamRecorder, disableLogs: true, numberOfAudioChannels: 1, bufferSize: 0, sampleRate: 0, video: HTMLVideoElement, etc.} - * @throws Will throw an error if "new" keyword is not used to initiate "RecordRTCPromisesHandler". Also throws error if first argument "MediaStream" is missing. - * @requires {@link RecordRTC} - */ - - function RecordRTCPromisesHandler(mediaStream, options) { - if (!this) { - throw 'Use "new RecordRTCPromisesHandler()"'; - } - - if (typeof mediaStream === 'undefined') { - throw 'First argument "MediaStream" is required.'; - } - - var self = this; - - /** - * @property {Blob} blob - Access/reach the native {@link RecordRTC} object. - * @memberof RecordRTCPromisesHandler - * @example - * let internal = recorder.recordRTC.getInternalRecorder(); - * alert(internal instanceof MediaStreamRecorder); - * recorder.recordRTC.onStateChanged = function(state) {}; - */ - self.recordRTC = new RecordRTC(mediaStream, options); - - /** - * This method records MediaStream. - * @method - * @memberof RecordRTCPromisesHandler - * @example - * recorder.startRecording() - * .then(successCB) - * .catch(errorCB); - */ - this.startRecording = function () { - return new Promise(function (resolve, reject) { - try { - self.recordRTC.startRecording(); - resolve(); - } catch (e) { - reject(e); - } - }); - }; - - /** - * This method stops the recording. - * @method - * @memberof RecordRTCPromisesHandler - * @example - * recorder.stopRecording().then(function() { - * var blob = recorder.getBlob(); - * }).catch(errorCB); - */ - this.stopRecording = function () { - return new Promise(function (resolve, reject) { - try { - self.recordRTC.stopRecording(function (url) { - self.blob = self.recordRTC.getBlob(); - - if (!self.blob || !self.blob.size) { - reject('Empty blob.', self.blob); - return; - } - - resolve(url); - }); - } catch (e) { - reject(e); - } - }); - }; - - /** - * This method pauses the recording. You can resume recording using "resumeRecording" method. - * @method - * @memberof RecordRTCPromisesHandler - * @example - * recorder.pauseRecording() - * .then(successCB) - * .catch(errorCB); - */ - this.pauseRecording = function () { - return new Promise(function (resolve, reject) { - try { - self.recordRTC.pauseRecording(); - resolve(); - } catch (e) { - reject(e); - } - }); - }; - - /** - * This method resumes the recording. - * @method - * @memberof RecordRTCPromisesHandler - * @example - * recorder.resumeRecording() - * .then(successCB) - * .catch(errorCB); - */ - this.resumeRecording = function () { - return new Promise(function (resolve, reject) { - try { - self.recordRTC.resumeRecording(); - resolve(); - } catch (e) { - reject(e); - } - }); - }; - - /** - * This method returns data-url for the recorded blob. - * @method - * @memberof RecordRTCPromisesHandler - * @example - * recorder.stopRecording().then(function() { - * recorder.getDataURL().then(function(dataURL) { - * window.open(dataURL); - * }).catch(errorCB);; - * }).catch(errorCB); - */ - this.getDataURL = function (callback) { - return new Promise(function (resolve, reject) { - try { - self.recordRTC.getDataURL(function (dataURL) { - resolve(dataURL); - }); - } catch (e) { - reject(e); - } - }); - }; - - /** - * This method returns the recorded blob. - * @method - * @memberof RecordRTCPromisesHandler - * @example - * recorder.stopRecording().then(function() { - * recorder.getBlob().then(function(blob) {}) - * }).catch(errorCB); - */ - this.getBlob = function () { - return new Promise(function (resolve, reject) { - try { - resolve(self.recordRTC.getBlob()); - } catch (e) { - reject(e); - } - }); - }; - - /** - * This method returns the internal recording object. - * @method - * @memberof RecordRTCPromisesHandler - * @example - * let internalRecorder = await recorder.getInternalRecorder(); - * if(internalRecorder instanceof MultiStreamRecorder) { - * internalRecorder.addStreams([newAudioStream]); - * internalRecorder.resetVideoStreams([screenStream]); - * } - * @returns {Object} - */ - this.getInternalRecorder = function () { - return new Promise(function (resolve, reject) { - try { - resolve(self.recordRTC.getInternalRecorder()); - } catch (e) { - reject(e); - } - }); - }; - - /** - * This method resets the recorder. So that you can reuse single recorder instance many times. - * @method - * @memberof RecordRTCPromisesHandler - * @example - * await recorder.reset(); - * recorder.startRecording(); // record again - */ - this.reset = function () { - return new Promise(function (resolve, reject) { - try { - resolve(self.recordRTC.reset()); - } catch (e) { - reject(e); - } - }); - }; - - /** - * Destroy RecordRTC instance. Clear all recorders and objects. - * @method - * @memberof RecordRTCPromisesHandler - * @example - * recorder.destroy().then(successCB).catch(errorCB); - */ - this.destroy = function () { - return new Promise(function (resolve, reject) { - try { - resolve(self.recordRTC.destroy()); - } catch (e) { - reject(e); - } - }); - }; - - /** - * Get recorder's readonly state. - * @method - * @memberof RecordRTCPromisesHandler - * @example - * let state = await recorder.getState(); - * // or - * recorder.getState().then(state => { console.log(state); }) - * @returns {String} Returns recording state. - */ - this.getState = function () { - return new Promise(function (resolve, reject) { - try { - resolve(self.recordRTC.getState()); - } catch (e) { - reject(e); - } - }); - }; - - /** - * @property {Blob} blob - Recorded data as "Blob" object. - * @memberof RecordRTCPromisesHandler - * @example - * await recorder.stopRecording(); - * let blob = recorder.getBlob(); // or "recorder.recordRTC.blob" - * invokeSaveAsDialog(blob); - */ - this.blob = null; - - /** - * RecordRTC version number - * @property {String} version - Release version number. - * @memberof RecordRTCPromisesHandler - * @static - * @readonly - * @example - * alert(recorder.version); - */ - this.version = '5.6.2'; - } - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.RecordRTCPromisesHandler = RecordRTCPromisesHandler; - } - - // ______________________ - // WebAssemblyRecorder.js - - /** - * WebAssemblyRecorder lets you create webm videos in JavaScript via WebAssembly. The library consumes raw RGBA32 buffers (4 bytes per pixel) and turns them into a webm video with the given framerate and quality. This makes it compatible out-of-the-box with ImageData from a CANVAS. With realtime mode you can also use webm-wasm for streaming webm videos. - * @summary Video recording feature in Chrome, Firefox and maybe Edge. - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @typedef WebAssemblyRecorder - * @class - * @example - * var recorder = new WebAssemblyRecorder(mediaStream); - * recorder.record(); - * recorder.stop(function(blob) { - * video.src = URL.createObjectURL(blob); - * }); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. - * @param {object} config - {webAssemblyPath:'webm-wasm.wasm',workerPath: 'webm-worker.js', frameRate: 30, width: 1920, height: 1080, bitrate: 1024, realtime: true} - */ - function WebAssemblyRecorder(stream, config) { - // based on: github.com/GoogleChromeLabs/webm-wasm - - if (typeof ReadableStream === 'undefined' || typeof WritableStream === 'undefined') { - // because it fixes readable/writable streams issues - console.error('Following polyfill is strongly recommended: https://unpkg.com/@mattiasbuelens/web-streams-polyfill/dist/polyfill.min.js'); - } - - config = config || {}; - - config.width = config.width || 640; - config.height = config.height || 480; - config.frameRate = config.frameRate || 30; - config.bitrate = config.bitrate || 1200; - config.realtime = config.realtime || true; - - var finished; - - function cameraStream() { - return new ReadableStream({ - start: function (controller) { - var cvs = document.createElement('canvas'); - var video = document.createElement('video'); - var first = true; - video.srcObject = stream; - video.muted = true; - video.height = config.height; - video.width = config.width; - video.volume = 0; - video.onplaying = function () { - cvs.width = config.width; - cvs.height = config.height; - var ctx = cvs.getContext('2d'); - var frameTimeout = 1000 / config.frameRate; - var cameraTimer = setInterval(function f() { - if (finished) { - clearInterval(cameraTimer); - controller.close(); - } - - if (first) { - first = false; - if (config.onVideoProcessStarted) { - config.onVideoProcessStarted(); - } - } - - ctx.drawImage(video, 0, 0); - if (controller._controlledReadableStream.state !== 'closed') { - try { - controller.enqueue( - ctx.getImageData(0, 0, config.width, config.height) - ); - } catch (e) { } - } - }, frameTimeout); - }; - video.play(); - } - }); - } - - var worker; - - function startRecording(stream, buffer) { - if (!config.workerPath && !buffer) { - finished = false; - - // is it safe to use @latest ? - - fetch( - 'https://unpkg.com/webm-wasm@latest/dist/webm-worker.js' - ).then(function (r) { - r.arrayBuffer().then(function (buffer) { - startRecording(stream, buffer); - }); - }); - return; - } - - if (!config.workerPath && buffer instanceof ArrayBuffer) { - var blob = new Blob([buffer], { - type: 'text/javascript' - }); - config.workerPath = URL.createObjectURL(blob); - } - - if (!config.workerPath) { - console.error('workerPath parameter is missing.'); - } - - worker = new Worker(config.workerPath); - - worker.postMessage(config.webAssemblyPath || 'https://unpkg.com/webm-wasm@latest/dist/webm-wasm.wasm'); - worker.addEventListener('message', function (event) { - if (event.data === 'READY') { - worker.postMessage({ - width: config.width, - height: config.height, - bitrate: config.bitrate || 1200, - timebaseDen: config.frameRate || 30, - realtime: config.realtime - }); - - cameraStream().pipeTo(new WritableStream({ - write: function (image) { - if (finished) { - console.error('Got image, but recorder is finished!'); - return; - } - - worker.postMessage(image.data.buffer, [image.data.buffer]); - } - })); - } else if (!!event.data) { - if (!isPaused) { - arrayOfBuffers.push(event.data); - } - } - }); - } - - /** - * This method records video. - * @method - * @memberof WebAssemblyRecorder - * @example - * recorder.record(); - */ - this.record = function () { - arrayOfBuffers = []; - isPaused = false; - this.blob = null; - startRecording(stream); - - if (typeof config.initCallback === 'function') { - config.initCallback(); - } - }; - - var isPaused; - - /** - * This method pauses the recording process. - * @method - * @memberof WebAssemblyRecorder - * @example - * recorder.pause(); - */ - this.pause = function () { - isPaused = true; - }; - - /** - * This method resumes the recording process. - * @method - * @memberof WebAssemblyRecorder - * @example - * recorder.resume(); - */ - this.resume = function () { - isPaused = false; - }; - - function terminate(callback) { - if (!worker) { - if (callback) { - callback(); - } - - return; - } - - // Wait for null event data to indicate that the encoding is complete - worker.addEventListener('message', function (event) { - if (event.data === null) { - worker.terminate(); - worker = null; - - if (callback) { - callback(); - } - } - }); - - worker.postMessage(null); - } - - var arrayOfBuffers = []; - - /** - * This method stops recording video. - * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. - * @method - * @memberof WebAssemblyRecorder - * @example - * recorder.stop(function(blob) { - * video.src = URL.createObjectURL(blob); - * }); - */ - this.stop = function (callback) { - finished = true; - - var recorder = this; - - terminate(function () { - recorder.blob = new Blob(arrayOfBuffers, { - type: 'video/webm' - }); - - callback(recorder.blob); - }); - }; - - // for debugging - this.name = 'WebAssemblyRecorder'; - this.toString = function () { - return this.name; - }; - - /** - * This method resets currently recorded data. - * @method - * @memberof WebAssemblyRecorder - * @example - * recorder.clearRecordedData(); - */ - this.clearRecordedData = function () { - arrayOfBuffers = []; - isPaused = false; - this.blob = null; - - // todo: if recording-ON then STOP it first - }; - - /** - * @property {Blob} blob - The recorded blob object. - * @memberof WebAssemblyRecorder - * @example - * recorder.stop(function(){ - * var blob = recorder.blob; - * }); - */ - this.blob = null; - } - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.WebAssemblyRecorder = WebAssemblyRecorder; - } - }); - - class RecordRTCLoader extends Emitter { - constructor(player) { - super(); - this.player = player; - this.fileName = ''; - this.fileType = player._opt.recordType || FILE_SUFFIX.webm; - this.isRecording = false; - this.recordingTimestamp = 0; - this.recordingInterval = null; - player.debug.log('Recorder', 'init'); - } - - destroy() { - this._reset(); - - this.player.debug.log('Recorder', 'destroy'); - } - - setFileName(fileName, fileType) { - this.fileName = fileName; - - if (FILE_SUFFIX.mp4 === fileType || FILE_SUFFIX.webm === fileType) { - this.fileType = fileType; - } - } - - get recording() { - return this.isRecording; - } - - get recordTime() { - return this.recordingTimestamp; - } - - startRecord() { - const debug = this.player.debug; - const options = { - type: 'video', - mimeType: 'video/webm;codecs=h264', - onTimeStamp: timestamp => { - debug.log('Recorder', 'record timestamp :' + timestamp); - }, - disableLogs: !this.player._opt.debug - }; - - try { - const stream = this.player.video.$videoElement.captureStream(25); - - if (this.player.audio && this.player.audio.mediaStreamAudioDestinationNode && this.player.audio.mediaStreamAudioDestinationNode.stream && !this.player.audio.isStateSuspended() && this.player.audio.hasAudio && this.player._opt.hasAudio) { - const audioStream = this.player.audio.mediaStreamAudioDestinationNode.stream; - - if (audioStream.getAudioTracks().length > 0) { - const audioTrack = audioStream.getAudioTracks()[0]; - - if (audioTrack && audioTrack.enabled) { - stream.addTrack(audioTrack); - } - } - } - - this.recorder = RecordRTC_1(stream, options); - } catch (e) { - debug.error('Recorder', 'startRecord error', e); - this.emit(EVENTS.recordCreateError); - } - - if (this.recorder) { - this.isRecording = true; - this.player.emit(EVENTS.recording, true); - this.recorder.startRecording(); - debug.log('Recorder', 'start recording'); - this.player.emit(EVENTS.recordStart); - this.recordingInterval = window.setInterval(() => { - this.recordingTimestamp += 1; - this.player.emit(EVENTS.recordingTimestamp, this.recordingTimestamp); - }, 1000); - } - } - - stopRecordAndSave() { - if (!this.recorder || !this.isRecording) { - return; - } - - this.recorder.stopRecording(() => { - this.player.debug.log('Recorder', 'stop recording'); - this.player.emit(EVENTS.recordEnd); - const fileName = (this.fileName || now()) + '.' + (this.fileType || FILE_SUFFIX.webm); - saveAs(this.recorder.getBlob(), fileName); - - this._reset(); - - this.player.emit(EVENTS.recording, false); - }); - } - - _reset() { - this.isRecording = false; - this.recordingTimestamp = 0; - - if (this.recorder) { - this.recorder.destroy(); - this.recorder = null; - } - - this.fileName = null; - - if (this.recordingInterval) { - clearInterval(this.recordingInterval); - } - - this.recordingInterval = null; - } - - } - - class Recorder { - constructor(player) { - const Loader = Recorder.getLoaderFactory(); - return new Loader(player); - } - - static getLoaderFactory() { - return RecordRTCLoader; - } - - } - - class DecoderWorker { - constructor(player) { - this.player = player; - this.decoderWorker = new Worker(player._opt.decoder); - - this._initDecoderWorker(); - - player.debug.log('decoderWorker', 'init'); - } - - destroy() { - this.decoderWorker.postMessage({ - cmd: WORKER_SEND_TYPE.close - }); - this.decoderWorker.terminate(); - this.decoderWorker = null; - this.player.debug.log(`decoderWorker`, 'destroy'); - } - - _initDecoderWorker() { - const { - debug, - events: { - proxy - } - } = this.player; - - this.decoderWorker.onmessage = event => { - const msg = event.data; - - switch (msg.cmd) { - case WORKER_CMD_TYPE.init: - debug.log(`decoderWorker`, 'onmessage:', WORKER_CMD_TYPE.init); - - if (!this.player.loaded) { - this.player.emit(EVENTS.load); - } - - this.player.emit(EVENTS.decoderWorkerInit); - - this._initWork(); - - break; - - case WORKER_CMD_TYPE.videoCode: - debug.log(`decoderWorker`, 'onmessage:', WORKER_CMD_TYPE.videoCode, msg.code); - - if (!this.player._times.decodeStart) { - this.player._times.decodeStart = now(); - } - - this.player.video.updateVideoInfo({ - encTypeCode: msg.code - }); - break; - - case WORKER_CMD_TYPE.audioCode: - debug.log(`decoderWorker`, 'onmessage:', WORKER_CMD_TYPE.audioCode, msg.code); - this.player.audio && this.player.audio.updateAudioInfo({ - encTypeCode: msg.code - }); - break; - - case WORKER_CMD_TYPE.initVideo: - debug.log(`decoderWorker`, 'onmessage:', WORKER_CMD_TYPE.initVideo, `width:${msg.w},height:${msg.h}`); - this.player.video.updateVideoInfo({ - width: msg.w, - height: msg.h - }); - - if (!this.player._opt.openWebglAlignment && !isWebglRenderSupport(msg.w)) { - this.player.emit(EVENTS_ERROR.webglAlignmentError); - return; - } - - this.player.video.initCanvasViewSize(); - break; - - case WORKER_CMD_TYPE.initAudio: - debug.log(`decoderWorker`, 'onmessage:', WORKER_CMD_TYPE.initAudio, `channels:${msg.channels},sampleRate:${msg.sampleRate}`); - - if (this.player.audio) { - this.player.audio.updateAudioInfo(msg); - this.player.audio.initScriptNode(msg); - } - - break; - - case WORKER_CMD_TYPE.render: - // debug.log(`decoderWorker`, 'onmessage:', WORKER_CMD_TYPE.render, `msg ts:${msg.ts}`); - this.player.handleRender(); - this.player.video.render(msg); - this.player.emit(EVENTS.timeUpdate, msg.ts); - this.player.updateStats({ - fps: true, - ts: msg.ts, - buf: msg.delay - }); - - if (!this.player._times.videoStart) { - this.player._times.videoStart = now(); - this.player.handlePlayToRenderTimes(); - } - - break; - - case WORKER_CMD_TYPE.playAudio: - // debug.log(`decoderWorker`, 'onmessage:', WORKER_CMD_TYPE.playAudio, `msg ts:${msg.ts}`); - // 只有在 playing 的时候。 - if (this.player.playing && this.player.audio) { - this.player.audio.play(msg.buffer, msg.ts); - } - - break; - - case WORKER_CMD_TYPE.wasmError: - if (msg.message) { - if (msg.message.indexOf(WASM_ERROR.invalidNalUnitSize) !== -1) { - this.player.emitError(EVENTS_ERROR.wasmDecodeError); - } - } - - break; - - default: - this.player[msg.cmd] && this.player[msg.cmd](msg); - } - }; - } - - _initWork() { - const opt = { - debug: this.player._opt.debug, - useOffscreen: this.player._opt.useOffscreen, - useWCS: this.player._opt.useWCS, - videoBuffer: this.player._opt.videoBuffer, - videoBufferDelay: this.player._opt.videoBufferDelay, - openWebglAlignment: this.player._opt.openWebglAlignment - }; - this.decoderWorker.postMessage({ - cmd: WORKER_SEND_TYPE.init, - opt: JSON.stringify(opt), - sampleRate: this.player.audio && this.player.audio.audioContext.sampleRate || 0 - }); - } - - decodeVideo(arrayBuffer, ts, isIFrame) { - const options = { - type: MEDIA_TYPE.video, - ts: Math.max(ts, 0), - isIFrame - }; // this.player.debug.log('decoderWorker', 'decodeVideo', options); - - this.decoderWorker.postMessage({ - cmd: WORKER_SEND_TYPE.decode, - buffer: arrayBuffer, - options - }, [arrayBuffer.buffer]); - } - - decodeAudio(arrayBuffer, ts) { - if (this.player._opt.useWCS) { - this._decodeAudioNoDelay(arrayBuffer, ts); - } else if (this.player._opt.useMSE) { - this._decodeAudioNoDelay(arrayBuffer, ts); - } else { - this._decodeAudio(arrayBuffer, ts); - } - } // - - - _decodeAudio(arrayBuffer, ts) { - const options = { - type: MEDIA_TYPE.audio, - ts: Math.max(ts, 0) - }; // this.player.debug.log('decoderWorker', 'decodeAudio',options); - - this.decoderWorker.postMessage({ - cmd: WORKER_SEND_TYPE.decode, - buffer: arrayBuffer, - options - }, [arrayBuffer.buffer]); - } - - _decodeAudioNoDelay(arrayBuffer, ts) { - // console.log('_decodeAudioNoDelay', arrayBuffer); - this.decoderWorker.postMessage({ - cmd: WORKER_SEND_TYPE.audioDecode, - buffer: arrayBuffer, - ts: Math.max(ts, 0) - }, [arrayBuffer.buffer]); - } - - updateWorkConfig(config) { - this.decoderWorker.postMessage({ - cmd: WORKER_SEND_TYPE.updateConfig, - key: config.key, - value: config.value - }); - } - - } - - class CommonLoader extends Emitter { - constructor(player) { - super(); - this.player = player; - this.stopId = null; - this.firstTimestamp = null; - this.startTimestamp = null; - this.delay = -1; - this.bufferList = []; - this.dropping = false; - this.initInterval(); - } - - destroy() { - if (this.stopId) { - clearInterval(this.stopId); - this.stopId = null; - } - - this.firstTimestamp = null; - this.startTimestamp = null; - this.delay = -1; - this.bufferList = []; - this.dropping = false; - this.off(); - this.player.debug.log('CommonDemux', 'destroy'); - } - - getDelay(timestamp) { - if (!timestamp) { - return -1; - } - - if (!this.firstTimestamp) { - this.firstTimestamp = timestamp; - this.startTimestamp = Date.now(); - this.delay = -1; - } else { - if (timestamp) { - const localTimestamp = Date.now() - this.startTimestamp; - const timeTimestamp = timestamp - this.firstTimestamp; - - if (localTimestamp >= timeTimestamp) { - this.delay = localTimestamp - timeTimestamp; - } else { - this.delay = timeTimestamp - localTimestamp; - } - } - } - - return this.delay; - } - - resetDelay() { - this.firstTimestamp = null; - this.startTimestamp = null; - this.delay = -1; - this.dropping = false; - } // - - - initInterval() { - this.player.debug.log('common dumex', `init Interval`); - - let _loop = () => { - let data; - const videoBuffer = this.player._opt.videoBuffer; - const videoBufferDelay = this.player._opt.videoBufferDelay; - - if (this.player._opt.useMSE && this.player.mseDecoder && this.player.mseDecoder.getSourceBufferUpdating()) { - this.player.debug.warn('CommonDemux', `_loop getSourceBufferUpdating is true and bufferList length is ${this.bufferList.length}`); - return; - } - - if (this.bufferList.length) { - if (this.dropping) { - // this.player.debug.log('common dumex', `is dropping`); - data = this.bufferList.shift(); - - if (data.type === MEDIA_TYPE.audio && data.payload[1] === 0) { - this._doDecoderDecode(data); - } - - while (!data.isIFrame && this.bufferList.length) { - data = this.bufferList.shift(); - - if (data.type === MEDIA_TYPE.audio && data.payload[1] === 0) { - this._doDecoderDecode(data); - } - } // i frame - - - if (data.isIFrame && this.getDelay(data.ts) <= Math.min(videoBuffer, 200)) { - this.dropping = false; - - this._doDecoderDecode(data); - } - } else { - data = this.bufferList[0]; - - if (this.getDelay(data.ts) === -1) { - // this.player.debug.log('common dumex', `delay is -1`); - this.bufferList.shift(); - - this._doDecoderDecode(data); - } else if (this.delay > videoBuffer + videoBufferDelay) { - // this.player.debug.log('common dumex', `delay is ${this.delay}, set dropping is true`); - this.resetDelay(); - this.dropping = true; - } else { - data = this.bufferList[0]; - - if (this.getDelay(data.ts) > videoBuffer) { - // drop frame - this.bufferList.shift(); - - this._doDecoderDecode(data); - } - } - } - } - }; - - _loop(); - - this.stopId = setInterval(_loop, 10); - } - - _doDecode(payload, type, ts, isIFrame, cts) { - const player = this.player; - let options = { - ts: ts, - cts: cts, - type: type, - isIFrame: false - }; // use offscreen - - if (player._opt.useWCS && !player._opt.useOffscreen) { - if (type === MEDIA_TYPE.video) { - options.isIFrame = isIFrame; - } - - this.pushBuffer(payload, options); - } else if (player._opt.useMSE) { - // use mse - if (type === MEDIA_TYPE.video) { - options.isIFrame = isIFrame; - } - - this.pushBuffer(payload, options); - } else { - // - if (type === MEDIA_TYPE.video) { - player.decoderWorker && player.decoderWorker.decodeVideo(payload, ts, isIFrame); - } else if (type === MEDIA_TYPE.audio) { - if (player._opt.hasAudio) { - player.decoderWorker && player.decoderWorker.decodeAudio(payload, ts); - } - } - } - } - - _doDecoderDecode(data) { - const player = this.player; - const { - webcodecsDecoder, - mseDecoder - } = player; - - if (data.type === MEDIA_TYPE.audio) { - if (player._opt.hasAudio) { - player.decoderWorker && player.decoderWorker.decodeAudio(data.payload, data.ts); - } - } else if (data.type === MEDIA_TYPE.video) { - if (player._opt.useWCS && !player._opt.useOffscreen) { - webcodecsDecoder.decodeVideo(data.payload, data.ts, data.isIFrame); - } else if (player._opt.useMSE) { - mseDecoder.decodeVideo(data.payload, data.ts, data.isIFrame, data.cts); - } - } - } - - pushBuffer(payload, options) { - // 音频 - if (options.type === MEDIA_TYPE.audio) { - this.bufferList.push({ - ts: options.ts, - payload: payload, - type: MEDIA_TYPE.audio - }); - } else if (options.type === MEDIA_TYPE.video) { - this.bufferList.push({ - ts: options.ts, - cts: options.cts, - payload: payload, - type: MEDIA_TYPE.video, - isIFrame: options.isIFrame - }); - } - } - - close() { } - - } - - class FlvLoader extends CommonLoader { - constructor(player) { - super(player); - this.input = this._inputFlv(); - this.flvDemux = this.dispatchFlvData(this.input); - player.debug.log('FlvDemux', 'init'); - } - - destroy() { - super.destroy(); - this.input = null; - this.flvDemux = null; - this.player.debug.log('FlvDemux', 'destroy'); - } - - dispatch(data) { - this.flvDemux(data); - } - - *_inputFlv() { - yield 9; - const tmp = new ArrayBuffer(4); - const tmp8 = new Uint8Array(tmp); - const tmp32 = new Uint32Array(tmp); - const player = this.player; - - while (true) { - tmp8[3] = 0; - const t = yield 15; - const type = t[4]; - tmp8[0] = t[7]; - tmp8[1] = t[6]; - tmp8[2] = t[5]; - const length = tmp32[0]; - tmp8[0] = t[10]; - tmp8[1] = t[9]; - tmp8[2] = t[8]; - let ts = tmp32[0]; - - if (ts === 0xFFFFFF) { - tmp8[3] = t[11]; - ts = tmp32[0]; - } - - const payload = yield length; - - switch (type) { - case FLV_MEDIA_TYPE.audio: - if (player._opt.hasAudio) { - player.updateStats({ - abps: payload.byteLength - }); - - if (payload.byteLength > 0) { - this._doDecode(payload, MEDIA_TYPE.audio, ts); - } - } - - break; - - case FLV_MEDIA_TYPE.video: - if (!player._times.demuxStart) { - player._times.demuxStart = now(); - } - - if (player._opt.hasVideo) { - player.updateStats({ - vbps: payload.byteLength - }); - const isIFrame = payload[0] >> 4 === 1; - - if (payload.byteLength > 0) { - tmp32[0] = payload[4]; - tmp32[1] = payload[3]; - tmp32[2] = payload[2]; - tmp32[3] = 0; - let cts = tmp32[0]; - - this._doDecode(payload, MEDIA_TYPE.video, ts, isIFrame, cts); - } - } - - break; - } - } - } - - dispatchFlvData(input) { - let need = input.next(); - let buffer = null; - return value => { - let data = new Uint8Array(value); - - if (buffer) { - let combine = new Uint8Array(buffer.length + data.length); - combine.set(buffer); - combine.set(data, buffer.length); - data = combine; - buffer = null; - } - - while (data.length >= need.value) { - let remain = data.slice(need.value); - need = input.next(data.slice(0, need.value)); - data = remain; - } - - if (data.length > 0) { - buffer = data; - } - }; - } - - close() { - this.input && this.input.return(null); - } - - } - - class M7sLoader extends CommonLoader { - constructor(player) { - super(player); - player.debug.log('M7sDemux', 'init'); - } - - destroy() { - super.destroy(); - this.player.debug.log('M7sDemux', 'destroy'); - this.player = null; - } - - dispatch(data) { - const player = this.player; - const dv = new DataView(data); - const type = dv.getUint8(0); - const ts = dv.getUint32(1, false); - - switch (type) { - case MEDIA_TYPE.audio: - if (player._opt.hasAudio) { - const payload = new Uint8Array(data, 5); - player.updateStats({ - abps: payload.byteLength - }); - - if (payload.byteLength > 0) { - this._doDecode(payload, type, ts); - } - } - - break; - - case MEDIA_TYPE.video: - if (player._opt.hasVideo) { - if (!player._times.demuxStart) { - player._times.demuxStart = now(); - } - - if (dv.byteLength > 5) { - const payload = new Uint8Array(data, 5); - const isIframe = dv.getUint8(5) >> 4 === 1; - player.updateStats({ - vbps: payload.byteLength - }); - - if (payload.byteLength > 0) { - this._doDecode(payload, type, ts, isIframe); - } - } else { - this.player.debug.warn('M7sDemux', 'dispatch', 'dv byteLength is', dv.byteLength); - } - } - - break; - } - } - - } - - class Demux { - constructor(player) { - const Loader = Demux.getLoaderFactory(player._opt.demuxType); - return new Loader(player); - } - - static getLoaderFactory(type) { - if (type === DEMUX_TYPE.m7s) { - return M7sLoader; - } else if (type === DEMUX_TYPE.flv) { - return FlvLoader; - } - } - - } - - /* - * Copyright (C) 2016 Bilibili. All Rights Reserved. - * - * @author zheng qian - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - // Exponential-Golomb buffer decoder - class ExpGolomb { - constructor(uint8array) { - this.TAG = 'ExpGolomb'; - this._buffer = uint8array; - this._buffer_index = 0; - this._total_bytes = uint8array.byteLength; - this._total_bits = uint8array.byteLength * 8; - this._current_word = 0; - this._current_word_bits_left = 0; - } - - destroy() { - this._buffer = null; - } - - _fillCurrentWord() { - let buffer_bytes_left = this._total_bytes - this._buffer_index; - - let bytes_read = Math.min(4, buffer_bytes_left); - let word = new Uint8Array(4); - word.set(this._buffer.subarray(this._buffer_index, this._buffer_index + bytes_read)); - this._current_word = new DataView(word.buffer).getUint32(0, false); - this._buffer_index += bytes_read; - this._current_word_bits_left = bytes_read * 8; - } - - readBits(bits) { - - if (bits <= this._current_word_bits_left) { - let result = this._current_word >>> 32 - bits; - this._current_word <<= bits; - this._current_word_bits_left -= bits; - return result; - } - - let result = this._current_word_bits_left ? this._current_word : 0; - result = result >>> 32 - this._current_word_bits_left; - let bits_need_left = bits - this._current_word_bits_left; - - this._fillCurrentWord(); - - let bits_read_next = Math.min(bits_need_left, this._current_word_bits_left); - let result2 = this._current_word >>> 32 - bits_read_next; - this._current_word <<= bits_read_next; - this._current_word_bits_left -= bits_read_next; - result = result << bits_read_next | result2; - return result; - } - - readBool() { - return this.readBits(1) === 1; - } - - readByte() { - return this.readBits(8); - } - - _skipLeadingZero() { - let zero_count; - - for (zero_count = 0; zero_count < this._current_word_bits_left; zero_count++) { - if (0 !== (this._current_word & 0x80000000 >>> zero_count)) { - this._current_word <<= zero_count; - this._current_word_bits_left -= zero_count; - return zero_count; - } - } - - this._fillCurrentWord(); - - return zero_count + this._skipLeadingZero(); - } - - readUEG() { - // unsigned exponential golomb - let leading_zeros = this._skipLeadingZero(); - - return this.readBits(leading_zeros + 1) - 1; - } - - readSEG() { - // signed exponential golomb - let value = this.readUEG(); - - if (value & 0x01) { - return value + 1 >>> 1; - } else { - return -1 * (value >>> 1); - } - } - - } - - /* - * Copyright (C) 2016 Bilibili. All Rights Reserved. - * - * @author zheng qian - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - class SPSParser { - static _ebsp2rbsp(uint8array) { - let src = uint8array; - let src_length = src.byteLength; - let dst = new Uint8Array(src_length); - let dst_idx = 0; - - for (let i = 0; i < src_length; i++) { - if (i >= 2) { - // Unescape: Skip 0x03 after 00 00 - if (src[i] === 0x03 && src[i - 1] === 0x00 && src[i - 2] === 0x00) { - continue; - } - } - - dst[dst_idx] = src[i]; - dst_idx++; - } - - return new Uint8Array(dst.buffer, 0, dst_idx); - } // 解析 SPS - // https://zhuanlan.zhihu.com/p/27896239 - - - static parseSPS(uint8array) { - let rbsp = SPSParser._ebsp2rbsp(uint8array); - - let gb = new ExpGolomb(rbsp); - gb.readByte(); // 标识当前H.264码流的profile。 - // 我们知道,H.264中定义了三种常用的档次profile: 基准档次:baseline profile;主要档次:main profile; 扩展档次:extended profile; - - let profile_idc = gb.readByte(); // profile_idc - - gb.readByte(); // constraint_set_flags[5] + reserved_zero[3] - // 标识当前码流的Level。编码的Level定义了某种条件下的最大视频分辨率、最大视频帧率等参数,码流所遵从的level由level_idc指定。 - - let level_idc = gb.readByte(); // level_idc - // 表示当前的序列参数集的id。通过该id值,图像参数集pps可以引用其代表的sps中的参数。 - - gb.readUEG(); // seq_parameter_set_id - - let profile_string = SPSParser.getProfileString(profile_idc); - let level_string = SPSParser.getLevelString(level_idc); - let chroma_format_idc = 1; - let chroma_format = 420; - let chroma_format_table = [0, 420, 422, 444]; - let bit_depth = 8; // - - if (profile_idc === 100 || profile_idc === 110 || profile_idc === 122 || profile_idc === 244 || profile_idc === 44 || profile_idc === 83 || profile_idc === 86 || profile_idc === 118 || profile_idc === 128 || profile_idc === 138 || profile_idc === 144) { - // - chroma_format_idc = gb.readUEG(); - - if (chroma_format_idc === 3) { - gb.readBits(1); // separate_colour_plane_flag - } - - if (chroma_format_idc <= 3) { - chroma_format = chroma_format_table[chroma_format_idc]; - } - - bit_depth = gb.readUEG() + 8; // bit_depth_luma_minus8 - - gb.readUEG(); // bit_depth_chroma_minus8 - - gb.readBits(1); // qpprime_y_zero_transform_bypass_flag - - if (gb.readBool()) { - // seq_scaling_matrix_present_flag - let scaling_list_count = chroma_format_idc !== 3 ? 8 : 12; - - for (let i = 0; i < scaling_list_count; i++) { - if (gb.readBool()) { - // seq_scaling_list_present_flag - if (i < 6) { - SPSParser._skipScalingList(gb, 16); - } else { - SPSParser._skipScalingList(gb, 64); - } - } - } - } - } // 用于计算MaxFrameNum的值。计算公式为MaxFrameNum = 2^(log2_max_frame_num_minus4 + - - - gb.readUEG(); // log2_max_frame_num_minus4 - // 表示解码picture order count(POC)的方法。POC是另一种计量图像序号的方式,与frame_num有着不同的计算方法。该语法元素的取值为0、1或2。 - - let pic_order_cnt_type = gb.readUEG(); - - if (pic_order_cnt_type === 0) { - gb.readUEG(); // log2_max_pic_order_cnt_lsb_minus_4 - } else if (pic_order_cnt_type === 1) { - gb.readBits(1); // delta_pic_order_always_zero_flag - - gb.readSEG(); // offset_for_non_ref_pic - - gb.readSEG(); // offset_for_top_to_bottom_field - - let num_ref_frames_in_pic_order_cnt_cycle = gb.readUEG(); - - for (let i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++) { - gb.readSEG(); // offset_for_ref_frame - } - } // 用于表示参考帧的最大数目。 - - - let ref_frames = gb.readUEG(); // max_num_ref_frames - // 标识位,说明frame_num中是否允许不连续的值。 - - gb.readBits(1); // gaps_in_frame_num_value_allowed_flag - // 用于计算图像的宽度。单位为宏块个数,因此图像的实际宽度为: - - let pic_width_in_mbs_minus1 = gb.readUEG(); // 使用PicHeightInMapUnits来度量视频中一帧图像的高度。 - // PicHeightInMapUnits并非图像明确的以像素或宏块为单位的高度,而需要考虑该宏块是帧编码或场编码。PicHeightInMapUnits的计算方式为: - - let pic_height_in_map_units_minus1 = gb.readUEG(); // 标识位,说明宏块的编码方式。当该标识位为0时,宏块可能为帧编码或场编码; - // 该标识位为1时,所有宏块都采用帧编码。根据该标识位取值不同,PicHeightInMapUnits的含义也不同, - // 为0时表示一场数据按宏块计算的高度,为1时表示一帧数据按宏块计算的高度。 - - let frame_mbs_only_flag = gb.readBits(1); - - if (frame_mbs_only_flag === 0) { - // 标识位,说明是否采用了宏块级的帧场自适应编码。当该标识位为0时,不存在帧编码和场编码之间的切换;当标识位为1时,宏块可能在帧编码和场编码模式之间进行选择。 - gb.readBits(1); // mb_adaptive_frame_field_flag - } // 标识位,用于B_Skip、B_Direct模式运动矢量的推导计算。 - - - gb.readBits(1); // direct_8x8_inference_flag - - let frame_crop_left_offset = 0; - let frame_crop_right_offset = 0; - let frame_crop_top_offset = 0; - let frame_crop_bottom_offset = 0; - let frame_cropping_flag = gb.readBool(); - - if (frame_cropping_flag) { - frame_crop_left_offset = gb.readUEG(); - frame_crop_right_offset = gb.readUEG(); - frame_crop_top_offset = gb.readUEG(); - frame_crop_bottom_offset = gb.readUEG(); - } - - let sar_width = 1, - sar_height = 1; - let fps = 0, - fps_fixed = true, - fps_num = 0, - fps_den = 0; // 标识位,说明SPS中是否存在VUI信息。 - - let vui_parameters_present_flag = gb.readBool(); - - if (vui_parameters_present_flag) { - if (gb.readBool()) { - // aspect_ratio_info_present_flag - let aspect_ratio_idc = gb.readByte(); - let sar_w_table = [1, 12, 10, 16, 40, 24, 20, 32, 80, 18, 15, 64, 160, 4, 3, 2]; - let sar_h_table = [1, 11, 11, 11, 33, 11, 11, 11, 33, 11, 11, 33, 99, 3, 2, 1]; - - if (aspect_ratio_idc > 0 && aspect_ratio_idc < 16) { - sar_width = sar_w_table[aspect_ratio_idc - 1]; - sar_height = sar_h_table[aspect_ratio_idc - 1]; - } else if (aspect_ratio_idc === 255) { - sar_width = gb.readByte() << 8 | gb.readByte(); - sar_height = gb.readByte() << 8 | gb.readByte(); - } - } - - if (gb.readBool()) { - // overscan_info_present_flag - gb.readBool(); // overscan_appropriate_flag - } - - if (gb.readBool()) { - // video_signal_type_present_flag - gb.readBits(4); // video_format & video_full_range_flag - - if (gb.readBool()) { - // colour_description_present_flag - gb.readBits(24); // colour_primaries & transfer_characteristics & matrix_coefficients - } - } - - if (gb.readBool()) { - // chroma_loc_info_present_flag - gb.readUEG(); // chroma_sample_loc_type_top_field - - gb.readUEG(); // chroma_sample_loc_type_bottom_field - } - - if (gb.readBool()) { - // timing_info_present_flag - let num_units_in_tick = gb.readBits(32); - let time_scale = gb.readBits(32); - fps_fixed = gb.readBool(); // fixed_frame_rate_flag - - fps_num = time_scale; - fps_den = num_units_in_tick * 2; - fps = fps_num / fps_den; - } - } - - let sarScale = 1; - - if (sar_width !== 1 || sar_height !== 1) { - sarScale = sar_width / sar_height; - } - - let crop_unit_x = 0, - crop_unit_y = 0; - - if (chroma_format_idc === 0) { - crop_unit_x = 1; - crop_unit_y = 2 - frame_mbs_only_flag; - } else { - let sub_wc = chroma_format_idc === 3 ? 1 : 2; - let sub_hc = chroma_format_idc === 1 ? 2 : 1; - crop_unit_x = sub_wc; - crop_unit_y = sub_hc * (2 - frame_mbs_only_flag); - } - - let codec_width = (pic_width_in_mbs_minus1 + 1) * 16; - let codec_height = (2 - frame_mbs_only_flag) * ((pic_height_in_map_units_minus1 + 1) * 16); - codec_width -= (frame_crop_left_offset + frame_crop_right_offset) * crop_unit_x; - codec_height -= (frame_crop_top_offset + frame_crop_bottom_offset) * crop_unit_y; - let present_width = Math.ceil(codec_width * sarScale); - gb.destroy(); - gb = null; // 解析出来的SPS 内容。 - - return { - profile_string: profile_string, - // baseline, high, high10, ... - level_string: level_string, - // 3, 3.1, 4, 4.1, 5, 5.1, ... - bit_depth: bit_depth, - // 8bit, 10bit, ... - ref_frames: ref_frames, - chroma_format: chroma_format, - // 4:2:0, 4:2:2, ... - chroma_format_string: SPSParser.getChromaFormatString(chroma_format), - frame_rate: { - fixed: fps_fixed, - fps: fps, - fps_den: fps_den, - fps_num: fps_num - }, - sar_ratio: { - width: sar_width, - height: sar_height - }, - codec_size: { - width: codec_width, - height: codec_height - }, - present_size: { - width: present_width, - height: codec_height - } - }; - } - - static _skipScalingList(gb, count) { - let last_scale = 8, - next_scale = 8; - let delta_scale = 0; - - for (let i = 0; i < count; i++) { - if (next_scale !== 0) { - delta_scale = gb.readSEG(); - next_scale = (last_scale + delta_scale + 256) % 256; - } - - last_scale = next_scale === 0 ? last_scale : next_scale; - } - } // profile_idc = 66 → baseline profile; - // profile_idc = 77 → main profile; - // profile_idc = 88 → extended profile; - // 在新版的标准中,还包括了High、High 10、High 4:2:2、High 4:4:4、High 10 Intra、High - // 4:2:2 Intra、High 4:4:4 Intra、CAVLC 4:4:4 Intra - - - static getProfileString(profile_idc) { - switch (profile_idc) { - case 66: - return 'Baseline'; - - case 77: - return 'Main'; - - case 88: - return 'Extended'; - - case 100: - return 'High'; - - case 110: - return 'High10'; - - case 122: - return 'High422'; - - case 244: - return 'High444'; - - default: - return 'Unknown'; - } - } - - static getLevelString(level_idc) { - return (level_idc / 10).toFixed(1); - } - - static getChromaFormatString(chroma) { - switch (chroma) { - case 420: - return '4:2:0'; - - case 422: - return '4:2:2'; - - case 444: - return '4:4:4'; - - default: - return 'Unknown'; - } - } - - } - - function parseAVCDecoderConfigurationRecord(arrayBuffer) { - const meta = {}; - const v = new DataView(arrayBuffer.buffer); - let version = v.getUint8(0); // configurationVersion - - let avcProfile = v.getUint8(1); // avcProfileIndication - - v.getUint8(2); // profile_compatibil - - v.getUint8(3); // AVCLevelIndication - - if (version !== 1 || avcProfile === 0) { - // this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: Invalid AVCDecoderConfigurationRecord'); - return meta; - } - - const _naluLengthSize = (v.getUint8(4) & 3) + 1; // lengthSizeMinusOne - - - if (_naluLengthSize !== 3 && _naluLengthSize !== 4) { - // holy shit!!! - // this._onError(DemuxErrors.FORMAT_ERROR, `Flv: Strange NaluLengthSizeMinusOne: ${_naluLengthSize - 1}`); - return meta; - } - - let spsCount = v.getUint8(5) & 31; // numOfSequenceParameterSets - - if (spsCount === 0) { - // this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: Invalid AVCDecoderConfigurationRecord: No SPS'); - return; - } - - let offset = 6; - - for (let i = 0; i < spsCount; i++) { - let len = v.getUint16(offset, false); // sequenceParameterSetLength - - offset += 2; - - if (len === 0) { - continue; - } // Notice: Nalu without startcode header (00 00 00 01) - - - let sps = new Uint8Array(arrayBuffer.buffer, offset, len); - offset += len; // flv.js作者选择了自己来解析这个数据结构,也是迫不得已,因为JS环境下没有ffmpeg,解析这个结构主要是为了提取 sps和pps。虽然理论上sps允许有多个,但其实一般就一个。 - // packetTtype 为 1 表示 NALU,NALU= network abstract layer unit,这是H.264的概念,网络抽象层数据单元,其实简单理解就是一帧视频数据。 - // pps的信息没什么用,所以作者只实现了sps的分析器,说明作者下了很大功夫去学习264的标准,其中的Golomb解码还是挺复杂的,能解对不容易,我在PC和手机平台都是用ffmpeg去解析的。 - // SPS里面包括了视频分辨率,帧率,profile level等视频重要信息。 - - let config = SPSParser.parseSPS(sps); - - if (i !== 0) { - // ignore other sps's config - continue; - } - - meta.codecWidth = config.codec_size.width; - meta.codecHeight = config.codec_size.height; - meta.presentWidth = config.present_size.width; - meta.presentHeight = config.present_size.height; - meta.profile = config.profile_string; - meta.level = config.level_string; - meta.bitDepth = config.bit_depth; - meta.chromaFormat = config.chroma_format; - meta.sarRatio = config.sar_ratio; - meta.frameRate = config.frame_rate; - - if (config.frame_rate.fixed === false || config.frame_rate.fps_num === 0 || config.frame_rate.fps_den === 0) { - meta.frameRate = {}; - } - - let fps_den = meta.frameRate.fps_den; - let fps_num = meta.frameRate.fps_num; - meta.refSampleDuration = meta.timescale * (fps_den / fps_num); - let codecArray = sps.subarray(1, 4); - let codecString = 'avc1.'; - - for (let j = 0; j < 3; j++) { - let h = codecArray[j].toString(16); - - if (h.length < 2) { - h = '0' + h; - } - - codecString += h; - } // codec - - - meta.codec = codecString; - } - - let ppsCount = v.getUint8(offset); // numOfPictureParameterSets - - if (ppsCount === 0) { - // this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: Invalid AVCDecoderConfigurationRecord: No PPS'); - return meta; - } - - offset++; - - for (let i = 0; i < ppsCount; i++) { - let len = v.getUint16(offset, false); // pictureParameterSetLength - - offset += 2; - - if (len === 0) { - continue; - } - - new Uint8Array(arrayBuffer.buffer, offset, len); // pps is useless for extracting video information - - offset += len; - } - - meta.videoType = 'avc'; // meta.avcc = arrayBuffer; - - return meta; - } - - class WebcodecsDecoder extends Emitter { - constructor(player) { - super(); - this.player = player; - this.hasInit = false; - this.isDecodeFirstIIframe = false; - this.isInitInfo = false; - this.decoder = null; - this.initDecoder(); - player.debug.log('Webcodecs', 'init'); - } - - destroy() { - if (this.decoder) { - if (this.decoder.state !== 'closed') { - this.decoder.close(); - } - - this.decoder = null; - } - - this.hasInit = false; - this.isInitInfo = false; - this.isDecodeFirstIIframe = false; - this.off(); - this.player.debug.log('Webcodecs', 'destroy'); - } - - initDecoder() { - const _this = this; - - this.decoder = new VideoDecoder({ - output(videoFrame) { - _this.handleDecode(videoFrame); - }, - - error(error) { - _this.handleError(error); - } - - }); - } - - handleDecode(videoFrame) { - if (!this.isInitInfo) { - this.player.video.updateVideoInfo({ - width: videoFrame.codedWidth, - height: videoFrame.codedHeight - }); - this.player.video.initCanvasViewSize(); - this.isInitInfo = true; - } - - if (!this.player._times.videoStart) { - this.player._times.videoStart = now(); - this.player.handlePlayToRenderTimes(); - } - - this.player.handleRender(); - this.player.video.render({ - videoFrame - }); - this.player.updateStats({ - fps: true, - ts: 0, - buf: this.player.demux.delay - }); - } - - handleError(error) { - this.player.debug.error('Webcodecs', 'VideoDecoder handleError', error); - } - - decodeVideo(payload, ts, isIframe) { - // this.player.debug.log('Webcodecs decoder', 'decodeVideo', ts, isIframe); - if (!this.hasInit) { - if (isIframe && payload[1] === 0) { - const videoCodec = payload[0] & 0x0F; - this.player.video.updateVideoInfo({ - encTypeCode: videoCodec - }); // 如果解码出来的是 - - if (videoCodec === VIDEO_ENC_CODE.h265) { - this.emit(EVENTS_ERROR.webcodecsH265NotSupport); - return; - } - - if (!this.player._times.decodeStart) { - this.player._times.decodeStart = now(); - } - - const config = formatVideoDecoderConfigure(payload.slice(5)); - this.decoder.configure(config); - this.hasInit = true; - } - } else { - // check width or height change - if (isIframe && payload[1] === 0) { - let data = payload.slice(5); - const config = parseAVCDecoderConfigurationRecord(data); - const videoInfo = this.player.video.videoInfo; - - if (config.codecWidth !== videoInfo.width || config.codecHeight !== videoInfo.height) { - this.player.debug.log('Webcodecs', `width or height is update, width ${videoInfo.width}-> ${config.codecWidth}, height ${videoInfo.height}-> ${config.codecHeight}`); - this.player.emit(EVENTS_ERROR.webcodecsWidthOrHeightChange); - return; - } - } // fix : Uncaught DOMException: Failed to execute 'decode' on 'VideoDecoder': A key frame is required after configure() or flush(). - - - if (!this.isDecodeFirstIIframe && isIframe) { - this.isDecodeFirstIIframe = true; - } - - if (this.isDecodeFirstIIframe) { - const chunk = new EncodedVideoChunk({ - data: payload.slice(5), - timestamp: ts, - type: isIframe ? ENCODED_VIDEO_TYPE.key : ENCODED_VIDEO_TYPE.delta - }); - this.player.emit(EVENTS.timeUpdate, ts); - - try { - if (this.isDecodeStateClosed()) { - this.player.debug.warn('Webcodecs', 'VideoDecoder isDecodeStateClosed true'); - return; - } - - this.decoder.decode(chunk); - } catch (e) { - this.player.debug.error('Webcodecs', 'VideoDecoder', e); - - if (e.toString().indexOf(WCS_ERROR.keyframeIsRequiredError) !== -1) { - this.player.emitError(EVENTS_ERROR.webcodecsDecodeError); - } else if (e.toString().indexOf(WCS_ERROR.canNotDecodeClosedCodec) !== -1) { - this.player.emitError(EVENTS_ERROR.webcodecsDecodeError); - } - } - } else { - this.player.debug.warn('Webcodecs', 'VideoDecoder isDecodeFirstIIframe false'); - } - } - } - - isDecodeStateClosed() { - return this.decoder.state === 'closed'; - } - - } - - const iconsMap = { - play: '播放', - pause: '暂停', - audio: '', - mute: '', - screenshot: '截图', - loading: '加载', - fullscreen: '全屏', - fullscreenExit: '退出全屏', - record: '录制', - recordStop: '停止录制' - }; - var icons = Object.keys(iconsMap).reduce((icons, key) => { - icons[key] = ` - - ${iconsMap[key] ? `${iconsMap[key]}` : ''} -`; - return icons; - }, {}); - - var template = ((player, control) => { - if (player._opt.hasControl && player._opt.controlAutoHide) { - player.$container.classList.add('jessibuca-controls-show-auto-hide'); - } else { - player.$container.classList.add('jessibuca-controls-show'); - } - - const options = player._opt; - const operateBtns = options.operateBtns; - player.$container.insertAdjacentHTML('beforeend', ` - ${options.background ? `
` : ''} -
- ${icons.loading} - ${options.loadingText ? `
${options.loadingText}
` : ''} -
- ${options.hasControl && operateBtns.play ? `
` : ''} - ${options.hasControl ? ` -
-
-
00:00:01
-
${icons.recordStop}
-
- ` : ''} - ${options.hasControl ? ` -
-
-
- ${options.showBandwidth ? `
` : ''} -
-
- ${operateBtns.audio ? ` -
- ${icons.audio} - ${icons.mute} -
-
-
-
-
-
-
- ` : ''} - ${operateBtns.play ? `
${icons.play}
${icons.pause}
` : ''} - ${operateBtns.screenshot ? `
${icons.screenshot}
` : ''} - ${operateBtns.record ? `
${icons.record}
${icons.recordStop}
` : ''} - ${operateBtns.fullscreen ? `
${icons.fullscreen}
${icons.fullscreenExit}
` : ''} -
-
-
- ` : ''} - - `); - Object.defineProperty(control, '$poster', { - value: player.$container.querySelector('.jessibuca-poster') - }); - Object.defineProperty(control, '$loading', { - value: player.$container.querySelector('.jessibuca-loading') - }); - Object.defineProperty(control, '$play', { - value: player.$container.querySelector('.jessibuca-play') - }); - Object.defineProperty(control, '$playBig', { - value: player.$container.querySelector('.jessibuca-play-big') - }); - Object.defineProperty(control, '$recording', { - value: player.$container.querySelector('.jessibuca-recording') - }); - Object.defineProperty(control, '$recordingTime', { - value: player.$container.querySelector('.jessibuca-recording-time') - }); - Object.defineProperty(control, '$recordingStop', { - value: player.$container.querySelector('.jessibuca-recording-stop') - }); - Object.defineProperty(control, '$pause', { - value: player.$container.querySelector('.jessibuca-pause') - }); - Object.defineProperty(control, '$controls', { - value: player.$container.querySelector('.jessibuca-controls') - }); - Object.defineProperty(control, '$fullscreen', { - value: player.$container.querySelector('.jessibuca-fullscreen') - }); - Object.defineProperty(control, '$fullscreen', { - value: player.$container.querySelector('.jessibuca-fullscreen') - }); - Object.defineProperty(control, '$volume', { - value: player.$container.querySelector('.jessibuca-volume') - }); - Object.defineProperty(control, '$volumePanelWrap', { - value: player.$container.querySelector('.jessibuca-volume-panel-wrap') - }); - Object.defineProperty(control, '$volumePanelText', { - value: player.$container.querySelector('.jessibuca-volume-panel-text') - }); - Object.defineProperty(control, '$volumePanel', { - value: player.$container.querySelector('.jessibuca-volume-panel') - }); - Object.defineProperty(control, '$volumeHandle', { - value: player.$container.querySelector('.jessibuca-volume-panel-handle') - }); - Object.defineProperty(control, '$volumeOn', { - value: player.$container.querySelector('.jessibuca-icon-audio') - }); - Object.defineProperty(control, '$volumeOff', { - value: player.$container.querySelector('.jessibuca-icon-mute') - }); - Object.defineProperty(control, '$fullscreen', { - value: player.$container.querySelector('.jessibuca-fullscreen') - }); - Object.defineProperty(control, '$fullscreenExit', { - value: player.$container.querySelector('.jessibuca-fullscreen-exit') - }); - Object.defineProperty(control, '$record', { - value: player.$container.querySelector('.jessibuca-record') - }); - Object.defineProperty(control, '$recordStop', { - value: player.$container.querySelector('.jessibuca-record-stop') - }); - Object.defineProperty(control, '$screenshot', { - value: player.$container.querySelector('.jessibuca-screenshot') - }); - Object.defineProperty(control, '$speed', { - value: player.$container.querySelector('.jessibuca-speed') - }); - }); - - var observer$1 = ((player, control) => { - const { - events: { - proxy - } - } = player; - const object = document.createElement('object'); - object.setAttribute('aria-hidden', 'true'); - object.setAttribute('tabindex', -1); - object.type = 'text/html'; - object.data = 'about:blank'; - setStyle(object, { - display: 'block', - position: 'absolute', - top: '0', - left: '0', - height: '100%', - width: '100%', - overflow: 'hidden', - pointerEvents: 'none', - zIndex: '-1' - }); - let playerWidth = player.width; - let playerHeight = player.height; - proxy(object, 'load', () => { - proxy(object.contentDocument.defaultView, 'resize', () => { - if (player.width !== playerWidth || player.height !== playerHeight) { - playerWidth = player.width; - playerHeight = player.height; - player.emit(EVENTS.resize); - screenfullH5Control(); - } - }); - }); - player.$container.appendChild(object); - player.on(EVENTS.destroy, () => { - player.$container.removeChild(object); - }); - - function setVolumeHandle(percentage) { - if (percentage === 0) { - setStyle(control.$volumeOn, 'display', 'none'); - setStyle(control.$volumeOff, 'display', 'flex'); - setStyle(control.$volumeHandle, 'top', `${48}px`); - } else { - if (control.$volumeHandle && control.$volumePanel) { - const panelHeight = getStyle(control.$volumePanel, 'height') || 60; - const handleHeight = getStyle(control.$volumeHandle, 'height'); - const top = panelHeight - (panelHeight - handleHeight) * percentage - handleHeight; - setStyle(control.$volumeHandle, 'top', `${top}px`); - setStyle(control.$volumeOn, 'display', 'flex'); - setStyle(control.$volumeOff, 'display', 'none'); - } - } - - control.$volumePanelText && (control.$volumePanelText.innerHTML = parseInt(percentage * 100)); - } - - player.on(EVENTS.volumechange, () => { - setVolumeHandle(player.volume); - }); - player.on(EVENTS.loading, flag => { - setStyle(control.$loading, 'display', flag ? 'flex' : 'none'); - setStyle(control.$poster, 'display', 'none'); - - if (flag) { - setStyle(control.$playBig, 'display', 'none'); - } - }); - - const screenfullChange = fullscreen => { - let isFullScreen = isBoolean(fullscreen) ? fullscreen : player.fullscreen; - setStyle(control.$fullscreenExit, 'display', isFullScreen ? 'flex' : 'none'); - setStyle(control.$fullscreen, 'display', isFullScreen ? 'none' : 'flex'); // control.autoSize(); - }; - - const screenfullH5Control = () => { - if (isMobile() && control.$controls && player._opt.useWebFullScreen) { - setTimeout(() => { - if (player.fullscreen) { - // console.log(player.width, player.height); - let translateX = player.height / 2 - player.width + CONTROL_HEIGHT / 2; - let translateY = player.height / 2 - CONTROL_HEIGHT / 2; - control.$controls.style.transform = `translateX(${-translateX}px) translateY(-${translateY}px) rotate(-90deg)`; - } else { - control.$controls.style.transform = `translateX(0) translateY(0) rotate(0)`; - } - }, 10); - } - }; - - try { - screenfull.on('change', screenfullChange); - player.events.destroys.push(() => { - screenfull.off('change', screenfullChange); - }); - } catch (error) {// - } // - - - player.on(EVENTS.webFullscreen, value => { - screenfullChange(value); - screenfullH5Control(); - }); - player.on(EVENTS.recording, () => { - setStyle(control.$record, 'display', player.recording ? 'none' : 'flex'); - setStyle(control.$recordStop, 'display', player.recording ? 'flex' : 'none'); - setStyle(control.$recording, 'display', player.recording ? 'flex' : 'none'); - }); // - - player.on(EVENTS.recordingTimestamp, timestamp => { - // console.log(timestamp); - control.$recordingTime && (control.$recordingTime.innerHTML = formatTimeTips(timestamp)); - }); - player.on(EVENTS.playing, flag => { - setStyle(control.$play, 'display', flag ? 'none' : 'flex'); - setStyle(control.$playBig, 'display', flag ? 'none' : 'block'); - setStyle(control.$pause, 'display', flag ? 'flex' : 'none'); - setStyle(control.$screenshot, 'display', flag ? 'flex' : 'none'); - setStyle(control.$record, 'display', flag ? 'flex' : 'none'); - setStyle(control.$qualityMenu, 'display', flag ? 'flex' : 'none'); - setStyle(control.$volume, 'display', flag ? 'flex' : 'none'); // setStyle(control.$fullscreen, 'display', flag ? 'flex' : 'none'); - - screenfullChange(); // 不在播放 - - if (!flag) { - control.$speed && (control.$speed.innerHTML = bpsSize('')); - } - }); - player.on(EVENTS.kBps, rate => { - const bps = bpsSize(rate); - control.$speed && (control.$speed.innerHTML = bps); - }); - }); - - var property = ((player, control) => { - Object.defineProperty(control, 'controlsRect', { - get: () => { - return control.$controls.getBoundingClientRect(); - } - }); - }); - - var events = ((player, control) => { - const { - events: { - proxy - }, - debug - } = player; - - function volumeChangeFromEvent(event) { - const { - bottom: panelBottom, - height: panelHeight - } = control.$volumePanel.getBoundingClientRect(); - const { - height: handleHeight - } = control.$volumeHandle.getBoundingClientRect(); - let moveLen = event.y; // if (isMobile() && player.fullscreen) { - // moveLen = event.x; - // } - - const percentage = clamp(panelBottom - moveLen - handleHeight / 2, 0, panelHeight - handleHeight / 2) / (panelHeight - handleHeight); - return percentage; - } // - - - proxy(window, ['click', 'contextmenu'], event => { - if (event.composedPath().indexOf(player.$container) > -1) { - control.isFocus = true; - } else { - control.isFocus = false; - } - }); // - - proxy(window, 'orientationchange', () => { - setTimeout(() => { - player.resize(); - }, 300); - }); - proxy(control.$controls, 'click', e => { - e.stopPropagation(); - }); - proxy(control.$pause, 'click', e => { - player.pause(); - }); // 监听 play 方法 - - proxy(control.$play, 'click', e => { - player.play(); - player.resumeAudioAfterPause(); - }); // 监听 play 方法 - - proxy(control.$playBig, 'click', e => { - player.play(); - player.resumeAudioAfterPause(); - }); - proxy(control.$volume, 'mouseover', () => { - control.$volumePanelWrap.classList.add('jessibuca-volume-panel-wrap-show'); - }); - proxy(control.$volume, 'mouseout', () => { - control.$volumePanelWrap.classList.remove('jessibuca-volume-panel-wrap-show'); - }); - proxy(control.$volumeOn, 'click', e => { - e.stopPropagation(); - setStyle(control.$volumeOn, 'display', 'none'); - setStyle(control.$volumeOff, 'display', 'block'); - const lastVolume = player.volume; - player.volume = 0; - player._lastVolume = lastVolume; - }); - proxy(control.$volumeOff, 'click', e => { - e.stopPropagation(); - setStyle(control.$volumeOn, 'display', 'block'); - setStyle(control.$volumeOff, 'display', 'none'); - player.volume = player.lastVolume || 0.5; - }); - proxy(control.$screenshot, 'click', e => { - e.stopPropagation(); - player.video.screenshot(); - }); - proxy(control.$volumePanel, 'click', event => { - event.stopPropagation(); - player.volume = volumeChangeFromEvent(event); - }); - proxy(control.$volumeHandle, 'mousedown', () => { - control.isVolumeDroging = true; - }); - proxy(control.$volumeHandle, 'mousemove', event => { - if (control.isVolumeDroging) { - player.volume = volumeChangeFromEvent(event); - } - }); - proxy(document, 'mouseup', () => { - if (control.isVolumeDroging) { - control.isVolumeDroging = false; - } - }); - proxy(control.$record, 'click', e => { - e.stopPropagation(); - player.recording = true; - }); - proxy(control.$recordStop, 'click', e => { - e.stopPropagation(); - player.recording = false; - }); - proxy(control.$recordingStop, 'click', e => { - e.stopPropagation(); - player.recording = false; - }); - proxy(control.$fullscreen, 'click', e => { - e.stopPropagation(); - player.fullscreen = true; - }); - proxy(control.$fullscreenExit, 'click', e => { - e.stopPropagation(); - player.fullscreen = false; - }); - - if (player._opt.hasControl && player._opt.controlAutoHide) { - // - proxy(player.$container, 'mouseover', () => { - if (!player.fullscreen) { - setStyle(control.$controls, 'display', 'block'); - startDelayControlHidden(); - } - }); - proxy(player.$container, 'mousemove', () => { - if (player.$container && control.$controls) { - if (!player.fullscreen) { - if (control.$controls.style.display === 'none') { - setStyle(control.$controls, 'display', 'block'); - startDelayControlHidden(); - } - } else { - if (control.$controls.style.display === 'none') { - setStyle(control.$controls, 'display', 'block'); - startDelayControlHidden(); - } - } - } - }); - proxy(player.$container, 'mouseout', () => { - stopDelayControlHidden(); - setStyle(control.$controls, 'display', 'none'); - }); - let delayHiddenTimeout = null; - - const startDelayControlHidden = () => { - stopDelayControlHidden(); - delayHiddenTimeout = setTimeout(() => { - setStyle(control.$controls, 'display', 'none'); - }, 5 * 1000); - }; - - const stopDelayControlHidden = () => { - if (delayHiddenTimeout) { - clearTimeout(delayHiddenTimeout); - delayHiddenTimeout = null; - } - }; - } - }); - - function styleInject(css, ref) { - if (ref === void 0) ref = {}; - var insertAt = ref.insertAt; - - if (!css || typeof document === 'undefined') { return; } - - var head = document.head || document.getElementsByTagName('head')[0]; - var style = document.createElement('style'); - style.type = 'text/css'; - - if (insertAt === 'top') { - if (head.firstChild) { - head.insertBefore(style, head.firstChild); - } else { - head.appendChild(style); - } - } else { - head.appendChild(style); - } - - if (style.styleSheet) { - style.styleSheet.cssText = css; - } else { - style.appendChild(document.createTextNode(css)); - } - } - - var css_248z$1 = "@keyframes rotation{0%{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(1turn)}}@keyframes magentaPulse{0%{background-color:#630030;-webkit-box-shadow:0 0 9px #333}50%{background-color:#a9014b;-webkit-box-shadow:0 0 18px #a9014b}to{background-color:#630030;-webkit-box-shadow:0 0 9px #333}}.jessibuca-container .jessibuca-icon{cursor:pointer;width:16px;height:16px}.jessibuca-container .jessibuca-poster{position:absolute;z-index:10;left:0;top:0;right:0;bottom:0;height:100%;width:100%;background-position:50%;background-repeat:no-repeat;background-size:contain;pointer-events:none}.jessibuca-container .jessibuca-play-big{position:absolute;display:none;height:100%;width:100%;background:rgba(0,0,0,.4)}.jessibuca-container .jessibuca-play-big:after{cursor:pointer;content:\"\";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);display:block;width:48px;height:48px;background-image:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACgklEQVRoQ+3ZPYsTQRjA8eeZZCFlWttAwCIkZOaZJt8hlvkeHrlccuAFT6wEG0FQOeQQLCIWih6chQgKgkkKIyqKCVYip54IWmiQkTmyYhFvd3Zn3yDb7szu/7cv7GaDkPEFM94PK0DSZ9DzDAyHw7uI2HRDlVJX5/N5r9FoHCYdr/fvCRiNRmpJ6AEidoUQ15NG+AH8BgD2n9AHANAmohdJQfwAfgGA4xF4bjabnW21Whob62ILoKNfAsAGEd2PU2ATcNSNiDf0/cE5/xAHxDpgEf0NADaJ6HLUiKgAbvcjpdSGlPJZVJCoAUfdSqkLxWLxTLlc/mkbEgtgET1TSnWklLdtIuIEuN23crlcp16vv7cBSQKgu38AwBYRXQyLSArg3hsjRDxNRE+CQhIF/BN9qVAobFYqle+mkLQAdLd+8K0T0U0TRJoAbvc9fVkJId75gaQRoLv1C2STiPTb7rFLWgE6+g0RncwyYEJEtawCvjDGmpzzp5kD6NfxfD7frtVqB17xen2a7oG3ALBm+oMoFQBEPD+dTvtBfpImDXjIGFvjnD/3c7ksG5MU4HDxWeZa0HB3XhKAXcdxOn5vUi9gnIDXSqm2lHLPK8pkfVyAbSLqm4T5HRs1YB8RO0KIid8g03FRAT4rpbpSyh3TINPxUQB2GGM9zvkn05gg420CJovLZT9ISNA5tgB9ItoOGhFmnh/AcZ/X9xhj65zzV2Eiwsz1A1j2B8dHAOgS0W6YnduY6wkYj8d3lFKn/j66Ea84jtOrVqtfbQSE3YYnYDAY5Eql0hYAnNDv6kKIx2F3anO+J8DmzqLY1goQxVE12ebqDJgcrSjGrs5AFEfVZJt/AF0m+jHzUTtnAAAAAElFTkSuQmCC\");background-repeat:no-repeat;background-position:50%}.jessibuca-container .jessibuca-play-big:hover:after{background-image:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACEElEQVRoQ+2ZXStEQRjH/3/yIXwDdz7J+i7kvdisXCk3SiFJW27kglBcSFFKbqwQSa4krykuKB09Naf2Yndn5jgzc06d53Znd36/mWfeniVyHsw5PwqB0DOonYEoijYBlOpAFwCMkHwLDS/9mwhEDUCfAAyTXA4tYSLwC6CtCegegH6S56FETAR+AHRoACcBTJAUWa+RloBAXwAYIrnt0yBNgZi7qtbHgw8RFwLC/QFglOScawlXAjH3gUqrE1cirgVi7mkAYyS/0xbxJSDcdwAGSa6nKeFTIOZeUyL3aYiEEBDuLwDjJGf+KxFKIOY+BdBL8iipSGiBmHtWbbuftiJZERBuOfgGSK7aSGRJIObeUml1ayKSRQHhlgtkiaTcdltGVgUE+ppkV54FaiS78yrwqlLoOI8Cch2XV548W7WRpTVwA6DP9kGUFYEpAOUkT9LQAvtq1M+0udKkQSgBqSlJWWYxKXj8vRACK+o6bbRIdYI+Ba7U7rKjg7L53JdAhWTZBsy0rWuBXZUuNVMg23auBF7UIl2yBbJt70JAoKV6/WwLk6R9mgKSJlJ1kLTxFmkJyCla8UZd15GJQKvyumyJ8gy8DAEvfZoINPqD41EtUjmUgoaJwAaAnjrKebVI34OSq85NBNqlCAWgE0CV5GEWwI3vQlmCbcSinYFCwPEIFDPgeIC1P1/MgHaIHDf4Aydx2TF7wnKeAAAAAElFTkSuQmCC\")}.jessibuca-container .jessibuca-recording{display:none;position:absolute;left:50%;top:0;padding:0 3px;transform:translateX(-50%);justify-content:space-around;align-items:center;width:95px;height:20px;background:#000;opacity:1;border-radius:0 0 8px 8px;z-index:1}.jessibuca-container .jessibuca-recording .jessibuca-recording-red-point{width:8px;height:8px;background:#ff1f1f;border-radius:50%;animation:magentaPulse 1s linear infinite}.jessibuca-container .jessibuca-recording .jessibuca-recording-time{font-size:14px;font-weight:500;color:#ddd}.jessibuca-container .jessibuca-recording .jessibuca-icon-recordStop{width:16px;height:16px;cursor:pointer}.jessibuca-container .jessibuca-loading{display:none;flex-direction:column;justify-content:center;align-items:center;position:absolute;z-index:20;left:0;top:0;right:0;bottom:0;width:100%;height:100%;pointer-events:none}.jessibuca-container .jessibuca-loading-text{line-height:20px;font-size:13px;color:#fff;margin-top:10px}.jessibuca-container .jessibuca-controls{background-color:#161616;box-sizing:border-box;display:flex;flex-direction:column;justify-content:flex-end;position:absolute;z-index:40;left:0;right:0;bottom:0;height:38px;width:100%;padding-left:13px;padding-right:13px;font-size:14px;color:#fff;opacity:0;visibility:hidden;transition:all .2s ease-in-out;-webkit-user-select:none;user-select:none;transition:width .5s ease-in}.jessibuca-container .jessibuca-controls .jessibuca-controls-item{position:relative;display:flex;justify-content:center;padding:0 8px}.jessibuca-container .jessibuca-controls .jessibuca-controls-item:hover .icon-title-tips{visibility:visible;opacity:1}.jessibuca-container .jessibuca-controls .jessibuca-fullscreen,.jessibuca-container .jessibuca-controls .jessibuca-fullscreen-exit,.jessibuca-container .jessibuca-controls .jessibuca-icon-audio,.jessibuca-container .jessibuca-controls .jessibuca-microphone-close,.jessibuca-container .jessibuca-controls .jessibuca-pause,.jessibuca-container .jessibuca-controls .jessibuca-play,.jessibuca-container .jessibuca-controls .jessibuca-record,.jessibuca-container .jessibuca-controls .jessibuca-record-stop,.jessibuca-container .jessibuca-controls .jessibuca-screenshot{display:none}.jessibuca-container .jessibuca-controls .jessibuca-icon-audio,.jessibuca-container .jessibuca-controls .jessibuca-icon-mute{z-index:1}.jessibuca-container .jessibuca-controls .jessibuca-controls-bottom{display:flex;justify-content:space-between;height:100%}.jessibuca-container .jessibuca-controls .jessibuca-controls-bottom .jessibuca-controls-left,.jessibuca-container .jessibuca-controls .jessibuca-controls-bottom .jessibuca-controls-right{display:flex;align-items:center}.jessibuca-container.jessibuca-controls-show .jessibuca-controls{opacity:1;visibility:visible}.jessibuca-container.jessibuca-controls-show-auto-hide .jessibuca-controls{opacity:.8;visibility:visible;display:none}.jessibuca-container.jessibuca-hide-cursor *{cursor:none!important}.jessibuca-container .jessibuca-icon-loading{width:50px;height:50px;background:url(\"data:image/png;base64,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\") no-repeat 50%;background-size:100% 100%;animation:rotation 1s linear infinite}.jessibuca-container .jessibuca-icon-screenshot{background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAE5UlEQVRoQ+1YW2sdVRT+1s7JxbsoVkEUrIIX0ouz15zYNA+N1RdtQfCltlUfvLbqL/BCwZ8grbHtizQqPojgBSr0JkiMmT2nxgapqBURtPVCq7HxJCeZJVPmxDlzZubMmXOSEsnAvOy917fXt9e39tp7E5b4R0vcfywTuNgRbBgBx3HuJqLVzPzmYjprjHkcwAlmLqXNm4XAISLaSESPaq2HF4OE67rbRGRYRA7btn1fbgLGmKsA/Azg0gBkGzO/vZAkHMd5hIiqc5wHcCMz/5k0Z2oExsfHV1QqldPAf8lORNu11m8tBAljzFYAYWxRSl1vWdZvuQj4RsYYF4AVBlgIOVVlE55HRIxt23ZuCfmGjuOsJ6LPoiAistW27XfaEYmIbOYhPc9bXywWR1oiEJDYQkR1zrYjEjGyqfqbKd8a7kJVtLgQ+30i8pht2wfyRKIdmJkJBPkQTbILfudJ7CTZNBvVpggEcgpvc/ML38zESbLJsxBNE/A9biX0rdjGyTQXgbxyapdsarb0PMlXtWnGoXbKpm0Essqp3bJpK4E0OXmed3+hUBDP8w5FI91M0rdcyLLILElOCbaZilSWeXMncRx4klTCY1spfG3dhZJWx3GcDUR0EEB3ZMw0ET2gtT6SZWWzjmlrBIJCl0hAKfWgZVmHszqXZVxbCSxpCS2JJA6umIhe8ZKKVLPbaBJ+S9toqVRa53nedgAbAKwIwH4FcAzAa0R0l4i8F7PPz189k6RFRA+LyNcAXojDV0oNW5b1eW4Cxpg9AHZkSaaa6hhzb065uDSCH2LmRB8Sk9gY4293g43Qo/1pV80m8yQMfZSZ781cB1zXHRKRZ2IMpgD8A+DamL4ZItqitX4/jbQx5iEA7wLoihn3V/ACckWMJN/QWj9b1x5tGBsbW6uUOh5pPy0iL3Z2dn6ilJqanp5ep5TaJSLhF4NppdRNaU8gPmapVLrO87yfIoXuWyJ6uVKp+HmFjo6OQSJ6FcBtYT+UUmstyxqvkWuUgDFmP4AnQu2/e563qlgs+u9DNZ8xZhRAX7VRRPbath0XuXk7Y8xeAE+FgL6fnJzsHRwcLIfBR0ZGLunq6poAsDLUvp+Zw7b1r9PGmJMAbg8Z7WDmoThZuK67WkS+DD18fcPMdzSQUBR/EzN/nIC/SUQ+DPXV4dclsTHmHAD/SfHCNzc3t7Kvr++HJKeMMacA3BL0nyuXyzcPDAxMxo0fHR29slAo/Ajg6qD/fE9Pzw29vb1/x42fmJi4vFwu+5G/LOg/y8zXNJLQ2dAES5JANMQ7mfn1jBI6ycx3NiMhItqstf4oAX+ziHwQ6qvDj5NQNIn/ALCKmX+JSeIvABRD7fuY+ekGBPYBeDI05tTMzExvf3+/vz2Hk91/ET8RSeI6/DoCpVJpjed5fmKGvzMAXpqdnT3oed5Ud3d3v4jsAqBr9Ei0Rmv9VRqBBPzvROQVETnq2xJRdRu9tRF+bCVOKWT+Kvl/TSIFk6SW/LAjKfjV5K8rZABi8dOOEv7FI7Z8x6zwEWbemLbyMfJr5qiSiJ96oclymBOR3bZtP9+M89WxxpjdAHY2sN3DzM8ljWl4I3Nd9x7/OE1ENcdpETnmH3e11n41zv0l4J8RkU+J6AAz+xtF4teQQG7PFslwmcAiLfSyhC72Qv9/I/Avns2OT7QJskoAAAAASUVORK5CYII=\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-screenshot:hover{background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAED0lEQVRoQ+2ZycsdRRTFf2ejqHFAMQqiYBTUoElUHLNx3GgCgpuYRF2o0UT9CxwQ/BMkMSbZSKLiQgQHUDCJgjiAxiEiESdEcJbEedgcKaj3UV+/6q7u/jovPPkK3qbr1ql76p5bt6qemPKmKfefeQKHOoLFCNg+H1gi6fFJOmv7VmCvpD1N87Yh8ApwNXCzpB2TIGF7DRDm2inpmt4EbB8LfAMcGUHWSHryYJKwfRMwmuMP4BRJv9TN2RgB2wuB72BWsq+V9MTBIGF7NZBiGzhJ0o+9CIRBtt8FLqgADC6nRDbpVO9Iuqi3hCKB5cDrGZDVkp4aIhIV2aSQyyW9MScCkcQqIOfsnCORkc3I31b5VtyFRmg1IQ7dt0ja3icSQ2C2JhAjUU2ykd+dE7tBNp2i2olAJJFuc+nCt564QTadF6IzgUhiVGiqyinKaQjZpJP2ItBXTkPJZhACXeU0pGwGI9BWTkPLZlACBTldG4o5EA6E1dY66edcyNrs8Q36zg1vVaTazNs7iXPgDVJJzYs7VRvHRzaDEohyugJ4CTi84sg/wHWSdnVxsGQ7aQLXS9pZcqpL/6AEplpCU5HE8YpJ9YrXUKQ6baN1+HPaRm1fBqwFQnKGK2ZoPwCvAo8Ai4FnMpPMHMwapHUj8DFwbw3+Dklv9iZgexOwvktSRduxU2VDlErwmyXV+lCbxLbDdndlCT3TX3vV7JgnKfRuSVflfMkSsL0ZuDMz4E/gL+CETN+/wCpJzzaRtn0D8DRwWMbu1/gCcnSm7zFJd1W/jxGwvQx4r2IYnlbuA14GAomQFw8B6YtBKFSnNj2BxEJ3IvB1pdB9CjwQ8yqYhcg/DJxZ8WOZpA/SbzkC24DbEqOfgPMkBRKzmu23gEuSj1sk5SI3Y2J7C3BHMuZz4FxJf6fgto8APgIWJd+3SUrHjr9O294HnJUMWi8pSGqs2V4CvJ88fH0i6eyChKr4KyS9WIO/Ang+6RvDz0XgABCeFEdtkaQv65yy/QVweuwPY0+T9FuNQ8cAXwHHxf7wdHiypN9r7BfEl8GjYv9+SceXJLQ/mSDYTh2Baog3SHq0pYT2STqno4RWSnqhBn8l8FzSN4bfJol/jkn8bXUS228DFyfft0paVyCwFbg9sQkSDEkctueZZju8iO+tJPEYfo7A0piYKd73wP3xnB+20cvjNnphxdmlkj4sEMjhfwY8COyOY0fb6Bkl/K6FLKxS+M1KpDhJY8mvrG5doRwlf66QZfGbjhLh4pEt35kV3iUp/IvTunU8qtTil/7gaHOY2yjpntaez9b5RmBDYewmSXfX2RRvZLYvbThOh+NuqMa9Ww1+yLnXgO2SwkZR24oEens2oYHzBCa00PMSOtQL/f+NwH+Hg8hAnbrYgQAAAABJRU5ErkJggg==\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-play{background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACgklEQVRoQ+3ZPYsTQRjA8eeZZCFlWttAwCIkZOaZJt8hlvkeHrlccuAFT6wEG0FQOeQQLCIWih6chQgKgkkKIyqKCVYip54IWmiQkTmyYhFvd3Zn3yDb7szu/7cv7GaDkPEFM94PK0DSZ9DzDAyHw7uI2HRDlVJX5/N5r9FoHCYdr/fvCRiNRmpJ6AEidoUQ15NG+AH8BgD2n9AHANAmohdJQfwAfgGA4xF4bjabnW21Whob62ILoKNfAsAGEd2PU2ATcNSNiDf0/cE5/xAHxDpgEf0NADaJ6HLUiKgAbvcjpdSGlPJZVJCoAUfdSqkLxWLxTLlc/mkbEgtgET1TSnWklLdtIuIEuN23crlcp16vv7cBSQKgu38AwBYRXQyLSArg3hsjRDxNRE+CQhIF/BN9qVAobFYqle+mkLQAdLd+8K0T0U0TRJoAbvc9fVkJId75gaQRoLv1C2STiPTb7rFLWgE6+g0RncwyYEJEtawCvjDGmpzzp5kD6NfxfD7frtVqB17xen2a7oG3ALBm+oMoFQBEPD+dTvtBfpImDXjIGFvjnD/3c7ksG5MU4HDxWeZa0HB3XhKAXcdxOn5vUi9gnIDXSqm2lHLPK8pkfVyAbSLqm4T5HRs1YB8RO0KIid8g03FRAT4rpbpSyh3TINPxUQB2GGM9zvkn05gg420CJovLZT9ISNA5tgB9ItoOGhFmnh/AcZ/X9xhj65zzV2Eiwsz1A1j2B8dHAOgS0W6YnduY6wkYj8d3lFKn/j66Ea84jtOrVqtfbQSE3YYnYDAY5Eql0hYAnNDv6kKIx2F3anO+J8DmzqLY1goQxVE12ebqDJgcrSjGrs5AFEfVZJt/AF0m+jHzUTtnAAAAAElFTkSuQmCC\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-play:hover{background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACEElEQVRoQ+2ZXStEQRjH/3/yIXwDdz7J+i7kvdisXCk3SiFJW27kglBcSFFKbqwQSa4krykuKB09Naf2Yndn5jgzc06d53Znd36/mWfeniVyHsw5PwqB0DOonYEoijYBlOpAFwCMkHwLDS/9mwhEDUCfAAyTXA4tYSLwC6CtCegegH6S56FETAR+AHRoACcBTJAUWa+RloBAXwAYIrnt0yBNgZi7qtbHgw8RFwLC/QFglOScawlXAjH3gUqrE1cirgVi7mkAYyS/0xbxJSDcdwAGSa6nKeFTIOZeUyL3aYiEEBDuLwDjJGf+KxFKIOY+BdBL8iipSGiBmHtWbbuftiJZERBuOfgGSK7aSGRJIObeUml1ayKSRQHhlgtkiaTcdltGVgUE+ppkV54FaiS78yrwqlLoOI8Cch2XV548W7WRpTVwA6DP9kGUFYEpAOUkT9LQAvtq1M+0udKkQSgBqSlJWWYxKXj8vRACK+o6bbRIdYI+Ba7U7rKjg7L53JdAhWTZBsy0rWuBXZUuNVMg23auBF7UIl2yBbJt70JAoKV6/WwLk6R9mgKSJlJ1kLTxFmkJyCla8UZd15GJQKvyumyJ8gy8DAEvfZoINPqD41EtUjmUgoaJwAaAnjrKebVI34OSq85NBNqlCAWgE0CV5GEWwI3vQlmCbcSinYFCwPEIFDPgeIC1P1/MgHaIHDf4Aydx2TF7wnKeAAAAAElFTkSuQmCC\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-pause{background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAABA0lEQVRoQ+1YwQqCUBAcfWXXsLr2AXWTPXno8yVB8AP6Aa3oHI+kCDqYaawJljSe133uzO44bx0M/HEG/v1gAd9mkAyQgY4I/F8LJUlyrQFtD2AtIkcNoFEU+Z7n7QD4DfFHEVlocrVmgAUAIAOl3mILPcDgEFcUhyrUKMGUUcroc3NQRimj9XJBGaWMvvPydKN0o6/9QTdKN6rZANxj6EbpRulGuZnjYqs8BbyR8Ub2Izeys+u6yyAIDpo/ehzHM2NMDsA0xFsRmWhyfTIDWSXxCEBmrd2EYXjSHJqm6bQoii2AOYBL5Z0xgFxEVppcrQvQJO0zhgX0iXbdWWSADHRE4AZQ731AhEUeNwAAAABJRU5ErkJggg==\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-pause:hover{background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAA7klEQVRoQ+2YSwrCQBBEX6HiVvxsPYDewfN7By/gD9ciQkvERQwJdBSiYs0mEDo96aruombEjy/9+P/jAj7NoBkwA28i8H8tFBFRA9oeWEo6ZgCNiDGwAYpn3TpKmmVytWbABQBmoNRbbqEHGB7iiuJYhRol2DJqGX1uDsuoZdRmLuNZSzGWUcuoZdRHSp/IylNgK2ErYSthK3FHwLcSvpXIjoLt9Jfa6TMwl3TIMBkRE2AH9BriL5KGmVyvWIltJXEfKN6tJJ0ym0bECFgDU+Ba+WZQFCdpkcnVuoBM0i5jXECXaNftZQbMwJsI3AAPN3dAQflHegAAAABJRU5ErkJggg==\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-record{background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAC+UlEQVRoQ+1ZS2sTURT+zlDJYE3XSq219QHVuEjnJDT+Bff9Abqw2voAEfGxqygUqWhVFHGl/yMLu9BwByxk5SNI66ML6U7axjhHbmhgWiftncxoOiV3FcI53z3f/e65594zhIQPSnj86BBot4IdBToKRFyBnbeFlFIScVEiuYvIWC6Xe2YK8pcC7SYA4CMzH4mDQBXAqilQBDsLQLfPf9FxnF4i8kwwmypARI+Wl5dvmIBEsUmlUkNE9NaHsVCpVAZGR0d/m+A2JSAid3K53E0TkCg2pVKpz7KseR/GfKVSGYxMAMA0M1+JEpyJb6lUOm5ZVnkrAsVisaunp+esiByr1Wp3R0ZGvmifzZK4XQQWHMc52MgBpdQuAOcAXABwuB400ZTjONdaIjA7O5u2bVsnWU1EujzP+5nP5xdMVjvIJkCBD8x8VCm1G8AYgAkAAxt8Z5j5YmgCSqlTAJ4D2OcD/AXgATNfbYVEAIFPIvKKiE4D6GuCea8xX6gtpJT6DmBvECgRFRzHeROWRAABE4iWCbwHEFhkPM/L5vP5dyaz+23+KwHXdR3P854S0YG1ILSCuthNMfNM2OC1/RYENLY+ygcBnPfht6ZAA6BYLNr6dyqVokKhsGpaNQ2TWJstreXaE2aed133sojcj41AKyvdzCdAgSXLsk4MDw9/a/i4rntbRPxFNZoC/5jAV2be759DKTUJ4FZSFFi0bbs/k8noy2R9dAjEuWU2YgXkQOK3kD6BMsysi2Z9JC2Jdcw/ALzwPO+xvmcl7Rj177JVEbkO4BARjSflFDJJuW1dBxJPoCIiL4noDIB1BS0pW6j+oJmbm+uuVqvjRKQfLr0bZHnIzJf0f6HeAybahrUJqAPruhLlcnnPysqKfpXp11n/Gv62zoHAroS+AafT6QkiGrIsazKbzX7eVIHEt1US39gCkOzWYthkjNE+tuZujDGZQ8XRXn8N4KT5lLFZ6uaYPt+nwyDuvC80YdhvB9uOAu1WoaNAR4GIK/AHvdr+QAexB7EAAAAASUVORK5CYII=\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-record:hover{background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACfUlEQVRoQ+2ZSYsUQRCFvycK4nJXXEbHBdwO4kn/gv9CD467ICIutxEFkREdFUU86T/xojcPntyQcT2INw+uISFVkD1Wd2dWlU7nUHlqisiX+fJFZGREi8yHMt8/HYG5VrBToFOg4QnMPxcyM2t4KE2nT0i6EwvylwIjQOCFpE1tEPgGfI0FamC3AFgazP8IrJL0KwZzkAI3gLMxIA1ttgCPA4w3wHpJP2NwBxG4KOlcDEgTGzNbA8wEGP57vA0CU5JONtlczFwz2wY8HUbAzBYCB4CtwCVJb33OIAXmioC70LoyBsxsEXAQOApsLIhelnS6FgEzW+5BBvwA/FS+SPJFa40KBZ5L2mxmS4AJ4IjHxCzwaUnHkgmY2V7gLrAyAPwOXJN0qg6DCgIvgQfAPsDjo2pcKddLciEz+wCs6AO6W9KjVBIVBGIgahN4BvRLMjslPYlZPbT53wR2AbeBtcUmXEFPdh5U06mbd/shBBzbr/Jx4FCAX0+BEsDMFocEYrNmFcE+BD4XsXZL0oyZnQCutkagzkn3m1NBwDe/Q9L74MAuFEqUn5op8I8JvJO0elacTALnc1HAH3Njkvwx+WeYWUegTa/pwaqIgexdyIN4uyRPmqULZRXEvulPwD3gpr+zcrtGQxfzRHYG2AAczuUWiom3kc4D2RN4BdwH9gM9CS0XFyoLGu9UuN974eIFVDiuSzruH5LqgRhtU20q8kBPV8LMlhVVmVdnYwX+SMdAZVeieAF7eeltmElJr4cpkH1bJfvGVvatxdR4bMu+teZuWxtKxWncXn8I7EldtQV7vz79fp9KwZp//9CksB8F206BuVahU6BToOEJ/Ab7+KdABdTt8AAAAABJRU5ErkJggg==\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-recordStop{background:url(\"data:image/png;base64,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\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-recordStop:hover{background:url(\"data:image/png;base64,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\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-fullscreen{background:url(\"data:image/png;base64,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\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-fullscreen:hover{background:url(\"data:image/png;base64,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\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-fullscreenExit{background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAADd0lEQVRoQ+2Zz2sdVRTHv+fJBDW6anDVXen6wZszYxYBiYgtFGst3VSDunKjpS0GpUlqfjVpsVVs6aaL0or4YxMVFCJZ2ZLdPUP+gq5bQnTxtNAkfTnlhnnlkmQy9yV9780rudt77tzv5/y4v4bQ4Y06XD/2ANodwec/AiJygJnvtdvTWfPnRkBEJAiCN8rl8kMfiPn5+Ve7u7v3rays0Orq6lJfX99/PuN2auMDoAD+BvA2M6/mTWSMOUtE48D6AjHGzN/kjdlNvy+AnWOOmQ/lTSYiEwDOWzsimgrDcCRvzG76GwGw8/zJzO9sN6GInAMwbW1UdSSKoqndCMwb6wNwGsB39Q+p6h/M/C4R2dTa1AoHYBWKyCkA1+pqiWi2Wq0e7e/vf7yRoJAAKcQggMtuJKIoOtoxACnE0/xOi/SXMAxPuhCFjUBdpIjYVWXSEf0TM3/g9BeriDMKdSPEz8z8vrU1xgwT0YXCrEJZy1iSJKOqOub0/8jMA0mSfKKqNwoPkHp7ioiGHIhRIvpHVa93BEBa2JcAfOlALAHo6RgAKzRJkk9V1S6xL7kpV4idOM31taxaIKJHqmpPnMMA9hcOQES2PDJkAT1XAAC+ZebPfWB3auNzmLObVsNRUNUXVHUujuM7OxXnMy4XwOcj29mIyOuq+lapVGrYCelKpkEQ3CyXy4tbzdN0AGPMxr2iYZ+sra3FcRybtgCIiK2BKw2rdgaUSqWoUqlIkQAepFDdAF7cBq5ERI9rtdr1OI7tmE2t6SmUEYFHAEaexYW/1QC2EF+ru5GIvg7D0D2GNJxprQY4o6qv1I/b6SpzOYqiLxpWng5oOQAzXxWRWwA+dkRfYOb1p5hGW6sBJpn5KytSRG4D+KguWFXHoyhy7xdeLC0F2ChSRL4H8OFuINoKYIUbY34gogHH3eeZef1K6tPaDpCm068A3nMEDzHzxY4BUNWSiPxORO6z5aDPPlGICNQ9bYyZIaLjjudzIQoFkKbTbwCO+UI0HcB9J/LdeY0xs0R02IGYYObRrWqiFQCfEZEtSHsfmGZm+4qxbbM/hQD8BeBNa0hEM2EYnmgLgP3lFARBT1dXly4vL//b29tbzQNIU+llAHeJaLFSqRzJes5vegR8xGbZLCwsHKzVav8z8/0sm0ID+MDvAfh4qZk2exFopnd9vv0ELrXBQO7fD10AAAAASUVORK5CYII=\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-fullscreenExit:hover{background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAC/ElEQVRoQ+2Zy49NQRCHvx+ReK6IlZ34E7CUiCAR4xEbTLCyQRATYswwb2IQZDYWgojHZpCQECts+ResiQwLj0RClNSkb9Lu3HtPz7mZc8+V6eXt6tP1VVV3VdcVbT7U5vozC9BqD/7/HjCzlZLet9rS9fbP9ICZvQPWSfqRAmFmS4ClMHm+JiR9S1mXVyYFwIBXwEZJv7I2MrPjQH8A6JN0OWtNM/OpAL7HS0mbsjYzswGgN8gNS+rJWtPM/HQAfJ9nkrY22tDMTgMjQaZH0nAzCmatTQE4ClyNPvQU2CbJQ2vKKB2Aa2hmR4DrkbbPgQ5Jv6sJSgkQILqA0dgTkjraBiBAxPHtPz2UtDuGKK0HKkqamd8qg5HS9yXtjebLdYjrHNRqiAeS9gQvnQGGSnML1bvGzOwc0BfN35PUaWYHgRulBwjW9ju+O4JwqM/AWFsABIgLwKkIYgJY1jYAAeJQuGIXVIVcKTKxh8WfBin9J+AVpx/eFWUEqFkyNACKp0rhgWYArkg6kQibSyylmPOklQdibijBX+fSLHFRJkDid+qKmdlaYENOI0zeEcBNSZ9qbVIEQHWuyGOTNZLetgrAz8ClPFpHa1ZL8rf5lFGEB2oBfAxQi4D5DeDmAP7mGJPka0oD4LnDr9imH/xFe8AP4vLIjBclxWXItCOtaIBjwOKo3HaFRyWdnLbmYUHhAJKumdkt4ECk9JCkSitmWixFAwxKOjt5uZvdBvZH2vZLit8XSSBFA/yjpJndAfY1A9FSgOCJu0BnBNErqfIkzfRCywECxCNgR6Rtt6TzmdqHBmyKXG4ZM4sTWc04NzNPWE+AuG3ZlZInSuGBinXMbBzYGVkrE6JUACGcHgPbUyGKAIj7REmZ18y897o5ghiQ5E/bltRChwE/kF7Xj0jyLkbDYWbzgBfA+iA4LmlXqwD8LydvszjAF0lfswBCKC0E3gBeP22p186f8RBKUbaejJmtAr5L+lBPptQAKfCzAClWmkmZWQ/MpHVTvv0X9iFAQGQyevIAAAAASUVORK5CYII=\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-audio{background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACrUlEQVRoQ+2ZPYgTURCAZzbBXJnCeL2Cnb87b9MEtPBUrrMQFAtrtT5/ClGs9LBWWz0RtbBUFCF4oJDsbO68wsLA2YqQSmLlvpEHu7IuMdlLcus+yUKKhJfZ+ebnvZl5CJY/aLn+MAP41x7M1QPMfFtr/crzvHfTAs8FoNPp1LTWzwHgqIg0lFLvrQHwfX8BER8DwC6jNCIecF13wwoA3/dvIuKNpLJa60Oe560XGoCZd4rICiKeTCtaeABmPg4AJmRqg6xcaABmvg4At4aFRyEBhoVM4UMoCplHADCfJTEL5YEsIVNID5iQAYCHALCYxeq5b6PMfF5EBAAEESthGK7W6/XPRpFWq7W3VCqtZg2ZcT3g+/6i4zjzIlLSWn/yPO/DIGMNLCWY2Sj/+xGRK0qpZfNDEASnROTFVi0fr8+aA8z8Ld6KEfGt67oLYwMAwEUium8EREn7OgeAjwCwPyo/nrque3YSgAtE9GDaAM1mc65arc4Zuf1+P2w0Gt9jJZl5DQAORt+fENG5wgEw8zUAMB/zbBBRwyqAIAjuiMjlSOlNItpjFUCqWl0josMzgChR/9hGAWBbknjmAdPhDdqa0gfZzAMJKyVP4v8hhJYRcSni+0JEu63ahZj5anyQici6UuqIVQDdbrfS6/UqRulyufyTiH5sF8AlIro37VpoWEHIzGZ2tM+sEZFnSqkzk9RCS0R01wjIsZz+mug53hDRia0AnI4bGgDYISItz/M2jYC8Gpp2u30MEWuO4zha665Sqp0ZYFStX/iWchRAItFGzoHSsrJ2ZFl1mHg6bfVYJeGJv85CC++BpIJZ5kSFC6G0ha0e7mYJqcJ7IOkRay84UhD2XjHFIFZf8iW9YcYoYRi+tO6aNeupOs66iU/icV46zf/MAKZpzXFk/QL+JG1PUPhRiQAAAABJRU5ErkJggg==\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-audio:hover{background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACSElEQVRoQ+2Zu4sUQRCHf5+C+gf4yBXMfMYHGvjCzEBQDIzV+HwEohipGKupD0QNDE8UEwUFTe68wEDhTMVUMFJ+0tArzbjs9u3Ojt0wBR0M9MzUV1XdXVWNKhcq1189wP/2YKcesH1d0nPgdVvgnQDY3iTpqaT9kuaAt9UA2D4o6aGkzVHpXcByFQC2r0q60lB2D7BUNIDtjZIeSDoyRNGyAWwfiiET4n6YlAtg+7Kka2PCozyAMSHT5CkLIIbMfUlbMhdmOQCZIVOeB2LI3JN0NNPq6bTZe8D2aUmOY72kN8DnoIXt7eF5FSEzkQdsB+OEsFwr6RPwbpixhqYStoPyqVwAbkaAY5KeTWD5wStZHrD9XdJgK34FhBP9H8kFOAvciQBhn3/RAcBHSTvjfx4DJ6cBOAPcbRvA9gZJYQT5DfwYKGl7UdLu+PwIOFUiwCVJYQRZBuZqA7gh6XxUegXYVhtAmq0uAnt7gLhQm9vorBZx74Hcc6D3QLKH/z2JGyVnlYs4pCfzEe4rsLW2XehicpAtAftqAwiZbhhBfgE/ZwVwDrjddi40KiG0HXpHO+KcJ8CJaXKheeBWBOgqnf6W1BwvgcOrATieFDTrJL0HViJAVwXNgVgPrJH0BfiQDTDKtREiNK7KLSnHASQLLacP1PxcVkWWq8PU3emq2yqJJ0b1Qsv2QKpdZp+orBBqmrfq5m5mSJXtgUZI1XnB0YCo94opCal6L/ka3ghtlIXqrllzT9VJ5k19Ek/y0zbf6QHatOYk3/oDujC8QMWgjf4AAAAASUVORK5CYII=\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-mute{background:url(\"data:image/png;base64,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\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-mute:hover{background:url(\"data:image/png;base64,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\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-text{font-size:14px;width:30px}.jessibuca-container .jessibuca-speed{font-size:14px;color:#fff}.jessibuca-container .jessibuca-quality-menu-list{position:absolute;left:50%;bottom:100%;visibility:hidden;opacity:0;transform:translateX(-50%);transition:visibility .3s,opacity .3s;background-color:rgba(0,0,0,.5);border-radius:4px}.jessibuca-container .jessibuca-quality-menu-list.jessibuca-quality-menu-shown{visibility:visible;opacity:1}.jessibuca-container .icon-title-tips{pointer-events:none;position:absolute;left:50%;bottom:100%;visibility:hidden;opacity:0;transform:translateX(-50%);transition:visibility .3s ease 0s,opacity .3s ease 0s;background-color:rgba(0,0,0,.5);border-radius:4px}.jessibuca-container .icon-title{display:inline-block;padding:5px 10px;font-size:12px;white-space:nowrap;color:#fff}.jessibuca-container .jessibuca-quality-menu{padding:8px 0}.jessibuca-container .jessibuca-quality-menu-item{display:block;height:25px;margin:0;padding:0 10px;cursor:pointer;font-size:14px;text-align:center;width:50px;color:hsla(0,0%,100%,.5);transition:color .3s,background-color .3s}.jessibuca-container .jessibuca-quality-menu-item:hover{background-color:hsla(0,0%,100%,.2)}.jessibuca-container .jessibuca-quality-menu-item:focus{outline:none}.jessibuca-container .jessibuca-quality-menu-item.jessibuca-quality-menu-item-active{color:#2298fc}.jessibuca-container .jessibuca-volume-panel-wrap{position:absolute;left:50%;bottom:100%;visibility:hidden;opacity:0;transform:translateX(-50%) translateY(22%);transition:visibility .3s,opacity .3s;background-color:rgba(0,0,0,.5);border-radius:4px;height:120px;width:50px;overflow:hidden}.jessibuca-container .jessibuca-volume-panel-wrap.jessibuca-volume-panel-wrap-show{visibility:visible;opacity:1}.jessibuca-container .jessibuca-volume-panel{cursor:pointer;position:absolute;top:21px;height:60px;width:50px;overflow:hidden}.jessibuca-container .jessibuca-volume-panel-text{position:absolute;left:0;top:0;width:50px;height:20px;line-height:20px;text-align:center;color:#fff;font-size:12px}.jessibuca-container .jessibuca-volume-panel-handle{position:absolute;top:48px;left:50%;width:12px;height:12px;border-radius:12px;margin-left:-6px;background:#fff}.jessibuca-container .jessibuca-volume-panel-handle:before{bottom:-54px;background:#fff}.jessibuca-container .jessibuca-volume-panel-handle:after{bottom:6px;background:hsla(0,0%,100%,.2)}.jessibuca-container .jessibuca-volume-panel-handle:after,.jessibuca-container .jessibuca-volume-panel-handle:before{content:\"\";position:absolute;display:block;left:50%;width:3px;margin-left:-1px;height:60px}.jessibuca-container.jessibuca-fullscreen-web .jessibuca-controls{width:100vh}.jessibuca-container.jessibuca-fullscreen-web .jessibuca-play-big:after{transform:translate(-50%,-50%) rotate(270deg)}.jessibuca-container.jessibuca-fullscreen-web .jessibuca-loading{flex-direction:row}.jessibuca-container.jessibuca-fullscreen-web .jessibuca-loading-text{transform:rotate(270deg)}\n/*# sourceMappingURL=data:application/json;base64,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eWZyYW1lcyByb3RhdGlvbiB7XG4gIGZyb20ge1xuICAgIC13ZWJraXQtdHJhbnNmb3JtOiByb3RhdGUoMGRlZyk7IH1cbiAgdG8ge1xuICAgIC13ZWJraXQtdHJhbnNmb3JtOiByb3RhdGUoMzYwZGVnKTsgfSB9XG5cbkBrZXlmcmFtZXMgbWFnZW50YVB1bHNlIHtcbiAgZnJvbSB7XG4gICAgYmFja2dyb3VuZC1jb2xvcjogIzYzMDAzMDtcbiAgICAtd2Via2l0LWJveC1zaGFkb3c6IDAgMCA5cHggIzMzMzsgfVxuICA1MCUge1xuICAgIGJhY2tncm91bmQtY29sb3I6ICNhOTAxNGI7XG4gICAgLXdlYmtpdC1ib3gtc2hhZG93OiAwIDAgMThweCAjYTkwMTRiOyB9XG4gIHRvIHtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjNjMwMDMwO1xuICAgIC13ZWJraXQtYm94LXNoYWRvdzogMCAwIDlweCAjMzMzOyB9IH1cblxuLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1pY29uIHtcbiAgY3Vyc29yOiBwb2ludGVyO1xuICB3aWR0aDogMTZweDtcbiAgaGVpZ2h0OiAxNnB4OyB9XG5cbi5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtcG9zdGVyIHtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICB6LWluZGV4OiAxMDtcbiAgbGVmdDogMDtcbiAgdG9wOiAwO1xuICByaWdodDogMDtcbiAgYm90dG9tOiAwO1xuICBoZWlnaHQ6IDEwMCU7XG4gIHdpZHRoOiAxMDAlO1xuICBiYWNrZ3JvdW5kLXBvc2l0aW9uOiBjZW50ZXIgY2VudGVyO1xuICBiYWNrZ3JvdW5kLXJlcGVhdDogbm8tcmVwZWF0O1xuICBiYWNrZ3JvdW5kLXNpemU6IGNvbnRhaW47XG4gIHBvaW50ZXItZXZlbnRzOiBub25lOyB9XG5cbi5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtcGxheS1iaWcge1xuICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gIGRpc3BsYXk6IG5vbmU7XG4gIGhlaWdodDogMTAwJTtcbiAgd2lkdGg6IDEwMCU7XG4gIGJhY2tncm91bmQ6IHJnYmEoMCwgMCwgMCwgMC40KTsgfVxuICAuamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLXBsYXktYmlnOmFmdGVyIHtcbiAgICBjdXJzb3I6IHBvaW50ZXI7XG4gICAgY29udGVudDogJyc7XG4gICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgIGxlZnQ6IDUwJTtcbiAgICB0b3A6IDUwJTtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtNTAlLCAtNTAlKTtcbiAgICBkaXNwbGF5OiBibG9jaztcbiAgICB3aWR0aDogNDhweDtcbiAgICBoZWlnaHQ6IDQ4cHg7XG4gICAgYmFja2dyb3VuZC1pbWFnZTogdXJsKFwiLi4vYXNzZXRzL3BsYXkucG5nXCIpO1xuICAgIGJhY2tncm91bmQtcmVwZWF0OiBuby1yZXBlYXQ7XG4gICAgYmFja2dyb3VuZC1wb3NpdGlvbjogY2VudGVyOyB9XG4gIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtcGxheS1iaWc6aG92ZXI6YWZ0ZXIge1xuICAgIGJhY2tncm91bmQtaW1hZ2U6IHVybChcIi4uL2Fzc2V0cy9wbGF5LWhvdmVyLnBuZ1wiKTsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLXJlY29yZGluZyB7XG4gIGRpc3BsYXk6IG5vbmU7XG4gIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgbGVmdDogNTAlO1xuICB0b3A6IDA7XG4gIHBhZGRpbmc6IDAgM3B4O1xuICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVgoLTUwJSk7XG4gIGp1c3RpZnktY29udGVudDogc3BhY2UtYXJvdW5kO1xuICBhbGlnbi1pdGVtczogY2VudGVyO1xuICB3aWR0aDogOTVweDtcbiAgaGVpZ2h0OiAyMHB4O1xuICBiYWNrZ3JvdW5kOiAjMDAwMDAwO1xuICBvcGFjaXR5OiAxO1xuICBib3JkZXItcmFkaXVzOiAwcHggMHB4IDhweCA4cHg7XG4gIHotaW5kZXg6IDE7IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1yZWNvcmRpbmcgLmplc3NpYnVjYS1yZWNvcmRpbmctcmVkLXBvaW50IHtcbiAgICB3aWR0aDogOHB4O1xuICAgIGhlaWdodDogOHB4O1xuICAgIGJhY2tncm91bmQ6ICNGRjFGMUY7XG4gICAgYm9yZGVyLXJhZGl1czogNTAlO1xuICAgIGFuaW1hdGlvbjogbWFnZW50YVB1bHNlIDFzIGxpbmVhciBpbmZpbml0ZTsgfVxuICAuamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLXJlY29yZGluZyAuamVzc2lidWNhLXJlY29yZGluZy10aW1lIHtcbiAgICBmb250LXNpemU6IDE0cHg7XG4gICAgZm9udC13ZWlnaHQ6IDUwMDtcbiAgICBjb2xvcjogI0RERERERDsgfVxuICAuamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLXJlY29yZGluZyAuamVzc2lidWNhLWljb24tcmVjb3JkU3RvcCB7XG4gICAgd2lkdGg6IDE2cHg7XG4gICAgaGVpZ2h0OiAxNnB4O1xuICAgIGN1cnNvcjogcG9pbnRlcjsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWxvYWRpbmcge1xuICBkaXNwbGF5OiBub25lO1xuICBmbGV4LWRpcmVjdGlvbjogY29sdW1uO1xuICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjtcbiAgYWxpZ24taXRlbXM6IGNlbnRlcjtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICB6LWluZGV4OiAyMDtcbiAgbGVmdDogMDtcbiAgdG9wOiAwO1xuICByaWdodDogMDtcbiAgYm90dG9tOiAwO1xuICB3aWR0aDogMTAwJTtcbiAgaGVpZ2h0OiAxMDAlO1xuICBwb2ludGVyLWV2ZW50czogbm9uZTsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWxvYWRpbmctdGV4dCB7XG4gIGxpbmUtaGVpZ2h0OiAyMHB4O1xuICBmb250LXNpemU6IDEzcHg7XG4gIGNvbG9yOiAjZmZmO1xuICBtYXJnaW4tdG9wOiAxMHB4OyB9XG5cbi5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtY29udHJvbHMge1xuICBiYWNrZ3JvdW5kLWNvbG9yOiAjMTYxNjE2O1xuICBib3gtc2l6aW5nOiBib3JkZXItYm94O1xuICBkaXNwbGF5OiBmbGV4O1xuICBmbGV4LWRpcmVjdGlvbjogY29sdW1uO1xuICBqdXN0aWZ5LWNvbnRlbnQ6IGZsZXgtZW5kO1xuICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gIHotaW5kZXg6IDQwO1xuICBsZWZ0OiAwO1xuICByaWdodDogMDtcbiAgYm90dG9tOiAwO1xuICBoZWlnaHQ6IDM4cHg7XG4gIHdpZHRoOiAxMDAlO1xuICBwYWRkaW5nLWxlZnQ6IDEzcHg7XG4gIHBhZGRpbmctcmlnaHQ6IDEzcHg7XG4gIGZvbnQtc2l6ZTogMTRweDtcbiAgY29sb3I6ICNmZmY7XG4gIG9wYWNpdHk6IDA7XG4gIHZpc2liaWxpdHk6IGhpZGRlbjtcbiAgdHJhbnNpdGlvbjogYWxsIDAuMnMgZWFzZS1pbi1vdXQ7XG4gIHVzZXItc2VsZWN0OiBub25lO1xuICB0cmFuc2l0aW9uOiB3aWR0aCAuNXMgZWFzZS1pbjsgfVxuICAuamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWNvbnRyb2xzIC5qZXNzaWJ1Y2EtY29udHJvbHMtaXRlbSB7XG4gICAgcG9zaXRpb246IHJlbGF0aXZlO1xuICAgIGRpc3BsYXk6IGZsZXg7XG4gICAganVzdGlmeS1jb250ZW50OiBjZW50ZXI7XG4gICAgcGFkZGluZzogMCA4cHg7IH1cbiAgICAuamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWNvbnRyb2xzIC5qZXNzaWJ1Y2EtY29udHJvbHMtaXRlbTpob3ZlciAuaWNvbi10aXRsZS10aXBzIHtcbiAgICAgIHZpc2liaWxpdHk6IHZpc2libGU7XG4gICAgICBvcGFjaXR5OiAxOyB9XG4gIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtY29udHJvbHMgLmplc3NpYnVjYS1taWNyb3Bob25lLWNsb3NlIHtcbiAgICBkaXNwbGF5OiBub25lOyB9XG4gIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtY29udHJvbHMgLmplc3NpYnVjYS1pY29uLWF1ZGlvIHtcbiAgICBkaXNwbGF5OiBub25lOyB9XG4gIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtY29udHJvbHMgLmplc3NpYnVjYS1wbGF5IHtcbiAgICBkaXNwbGF5OiBub25lOyB9XG4gIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtY29udHJvbHMgLmplc3NpYnVjYS1wYXVzZSB7XG4gICAgZGlzcGxheTogbm9uZTsgfVxuICAuamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWNvbnRyb2xzIC5qZXNzaWJ1Y2EtZnVsbHNjcmVlbi1leGl0IHtcbiAgICBkaXNwbGF5OiBub25lOyB9XG4gIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtY29udHJvbHMgLmplc3NpYnVjYS1zY3JlZW5zaG90IHtcbiAgICBkaXNwbGF5OiBub25lOyB9XG4gIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtY29udHJvbHMgLmplc3NpYnVjYS1yZWNvcmQge1xuICAgIGRpc3BsYXk6IG5vbmU7IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1jb250cm9scyAuamVzc2lidWNhLWZ1bGxzY3JlZW4ge1xuICAgIGRpc3BsYXk6IG5vbmU7IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1jb250cm9scyAuamVzc2lidWNhLXJlY29yZC1zdG9wIHtcbiAgICBkaXNwbGF5OiBub25lOyB9XG4gIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtY29udHJvbHMgLmplc3NpYnVjYS1pY29uLWF1ZGlvLCAuamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWNvbnRyb2xzIC5qZXNzaWJ1Y2EtaWNvbi1tdXRlIHtcbiAgICB6LWluZGV4OiAxOyB9XG4gIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtY29udHJvbHMgLmplc3NpYnVjYS1jb250cm9scy1ib3R0b20ge1xuICAgIGRpc3BsYXk6IGZsZXg7XG4gICAganVzdGlmeS1jb250ZW50OiBzcGFjZS1iZXR3ZWVuO1xuICAgIGhlaWdodDogMTAwJTsgfVxuICAgIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtY29udHJvbHMgLmplc3NpYnVjYS1jb250cm9scy1ib3R0b20gLmplc3NpYnVjYS1jb250cm9scy1sZWZ0IHtcbiAgICAgIGRpc3BsYXk6IGZsZXg7XG4gICAgICBhbGlnbi1pdGVtczogY2VudGVyOyB9XG4gICAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1jb250cm9scyAuamVzc2lidWNhLWNvbnRyb2xzLWJvdHRvbSAuamVzc2lidWNhLWNvbnRyb2xzLXJpZ2h0IHtcbiAgICAgIGRpc3BsYXk6IGZsZXg7XG4gICAgICBhbGlnbi1pdGVtczogY2VudGVyOyB9XG5cbi5qZXNzaWJ1Y2EtY29udGFpbmVyLmplc3NpYnVjYS1jb250cm9scy1zaG93IC5qZXNzaWJ1Y2EtY29udHJvbHMge1xuICBvcGFjaXR5OiAxO1xuICB2aXNpYmlsaXR5OiB2aXNpYmxlOyB9XG5cbi5qZXNzaWJ1Y2EtY29udGFpbmVyLmplc3NpYnVjYS1jb250cm9scy1zaG93LWF1dG8taGlkZSAuamVzc2lidWNhLWNvbnRyb2xzIHtcbiAgb3BhY2l0eTogMC44O1xuICB2aXNpYmlsaXR5OiB2aXNpYmxlO1xuICBkaXNwbGF5OiBub25lOyB9XG5cbi5qZXNzaWJ1Y2EtY29udGFpbmVyLmplc3NpYnVjYS1oaWRlLWN1cnNvciAqIHtcbiAgY3Vyc29yOiBub25lICFpbXBvcnRhbnQ7IH1cblxuLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1pY29uLWxvYWRpbmcge1xuICB3aWR0aDogNTBweDtcbiAgaGVpZ2h0OiA1MHB4O1xuICBiYWNrZ3JvdW5kOiB1cmwoXCIuLi9hc3NldHMvbG9hZGluZy5wbmdcIikgbm8tcmVwZWF0IGNlbnRlcjtcbiAgYmFja2dyb3VuZC1zaXplOiAxMDAlIDEwMCU7XG4gIGFuaW1hdGlvbjogcm90YXRpb24gMXMgbGluZWFyIGluZmluaXRlOyB9XG5cbi5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtaWNvbi1zY3JlZW5zaG90IHtcbiAgYmFja2dyb3VuZDogdXJsKFwiLi4vYXNzZXRzL3NjcmVlbnNob3QucG5nXCIpIG5vLXJlcGVhdCBjZW50ZXI7XG4gIGJhY2tncm91bmQtc2l6ZTogMTAwJSAxMDAlOyB9XG4gIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtaWNvbi1zY3JlZW5zaG90OmhvdmVyIHtcbiAgICBiYWNrZ3JvdW5kOiB1cmwoXCIuLi9hc3NldHMvc2NyZWVuc2hvdC1ob3Zlci5wbmdcIikgbm8tcmVwZWF0IGNlbnRlcjtcbiAgICBiYWNrZ3JvdW5kLXNpemU6IDEwMCUgMTAwJTsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWljb24tcGxheSB7XG4gIGJhY2tncm91bmQ6IHVybChcIi4uL2Fzc2V0cy9wbGF5LnBuZ1wiKSBuby1yZXBlYXQgY2VudGVyO1xuICBiYWNrZ3JvdW5kLXNpemU6IDEwMCUgMTAwJTsgfVxuICAuamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWljb24tcGxheTpob3ZlciB7XG4gICAgYmFja2dyb3VuZDogdXJsKFwiLi4vYXNzZXRzL3BsYXktaG92ZXIucG5nXCIpIG5vLXJlcGVhdCBjZW50ZXI7XG4gICAgYmFja2dyb3VuZC1zaXplOiAxMDAlIDEwMCU7IH1cblxuLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1pY29uLXBhdXNlIHtcbiAgYmFja2dyb3VuZDogdXJsKFwiLi4vYXNzZXRzL3BhdXNlLnBuZ1wiKSBuby1yZXBlYXQgY2VudGVyO1xuICBiYWNrZ3JvdW5kLXNpemU6IDEwMCUgMTAwJTsgfVxuICAuamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWljb24tcGF1c2U6aG92ZXIge1xuICAgIGJhY2tncm91bmQ6IHVybChcIi4uL2Fzc2V0cy9wYXVzZS1ob3Zlci5wbmdcIikgbm8tcmVwZWF0IGNlbnRlcjtcbiAgICBiYWNrZ3JvdW5kLXNpemU6IDEwMCUgMTAwJTsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWljb24tcmVjb3JkIHtcbiAgYmFja2dyb3VuZDogdXJsKFwiLi4vYXNzZXRzL3JlY29yZC5wbmdcIikgbm8tcmVwZWF0IGNlbnRlcjtcbiAgYmFja2dyb3VuZC1zaXplOiAxMDAlIDEwMCU7IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1pY29uLXJlY29yZDpob3ZlciB7XG4gICAgYmFja2dyb3VuZDogdXJsKFwiLi4vYXNzZXRzL3JlY29yZC1ob3Zlci5wbmdcIikgbm8tcmVwZWF0IGNlbnRlcjtcbiAgICBiYWNrZ3JvdW5kLXNpemU6IDEwMCUgMTAwJTsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWljb24tcmVjb3JkU3RvcCB7XG4gIGJhY2tncm91bmQ6IHVybChcIi4uL2Fzc2V0cy9yZWNvcmQtc3RvcC5wbmdcIikgbm8tcmVwZWF0IGNlbnRlcjtcbiAgYmFja2dyb3VuZC1zaXplOiAxMDAlIDEwMCU7IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1pY29uLXJlY29yZFN0b3A6aG92ZXIge1xuICAgIGJhY2tncm91bmQ6IHVybChcIi4uL2Fzc2V0cy9yZWNvcmQtc3RvcC1ob3Zlci5wbmdcIikgbm8tcmVwZWF0IGNlbnRlcjtcbiAgICBiYWNrZ3JvdW5kLXNpemU6IDEwMCUgMTAwJTsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWljb24tZnVsbHNjcmVlbiB7XG4gIGJhY2tncm91bmQ6IHVybChcIi4uL2Fzc2V0cy9mdWxsc2NyZWVuLnBuZ1wiKSBuby1yZXBlYXQgY2VudGVyO1xuICBiYWNrZ3JvdW5kLXNpemU6IDEwMCUgMTAwJTsgfVxuICAuamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWljb24tZnVsbHNjcmVlbjpob3ZlciB7XG4gICAgYmFja2dyb3VuZDogdXJsKFwiLi4vYXNzZXRzL2Z1bGxzY3JlZW4taG92ZXIucG5nXCIpIG5vLXJlcGVhdCBjZW50ZXI7XG4gICAgYmFja2dyb3VuZC1zaXplOiAxMDAlIDEwMCU7IH1cblxuLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1pY29uLWZ1bGxzY3JlZW5FeGl0IHtcbiAgYmFja2dyb3VuZDogdXJsKFwiLi4vYXNzZXRzL2V4aXQtZnVsbHNjcmVlbi5wbmdcIikgbm8tcmVwZWF0IGNlbnRlcjtcbiAgYmFja2dyb3VuZC1zaXplOiAxMDAlIDEwMCU7IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1pY29uLWZ1bGxzY3JlZW5FeGl0OmhvdmVyIHtcbiAgICBiYWNrZ3JvdW5kOiB1cmwoXCIuLi9hc3NldHMvZXhpdC1mdWxsc2NyZWVuLWhvdmVyLnBuZ1wiKSBuby1yZXBlYXQgY2VudGVyO1xuICAgIGJhY2tncm91bmQtc2l6ZTogMTAwJSAxMDAlOyB9XG5cbi5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtaWNvbi1hdWRpbyB7XG4gIGJhY2tncm91bmQ6IHVybChcIi4uL2Fzc2V0cy9hdWRpby5wbmdcIikgbm8tcmVwZWF0IGNlbnRlcjtcbiAgYmFja2dyb3VuZC1zaXplOiAxMDAlIDEwMCU7IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1pY29uLWF1ZGlvOmhvdmVyIHtcbiAgICBiYWNrZ3JvdW5kOiB1cmwoXCIuLi9hc3NldHMvYXVkaW8taG92ZXIucG5nXCIpIG5vLXJlcGVhdCBjZW50ZXI7XG4gICAgYmFja2dyb3VuZC1zaXplOiAxMDAlIDEwMCU7IH1cblxuLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1pY29uLW11dGUge1xuICBiYWNrZ3JvdW5kOiB1cmwoXCIuLi9hc3NldHMvbXV0ZS5wbmdcIikgbm8tcmVwZWF0IGNlbnRlcjtcbiAgYmFja2dyb3VuZC1zaXplOiAxMDAlIDEwMCU7IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1pY29uLW11dGU6aG92ZXIge1xuICAgIGJhY2tncm91bmQ6IHVybChcIi4uL2Fzc2V0cy9tdXRlLWhvdmVyLnBuZ1wiKSBuby1yZXBlYXQgY2VudGVyO1xuICAgIGJhY2tncm91bmQtc2l6ZTogMTAwJSAxMDAlOyB9XG5cbi5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtaWNvbi10ZXh0IHtcbiAgZm9udC1zaXplOiAxNHB4O1xuICB3aWR0aDogMzBweDsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLXNwZWVkIHtcbiAgZm9udC1zaXplOiAxNHB4O1xuICBjb2xvcjogI2ZmZjsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLXF1YWxpdHktbWVudS1saXN0IHtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICBsZWZ0OiA1MCU7XG4gIGJvdHRvbTogMTAwJTtcbiAgdmlzaWJpbGl0eTogaGlkZGVuO1xuICBvcGFjaXR5OiAwO1xuICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVgoLTUwJSk7XG4gIHRyYW5zaXRpb246IHZpc2liaWxpdHkgMzAwbXMsIG9wYWNpdHkgMzAwbXM7XG4gIGJhY2tncm91bmQtY29sb3I6IHJnYmEoMCwgMCwgMCwgMC41KTtcbiAgYm9yZGVyLXJhZGl1czogNHB4OyB9XG4gIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtcXVhbGl0eS1tZW51LWxpc3QuamVzc2lidWNhLXF1YWxpdHktbWVudS1zaG93biB7XG4gICAgdmlzaWJpbGl0eTogdmlzaWJsZTtcbiAgICBvcGFjaXR5OiAxOyB9XG5cbi5qZXNzaWJ1Y2EtY29udGFpbmVyIC5pY29uLXRpdGxlLXRpcHMge1xuICBwb2ludGVyLWV2ZW50czogbm9uZTtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICBsZWZ0OiA1MCU7XG4gIGJvdHRvbTogMTAwJTtcbiAgdmlzaWJpbGl0eTogaGlkZGVuO1xuICBvcGFjaXR5OiAwO1xuICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVgoLTUwJSk7XG4gIHRyYW5zaXRpb246IHZpc2liaWxpdHkgMzAwbXMgZWFzZSAwcywgb3BhY2l0eSAzMDBtcyBlYXNlIDBzO1xuICBiYWNrZ3JvdW5kLWNvbG9yOiByZ2JhKDAsIDAsIDAsIDAuNSk7XG4gIGJvcmRlci1yYWRpdXM6IDRweDsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lciAuaWNvbi10aXRsZSB7XG4gIGRpc3BsYXk6IGlubGluZS1ibG9jaztcbiAgcGFkZGluZzogNXB4IDEwcHg7XG4gIGZvbnQtc2l6ZTogMTJweDtcbiAgd2hpdGUtc3BhY2U6IG5vd3JhcDtcbiAgY29sb3I6IHdoaXRlOyB9XG5cbi5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtcXVhbGl0eS1tZW51IHtcbiAgcGFkZGluZzogOHB4IDA7IH1cblxuLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1xdWFsaXR5LW1lbnUtaXRlbSB7XG4gIGRpc3BsYXk6IGJsb2NrO1xuICBoZWlnaHQ6IDI1cHg7XG4gIG1hcmdpbjogMDtcbiAgcGFkZGluZzogMCAxMHB4O1xuICBjdXJzb3I6IHBvaW50ZXI7XG4gIGZvbnQtc2l6ZTogMTRweDtcbiAgdGV4dC1hbGlnbjogY2VudGVyO1xuICB3aWR0aDogNTBweDtcbiAgY29sb3I6IHJnYmEoMjU1LCAyNTUsIDI1NSwgMC41KTtcbiAgdHJhbnNpdGlvbjogY29sb3IgMzAwbXMsIGJhY2tncm91bmQtY29sb3IgMzAwbXM7IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1xdWFsaXR5LW1lbnUtaXRlbTpob3ZlciB7XG4gICAgYmFja2dyb3VuZC1jb2xvcjogcmdiYSgyNTUsIDI1NSwgMjU1LCAwLjIpOyB9XG4gIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtcXVhbGl0eS1tZW51LWl0ZW06Zm9jdXMge1xuICAgIG91dGxpbmU6IG5vbmU7IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1xdWFsaXR5LW1lbnUtaXRlbS5qZXNzaWJ1Y2EtcXVhbGl0eS1tZW51LWl0ZW0tYWN0aXZlIHtcbiAgICBjb2xvcjogIzIyOThGQzsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLXZvbHVtZS1wYW5lbC13cmFwIHtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICBsZWZ0OiA1MCU7XG4gIGJvdHRvbTogMTAwJTtcbiAgdmlzaWJpbGl0eTogaGlkZGVuO1xuICBvcGFjaXR5OiAwO1xuICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVgoLTUwJSkgdHJhbnNsYXRlWSgyMiUpO1xuICB0cmFuc2l0aW9uOiB2aXNpYmlsaXR5IDMwMG1zLCBvcGFjaXR5IDMwMG1zO1xuICBiYWNrZ3JvdW5kLWNvbG9yOiByZ2JhKDAsIDAsIDAsIDAuNSk7XG4gIGJvcmRlci1yYWRpdXM6IDRweDtcbiAgaGVpZ2h0OiAxMjBweDtcbiAgd2lkdGg6IDUwcHg7XG4gIG92ZXJmbG93OiBoaWRkZW47IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS12b2x1bWUtcGFuZWwtd3JhcC5qZXNzaWJ1Y2Etdm9sdW1lLXBhbmVsLXdyYXAtc2hvdyB7XG4gICAgdmlzaWJpbGl0eTogdmlzaWJsZTtcbiAgICBvcGFjaXR5OiAxOyB9XG5cbi5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2Etdm9sdW1lLXBhbmVsIHtcbiAgY3Vyc29yOiBwb2ludGVyO1xuICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gIHRvcDogMjFweDtcbiAgaGVpZ2h0OiA2MHB4O1xuICB3aWR0aDogNTBweDtcbiAgb3ZlcmZsb3c6IGhpZGRlbjsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLXZvbHVtZS1wYW5lbC10ZXh0IHtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICBsZWZ0OiAwO1xuICB0b3A6IDA7XG4gIHdpZHRoOiA1MHB4O1xuICBoZWlnaHQ6IDIwcHg7XG4gIGxpbmUtaGVpZ2h0OiAyMHB4O1xuICB0ZXh0LWFsaWduOiBjZW50ZXI7XG4gIGNvbG9yOiAjZmZmO1xuICBmb250LXNpemU6IDEycHg7IH1cblxuLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS12b2x1bWUtcGFuZWwtaGFuZGxlIHtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICB0b3A6IDQ4cHg7XG4gIGxlZnQ6IDUwJTtcbiAgd2lkdGg6IDEycHg7XG4gIGhlaWdodDogMTJweDtcbiAgYm9yZGVyLXJhZGl1czogMTJweDtcbiAgbWFyZ2luLWxlZnQ6IC02cHg7XG4gIGJhY2tncm91bmQ6ICNmZmY7IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS12b2x1bWUtcGFuZWwtaGFuZGxlOjpiZWZvcmUge1xuICAgIGJvdHRvbTogLTU0cHg7XG4gICAgYmFja2dyb3VuZDogI2ZmZjsgfVxuICAuamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLXZvbHVtZS1wYW5lbC1oYW5kbGU6OmFmdGVyIHtcbiAgICBib3R0b206IDZweDtcbiAgICBiYWNrZ3JvdW5kOiByZ2JhKDI1NSwgMjU1LCAyNTUsIDAuMik7IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS12b2x1bWUtcGFuZWwtaGFuZGxlOjpiZWZvcmUsIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2Etdm9sdW1lLXBhbmVsLWhhbmRsZTo6YWZ0ZXIge1xuICAgIGNvbnRlbnQ6ICcnO1xuICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICBkaXNwbGF5OiBibG9jaztcbiAgICBsZWZ0OiA1MCU7XG4gICAgd2lkdGg6IDNweDtcbiAgICBtYXJnaW4tbGVmdDogLTFweDtcbiAgICBoZWlnaHQ6IDYwcHg7IH1cblxuLmplc3NpYnVjYS1jb250YWluZXIuamVzc2lidWNhLWZ1bGxzY3JlZW4td2ViIC5qZXNzaWJ1Y2EtY29udHJvbHMge1xuICB3aWR0aDogMTAwdmg7IH1cblxuLmplc3NpYnVjYS1jb250YWluZXIuamVzc2lidWNhLWZ1bGxzY3JlZW4td2ViIC5qZXNzaWJ1Y2EtcGxheS1iaWc6YWZ0ZXIge1xuICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtNTAlLCAtNTAlKSByb3RhdGUoMjcwZGVnKTsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lci5qZXNzaWJ1Y2EtZnVsbHNjcmVlbi13ZWIgLmplc3NpYnVjYS1sb2FkaW5nIHtcbiAgZmxleC1kaXJlY3Rpb246IHJvdzsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lci5qZXNzaWJ1Y2EtZnVsbHNjcmVlbi13ZWIgLmplc3NpYnVjYS1sb2FkaW5nLXRleHQge1xuICB0cmFuc2Zvcm06IHJvdGF0ZSgyNzBkZWcpOyB9XG4iXX0= */"; - styleInject(css_248z$1); - - // todo: 待定 - var hotkey = ((player, control) => { - const { - events: { - proxy - } - } = player; - const keys = {}; - - function addHotkey(key, event) { - if (keys[key]) { - keys[key].push(event); - } else { - keys[key] = [event]; - } - } // - - - addHotkey(HOT_KEY.esc, () => { - if (player.fullscreen) { - player.fullscreen = false; - } - }); // - - addHotkey(HOT_KEY.arrowUp, () => { - player.volume += 0.05; - }); // - - addHotkey(HOT_KEY.arrowDown, () => { - player.volume -= 0.05; - }); - proxy(window, 'keydown', event => { - if (control.isFocus) { - const tag = document.activeElement.tagName.toUpperCase(); - const editable = document.activeElement.getAttribute('contenteditable'); - - if (tag !== 'INPUT' && tag !== 'TEXTAREA' && editable !== '' && editable !== 'true') { - const events = keys[event.keyCode]; - - if (events) { - event.preventDefault(); - events.forEach(fn => fn()); - } - } - } - }); - }); - - class Control { - constructor(player) { - this.player = player; - template(player, this); - property(player, this); - observer$1(player, this); - events(player, this); - - if (player._opt.hotKey) { - hotkey(player, this); - } - - this.player.debug.log('Control', 'init'); - } - - destroy() { - if (this.$poster) { - const result = removeElement(this.$poster); - - if (!result) { - const $poster = this.player.$container.querySelector('.jessibuca-poster'); - - if ($poster && this.player.$container) { - this.player.$container.removeChild($poster); - } - } - } - - if (this.$loading) { - const result = removeElement(this.$loading); - - if (!result) { - const $loading = this.player.$container.querySelector('.jessibuca-loading'); - - if ($loading && this.player.$container) { - this.player.$container.removeChild($loading); - } - } - } - - if (this.$controls) { - const result = removeElement(this.$controls); - - if (!result) { - const $controls = this.player.$container.querySelector('.jessibuca-controls'); - - if ($controls && this.player.$container) { - this.player.$container.removeChild($controls); - } - } - } - - if (this.$recording) { - const result = removeElement(this.$recording); - - if (!result) { - const $recording = this.player.$container.querySelector('.jessibuca-recording'); - - if ($recording && this.player.$container) { - this.player.$container.removeChild($recording); - } - } - } - - if (this.$playBig) { - const result = removeElement(this.$playBig); - - if (!result) { - const $playBig = this.player.$container.querySelector('.jessibuca-play-big'); - - if ($playBig && this.player.$container) { - this.player.$container.removeChild($playBig); - } - } - } - - this.player.debug.log('control', 'destroy'); - } - - autoSize() { - const player = this.player; - player.$container.style.padding = '0 0'; - const playerWidth = player.width; - const playerHeight = player.height; - const playerRatio = playerWidth / playerHeight; - const canvasWidth = player.video.$videoElement.width; - const canvasHeight = player.video.$videoElement.height; - const canvasRatio = canvasWidth / canvasHeight; - - if (playerRatio > canvasRatio) { - const padding = (playerWidth - playerHeight * canvasRatio) / 2; - player.$container.style.padding = `0 ${padding}px`; - } else { - const padding = (playerHeight - playerWidth / canvasRatio) / 2; - player.$container.style.padding = `${padding}px 0`; - } - } - - } - - var css_248z = ".jessibuca-container{position:relative;display:block;width:100%;height:100%;overflow:hidden}.jessibuca-container.jessibuca-fullscreen-web{position:fixed;z-index:9999;left:0;top:0;right:0;bottom:0;width:100vw!important;height:100vh!important;background:#000}\n/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0eWxlLnNjc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEscUJBQ0UsaUJBQWtCLENBQ2xCLGFBQWMsQ0FDZCxVQUFXLENBQ1gsV0FBWSxDQUNaLGVBQWtCLENBQ2xCLDhDQUNFLGNBQWUsQ0FDZixZQUFhLENBQ2IsTUFBTyxDQUNQLEtBQU0sQ0FDTixPQUFRLENBQ1IsUUFBUyxDQUNULHFCQUF1QixDQUN2QixzQkFBd0IsQ0FDeEIsZUFBa0IiLCJmaWxlIjoic3R5bGUuc2NzcyIsInNvdXJjZXNDb250ZW50IjpbIi5qZXNzaWJ1Y2EtY29udGFpbmVyIHtcbiAgcG9zaXRpb246IHJlbGF0aXZlO1xuICBkaXNwbGF5OiBibG9jaztcbiAgd2lkdGg6IDEwMCU7XG4gIGhlaWdodDogMTAwJTtcbiAgb3ZlcmZsb3c6IGhpZGRlbjsgfVxuICAuamVzc2lidWNhLWNvbnRhaW5lci5qZXNzaWJ1Y2EtZnVsbHNjcmVlbi13ZWIge1xuICAgIHBvc2l0aW9uOiBmaXhlZDtcbiAgICB6LWluZGV4OiA5OTk5O1xuICAgIGxlZnQ6IDA7XG4gICAgdG9wOiAwO1xuICAgIHJpZ2h0OiAwO1xuICAgIGJvdHRvbTogMDtcbiAgICB3aWR0aDogMTAwdncgIWltcG9ydGFudDtcbiAgICBoZWlnaHQ6IDEwMHZoICFpbXBvcnRhbnQ7XG4gICAgYmFja2dyb3VuZDogIzAwMDsgfVxuIl19 */"; - styleInject(css_248z); - - var observer = (player => { - const { - _opt, - debug, - events: { - proxy - } - } = player; - - if (_opt.supportDblclickFullscreen) { - proxy(player.$container, 'dblclick', e => { - const target = getTarget(e); - const nodeName = target.nodeName.toLowerCase(); - - if (nodeName === 'canvas' || nodeName === 'video') { - player.fullscreen = !player.fullscreen; - } - }); - } // - - - proxy(document, 'visibilitychange', () => { - if (_opt.hiddenAutoPause) { - debug.log('visibilitychange', document.visibilityState, player._isPlayingBeforePageHidden); - - if ("visible" === document.visibilityState) { - if (player._isPlayingBeforePageHidden) { - player.play(); - } - } else { - player._isPlayingBeforePageHidden = player.playing; // hidden - - if (player.playing) { - player.pause(); - } - } - } - }); - proxy(window, 'fullscreenchange', () => { - // - if (player.keepScreenOn !== null && "visible" === document.visibilityState) { - player.enableWakeLock(); - } - }); - }); - - class MP4$1 { - static init() { - MP4$1.types = { - avc1: [], - avcC: [], - hvc1: [], - hvcC: [], - 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: [] - }; - - for (let name in MP4$1.types) { - if (MP4$1.types.hasOwnProperty(name)) { - MP4$1.types[name] = [name.charCodeAt(0), name.charCodeAt(1), name.charCodeAt(2), name.charCodeAt(3)]; - } - } - - let constants = MP4$1.constants = {}; - constants.FTYP = new Uint8Array([0x69, 0x73, 0x6F, 0x6D, // major_brand: isom - 0x0, 0x0, 0x0, 0x1, // minor_version: 0x01 - 0x69, 0x73, 0x6F, 0x6D, // isom - 0x61, 0x76, 0x63, 0x31 // avc1 - ]); - constants.STSD_PREFIX = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags - 0x00, 0x00, 0x00, 0x01 // entry_count - ]); - constants.STTS = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags - 0x00, 0x00, 0x00, 0x00 // entry_count - ]); - constants.STSC = constants.STCO = constants.STTS; - constants.STSZ = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags - 0x00, 0x00, 0x00, 0x00, // sample_size - 0x00, 0x00, 0x00, 0x00 // sample_count - ]); - constants.HDLR_VIDEO = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags - 0x00, 0x00, 0x00, 0x00, // pre_defined - 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide' - 0x00, 0x00, 0x00, 0x00, // reserved: 3 * 4 bytes - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x69, 0x64, 0x65, 0x6F, 0x48, 0x61, 0x6E, 0x64, 0x6C, 0x65, 0x72, 0x00 // name: VideoHandler - ]); - constants.HDLR_AUDIO = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags - 0x00, 0x00, 0x00, 0x00, // pre_defined - 0x73, 0x6F, 0x75, 0x6E, // handler_type: 'soun' - 0x00, 0x00, 0x00, 0x00, // reserved: 3 * 4 bytes - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x6F, 0x75, 0x6E, 0x64, 0x48, 0x61, 0x6E, 0x64, 0x6C, 0x65, 0x72, 0x00 // name: SoundHandler - ]); - constants.DREF = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags - 0x00, 0x00, 0x00, 0x01, // entry_count - 0x00, 0x00, 0x00, 0x0C, // entry_size - 0x75, 0x72, 0x6C, 0x20, // type 'url ' - 0x00, 0x00, 0x00, 0x01 // version(0) + flags - ]); // Sound media header - - constants.SMHD = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags - 0x00, 0x00, 0x00, 0x00 // balance(2) + reserved(2) - ]); // video media header - - constants.VMHD = new Uint8Array([0x00, 0x00, 0x00, 0x01, // version(0) + flags - 0x00, 0x00, // graphicsmode: 2 bytes - 0x00, 0x00, 0x00, 0x00, // opcolor: 3 * 2 bytes - 0x00, 0x00]); - } // Generate a box - - - static box(type) { - let size = 8; - let result = null; - let datas = Array.prototype.slice.call(arguments, 1); - let arrayCount = datas.length; - - for (let i = 0; i < arrayCount; i++) { - size += datas[i].byteLength; - } - - result = new Uint8Array(size); - result[0] = size >>> 24 & 0xFF; // size - - result[1] = size >>> 16 & 0xFF; - result[2] = size >>> 8 & 0xFF; - result[3] = size & 0xFF; - result.set(type, 4); // type - - let offset = 8; - - for (let i = 0; i < arrayCount; i++) { - // data body - result.set(datas[i], offset); - offset += datas[i].byteLength; - } - - return result; - } // emit ftyp & moov - - - static generateInitSegment(meta) { - let ftyp = MP4$1.box(MP4$1.types.ftyp, MP4$1.constants.FTYP); - let moov = MP4$1.moov(meta); - let result = new Uint8Array(ftyp.byteLength + moov.byteLength); - result.set(ftyp, 0); - result.set(moov, ftyp.byteLength); - return result; - } // Movie metadata box - - - static moov(meta) { - let mvhd = MP4$1.mvhd(meta.timescale, meta.duration); - let trak = MP4$1.trak(meta); - let mvex = MP4$1.mvex(meta); - return MP4$1.box(MP4$1.types.moov, mvhd, trak, mvex); - } // Movie header box - - - static mvhd(timescale, duration) { - return MP4$1.box(MP4$1.types.mvhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags - 0x00, 0x00, 0x00, 0x00, // creation_time - 0x00, 0x00, 0x00, 0x00, // modification_time - timescale >>> 24 & 0xFF, // timescale: 4 bytes - timescale >>> 16 & 0xFF, timescale >>> 8 & 0xFF, timescale & 0xFF, duration >>> 24 & 0xFF, // duration: 4 bytes - duration >>> 16 & 0xFF, duration >>> 8 & 0xFF, duration & 0xFF, 0x00, 0x01, 0x00, 0x00, // Preferred rate: 1.0 - 0x01, 0x00, 0x00, 0x00, // PreferredVolume(1.0, 2bytes) + reserved(2bytes) - 0x00, 0x00, 0x00, 0x00, // reserved: 4 + 4 bytes - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // ----begin composition matrix---- - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // ----end composition matrix---- - 0x00, 0x00, 0x00, 0x00, // ----begin pre_defined 6 * 4 bytes---- - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ----end pre_defined 6 * 4 bytes---- - 0xFF, 0xFF, 0xFF, 0xFF // next_track_ID - ])); - } // Track box - - - static trak(meta) { - return MP4$1.box(MP4$1.types.trak, MP4$1.tkhd(meta), MP4$1.mdia(meta)); - } // Track header box - - - static tkhd(meta) { - let trackId = meta.id, - duration = meta.duration; - let width = meta.presentWidth, - height = meta.presentHeight; - return MP4$1.box(MP4$1.types.tkhd, new Uint8Array([0x00, 0x00, 0x00, 0x07, // version(0) + flags - 0x00, 0x00, 0x00, 0x00, // creation_time - 0x00, 0x00, 0x00, 0x00, // modification_time - trackId >>> 24 & 0xFF, // track_ID: 4 bytes - trackId >>> 16 & 0xFF, trackId >>> 8 & 0xFF, trackId & 0xFF, 0x00, 0x00, 0x00, 0x00, // reserved: 4 bytes - duration >>> 24 & 0xFF, // duration: 4 bytes - duration >>> 16 & 0xFF, duration >>> 8 & 0xFF, duration & 0xFF, 0x00, 0x00, 0x00, 0x00, // reserved: 2 * 4 bytes - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // layer(2bytes) + alternate_group(2bytes) - 0x00, 0x00, 0x00, 0x00, // volume(2bytes) + reserved(2bytes) - 0x00, 0x01, 0x00, 0x00, // ----begin composition matrix---- - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // ----end composition matrix---- - width >>> 8 & 0xFF, // width and height - width & 0xFF, 0x00, 0x00, height >>> 8 & 0xFF, height & 0xFF, 0x00, 0x00])); - } - - static mdia(meta) { - return MP4$1.box(MP4$1.types.mdia, MP4$1.mdhd(meta), MP4$1.hdlr(meta), MP4$1.minf(meta)); - } // Media header box - - - static mdhd(meta) { - let timescale = meta.timescale; - let duration = meta.duration; - return MP4$1.box(MP4$1.types.mdhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags - 0x00, 0x00, 0x00, 0x00, // creation_time - 0x00, 0x00, 0x00, 0x00, // modification_time - timescale >>> 24 & 0xFF, // timescale: 4 bytes - timescale >>> 16 & 0xFF, timescale >>> 8 & 0xFF, timescale & 0xFF, duration >>> 24 & 0xFF, // duration: 4 bytes - duration >>> 16 & 0xFF, duration >>> 8 & 0xFF, duration & 0xFF, 0x55, 0xC4, // language: und (undetermined) - 0x00, 0x00 // pre_defined = 0 - ])); - } // Media handler reference box - - - static hdlr(meta) { - let data = null; - - if (meta.type === 'audio') { - data = MP4$1.constants.HDLR_AUDIO; - } else { - data = MP4$1.constants.HDLR_VIDEO; - } - - return MP4$1.box(MP4$1.types.hdlr, data); - } // Media infomation box - - - static minf(meta) { - let xmhd = null; - - if (meta.type === 'audio') { - xmhd = MP4$1.box(MP4$1.types.smhd, MP4$1.constants.SMHD); - } else { - xmhd = MP4$1.box(MP4$1.types.vmhd, MP4$1.constants.VMHD); - } - - return MP4$1.box(MP4$1.types.minf, xmhd, MP4$1.dinf(), MP4$1.stbl(meta)); - } // Data infomation box - - - static dinf() { - let result = MP4$1.box(MP4$1.types.dinf, MP4$1.box(MP4$1.types.dref, MP4$1.constants.DREF)); - return result; - } // Sample table box - - - static stbl(meta) { - let result = MP4$1.box(MP4$1.types.stbl, // type: stbl - MP4$1.stsd(meta), // Sample Description Table - MP4$1.box(MP4$1.types.stts, MP4$1.constants.STTS), // Time-To-Sample - MP4$1.box(MP4$1.types.stsc, MP4$1.constants.STSC), // Sample-To-Chunk - MP4$1.box(MP4$1.types.stsz, MP4$1.constants.STSZ), // Sample size - MP4$1.box(MP4$1.types.stco, MP4$1.constants.STCO) // Chunk offset - ); - return result; - } // Sample description box - - - static stsd(meta) { - if (meta.type === 'audio') { - // else: aac -> mp4a - return MP4$1.box(MP4$1.types.stsd, MP4$1.constants.STSD_PREFIX, MP4$1.mp4a(meta)); - } else { - if (meta.videoType === 'avc') { - // - return MP4$1.box(MP4$1.types.stsd, MP4$1.constants.STSD_PREFIX, MP4$1.avc1(meta)); - } else { - // - return MP4$1.box(MP4$1.types.stsd, MP4$1.constants.STSD_PREFIX, MP4$1.hvc1(meta)); - } - } - } - - static mp4a(meta) { - let channelCount = meta.channelCount; - let sampleRate = meta.audioSampleRate; - let data = new Uint8Array([0x00, 0x00, 0x00, 0x00, // reserved(4) - 0x00, 0x00, 0x00, 0x01, // reserved(2) + data_reference_index(2) - 0x00, 0x00, 0x00, 0x00, // reserved: 2 * 4 bytes - 0x00, 0x00, 0x00, 0x00, 0x00, channelCount, // channelCount(2) - 0x00, 0x10, // sampleSize(2) - 0x00, 0x00, 0x00, 0x00, // reserved(4) - sampleRate >>> 8 & 0xFF, // Audio sample rate - sampleRate & 0xFF, 0x00, 0x00]); - return MP4$1.box(MP4$1.types.mp4a, data, MP4$1.esds(meta)); - } - - static esds(meta) { - let config = meta.config || []; - let configSize = config.length; - let data = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version 0 + flags - 0x03, // descriptor_type - 0x17 + configSize, // length3 - 0x00, 0x01, // es_id - 0x00, // stream_priority - 0x04, // descriptor_type - 0x0F + configSize, // length - 0x40, // codec: mpeg4_audio - 0x15, // stream_type: Audio - 0x00, 0x00, 0x00, // buffer_size - 0x00, 0x00, 0x00, 0x00, // maxBitrate - 0x00, 0x00, 0x00, 0x00, // avgBitrate - 0x05 // descriptor_type - ].concat([configSize]).concat(config).concat([0x06, 0x01, 0x02 // GASpecificConfig - ])); - return MP4$1.box(MP4$1.types.esds, data); - } // avc - - - static avc1(meta) { - let avcc = meta.avcc; - const width = meta.codecWidth; - const height = meta.codecHeight; - let data = 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, width >>> 8 & 255, width & 255, height >>> 8 & 255, height & 255, 0, 72, 0, 0, 0, 72, 0, 0, 0, 0, 0, 0, 0, 1, 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, 0, 0, 0, 0, 0, 0, 24, 255, 255]); - return MP4$1.box(MP4$1.types.avc1, data, MP4$1.box(MP4$1.types.avcC, avcc)); - } // hvc - - - static hvc1(meta) { - let avcc = meta.avcc; - const width = meta.codecWidth; - const height = meta.codecHeight; - let data = 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, width >>> 8 & 255, width & 255, height >>> 8 & 255, height & 255, 0, 72, 0, 0, 0, 72, 0, 0, 0, 0, 0, 0, 0, 1, 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, 0, 0, 0, 0, 0, 0, 24, 255, 255]); - return MP4$1.box(MP4$1.types.hvc1, data, MP4$1.box(MP4$1.types.hvcC, avcc)); - } // Movie Extends box - - - static mvex(meta) { - return MP4$1.box(MP4$1.types.mvex, MP4$1.trex(meta)); - } // Track Extends box - - - static trex(meta) { - let trackId = meta.id; - let data = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags - trackId >>> 24 & 0xFF, // track_ID - trackId >>> 16 & 0xFF, trackId >>> 8 & 0xFF, trackId & 0xFF, 0x00, 0x00, 0x00, 0x01, // default_sample_description_index - 0x00, 0x00, 0x00, 0x00, // default_sample_duration - 0x00, 0x00, 0x00, 0x00, // default_sample_size - 0x00, 0x01, 0x00, 0x01 // default_sample_flags - ]); - return MP4$1.box(MP4$1.types.trex, data); - } // Movie fragment box - - - static moof(track, baseMediaDecodeTime) { - return MP4$1.box(MP4$1.types.moof, MP4$1.mfhd(track.sequenceNumber), MP4$1.traf(track, baseMediaDecodeTime)); - } // - - - static mfhd(sequenceNumber) { - let data = new Uint8Array([0x00, 0x00, 0x00, 0x00, sequenceNumber >>> 24 & 0xFF, // sequence_number: int32 - sequenceNumber >>> 16 & 0xFF, sequenceNumber >>> 8 & 0xFF, sequenceNumber & 0xFF]); - return MP4$1.box(MP4$1.types.mfhd, data); - } // Track fragment box - - - static traf(track, baseMediaDecodeTime) { - let trackId = track.id; // Track fragment header box - - let tfhd = MP4$1.box(MP4$1.types.tfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) & flags - trackId >>> 24 & 0xFF, // track_ID - trackId >>> 16 & 0xFF, trackId >>> 8 & 0xFF, trackId & 0xFF])); // Track Fragment Decode Time - - let tfdt = MP4$1.box(MP4$1.types.tfdt, new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) & flags - baseMediaDecodeTime >>> 24 & 0xFF, // baseMediaDecodeTime: int32 - baseMediaDecodeTime >>> 16 & 0xFF, baseMediaDecodeTime >>> 8 & 0xFF, baseMediaDecodeTime & 0xFF])); - let sdtp = MP4$1.sdtp(track); - let trun = MP4$1.trun(track, sdtp.byteLength + 16 + 16 + 8 + 16 + 8 + 8); - return MP4$1.box(MP4$1.types.traf, tfhd, tfdt, trun, sdtp); - } // Sample Dependency Type box - - - static sdtp(track) { - let data = new Uint8Array(4 + 1); - let flags = track.flags; - data[4] = flags.isLeading << 6 | flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy; - return MP4$1.box(MP4$1.types.sdtp, data); - } // trun - - - static trun(track, offset) { - let dataSize = 12 + 16; - let data = new Uint8Array(dataSize); - offset += 8 + dataSize; - data.set([0x00, 0x00, 0x0F, 0x01, // version(0) & flags - 0x00, 0x00, 0x00, 0x01, // sample_count - offset >>> 24 & 0xFF, // data_offset - offset >>> 16 & 0xFF, offset >>> 8 & 0xFF, offset & 0xFF], 0); - let duration = track.duration; - let size = track.size; - let flags = track.flags; - let cts = track.cts; - data.set([duration >>> 24 & 0xFF, // sample_duration - duration >>> 16 & 0xFF, duration >>> 8 & 0xFF, duration & 0xFF, size >>> 24 & 0xFF, // sample_size - size >>> 16 & 0xFF, size >>> 8 & 0xFF, size & 0xFF, flags.isLeading << 2 | flags.dependsOn, // sample_flags - flags.isDependedOn << 6 | flags.hasRedundancy << 4 | flags.isNonSync, 0x00, 0x00, // sample_degradation_priority - cts >>> 24 & 0xFF, // sample_composition_time_offset - cts >>> 16 & 0xFF, cts >>> 8 & 0xFF, cts & 0xFF], 12); - return MP4$1.box(MP4$1.types.trun, data); - } // mdat - - - static mdat(data) { - return MP4$1.box(MP4$1.types.mdat, data); - } - - } - - MP4$1.init(); - - class MseDecoder extends Emitter { - constructor(player) { - super(); - this.player = player; - this.isAvc = true; - this.mediaSource = new window.MediaSource(); - this.sourceBuffer = null; - this.hasInit = false; - this.isInitInfo = false; - this.cacheTrack = {}; - this.timeInit = false; - this.sequenceNumber = 0; - this.mediaSourceOpen = false; - this.dropping = false; - this.firstRenderTime = null; - this.mediaSourceAppendBufferError = false; - this.mediaSourceAppendBufferFull = false; - this.isDecodeFirstIIframe = false; - this.player.video.$videoElement.src = window.URL.createObjectURL(this.mediaSource); - const { - debug, - events: { - proxy - } - } = player; - proxy(this.mediaSource, 'sourceopen', () => { - this.mediaSourceOpen = true; - this.player.emit(EVENTS.mseSourceOpen); - }); - proxy(this.mediaSource, 'sourceclose', () => { - this.player.emit(EVENTS.mseSourceClose); - }); - player.debug.log('MediaSource', 'init'); - } - - destroy() { - this.stop(); - this.mediaSource = null; - this.mediaSourceOpen = false; - this.sourceBuffer = null; - this.hasInit = false; - this.isInitInfo = false; - this.sequenceNumber = 0; - this.cacheTrack = null; - this.timeInit = false; - this.mediaSourceAppendBufferError = false; - this.mediaSourceAppendBufferFull = false; - this.isDecodeFirstIIframe = false; - this.off(); - this.player.debug.log('MediaSource', 'destroy'); - } - - get state() { - return this.mediaSource && this.mediaSource.readyState; - } - - get isStateOpen() { - return this.state === MEDIA_SOURCE_STATE.open; - } - - get isStateClosed() { - return this.state === MEDIA_SOURCE_STATE.closed; - } - - get isStateEnded() { - return this.state === MEDIA_SOURCE_STATE.ended; - } - - get duration() { - return this.mediaSource && this.mediaSource.duration; - } - - set duration(duration) { - this.mediaSource.duration = duration; - } - - decodeVideo(payload, ts, isIframe, cts) { - const player = this.player; - - if (!player) { - return; - } - - if (!this.hasInit) { - if (isIframe && payload[1] === 0) { - const videoCodec = payload[0] & 0x0F; - player.video.updateVideoInfo({ - encTypeCode: videoCodec - }); // 如果解码出来的是 - - if (videoCodec === VIDEO_ENC_CODE.h265) { - this.emit(EVENTS_ERROR.mediaSourceH265NotSupport); - return; - } - - if (!player._times.decodeStart) { - player._times.decodeStart = now(); - } - - this._decodeConfigurationRecord(payload, ts, isIframe, videoCodec); - - this.hasInit = true; - } - } else { - if (isIframe && payload[1] === 0) { - let config = parseAVCDecoderConfigurationRecord(payload.slice(5)); - const videoInfo = this.player.video.videoInfo; - - if (videoInfo && videoInfo.width && videoInfo.height && config && config.codecWidth && config.codecHeight && (config.codecWidth !== videoInfo.width || config.codecHeight !== videoInfo.height)) { - this.player.debug.warn('MediaSource', `width or height is update, width ${videoInfo.width}-> ${config.codecWidth}, height ${videoInfo.height}-> ${config.codecHeight}`); - this.isInitInfo = false; - this.player.video.init = false; - } - } - - if (!this.isDecodeFirstIIframe && isIframe) { - this.isDecodeFirstIIframe = true; - } - - if (this.isDecodeFirstIIframe) { - if (this.firstRenderTime === null) { - this.firstRenderTime = ts; - } - - const dts = ts - this.firstRenderTime; - - this._decodeVideo(payload, dts, isIframe, cts); - } else { - this.player.debug.warn('MediaSource', 'decodeVideo isDecodeFirstIIframe false'); - } - } - } - - _decodeConfigurationRecord(payload, ts, isIframe, videoCodec) { - let data = payload.slice(5); - let config = {}; - config = parseAVCDecoderConfigurationRecord(data); - const metaData = { - id: 1, - // video tag data - type: 'video', - timescale: 1000, - duration: 0, - avcc: data, - codecWidth: config.codecWidth, - codecHeight: config.codecHeight, - videoType: config.videoType - }; // ftyp - - const metaBox = MP4$1.generateInitSegment(metaData); - this.isAvc = true; - this.appendBuffer(metaBox.buffer); - this.sequenceNumber = 0; - this.cacheTrack = null; - this.timeInit = false; - } // - - - _decodeVideo(payload, dts, isIframe, cts) { - const player = this.player; - let arrayBuffer = payload.slice(5); - let bytes = arrayBuffer.byteLength; // player.debug.log('MediaSource', '_decodeVideo', ts); - - const $video = player.video.$videoElement; - const videoBufferDelay = player._opt.videoBufferDelay; - - if ($video.buffered.length > 1) { - this.removeBuffer($video.buffered.start(0), $video.buffered.end(0)); - this.timeInit = false; - } - - if (this.dropping && dts - this.cacheTrack.dts > videoBufferDelay) { - this.dropping = false; - this.cacheTrack = {}; - } else if (this.cacheTrack && dts >= this.cacheTrack.dts) { - // 需要额外加8个size - let mdatBytes = 8 + this.cacheTrack.size; - let mdatbox = new Uint8Array(mdatBytes); - mdatbox[0] = mdatBytes >>> 24 & 255; - mdatbox[1] = mdatBytes >>> 16 & 255; - mdatbox[2] = mdatBytes >>> 8 & 255; - mdatbox[3] = mdatBytes & 255; - mdatbox.set(MP4$1.types.mdat, 4); - mdatbox.set(this.cacheTrack.data, 8); - this.cacheTrack.duration = dts - this.cacheTrack.dts; // moof - - let moofbox = MP4$1.moof(this.cacheTrack, this.cacheTrack.dts); - let result = new Uint8Array(moofbox.byteLength + mdatbox.byteLength); - result.set(moofbox, 0); - result.set(mdatbox, moofbox.byteLength); // appendBuffer - - this.appendBuffer(result.buffer); - player.handleRender(); - player.updateStats({ - fps: true, - ts: dts, - buf: player.demux && player.demux.delay || 0 - }); - - if (!player._times.videoStart) { - player._times.videoStart = now(); - player.handlePlayToRenderTimes(); - } - } else { - player.debug.log('MediaSource', 'timeInit set false , cacheTrack = {}'); - this.timeInit = false; - this.cacheTrack = {}; - } - - if (!this.cacheTrack) { - this.cacheTrack = {}; - } - - this.cacheTrack.id = 1; - this.cacheTrack.sequenceNumber = ++this.sequenceNumber; - this.cacheTrack.size = bytes; - this.cacheTrack.dts = dts; - this.cacheTrack.cts = cts; - this.cacheTrack.isKeyframe = isIframe; - this.cacheTrack.data = arrayBuffer; // - - this.cacheTrack.flags = { - isLeading: 0, - dependsOn: isIframe ? 2 : 1, - isDependedOn: isIframe ? 1 : 0, - hasRedundancy: 0, - isNonSync: isIframe ? 0 : 1 - }; // - - if (!this.timeInit && $video.buffered.length === 1) { - player.debug.log('MediaSource', 'timeInit set true'); - this.timeInit = true; - $video.currentTime = $video.buffered.end(0); - } - - if (!this.isInitInfo && $video.videoWidth > 0 && $video.videoHeight > 0) { - player.debug.log('MediaSource', `updateVideoInfo: ${$video.videoWidth},${$video.videoHeight}`); - player.video.updateVideoInfo({ - width: $video.videoWidth, - height: $video.videoHeight - }); - player.video.initCanvasViewSize(); - this.isInitInfo = true; - } - } - - appendBuffer(buffer) { - const { - debug, - events: { - proxy - } - } = this.player; - - if (this.sourceBuffer === null) { - this.sourceBuffer = this.mediaSource.addSourceBuffer(MP4_CODECS.avc); - proxy(this.sourceBuffer, 'error', error => { - this.player.emit(EVENTS.mseSourceBufferError, error); // this.dropSourceBuffer(false) - }); - } - - if (this.mediaSourceAppendBufferError) { - debug.error('MediaSource', `this.mediaSourceAppendBufferError is true`); - return; - } - - if (this.mediaSourceAppendBufferFull) { - debug.error('MediaSource', `this.mediaSourceAppendBufferFull is true`); - return; - } - - if (this.sourceBuffer.updating === false && this.isStateOpen) { - try { - this.sourceBuffer.appendBuffer(buffer); - } catch (e) { - debug.warn('MediaSource', 'this.sourceBuffer.appendBuffer()', e.code, e); - - if (e.code === 22) { - // QuotaExceededError - // The SourceBuffer is full, and cannot free space to append additional buffers - this.stop(); - this.mediaSourceAppendBufferFull = true; - this.emit(EVENTS_ERROR.mediaSourceFull); - } else if (e.code === 11) { - // Failed to execute 'appendBuffer' on 'SourceBuffer': The HTMLMediaElement.error attribute is not null. - this.stop(); - this.mediaSourceAppendBufferError = true; - this.emit(EVENTS_ERROR.mediaSourceAppendBufferError); - } else { - debug.error('MediaSource', 'appendBuffer error', e); - this.player.emit(EVENTS.mseSourceBufferError, e); - } - } - - return; - } - - if (this.isStateClosed) { - this.player.emitError(EVENTS_ERROR.mseSourceBufferError, 'mediaSource is not attached to video or mediaSource is closed'); - } else if (this.isStateEnded) { - this.player.emitError(EVENTS_ERROR.mseSourceBufferError, 'mediaSource is closed'); - } else { - if (this.sourceBuffer.updating === true) { - this.player.emit(EVENTS.mseSourceBufferBusy); // this.dropSourceBuffer(true); - } - } - } - - stop() { - this.abortSourceBuffer(); - this.removeSourceBuffer(); - this.endOfStream(); - } - - dropSourceBuffer(isDropping) { - const $video = this.player.video.$videoElement; - this.dropping = isDropping; - - if ($video.buffered.length > 0) { - if ($video.buffered.end(0) - $video.currentTime > 1) { - this.player.debug.warn('MediaSource', 'dropSourceBuffer', `$video.buffered.end(0) is ${$video.buffered.end(0)} - $video.currentTime ${$video.currentTime}`); - $video.currentTime = $video.buffered.end(0); - } - } - } - - removeBuffer(start, end) { - if (this.isStateOpen && this.sourceBuffer.updating === false) { - try { - this.sourceBuffer.remove(start, end); - } catch (e) { - this.player.debug.warn('MediaSource', 'removeBuffer() error', e); - } - } else { - this.player.debug.warn('MediaSource', 'removeBuffer() this.isStateOpen is', this.isStateOpen, 'this.sourceBuffer.updating', this.sourceBuffer.updating); - } - } - - endOfStream() { - // fix: MediaSource endOfStream before demuxer initialization completes (before HAVE_METADATA) is treated as an error - const $videoElement = this.player.video && this.player.video.$videoElement; - - if (this.isStateOpen && $videoElement && $videoElement.readyState >= 1) { - try { - this.mediaSource.endOfStream(); - } catch (e) { - this.player.debug.warn('MediaSource', 'endOfStream() error', e); - } - } - } - - abortSourceBuffer() { - if (this.isStateOpen) { - if (this.sourceBuffer) { - this.sourceBuffer.abort(); - this.sourceBuffer = null; - } - } - } - - removeSourceBuffer() { - if (!this.isStateClosed) { - if (this.mediaSource && this.sourceBuffer) { - try { - this.mediaSource.removeSourceBuffer(this.sourceBuffer); - } catch (e) { - this.player.debug.warn('MediaSource', 'removeSourceBuffer() error', e); - } - } - } - } - - getSourceBufferUpdating() { - return this.sourceBuffer && this.sourceBuffer.updating; - } - - } - - // tks: https://github.com/richtr/NoSleep.js - const WEBM = "data:video/webm;base64,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"; - const MP4 = "data:video/mp4;base64,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"; // Detect iOS browsers < version 10 - - const oldIOS = () => typeof navigator !== "undefined" && parseFloat(("" + (/CPU.*OS ([0-9_]{3,4})[0-9_]{0,1}|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0, ""])[1]).replace("undefined", "3_2").replace("_", ".").replace("_", "")) < 10 && !window.MSStream; // Detect native Wake Lock API support - - - const nativeWakeLock = () => "wakeLock" in navigator; - - class NoSleep { - constructor(player) { - this.player = player; - this.enabled = false; - - if (nativeWakeLock()) { - this._wakeLock = null; - - const handleVisibilityChange = () => { - if (this._wakeLock !== null && document.visibilityState === "visible") { - this.enable(); - } - }; - - document.addEventListener("visibilitychange", handleVisibilityChange); - document.addEventListener("fullscreenchange", handleVisibilityChange); - } else if (oldIOS()) { - this.noSleepTimer = null; - } else { - // Set up no sleep video element - this.noSleepVideo = document.createElement("video"); - this.noSleepVideo.setAttribute("title", "No Sleep"); - this.noSleepVideo.setAttribute("playsinline", ""); - - this._addSourceToVideo(this.noSleepVideo, "webm", WEBM); - - this._addSourceToVideo(this.noSleepVideo, "mp4", MP4); - - this.noSleepVideo.addEventListener("loadedmetadata", () => { - if (this.noSleepVideo.duration <= 1) { - // webm source - this.noSleepVideo.setAttribute("loop", ""); - } else { - // mp4 source - this.noSleepVideo.addEventListener("timeupdate", () => { - if (this.noSleepVideo.currentTime > 0.5) { - this.noSleepVideo.currentTime = Math.random(); - } - }); - } - }); - } - } - - _addSourceToVideo(element, type, dataURI) { - var source = document.createElement("source"); - source.src = dataURI; - source.type = `video/${type}`; - element.appendChild(source); - } - - get isEnabled() { - return this.enabled; - } - - enable() { - const debug = this.player.debug; - - if (nativeWakeLock()) { - return navigator.wakeLock.request("screen").then(wakeLock => { - this._wakeLock = wakeLock; - this.enabled = true; - debug.log('wakeLock', 'Wake Lock active.'); - - this._wakeLock.addEventListener("release", () => { - // ToDo: Potentially emit an event for the page to observe since - // Wake Lock releases happen when page visibility changes. - // (https://web.dev/wakelock/#wake-lock-lifecycle) - debug.log('wakeLock', 'Wake Lock released.'); - }); - }).catch(err => { - this.enabled = false; - debug.error('wakeLock', `${err.name}, ${err.message}`); - throw err; - }); - } else if (oldIOS()) { - this.disable(); - this.noSleepTimer = window.setInterval(() => { - if (!document.hidden) { - window.location.href = window.location.href.split("#")[0]; - window.setTimeout(window.stop, 0); - } - }, 15000); - this.enabled = true; - return Promise.resolve(); - } else { - let playPromise = this.noSleepVideo.play(); - return playPromise.then(res => { - this.enabled = true; - return res; - }).catch(err => { - this.enabled = false; - throw err; - }); - } - } - - disable() { - const debug = this.player.debug; - - if (nativeWakeLock()) { - if (this._wakeLock) { - this._wakeLock.release(); - } - - this._wakeLock = null; - } else if (oldIOS()) { - if (this.noSleepTimer) { - debug.warn('wakeLock', 'NoSleep now disabled for older iOS devices.'); - window.clearInterval(this.noSleepTimer); - this.noSleepTimer = null; - } - } else { - this.noSleepVideo.pause(); - } - - this.enabled = false; - } - - } - - class Player extends Emitter { - constructor(container, options) { - super(); - this.$container = container; - this._opt = Object.assign({}, DEFAULT_PLAYER_OPTIONS, options); - this.debug = new Debug(this); // - - if (this._opt.useWCS) { - this._opt.useWCS = supportWCS(); - } // - - - if (this._opt.useMSE) { - this._opt.useMSE = supportMSE(); - } // - - - if (this._opt.wcsUseVideoRender) { - this._opt.wcsUseVideoRender = supportMediaStreamTrack(); - } // 如果使用mse则强制不允许 webcodecs - - - if (this._opt.useMSE) { - if (this._opt.useWCS) { - this.debug.log('Player', 'useWCS set true->false'); - } - - if (!this._opt.forceNoOffscreen) { - this.debug.log('Player', 'forceNoOffscreen set false->true'); - } - - this._opt.useWCS = false; - this._opt.forceNoOffscreen = true; - } - - if (!this._opt.forceNoOffscreen) { - if (!supportOffscreenV2()) { - this._opt.forceNoOffscreen = true; - this._opt.useOffscreen = false; - } else { - this._opt.useOffscreen = true; - } - } - - if (!this._opt.hasAudio) { - this._opt.operateBtns.audio = false; - } - - this._opt.hasControl = this._hasControl(); // - - this._loading = false; - this._playing = false; - this._hasLoaded = false; // - - this._checkHeartTimeout = null; - this._checkLoadingTimeout = null; - this._checkStatsInterval = null; // - - this._startBpsTime = null; - this._isPlayingBeforePageHidden = false; - this._stats = { - buf: 0, - // 当前缓冲区时长,单位毫秒, - fps: 0, - // 当前视频帧率 - abps: 0, - // 当前音频码率,单位bit - vbps: 0, - // 当前视频码率,单位bit - ts: 0 // 当前视频帧pts,单位毫秒 - - }; // 各个步骤的时间统计 - - this._times = initPlayTimes(); // - - this._videoTimestamp = 0; - this._audioTimestamp = 0; - property$1(this); - this.events = new Events(this); - this.video = new Video(this); - - if (this._opt.hasAudio) { - this.audio = new Audio(this); - } - - this.recorder = new Recorder(this); - - if (!this._onlyMseOrWcsVideo()) { - this.decoderWorker = new DecoderWorker(this); - } else { - this.loaded = true; - } - - this.stream = null; - this.demux = null; - this._lastVolume = null; - - if (this._opt.useWCS) { - this.webcodecsDecoder = new WebcodecsDecoder(this); - this.loaded = true; - } - - if (this._opt.useMSE) { - this.mseDecoder = new MseDecoder(this); - this.loaded = true; - } // - - - this.control = new Control(this); - - if (isMobile()) { - this.keepScreenOn = new NoSleep(this); - } - - events$1(this); - observer(this); - - if (this._opt.useWCS) { - this.debug.log('Player', 'use WCS'); - } - - if (this._opt.useMSE) { - this.debug.log('Player', 'use MSE'); - } - - if (this._opt.useOffscreen) { - this.debug.log('Player', 'use offscreen'); - } - - this.debug.log('Player options', this._opt); - } - - destroy() { - this._loading = false; - this._playing = false; - this._hasLoaded = false; - this._lastVolume = null; - this._times = initPlayTimes(); - - if (this.decoderWorker) { - this.decoderWorker.destroy(); - this.decoderWorker = null; - } - - if (this.video) { - this.video.destroy(); - this.video = null; - } - - if (this.audio) { - this.audio.destroy(); - this.audio = null; - } - - if (this.stream) { - this.stream.destroy(); - this.stream = null; - } - - if (this.recorder) { - this.recorder.destroy(); - this.recorder = null; - } - - if (this.control) { - this.control.destroy(); - this.control = null; - } - - if (this.webcodecsDecoder) { - this.webcodecsDecoder.destroy(); - this.webcodecsDecoder = null; - } - - if (this.mseDecoder) { - this.mseDecoder.destroy(); - this.mseDecoder = null; - } - - if (this.demux) { - this.demux.destroy(); - this.demux = null; - } - - if (this.events) { - this.events.destroy(); - this.events = null; - } - - this.clearCheckHeartTimeout(); - this.clearCheckLoadingTimeout(); - this.clearStatsInterval(); // - - this.releaseWakeLock(); - this.keepScreenOn = null; // reset stats - - this.resetStats(); - this._audioTimestamp = 0; - this._videoTimestamp = 0; // 其他没法解耦的,通过 destroy 方式 - - this.emit('destroy'); // 接触所有绑定事件 - - this.off(); - this.debug.log('play', 'destroy end'); - } - - set fullscreen(value) { - if (isMobile() && this._opt.useWebFullScreen) { - this.emit(EVENTS.webFullscreen, value); - setTimeout(() => { - this.updateOption({ - rotate: value ? 270 : 0 - }); - this.resize(); - }, 10); - } else { - this.emit(EVENTS.fullscreen, value); - } - } - - get fullscreen() { - return isFullScreen() || this.webFullscreen; - } - - set webFullscreen(value) { - this.emit(EVENTS.webFullscreen, value); - } - - get webFullscreen() { - return this.$container.classList.contains('jessibuca-fullscreen-web'); - } - - set loaded(value) { - this._hasLoaded = value; - } - - get loaded() { - return this._hasLoaded; - } // - - - set playing(value) { - if (value) { - // 将loading 设置为 false - this.loading = false; - } - - if (this.playing !== value) { - this._playing = value; - this.emit(EVENTS.playing, value); - this.emit(EVENTS.volumechange, this.volume); - - if (value) { - this.emit(EVENTS.play); - } else { - this.emit(EVENTS.pause); - } - } - } - - get playing() { - return this._playing; - } - - get volume() { - return this.audio && this.audio.volume || 0; - } - - set volume(value) { - if (value !== this.volume) { - this.audio && this.audio.setVolume(value); - this._lastVolume = value; - } - } - - get lastVolume() { - return this._lastVolume; - } - - set loading(value) { - if (this.loading !== value) { - this._loading = value; - this.emit(EVENTS.loading, this._loading); - } - } - - get loading() { - return this._loading; - } - - set recording(value) { - if (value) { - if (this.playing) { - this.recorder && this.recorder.startRecord(); - } - } else { - this.recorder && this.recorder.stopRecordAndSave(); - } - } - - get recording() { - return this.recorder ? this.recorder.recording : false; - } - - set audioTimestamp(value) { - if (value === null) { - return; - } - - this._audioTimestamp = value; - } // - - - get audioTimestamp() { - return this._audioTimestamp; - } // - - - set videoTimestamp(value) { - if (value === null) { - return; - } - - this._videoTimestamp = value; // just for wasm - - if (!this._opt.useWCS && !this._opt.useMSE) { - if (this.audioTimestamp && this.videoTimestamp) { - this.audio && this.audio.emit(EVENTS.videoSyncAudio, { - audioTimestamp: this.audioTimestamp, - videoTimestamp: this.videoTimestamp, - diff: this.audioTimestamp - this.videoTimestamp - }); - } - } - } // - - - get videoTimestamp() { - return this._videoTimestamp; - } - - get isDebug() { - return this._opt.debug === true; - } - /** - * - * @param options - */ - - - updateOption(options) { - this._opt = Object.assign({}, this._opt, options); - } - /** - * - * @returns {Promise} - */ - - - init() { - return new Promise((resolve, reject) => { - if (!this.stream) { - this.stream = new Stream(this); - } - - if (!this.audio) { - if (this._opt.hasAudio) { - this.audio = new Audio(this); - } - } - - if (!this.demux) { - this.demux = new Demux(this); - } - - if (this._opt.useWCS) { - if (!this.webcodecsDecoder) { - this.webcodecsDecoder = new WebcodecsDecoder(this); - } - } - - if (this._opt.useMSE) { - if (!this.mseDecoder) { - this.mseDecoder = new MseDecoder(this); - } - } - - if (!this.decoderWorker && !this._onlyMseOrWcsVideo()) { - this.decoderWorker = new DecoderWorker(this); - this.once(EVENTS.decoderWorkerInit, () => { - resolve(); - }); - } else { - resolve(); - } - }); - } - /** - * - * @param url - * @returns {Promise} - */ - - - play(url, options) { - return new Promise((resolve, reject) => { - if (!url && !this._opt.url) { - return reject(); - } - - this.loading = true; - this.playing = false; - this._times.playInitStart = now(); - - if (!url) { - url = this._opt.url; - } - - this._opt.url = url; - this.clearCheckHeartTimeout(); - this.init().then(() => { - this._times.playStart = now(); // - - if (this._opt.isNotMute) { - this.mute(false); - } - - if (this.webcodecsDecoder) { - this.webcodecsDecoder.once(EVENTS_ERROR.webcodecsH265NotSupport, () => { - this.emit(EVENTS_ERROR.webcodecsH265NotSupport); - - if (!this._opt.autoWasm) { - this.emit(EVENTS.error, EVENTS_ERROR.webcodecsH265NotSupport); - } - }); - } - - if (this.mseDecoder) { - this.mseDecoder.once(EVENTS_ERROR.mediaSourceH265NotSupport, () => { - this.emit(EVENTS_ERROR.mediaSourceH265NotSupport); - - if (!this._opt.autoWasm) { - this.emit(EVENTS.error, EVENTS_ERROR.mediaSourceH265NotSupport); - } - }); - this.mseDecoder.once(EVENTS_ERROR.mediaSourceFull, () => { - this.emitError(EVENTS_ERROR.mediaSourceFull); - }); - this.mseDecoder.once(EVENTS_ERROR.mediaSourceAppendBufferError, () => { - this.emitError(EVENTS_ERROR.mediaSourceAppendBufferError); - }); - this.mseDecoder.once(EVENTS_ERROR.mediaSourceBufferListLarge, () => { - this.emitError(EVENTS_ERROR.mediaSourceBufferListLarge); - }); - this.mseDecoder.once(EVENTS_ERROR.mediaSourceAppendBufferEndTimeout, () => { - this.emitError(EVENTS_ERROR.mediaSourceAppendBufferEndTimeout); - }); - } - - this.enableWakeLock(); - this.stream.fetchStream(url, options); // - - this.checkLoadingTimeout(); // fetch error - - this.stream.once(EVENTS_ERROR.fetchError, error => { - reject(error); - }); // ws - - this.stream.once(EVENTS_ERROR.websocketError, error => { - reject(error); - }); // stream end - - this.stream.once(EVENTS.streamEnd, () => { - reject(); - }); // success - - this.stream.once(EVENTS.streamSuccess, () => { - resolve(); - this._times.streamResponse = now(); // - - this.video.play(); - this.checkStatsInterval(); - }); - }).catch(e => { - reject(e); - }); - }); - } - /** - * - */ - - - close() { - return new Promise((resolve, reject) => { - this._close().then(() => { - this.video && this.video.clearView(); - resolve(); - }); - }); - } - - resumeAudioAfterPause() { - if (this.lastVolume) { - this.volume = this.lastVolume; - } - } - - _close() { - return new Promise((resolve, reject) => { - // - if (this.stream) { - this.stream.destroy(); - this.stream = null; - } - - if (this.demux) { - this.demux.destroy(); - this.demux = null; - } // - - - if (this.decoderWorker) { - this.decoderWorker.destroy(); - this.decoderWorker = null; - } - - if (this.webcodecsDecoder) { - this.webcodecsDecoder.destroy(); - this.webcodecsDecoder = null; - } - - if (this.mseDecoder) { - this.mseDecoder.destroy(); - this.mseDecoder = null; - } - - if (this.audio) { - this.audio.destroy(); - this.audio = null; - } - - this.clearCheckHeartTimeout(); - this.clearCheckLoadingTimeout(); - this.clearStatsInterval(); - this.playing = false; - this.loading = false; - this.recording = false; - - if (this.video) { - this.video.resetInit(); - this.video.pause(true); - } // release lock - - - this.releaseWakeLock(); // reset stats - - this.resetStats(); // - - this._audioTimestamp = 0; - this._videoTimestamp = 0; // - - this._times = initPlayTimes(); // - - setTimeout(() => { - resolve(); - }, 0); - }); - } - /** - * - * @param flag {boolean} 是否清除画面 - * @returns {Promise} - */ - - - pause() { - let flag = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; - - if (flag) { - return this.close(); - } else { - return this._close(); - } - } - /** - * - * @param flag - */ - - - mute(flag) { - this.audio && this.audio.mute(flag); - } - /** - * - */ - - - resize() { - this.video.resize(); - } - /** - * - * @param fileName - * @param fileType - */ - - - startRecord(fileName, fileType) { - if (this.recording) { - return; - } - - this.recorder.setFileName(fileName, fileType); - this.recording = true; - } - /** - * - */ - - - stopRecordAndSave() { - if (this.recording) { - this.recording = false; - } - } - - _hasControl() { - let result = false; - let hasBtnShow = false; - Object.keys(this._opt.operateBtns).forEach(key => { - if (this._opt.operateBtns[key]) { - hasBtnShow = true; - } - }); - - if (this._opt.showBandwidth || this._opt.text || hasBtnShow) { - result = true; - } - - return result; - } - - _onlyMseOrWcsVideo() { - return this._opt.hasAudio === false && (this._opt.useMSE || this._opt.useWCS && !this._opt.useOffscreen); - } - - checkHeart() { - this.clearCheckHeartTimeout(); - this.checkHeartTimeout(); - } // 心跳检查,如果渲染间隔暂停了多少时间之后,就会抛出异常 - - - checkHeartTimeout() { - this._checkHeartTimeout = setTimeout(() => { - if (this.playing) { - // check again - if (this._stats.fps !== 0) { - return; - } - - this.pause().then(() => { - this.emit(EVENTS.timeout, EVENTS.delayTimeout); - this.emit(EVENTS.delayTimeout); - }); - } - }, this._opt.heartTimeout * 1000); - } - - checkStatsInterval() { - this._checkStatsInterval = setInterval(() => { - this.updateStats(); - }, 1000); - } // - - - clearCheckHeartTimeout() { - if (this._checkHeartTimeout) { - clearTimeout(this._checkHeartTimeout); - this._checkHeartTimeout = null; - } - } // loading 等待时间 - - - checkLoadingTimeout() { - this._checkLoadingTimeout = setTimeout(() => { - // check again - if (this.playing) { - return; - } - - this.pause().then(() => { - this.emit(EVENTS.timeout, EVENTS.loadingTimeout); - this.emit(EVENTS.loadingTimeout); - }); - }, this._opt.loadingTimeout * 1000); - } - - clearCheckLoadingTimeout() { - if (this._checkLoadingTimeout) { - clearTimeout(this._checkLoadingTimeout); - this._checkLoadingTimeout = null; - } - } - - clearStatsInterval() { - if (this._checkStatsInterval) { - clearInterval(this._checkStatsInterval); - this._checkStatsInterval = null; - } - } - - handleRender() { - if (this.loading) { - this.emit(EVENTS.start); - this.loading = false; - this.clearCheckLoadingTimeout(); - } - - if (!this.playing) { - this.playing = true; - } - - this.checkHeart(); - } // - - - updateStats(options) { - options = options || {}; - - if (!this._startBpsTime) { - this._startBpsTime = now(); - } - - if (isNotEmpty(options.ts)) { - this._stats.ts = options.ts; - } - - if (isNotEmpty(options.buf)) { - this._stats.buf = options.buf; - } - - if (options.fps) { - this._stats.fps += 1; - } - - if (options.abps) { - this._stats.abps += options.abps; - } - - if (options.vbps) { - this._stats.vbps += options.vbps; - } - - const _nowTime = now(); - - const timestamp = _nowTime - this._startBpsTime; - - if (timestamp < 1 * 1000) { - return; - } - - this.emit(EVENTS.stats, this._stats); - this.emit(EVENTS.performance, fpsStatus(this._stats.fps)); - this._stats.fps = 0; - this._stats.abps = 0; - this._stats.vbps = 0; - this._startBpsTime = _nowTime; - } - - resetStats() { - this._startBpsTime = null; - this._stats = { - buf: 0, - //ms - fps: 0, - abps: 0, - vbps: 0, - ts: 0 - }; - } - - enableWakeLock() { - if (this._opt.keepScreenOn) { - this.keepScreenOn && this.keepScreenOn.enable(); - } - } - - releaseWakeLock() { - if (this._opt.keepScreenOn) { - this.keepScreenOn && this.keepScreenOn.disable(); - } - } - - handlePlayToRenderTimes() { - const _times = this._times; - _times.playTimestamp = _times.playStart - _times.playInitStart; - _times.streamTimestamp = _times.streamStart - _times.playStart; - _times.streamResponseTimestamp = _times.streamResponse - _times.streamStart; - _times.demuxTimestamp = _times.demuxStart - _times.streamResponse; - _times.decodeTimestamp = _times.decodeStart - _times.demuxStart; - _times.videoTimestamp = _times.videoStart - _times.decodeStart; - _times.allTimestamp = _times.videoStart - _times.playInitStart; - this.emit(EVENTS.playToRenderTimes, _times); - } - - getOption() { - return this._opt; - } - - emitError(errorType) { - let message = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; - this.emit(EVENTS.error, errorType, message); - this.emit(errorType, message); - } - - } - - class Jessibuca extends Emitter { - constructor(options) { - super(); - let _opt = options; - let $container = options.container; - - if (typeof options.container === 'string') { - $container = document.querySelector(options.container); - } - - if (!$container) { - throw new Error('Jessibuca need container option'); - } // check container node name - - - if ($container.nodeName === 'CANVAS' || $container.nodeName === 'VIDEO') { - throw new Error(`Jessibuca container type can not be ${$container.nodeName} type`); - } - - if (_opt.videoBuffer >= _opt.heartTimeout) { - throw new Error(`Jessibuca videoBuffer ${_opt.videoBuffer}s must be less than heartTimeout ${_opt.heartTimeout}s`); - } - - $container.classList.add('jessibuca-container'); - delete _opt.container; // 禁用离屏渲染 - - _opt.forceNoOffscreen = true; // 移动端不支持自动关闭控制栏 - - if (isMobile()) { - _opt.controlAutoHide = false; - } // s -> ms - - - if (isNotEmpty(_opt.videoBuffer)) { - _opt.videoBuffer = Number(_opt.videoBuffer) * 1000; - } // setting - - - if (isNotEmpty(_opt.timeout)) { - if (isEmpty(_opt.loadingTimeout)) { - _opt.loadingTimeout = _opt.timeout; - } - - if (isEmpty(_opt.heartTimeout)) { - _opt.heartTimeout = _opt.timeout; - } - } - - this._opt = _opt; - this.$container = $container; - this._loadingTimeoutReplayTimes = 0; - this._heartTimeoutReplayTimes = 0; - this.events = new Events(this); - this.debug = new Debug(this); - - this._initPlayer($container, _opt); - } - /** - * - */ - - - destroy() { - if (this.events) { - this.events.destroy(); - this.events = null; - } - - if (this.player) { - this.player.destroy(); - this.player = null; - } - - this.$container = null; - this._opt = null; - this._loadingTimeoutReplayTimes = 0; - this._heartTimeoutReplayTimes = 0; - this.off(); - } - - _initPlayer($container, options) { - this.player = new Player($container, options); - this.debug.log('jessibuca', '_initPlayer', this.player.getOption()); - - this._bindEvents(); - } - - _resetPlayer() { - let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - this.player.destroy(); - this.player = null; - this._opt = Object.assign(this._opt, options); - this._opt.url = ''; // reset url - - this._initPlayer(this.$container, this._opt); - } - - _bindEvents() { - // 对外的事件 - Object.keys(JESSIBUCA_EVENTS).forEach(key => { - this.player.on(JESSIBUCA_EVENTS[key], value => { - this.emit(key, value); - }); - }); - } - /** - * 是否开启控制台调试打印 - * @param value {Boolean} - */ - - - setDebug(value) { - this.player.updateOption({ - debug: !!value - }); - } - /** - * - */ - - - mute() { - this.player.mute(true); - } - /** - * - */ - - - cancelMute() { - this.player.mute(false); - } - /** - * - * @param value {number} - */ - - - setVolume(value) { - this.player.volume = value; - } - /** - * - */ - - - audioResume() { - this.player.audio && this.player.audio.audioEnabled(true); - } - /** - * 设置超时时长, 单位秒 在连接成功之前和播放中途,如果超过设定时长无数据返回,则回调timeout事件 - * @param value {number} - */ - - - setTimeout(time) { - time = Number(time); - this.player.updateOption({ - timeout: time, - loadingTimeout: time, - heartTimeout: time - }); - } - /** - * - * @param type {number}: 0,1,2 - */ - - - setScaleMode(type) { - type = Number(type); - let options = { - isFullResize: false, - isResize: false - }; - - switch (type) { - case SCALE_MODE_TYPE.full: - options.isFullResize = false; - options.isResize = false; - break; - - case SCALE_MODE_TYPE.auto: - options.isFullResize = false; - options.isResize = true; - break; - - case SCALE_MODE_TYPE.fullAuto: - options.isFullResize = true; - options.isResize = true; - break; - } - - this.player.updateOption(options); - this.resize(); - } - /** - * - * @returns {Promise} - */ - - - pause() { - return new Promise((resolve, reject) => { - if (this.player) { - this.player.pause().then(() => { - resolve(); - }).catch(e => { - reject(e); - }); - } else { - reject('player is null'); - } - }); - } - /** - * - */ - - - close() { - // clear url - this._opt.url = ''; - this._opt.playOptions = {}; - return this.player.close(); - } - /** - * - */ - - - clearView() { - this.player.video.clearView(); - } - /** - * - * @param url {string} - * @param options {object} - * @returns {Promise} - */ - - - play(url) { - let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - return new Promise((resolve, reject) => { - if (!url && !this._opt.url) { - this.emit(EVENTS.error, EVENTS_ERROR.playError); - reject('play url is empty'); - return; - } - - if (url) { - // url 相等的时候。 - if (this._opt.url) { - // 存在相同的 url - if (url === this._opt.url) { - // 正在播放 - if (this.player.playing) { - resolve(); - } else { - // pause -> play - this.clearView(); - this.player.play(this._opt.url, this._opt.playOptions).then(() => { - resolve(); // 恢复下之前的音量 - - this.player.resumeAudioAfterPause(); - }).catch(e => { - this.debug.warn('jessibuca', 'pause -> play and play error', e); - this.player.pause().then(() => { - reject(e); - }); - }); - } - } else { - // url 发生改变了 - this.player.pause().then(() => { - // 清除 画面 - this.clearView(); - - this._play(url, options).then(() => { - resolve(); - }).catch(e => { - this.debug.warn('jessibuca', 'this._play error', e); - reject(e); - }); - }).catch(e => { - this.debug.warn('jessibuca', 'this._opt.url is null and pause error', e); - reject(e); - }); - } - } else { - this._play(url, options).then(() => { - resolve(); - }).catch(e => { - this.debug.warn('jessibuca', 'this._play error', e); - reject(e); - }); - } - } else { - // url 不存在的时候 - // 就是从 play -> pause -> play - this.player.play(this._opt.url, this._opt.playOptions).then(() => { - resolve(); // 恢复下之前的音量 - - this.player.resumeAudioAfterPause(); - }).catch(e => { - this.debug.warn('jessibuca', 'url is null and play error', e); - this.player.pause().then(() => { - reject(e); - }); - }); - } - }); - } - /** - * - * @param url {string} - * @param options {object} - * @returns {Promise} - * @private - */ - - - _play(url) { - let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - return new Promise((resolve, reject) => { - this._opt.url = url; - this._opt.playOptions = options; // 新的url - - const isHttp = url.indexOf("http") === 0; // - - const protocol = isHttp ? PLAYER_PLAY_PROTOCOL.fetch : PLAYER_PLAY_PROTOCOL.websocket; // - - const demuxType = isHttp || url.indexOf(".flv") !== -1 || this._opt.isFlv ? DEMUX_TYPE.flv : DEMUX_TYPE.m7s; - this.player.updateOption({ - protocol, - demuxType - }); - this.player.once(EVENTS_ERROR.webglAlignmentError, () => { - this.pause().then(() => { - this.debug.log('Jessibuca', 'webglAlignmentError'); - - this._resetPlayer({ - openWebglAlignment: true - }); - - this.play(url, options).then(() => { - // resolve(); - this.debug.log('Jessibuca', 'webglAlignmentError and play success'); - }).catch(() => { - // reject(); - this.debug.log('Jessibuca', 'webglAlignmentError and play error'); - }); - }); - }); - this.player.once(EVENTS_ERROR.mediaSourceH265NotSupport, () => { - this.pause().then(() => { - if (this.player._opt.autoWasm) { - this.debug.log('Jessibuca', 'auto wasm [mse-> wasm] reset player and play'); - - this._resetPlayer({ - useMSE: false - }); - - this.play(url, options).then(() => { - // resolve(); - this.debug.log('Jessibuca', 'auto wasm [mse-> wasm] reset player and play success'); - }).catch(() => { - // reject(); - this.debug.log('Jessibuca', 'auto wasm [mse-> wasm] reset player and play error'); - }); - } - }); - }); // media source full error - - this.player.once(EVENTS_ERROR.mediaSourceFull, () => { - this.pause().then(() => { - this.debug.log('Jessibuca', 'media source full'); - - this._resetPlayer(); - - this.play(url, options).then(() => { - // resolve(); - this.debug.log('Jessibuca', 'media source full and reset player and play success'); - }).catch(() => { - // reject(); - this.debug.warn('Jessibuca', 'media source full and reset player and play error'); - }); - }); - }); // media source append buffer error - - this.player.once(EVENTS_ERROR.mediaSourceAppendBufferError, () => { - this.pause().then(() => { - this.debug.log('Jessibuca', 'media source append buffer error'); - - this._resetPlayer(); - - this.play(url, options).then(() => { - // resolve(); - this.debug.log('Jessibuca', 'media source append buffer error and reset player and play success'); - }).catch(() => { - // reject(); - this.debug.warn('Jessibuca', 'media source append buffer error and reset player and play error'); - }); - }); - }); - this.player.once(EVENTS_ERROR.mediaSourceBufferListLarge, () => { - this.pause().then(() => { - this.debug.log('Jessibuca', 'media source buffer list large'); - - this._resetPlayer(); - - this.play(url, options).then(() => { - // resolve(); - this.debug.log('Jessibuca', 'media source buffer list large and reset player and play success'); - }).catch(() => { - // reject(); - this.debug.warn('Jessibuca', 'media source buffer list large and reset player and play error'); - }); - }); - }); - this.player.once(EVENTS_ERROR.mediaSourceAppendBufferEndTimeout, () => { - this.pause().then(() => { - this.debug.log('Jessibuca', 'media source append buffer end timeout'); - - this._resetPlayer(); - - this.play(url, options).then(() => { - // resolve(); - this.debug.log('Jessibuca', 'media source append buffer end timeout and reset player and play success'); - }).catch(() => { - // reject(); - this.debug.warn('Jessibuca', 'media source append buffer end timeout and reset player and play error'); - }); - }); - }); - this.player.once(EVENTS_ERROR.mseSourceBufferError, () => { - this.pause().then(() => { - this.debug.log('Jessibuca', 'mseSourceBufferError close success'); - }); - }); // - - this.player.once(EVENTS_ERROR.webcodecsH265NotSupport, () => { - this.pause().then(() => { - if (this.player._opt.autoWasm) { - this.debug.log('Jessibuca', 'auto wasm [wcs-> wasm] reset player and play'); - - this._resetPlayer({ - useWCS: false - }); - - this.play(url, options).then(() => { - // resolve(); - this.debug.log('Jessibuca', 'auto wasm [wcs-> wasm] reset player and play success'); - }).catch(() => { - // reject(); - this.debug.warn('Jessibuca', 'auto wasm [wcs-> wasm] reset player and play error'); - }); - } - }); - }); // webcodecs - - this.player.once(EVENTS_ERROR.webcodecsWidthOrHeightChange, () => { - this.pause().then(() => { - this.debug.log('Jessibuca', 'webcodecs Width Or Height Change reset player and play'); - - this._resetPlayer({ - useWCS: true - }); - - this.play(url, options).then(() => { - // resolve(); - this.debug.log('Jessibuca', 'webcodecs Width Or Height Change reset player and play success'); - }).catch(() => { - // reject(); - this.debug.warn('Jessibuca', 'webcodecs Width Or Height Change reset player and play error'); - }); - }); - }); // webcodecs - - this.player.once(EVENTS_ERROR.webcodecsDecodeError, () => { - this.pause().then(() => { - if (this.player._opt.autoWasm) { - this.debug.log('Jessibuca', 'webcodecs decode error reset player and play'); - - this._resetPlayer({ - useWCS: false - }); - - this.play(url, options).then(() => { - // resolve(); - this.debug.log('Jessibuca', 'webcodecs decode error reset player and play success'); - }).catch(() => { - // reject(); - this.debug.warn('Jessibuca', 'webcodecs decode error reset player and play error'); - }); - } - }); - }); // wasm。 - - this.player.once(EVENTS_ERROR.wasmDecodeError, () => { - if (this.player._opt.wasmDecodeErrorReplay) { - this.pause().then(() => { - this.debug.log('Jessibuca', 'wasm decode error and reset player and play'); - - this._resetPlayer({ - useWCS: false - }); - - this.play(url, options).then(() => { - // resolve(); - this.debug.log('Jessibuca', 'wasm decode error and reset player and play success'); - }).catch(() => { - // reject(); - this.debug.warn('Jessibuca', 'wasm decode error and reset player and play error'); - }); - }); - } - }); // 监听 delay timeout - - this.player.on(EVENTS.delayTimeout, () => { - if (this.player._opt.heartTimeoutReplay && (this._heartTimeoutReplayTimes < this.player._opt.heartTimeoutReplayTimes || this.player._opt.heartTimeoutReplayTimes === -1)) { - this.debug.log('Jessibuca', `delay timeout replay time is ${this._heartTimeoutReplayTimes}`); - this._heartTimeoutReplayTimes += 1; - this.play(url, options).then(() => { - // resolve(); - this._heartTimeoutReplayTimes = 0; - }).catch(() => {// reject(); - }); - } - }); // 监听 loading timeout - - this.player.on(EVENTS.loadingTimeout, () => { - if (this.player._opt.loadingTimeoutReplay && (this._loadingTimeoutReplayTimes < this.player._opt.loadingTimeoutReplayTimes || this.player._opt.loadingTimeoutReplayTimes === -1)) { - this.debug.log('Jessibuca', `loading timeout replay time is ${this._loadingTimeoutReplayTimes}`); - this._loadingTimeoutReplayTimes += 1; - this.play(url, options).then(() => { - // resolve(); - this._loadingTimeoutReplayTimes = 0; - }).catch(() => {// reject(); - }); - } - }); - - if (this.hasLoaded()) { - this.player.play(url, options).then(() => { - resolve(); - }).catch(e => { - this.debug.warn('Jessibuca', 'hasLoaded and play error', e); - this.player && this.player.pause().then(() => { - reject(e); - }); - }); - } else { - this.player.once(EVENTS.decoderWorkerInit, () => { - this.player.play(url, options).then(() => { - resolve(); - }).catch(e => { - this.debug.warn('Jessibuca', 'decoderWorkerInit and play error', e); - this.player && this.player.pause().then(() => { - reject(e); - }); - }); - }); - } - }); - } - /** - * - */ - - - resize() { - this.player.resize(); - } - /** - * - * @param time {number} s - */ - - - setBufferTime(time) { - time = Number(time); // s -> ms - - this.player.updateOption({ - videoBuffer: time * 1000 - }); // update worker config - - this.player.decoderWorker && this.player.decoderWorker.updateWorkConfig({ - key: 'videoBuffer', - value: time * 1000 - }); - } - /** - * - * @param deg {number} - */ - - - setRotate(deg) { - deg = parseInt(deg, 10); - const list = [0, 90, 180, 270]; - - if (this._opt.rotate === deg || list.indexOf(deg) === -1) { - return; - } - - this.player.updateOption({ - rotate: deg - }); - this.resize(); - } - /** - * - * @returns {boolean} - */ - - - hasLoaded() { - return this.player.loaded; - } - /** - * - */ - - - setKeepScreenOn() { - this.player.updateOption({ - keepScreenOn: true - }); - } - /** - * - * @param flag {Boolean} - */ - - - setFullscreen(flag) { - const fullscreen = !!flag; - - if (this.player.fullscreen !== fullscreen) { - this.player.fullscreen = fullscreen; - } - } - /** - * - * @param filename {string} - * @param format {string} - * @param quality {number} - * @param type {string} download,base64,blob - */ - - - screenshot(filename, format, quality, type) { - if (!this.player.video) { - return ''; - } - - return this.player.video.screenshot(filename, format, quality, type); - } - /** - * - * @param fileName {string} - * @param fileType {string} - * @returns {Promise} - */ - - - startRecord(fileName, fileType) { - return new Promise((resolve, reject) => { - if (this.player.playing) { - this.player.startRecord(fileName, fileType); - resolve(); - } else { - reject(); - } - }); - } - - stopRecordAndSave() { - if (this.player.recording) { - this.player.stopRecordAndSave(); - } - } - /** - * - * @returns {Boolean} - */ - - - isPlaying() { - return this.player ? this.player.playing : false; - } - /** - * 是否静音状态 - * @returns {Boolean} - */ - - - isMute() { - return this.player.audio ? this.player.audio.isMute : true; - } - /** - * 是否在录制视频 - * @returns {*} - */ - - - isRecording() { - return this.player.recorder.recording; - } - - } - - _defineProperty(Jessibuca, "ERROR", EVENTS_ERROR); - - _defineProperty(Jessibuca, "TIMEOUT", { - loadingTimeout: EVENTS.loadingTimeout, - delayTimeout: EVENTS.delayTimeout - }); - - window.Jessibuca = Jessibuca; - - return Jessibuca; - -})); -//# sourceMappingURL=jessibuca.js.map -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.jessibuca = factory()); -})(this, (function () { - 'use strict'; - - var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; - - function unwrapExports(x) { - return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; - } - - function createCommonjsModule(fn, module) { - return module = { exports: {} }, fn(module, module.exports), module.exports; - } - - var defineProperty = createCommonjsModule(function (module) { - function _defineProperty(obj, key, value) { - if (key in obj) { - Object.defineProperty(obj, key, { - value: value, - enumerable: true, - configurable: true, - writable: true - }); - } else { - obj[key] = value; - } - - return obj; - } - - module.exports = _defineProperty, module.exports.__esModule = true, module.exports["default"] = module.exports; - }); - - var _defineProperty = unwrapExports(defineProperty); - - // 播放协议 - const PLAYER_PLAY_PROTOCOL = { - websocket: 0, - fetch: 1, - webrtc: 2 - }; - const DEMUX_TYPE = { - flv: 'flv', - m7s: 'm7s' - }; - const FILE_SUFFIX = { - mp4: 'mp4', - webm: 'webm' - }; - - const DEFAULT_PLAYER_OPTIONS = { - videoBuffer: 1000, - //1000ms 1 second - videoBufferDelay: 1000, - // 1000ms - isResize: true, - isFullResize: false, - // - isFlv: false, - debug: false, - hotKey: false, - // 快捷键 - loadingTimeout: 10, - // loading timeout - heartTimeout: 5, - // heart timeout - timeout: 10, - // second - loadingTimeoutReplay: true, - // loading timeout replay. default is true - heartTimeoutReplay: true, - // heart timeout replay. - loadingTimeoutReplayTimes: 3, - // loading timeout replay fail times - heartTimeoutReplayTimes: 3, - // heart timeout replay fail times - supportDblclickFullscreen: false, - // support double click toggle fullscreen - showBandwidth: false, - // show band width - keepScreenOn: false, - // - isNotMute: false, - // - hasAudio: true, - // has audio - hasVideo: true, - // has video - operateBtns: { - fullscreen: false, - screenshot: false, - play: false, - audio: false, - record: false - }, - controlAutoHide: false, - // control auto hide - hasControl: false, - loadingText: '', - // loading Text - background: '', - decoder: 'decoder.js', - url: '', - // play url - rotate: 0, - // - // text: '', - forceNoOffscreen: true, - // 默认是不采用 - hiddenAutoPause: false, - // - protocol: PLAYER_PLAY_PROTOCOL.fetch, - demuxType: DEMUX_TYPE.flv, - // demux type - useWCS: false, - // - wcsUseVideoRender: true, - // 默认设置为true - useMSE: false, - // - useOffscreen: false, - // - autoWasm: true, - // 自动降级到 wasm 模式 - wasmDecodeErrorReplay: true, - // 解码失败重新播放。 - openWebglAlignment: false, - // https://github.com/langhuihui/jessibuca/issues/152 - wasmDecodeAudioSyncVideo: false, - // wasm 解码之后音视频同步 - recordType: FILE_SUFFIX.webm, - useWebFullScreen: false // use web full screen - - }; - const WORKER_CMD_TYPE = { - init: 'init', - initVideo: 'initVideo', - render: 'render', - playAudio: 'playAudio', - initAudio: 'initAudio', - kBps: 'kBps', - decode: 'decode', - audioCode: 'audioCode', - videoCode: 'videoCode', - wasmError: 'wasmError' - }; - const WASM_ERROR = { - invalidNalUnitSize: 'Invalid NAL unit size' // errorSplittingTheInputIntoNALUnits: 'Error splitting the input into NAL units' - - }; - const MEDIA_TYPE = { - audio: 1, - video: 2 - }; - const FLV_MEDIA_TYPE = { - audio: 8, - video: 9 - }; - const WORKER_SEND_TYPE = { - init: 'init', - decode: 'decode', - audioDecode: 'audioDecode', - videoDecode: 'videoDecode', - close: 'close', - updateConfig: 'updateConfig' - }; // - - const EVENTS = { - fullscreen: 'fullscreen$2', - webFullscreen: 'webFullscreen', - decoderWorkerInit: 'decoderWorkerInit', - play: 'play', - playing: 'playing', - pause: 'pause', - mute: 'mute', - load: 'load', - loading: 'loading', - videoInfo: 'videoInfo', - timeUpdate: 'timeUpdate', - audioInfo: "audioInfo", - log: 'log', - error: "error", - kBps: 'kBps', - timeout: 'timeout', - delayTimeout: 'delayTimeout', - loadingTimeout: 'loadingTimeout', - stats: 'stats', - performance: "performance", - record: 'record', - recording: 'recording', - recordingTimestamp: 'recordingTimestamp', - recordStart: 'recordStart', - recordEnd: 'recordEnd', - recordCreateError: 'recordCreateError', - buffer: 'buffer', - videoFrame: 'videoFrame', - start: 'start', - metadata: 'metadata', - resize: 'resize', - streamEnd: 'streamEnd', - streamSuccess: 'streamSuccess', - streamMessage: 'streamMessage', - streamError: 'streamError', - volumechange: 'volumechange', - destroy: 'destroy', - mseSourceOpen: 'mseSourceOpen', - mseSourceClose: 'mseSourceClose', - mseSourceBufferError: 'mseSourceBufferError', - mseSourceBufferBusy: 'mseSourceBufferBusy', - mseSourceBufferFull: 'mseSourceBufferFull', - videoWaiting: 'videoWaiting', - videoTimeUpdate: 'videoTimeUpdate', - videoSyncAudio: 'videoSyncAudio', - playToRenderTimes: 'playToRenderTimes' - }; - const JESSIBUCA_EVENTS = { - load: EVENTS.load, - timeUpdate: EVENTS.timeUpdate, - videoInfo: EVENTS.videoInfo, - audioInfo: EVENTS.audioInfo, - error: EVENTS.error, - kBps: EVENTS.kBps, - log: EVENTS.log, - start: EVENTS.start, - timeout: EVENTS.timeout, - loadingTimeout: EVENTS.loadingTimeout, - delayTimeout: EVENTS.delayTimeout, - fullscreen: 'fullscreen', - webFullscreen: EVENTS.webFullscreen, - play: EVENTS.play, - pause: EVENTS.pause, - mute: EVENTS.mute, - stats: EVENTS.stats, - volumechange: EVENTS.volumechange, - performance: EVENTS.performance, - recordingTimestamp: EVENTS.recordingTimestamp, - recordStart: EVENTS.recordStart, - recordEnd: EVENTS.recordEnd, - playToRenderTimes: EVENTS.playToRenderTimes - }; - const EVENTS_ERROR = { - playError: 'playIsNotPauseOrUrlIsNull', - fetchError: "fetchError", - websocketError: 'websocketError', - webcodecsH265NotSupport: 'webcodecsH265NotSupport', - webcodecsDecodeError: 'webcodecsDecodeError', - webcodecsWidthOrHeightChange: 'webcodecsWidthOrHeightChange', - mediaSourceH265NotSupport: 'mediaSourceH265NotSupport', - mediaSourceFull: EVENTS.mseSourceBufferFull, - mseSourceBufferError: EVENTS.mseSourceBufferError, - mediaSourceAppendBufferError: 'mediaSourceAppendBufferError', - mediaSourceBufferListLarge: 'mediaSourceBufferListLarge', - mediaSourceAppendBufferEndTimeout: 'mediaSourceAppendBufferEndTimeout', - wasmDecodeError: 'wasmDecodeError', - webglAlignmentError: 'webglAlignmentError' - }; - const WEBSOCKET_STATUS = { - notConnect: 'notConnect', - open: 'open', - close: 'close', - error: 'error' - }; - const SCREENSHOT_TYPE = { - download: 'download', - base64: 'base64', - blob: 'blob' - }; - const VIDEO_ENC_TYPE = { - 7: 'H264(AVC)', - // - 12: 'H265(HEVC)' // - - }; - const VIDEO_ENC_CODE = { - h264: 7, - h265: 12 - }; - const AUDIO_ENC_TYPE = { - 10: 'AAC', - 7: 'ALAW', - 8: 'MULAW' - }; - const CONTROL_HEIGHT = 38; - const SCALE_MODE_TYPE = { - full: 0, - // 视频画面完全填充canvas区域,画面会被拉伸 - auto: 1, - // 视频画面做等比缩放后,高或宽对齐canvas区域,画面不被拉伸,但有黑边 - fullAuto: 2 // 视频画面做等比缩放后,完全填充canvas区域,画面不被拉伸,没有黑边,但画面显示不全 - - }; - const CANVAS_RENDER_TYPE = { - webcodecs: 'webcodecs', - webgl: 'webgl', - offscreen: 'offscreen' - }; - const ENCODED_VIDEO_TYPE = { - key: 'key', - delta: 'delta' - }; - const MP4_CODECS = { - avc: 'video/mp4; codecs="avc1.64002A"', - hev: 'video/mp4; codecs="hev1.1.6.L123.b0"' - }; - const MEDIA_SOURCE_STATE = { - ended: 'ended', - open: 'open', - closed: 'closed' - }; // frag duration - const AUDIO_SYNC_VIDEO_DIFF = 1000; - const HOT_KEY = { - esc: 27, - // - arrowUp: 38, - // - arrowDown: 40 // - - }; - const WCS_ERROR = { - keyframeIsRequiredError: 'A key frame is required after configure() or flush()', - canNotDecodeClosedCodec: "Cannot call 'decode' on a closed codec" - }; - const FETCH_ERROR = { - abortError1: 'The user aborted a request', - abortError2: 'AbortError', - abort: 'AbortError' - }; - - class Debug { - constructor(master) { - this.log = function (name) { - if (master._opt.debug) { - for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - args[_key - 1] = arguments[_key]; - } - - console.log(`Jessibuca: [${name}]`, ...args); - } - }; - - this.warn = function (name) { - if (master._opt.debug) { - for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { - args[_key2 - 1] = arguments[_key2]; - } - - console.warn(`Jessibuca: [${name}]`, ...args); - } - }; - - this.error = function (name) { - for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) { - args[_key3 - 1] = arguments[_key3]; - } - - console.error(`Jessibuca: [${name}]`, ...args); - }; - } - - } - - class Events { - constructor(master) { - this.destroys = []; - this.proxy = this.proxy.bind(this); - this.master = master; - } - - proxy(target, name, callback) { - let option = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; - - if (!target) { - return; - } - - if (Array.isArray(name)) { - return name.map(item => this.proxy(target, item, callback, option)); - } - - target.addEventListener(name, callback, option); - - const destroy = () => target.removeEventListener(name, callback, option); - - this.destroys.push(destroy); - return destroy; - } - - destroy() { - this.master.debug && this.master.debug.log(`Events`, 'destroy'); - this.destroys.forEach(event => event()); - } - - } - - var property$1 = (player => { - Object.defineProperty(player, 'rect', { - get: () => { - const clientRect = player.$container.getBoundingClientRect(); - clientRect.width = Math.max(clientRect.width, player.$container.clientWidth); - clientRect.height = Math.max(clientRect.height, player.$container.clientHeight); - return clientRect; - } - }); - ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(key => { - Object.defineProperty(player, key, { - get: () => { - return player.rect[key]; - } - }); - }); - }); - - var screenfull = createCommonjsModule(function (module) { - /*! - * screenfull - * v5.1.0 - 2020-12-24 - * (c) Sindre Sorhus; MIT License - */ - (function () { - - var document = typeof window !== 'undefined' && typeof window.document !== 'undefined' ? window.document : {}; - var isCommonjs = module.exports; - - var fn = (function () { - var val; - - var fnMap = [ - [ - 'requestFullscreen', - 'exitFullscreen', - 'fullscreenElement', - 'fullscreenEnabled', - 'fullscreenchange', - 'fullscreenerror' - ], - // New WebKit - [ - 'webkitRequestFullscreen', - 'webkitExitFullscreen', - 'webkitFullscreenElement', - 'webkitFullscreenEnabled', - 'webkitfullscreenchange', - 'webkitfullscreenerror' - - ], - // Old WebKit - [ - 'webkitRequestFullScreen', - 'webkitCancelFullScreen', - 'webkitCurrentFullScreenElement', - 'webkitCancelFullScreen', - 'webkitfullscreenchange', - 'webkitfullscreenerror' - - ], - [ - 'mozRequestFullScreen', - 'mozCancelFullScreen', - 'mozFullScreenElement', - 'mozFullScreenEnabled', - 'mozfullscreenchange', - 'mozfullscreenerror' - ], - [ - 'msRequestFullscreen', - 'msExitFullscreen', - 'msFullscreenElement', - 'msFullscreenEnabled', - 'MSFullscreenChange', - 'MSFullscreenError' - ] - ]; - - var i = 0; - var l = fnMap.length; - var ret = {}; - - for (; i < l; i++) { - val = fnMap[i]; - if (val && val[1] in document) { - for (i = 0; i < val.length; i++) { - ret[fnMap[0][i]] = val[i]; - } - return ret; - } - } - - return false; - })(); - - var eventNameMap = { - change: fn.fullscreenchange, - error: fn.fullscreenerror - }; - - var screenfull = { - request: function (element, options) { - return new Promise(function (resolve, reject) { - var onFullScreenEntered = function () { - this.off('change', onFullScreenEntered); - resolve(); - }.bind(this); - - this.on('change', onFullScreenEntered); - - element = element || document.documentElement; - - var returnPromise = element[fn.requestFullscreen](options); - - if (returnPromise instanceof Promise) { - returnPromise.then(onFullScreenEntered).catch(reject); - } - }.bind(this)); - }, - exit: function () { - return new Promise(function (resolve, reject) { - if (!this.isFullscreen) { - resolve(); - return; - } - - var onFullScreenExit = function () { - this.off('change', onFullScreenExit); - resolve(); - }.bind(this); - - this.on('change', onFullScreenExit); - - var returnPromise = document[fn.exitFullscreen](); - - if (returnPromise instanceof Promise) { - returnPromise.then(onFullScreenExit).catch(reject); - } - }.bind(this)); - }, - toggle: function (element, options) { - return this.isFullscreen ? this.exit() : this.request(element, options); - }, - onchange: function (callback) { - this.on('change', callback); - }, - onerror: function (callback) { - this.on('error', callback); - }, - on: function (event, callback) { - var eventName = eventNameMap[event]; - if (eventName) { - document.addEventListener(eventName, callback, false); - } - }, - off: function (event, callback) { - var eventName = eventNameMap[event]; - if (eventName) { - document.removeEventListener(eventName, callback, false); - } - }, - raw: fn - }; - - if (!fn) { - if (isCommonjs) { - module.exports = { isEnabled: false }; - } else { - window.screenfull = { isEnabled: false }; - } - - return; - } - - Object.defineProperties(screenfull, { - isFullscreen: { - get: function () { - return Boolean(document[fn.fullscreenElement]); - } - }, - element: { - enumerable: true, - get: function () { - return document[fn.fullscreenElement]; - } - }, - isEnabled: { - enumerable: true, - get: function () { - // Coerce to boolean in case of old WebKit - return Boolean(document[fn.fullscreenEnabled]); - } - } - }); - - if (isCommonjs) { - module.exports = screenfull; - } else { - window.screenfull = screenfull; - } - })(); - }); - screenfull.isEnabled; - - function noop() { } - function supportOffscreen($canvas) { - return typeof $canvas.transferControlToOffscreen === 'function'; - } - function supportOffscreenV2() { - return typeof OffscreenCanvas !== "undefined"; - } - function createContextGL($canvas) { - let gl = null; - const validContextNames = ["webgl", "experimental-webgl", "moz-webgl", "webkit-3d"]; - let nameIndex = 0; - - while (!gl && nameIndex < validContextNames.length) { - const contextName = validContextNames[nameIndex]; - - try { - let contextOptions = { - preserveDrawingBuffer: true - }; - gl = $canvas.getContext(contextName, contextOptions); - } catch (e) { - gl = null; - } - - if (!gl || typeof gl.getParameter !== "function") { - gl = null; - } - - ++nameIndex; - } - - return gl; - } - function dataURLToFile() { - let dataURL = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; - const arr = dataURL.split(","); - const bstr = atob(arr[1]); - const type = arr[0].replace("data:", "").replace(";base64", ""); - let n = bstr.length, - u8arr = new Uint8Array(n); - - while (n--) { - u8arr[n] = bstr.charCodeAt(n); - } - - return new File([u8arr], 'file', { - type - }); - } - function now() { - return new Date().getTime(); - } - (() => { - try { - if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") { - const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00)); - if (module instanceof WebAssembly.Module) return new WebAssembly.Instance(module) instanceof WebAssembly.Instance; - } - } catch (e) { } - - return false; - })(); - function clamp(num, a, b) { - return Math.max(Math.min(num, Math.max(a, b)), Math.min(a, b)); - } - function setStyle(element, key, value) { - if (!element) { - return; - } - - if (typeof key === 'object') { - Object.keys(key).forEach(item => { - setStyle(element, item, key[item]); - }); - } - - element.style[key] = value; - return element; - } - function getStyle(element, key) { - let numberType = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; - - if (!element) { - return 0; - } - - const value = getComputedStyle(element, null).getPropertyValue(key); - return numberType ? parseFloat(value) : value; - } - function getNowTime() { - if (performance && typeof performance.now === 'function') { - return performance.now(); - } - - return Date.now(); - } - function calculationRate(callback) { - let totalSize = 0; - let lastTime = getNowTime(); - return size => { - totalSize += size; - const thisTime = getNowTime(); - const diffTime = thisTime - lastTime; - - if (diffTime >= 1000) { - callback(totalSize / diffTime * 1000); - lastTime = thisTime; - totalSize = 0; - } - }; - } - function isMobile() { - return /iphone|ipod|android.*mobile|windows.*phone|blackberry.*mobile/i.test(window.navigator.userAgent.toLowerCase()); - } - - function supportWCS() { - return "VideoEncoder" in window; - } - function formatVideoDecoderConfigure(avcC) { - let codecArray = avcC.subarray(1, 4); - let codecString = "avc1."; - - for (let j = 0; j < 3; j++) { - let h = codecArray[j].toString(16); - - if (h.length < 2) { - h = "0" + h; - } - - codecString += h; - } - - return { - codec: codecString, - description: avcC - }; - } - function isFullScreen() { - return screenfull.isFullscreen; - } - function bpsSize(value) { - if (null == value || value === '' || parseInt(value) === 0 || isNaN(parseInt(value))) { - return "0KB/s"; - } - - let size = parseFloat(value); - size = size.toFixed(2); - return size + 'KB/s'; - } - function fpsStatus(fps) { - let result = 0; - - if (fps >= 24) { - result = 2; - } else if (fps >= 15) { - result = 1; - } - - return result; - } - function createEmptyImageBitmap(width, height) { - const $canvasElement = document.createElement("canvas"); - $canvasElement.width = width; - $canvasElement.height = height; - return window.createImageBitmap($canvasElement, 0, 0, width, height); - } - function supportMSE() { - return window.MediaSource && window.MediaSource.isTypeSupported(MP4_CODECS.avc); - } - function supportMediaStreamTrack() { - return window.MediaStreamTrackGenerator && typeof window.MediaStreamTrackGenerator === 'function'; - } - function isEmpty(value) { - return value === null || value === undefined; - } - function isBoolean(value) { - return value === true || value === false; - } - function isNotEmpty(value) { - return !isEmpty(value); - } - function initPlayTimes() { - return { - playInitStart: '', - //1 - playStart: '', - // 2 - streamStart: '', - //3 - streamResponse: '', - // 4 - demuxStart: '', - // 5 - decodeStart: '', - // 6 - videoStart: '', - // 7 - playTimestamp: '', - // playStart- playInitStart - streamTimestamp: '', - // streamStart - playStart - streamResponseTimestamp: '', - // streamResponse - streamStart - demuxTimestamp: '', - // demuxStart - streamResponse - decodeTimestamp: '', - // decodeStart - demuxStart - videoTimestamp: '', - // videoStart - decodeStart - allTimestamp: '' // videoStart - playInitStart - - }; - } // create watermark - function formatTimeTips(time) { - var result; // - - if (time > -1) { - var hour = Math.floor(time / 3600); - var min = Math.floor(time / 60) % 60; - var sec = time % 60; - sec = Math.round(sec); - - if (hour < 10) { - result = '0' + hour + ":"; - } else { - result = hour + ":"; - } - - if (min < 10) { - result += "0"; - } - - result += min + ":"; - - if (sec < 10) { - result += "0"; - } - - result += sec.toFixed(0); - } - - return result; - } - function getTarget(e) { - const event = e || window.event; - const target = event.target || event.srcElement; - return target; - } - function isWebglRenderSupport(width) { - return width / 2 % 4 === 0; - } - function getBrowser() { - const UserAgent = navigator.userAgent.toLowerCase(); - const browserInfo = {}; - const browserArray = { - IE: window.ActiveXObject || "ActiveXObject" in window, - // IE - Chrome: UserAgent.indexOf('chrome') > -1 && UserAgent.indexOf('safari') > -1, - // Chrome浏览器 - Firefox: UserAgent.indexOf('firefox') > -1, - // 火狐浏览器 - Opera: UserAgent.indexOf('opera') > -1, - // Opera浏览器 - Safari: UserAgent.indexOf('safari') > -1 && UserAgent.indexOf('chrome') == -1, - // safari浏览器 - Edge: UserAgent.indexOf('edge') > -1, - // Edge浏览器 - QQBrowser: /qqbrowser/.test(UserAgent), - // qq浏览器 - WeixinBrowser: /MicroMessenger/i.test(UserAgent) // 微信浏览器 - - }; // console.log(browserArray) - - for (let i in browserArray) { - if (browserArray[i]) { - let versions = ''; - - if (i === 'IE') { - versions = UserAgent.match(/(msie\s|trident.*rv:)([\w.]+)/)[2]; - } else if (i === 'Chrome') { - for (let mt in navigator.mimeTypes) { - //检测是否是360浏览器(测试只有pc端的360才起作用) - if (navigator.mimeTypes[mt]['type'] === 'application/360softmgrplugin') { - i = '360'; - } - } - - versions = UserAgent.match(/chrome\/([\d.]+)/)[1]; - } else if (i === 'Firefox') { - versions = UserAgent.match(/firefox\/([\d.]+)/)[1]; - } else if (i === 'Opera') { - versions = UserAgent.match(/opera\/([\d.]+)/)[1]; - } else if (i === 'Safari') { - versions = UserAgent.match(/version\/([\d.]+)/)[1]; - } else if (i === 'Edge') { - versions = UserAgent.match(/edge\/([\d.]+)/)[1]; - } else if (i === 'QQBrowser') { - versions = UserAgent.match(/qqbrowser\/([\d.]+)/)[1]; - } - - browserInfo.type = i; - browserInfo.version = parseInt(versions); - } - } - - return browserInfo; - } - function closeVideoFrame(videoFrame) { - if (videoFrame.close) { - videoFrame.close(); - } else if (videoFrame.destroy) { - videoFrame.destroy(); - } - } - function removeElement(element) { - let result = false; - - if (element) { - if (element.parentNode) { - element.parentNode.removeChild(element); - result = true; - } - } - - return result; - } - - var events$1 = (player => { - try { - const screenfullChange = e => { - if (getTarget(e) === player.$container) { - player.emit(JESSIBUCA_EVENTS.fullscreen, player.fullscreen); // 如果不是fullscreen,则触发下 resize 方法 - - if (!player.fullscreen) { - player.resize(); - } else { - if (player._opt.useMSE) { - player.resize(); - } - } - } - }; - - screenfull.on('change', screenfullChange); - player.events.destroys.push(() => { - screenfull.off('change', screenfullChange); - }); - } catch (error) {// - } // - - - player.on(EVENTS.decoderWorkerInit, () => { - player.debug.log('player', 'has loaded'); - player.loaded = true; - }); // - - player.on(EVENTS.play, () => { - player.loading = false; - }); // - - player.on(EVENTS.fullscreen, value => { - if (value) { - try { - screenfull.request(player.$container).then(() => { }).catch(e => { - if (isMobile() && player._opt.useWebFullScreen) { - player.webFullscreen = true; - } - }); - } catch (e) { - if (isMobile() && player._opt.useWebFullScreen) { - player.webFullscreen = true; - } - } - } else { - try { - screenfull.exit().then(() => { - if (player.webFullscreen) { - player.webFullscreen = false; - } - }).catch(() => { - player.webFullscreen = false; - }); - } catch (e) { - player.webFullscreen = false; - } - } - }); - - if (isMobile()) { - player.on(EVENTS.webFullscreen, value => { - if (value) { - player.$container.classList.add('jessibuca-fullscreen-web'); - } else { - player.$container.classList.remove('jessibuca-fullscreen-web'); - } // - - - player.emit(JESSIBUCA_EVENTS.fullscreen, player.fullscreen); - }); - } // - - - player.on(EVENTS.resize, () => { - player.video && player.video.resize(); - }); - - if (player._opt.debug) { - const ignoreList = [EVENTS.timeUpdate]; - Object.keys(EVENTS).forEach(key => { - player.on(EVENTS[key], value => { - if (ignoreList.includes(key)) { - return; - } - - player.debug.log('player events', EVENTS[key], value); - }); - }); - Object.keys(EVENTS_ERROR).forEach(key => { - player.on(EVENTS_ERROR[key], value => { - player.debug.log('player event error', EVENTS_ERROR[key], value); - }); - }); - } - }); - - class Emitter { - on(name, fn, ctx) { - const e = this.e || (this.e = {}); - (e[name] || (e[name] = [])).push({ - fn, - ctx - }); - return this; - } - - once(name, fn, ctx) { - const self = this; - - function listener() { - self.off(name, listener); - - for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { - args[_key] = arguments[_key]; - } - - fn.apply(ctx, args); - } - - listener._ = fn; - return this.on(name, listener, ctx); - } - - emit(name) { - const evtArr = ((this.e || (this.e = {}))[name] || []).slice(); - - for (var _len2 = arguments.length, data = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { - data[_key2 - 1] = arguments[_key2]; - } - - for (let i = 0; i < evtArr.length; i += 1) { - evtArr[i].fn.apply(evtArr[i].ctx, data); - } - - return this; - } - - off(name, callback) { - const e = this.e || (this.e = {}); - - if (!name) { - Object.keys(e).forEach(key => { - delete e[key]; - }); - delete this.e; - return; - } - - const evts = e[name]; - const liveEvents = []; - - if (evts && callback) { - for (let i = 0, len = evts.length; i < len; i += 1) { - if (evts[i].fn !== callback && evts[i].fn._ !== callback) liveEvents.push(evts[i]); - } - } - - if (liveEvents.length) { - e[name] = liveEvents; - } else { - delete e[name]; - } - - return this; - } - - } - - var createWebGL = ((gl, openWebglAlignment) => { - var vertexShaderScript = ['attribute vec4 vertexPos;', 'attribute vec4 texturePos;', 'varying vec2 textureCoord;', 'void main()', '{', 'gl_Position = vertexPos;', 'textureCoord = texturePos.xy;', '}'].join('\n'); - var fragmentShaderScript = ['precision highp float;', 'varying highp vec2 textureCoord;', 'uniform sampler2D ySampler;', 'uniform sampler2D uSampler;', 'uniform sampler2D vSampler;', 'const mat4 YUV2RGB = mat4', '(', '1.1643828125, 0, 1.59602734375, -.87078515625,', '1.1643828125, -.39176171875, -.81296875, .52959375,', '1.1643828125, 2.017234375, 0, -1.081390625,', '0, 0, 0, 1', ');', 'void main(void) {', 'highp float y = texture2D(ySampler, textureCoord).r;', 'highp float u = texture2D(uSampler, textureCoord).r;', 'highp float v = texture2D(vSampler, textureCoord).r;', 'gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;', '}'].join('\n'); - - if (openWebglAlignment) { - gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); - } - - var vertexShader = gl.createShader(gl.VERTEX_SHADER); - gl.shaderSource(vertexShader, vertexShaderScript); - gl.compileShader(vertexShader); - - if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) { - console.log('Vertex shader failed to compile: ' + gl.getShaderInfoLog(vertexShader)); - } - - var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); - gl.shaderSource(fragmentShader, fragmentShaderScript); - gl.compileShader(fragmentShader); - - if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) { - console.log('Fragment shader failed to compile: ' + gl.getShaderInfoLog(fragmentShader)); - } - - var program = gl.createProgram(); - gl.attachShader(program, vertexShader); - gl.attachShader(program, fragmentShader); - gl.linkProgram(program); - - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { - console.log('Program failed to compile: ' + gl.getProgramInfoLog(program)); - } - - gl.useProgram(program); // initBuffers - - var vertexPosBuffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer); - gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 1, -1, 1, 1, -1, -1, -1]), gl.STATIC_DRAW); - var vertexPosRef = gl.getAttribLocation(program, 'vertexPos'); - gl.enableVertexAttribArray(vertexPosRef); - gl.vertexAttribPointer(vertexPosRef, 2, gl.FLOAT, false, 0, 0); - var texturePosBuffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer); - gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 0, 0, 0, 1, 1, 0, 1]), gl.STATIC_DRAW); - var texturePosRef = gl.getAttribLocation(program, 'texturePos'); - gl.enableVertexAttribArray(texturePosRef); - gl.vertexAttribPointer(texturePosRef, 2, gl.FLOAT, false, 0, 0); - - function _initTexture(name, index) { - var textureRef = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, textureRef); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.bindTexture(gl.TEXTURE_2D, null); - gl.uniform1i(gl.getUniformLocation(program, name), index); - return textureRef; - } - - var yTextureRef = _initTexture('ySampler', 0); - - var uTextureRef = _initTexture('uSampler', 1); - - var vTextureRef = _initTexture('vSampler', 2); - - return { - render: function (w, h, y, u, v) { - gl.viewport(0, 0, w, h); - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, yTextureRef); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, w, h, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, y); - gl.activeTexture(gl.TEXTURE1); - gl.bindTexture(gl.TEXTURE_2D, uTextureRef); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, w / 2, h / 2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, u); - gl.activeTexture(gl.TEXTURE2); - gl.bindTexture(gl.TEXTURE_2D, vTextureRef); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, w / 2, h / 2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, v); - gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); - }, - destroy: function () { - try { - gl.deleteProgram(program); - gl.deleteBuffer(vertexPosBuffer); - gl.deleteBuffer(texturePosBuffer); - gl.deleteTexture(yTextureRef); - gl.deleteTexture(uTextureRef); - gl.deleteTexture(vTextureRef); - } catch (e) {// console.error(e); - } - } - }; - }); - - class CommonLoader$1 extends Emitter { - constructor() { - super(); - this.init = false; - } - - resetInit() { - this.init = false; - this.videoInfo = { - width: '', - height: '', - encType: '', - encTypeCode: '' - }; - } - - destroy() { - this.resetInit(); - this.player.$container.removeChild(this.$videoElement); - this.off(); - } // - - - updateVideoInfo(data) { - if (data.encTypeCode) { - this.videoInfo.encType = VIDEO_ENC_TYPE[data.encTypeCode]; - } - - if (data.width) { - this.videoInfo.width = data.width; - } - - if (data.height) { - this.videoInfo.height = data.height; - } // video 基本信息 - - - if (this.videoInfo.encType && this.videoInfo.height && this.videoInfo.width && !this.init) { - this.player.emit(EVENTS.videoInfo, this.videoInfo); - this.init = true; - } - } - - play() { } - - pause() { } - - clearView() { } - - } - - /* - * FileSaver.js - * A saveAs() FileSaver implementation. - * - * By Eli Grey, http://eligrey.com - * - * License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT) - * source : http://purl.eligrey.com/github/FileSaver.js - */ - // The one and only way of getting global scope in all environments - // https://stackoverflow.com/q/3277182/1008999 - var _global = typeof window === 'object' && window.window === window ? window : typeof self === 'object' && self.self === self ? self : typeof global === 'object' && global.global === global ? global : undefined; - - function bom(blob, opts) { - if (typeof opts === 'undefined') opts = { - autoBom: false - }; else if (typeof opts !== 'object') { - console.warn('Deprecated: Expected third argument to be a object'); - opts = { - autoBom: !opts - }; - } // prepend BOM for UTF-8 XML and text/* types (including HTML) - // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF - - if (opts.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { - return new Blob([String.fromCharCode(0xFEFF), blob], { - type: blob.type - }); - } - - return blob; - } - - function download(url, name, opts) { - var xhr = new XMLHttpRequest(); - xhr.open('GET', url); - xhr.responseType = 'blob'; - - xhr.onload = function () { - saveAs(xhr.response, name, opts); - }; - - xhr.onerror = function () { - console.error('could not download file'); - }; - - xhr.send(); - } - - function corsEnabled(url) { - var xhr = new XMLHttpRequest(); // use sync to avoid popup blocker - - xhr.open('HEAD', url, false); - - try { - xhr.send(); - } catch (e) { } - - return xhr.status >= 200 && xhr.status <= 299; - } // `a.click()` doesn't work for all browsers (#465) - - - function click(node) { - try { - node.dispatchEvent(new MouseEvent('click')); - } catch (e) { - var evt = document.createEvent('MouseEvents'); - evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null); - node.dispatchEvent(evt); - } - } // Detect WebView inside a native macOS app by ruling out all browsers - // We just need to check for 'Safari' because all other browsers (besides Firefox) include that too - // https://www.whatismybrowser.com/guides/the-latest-user-agent/macos - - - var isMacOSWebView = _global.navigator && /Macintosh/.test(navigator.userAgent) && /AppleWebKit/.test(navigator.userAgent) && !/Safari/.test(navigator.userAgent); - var saveAs = // probably in some web worker - typeof window !== 'object' || window !== _global ? function saveAs() { - /* noop */ - } // Use download attribute first if possible (#193 Lumia mobile) unless this is a macOS WebView - : 'download' in HTMLAnchorElement.prototype && !isMacOSWebView ? function saveAs(blob, name, opts) { - var URL = _global.URL || _global.webkitURL; // Namespace is used to prevent conflict w/ Chrome Poper Blocker extension (Issue #561) - - var a = document.createElementNS('http://www.w3.org/1999/xhtml', 'a'); - name = name || blob.name || 'download'; - a.download = name; - a.rel = 'noopener'; // tabnabbing - // TODO: detect chrome extensions & packaged apps - // a.target = '_blank' - - if (typeof blob === 'string') { - // Support regular links - a.href = blob; - - if (a.origin !== location.origin) { - corsEnabled(a.href) ? download(blob, name, opts) : click(a, a.target = '_blank'); - } else { - click(a); - } - } else { - // Support blobs - a.href = URL.createObjectURL(blob); - setTimeout(function () { - URL.revokeObjectURL(a.href); - }, 4E4); // 40s - - setTimeout(function () { - click(a); - }, 0); - } - } // Use msSaveOrOpenBlob as a second approach - : 'msSaveOrOpenBlob' in navigator ? function saveAs(blob, name, opts) { - name = name || blob.name || 'download'; - - if (typeof blob === 'string') { - if (corsEnabled(blob)) { - download(blob, name, opts); - } else { - var a = document.createElement('a'); - a.href = blob; - a.target = '_blank'; - setTimeout(function () { - click(a); - }); - } - } else { - navigator.msSaveOrOpenBlob(bom(blob, opts), name); - } - } // Fallback to using FileReader and a popup - : function saveAs(blob, name, opts, popup) { - // Open a popup immediately do go around popup blocker - // Mostly only available on user interaction and the fileReader is async so... - popup = popup || open('', '_blank'); - - if (popup) { - popup.document.title = popup.document.body.innerText = 'downloading...'; - } - - if (typeof blob === 'string') return download(blob, name, opts); - var force = blob.type === 'application/octet-stream'; - - var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari; - - var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent); - - if ((isChromeIOS || force && isSafari || isMacOSWebView) && typeof FileReader !== 'undefined') { - // Safari doesn't allow downloading of blob URLs - var reader = new FileReader(); - - reader.onloadend = function () { - var url = reader.result; - url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;'); - if (popup) popup.location.href = url; else location = url; - popup = null; // reverse-tabnabbing #460 - }; - - reader.readAsDataURL(blob); - } else { - var URL = _global.URL || _global.webkitURL; - var url = URL.createObjectURL(blob); - if (popup) popup.location = url; else location.href = url; - popup = null; // reverse-tabnabbing #460 - - setTimeout(function () { - URL.revokeObjectURL(url); - }, 4E4); // 40s - } - }; - - class CanvasVideoLoader extends CommonLoader$1 { - constructor(player) { - super(); - this.player = player; - const $canvasElement = document.createElement("canvas"); - $canvasElement.style.position = "absolute"; - $canvasElement.style.top = 0; - $canvasElement.style.left = 0; - this.$videoElement = $canvasElement; - player.$container.appendChild(this.$videoElement); - this.context2D = null; - this.contextGl = null; - this.contextGlRender = null; - this.contextGlDestroy = null; - this.bitmaprenderer = null; - this.renderType = null; - this.videoInfo = { - width: '', - height: '', - encType: '' - }; // - - this._initCanvasRender(); - - this.player.debug.log('CanvasVideo', 'init'); - } - - destroy() { - super.destroy(); - - if (this.contextGl) { - this.contextGl = null; - } - - if (this.context2D) { - this.context2D = null; - } - - if (this.contextGlRender) { - this.contextGlDestroy && this.contextGlDestroy(); - this.contextGlDestroy = null; - this.contextGlRender = null; - } - - if (this.bitmaprenderer) { - this.bitmaprenderer = null; - } - - this.renderType = null; - this.player.debug.log(`CanvasVideoLoader`, 'destroy'); - } - - _initContextGl() { - this.contextGl = createContextGL(this.$videoElement); - - if (this.contextGl) { - const webgl = createWebGL(this.contextGl, this.player._opt.openWebglAlignment); - this.contextGlRender = webgl.render; - this.contextGlDestroy = webgl.destroy; - } else { - this.player.debug.error(`CanvasVideoLoader`, 'init webgl fail'); - } - } - - _initContext2D() { - this.context2D = this.$videoElement.getContext('2d'); - } // 渲染类型 - - - _initCanvasRender() { - if (this.player._opt.useWCS && !this._supportOffscreen()) { - this.renderType = CANVAS_RENDER_TYPE.webcodecs; - - this._initContext2D(); - } else if (this._supportOffscreen()) { - this.renderType = CANVAS_RENDER_TYPE.offscreen; - - this._bindOffscreen(); - } else { - this.renderType = CANVAS_RENDER_TYPE.webgl; - - this._initContextGl(); - } - } - - _supportOffscreen() { - return supportOffscreen(this.$videoElement) && this.player._opt.useOffscreen; - } // - - - _bindOffscreen() { - this.bitmaprenderer = this.$videoElement.getContext('bitmaprenderer'); - } - - initCanvasViewSize() { - this.$videoElement.width = this.videoInfo.width; - this.$videoElement.height = this.videoInfo.height; - this.resize(); - } // - - - render(msg) { - this.player.videoTimestamp = msg.ts; - - switch (this.renderType) { - case CANVAS_RENDER_TYPE.offscreen: - this.bitmaprenderer.transferFromImageBitmap(msg.buffer); - break; - - case CANVAS_RENDER_TYPE.webgl: - this.contextGlRender(this.$videoElement.width, this.$videoElement.height, msg.output[0], msg.output[1], msg.output[2]); - break; - - case CANVAS_RENDER_TYPE.webcodecs: - // can use createImageBitmap in wexin - this.context2D.drawImage(msg.videoFrame, 0, 0, this.$videoElement.width, this.$videoElement.height); - closeVideoFrame(msg.videoFrame); - break; - } - } - - screenshot(filename, format, quality, type) { - filename = filename || now(); - type = type || SCREENSHOT_TYPE.download; - const formatType = { - png: 'image/png', - jpeg: 'image/jpeg', - webp: 'image/webp' - }; - let encoderOptions = 0.92; - - if (!formatType[format] && SCREENSHOT_TYPE[format]) { - type = format; - format = 'png'; - quality = undefined; - } - - if (typeof quality === "string") { - type = quality; - quality = undefined; - } - - if (typeof quality !== 'undefined') { - encoderOptions = Number(quality); - } - - const dataURL = this.$videoElement.toDataURL(formatType[format] || formatType.png, encoderOptions); - - if (type === SCREENSHOT_TYPE.base64) { - return dataURL; - } else { - const file = dataURLToFile(dataURL); - - if (type === SCREENSHOT_TYPE.blob) { - return file; - } else if (type === SCREENSHOT_TYPE.download) { - // downloadImg(file, filename); - saveAs(file, filename); - } - } - } // - - - clearView() { - switch (this.renderType) { - case CANVAS_RENDER_TYPE.offscreen: - createEmptyImageBitmap(this.$videoElement.width, this.$videoElement.height).then(imageBitMap => { - this.bitmaprenderer.transferFromImageBitmap(imageBitMap); - }); - break; - - case CANVAS_RENDER_TYPE.webgl: - this.contextGl.clear(this.contextGl.COLOR_BUFFER_BIT); - break; - - case CANVAS_RENDER_TYPE.webcodecs: - this.context2D.clearRect(0, 0, this.$videoElement.width, this.$videoElement.height); - break; - } - } - - resize() { - this.player.debug.log('canvasVideo', 'resize'); - const option = this.player._opt; - let width = this.player.width; - let height = this.player.height; - - if (option.hasControl && !option.controlAutoHide) { - if (isMobile() && this.player.fullscreen && option.useWebFullScreen) { - width -= CONTROL_HEIGHT; - } else { - height -= CONTROL_HEIGHT; - } - } - - let resizeWidth = this.$videoElement.width; - let resizeHeight = this.$videoElement.height; - const rotate = option.rotate; - let left = (width - resizeWidth) / 2; - let top = (height - resizeHeight) / 2; - - if (rotate === 270 || rotate === 90) { - resizeWidth = this.$videoElement.height; - resizeHeight = this.$videoElement.width; - } - - const wScale = width / resizeWidth; - const hScale = height / resizeHeight; - let scale = wScale > hScale ? hScale : wScale; // - - if (!option.isResize) { - if (wScale !== hScale) { - scale = wScale + ',' + hScale; - } - } // - - - if (option.isFullResize) { - scale = wScale > hScale ? wScale : hScale; - } - - let transform = "scale(" + scale + ")"; - - if (rotate) { - transform += ' rotate(' + rotate + 'deg)'; - } - - this.$videoElement.style.transform = transform; - this.$videoElement.style.left = left + "px"; - this.$videoElement.style.top = top + "px"; - } - - } - - class VideoLoader extends CommonLoader$1 { - constructor(player) { - super(); - this.player = player; - const $videoElement = document.createElement('video'); - const $canvasElement = document.createElement('canvas'); - $videoElement.muted = true; - $videoElement.style.position = "absolute"; - $videoElement.style.top = 0; - $videoElement.style.left = 0; - this._delayPlay = false; - player.$container.appendChild($videoElement); - this.videoInfo = { - width: '', - height: '', - encType: '' - }; - const _opt = this.player._opt; - - if (_opt.useWCS && _opt.wcsUseVideoRender) { - this.trackGenerator = new MediaStreamTrackGenerator({ - kind: 'video' - }); - $videoElement.srcObject = new MediaStream([this.trackGenerator]); - this.vwriter = this.trackGenerator.writable.getWriter(); - } - - this.$videoElement = $videoElement; - this.$canvasElement = $canvasElement; - this.canvasContext = $canvasElement.getContext('2d'); - this.fixChromeVideoFlashBug(); - this.resize(); - const { - proxy - } = this.player.events; - proxy(this.$videoElement, 'canplay', () => { - this.player.debug.log('Video', `canplay and _delayPlay is ${this._delayPlay}`); - - if (this._delayPlay) { - this._play(); - } - }); - proxy(this.$videoElement, 'waiting', () => { - this.player.emit(EVENTS.videoWaiting); - }); - proxy(this.$videoElement, 'timeupdate', event => { - // this.player.emit(EVENTS.videoTimeUpdate, event.timeStamp); - const timeStamp = parseInt(event.timeStamp, 10); - this.player.emit(EVENTS.timeUpdate, timeStamp); // check is pause; - - if (!this.isPlaying() && this.init) { - this.player.debug.log('Video', `timeupdate and this.isPlaying is false and retry play`); - this.$videoElement.play(); - } - }); - this.player.debug.log('Video', 'init'); - } - - destroy() { - super.destroy(); - this.$canvasElement = null; - this.canvasContext = null; - - if (this.$videoElement) { - this.$videoElement.pause(); - this.$videoElement.currentTime = 0; - this.$videoElement.src = ''; - this.$videoElement.removeAttribute('src'); - this.$videoElement = null; - } - - if (this.trackGenerator) { - this.trackGenerator.stop(); - this.trackGenerator = null; - } - - if (this.vwriter) { - this.vwriter.close(); - this.vwriter = null; - } - - this.player.debug.log('Video', 'destroy'); - } - - fixChromeVideoFlashBug() { - const browser = getBrowser(); - const type = browser.type.toLowerCase(); - - if (type === 'chrome' || type === 'edge') { - const $container = this.player.$container; - $container.style.backdropFilter = 'blur(0px)'; - $container.style.translateZ = '0'; - } - } - - play() { - if (this.$videoElement) { - const readyState = this._getVideoReadyState(); - - this.player.debug.log('Video', `play and readyState: ${readyState}`); - - if (readyState === 0) { - this.player.debug.warn('Video', 'readyState is 0 and set _delayPlay to true'); - this._delayPlay = true; - return; - } - - this._play(); - } - } - - _getVideoReadyState() { - let result = 0; - - if (this.$videoElement) { - result = this.$videoElement.readyState; - } - - return result; - } - - _play() { - this.$videoElement && this.$videoElement.play().then(() => { - this._delayPlay = false; - this.player.debug.log('Video', '_play success'); - setTimeout(() => { - if (!this.isPlaying()) { - this.player.debug.warn('Video', `play failed and retry play`); - - this._play(); - } - }, 100); - }).catch(e => { - this.player.debug.error('Video', '_play error', e); - }); - } - - pause(isNow) { - // 预防 - // https://developer.chrome.com/blog/play-request-was-interrupted/ - // http://alonesuperman.com/?p=23 - if (isNow) { - this.$videoElement && this.$videoElement.pause(); - } else { - setTimeout(() => { - this.$videoElement && this.$videoElement.pause(); - }, 100); - } - } - - clearView() { } - - screenshot(filename, format, quality, type) { - filename = filename || now(); - type = type || SCREENSHOT_TYPE.download; - const formatType = { - png: 'image/png', - jpeg: 'image/jpeg', - webp: 'image/webp' - }; - let encoderOptions = 0.92; - - if (!formatType[format] && SCREENSHOT_TYPE[format]) { - type = format; - format = 'png'; - quality = undefined; - } - - if (typeof quality === "string") { - type = quality; - quality = undefined; - } - - if (typeof quality !== 'undefined') { - encoderOptions = Number(quality); - } - - const $video = this.$videoElement; - let canvas = this.$canvasElement; - canvas.width = $video.videoWidth; - canvas.height = $video.videoHeight; - this.canvasContext.drawImage($video, 0, 0, canvas.width, canvas.height); - const dataURL = canvas.toDataURL(formatType[format] || formatType.png, encoderOptions); // release memory - - this.canvasContext.clearRect(0, 0, canvas.width, canvas.height); - canvas.width = 0; - canvas.height = 0; - - if (type === SCREENSHOT_TYPE.base64) { - return dataURL; - } else { - const file = dataURLToFile(dataURL); - - if (type === SCREENSHOT_TYPE.blob) { - return file; - } else if (type === SCREENSHOT_TYPE.download) { - // downloadImg(file, filename); - saveAs(file, filename); - } - } - } - - initCanvasViewSize() { - this.resize(); - } // - - - render(msg) { - if (this.vwriter) { - this.vwriter.write(msg.videoFrame); - } - } - - resize() { - let width = this.player.width; - let height = this.player.height; - const option = this.player._opt; - const rotate = option.rotate; - - if (option.hasControl && !option.controlAutoHide) { - if (isMobile() && this.player.fullscreen && option.useWebFullScreen) { - width -= CONTROL_HEIGHT; - } else { - height -= CONTROL_HEIGHT; - } - } - - this.$videoElement.width = width; - this.$videoElement.height = height; - - if (rotate === 270 || rotate === 90) { - this.$videoElement.width = height; - this.$videoElement.height = width; - } - - let resizeWidth = this.$videoElement.width; - let resizeHeight = this.$videoElement.height; - let left = (width - resizeWidth) / 2; - let top = (height - resizeHeight) / 2; - let objectFill = 'contain'; // 默认是true - // 视频画面做等比缩放后,高或宽对齐canvas区域,画面不被拉伸,但有黑边 - // 视频画面完全填充canvas区域,画面会被拉伸 - - if (!option.isResize) { - objectFill = 'fill'; - } // 视频画面做等比缩放后,完全填充canvas区域,画面不被拉伸,没有黑边,但画面显示不全 - - - if (option.isFullResize) { - objectFill = 'none'; - } - - this.$videoElement.style.objectFit = objectFill; - this.$videoElement.style.transform = 'rotate(' + rotate + 'deg)'; - this.$videoElement.style.left = left + "px"; - this.$videoElement.style.top = top + "px"; - } - - isPlaying() { - return this.$videoElement && !this.$videoElement.paused; - } - - } - - class Video { - constructor(player) { - const Loader = Video.getLoaderFactory(player._opt); - return new Loader(player); - } - - static getLoaderFactory(opt) { - if (opt.useMSE || opt.useWCS && !opt.useOffscreen && opt.wcsUseVideoRender) { - return VideoLoader; - } else { - return CanvasVideoLoader; - } - } - - } - - class AudioContextLoader extends Emitter { - constructor(player) { - super(); - this.bufferList = []; - this.player = player; - this.scriptNode = null; - this.hasInitScriptNode = false; - this.audioContextChannel = null; - this.audioContext = new (window.AudioContext || window.webkitAudioContext)(); // - - this.gainNode = this.audioContext.createGain(); // Get an AudioBufferSourceNode. - // This is the AudioNode to use when we want to play an AudioBuffer - - const source = this.audioContext.createBufferSource(); // set the buffer in the AudioBufferSourceNode - - source.buffer = this.audioContext.createBuffer(1, 1, 22050); // connect the AudioBufferSourceNode to the - // destination so we can hear the sound - - source.connect(this.audioContext.destination); // noteOn as start - // start the source playing - - if (source.noteOn) { - source.noteOn(0); - } else { - source.start(0); - } - - this.audioBufferSourceNode = source; // - - this.mediaStreamAudioDestinationNode = this.audioContext.createMediaStreamDestination(); // - - this.audioEnabled(true); // default setting 0 - - this.gainNode.gain.value = 0; - this.playing = false; // - - this.audioSyncVideoOption = { - diff: null - }; - this.audioInfo = { - encType: '', - channels: '', - sampleRate: '' - }; - this.init = false; - this.hasAudio = false; // update - - this.on(EVENTS.videoSyncAudio, options => { - // this.player.debug.log('AudioContext', `videoSyncAudio , audioTimestamp: ${options.audioTimestamp},videoTimestamp: ${options.videoTimestamp},diff:${options.diff}`) - this.audioSyncVideoOption = options; - }); - this.player.debug.log('AudioContext', 'init'); - } - - resetInit() { - this.init = false; - this.audioInfo = { - encType: '', - channels: '', - sampleRate: '' - }; - } - - destroy() { - this.closeAudio(); - this.resetInit(); - this.audioContext.close(); - this.audioContext = null; - this.gainNode = null; - this.hasAudio = false; - this.playing = false; - - if (this.scriptNode) { - this.scriptNode.onaudioprocess = noop; - this.scriptNode = null; - } - - this.audioBufferSourceNode = null; - this.mediaStreamAudioDestinationNode = null; - this.hasInitScriptNode = false; - this.audioSyncVideoOption = { - diff: null - }; - this.off(); - this.player.debug.log('AudioContext', 'destroy'); - } - - updateAudioInfo(data) { - if (data.encTypeCode) { - this.audioInfo.encType = AUDIO_ENC_TYPE[data.encTypeCode]; - } - - if (data.channels) { - this.audioInfo.channels = data.channels; - } - - if (data.sampleRate) { - this.audioInfo.sampleRate = data.sampleRate; - } // audio 基本信息 - - - if (this.audioInfo.sampleRate && this.audioInfo.channels && this.audioInfo.encType && !this.init) { - this.player.emit(EVENTS.audioInfo, this.audioInfo); - this.init = true; - } - } // - - - get isPlaying() { - return this.playing; - } - - get isMute() { - return this.gainNode.gain.value === 0; - } - - get volume() { - return this.gainNode.gain.value; - } - - get bufferSize() { - return this.bufferList.length; - } - - initScriptNode() { - this.playing = true; - - if (this.hasInitScriptNode) { - return; - } - - const channels = this.audioInfo.channels; - const scriptNode = this.audioContext.createScriptProcessor(1024, 0, channels); // tips: if audio isStateSuspended onaudioprocess method not working - - scriptNode.onaudioprocess = audioProcessingEvent => { - const outputBuffer = audioProcessingEvent.outputBuffer; - - if (this.bufferList.length && this.playing) { - // just for wasm - if (!this.player._opt.useWCS && !this.player._opt.useMSE && this.player._opt.wasmDecodeAudioSyncVideo) { - // audio > video - // wait - if (this.audioSyncVideoOption.diff > AUDIO_SYNC_VIDEO_DIFF) { - this.player.debug.warn('AudioContext', `audioSyncVideoOption more than diff :${this.audioSyncVideoOption.diff}, waiting`); // wait - - return; - } // audio < video - // throw away then chase video - else if (this.audioSyncVideoOption.diff < -AUDIO_SYNC_VIDEO_DIFF) { - this.player.debug.warn('AudioContext', `audioSyncVideoOption less than diff :${this.audioSyncVideoOption.diff}, dropping`); // - - let bufferItem = this.bufferList.shift(); // - - while (bufferItem.ts - this.player.videoTimestamp < -AUDIO_SYNC_VIDEO_DIFF && this.bufferList.length > 0) { - // this.player.debug.warn('AudioContext', `audioSyncVideoOption less than inner ts is:${bufferItem.ts}, videoTimestamp is ${this.player.videoTimestamp},diff:${bufferItem.ts - this.player.videoTimestamp}`) - bufferItem = this.bufferList.shift(); - } - - if (this.bufferList.length === 0) { - return; - } - } - } - - if (this.bufferList.length === 0) { - return; - } - - const bufferItem = this.bufferList.shift(); // update audio time stamp - - if (bufferItem && bufferItem.ts) { - this.player.audioTimestamp = bufferItem.ts; - } - - for (let channel = 0; channel < channels; channel++) { - const b = bufferItem.buffer[channel]; - const nowBuffering = outputBuffer.getChannelData(channel); - - for (let i = 0; i < 1024; i++) { - nowBuffering[i] = b[i] || 0; - } - } - } - }; - - scriptNode.connect(this.gainNode); - this.scriptNode = scriptNode; - this.gainNode.connect(this.audioContext.destination); - this.gainNode.connect(this.mediaStreamAudioDestinationNode); - this.hasInitScriptNode = true; - } - - mute(flag) { - if (flag) { - if (!this.isMute) { - this.player.emit(EVENTS.mute, flag); - } - - this.setVolume(0); - this.clear(); - } else { - if (this.isMute) { - this.player.emit(EVENTS.mute, flag); - } - - this.setVolume(0.5); - } - } - - setVolume(volume) { - volume = parseFloat(volume).toFixed(2); - - if (isNaN(volume)) { - return; - } - - this.audioEnabled(true); - volume = clamp(volume, 0, 1); - this.gainNode.gain.value = volume; - this.gainNode.gain.setValueAtTime(volume, this.audioContext.currentTime); - this.player.emit(EVENTS.volumechange, this.player.volume); - } - - closeAudio() { - if (this.hasInitScriptNode) { - this.scriptNode && this.scriptNode.disconnect(this.gainNode); - this.gainNode && this.gainNode.disconnect(this.audioContext.destination); - this.gainNode && this.gainNode.disconnect(this.mediaStreamAudioDestinationNode); - } - - this.clear(); - } // 是否播放。。。 - - - audioEnabled(flag) { - if (flag) { - if (this.audioContext.state === 'suspended') { - // resume - this.audioContext.resume(); - } - } else { - if (this.audioContext.state === 'running') { - // suspend - this.audioContext.suspend(); - } - } - } - - isStateRunning() { - return this.audioContext.state === 'running'; - } - - isStateSuspended() { - return this.audioContext.state === 'suspended'; - } - - clear() { - this.bufferList = []; - } - - play(buffer, ts) { - // if is mute - if (this.isMute) { - return; - } - - this.hasAudio = true; - this.bufferList.push({ - buffer, - ts - }); - - if (this.bufferList.length > 20) { - this.player.debug.warn('AudioContext', `bufferList is large: ${this.bufferList.length}`); // out of memory - - if (this.bufferList.length > 50) { - this.bufferList.shift(); - } - } // this.player.debug.log('AudioContext', `bufferList is ${this.bufferList.length}`) - - } - - pause() { - this.audioSyncVideoOption = { - diff: null - }; - this.playing = false; - this.clear(); - } - - resume() { - this.playing = true; - } - - } - - class Audio { - constructor(player) { - const Loader = Audio.getLoaderFactory(); - return new Loader(player); - } - - static getLoaderFactory() { - return AudioContextLoader; - } - - } - - class FetchLoader extends Emitter { - constructor(player) { - super(); - this.player = player; - this.playing = false; - this.abortController = new AbortController(); // - - this.streamRate = calculationRate(rate => { - player.emit(EVENTS.kBps, (rate / 1024).toFixed(2)); - }); - player.debug.log('FetchStream', 'init'); - } - - destroy() { - this.abort(); - this.off(); - this.streamRate = null; - this.player.debug.log('FetchStream', 'destroy'); - } - /** - * - * @param url - * @param options - */ - - - fetchStream(url) { - let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - const { - demux - } = this.player; - this.player.debug.log('FetchStream', 'fetchStream', url, JSON.stringify(options)); - this.player._times.streamStart = now(); - const fetchOptions = Object.assign({ - signal: this.abortController.signal - }, { - headers: options.headers || {} - }); - fetch(url, fetchOptions).then(res => { - const reader = res.body.getReader(); - this.emit(EVENTS.streamSuccess); - - const fetchNext = () => { - reader.read().then(_ref => { - let { - done, - value - } = _ref; - - if (done) { - demux.close(); - } else { - this.streamRate && this.streamRate(value.byteLength); - demux.dispatch(value); - fetchNext(); - } - }).catch(e => { - demux.close(); - const errorString = e.toString(); // aborted a request 。 - - if (errorString.indexOf(FETCH_ERROR.abortError1) !== -1) { - return; - } - - if (errorString.indexOf(FETCH_ERROR.abortError2) !== -1) { - return; - } - - if (e.name === FETCH_ERROR.abort) { - return; - } - - this.abort(); - this.emit(EVENTS_ERROR.fetchError, e); - this.player.emit(EVENTS.error, EVENTS_ERROR.fetchError); - }); - }; - - fetchNext(); - }).catch(e => { - if (e.name === 'AbortError') { - return; - } - - demux.close(); - this.abort(); - this.emit(EVENTS_ERROR.fetchError, e); - this.player.emit(EVENTS.error, EVENTS_ERROR.fetchError); - }); - } - - abort() { - if (this.abortController) { - this.abortController.abort(); - this.abortController = null; - } - } - - } - - class WebsocketLoader extends Emitter { - constructor(player) { - super(); - this.player = player; - this.socket = null; - this.socketStatus = WEBSOCKET_STATUS.notConnect; - this.wsUrl = null; // - - this.streamRate = calculationRate(rate => { - player.emit(EVENTS.kBps, (rate / 1024).toFixed(2)); - }); - player.debug.log('WebsocketLoader', 'init'); - } - - destroy() { - if (this.socket) { - this.socket.close(1000, 'Client disconnecting'); - this.socket = null; - } - - this.socketStatus = WEBSOCKET_STATUS.notConnect; - this.streamRate = null; - this.wsUrl = null; - this.off(); - this.player.debug.log('websocketLoader', 'destroy'); - } - - _createWebSocket() { - const player = this.player; - const { - debug, - events: { - proxy - }, - demux - } = player; - this.socket = new WebSocket(this.wsUrl); - this.socket.binaryType = 'arraybuffer'; - proxy(this.socket, 'open', () => { - this.emit(EVENTS.streamSuccess); - debug.log('websocketLoader', 'socket open'); - this.socketStatus = WEBSOCKET_STATUS.open; - }); - proxy(this.socket, 'message', event => { - this.streamRate && this.streamRate(event.data.byteLength); - - this._handleMessage(event.data); - }); - proxy(this.socket, 'close', () => { - debug.log('websocketLoader', 'socket close'); - this.emit(EVENTS.streamEnd); - this.socketStatus = WEBSOCKET_STATUS.close; - }); - proxy(this.socket, 'error', error => { - debug.log('websocketLoader', 'socket error'); - this.emit(EVENTS_ERROR.websocketError, error); - this.player.emit(EVENTS.error, EVENTS_ERROR.websocketError); - this.socketStatus = WEBSOCKET_STATUS.error; - demux.close(); - debug.log('websocketLoader', `socket error:`, error); - }); - } // - - - _handleMessage(message) { - const { - demux - } = this.player; - - if (!demux) { - this.player.debug.warn('websocketLoader', 'websocket handle message demux is null'); - return; - } - - demux.dispatch(message); - } - /** - * - * @param url - * @param options - */ - - - fetchStream(url, options) { - this.player._times.streamStart = now(); - this.wsUrl = url; - - this._createWebSocket(); - } - - } - - class Stream { - constructor(player) { - const Loader = Stream.getLoaderFactory(player._opt.protocol); - return new Loader(player); - } - - static getLoaderFactory(protocol) { - if (protocol === PLAYER_PLAY_PROTOCOL.fetch) { - return FetchLoader; - } else if (protocol === PLAYER_PLAY_PROTOCOL.websocket) { - return WebsocketLoader; - } - } - - } - - var RecordRTC_1 = createCommonjsModule(function (module) { - - // Last time updated: 2021-03-09 3:20:22 AM UTC - - // ________________ - // RecordRTC v5.6.2 - - // Open-Sourced: https://github.com/muaz-khan/RecordRTC - - // -------------------------------------------------- - // Muaz Khan - www.MuazKhan.com - // MIT License - www.WebRTC-Experiment.com/licence - // -------------------------------------------------- - - // ____________ - // RecordRTC.js - - /** - * {@link https://github.com/muaz-khan/RecordRTC|RecordRTC} is a WebRTC JavaScript library for audio/video as well as screen activity recording. It supports Chrome, Firefox, Opera, Android, and Microsoft Edge. Platforms: Linux, Mac and Windows. - * @summary Record audio, video or screen inside the browser. - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @typedef RecordRTC - * @class - * @example - * var recorder = RecordRTC(mediaStream or [arrayOfMediaStream], { - * type: 'video', // audio or video or gif or canvas - * recorderType: MediaStreamRecorder || CanvasRecorder || StereoAudioRecorder || Etc - * }); - * recorder.startRecording(); - * @see For further information: - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - * @param {MediaStream} mediaStream - Single media-stream object, array of media-streams, html-canvas-element, etc. - * @param {object} config - {type:"video", recorderType: MediaStreamRecorder, disableLogs: true, numberOfAudioChannels: 1, bufferSize: 0, sampleRate: 0, desiredSampRate: 16000, video: HTMLVideoElement, etc.} - */ - - function RecordRTC(mediaStream, config) { - if (!mediaStream) { - throw 'First parameter is required.'; - } - - config = config || { - type: 'video' - }; - - config = new RecordRTCConfiguration(mediaStream, config); - - // a reference to user's recordRTC object - var self = this; - - function startRecording(config2) { - if (!config.disableLogs) { - console.log('RecordRTC version: ', self.version); - } - - if (!!config2) { - // allow users to set options using startRecording method - // config2 is similar to main "config" object (second parameter over RecordRTC constructor) - config = new RecordRTCConfiguration(mediaStream, config2); - } - - if (!config.disableLogs) { - console.log('started recording ' + config.type + ' stream.'); - } - - if (mediaRecorder) { - mediaRecorder.clearRecordedData(); - mediaRecorder.record(); - - setState('recording'); - - if (self.recordingDuration) { - handleRecordingDuration(); - } - return self; - } - - initRecorder(function () { - if (self.recordingDuration) { - handleRecordingDuration(); - } - }); - - return self; - } - - function initRecorder(initCallback) { - if (initCallback) { - config.initCallback = function () { - initCallback(); - initCallback = config.initCallback = null; // recorder.initRecorder should be call-backed once. - }; - } - - var Recorder = new GetRecorderType(mediaStream, config); - - mediaRecorder = new Recorder(mediaStream, config); - mediaRecorder.record(); - - setState('recording'); - - if (!config.disableLogs) { - console.log('Initialized recorderType:', mediaRecorder.constructor.name, 'for output-type:', config.type); - } - } - - function stopRecording(callback) { - callback = callback || function () { }; - - if (!mediaRecorder) { - warningLog(); - return; - } - - if (self.state === 'paused') { - self.resumeRecording(); - - setTimeout(function () { - stopRecording(callback); - }, 1); - return; - } - - if (self.state !== 'recording' && !config.disableLogs) { - console.warn('Recording state should be: "recording", however current state is: ', self.state); - } - - if (!config.disableLogs) { - console.log('Stopped recording ' + config.type + ' stream.'); - } - - if (config.type !== 'gif') { - mediaRecorder.stop(_callback); - } else { - mediaRecorder.stop(); - _callback(); - } - - setState('stopped'); - - function _callback(__blob) { - if (!mediaRecorder) { - if (typeof callback.call === 'function') { - callback.call(self, ''); - } else { - callback(''); - } - return; - } - - Object.keys(mediaRecorder).forEach(function (key) { - if (typeof mediaRecorder[key] === 'function') { - return; - } - - self[key] = mediaRecorder[key]; - }); - - var blob = mediaRecorder.blob; - - if (!blob) { - if (__blob) { - mediaRecorder.blob = blob = __blob; - } else { - throw 'Recording failed.'; - } - } - - if (blob && !config.disableLogs) { - console.log(blob.type, '->', bytesToSize(blob.size)); - } - - if (callback) { - var url; - - try { - url = URL.createObjectURL(blob); - } catch (e) { } - - if (typeof callback.call === 'function') { - callback.call(self, url); - } else { - callback(url); - } - } - - if (!config.autoWriteToDisk) { - return; - } - - getDataURL(function (dataURL) { - var parameter = {}; - parameter[config.type + 'Blob'] = dataURL; - DiskStorage.Store(parameter); - }); - } - } - - function pauseRecording() { - if (!mediaRecorder) { - warningLog(); - return; - } - - if (self.state !== 'recording') { - if (!config.disableLogs) { - console.warn('Unable to pause the recording. Recording state: ', self.state); - } - return; - } - - setState('paused'); - - mediaRecorder.pause(); - - if (!config.disableLogs) { - console.log('Paused recording.'); - } - } - - function resumeRecording() { - if (!mediaRecorder) { - warningLog(); - return; - } - - if (self.state !== 'paused') { - if (!config.disableLogs) { - console.warn('Unable to resume the recording. Recording state: ', self.state); - } - return; - } - - setState('recording'); - - // not all libs have this method yet - mediaRecorder.resume(); - - if (!config.disableLogs) { - console.log('Resumed recording.'); - } - } - - function readFile(_blob) { - postMessage(new FileReaderSync().readAsDataURL(_blob)); - } - - function getDataURL(callback, _mediaRecorder) { - if (!callback) { - throw 'Pass a callback function over getDataURL.'; - } - - var blob = _mediaRecorder ? _mediaRecorder.blob : (mediaRecorder || {}).blob; - - if (!blob) { - if (!config.disableLogs) { - console.warn('Blob encoder did not finish its job yet.'); - } - - setTimeout(function () { - getDataURL(callback, _mediaRecorder); - }, 1000); - return; - } - - if (typeof Worker !== 'undefined' && !navigator.mozGetUserMedia) { - var webWorker = processInWebWorker(readFile); - - webWorker.onmessage = function (event) { - callback(event.data); - }; - - webWorker.postMessage(blob); - } else { - var reader = new FileReader(); - reader.readAsDataURL(blob); - reader.onload = function (event) { - callback(event.target.result); - }; - } - - function processInWebWorker(_function) { - try { - var blob = URL.createObjectURL(new Blob([_function.toString(), - 'this.onmessage = function (eee) {' + _function.name + '(eee.data);}' - ], { - type: 'application/javascript' - })); - - var worker = new Worker(blob); - URL.revokeObjectURL(blob); - return worker; - } catch (e) { } - } - } - - function handleRecordingDuration(counter) { - counter = counter || 0; - - if (self.state === 'paused') { - setTimeout(function () { - handleRecordingDuration(counter); - }, 1000); - return; - } - - if (self.state === 'stopped') { - return; - } - - if (counter >= self.recordingDuration) { - stopRecording(self.onRecordingStopped); - return; - } - - counter += 1000; // 1-second - - setTimeout(function () { - handleRecordingDuration(counter); - }, 1000); - } - - function setState(state) { - if (!self) { - return; - } - - self.state = state; - - if (typeof self.onStateChanged.call === 'function') { - self.onStateChanged.call(self, state); - } else { - self.onStateChanged(state); - } - } - - var WARNING = 'It seems that recorder is destroyed or "startRecording" is not invoked for ' + config.type + ' recorder.'; - - function warningLog() { - if (config.disableLogs === true) { - return; - } - - console.warn(WARNING); - } - - var mediaRecorder; - - var returnObject = { - /** - * This method starts the recording. - * @method - * @memberof RecordRTC - * @instance - * @example - * var recorder = RecordRTC(mediaStream, { - * type: 'video' - * }); - * recorder.startRecording(); - */ - startRecording: startRecording, - - /** - * This method stops the recording. It is strongly recommended to get "blob" or "URI" inside the callback to make sure all recorders finished their job. - * @param {function} callback - Callback to get the recorded blob. - * @method - * @memberof RecordRTC - * @instance - * @example - * recorder.stopRecording(function() { - * // use either "this" or "recorder" object; both are identical - * video.src = this.toURL(); - * var blob = this.getBlob(); - * }); - */ - stopRecording: stopRecording, - - /** - * This method pauses the recording. You can resume recording using "resumeRecording" method. - * @method - * @memberof RecordRTC - * @instance - * @todo Firefox is unable to pause the recording. Fix it. - * @example - * recorder.pauseRecording(); // pause the recording - * recorder.resumeRecording(); // resume again - */ - pauseRecording: pauseRecording, - - /** - * This method resumes the recording. - * @method - * @memberof RecordRTC - * @instance - * @example - * recorder.pauseRecording(); // first of all, pause the recording - * recorder.resumeRecording(); // now resume it - */ - resumeRecording: resumeRecording, - - /** - * This method initializes the recording. - * @method - * @memberof RecordRTC - * @instance - * @todo This method should be deprecated. - * @example - * recorder.initRecorder(); - */ - initRecorder: initRecorder, - - /** - * Ask RecordRTC to auto-stop the recording after 5 minutes. - * @method - * @memberof RecordRTC - * @instance - * @example - * var fiveMinutes = 5 * 1000 * 60; - * recorder.setRecordingDuration(fiveMinutes, function() { - * var blob = this.getBlob(); - * video.src = this.toURL(); - * }); - * - * // or otherwise - * recorder.setRecordingDuration(fiveMinutes).onRecordingStopped(function() { - * var blob = this.getBlob(); - * video.src = this.toURL(); - * }); - */ - setRecordingDuration: function (recordingDuration, callback) { - if (typeof recordingDuration === 'undefined') { - throw 'recordingDuration is required.'; - } - - if (typeof recordingDuration !== 'number') { - throw 'recordingDuration must be a number.'; - } - - self.recordingDuration = recordingDuration; - self.onRecordingStopped = callback || function () { }; - - return { - onRecordingStopped: function (callback) { - self.onRecordingStopped = callback; - } - }; - }, - - /** - * This method can be used to clear/reset all the recorded data. - * @method - * @memberof RecordRTC - * @instance - * @todo Figure out the difference between "reset" and "clearRecordedData" methods. - * @example - * recorder.clearRecordedData(); - */ - clearRecordedData: function () { - if (!mediaRecorder) { - warningLog(); - return; - } - - mediaRecorder.clearRecordedData(); - - if (!config.disableLogs) { - console.log('Cleared old recorded data.'); - } - }, - - /** - * Get the recorded blob. Use this method inside the "stopRecording" callback. - * @method - * @memberof RecordRTC - * @instance - * @example - * recorder.stopRecording(function() { - * var blob = this.getBlob(); - * - * var file = new File([blob], 'filename.webm', { - * type: 'video/webm' - * }); - * - * var formData = new FormData(); - * formData.append('file', file); // upload "File" object rather than a "Blob" - * uploadToServer(formData); - * }); - * @returns {Blob} Returns recorded data as "Blob" object. - */ - getBlob: function () { - if (!mediaRecorder) { - warningLog(); - return; - } - - return mediaRecorder.blob; - }, - - /** - * Get data-URI instead of Blob. - * @param {function} callback - Callback to get the Data-URI. - * @method - * @memberof RecordRTC - * @instance - * @example - * recorder.stopRecording(function() { - * recorder.getDataURL(function(dataURI) { - * video.src = dataURI; - * }); - * }); - */ - getDataURL: getDataURL, - - /** - * Get virtual/temporary URL. Usage of this URL is limited to current tab. - * @method - * @memberof RecordRTC - * @instance - * @example - * recorder.stopRecording(function() { - * video.src = this.toURL(); - * }); - * @returns {String} Returns a virtual/temporary URL for the recorded "Blob". - */ - toURL: function () { - if (!mediaRecorder) { - warningLog(); - return; - } - - return URL.createObjectURL(mediaRecorder.blob); - }, - - /** - * Get internal recording object (i.e. internal module) e.g. MutliStreamRecorder, MediaStreamRecorder, StereoAudioRecorder or WhammyRecorder etc. - * @method - * @memberof RecordRTC - * @instance - * @example - * var internalRecorder = recorder.getInternalRecorder(); - * if(internalRecorder instanceof MultiStreamRecorder) { - * internalRecorder.addStreams([newAudioStream]); - * internalRecorder.resetVideoStreams([screenStream]); - * } - * @returns {Object} Returns internal recording object. - */ - getInternalRecorder: function () { - return mediaRecorder; - }, - - /** - * Invoke save-as dialog to save the recorded blob into your disk. - * @param {string} fileName - Set your own file name. - * @method - * @memberof RecordRTC - * @instance - * @example - * recorder.stopRecording(function() { - * this.save('file-name'); - * - * // or manually: - * invokeSaveAsDialog(this.getBlob(), 'filename.webm'); - * }); - */ - save: function (fileName) { - if (!mediaRecorder) { - warningLog(); - return; - } - - invokeSaveAsDialog(mediaRecorder.blob, fileName); - }, - - /** - * This method gets a blob from indexed-DB storage. - * @param {function} callback - Callback to get the recorded blob. - * @method - * @memberof RecordRTC - * @instance - * @example - * recorder.getFromDisk(function(dataURL) { - * video.src = dataURL; - * }); - */ - getFromDisk: function (callback) { - if (!mediaRecorder) { - warningLog(); - return; - } - - RecordRTC.getFromDisk(config.type, callback); - }, - - /** - * This method appends an array of webp images to the recorded video-blob. It takes an "array" object. - * @type {Array.} - * @param {Array} arrayOfWebPImages - Array of webp images. - * @method - * @memberof RecordRTC - * @instance - * @todo This method should be deprecated. - * @example - * var arrayOfWebPImages = []; - * arrayOfWebPImages.push({ - * duration: index, - * image: 'data:image/webp;base64,...' - * }); - * recorder.setAdvertisementArray(arrayOfWebPImages); - */ - setAdvertisementArray: function (arrayOfWebPImages) { - config.advertisement = []; - - var length = arrayOfWebPImages.length; - for (var i = 0; i < length; i++) { - config.advertisement.push({ - duration: i, - image: arrayOfWebPImages[i] - }); - } - }, - - /** - * It is equivalent to "recorder.getBlob()" method. Usage of "getBlob" is recommended, though. - * @property {Blob} blob - Recorded Blob can be accessed using this property. - * @memberof RecordRTC - * @instance - * @readonly - * @example - * recorder.stopRecording(function() { - * var blob = this.blob; - * - * // below one is recommended - * var blob = this.getBlob(); - * }); - */ - blob: null, - - /** - * This works only with {recorderType:StereoAudioRecorder}. Use this property on "stopRecording" to verify the encoder's sample-rates. - * @property {number} bufferSize - Buffer-size used to encode the WAV container - * @memberof RecordRTC - * @instance - * @readonly - * @example - * recorder.stopRecording(function() { - * alert('Recorder used this buffer-size: ' + this.bufferSize); - * }); - */ - bufferSize: 0, - - /** - * This works only with {recorderType:StereoAudioRecorder}. Use this property on "stopRecording" to verify the encoder's sample-rates. - * @property {number} sampleRate - Sample-rates used to encode the WAV container - * @memberof RecordRTC - * @instance - * @readonly - * @example - * recorder.stopRecording(function() { - * alert('Recorder used these sample-rates: ' + this.sampleRate); - * }); - */ - sampleRate: 0, - - /** - * {recorderType:StereoAudioRecorder} returns ArrayBuffer object. - * @property {ArrayBuffer} buffer - Audio ArrayBuffer, supported only in Chrome. - * @memberof RecordRTC - * @instance - * @readonly - * @example - * recorder.stopRecording(function() { - * var arrayBuffer = this.buffer; - * alert(arrayBuffer.byteLength); - * }); - */ - buffer: null, - - /** - * This method resets the recorder. So that you can reuse single recorder instance many times. - * @method - * @memberof RecordRTC - * @instance - * @example - * recorder.reset(); - * recorder.startRecording(); - */ - reset: function () { - if (self.state === 'recording' && !config.disableLogs) { - console.warn('Stop an active recorder.'); - } - - if (mediaRecorder && typeof mediaRecorder.clearRecordedData === 'function') { - mediaRecorder.clearRecordedData(); - } - mediaRecorder = null; - setState('inactive'); - self.blob = null; - }, - - /** - * This method is called whenever recorder's state changes. Use this as an "event". - * @property {String} state - A recorder's state can be: recording, paused, stopped or inactive. - * @method - * @memberof RecordRTC - * @instance - * @example - * recorder.onStateChanged = function(state) { - * console.log('Recorder state: ', state); - * }; - */ - onStateChanged: function (state) { - if (!config.disableLogs) { - console.log('Recorder state changed:', state); - } - }, - - /** - * A recorder can have inactive, recording, paused or stopped states. - * @property {String} state - A recorder's state can be: recording, paused, stopped or inactive. - * @memberof RecordRTC - * @static - * @readonly - * @example - * // this looper function will keep you updated about the recorder's states. - * (function looper() { - * document.querySelector('h1').innerHTML = 'Recorder\'s state is: ' + recorder.state; - * if(recorder.state === 'stopped') return; // ignore+stop - * setTimeout(looper, 1000); // update after every 3-seconds - * })(); - * recorder.startRecording(); - */ - state: 'inactive', - - /** - * Get recorder's readonly state. - * @method - * @memberof RecordRTC - * @example - * var state = recorder.getState(); - * @returns {String} Returns recording state. - */ - getState: function () { - return self.state; - }, - - /** - * Destroy RecordRTC instance. Clear all recorders and objects. - * @method - * @memberof RecordRTC - * @example - * recorder.destroy(); - */ - destroy: function () { - var disableLogsCache = config.disableLogs; - - config = { - disableLogs: true - }; - self.reset(); - setState('destroyed'); - returnObject = self = null; - - if (Storage.AudioContextConstructor) { - Storage.AudioContextConstructor.close(); - Storage.AudioContextConstructor = null; - } - - config.disableLogs = disableLogsCache; - - if (!config.disableLogs) { - console.log('RecordRTC is destroyed.'); - } - }, - - /** - * RecordRTC version number - * @property {String} version - Release version number. - * @memberof RecordRTC - * @static - * @readonly - * @example - * alert(recorder.version); - */ - version: '5.6.2' - }; - - if (!this) { - self = returnObject; - return returnObject; - } - - // if someone wants to use RecordRTC with the "new" keyword. - for (var prop in returnObject) { - this[prop] = returnObject[prop]; - } - - self = this; - - return returnObject; - } - - RecordRTC.version = '5.6.2'; - - { - module.exports = RecordRTC; - } - - RecordRTC.getFromDisk = function (type, callback) { - if (!callback) { - throw 'callback is mandatory.'; - } - - console.log('Getting recorded ' + (type === 'all' ? 'blobs' : type + ' blob ') + ' from disk!'); - DiskStorage.Fetch(function (dataURL, _type) { - if (type !== 'all' && _type === type + 'Blob' && callback) { - callback(dataURL); - } - - if (type === 'all' && callback) { - callback(dataURL, _type.replace('Blob', '')); - } - }); - }; - - /** - * This method can be used to store recorded blobs into IndexedDB storage. - * @param {object} options - {audio: Blob, video: Blob, gif: Blob} - * @method - * @memberof RecordRTC - * @example - * RecordRTC.writeToDisk({ - * audio: audioBlob, - * video: videoBlob, - * gif : gifBlob - * }); - */ - RecordRTC.writeToDisk = function (options) { - console.log('Writing recorded blob(s) to disk!'); - options = options || {}; - if (options.audio && options.video && options.gif) { - options.audio.getDataURL(function (audioDataURL) { - options.video.getDataURL(function (videoDataURL) { - options.gif.getDataURL(function (gifDataURL) { - DiskStorage.Store({ - audioBlob: audioDataURL, - videoBlob: videoDataURL, - gifBlob: gifDataURL - }); - }); - }); - }); - } else if (options.audio && options.video) { - options.audio.getDataURL(function (audioDataURL) { - options.video.getDataURL(function (videoDataURL) { - DiskStorage.Store({ - audioBlob: audioDataURL, - videoBlob: videoDataURL - }); - }); - }); - } else if (options.audio && options.gif) { - options.audio.getDataURL(function (audioDataURL) { - options.gif.getDataURL(function (gifDataURL) { - DiskStorage.Store({ - audioBlob: audioDataURL, - gifBlob: gifDataURL - }); - }); - }); - } else if (options.video && options.gif) { - options.video.getDataURL(function (videoDataURL) { - options.gif.getDataURL(function (gifDataURL) { - DiskStorage.Store({ - videoBlob: videoDataURL, - gifBlob: gifDataURL - }); - }); - }); - } else if (options.audio) { - options.audio.getDataURL(function (audioDataURL) { - DiskStorage.Store({ - audioBlob: audioDataURL - }); - }); - } else if (options.video) { - options.video.getDataURL(function (videoDataURL) { - DiskStorage.Store({ - videoBlob: videoDataURL - }); - }); - } else if (options.gif) { - options.gif.getDataURL(function (gifDataURL) { - DiskStorage.Store({ - gifBlob: gifDataURL - }); - }); - } - }; - - // __________________________ - // RecordRTC-Configuration.js - - /** - * {@link RecordRTCConfiguration} is an inner/private helper for {@link RecordRTC}. - * @summary It configures the 2nd parameter passed over {@link RecordRTC} and returns a valid "config" object. - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @typedef RecordRTCConfiguration - * @class - * @example - * var options = RecordRTCConfiguration(mediaStream, options); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. - * @param {object} config - {type:"video", disableLogs: true, numberOfAudioChannels: 1, bufferSize: 0, sampleRate: 0, video: HTMLVideoElement, getNativeBlob:true, etc.} - */ - - function RecordRTCConfiguration(mediaStream, config) { - if (!config.recorderType && !config.type) { - if (!!config.audio && !!config.video) { - config.type = 'video'; - } else if (!!config.audio && !config.video) { - config.type = 'audio'; - } - } - - if (config.recorderType && !config.type) { - if (config.recorderType === WhammyRecorder || config.recorderType === CanvasRecorder || (typeof WebAssemblyRecorder !== 'undefined' && config.recorderType === WebAssemblyRecorder)) { - config.type = 'video'; - } else if (config.recorderType === GifRecorder) { - config.type = 'gif'; - } else if (config.recorderType === StereoAudioRecorder) { - config.type = 'audio'; - } else if (config.recorderType === MediaStreamRecorder) { - if (getTracks(mediaStream, 'audio').length && getTracks(mediaStream, 'video').length) { - config.type = 'video'; - } else if (!getTracks(mediaStream, 'audio').length && getTracks(mediaStream, 'video').length) { - config.type = 'video'; - } else if (getTracks(mediaStream, 'audio').length && !getTracks(mediaStream, 'video').length) { - config.type = 'audio'; - } else; - } - } - - if (typeof MediaStreamRecorder !== 'undefined' && typeof MediaRecorder !== 'undefined' && 'requestData' in MediaRecorder.prototype) { - if (!config.mimeType) { - config.mimeType = 'video/webm'; - } - - if (!config.type) { - config.type = config.mimeType.split('/')[0]; - } - - if (!config.bitsPerSecond); - } - - // consider default type=audio - if (!config.type) { - if (config.mimeType) { - config.type = config.mimeType.split('/')[0]; - } - if (!config.type) { - config.type = 'audio'; - } - } - - return config; - } - - // __________________ - // GetRecorderType.js - - /** - * {@link GetRecorderType} is an inner/private helper for {@link RecordRTC}. - * @summary It returns best recorder-type available for your browser. - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @typedef GetRecorderType - * @class - * @example - * var RecorderType = GetRecorderType(options); - * var recorder = new RecorderType(options); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. - * @param {object} config - {type:"video", disableLogs: true, numberOfAudioChannels: 1, bufferSize: 0, sampleRate: 0, video: HTMLVideoElement, etc.} - */ - - function GetRecorderType(mediaStream, config) { - var recorder; - - // StereoAudioRecorder can work with all three: Edge, Firefox and Chrome - // todo: detect if it is Edge, then auto use: StereoAudioRecorder - if (isChrome || isEdge || isOpera) { - // Media Stream Recording API has not been implemented in chrome yet; - // That's why using WebAudio API to record stereo audio in WAV format - recorder = StereoAudioRecorder; - } - - if (typeof MediaRecorder !== 'undefined' && 'requestData' in MediaRecorder.prototype && !isChrome) { - recorder = MediaStreamRecorder; - } - - // video recorder (in WebM format) - if (config.type === 'video' && (isChrome || isOpera)) { - recorder = WhammyRecorder; - - if (typeof WebAssemblyRecorder !== 'undefined' && typeof ReadableStream !== 'undefined') { - recorder = WebAssemblyRecorder; - } - } - - // video recorder (in Gif format) - if (config.type === 'gif') { - recorder = GifRecorder; - } - - // html2canvas recording! - if (config.type === 'canvas') { - recorder = CanvasRecorder; - } - - if (isMediaRecorderCompatible() && recorder !== CanvasRecorder && recorder !== GifRecorder && typeof MediaRecorder !== 'undefined' && 'requestData' in MediaRecorder.prototype) { - if (getTracks(mediaStream, 'video').length || getTracks(mediaStream, 'audio').length) { - // audio-only recording - if (config.type === 'audio') { - if (typeof MediaRecorder.isTypeSupported === 'function' && MediaRecorder.isTypeSupported('audio/webm')) { - recorder = MediaStreamRecorder; - } - // else recorder = StereoAudioRecorder; - } else { - // video or screen tracks - if (typeof MediaRecorder.isTypeSupported === 'function' && MediaRecorder.isTypeSupported('video/webm')) { - recorder = MediaStreamRecorder; - } - } - } - } - - if (mediaStream instanceof Array && mediaStream.length) { - recorder = MultiStreamRecorder; - } - - if (config.recorderType) { - recorder = config.recorderType; - } - - if (!config.disableLogs && !!recorder && !!recorder.name) { - console.log('Using recorderType:', recorder.name || recorder.constructor.name); - } - - if (!recorder && isSafari) { - recorder = MediaStreamRecorder; - } - - return recorder; - } - - // _____________ - // MRecordRTC.js - - /** - * MRecordRTC runs on top of {@link RecordRTC} to bring multiple recordings in a single place, by providing simple API. - * @summary MRecordRTC stands for "Multiple-RecordRTC". - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @typedef MRecordRTC - * @class - * @example - * var recorder = new MRecordRTC(); - * recorder.addStream(MediaStream); - * recorder.mediaType = { - * audio: true, // or StereoAudioRecorder or MediaStreamRecorder - * video: true, // or WhammyRecorder or MediaStreamRecorder or WebAssemblyRecorder or CanvasRecorder - * gif: true // or GifRecorder - * }; - * // mimeType is optional and should be set only in advance cases. - * recorder.mimeType = { - * audio: 'audio/wav', - * video: 'video/webm', - * gif: 'image/gif' - * }; - * recorder.startRecording(); - * @see For further information: - * @see {@link https://github.com/muaz-khan/RecordRTC/tree/master/MRecordRTC|MRecordRTC Source Code} - * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. - * @requires {@link RecordRTC} - */ - - function MRecordRTC(mediaStream) { - - /** - * This method attaches MediaStream object to {@link MRecordRTC}. - * @param {MediaStream} mediaStream - A MediaStream object, either fetched using getUserMedia API, or generated using captureStreamUntilEnded or WebAudio API. - * @method - * @memberof MRecordRTC - * @example - * recorder.addStream(MediaStream); - */ - this.addStream = function (_mediaStream) { - if (_mediaStream) { - mediaStream = _mediaStream; - } - }; - - /** - * This property can be used to set the recording type e.g. audio, or video, or gif, or canvas. - * @property {object} mediaType - {audio: true, video: true, gif: true} - * @memberof MRecordRTC - * @example - * var recorder = new MRecordRTC(); - * recorder.mediaType = { - * audio: true, // TRUE or StereoAudioRecorder or MediaStreamRecorder - * video: true, // TRUE or WhammyRecorder or MediaStreamRecorder or WebAssemblyRecorder or CanvasRecorder - * gif : true // TRUE or GifRecorder - * }; - */ - this.mediaType = { - audio: true, - video: true - }; - - /** - * This method starts recording. - * @method - * @memberof MRecordRTC - * @example - * recorder.startRecording(); - */ - this.startRecording = function () { - var mediaType = this.mediaType; - var recorderType; - var mimeType = this.mimeType || { - audio: null, - video: null, - gif: null - }; - - if (typeof mediaType.audio !== 'function' && isMediaRecorderCompatible() && !getTracks(mediaStream, 'audio').length) { - mediaType.audio = false; - } - - if (typeof mediaType.video !== 'function' && isMediaRecorderCompatible() && !getTracks(mediaStream, 'video').length) { - mediaType.video = false; - } - - if (typeof mediaType.gif !== 'function' && isMediaRecorderCompatible() && !getTracks(mediaStream, 'video').length) { - mediaType.gif = false; - } - - if (!mediaType.audio && !mediaType.video && !mediaType.gif) { - throw 'MediaStream must have either audio or video tracks.'; - } - - if (!!mediaType.audio) { - recorderType = null; - if (typeof mediaType.audio === 'function') { - recorderType = mediaType.audio; - } - - this.audioRecorder = new RecordRTC(mediaStream, { - type: 'audio', - bufferSize: this.bufferSize, - sampleRate: this.sampleRate, - numberOfAudioChannels: this.numberOfAudioChannels || 2, - disableLogs: this.disableLogs, - recorderType: recorderType, - mimeType: mimeType.audio, - timeSlice: this.timeSlice, - onTimeStamp: this.onTimeStamp - }); - - if (!mediaType.video) { - this.audioRecorder.startRecording(); - } - } - - if (!!mediaType.video) { - recorderType = null; - if (typeof mediaType.video === 'function') { - recorderType = mediaType.video; - } - - var newStream = mediaStream; - - if (isMediaRecorderCompatible() && !!mediaType.audio && typeof mediaType.audio === 'function') { - var videoTrack = getTracks(mediaStream, 'video')[0]; - - if (isFirefox) { - newStream = new MediaStream(); - newStream.addTrack(videoTrack); - - if (recorderType && recorderType === WhammyRecorder) { - // Firefox does NOT supports webp-encoding yet - // But Firefox do supports WebAssemblyRecorder - recorderType = MediaStreamRecorder; - } - } else { - newStream = new MediaStream(); - newStream.addTrack(videoTrack); - } - } - - this.videoRecorder = new RecordRTC(newStream, { - type: 'video', - video: this.video, - canvas: this.canvas, - frameInterval: this.frameInterval || 10, - disableLogs: this.disableLogs, - recorderType: recorderType, - mimeType: mimeType.video, - timeSlice: this.timeSlice, - onTimeStamp: this.onTimeStamp, - workerPath: this.workerPath, - webAssemblyPath: this.webAssemblyPath, - frameRate: this.frameRate, // used by WebAssemblyRecorder; values: usually 30; accepts any. - bitrate: this.bitrate // used by WebAssemblyRecorder; values: 0 to 1000+ - }); - - if (!mediaType.audio) { - this.videoRecorder.startRecording(); - } - } - - if (!!mediaType.audio && !!mediaType.video) { - var self = this; - - var isSingleRecorder = isMediaRecorderCompatible() === true; - - if (mediaType.audio instanceof StereoAudioRecorder && !!mediaType.video) { - isSingleRecorder = false; - } else if (mediaType.audio !== true && mediaType.video !== true && mediaType.audio !== mediaType.video) { - isSingleRecorder = false; - } - - if (isSingleRecorder === true) { - self.audioRecorder = null; - self.videoRecorder.startRecording(); - } else { - self.videoRecorder.initRecorder(function () { - self.audioRecorder.initRecorder(function () { - // Both recorders are ready to record things accurately - self.videoRecorder.startRecording(); - self.audioRecorder.startRecording(); - }); - }); - } - } - - if (!!mediaType.gif) { - recorderType = null; - if (typeof mediaType.gif === 'function') { - recorderType = mediaType.gif; - } - this.gifRecorder = new RecordRTC(mediaStream, { - type: 'gif', - frameRate: this.frameRate || 200, - quality: this.quality || 10, - disableLogs: this.disableLogs, - recorderType: recorderType, - mimeType: mimeType.gif - }); - this.gifRecorder.startRecording(); - } - }; - - /** - * This method stops recording. - * @param {function} callback - Callback function is invoked when all encoders finished their jobs. - * @method - * @memberof MRecordRTC - * @example - * recorder.stopRecording(function(recording){ - * var audioBlob = recording.audio; - * var videoBlob = recording.video; - * var gifBlob = recording.gif; - * }); - */ - this.stopRecording = function (callback) { - callback = callback || function () { }; - - if (this.audioRecorder) { - this.audioRecorder.stopRecording(function (blobURL) { - callback(blobURL, 'audio'); - }); - } - - if (this.videoRecorder) { - this.videoRecorder.stopRecording(function (blobURL) { - callback(blobURL, 'video'); - }); - } - - if (this.gifRecorder) { - this.gifRecorder.stopRecording(function (blobURL) { - callback(blobURL, 'gif'); - }); - } - }; - - /** - * This method pauses recording. - * @method - * @memberof MRecordRTC - * @example - * recorder.pauseRecording(); - */ - this.pauseRecording = function () { - if (this.audioRecorder) { - this.audioRecorder.pauseRecording(); - } - - if (this.videoRecorder) { - this.videoRecorder.pauseRecording(); - } - - if (this.gifRecorder) { - this.gifRecorder.pauseRecording(); - } - }; - - /** - * This method resumes recording. - * @method - * @memberof MRecordRTC - * @example - * recorder.resumeRecording(); - */ - this.resumeRecording = function () { - if (this.audioRecorder) { - this.audioRecorder.resumeRecording(); - } - - if (this.videoRecorder) { - this.videoRecorder.resumeRecording(); - } - - if (this.gifRecorder) { - this.gifRecorder.resumeRecording(); - } - }; - - /** - * This method can be used to manually get all recorded blobs. - * @param {function} callback - All recorded blobs are passed back to the "callback" function. - * @method - * @memberof MRecordRTC - * @example - * recorder.getBlob(function(recording){ - * var audioBlob = recording.audio; - * var videoBlob = recording.video; - * var gifBlob = recording.gif; - * }); - * // or - * var audioBlob = recorder.getBlob().audio; - * var videoBlob = recorder.getBlob().video; - */ - this.getBlob = function (callback) { - var output = {}; - - if (this.audioRecorder) { - output.audio = this.audioRecorder.getBlob(); - } - - if (this.videoRecorder) { - output.video = this.videoRecorder.getBlob(); - } - - if (this.gifRecorder) { - output.gif = this.gifRecorder.getBlob(); - } - - if (callback) { - callback(output); - } - - return output; - }; - - /** - * Destroy all recorder instances. - * @method - * @memberof MRecordRTC - * @example - * recorder.destroy(); - */ - this.destroy = function () { - if (this.audioRecorder) { - this.audioRecorder.destroy(); - this.audioRecorder = null; - } - - if (this.videoRecorder) { - this.videoRecorder.destroy(); - this.videoRecorder = null; - } - - if (this.gifRecorder) { - this.gifRecorder.destroy(); - this.gifRecorder = null; - } - }; - - /** - * This method can be used to manually get all recorded blobs' DataURLs. - * @param {function} callback - All recorded blobs' DataURLs are passed back to the "callback" function. - * @method - * @memberof MRecordRTC - * @example - * recorder.getDataURL(function(recording){ - * var audioDataURL = recording.audio; - * var videoDataURL = recording.video; - * var gifDataURL = recording.gif; - * }); - */ - this.getDataURL = function (callback) { - this.getBlob(function (blob) { - if (blob.audio && blob.video) { - getDataURL(blob.audio, function (_audioDataURL) { - getDataURL(blob.video, function (_videoDataURL) { - callback({ - audio: _audioDataURL, - video: _videoDataURL - }); - }); - }); - } else if (blob.audio) { - getDataURL(blob.audio, function (_audioDataURL) { - callback({ - audio: _audioDataURL - }); - }); - } else if (blob.video) { - getDataURL(blob.video, function (_videoDataURL) { - callback({ - video: _videoDataURL - }); - }); - } - }); - - function getDataURL(blob, callback00) { - if (typeof Worker !== 'undefined') { - var webWorker = processInWebWorker(function readFile(_blob) { - postMessage(new FileReaderSync().readAsDataURL(_blob)); - }); - - webWorker.onmessage = function (event) { - callback00(event.data); - }; - - webWorker.postMessage(blob); - } else { - var reader = new FileReader(); - reader.readAsDataURL(blob); - reader.onload = function (event) { - callback00(event.target.result); - }; - } - } - - function processInWebWorker(_function) { - var blob = URL.createObjectURL(new Blob([_function.toString(), - 'this.onmessage = function (eee) {' + _function.name + '(eee.data);}' - ], { - type: 'application/javascript' - })); - - var worker = new Worker(blob); - var url; - if (typeof URL !== 'undefined') { - url = URL; - } else if (typeof webkitURL !== 'undefined') { - url = webkitURL; - } else { - throw 'Neither URL nor webkitURL detected.'; - } - url.revokeObjectURL(blob); - return worker; - } - }; - - /** - * This method can be used to ask {@link MRecordRTC} to write all recorded blobs into IndexedDB storage. - * @method - * @memberof MRecordRTC - * @example - * recorder.writeToDisk(); - */ - this.writeToDisk = function () { - RecordRTC.writeToDisk({ - audio: this.audioRecorder, - video: this.videoRecorder, - gif: this.gifRecorder - }); - }; - - /** - * This method can be used to invoke a save-as dialog for all recorded blobs. - * @param {object} args - {audio: 'audio-name', video: 'video-name', gif: 'gif-name'} - * @method - * @memberof MRecordRTC - * @example - * recorder.save({ - * audio: 'audio-file-name', - * video: 'video-file-name', - * gif : 'gif-file-name' - * }); - */ - this.save = function (args) { - args = args || { - audio: true, - video: true, - gif: true - }; - - if (!!args.audio && this.audioRecorder) { - this.audioRecorder.save(typeof args.audio === 'string' ? args.audio : ''); - } - - if (!!args.video && this.videoRecorder) { - this.videoRecorder.save(typeof args.video === 'string' ? args.video : ''); - } - if (!!args.gif && this.gifRecorder) { - this.gifRecorder.save(typeof args.gif === 'string' ? args.gif : ''); - } - }; - } - - /** - * This method can be used to get all recorded blobs from IndexedDB storage. - * @param {string} type - 'all' or 'audio' or 'video' or 'gif' - * @param {function} callback - Callback function to get all stored blobs. - * @method - * @memberof MRecordRTC - * @example - * MRecordRTC.getFromDisk('all', function(dataURL, type){ - * if(type === 'audio') { } - * if(type === 'video') { } - * if(type === 'gif') { } - * }); - */ - MRecordRTC.getFromDisk = RecordRTC.getFromDisk; - - /** - * This method can be used to store recorded blobs into IndexedDB storage. - * @param {object} options - {audio: Blob, video: Blob, gif: Blob} - * @method - * @memberof MRecordRTC - * @example - * MRecordRTC.writeToDisk({ - * audio: audioBlob, - * video: videoBlob, - * gif : gifBlob - * }); - */ - MRecordRTC.writeToDisk = RecordRTC.writeToDisk; - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.MRecordRTC = MRecordRTC; - } - - var browserFakeUserAgent = 'Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45'; - - (function (that) { - if (!that) { - return; - } - - if (typeof window !== 'undefined') { - return; - } - - if (typeof commonjsGlobal === 'undefined') { - return; - } - - commonjsGlobal.navigator = { - userAgent: browserFakeUserAgent, - getUserMedia: function () { } - }; - - if (!commonjsGlobal.console) { - commonjsGlobal.console = {}; - } - - if (typeof commonjsGlobal.console.log === 'undefined' || typeof commonjsGlobal.console.error === 'undefined') { - commonjsGlobal.console.error = commonjsGlobal.console.log = commonjsGlobal.console.log || function () { - console.log(arguments); - }; - } - - if (typeof document === 'undefined') { - /*global document:true */ - that.document = { - documentElement: { - appendChild: function () { - return ''; - } - } - }; - - document.createElement = document.captureStream = document.mozCaptureStream = function () { - var obj = { - getContext: function () { - return obj; - }, - play: function () { }, - pause: function () { }, - drawImage: function () { }, - toDataURL: function () { - return ''; - }, - style: {} - }; - return obj; - }; - - that.HTMLVideoElement = function () { }; - } - - if (typeof location === 'undefined') { - /*global location:true */ - that.location = { - protocol: 'file:', - href: '', - hash: '' - }; - } - - if (typeof screen === 'undefined') { - /*global screen:true */ - that.screen = { - width: 0, - height: 0 - }; - } - - if (typeof URL === 'undefined') { - /*global screen:true */ - that.URL = { - createObjectURL: function () { - return ''; - }, - revokeObjectURL: function () { - return ''; - } - }; - } - - /*global window:true */ - that.window = commonjsGlobal; - })(typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : null); - - // _____________________________ - // Cross-Browser-Declarations.js - - // animation-frame used in WebM recording - - /*jshint -W079 */ - var requestAnimationFrame = window.requestAnimationFrame; - if (typeof requestAnimationFrame === 'undefined') { - if (typeof webkitRequestAnimationFrame !== 'undefined') { - /*global requestAnimationFrame:true */ - requestAnimationFrame = webkitRequestAnimationFrame; - } else if (typeof mozRequestAnimationFrame !== 'undefined') { - /*global requestAnimationFrame:true */ - requestAnimationFrame = mozRequestAnimationFrame; - } else if (typeof msRequestAnimationFrame !== 'undefined') { - /*global requestAnimationFrame:true */ - requestAnimationFrame = msRequestAnimationFrame; - } else if (typeof requestAnimationFrame === 'undefined') { - // via: https://gist.github.com/paulirish/1579671 - var lastTime = 0; - - /*global requestAnimationFrame:true */ - requestAnimationFrame = function (callback, element) { - var currTime = new Date().getTime(); - var timeToCall = Math.max(0, 16 - (currTime - lastTime)); - var id = setTimeout(function () { - callback(currTime + timeToCall); - }, timeToCall); - lastTime = currTime + timeToCall; - return id; - }; - } - } - - /*jshint -W079 */ - var cancelAnimationFrame = window.cancelAnimationFrame; - if (typeof cancelAnimationFrame === 'undefined') { - if (typeof webkitCancelAnimationFrame !== 'undefined') { - /*global cancelAnimationFrame:true */ - cancelAnimationFrame = webkitCancelAnimationFrame; - } else if (typeof mozCancelAnimationFrame !== 'undefined') { - /*global cancelAnimationFrame:true */ - cancelAnimationFrame = mozCancelAnimationFrame; - } else if (typeof msCancelAnimationFrame !== 'undefined') { - /*global cancelAnimationFrame:true */ - cancelAnimationFrame = msCancelAnimationFrame; - } else if (typeof cancelAnimationFrame === 'undefined') { - /*global cancelAnimationFrame:true */ - cancelAnimationFrame = function (id) { - clearTimeout(id); - }; - } - } - - // WebAudio API representer - var AudioContext = window.AudioContext; - - if (typeof AudioContext === 'undefined') { - if (typeof webkitAudioContext !== 'undefined') { - /*global AudioContext:true */ - AudioContext = webkitAudioContext; - } - - if (typeof mozAudioContext !== 'undefined') { - /*global AudioContext:true */ - AudioContext = mozAudioContext; - } - } - - /*jshint -W079 */ - var URL = window.URL; - - if (typeof URL === 'undefined' && typeof webkitURL !== 'undefined') { - /*global URL:true */ - URL = webkitURL; - } - - if (typeof navigator !== 'undefined' && typeof navigator.getUserMedia === 'undefined') { // maybe window.navigator? - if (typeof navigator.webkitGetUserMedia !== 'undefined') { - navigator.getUserMedia = navigator.webkitGetUserMedia; - } - - if (typeof navigator.mozGetUserMedia !== 'undefined') { - navigator.getUserMedia = navigator.mozGetUserMedia; - } - } - - var isEdge = navigator.userAgent.indexOf('Edge') !== -1 && (!!navigator.msSaveBlob || !!navigator.msSaveOrOpenBlob); - var isOpera = !!window.opera || navigator.userAgent.indexOf('OPR/') !== -1; - var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1 && ('netscape' in window) && / rv:/.test(navigator.userAgent); - var isChrome = (!isOpera && !isEdge && !!navigator.webkitGetUserMedia) || isElectron() || navigator.userAgent.toLowerCase().indexOf('chrome/') !== -1; - - var isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); - - if (isSafari && !isChrome && navigator.userAgent.indexOf('CriOS') !== -1) { - isSafari = false; - isChrome = true; - } - - var MediaStream = window.MediaStream; - - if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefined') { - MediaStream = webkitMediaStream; - } - - /*global MediaStream:true */ - if (typeof MediaStream !== 'undefined') { - // override "stop" method for all browsers - if (typeof MediaStream.prototype.stop === 'undefined') { - MediaStream.prototype.stop = function () { - this.getTracks().forEach(function (track) { - track.stop(); - }); - }; - } - } - - // below function via: http://goo.gl/B3ae8c - /** - * Return human-readable file size. - * @param {number} bytes - Pass bytes and get formatted string. - * @returns {string} - formatted string - * @example - * bytesToSize(1024*1024*5) === '5 GB' - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - */ - function bytesToSize(bytes) { - var k = 1000; - var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; - if (bytes === 0) { - return '0 Bytes'; - } - var i = parseInt(Math.floor(Math.log(bytes) / Math.log(k)), 10); - return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i]; - } - - /** - * @param {Blob} file - File or Blob object. This parameter is required. - * @param {string} fileName - Optional file name e.g. "Recorded-Video.webm" - * @example - * invokeSaveAsDialog(blob or file, [optional] fileName); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - */ - function invokeSaveAsDialog(file, fileName) { - if (!file) { - throw 'Blob object is required.'; - } - - if (!file.type) { - try { - file.type = 'video/webm'; - } catch (e) { } - } - - var fileExtension = (file.type || 'video/webm').split('/')[1]; - if (fileExtension.indexOf(';') !== -1) { - // extended mimetype, e.g. 'video/webm;codecs=vp8,opus' - fileExtension = fileExtension.split(';')[0]; - } - if (fileName && fileName.indexOf('.') !== -1) { - var splitted = fileName.split('.'); - fileName = splitted[0]; - fileExtension = splitted[1]; - } - - var fileFullName = (fileName || (Math.round(Math.random() * 9999999999) + 888888888)) + '.' + fileExtension; - - if (typeof navigator.msSaveOrOpenBlob !== 'undefined') { - return navigator.msSaveOrOpenBlob(file, fileFullName); - } else if (typeof navigator.msSaveBlob !== 'undefined') { - return navigator.msSaveBlob(file, fileFullName); - } - - var hyperlink = document.createElement('a'); - hyperlink.href = URL.createObjectURL(file); - hyperlink.download = fileFullName; - - hyperlink.style = 'display:none;opacity:0;color:transparent;'; - (document.body || document.documentElement).appendChild(hyperlink); - - if (typeof hyperlink.click === 'function') { - hyperlink.click(); - } else { - hyperlink.target = '_blank'; - hyperlink.dispatchEvent(new MouseEvent('click', { - view: window, - bubbles: true, - cancelable: true - })); - } - - URL.revokeObjectURL(hyperlink.href); - } - - /** - * from: https://github.com/cheton/is-electron/blob/master/index.js - **/ - function isElectron() { - // Renderer process - if (typeof window !== 'undefined' && typeof window.process === 'object' && window.process.type === 'renderer') { - return true; - } - - // Main process - if (typeof process !== 'undefined' && typeof process.versions === 'object' && !!process.versions.electron) { - return true; - } - - // Detect the user agent when the `nodeIntegration` option is set to true - if (typeof navigator === 'object' && typeof navigator.userAgent === 'string' && navigator.userAgent.indexOf('Electron') >= 0) { - return true; - } - - return false; - } - - function getTracks(stream, kind) { - if (!stream || !stream.getTracks) { - return []; - } - - return stream.getTracks().filter(function (t) { - return t.kind === (kind || 'audio'); - }); - } - - function setSrcObject(stream, element) { - if ('srcObject' in element) { - element.srcObject = stream; - } else if ('mozSrcObject' in element) { - element.mozSrcObject = stream; - } else { - element.srcObject = stream; - } - } - - /** - * @param {Blob} file - File or Blob object. - * @param {function} callback - Callback function. - * @example - * getSeekableBlob(blob or file, callback); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - */ - function getSeekableBlob(inputBlob, callback) { - // EBML.js copyrights goes to: https://github.com/legokichi/ts-ebml - if (typeof EBML === 'undefined') { - throw new Error('Please link: https://www.webrtc-experiment.com/EBML.js'); - } - - var reader = new EBML.Reader(); - var decoder = new EBML.Decoder(); - var tools = EBML.tools; - - var fileReader = new FileReader(); - fileReader.onload = function (e) { - var ebmlElms = decoder.decode(this.result); - ebmlElms.forEach(function (element) { - reader.read(element); - }); - reader.stop(); - var refinedMetadataBuf = tools.makeMetadataSeekable(reader.metadatas, reader.duration, reader.cues); - var body = this.result.slice(reader.metadataSize); - var newBlob = new Blob([refinedMetadataBuf, body], { - type: 'video/webm' - }); - - callback(newBlob); - }; - fileReader.readAsArrayBuffer(inputBlob); - } - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.invokeSaveAsDialog = invokeSaveAsDialog; - RecordRTC.getTracks = getTracks; - RecordRTC.getSeekableBlob = getSeekableBlob; - RecordRTC.bytesToSize = bytesToSize; - RecordRTC.isElectron = isElectron; - } - - // __________ (used to handle stuff like http://goo.gl/xmE5eg) issue #129 - // Storage.js - - /** - * Storage is a standalone object used by {@link RecordRTC} to store reusable objects e.g. "new AudioContext". - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @example - * Storage.AudioContext === webkitAudioContext - * @property {webkitAudioContext} AudioContext - Keeps a reference to AudioContext object. - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - */ - - var Storage = {}; - - if (typeof AudioContext !== 'undefined') { - Storage.AudioContext = AudioContext; - } else if (typeof webkitAudioContext !== 'undefined') { - Storage.AudioContext = webkitAudioContext; - } - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.Storage = Storage; - } - - function isMediaRecorderCompatible() { - if (isFirefox || isSafari || isEdge) { - return true; - } - var nAgt = navigator.userAgent; - var fullVersion = '' + parseFloat(navigator.appVersion); - var majorVersion = parseInt(navigator.appVersion, 10); - var verOffset, ix; - - if (isChrome || isOpera) { - verOffset = nAgt.indexOf('Chrome'); - fullVersion = nAgt.substring(verOffset + 7); - } - - // trim the fullVersion string at semicolon/space if present - if ((ix = fullVersion.indexOf(';')) !== -1) { - fullVersion = fullVersion.substring(0, ix); - } - - if ((ix = fullVersion.indexOf(' ')) !== -1) { - fullVersion = fullVersion.substring(0, ix); - } - - majorVersion = parseInt('' + fullVersion, 10); - - if (isNaN(majorVersion)) { - fullVersion = '' + parseFloat(navigator.appVersion); - majorVersion = parseInt(navigator.appVersion, 10); - } - - return majorVersion >= 49; - } - - // ______________________ - // MediaStreamRecorder.js - - /** - * MediaStreamRecorder is an abstraction layer for {@link https://w3c.github.io/mediacapture-record/MediaRecorder.html|MediaRecorder API}. It is used by {@link RecordRTC} to record MediaStream(s) in both Chrome and Firefox. - * @summary Runs top over {@link https://w3c.github.io/mediacapture-record/MediaRecorder.html|MediaRecorder API}. - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://github.com/muaz-khan|Muaz Khan} - * @typedef MediaStreamRecorder - * @class - * @example - * var config = { - * mimeType: 'video/webm', // vp8, vp9, h264, mkv, opus/vorbis - * audioBitsPerSecond : 256 * 8 * 1024, - * videoBitsPerSecond : 256 * 8 * 1024, - * bitsPerSecond: 256 * 8 * 1024, // if this is provided, skip above two - * checkForInactiveTracks: true, - * timeSlice: 1000, // concatenate intervals based blobs - * ondataavailable: function() {} // get intervals based blobs - * } - * var recorder = new MediaStreamRecorder(mediaStream, config); - * recorder.record(); - * recorder.stop(function(blob) { - * video.src = URL.createObjectURL(blob); - * - * // or - * var blob = recorder.blob; - * }); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. - * @param {object} config - {disableLogs:true, initCallback: function, mimeType: "video/webm", timeSlice: 1000} - * @throws Will throw an error if first argument "MediaStream" is missing. Also throws error if "MediaRecorder API" are not supported by the browser. - */ - - function MediaStreamRecorder(mediaStream, config) { - var self = this; - - if (typeof mediaStream === 'undefined') { - throw 'First argument "MediaStream" is required.'; - } - - if (typeof MediaRecorder === 'undefined') { - throw 'Your browser does not support the Media Recorder API. Please try other modules e.g. WhammyRecorder or StereoAudioRecorder.'; - } - - config = config || { - // bitsPerSecond: 256 * 8 * 1024, - mimeType: 'video/webm' - }; - - if (config.type === 'audio') { - if (getTracks(mediaStream, 'video').length && getTracks(mediaStream, 'audio').length) { - var stream; - if (!!navigator.mozGetUserMedia) { - stream = new MediaStream(); - stream.addTrack(getTracks(mediaStream, 'audio')[0]); - } else { - // webkitMediaStream - stream = new MediaStream(getTracks(mediaStream, 'audio')); - } - mediaStream = stream; - } - - if (!config.mimeType || config.mimeType.toString().toLowerCase().indexOf('audio') === -1) { - config.mimeType = isChrome ? 'audio/webm' : 'audio/ogg'; - } - - if (config.mimeType && config.mimeType.toString().toLowerCase() !== 'audio/ogg' && !!navigator.mozGetUserMedia) { - // forcing better codecs on Firefox (via #166) - config.mimeType = 'audio/ogg'; - } - } - - var arrayOfBlobs = []; - - /** - * This method returns array of blobs. Use only with "timeSlice". Its useful to preview recording anytime, without using the "stop" method. - * @method - * @memberof MediaStreamRecorder - * @example - * var arrayOfBlobs = recorder.getArrayOfBlobs(); - * @returns {Array} Returns array of recorded blobs. - */ - this.getArrayOfBlobs = function () { - return arrayOfBlobs; - }; - - /** - * This method records MediaStream. - * @method - * @memberof MediaStreamRecorder - * @example - * recorder.record(); - */ - this.record = function () { - // set defaults - self.blob = null; - self.clearRecordedData(); - self.timestamps = []; - allStates = []; - arrayOfBlobs = []; - - var recorderHints = config; - - if (!config.disableLogs) { - console.log('Passing following config over MediaRecorder API.', recorderHints); - } - - if (mediaRecorder) { - // mandatory to make sure Firefox doesn't fails to record streams 3-4 times without reloading the page. - mediaRecorder = null; - } - - if (isChrome && !isMediaRecorderCompatible()) { - // to support video-only recording on stable - recorderHints = 'video/vp8'; - } - - if (typeof MediaRecorder.isTypeSupported === 'function' && recorderHints.mimeType) { - if (!MediaRecorder.isTypeSupported(recorderHints.mimeType)) { - if (!config.disableLogs) { - console.warn('MediaRecorder API seems unable to record mimeType:', recorderHints.mimeType); - } - - recorderHints.mimeType = config.type === 'audio' ? 'audio/webm' : 'video/webm'; - } - } - - // using MediaRecorder API here - try { - mediaRecorder = new MediaRecorder(mediaStream, recorderHints); - - // reset - config.mimeType = recorderHints.mimeType; - } catch (e) { - // chrome-based fallback - mediaRecorder = new MediaRecorder(mediaStream); - } - - // old hack? - if (recorderHints.mimeType && !MediaRecorder.isTypeSupported && 'canRecordMimeType' in mediaRecorder && mediaRecorder.canRecordMimeType(recorderHints.mimeType) === false) { - if (!config.disableLogs) { - console.warn('MediaRecorder API seems unable to record mimeType:', recorderHints.mimeType); - } - } - - // Dispatching OnDataAvailable Handler - mediaRecorder.ondataavailable = function (e) { - if (e.data) { - allStates.push('ondataavailable: ' + bytesToSize(e.data.size)); - } - - if (typeof config.timeSlice === 'number') { - if (e.data && e.data.size) { - arrayOfBlobs.push(e.data); - updateTimeStamp(); - - if (typeof config.ondataavailable === 'function') { - // intervals based blobs - var blob = config.getNativeBlob ? e.data : new Blob([e.data], { - type: getMimeType(recorderHints) - }); - config.ondataavailable(blob); - } - } - return; - } - - if (!e.data || !e.data.size || e.data.size < 100 || self.blob) { - // make sure that stopRecording always getting fired - // even if there is invalid data - if (self.recordingCallback) { - self.recordingCallback(new Blob([], { - type: getMimeType(recorderHints) - })); - self.recordingCallback = null; - } - return; - } - - self.blob = config.getNativeBlob ? e.data : new Blob([e.data], { - type: getMimeType(recorderHints) - }); - - if (self.recordingCallback) { - self.recordingCallback(self.blob); - self.recordingCallback = null; - } - }; - - mediaRecorder.onstart = function () { - allStates.push('started'); - }; - - mediaRecorder.onpause = function () { - allStates.push('paused'); - }; - - mediaRecorder.onresume = function () { - allStates.push('resumed'); - }; - - mediaRecorder.onstop = function () { - allStates.push('stopped'); - }; - - mediaRecorder.onerror = function (error) { - if (!error) { - return; - } - - if (!error.name) { - error.name = 'UnknownError'; - } - - allStates.push('error: ' + error); - - if (!config.disableLogs) { - // via: https://w3c.github.io/mediacapture-record/MediaRecorder.html#exception-summary - if (error.name.toString().toLowerCase().indexOf('invalidstate') !== -1) { - console.error('The MediaRecorder is not in a state in which the proposed operation is allowed to be executed.', error); - } else if (error.name.toString().toLowerCase().indexOf('notsupported') !== -1) { - console.error('MIME type (', recorderHints.mimeType, ') is not supported.', error); - } else if (error.name.toString().toLowerCase().indexOf('security') !== -1) { - console.error('MediaRecorder security error', error); - } - - // older code below - else if (error.name === 'OutOfMemory') { - console.error('The UA has exhaused the available memory. User agents SHOULD provide as much additional information as possible in the message attribute.', error); - } else if (error.name === 'IllegalStreamModification') { - console.error('A modification to the stream has occurred that makes it impossible to continue recording. An example would be the addition of a Track while recording is occurring. User agents SHOULD provide as much additional information as possible in the message attribute.', error); - } else if (error.name === 'OtherRecordingError') { - console.error('Used for an fatal error other than those listed above. User agents SHOULD provide as much additional information as possible in the message attribute.', error); - } else if (error.name === 'GenericError') { - console.error('The UA cannot provide the codec or recording option that has been requested.', error); - } else { - console.error('MediaRecorder Error', error); - } - } - - (function (looper) { - if (!self.manuallyStopped && mediaRecorder && mediaRecorder.state === 'inactive') { - delete config.timeslice; - - // 10 minutes, enough? - mediaRecorder.start(10 * 60 * 1000); - return; - } - - setTimeout(looper, 1000); - })(); - - if (mediaRecorder.state !== 'inactive' && mediaRecorder.state !== 'stopped') { - mediaRecorder.stop(); - } - }; - - if (typeof config.timeSlice === 'number') { - updateTimeStamp(); - mediaRecorder.start(config.timeSlice); - } else { - // default is 60 minutes; enough? - // use config => {timeSlice: 1000} otherwise - - mediaRecorder.start(3.6e+6); - } - - if (config.initCallback) { - config.initCallback(); // old code - } - }; - - /** - * @property {Array} timestamps - Array of time stamps - * @memberof MediaStreamRecorder - * @example - * console.log(recorder.timestamps); - */ - this.timestamps = []; - - function updateTimeStamp() { - self.timestamps.push(new Date().getTime()); - - if (typeof config.onTimeStamp === 'function') { - config.onTimeStamp(self.timestamps[self.timestamps.length - 1], self.timestamps); - } - } - - function getMimeType(secondObject) { - if (mediaRecorder && mediaRecorder.mimeType) { - return mediaRecorder.mimeType; - } - - return secondObject.mimeType || 'video/webm'; - } - - /** - * This method stops recording MediaStream. - * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. - * @method - * @memberof MediaStreamRecorder - * @example - * recorder.stop(function(blob) { - * video.src = URL.createObjectURL(blob); - * }); - */ - this.stop = function (callback) { - callback = callback || function () { }; - - self.manuallyStopped = true; // used inside the mediaRecorder.onerror - - if (!mediaRecorder) { - return; - } - - this.recordingCallback = callback; - - if (mediaRecorder.state === 'recording') { - mediaRecorder.stop(); - } - - if (typeof config.timeSlice === 'number') { - setTimeout(function () { - self.blob = new Blob(arrayOfBlobs, { - type: getMimeType(config) - }); - - self.recordingCallback(self.blob); - }, 100); - } - }; - - /** - * This method pauses the recording process. - * @method - * @memberof MediaStreamRecorder - * @example - * recorder.pause(); - */ - this.pause = function () { - if (!mediaRecorder) { - return; - } - - if (mediaRecorder.state === 'recording') { - mediaRecorder.pause(); - } - }; - - /** - * This method resumes the recording process. - * @method - * @memberof MediaStreamRecorder - * @example - * recorder.resume(); - */ - this.resume = function () { - if (!mediaRecorder) { - return; - } - - if (mediaRecorder.state === 'paused') { - mediaRecorder.resume(); - } - }; - - /** - * This method resets currently recorded data. - * @method - * @memberof MediaStreamRecorder - * @example - * recorder.clearRecordedData(); - */ - this.clearRecordedData = function () { - if (mediaRecorder && mediaRecorder.state === 'recording') { - self.stop(clearRecordedDataCB); - } - - clearRecordedDataCB(); - }; - - function clearRecordedDataCB() { - arrayOfBlobs = []; - mediaRecorder = null; - self.timestamps = []; - } - - // Reference to "MediaRecorder" object - var mediaRecorder; - - /** - * Access to native MediaRecorder API - * @method - * @memberof MediaStreamRecorder - * @instance - * @example - * var internal = recorder.getInternalRecorder(); - * internal.ondataavailable = function() {}; // override - * internal.stream, internal.onpause, internal.onstop, etc. - * @returns {Object} Returns internal recording object. - */ - this.getInternalRecorder = function () { - return mediaRecorder; - }; - - function isMediaStreamActive() { - if ('active' in mediaStream) { - if (!mediaStream.active) { - return false; - } - } else if ('ended' in mediaStream) { // old hack - if (mediaStream.ended) { - return false; - } - } - return true; - } - - /** - * @property {Blob} blob - Recorded data as "Blob" object. - * @memberof MediaStreamRecorder - * @example - * recorder.stop(function() { - * var blob = recorder.blob; - * }); - */ - this.blob = null; - - - /** - * Get MediaRecorder readonly state. - * @method - * @memberof MediaStreamRecorder - * @example - * var state = recorder.getState(); - * @returns {String} Returns recording state. - */ - this.getState = function () { - if (!mediaRecorder) { - return 'inactive'; - } - - return mediaRecorder.state || 'inactive'; - }; - - // list of all recording states - var allStates = []; - - /** - * Get MediaRecorder all recording states. - * @method - * @memberof MediaStreamRecorder - * @example - * var state = recorder.getAllStates(); - * @returns {Array} Returns all recording states - */ - this.getAllStates = function () { - return allStates; - }; - - // if any Track within the MediaStream is muted or not enabled at any time, - // the browser will only record black frames - // or silence since that is the content produced by the Track - // so we need to stopRecording as soon as any single track ends. - if (typeof config.checkForInactiveTracks === 'undefined') { - config.checkForInactiveTracks = false; // disable to minimize CPU usage - } - - var self = this; - - // this method checks if media stream is stopped - // or if any track is ended. - (function looper() { - if (!mediaRecorder || config.checkForInactiveTracks === false) { - return; - } - - if (isMediaStreamActive() === false) { - if (!config.disableLogs) { - console.log('MediaStream seems stopped.'); - } - self.stop(); - return; - } - - setTimeout(looper, 1000); // check every second - })(); - - // for debugging - this.name = 'MediaStreamRecorder'; - this.toString = function () { - return this.name; - }; - } - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.MediaStreamRecorder = MediaStreamRecorder; - } - - // source code from: http://typedarray.org/wp-content/projects/WebAudioRecorder/script.js - // https://github.com/mattdiamond/Recorderjs#license-mit - // ______________________ - // StereoAudioRecorder.js - - /** - * StereoAudioRecorder is a standalone class used by {@link RecordRTC} to bring "stereo" audio-recording in chrome. - * @summary JavaScript standalone object for stereo audio recording. - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @typedef StereoAudioRecorder - * @class - * @example - * var recorder = new StereoAudioRecorder(MediaStream, { - * sampleRate: 44100, - * bufferSize: 4096 - * }); - * recorder.record(); - * recorder.stop(function(blob) { - * video.src = URL.createObjectURL(blob); - * }); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. - * @param {object} config - {sampleRate: 44100, bufferSize: 4096, numberOfAudioChannels: 1, etc.} - */ - - function StereoAudioRecorder(mediaStream, config) { - if (!getTracks(mediaStream, 'audio').length) { - throw 'Your stream has no audio tracks.'; - } - - config = config || {}; - - var self = this; - - // variables - var leftchannel = []; - var rightchannel = []; - var recording = false; - var recordingLength = 0; - var jsAudioNode; - - var numberOfAudioChannels = 2; - - /** - * Set sample rates such as 8K or 16K. Reference: http://stackoverflow.com/a/28977136/552182 - * @property {number} desiredSampRate - Desired Bits per sample * 1000 - * @memberof StereoAudioRecorder - * @instance - * @example - * var recorder = StereoAudioRecorder(mediaStream, { - * desiredSampRate: 16 * 1000 // bits-per-sample * 1000 - * }); - */ - var desiredSampRate = config.desiredSampRate; - - // backward compatibility - if (config.leftChannel === true) { - numberOfAudioChannels = 1; - } - - if (config.numberOfAudioChannels === 1) { - numberOfAudioChannels = 1; - } - - if (!numberOfAudioChannels || numberOfAudioChannels < 1) { - numberOfAudioChannels = 2; - } - - if (!config.disableLogs) { - console.log('StereoAudioRecorder is set to record number of channels: ' + numberOfAudioChannels); - } - - // if any Track within the MediaStream is muted or not enabled at any time, - // the browser will only record black frames - // or silence since that is the content produced by the Track - // so we need to stopRecording as soon as any single track ends. - if (typeof config.checkForInactiveTracks === 'undefined') { - config.checkForInactiveTracks = true; - } - - function isMediaStreamActive() { - if (config.checkForInactiveTracks === false) { - // always return "true" - return true; - } - - if ('active' in mediaStream) { - if (!mediaStream.active) { - return false; - } - } else if ('ended' in mediaStream) { // old hack - if (mediaStream.ended) { - return false; - } - } - return true; - } - - /** - * This method records MediaStream. - * @method - * @memberof StereoAudioRecorder - * @example - * recorder.record(); - */ - this.record = function () { - if (isMediaStreamActive() === false) { - throw 'Please make sure MediaStream is active.'; - } - - resetVariables(); - - isAudioProcessStarted = isPaused = false; - recording = true; - - if (typeof config.timeSlice !== 'undefined') { - looper(); - } - }; - - function mergeLeftRightBuffers(config, callback) { - function mergeAudioBuffers(config, cb) { - var numberOfAudioChannels = config.numberOfAudioChannels; - - // todo: "slice(0)" --- is it causes loop? Should be removed? - var leftBuffers = config.leftBuffers.slice(0); - var rightBuffers = config.rightBuffers.slice(0); - var sampleRate = config.sampleRate; - var internalInterleavedLength = config.internalInterleavedLength; - var desiredSampRate = config.desiredSampRate; - - if (numberOfAudioChannels === 2) { - leftBuffers = mergeBuffers(leftBuffers, internalInterleavedLength); - rightBuffers = mergeBuffers(rightBuffers, internalInterleavedLength); - - if (desiredSampRate) { - leftBuffers = interpolateArray(leftBuffers, desiredSampRate, sampleRate); - rightBuffers = interpolateArray(rightBuffers, desiredSampRate, sampleRate); - } - } - - if (numberOfAudioChannels === 1) { - leftBuffers = mergeBuffers(leftBuffers, internalInterleavedLength); - - if (desiredSampRate) { - leftBuffers = interpolateArray(leftBuffers, desiredSampRate, sampleRate); - } - } - - // set sample rate as desired sample rate - if (desiredSampRate) { - sampleRate = desiredSampRate; - } - - // for changing the sampling rate, reference: - // http://stackoverflow.com/a/28977136/552182 - function interpolateArray(data, newSampleRate, oldSampleRate) { - var fitCount = Math.round(data.length * (newSampleRate / oldSampleRate)); - var newData = []; - var springFactor = Number((data.length - 1) / (fitCount - 1)); - newData[0] = data[0]; - for (var i = 1; i < fitCount - 1; i++) { - var tmp = i * springFactor; - var before = Number(Math.floor(tmp)).toFixed(); - var after = Number(Math.ceil(tmp)).toFixed(); - var atPoint = tmp - before; - newData[i] = linearInterpolate(data[before], data[after], atPoint); - } - newData[fitCount - 1] = data[data.length - 1]; - return newData; - } - - function linearInterpolate(before, after, atPoint) { - return before + (after - before) * atPoint; - } - - function mergeBuffers(channelBuffer, rLength) { - var result = new Float64Array(rLength); - var offset = 0; - var lng = channelBuffer.length; - - for (var i = 0; i < lng; i++) { - var buffer = channelBuffer[i]; - result.set(buffer, offset); - offset += buffer.length; - } - - return result; - } - - function interleave(leftChannel, rightChannel) { - var length = leftChannel.length + rightChannel.length; - - var result = new Float64Array(length); - - var inputIndex = 0; - - for (var index = 0; index < length;) { - result[index++] = leftChannel[inputIndex]; - result[index++] = rightChannel[inputIndex]; - inputIndex++; - } - return result; - } - - function writeUTFBytes(view, offset, string) { - var lng = string.length; - for (var i = 0; i < lng; i++) { - view.setUint8(offset + i, string.charCodeAt(i)); - } - } - - // interleave both channels together - var interleaved; - - if (numberOfAudioChannels === 2) { - interleaved = interleave(leftBuffers, rightBuffers); - } - - if (numberOfAudioChannels === 1) { - interleaved = leftBuffers; - } - - var interleavedLength = interleaved.length; - - // create wav file - var resultingBufferLength = 44 + interleavedLength * 2; - - var buffer = new ArrayBuffer(resultingBufferLength); - - var view = new DataView(buffer); - - // RIFF chunk descriptor/identifier - writeUTFBytes(view, 0, 'RIFF'); - - // RIFF chunk length - // changed "44" to "36" via #401 - view.setUint32(4, 36 + interleavedLength * 2, true); - - // RIFF type - writeUTFBytes(view, 8, 'WAVE'); - - // format chunk identifier - // FMT sub-chunk - writeUTFBytes(view, 12, 'fmt '); - - // format chunk length - view.setUint32(16, 16, true); - - // sample format (raw) - view.setUint16(20, 1, true); - - // stereo (2 channels) - view.setUint16(22, numberOfAudioChannels, true); - - // sample rate - view.setUint32(24, sampleRate, true); - - // byte rate (sample rate * block align) - view.setUint32(28, sampleRate * numberOfAudioChannels * 2, true); - - // block align (channel count * bytes per sample) - view.setUint16(32, numberOfAudioChannels * 2, true); - - // bits per sample - view.setUint16(34, 16, true); - - // data sub-chunk - // data chunk identifier - writeUTFBytes(view, 36, 'data'); - - // data chunk length - view.setUint32(40, interleavedLength * 2, true); - - // write the PCM samples - var lng = interleavedLength; - var index = 44; - var volume = 1; - for (var i = 0; i < lng; i++) { - view.setInt16(index, interleaved[i] * (0x7FFF * volume), true); - index += 2; - } - - if (cb) { - return cb({ - buffer: buffer, - view: view - }); - } - - postMessage({ - buffer: buffer, - view: view - }); - } - - if (config.noWorker) { - mergeAudioBuffers(config, function (data) { - callback(data.buffer, data.view); - }); - return; - } - - - var webWorker = processInWebWorker(mergeAudioBuffers); - - webWorker.onmessage = function (event) { - callback(event.data.buffer, event.data.view); - - // release memory - URL.revokeObjectURL(webWorker.workerURL); - - // kill webworker (or Chrome will kill your page after ~25 calls) - webWorker.terminate(); - }; - - webWorker.postMessage(config); - } - - function processInWebWorker(_function) { - var workerURL = URL.createObjectURL(new Blob([_function.toString(), - ';this.onmessage = function (eee) {' + _function.name + '(eee.data);}' - ], { - type: 'application/javascript' - })); - - var worker = new Worker(workerURL); - worker.workerURL = workerURL; - return worker; - } - - /** - * This method stops recording MediaStream. - * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. - * @method - * @memberof StereoAudioRecorder - * @example - * recorder.stop(function(blob) { - * video.src = URL.createObjectURL(blob); - * }); - */ - this.stop = function (callback) { - callback = callback || function () { }; - - // stop recording - recording = false; - - mergeLeftRightBuffers({ - desiredSampRate: desiredSampRate, - sampleRate: sampleRate, - numberOfAudioChannels: numberOfAudioChannels, - internalInterleavedLength: recordingLength, - leftBuffers: leftchannel, - rightBuffers: numberOfAudioChannels === 1 ? [] : rightchannel, - noWorker: config.noWorker - }, function (buffer, view) { - /** - * @property {Blob} blob - The recorded blob object. - * @memberof StereoAudioRecorder - * @example - * recorder.stop(function(){ - * var blob = recorder.blob; - * }); - */ - self.blob = new Blob([view], { - type: 'audio/wav' - }); - - /** - * @property {ArrayBuffer} buffer - The recorded buffer object. - * @memberof StereoAudioRecorder - * @example - * recorder.stop(function(){ - * var buffer = recorder.buffer; - * }); - */ - self.buffer = new ArrayBuffer(view.buffer.byteLength); - - /** - * @property {DataView} view - The recorded data-view object. - * @memberof StereoAudioRecorder - * @example - * recorder.stop(function(){ - * var view = recorder.view; - * }); - */ - self.view = view; - - self.sampleRate = desiredSampRate || sampleRate; - self.bufferSize = bufferSize; - - // recorded audio length - self.length = recordingLength; - - isAudioProcessStarted = false; - - if (callback) { - callback(self.blob); - } - }); - }; - - if (typeof RecordRTC.Storage === 'undefined') { - RecordRTC.Storage = { - AudioContextConstructor: null, - AudioContext: window.AudioContext || window.webkitAudioContext - }; - } - - if (!RecordRTC.Storage.AudioContextConstructor || RecordRTC.Storage.AudioContextConstructor.state === 'closed') { - RecordRTC.Storage.AudioContextConstructor = new RecordRTC.Storage.AudioContext(); - } - - var context = RecordRTC.Storage.AudioContextConstructor; - - // creates an audio node from the microphone incoming stream - var audioInput = context.createMediaStreamSource(mediaStream); - - var legalBufferValues = [0, 256, 512, 1024, 2048, 4096, 8192, 16384]; - - /** - * From the spec: This value controls how frequently the audioprocess event is - * dispatched and how many sample-frames need to be processed each call. - * Lower values for buffer size will result in a lower (better) latency. - * Higher values will be necessary to avoid audio breakup and glitches - * The size of the buffer (in sample-frames) which needs to - * be processed each time onprocessaudio is called. - * Legal values are (256, 512, 1024, 2048, 4096, 8192, 16384). - * @property {number} bufferSize - Buffer-size for how frequently the audioprocess event is dispatched. - * @memberof StereoAudioRecorder - * @example - * recorder = new StereoAudioRecorder(mediaStream, { - * bufferSize: 4096 - * }); - */ - - // "0" means, let chrome decide the most accurate buffer-size for current platform. - var bufferSize = typeof config.bufferSize === 'undefined' ? 4096 : config.bufferSize; - - if (legalBufferValues.indexOf(bufferSize) === -1) { - if (!config.disableLogs) { - console.log('Legal values for buffer-size are ' + JSON.stringify(legalBufferValues, null, '\t')); - } - } - - if (context.createJavaScriptNode) { - jsAudioNode = context.createJavaScriptNode(bufferSize, numberOfAudioChannels, numberOfAudioChannels); - } else if (context.createScriptProcessor) { - jsAudioNode = context.createScriptProcessor(bufferSize, numberOfAudioChannels, numberOfAudioChannels); - } else { - throw 'WebAudio API has no support on this browser.'; - } - - // connect the stream to the script processor - audioInput.connect(jsAudioNode); - - if (!config.bufferSize) { - bufferSize = jsAudioNode.bufferSize; // device buffer-size - } - - /** - * The sample rate (in sample-frames per second) at which the - * AudioContext handles audio. It is assumed that all AudioNodes - * in the context run at this rate. In making this assumption, - * sample-rate converters or "varispeed" processors are not supported - * in real-time processing. - * The sampleRate parameter describes the sample-rate of the - * linear PCM audio data in the buffer in sample-frames per second. - * An implementation must support sample-rates in at least - * the range 22050 to 96000. - * @property {number} sampleRate - Buffer-size for how frequently the audioprocess event is dispatched. - * @memberof StereoAudioRecorder - * @example - * recorder = new StereoAudioRecorder(mediaStream, { - * sampleRate: 44100 - * }); - */ - var sampleRate = typeof config.sampleRate !== 'undefined' ? config.sampleRate : context.sampleRate || 44100; - - if (sampleRate < 22050 || sampleRate > 96000) { - // Ref: http://stackoverflow.com/a/26303918/552182 - if (!config.disableLogs) { - console.log('sample-rate must be under range 22050 and 96000.'); - } - } - - if (!config.disableLogs) { - if (config.desiredSampRate) { - console.log('Desired sample-rate: ' + config.desiredSampRate); - } - } - - var isPaused = false; - /** - * This method pauses the recording process. - * @method - * @memberof StereoAudioRecorder - * @example - * recorder.pause(); - */ - this.pause = function () { - isPaused = true; - }; - - /** - * This method resumes the recording process. - * @method - * @memberof StereoAudioRecorder - * @example - * recorder.resume(); - */ - this.resume = function () { - if (isMediaStreamActive() === false) { - throw 'Please make sure MediaStream is active.'; - } - - if (!recording) { - if (!config.disableLogs) { - console.log('Seems recording has been restarted.'); - } - this.record(); - return; - } - - isPaused = false; - }; - - /** - * This method resets currently recorded data. - * @method - * @memberof StereoAudioRecorder - * @example - * recorder.clearRecordedData(); - */ - this.clearRecordedData = function () { - config.checkForInactiveTracks = false; - - if (recording) { - this.stop(clearRecordedDataCB); - } - - clearRecordedDataCB(); - }; - - function resetVariables() { - leftchannel = []; - rightchannel = []; - recordingLength = 0; - isAudioProcessStarted = false; - recording = false; - isPaused = false; - context = null; - - self.leftchannel = leftchannel; - self.rightchannel = rightchannel; - self.numberOfAudioChannels = numberOfAudioChannels; - self.desiredSampRate = desiredSampRate; - self.sampleRate = sampleRate; - self.recordingLength = recordingLength; - - intervalsBasedBuffers = { - left: [], - right: [], - recordingLength: 0 - }; - } - - function clearRecordedDataCB() { - if (jsAudioNode) { - jsAudioNode.onaudioprocess = null; - jsAudioNode.disconnect(); - jsAudioNode = null; - } - - if (audioInput) { - audioInput.disconnect(); - audioInput = null; - } - - resetVariables(); - } - - // for debugging - this.name = 'StereoAudioRecorder'; - this.toString = function () { - return this.name; - }; - - var isAudioProcessStarted = false; - - function onAudioProcessDataAvailable(e) { - if (isPaused) { - return; - } - - if (isMediaStreamActive() === false) { - if (!config.disableLogs) { - console.log('MediaStream seems stopped.'); - } - jsAudioNode.disconnect(); - recording = false; - } - - if (!recording) { - if (audioInput) { - audioInput.disconnect(); - audioInput = null; - } - return; - } - - /** - * This method is called on "onaudioprocess" event's first invocation. - * @method {function} onAudioProcessStarted - * @memberof StereoAudioRecorder - * @example - * recorder.onAudioProcessStarted: function() { }; - */ - if (!isAudioProcessStarted) { - isAudioProcessStarted = true; - if (config.onAudioProcessStarted) { - config.onAudioProcessStarted(); - } - - if (config.initCallback) { - config.initCallback(); - } - } - - var left = e.inputBuffer.getChannelData(0); - - // we clone the samples - var chLeft = new Float32Array(left); - leftchannel.push(chLeft); - - if (numberOfAudioChannels === 2) { - var right = e.inputBuffer.getChannelData(1); - var chRight = new Float32Array(right); - rightchannel.push(chRight); - } - - recordingLength += bufferSize; - - // export raw PCM - self.recordingLength = recordingLength; - - if (typeof config.timeSlice !== 'undefined') { - intervalsBasedBuffers.recordingLength += bufferSize; - intervalsBasedBuffers.left.push(chLeft); - - if (numberOfAudioChannels === 2) { - intervalsBasedBuffers.right.push(chRight); - } - } - } - - jsAudioNode.onaudioprocess = onAudioProcessDataAvailable; - - // to prevent self audio to be connected with speakers - if (context.createMediaStreamDestination) { - jsAudioNode.connect(context.createMediaStreamDestination()); - } else { - jsAudioNode.connect(context.destination); - } - - // export raw PCM - this.leftchannel = leftchannel; - this.rightchannel = rightchannel; - this.numberOfAudioChannels = numberOfAudioChannels; - this.desiredSampRate = desiredSampRate; - this.sampleRate = sampleRate; - self.recordingLength = recordingLength; - - // helper for intervals based blobs - var intervalsBasedBuffers = { - left: [], - right: [], - recordingLength: 0 - }; - - // this looper is used to support intervals based blobs (via timeSlice+ondataavailable) - function looper() { - if (!recording || typeof config.ondataavailable !== 'function' || typeof config.timeSlice === 'undefined') { - return; - } - - if (intervalsBasedBuffers.left.length) { - mergeLeftRightBuffers({ - desiredSampRate: desiredSampRate, - sampleRate: sampleRate, - numberOfAudioChannels: numberOfAudioChannels, - internalInterleavedLength: intervalsBasedBuffers.recordingLength, - leftBuffers: intervalsBasedBuffers.left, - rightBuffers: numberOfAudioChannels === 1 ? [] : intervalsBasedBuffers.right - }, function (buffer, view) { - var blob = new Blob([view], { - type: 'audio/wav' - }); - config.ondataavailable(blob); - - setTimeout(looper, config.timeSlice); - }); - - intervalsBasedBuffers = { - left: [], - right: [], - recordingLength: 0 - }; - } else { - setTimeout(looper, config.timeSlice); - } - } - } - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.StereoAudioRecorder = StereoAudioRecorder; - } - - // _________________ - // CanvasRecorder.js - - /** - * CanvasRecorder is a standalone class used by {@link RecordRTC} to bring HTML5-Canvas recording into video WebM. It uses HTML2Canvas library and runs top over {@link Whammy}. - * @summary HTML2Canvas recording into video WebM. - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @typedef CanvasRecorder - * @class - * @example - * var recorder = new CanvasRecorder(htmlElement, { disableLogs: true, useWhammyRecorder: true }); - * recorder.record(); - * recorder.stop(function(blob) { - * video.src = URL.createObjectURL(blob); - * }); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - * @param {HTMLElement} htmlElement - querySelector/getElementById/getElementsByTagName[0]/etc. - * @param {object} config - {disableLogs:true, initCallback: function} - */ - - function CanvasRecorder(htmlElement, config) { - if (typeof html2canvas === 'undefined') { - throw 'Please link: https://www.webrtc-experiment.com/screenshot.js'; - } - - config = config || {}; - if (!config.frameInterval) { - config.frameInterval = 10; - } - - // via DetectRTC.js - var isCanvasSupportsStreamCapturing = false; - ['captureStream', 'mozCaptureStream', 'webkitCaptureStream'].forEach(function (item) { - if (item in document.createElement('canvas')) { - isCanvasSupportsStreamCapturing = true; - } - }); - - var _isChrome = (!!window.webkitRTCPeerConnection || !!window.webkitGetUserMedia) && !!window.chrome; - - var chromeVersion = 50; - var matchArray = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./); - if (_isChrome && matchArray && matchArray[2]) { - chromeVersion = parseInt(matchArray[2], 10); - } - - if (_isChrome && chromeVersion < 52) { - isCanvasSupportsStreamCapturing = false; - } - - if (config.useWhammyRecorder) { - isCanvasSupportsStreamCapturing = false; - } - - var globalCanvas, mediaStreamRecorder; - - if (isCanvasSupportsStreamCapturing) { - if (!config.disableLogs) { - console.log('Your browser supports both MediRecorder API and canvas.captureStream!'); - } - - if (htmlElement instanceof HTMLCanvasElement) { - globalCanvas = htmlElement; - } else if (htmlElement instanceof CanvasRenderingContext2D) { - globalCanvas = htmlElement.canvas; - } else { - throw 'Please pass either HTMLCanvasElement or CanvasRenderingContext2D.'; - } - } else if (!!navigator.mozGetUserMedia) { - if (!config.disableLogs) { - console.error('Canvas recording is NOT supported in Firefox.'); - } - } - - var isRecording; - - /** - * This method records Canvas. - * @method - * @memberof CanvasRecorder - * @example - * recorder.record(); - */ - this.record = function () { - isRecording = true; - - if (isCanvasSupportsStreamCapturing && !config.useWhammyRecorder) { - // CanvasCaptureMediaStream - var canvasMediaStream; - if ('captureStream' in globalCanvas) { - canvasMediaStream = globalCanvas.captureStream(25); // 25 FPS - } else if ('mozCaptureStream' in globalCanvas) { - canvasMediaStream = globalCanvas.mozCaptureStream(25); - } else if ('webkitCaptureStream' in globalCanvas) { - canvasMediaStream = globalCanvas.webkitCaptureStream(25); - } - - try { - var mdStream = new MediaStream(); - mdStream.addTrack(getTracks(canvasMediaStream, 'video')[0]); - canvasMediaStream = mdStream; - } catch (e) { } - - if (!canvasMediaStream) { - throw 'captureStream API are NOT available.'; - } - - // Note: Jan 18, 2016 status is that, - // Firefox MediaRecorder API can't record CanvasCaptureMediaStream object. - mediaStreamRecorder = new MediaStreamRecorder(canvasMediaStream, { - mimeType: config.mimeType || 'video/webm' - }); - mediaStreamRecorder.record(); - } else { - whammy.frames = []; - lastTime = new Date().getTime(); - drawCanvasFrame(); - } - - if (config.initCallback) { - config.initCallback(); - } - }; - - this.getWebPImages = function (callback) { - if (htmlElement.nodeName.toLowerCase() !== 'canvas') { - callback(); - return; - } - - var framesLength = whammy.frames.length; - whammy.frames.forEach(function (frame, idx) { - var framesRemaining = framesLength - idx; - if (!config.disableLogs) { - console.log(framesRemaining + '/' + framesLength + ' frames remaining'); - } - - if (config.onEncodingCallback) { - config.onEncodingCallback(framesRemaining, framesLength); - } - - var webp = frame.image.toDataURL('image/webp', 1); - whammy.frames[idx].image = webp; - }); - - if (!config.disableLogs) { - console.log('Generating WebM'); - } - - callback(); - }; - - /** - * This method stops recording Canvas. - * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. - * @method - * @memberof CanvasRecorder - * @example - * recorder.stop(function(blob) { - * video.src = URL.createObjectURL(blob); - * }); - */ - this.stop = function (callback) { - isRecording = false; - - var that = this; - - if (isCanvasSupportsStreamCapturing && mediaStreamRecorder) { - mediaStreamRecorder.stop(callback); - return; - } - - this.getWebPImages(function () { - /** - * @property {Blob} blob - Recorded frames in video/webm blob. - * @memberof CanvasRecorder - * @example - * recorder.stop(function() { - * var blob = recorder.blob; - * }); - */ - whammy.compile(function (blob) { - if (!config.disableLogs) { - console.log('Recording finished!'); - } - - that.blob = blob; - - if (that.blob.forEach) { - that.blob = new Blob([], { - type: 'video/webm' - }); - } - - if (callback) { - callback(that.blob); - } - - whammy.frames = []; - }); - }); - }; - - var isPausedRecording = false; - - /** - * This method pauses the recording process. - * @method - * @memberof CanvasRecorder - * @example - * recorder.pause(); - */ - this.pause = function () { - isPausedRecording = true; - - if (mediaStreamRecorder instanceof MediaStreamRecorder) { - mediaStreamRecorder.pause(); - return; - } - }; - - /** - * This method resumes the recording process. - * @method - * @memberof CanvasRecorder - * @example - * recorder.resume(); - */ - this.resume = function () { - isPausedRecording = false; - - if (mediaStreamRecorder instanceof MediaStreamRecorder) { - mediaStreamRecorder.resume(); - return; - } - - if (!isRecording) { - this.record(); - } - }; - - /** - * This method resets currently recorded data. - * @method - * @memberof CanvasRecorder - * @example - * recorder.clearRecordedData(); - */ - this.clearRecordedData = function () { - if (isRecording) { - this.stop(clearRecordedDataCB); - } - clearRecordedDataCB(); - }; - - function clearRecordedDataCB() { - whammy.frames = []; - isRecording = false; - isPausedRecording = false; - } - - // for debugging - this.name = 'CanvasRecorder'; - this.toString = function () { - return this.name; - }; - - function cloneCanvas() { - //create a new canvas - var newCanvas = document.createElement('canvas'); - var context = newCanvas.getContext('2d'); - - //set dimensions - newCanvas.width = htmlElement.width; - newCanvas.height = htmlElement.height; - - //apply the old canvas to the new one - context.drawImage(htmlElement, 0, 0); - - //return the new canvas - return newCanvas; - } - - function drawCanvasFrame() { - if (isPausedRecording) { - lastTime = new Date().getTime(); - return setTimeout(drawCanvasFrame, 500); - } - - if (htmlElement.nodeName.toLowerCase() === 'canvas') { - var duration = new Date().getTime() - lastTime; - // via #206, by Jack i.e. @Seymourr - lastTime = new Date().getTime(); - - whammy.frames.push({ - image: cloneCanvas(), - duration: duration - }); - - if (isRecording) { - setTimeout(drawCanvasFrame, config.frameInterval); - } - return; - } - - html2canvas(htmlElement, { - grabMouse: typeof config.showMousePointer === 'undefined' || config.showMousePointer, - onrendered: function (canvas) { - var duration = new Date().getTime() - lastTime; - if (!duration) { - return setTimeout(drawCanvasFrame, config.frameInterval); - } - - // via #206, by Jack i.e. @Seymourr - lastTime = new Date().getTime(); - - whammy.frames.push({ - image: canvas.toDataURL('image/webp', 1), - duration: duration - }); - - if (isRecording) { - setTimeout(drawCanvasFrame, config.frameInterval); - } - } - }); - } - - var lastTime = new Date().getTime(); - - var whammy = new Whammy.Video(100); - } - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.CanvasRecorder = CanvasRecorder; - } - - // _________________ - // WhammyRecorder.js - - /** - * WhammyRecorder is a standalone class used by {@link RecordRTC} to bring video recording in Chrome. It runs top over {@link Whammy}. - * @summary Video recording feature in Chrome. - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @typedef WhammyRecorder - * @class - * @example - * var recorder = new WhammyRecorder(mediaStream); - * recorder.record(); - * recorder.stop(function(blob) { - * video.src = URL.createObjectURL(blob); - * }); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. - * @param {object} config - {disableLogs: true, initCallback: function, video: HTMLVideoElement, etc.} - */ - - function WhammyRecorder(mediaStream, config) { - - config = config || {}; - - if (!config.frameInterval) { - config.frameInterval = 10; - } - - if (!config.disableLogs) { - console.log('Using frames-interval:', config.frameInterval); - } - - /** - * This method records video. - * @method - * @memberof WhammyRecorder - * @example - * recorder.record(); - */ - this.record = function () { - if (!config.width) { - config.width = 320; - } - - if (!config.height) { - config.height = 240; - } - - if (!config.video) { - config.video = { - width: config.width, - height: config.height - }; - } - - if (!config.canvas) { - config.canvas = { - width: config.width, - height: config.height - }; - } - - canvas.width = config.canvas.width || 320; - canvas.height = config.canvas.height || 240; - - context = canvas.getContext('2d'); - - // setting defaults - if (config.video && config.video instanceof HTMLVideoElement) { - video = config.video.cloneNode(); - - if (config.initCallback) { - config.initCallback(); - } - } else { - video = document.createElement('video'); - - setSrcObject(mediaStream, video); - - video.onloadedmetadata = function () { // "onloadedmetadata" may NOT work in FF? - if (config.initCallback) { - config.initCallback(); - } - }; - - video.width = config.video.width; - video.height = config.video.height; - } - - video.muted = true; - video.play(); - - lastTime = new Date().getTime(); - whammy = new Whammy.Video(); - - if (!config.disableLogs) { - console.log('canvas resolutions', canvas.width, '*', canvas.height); - console.log('video width/height', video.width || canvas.width, '*', video.height || canvas.height); - } - - drawFrames(config.frameInterval); - }; - - /** - * Draw and push frames to Whammy - * @param {integer} frameInterval - set minimum interval (in milliseconds) between each time we push a frame to Whammy - */ - function drawFrames(frameInterval) { - frameInterval = typeof frameInterval !== 'undefined' ? frameInterval : 10; - - var duration = new Date().getTime() - lastTime; - if (!duration) { - return setTimeout(drawFrames, frameInterval, frameInterval); - } - - if (isPausedRecording) { - lastTime = new Date().getTime(); - return setTimeout(drawFrames, 100); - } - - // via #206, by Jack i.e. @Seymourr - lastTime = new Date().getTime(); - - if (video.paused) { - // via: https://github.com/muaz-khan/WebRTC-Experiment/pull/316 - // Tweak for Android Chrome - video.play(); - } - - context.drawImage(video, 0, 0, canvas.width, canvas.height); - whammy.frames.push({ - duration: duration, - image: canvas.toDataURL('image/webp') - }); - - if (!isStopDrawing) { - setTimeout(drawFrames, frameInterval, frameInterval); - } - } - - function asyncLoop(o) { - var i = -1, - length = o.length; - - (function loop() { - i++; - if (i === length) { - o.callback(); - return; - } - - // "setTimeout" added by Jim McLeod - setTimeout(function () { - o.functionToLoop(loop, i); - }, 1); - })(); - } - - - /** - * remove black frames from the beginning to the specified frame - * @param {Array} _frames - array of frames to be checked - * @param {number} _framesToCheck - number of frame until check will be executed (-1 - will drop all frames until frame not matched will be found) - * @param {number} _pixTolerance - 0 - very strict (only black pixel color) ; 1 - all - * @param {number} _frameTolerance - 0 - very strict (only black frame color) ; 1 - all - * @returns {Array} - array of frames - */ - // pull#293 by @volodalexey - function dropBlackFrames(_frames, _framesToCheck, _pixTolerance, _frameTolerance, callback) { - var localCanvas = document.createElement('canvas'); - localCanvas.width = canvas.width; - localCanvas.height = canvas.height; - var context2d = localCanvas.getContext('2d'); - var resultFrames = []; - - var checkUntilNotBlack = _framesToCheck === -1; - var endCheckFrame = (_framesToCheck && _framesToCheck > 0 && _framesToCheck <= _frames.length) ? - _framesToCheck : _frames.length; - var sampleColor = { - r: 0, - g: 0, - b: 0 - }; - var maxColorDifference = Math.sqrt( - Math.pow(255, 2) + - Math.pow(255, 2) + - Math.pow(255, 2) - ); - var pixTolerance = _pixTolerance && _pixTolerance >= 0 && _pixTolerance <= 1 ? _pixTolerance : 0; - var frameTolerance = _frameTolerance && _frameTolerance >= 0 && _frameTolerance <= 1 ? _frameTolerance : 0; - var doNotCheckNext = false; - - asyncLoop({ - length: endCheckFrame, - functionToLoop: function (loop, f) { - var matchPixCount, endPixCheck, maxPixCount; - - var finishImage = function () { - if (!doNotCheckNext && maxPixCount - matchPixCount <= maxPixCount * frameTolerance); else { - // console.log('frame is passed : ' + f); - if (checkUntilNotBlack) { - doNotCheckNext = true; - } - resultFrames.push(_frames[f]); - } - loop(); - }; - - if (!doNotCheckNext) { - var image = new Image(); - image.onload = function () { - context2d.drawImage(image, 0, 0, canvas.width, canvas.height); - var imageData = context2d.getImageData(0, 0, canvas.width, canvas.height); - matchPixCount = 0; - endPixCheck = imageData.data.length; - maxPixCount = imageData.data.length / 4; - - for (var pix = 0; pix < endPixCheck; pix += 4) { - var currentColor = { - r: imageData.data[pix], - g: imageData.data[pix + 1], - b: imageData.data[pix + 2] - }; - var colorDifference = Math.sqrt( - Math.pow(currentColor.r - sampleColor.r, 2) + - Math.pow(currentColor.g - sampleColor.g, 2) + - Math.pow(currentColor.b - sampleColor.b, 2) - ); - // difference in color it is difference in color vectors (r1,g1,b1) <=> (r2,g2,b2) - if (colorDifference <= maxColorDifference * pixTolerance) { - matchPixCount++; - } - } - finishImage(); - }; - image.src = _frames[f].image; - } else { - finishImage(); - } - }, - callback: function () { - resultFrames = resultFrames.concat(_frames.slice(endCheckFrame)); - - if (resultFrames.length <= 0) { - // at least one last frame should be available for next manipulation - // if total duration of all frames will be < 1000 than ffmpeg doesn't work well... - resultFrames.push(_frames[_frames.length - 1]); - } - callback(resultFrames); - } - }); - } - - var isStopDrawing = false; - - /** - * This method stops recording video. - * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. - * @method - * @memberof WhammyRecorder - * @example - * recorder.stop(function(blob) { - * video.src = URL.createObjectURL(blob); - * }); - */ - this.stop = function (callback) { - callback = callback || function () { }; - - isStopDrawing = true; - - var _this = this; - // analyse of all frames takes some time! - setTimeout(function () { - // e.g. dropBlackFrames(frames, 10, 1, 1) - will cut all 10 frames - // e.g. dropBlackFrames(frames, 10, 0.5, 0.5) - will analyse 10 frames - // e.g. dropBlackFrames(frames, 10) === dropBlackFrames(frames, 10, 0, 0) - will analyse 10 frames with strict black color - dropBlackFrames(whammy.frames, -1, null, null, function (frames) { - whammy.frames = frames; - - // to display advertisement images! - if (config.advertisement && config.advertisement.length) { - whammy.frames = config.advertisement.concat(whammy.frames); - } - - /** - * @property {Blob} blob - Recorded frames in video/webm blob. - * @memberof WhammyRecorder - * @example - * recorder.stop(function() { - * var blob = recorder.blob; - * }); - */ - whammy.compile(function (blob) { - _this.blob = blob; - - if (_this.blob.forEach) { - _this.blob = new Blob([], { - type: 'video/webm' - }); - } - - if (callback) { - callback(_this.blob); - } - }); - }); - }, 10); - }; - - var isPausedRecording = false; - - /** - * This method pauses the recording process. - * @method - * @memberof WhammyRecorder - * @example - * recorder.pause(); - */ - this.pause = function () { - isPausedRecording = true; - }; - - /** - * This method resumes the recording process. - * @method - * @memberof WhammyRecorder - * @example - * recorder.resume(); - */ - this.resume = function () { - isPausedRecording = false; - - if (isStopDrawing) { - this.record(); - } - }; - - /** - * This method resets currently recorded data. - * @method - * @memberof WhammyRecorder - * @example - * recorder.clearRecordedData(); - */ - this.clearRecordedData = function () { - if (!isStopDrawing) { - this.stop(clearRecordedDataCB); - } - clearRecordedDataCB(); - }; - - function clearRecordedDataCB() { - whammy.frames = []; - isStopDrawing = true; - isPausedRecording = false; - } - - // for debugging - this.name = 'WhammyRecorder'; - this.toString = function () { - return this.name; - }; - - var canvas = document.createElement('canvas'); - var context = canvas.getContext('2d'); - - var video; - var lastTime; - var whammy; - } - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.WhammyRecorder = WhammyRecorder; - } - - // https://github.com/antimatter15/whammy/blob/master/LICENSE - // _________ - // Whammy.js - - // todo: Firefox now supports webp for webm containers! - // their MediaRecorder implementation works well! - // should we provide an option to record via Whammy.js or MediaRecorder API is a better solution? - - /** - * Whammy is a standalone class used by {@link RecordRTC} to bring video recording in Chrome. It is written by {@link https://github.com/antimatter15|antimatter15} - * @summary A real time javascript webm encoder based on a canvas hack. - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @typedef Whammy - * @class - * @example - * var recorder = new Whammy().Video(15); - * recorder.add(context || canvas || dataURL); - * var output = recorder.compile(); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - */ - - var Whammy = (function () { - // a more abstract-ish API - - function WhammyVideo(duration) { - this.frames = []; - this.duration = duration || 1; - this.quality = 0.8; - } - - /** - * Pass Canvas or Context or image/webp(string) to {@link Whammy} encoder. - * @method - * @memberof Whammy - * @example - * recorder = new Whammy().Video(0.8, 100); - * recorder.add(canvas || context || 'image/webp'); - * @param {string} frame - Canvas || Context || image/webp - * @param {number} duration - Stick a duration (in milliseconds) - */ - WhammyVideo.prototype.add = function (frame, duration) { - if ('canvas' in frame) { //CanvasRenderingContext2D - frame = frame.canvas; - } - - if ('toDataURL' in frame) { - frame = frame.toDataURL('image/webp', this.quality); - } - - if (!(/^data:image\/webp;base64,/ig).test(frame)) { - throw 'Input must be formatted properly as a base64 encoded DataURI of type image/webp'; - } - this.frames.push({ - image: frame, - duration: duration || this.duration - }); - }; - - function processInWebWorker(_function) { - var blob = URL.createObjectURL(new Blob([_function.toString(), - 'this.onmessage = function (eee) {' + _function.name + '(eee.data);}' - ], { - type: 'application/javascript' - })); - - var worker = new Worker(blob); - URL.revokeObjectURL(blob); - return worker; - } - - function whammyInWebWorker(frames) { - function ArrayToWebM(frames) { - var info = checkFrames(frames); - if (!info) { - return []; - } - - var clusterMaxDuration = 30000; - - var EBML = [{ - 'id': 0x1a45dfa3, // EBML - 'data': [{ - 'data': 1, - 'id': 0x4286 // EBMLVersion - }, { - 'data': 1, - 'id': 0x42f7 // EBMLReadVersion - }, { - 'data': 4, - 'id': 0x42f2 // EBMLMaxIDLength - }, { - 'data': 8, - 'id': 0x42f3 // EBMLMaxSizeLength - }, { - 'data': 'webm', - 'id': 0x4282 // DocType - }, { - 'data': 2, - 'id': 0x4287 // DocTypeVersion - }, { - 'data': 2, - 'id': 0x4285 // DocTypeReadVersion - }] - }, { - 'id': 0x18538067, // Segment - 'data': [{ - 'id': 0x1549a966, // Info - 'data': [{ - 'data': 1e6, //do things in millisecs (num of nanosecs for duration scale) - 'id': 0x2ad7b1 // TimecodeScale - }, { - 'data': 'whammy', - 'id': 0x4d80 // MuxingApp - }, { - 'data': 'whammy', - 'id': 0x5741 // WritingApp - }, { - 'data': doubleToString(info.duration), - 'id': 0x4489 // Duration - }] - }, { - 'id': 0x1654ae6b, // Tracks - 'data': [{ - 'id': 0xae, // TrackEntry - 'data': [{ - 'data': 1, - 'id': 0xd7 // TrackNumber - }, { - 'data': 1, - 'id': 0x73c5 // TrackUID - }, { - 'data': 0, - 'id': 0x9c // FlagLacing - }, { - 'data': 'und', - 'id': 0x22b59c // Language - }, { - 'data': 'V_VP8', - 'id': 0x86 // CodecID - }, { - 'data': 'VP8', - 'id': 0x258688 // CodecName - }, { - 'data': 1, - 'id': 0x83 // TrackType - }, { - 'id': 0xe0, // Video - 'data': [{ - 'data': info.width, - 'id': 0xb0 // PixelWidth - }, { - 'data': info.height, - 'id': 0xba // PixelHeight - }] - }] - }] - }] - }]; - - //Generate clusters (max duration) - var frameNumber = 0; - var clusterTimecode = 0; - while (frameNumber < frames.length) { - - var clusterFrames = []; - var clusterDuration = 0; - do { - clusterFrames.push(frames[frameNumber]); - clusterDuration += frames[frameNumber].duration; - frameNumber++; - } while (frameNumber < frames.length && clusterDuration < clusterMaxDuration); - - var clusterCounter = 0; - var cluster = { - 'id': 0x1f43b675, // Cluster - 'data': getClusterData(clusterTimecode, clusterCounter, clusterFrames) - }; //Add cluster to segment - EBML[1].data.push(cluster); - clusterTimecode += clusterDuration; - } - - return generateEBML(EBML); - } - - function getClusterData(clusterTimecode, clusterCounter, clusterFrames) { - return [{ - 'data': clusterTimecode, - 'id': 0xe7 // Timecode - }].concat(clusterFrames.map(function (webp) { - var block = makeSimpleBlock({ - discardable: 0, - frame: webp.data.slice(4), - invisible: 0, - keyframe: 1, - lacing: 0, - trackNum: 1, - timecode: Math.round(clusterCounter) - }); - clusterCounter += webp.duration; - return { - data: block, - id: 0xa3 - }; - })); - } - - // sums the lengths of all the frames and gets the duration - - function checkFrames(frames) { - if (!frames[0]) { - postMessage({ - error: 'Something went wrong. Maybe WebP format is not supported in the current browser.' - }); - return; - } - - var width = frames[0].width, - height = frames[0].height, - duration = frames[0].duration; - - for (var i = 1; i < frames.length; i++) { - duration += frames[i].duration; - } - return { - duration: duration, - width: width, - height: height - }; - } - - function numToBuffer(num) { - var parts = []; - while (num > 0) { - parts.push(num & 0xff); - num = num >> 8; - } - return new Uint8Array(parts.reverse()); - } - - function strToBuffer(str) { - return new Uint8Array(str.split('').map(function (e) { - return e.charCodeAt(0); - })); - } - - function bitsToBuffer(bits) { - var data = []; - var pad = (bits.length % 8) ? (new Array(1 + 8 - (bits.length % 8))).join('0') : ''; - bits = pad + bits; - for (var i = 0; i < bits.length; i += 8) { - data.push(parseInt(bits.substr(i, 8), 2)); - } - return new Uint8Array(data); - } - - function generateEBML(json) { - var ebml = []; - for (var i = 0; i < json.length; i++) { - var data = json[i].data; - - if (typeof data === 'object') { - data = generateEBML(data); - } - - if (typeof data === 'number') { - data = bitsToBuffer(data.toString(2)); - } - - if (typeof data === 'string') { - data = strToBuffer(data); - } - - var len = data.size || data.byteLength || data.length; - var zeroes = Math.ceil(Math.ceil(Math.log(len) / Math.log(2)) / 8); - var sizeToString = len.toString(2); - var padded = (new Array((zeroes * 7 + 7 + 1) - sizeToString.length)).join('0') + sizeToString; - var size = (new Array(zeroes)).join('0') + '1' + padded; - - ebml.push(numToBuffer(json[i].id)); - ebml.push(bitsToBuffer(size)); - ebml.push(data); - } - - return new Blob(ebml, { - type: 'video/webm' - }); - } - - function makeSimpleBlock(data) { - var flags = 0; - - if (data.keyframe) { - flags |= 128; - } - - if (data.invisible) { - flags |= 8; - } - - if (data.lacing) { - flags |= (data.lacing << 1); - } - - if (data.discardable) { - flags |= 1; - } - - if (data.trackNum > 127) { - throw 'TrackNumber > 127 not supported'; - } - - var out = [data.trackNum | 0x80, data.timecode >> 8, data.timecode & 0xff, flags].map(function (e) { - return String.fromCharCode(e); - }).join('') + data.frame; - - return out; - } - - function parseWebP(riff) { - var VP8 = riff.RIFF[0].WEBP[0]; - - var frameStart = VP8.indexOf('\x9d\x01\x2a'); // A VP8 keyframe starts with the 0x9d012a header - for (var i = 0, c = []; i < 4; i++) { - c[i] = VP8.charCodeAt(frameStart + 3 + i); - } - - var width, height, tmp; - - //the code below is literally copied verbatim from the bitstream spec - tmp = (c[1] << 8) | c[0]; - width = tmp & 0x3FFF; - tmp = (c[3] << 8) | c[2]; - height = tmp & 0x3FFF; - return { - width: width, - height: height, - data: VP8, - riff: riff - }; - } - - function getStrLength(string, offset) { - return parseInt(string.substr(offset + 4, 4).split('').map(function (i) { - var unpadded = i.charCodeAt(0).toString(2); - return (new Array(8 - unpadded.length + 1)).join('0') + unpadded; - }).join(''), 2); - } - - function parseRIFF(string) { - var offset = 0; - var chunks = {}; - - while (offset < string.length) { - var id = string.substr(offset, 4); - var len = getStrLength(string, offset); - var data = string.substr(offset + 4 + 4, len); - offset += 4 + 4 + len; - chunks[id] = chunks[id] || []; - - if (id === 'RIFF' || id === 'LIST') { - chunks[id].push(parseRIFF(data)); - } else { - chunks[id].push(data); - } - } - return chunks; - } - - function doubleToString(num) { - return [].slice.call( - new Uint8Array((new Float64Array([num])).buffer), 0).map(function (e) { - return String.fromCharCode(e); - }).reverse().join(''); - } - - var webm = new ArrayToWebM(frames.map(function (frame) { - var webp = parseWebP(parseRIFF(atob(frame.image.slice(23)))); - webp.duration = frame.duration; - return webp; - })); - - postMessage(webm); - } - - /** - * Encodes frames in WebM container. It uses WebWorkinvoke to invoke 'ArrayToWebM' method. - * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. - * @method - * @memberof Whammy - * @example - * recorder = new Whammy().Video(0.8, 100); - * recorder.compile(function(blob) { - * // blob.size - blob.type - * }); - */ - WhammyVideo.prototype.compile = function (callback) { - var webWorker = processInWebWorker(whammyInWebWorker); - - webWorker.onmessage = function (event) { - if (event.data.error) { - console.error(event.data.error); - return; - } - callback(event.data); - }; - - webWorker.postMessage(this.frames); - }; - - return { - /** - * A more abstract-ish API. - * @method - * @memberof Whammy - * @example - * recorder = new Whammy().Video(0.8, 100); - * @param {?number} speed - 0.8 - * @param {?number} quality - 100 - */ - Video: WhammyVideo - }; - })(); - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.Whammy = Whammy; - } - - // ______________ (indexed-db) - // DiskStorage.js - - /** - * DiskStorage is a standalone object used by {@link RecordRTC} to store recorded blobs in IndexedDB storage. - * @summary Writing blobs into IndexedDB. - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @example - * DiskStorage.Store({ - * audioBlob: yourAudioBlob, - * videoBlob: yourVideoBlob, - * gifBlob : yourGifBlob - * }); - * DiskStorage.Fetch(function(dataURL, type) { - * if(type === 'audioBlob') { } - * if(type === 'videoBlob') { } - * if(type === 'gifBlob') { } - * }); - * // DiskStorage.dataStoreName = 'recordRTC'; - * // DiskStorage.onError = function(error) { }; - * @property {function} init - This method must be called once to initialize IndexedDB ObjectStore. Though, it is auto-used internally. - * @property {function} Fetch - This method fetches stored blobs from IndexedDB. - * @property {function} Store - This method stores blobs in IndexedDB. - * @property {function} onError - This function is invoked for any known/unknown error. - * @property {string} dataStoreName - Name of the ObjectStore created in IndexedDB storage. - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - */ - - - var DiskStorage = { - /** - * This method must be called once to initialize IndexedDB ObjectStore. Though, it is auto-used internally. - * @method - * @memberof DiskStorage - * @internal - * @example - * DiskStorage.init(); - */ - init: function () { - var self = this; - - if (typeof indexedDB === 'undefined' || typeof indexedDB.open === 'undefined') { - console.error('IndexedDB API are not available in this browser.'); - return; - } - - var dbVersion = 1; - var dbName = this.dbName || location.href.replace(/\/|:|#|%|\.|\[|\]/g, ''), - db; - var request = indexedDB.open(dbName, dbVersion); - - function createObjectStore(dataBase) { - dataBase.createObjectStore(self.dataStoreName); - } - - function putInDB() { - var transaction = db.transaction([self.dataStoreName], 'readwrite'); - - if (self.videoBlob) { - transaction.objectStore(self.dataStoreName).put(self.videoBlob, 'videoBlob'); - } - - if (self.gifBlob) { - transaction.objectStore(self.dataStoreName).put(self.gifBlob, 'gifBlob'); - } - - if (self.audioBlob) { - transaction.objectStore(self.dataStoreName).put(self.audioBlob, 'audioBlob'); - } - - function getFromStore(portionName) { - transaction.objectStore(self.dataStoreName).get(portionName).onsuccess = function (event) { - if (self.callback) { - self.callback(event.target.result, portionName); - } - }; - } - - getFromStore('audioBlob'); - getFromStore('videoBlob'); - getFromStore('gifBlob'); - } - - request.onerror = self.onError; - - request.onsuccess = function () { - db = request.result; - db.onerror = self.onError; - - if (db.setVersion) { - if (db.version !== dbVersion) { - var setVersion = db.setVersion(dbVersion); - setVersion.onsuccess = function () { - createObjectStore(db); - putInDB(); - }; - } else { - putInDB(); - } - } else { - putInDB(); - } - }; - request.onupgradeneeded = function (event) { - createObjectStore(event.target.result); - }; - }, - /** - * This method fetches stored blobs from IndexedDB. - * @method - * @memberof DiskStorage - * @internal - * @example - * DiskStorage.Fetch(function(dataURL, type) { - * if(type === 'audioBlob') { } - * if(type === 'videoBlob') { } - * if(type === 'gifBlob') { } - * }); - */ - Fetch: function (callback) { - this.callback = callback; - this.init(); - - return this; - }, - /** - * This method stores blobs in IndexedDB. - * @method - * @memberof DiskStorage - * @internal - * @example - * DiskStorage.Store({ - * audioBlob: yourAudioBlob, - * videoBlob: yourVideoBlob, - * gifBlob : yourGifBlob - * }); - */ - Store: function (config) { - this.audioBlob = config.audioBlob; - this.videoBlob = config.videoBlob; - this.gifBlob = config.gifBlob; - - this.init(); - - return this; - }, - /** - * This function is invoked for any known/unknown error. - * @method - * @memberof DiskStorage - * @internal - * @example - * DiskStorage.onError = function(error){ - * alerot( JSON.stringify(error) ); - * }; - */ - onError: function (error) { - console.error(JSON.stringify(error, null, '\t')); - }, - - /** - * @property {string} dataStoreName - Name of the ObjectStore created in IndexedDB storage. - * @memberof DiskStorage - * @internal - * @example - * DiskStorage.dataStoreName = 'recordRTC'; - */ - dataStoreName: 'recordRTC', - dbName: null - }; - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.DiskStorage = DiskStorage; - } - - // ______________ - // GifRecorder.js - - /** - * GifRecorder is standalone calss used by {@link RecordRTC} to record video or canvas into animated gif. - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @typedef GifRecorder - * @class - * @example - * var recorder = new GifRecorder(mediaStream || canvas || context, { onGifPreview: function, onGifRecordingStarted: function, width: 1280, height: 720, frameRate: 200, quality: 10 }); - * recorder.record(); - * recorder.stop(function(blob) { - * img.src = URL.createObjectURL(blob); - * }); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - * @param {MediaStream} mediaStream - MediaStream object or HTMLCanvasElement or CanvasRenderingContext2D. - * @param {object} config - {disableLogs:true, initCallback: function, width: 320, height: 240, frameRate: 200, quality: 10} - */ - - function GifRecorder(mediaStream, config) { - if (typeof GIFEncoder === 'undefined') { - var script = document.createElement('script'); - script.src = 'https://www.webrtc-experiment.com/gif-recorder.js'; - (document.body || document.documentElement).appendChild(script); - } - - config = config || {}; - - var isHTMLObject = mediaStream instanceof CanvasRenderingContext2D || mediaStream instanceof HTMLCanvasElement; - - /** - * This method records MediaStream. - * @method - * @memberof GifRecorder - * @example - * recorder.record(); - */ - this.record = function () { - if (typeof GIFEncoder === 'undefined') { - setTimeout(self.record, 1000); - return; - } - - if (!isLoadedMetaData) { - setTimeout(self.record, 1000); - return; - } - - if (!isHTMLObject) { - if (!config.width) { - config.width = video.offsetWidth || 320; - } - - if (!config.height) { - config.height = video.offsetHeight || 240; - } - - if (!config.video) { - config.video = { - width: config.width, - height: config.height - }; - } - - if (!config.canvas) { - config.canvas = { - width: config.width, - height: config.height - }; - } - - canvas.width = config.canvas.width || 320; - canvas.height = config.canvas.height || 240; - - video.width = config.video.width || 320; - video.height = config.video.height || 240; - } - - // external library to record as GIF images - gifEncoder = new GIFEncoder(); - - // void setRepeat(int iter) - // Sets the number of times the set of GIF frames should be played. - // Default is 1; 0 means play indefinitely. - gifEncoder.setRepeat(0); - - // void setFrameRate(Number fps) - // Sets frame rate in frames per second. - // Equivalent to setDelay(1000/fps). - // Using "setDelay" instead of "setFrameRate" - gifEncoder.setDelay(config.frameRate || 200); - - // void setQuality(int quality) - // Sets quality of color quantization (conversion of images to the - // maximum 256 colors allowed by the GIF specification). - // Lower values (minimum = 1) produce better colors, - // but slow processing significantly. 10 is the default, - // and produces good color mapping at reasonable speeds. - // Values greater than 20 do not yield significant improvements in speed. - gifEncoder.setQuality(config.quality || 10); - - // Boolean start() - // This writes the GIF Header and returns false if it fails. - gifEncoder.start(); - - if (typeof config.onGifRecordingStarted === 'function') { - config.onGifRecordingStarted(); - } - - function drawVideoFrame(time) { - if (self.clearedRecordedData === true) { - return; - } - - if (isPausedRecording) { - return setTimeout(function () { - drawVideoFrame(time); - }, 100); - } - - lastAnimationFrame = requestAnimationFrame(drawVideoFrame); - - if (typeof lastFrameTime === undefined) { - lastFrameTime = time; - } - - // ~10 fps - if (time - lastFrameTime < 90) { - return; - } - - if (!isHTMLObject && video.paused) { - // via: https://github.com/muaz-khan/WebRTC-Experiment/pull/316 - // Tweak for Android Chrome - video.play(); - } - - if (!isHTMLObject) { - context.drawImage(video, 0, 0, canvas.width, canvas.height); - } - - if (config.onGifPreview) { - config.onGifPreview(canvas.toDataURL('image/png')); - } - - gifEncoder.addFrame(context); - lastFrameTime = time; - } - - lastAnimationFrame = requestAnimationFrame(drawVideoFrame); - - if (config.initCallback) { - config.initCallback(); - } - }; - - /** - * This method stops recording MediaStream. - * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. - * @method - * @memberof GifRecorder - * @example - * recorder.stop(function(blob) { - * img.src = URL.createObjectURL(blob); - * }); - */ - this.stop = function (callback) { - callback = callback || function () { }; - - if (lastAnimationFrame) { - cancelAnimationFrame(lastAnimationFrame); - } - - /** - * @property {Blob} blob - The recorded blob object. - * @memberof GifRecorder - * @example - * recorder.stop(function(){ - * var blob = recorder.blob; - * }); - */ - this.blob = new Blob([new Uint8Array(gifEncoder.stream().bin)], { - type: 'image/gif' - }); - - callback(this.blob); - - // bug: find a way to clear old recorded blobs - gifEncoder.stream().bin = []; - }; - - var isPausedRecording = false; - - /** - * This method pauses the recording process. - * @method - * @memberof GifRecorder - * @example - * recorder.pause(); - */ - this.pause = function () { - isPausedRecording = true; - }; - - /** - * This method resumes the recording process. - * @method - * @memberof GifRecorder - * @example - * recorder.resume(); - */ - this.resume = function () { - isPausedRecording = false; - }; - - /** - * This method resets currently recorded data. - * @method - * @memberof GifRecorder - * @example - * recorder.clearRecordedData(); - */ - this.clearRecordedData = function () { - self.clearedRecordedData = true; - clearRecordedDataCB(); - }; - - function clearRecordedDataCB() { - if (gifEncoder) { - gifEncoder.stream().bin = []; - } - } - - // for debugging - this.name = 'GifRecorder'; - this.toString = function () { - return this.name; - }; - - var canvas = document.createElement('canvas'); - var context = canvas.getContext('2d'); - - if (isHTMLObject) { - if (mediaStream instanceof CanvasRenderingContext2D) { - context = mediaStream; - canvas = context.canvas; - } else if (mediaStream instanceof HTMLCanvasElement) { - context = mediaStream.getContext('2d'); - canvas = mediaStream; - } - } - - var isLoadedMetaData = true; - - if (!isHTMLObject) { - var video = document.createElement('video'); - video.muted = true; - video.autoplay = true; - video.playsInline = true; - - isLoadedMetaData = false; - video.onloadedmetadata = function () { - isLoadedMetaData = true; - }; - - setSrcObject(mediaStream, video); - - video.play(); - } - - var lastAnimationFrame = null; - var lastFrameTime; - - var gifEncoder; - - var self = this; - } - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.GifRecorder = GifRecorder; - } - - // Last time updated: 2019-06-21 4:09:42 AM UTC - - // ________________________ - // MultiStreamsMixer v1.2.2 - - // Open-Sourced: https://github.com/muaz-khan/MultiStreamsMixer - - // -------------------------------------------------- - // Muaz Khan - www.MuazKhan.com - // MIT License - www.WebRTC-Experiment.com/licence - // -------------------------------------------------- - - function MultiStreamsMixer(arrayOfMediaStreams, elementClass) { - - var browserFakeUserAgent = 'Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45'; - - (function (that) { - if (typeof RecordRTC !== 'undefined') { - return; - } - - if (!that) { - return; - } - - if (typeof window !== 'undefined') { - return; - } - - if (typeof commonjsGlobal === 'undefined') { - return; - } - - commonjsGlobal.navigator = { - userAgent: browserFakeUserAgent, - getUserMedia: function () { } - }; - - if (!commonjsGlobal.console) { - commonjsGlobal.console = {}; - } - - if (typeof commonjsGlobal.console.log === 'undefined' || typeof commonjsGlobal.console.error === 'undefined') { - commonjsGlobal.console.error = commonjsGlobal.console.log = commonjsGlobal.console.log || function () { - console.log(arguments); - }; - } - - if (typeof document === 'undefined') { - /*global document:true */ - that.document = { - documentElement: { - appendChild: function () { - return ''; - } - } - }; - - document.createElement = document.captureStream = document.mozCaptureStream = function () { - var obj = { - getContext: function () { - return obj; - }, - play: function () { }, - pause: function () { }, - drawImage: function () { }, - toDataURL: function () { - return ''; - }, - style: {} - }; - return obj; - }; - - that.HTMLVideoElement = function () { }; - } - - if (typeof location === 'undefined') { - /*global location:true */ - that.location = { - protocol: 'file:', - href: '', - hash: '' - }; - } - - if (typeof screen === 'undefined') { - /*global screen:true */ - that.screen = { - width: 0, - height: 0 - }; - } - - if (typeof URL === 'undefined') { - /*global screen:true */ - that.URL = { - createObjectURL: function () { - return ''; - }, - revokeObjectURL: function () { - return ''; - } - }; - } - - /*global window:true */ - that.window = commonjsGlobal; - })(typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : null); - - // requires: chrome://flags/#enable-experimental-web-platform-features - - elementClass = elementClass || 'multi-streams-mixer'; - - var videos = []; - var isStopDrawingFrames = false; - - var canvas = document.createElement('canvas'); - var context = canvas.getContext('2d'); - canvas.style.opacity = 0; - canvas.style.position = 'absolute'; - canvas.style.zIndex = -1; - canvas.style.top = '-1000em'; - canvas.style.left = '-1000em'; - canvas.className = elementClass; - (document.body || document.documentElement).appendChild(canvas); - - this.disableLogs = false; - this.frameInterval = 10; - - this.width = 360; - this.height = 240; - - // use gain node to prevent echo - this.useGainNode = true; - - var self = this; - - // _____________________________ - // Cross-Browser-Declarations.js - - // WebAudio API representer - var AudioContext = window.AudioContext; - - if (typeof AudioContext === 'undefined') { - if (typeof webkitAudioContext !== 'undefined') { - /*global AudioContext:true */ - AudioContext = webkitAudioContext; - } - - if (typeof mozAudioContext !== 'undefined') { - /*global AudioContext:true */ - AudioContext = mozAudioContext; - } - } - - /*jshint -W079 */ - var URL = window.URL; - - if (typeof URL === 'undefined' && typeof webkitURL !== 'undefined') { - /*global URL:true */ - URL = webkitURL; - } - - if (typeof navigator !== 'undefined' && typeof navigator.getUserMedia === 'undefined') { // maybe window.navigator? - if (typeof navigator.webkitGetUserMedia !== 'undefined') { - navigator.getUserMedia = navigator.webkitGetUserMedia; - } - - if (typeof navigator.mozGetUserMedia !== 'undefined') { - navigator.getUserMedia = navigator.mozGetUserMedia; - } - } - - var MediaStream = window.MediaStream; - - if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefined') { - MediaStream = webkitMediaStream; - } - - /*global MediaStream:true */ - if (typeof MediaStream !== 'undefined') { - // override "stop" method for all browsers - if (typeof MediaStream.prototype.stop === 'undefined') { - MediaStream.prototype.stop = function () { - this.getTracks().forEach(function (track) { - track.stop(); - }); - }; - } - } - - var Storage = {}; - - if (typeof AudioContext !== 'undefined') { - Storage.AudioContext = AudioContext; - } else if (typeof webkitAudioContext !== 'undefined') { - Storage.AudioContext = webkitAudioContext; - } - - function setSrcObject(stream, element) { - if ('srcObject' in element) { - element.srcObject = stream; - } else if ('mozSrcObject' in element) { - element.mozSrcObject = stream; - } else { - element.srcObject = stream; - } - } - - this.startDrawingFrames = function () { - drawVideosToCanvas(); - }; - - function drawVideosToCanvas() { - if (isStopDrawingFrames) { - return; - } - - var videosLength = videos.length; - - var fullcanvas = false; - var remaining = []; - videos.forEach(function (video) { - if (!video.stream) { - video.stream = {}; - } - - if (video.stream.fullcanvas) { - fullcanvas = video; - } else { - // todo: video.stream.active or video.stream.live to fix blank frames issues? - remaining.push(video); - } - }); - - if (fullcanvas) { - canvas.width = fullcanvas.stream.width; - canvas.height = fullcanvas.stream.height; - } else if (remaining.length) { - canvas.width = videosLength > 1 ? remaining[0].width * 2 : remaining[0].width; - - var height = 1; - if (videosLength === 3 || videosLength === 4) { - height = 2; - } - if (videosLength === 5 || videosLength === 6) { - height = 3; - } - if (videosLength === 7 || videosLength === 8) { - height = 4; - } - if (videosLength === 9 || videosLength === 10) { - height = 5; - } - canvas.height = remaining[0].height * height; - } else { - canvas.width = self.width || 360; - canvas.height = self.height || 240; - } - - if (fullcanvas && fullcanvas instanceof HTMLVideoElement) { - drawImage(fullcanvas); - } - - remaining.forEach(function (video, idx) { - drawImage(video, idx); - }); - - setTimeout(drawVideosToCanvas, self.frameInterval); - } - - function drawImage(video, idx) { - if (isStopDrawingFrames) { - return; - } - - var x = 0; - var y = 0; - var width = video.width; - var height = video.height; - - if (idx === 1) { - x = video.width; - } - - if (idx === 2) { - y = video.height; - } - - if (idx === 3) { - x = video.width; - y = video.height; - } - - if (idx === 4) { - y = video.height * 2; - } - - if (idx === 5) { - x = video.width; - y = video.height * 2; - } - - if (idx === 6) { - y = video.height * 3; - } - - if (idx === 7) { - x = video.width; - y = video.height * 3; - } - - if (typeof video.stream.left !== 'undefined') { - x = video.stream.left; - } - - if (typeof video.stream.top !== 'undefined') { - y = video.stream.top; - } - - if (typeof video.stream.width !== 'undefined') { - width = video.stream.width; - } - - if (typeof video.stream.height !== 'undefined') { - height = video.stream.height; - } - - context.drawImage(video, x, y, width, height); - - if (typeof video.stream.onRender === 'function') { - video.stream.onRender(context, x, y, width, height, idx); - } - } - - function getMixedStream() { - isStopDrawingFrames = false; - var mixedVideoStream = getMixedVideoStream(); - - var mixedAudioStream = getMixedAudioStream(); - if (mixedAudioStream) { - mixedAudioStream.getTracks().filter(function (t) { - return t.kind === 'audio'; - }).forEach(function (track) { - mixedVideoStream.addTrack(track); - }); - } - arrayOfMediaStreams.forEach(function (stream) { - if (stream.fullcanvas); - }); - - // mixedVideoStream.prototype.appendStreams = appendStreams; - // mixedVideoStream.prototype.resetVideoStreams = resetVideoStreams; - // mixedVideoStream.prototype.clearRecordedData = clearRecordedData; - - return mixedVideoStream; - } - - function getMixedVideoStream() { - resetVideoStreams(); - - var capturedStream; - - if ('captureStream' in canvas) { - capturedStream = canvas.captureStream(); - } else if ('mozCaptureStream' in canvas) { - capturedStream = canvas.mozCaptureStream(); - } else if (!self.disableLogs) { - console.error('Upgrade to latest Chrome or otherwise enable this flag: chrome://flags/#enable-experimental-web-platform-features'); - } - - var videoStream = new MediaStream(); - - capturedStream.getTracks().filter(function (t) { - return t.kind === 'video'; - }).forEach(function (track) { - videoStream.addTrack(track); - }); - - canvas.stream = videoStream; - - return videoStream; - } - - function getMixedAudioStream() { - // via: @pehrsons - if (!Storage.AudioContextConstructor) { - Storage.AudioContextConstructor = new Storage.AudioContext(); - } - - self.audioContext = Storage.AudioContextConstructor; - - self.audioSources = []; - - if (self.useGainNode === true) { - self.gainNode = self.audioContext.createGain(); - self.gainNode.connect(self.audioContext.destination); - self.gainNode.gain.value = 0; // don't hear self - } - - var audioTracksLength = 0; - arrayOfMediaStreams.forEach(function (stream) { - if (!stream.getTracks().filter(function (t) { - return t.kind === 'audio'; - }).length) { - return; - } - - audioTracksLength++; - - var audioSource = self.audioContext.createMediaStreamSource(stream); - - if (self.useGainNode === true) { - audioSource.connect(self.gainNode); - } - - self.audioSources.push(audioSource); - }); - - if (!audioTracksLength) { - // because "self.audioContext" is not initialized - // that's why we've to ignore rest of the code - return; - } - - self.audioDestination = self.audioContext.createMediaStreamDestination(); - self.audioSources.forEach(function (audioSource) { - audioSource.connect(self.audioDestination); - }); - return self.audioDestination.stream; - } - - function getVideo(stream) { - var video = document.createElement('video'); - - setSrcObject(stream, video); - - video.className = elementClass; - - video.muted = true; - video.volume = 0; - - video.width = stream.width || self.width || 360; - video.height = stream.height || self.height || 240; - - video.play(); - - return video; - } - - this.appendStreams = function (streams) { - if (!streams) { - throw 'First parameter is required.'; - } - - if (!(streams instanceof Array)) { - streams = [streams]; - } - - streams.forEach(function (stream) { - var newStream = new MediaStream(); - - if (stream.getTracks().filter(function (t) { - return t.kind === 'video'; - }).length) { - var video = getVideo(stream); - video.stream = stream; - videos.push(video); - - newStream.addTrack(stream.getTracks().filter(function (t) { - return t.kind === 'video'; - })[0]); - } - - if (stream.getTracks().filter(function (t) { - return t.kind === 'audio'; - }).length) { - var audioSource = self.audioContext.createMediaStreamSource(stream); - self.audioDestination = self.audioContext.createMediaStreamDestination(); - audioSource.connect(self.audioDestination); - - newStream.addTrack(self.audioDestination.stream.getTracks().filter(function (t) { - return t.kind === 'audio'; - })[0]); - } - - arrayOfMediaStreams.push(newStream); - }); - }; - - this.releaseStreams = function () { - videos = []; - isStopDrawingFrames = true; - - if (self.gainNode) { - self.gainNode.disconnect(); - self.gainNode = null; - } - - if (self.audioSources.length) { - self.audioSources.forEach(function (source) { - source.disconnect(); - }); - self.audioSources = []; - } - - if (self.audioDestination) { - self.audioDestination.disconnect(); - self.audioDestination = null; - } - - if (self.audioContext) { - self.audioContext.close(); - } - - self.audioContext = null; - - context.clearRect(0, 0, canvas.width, canvas.height); - - if (canvas.stream) { - canvas.stream.stop(); - canvas.stream = null; - } - }; - - this.resetVideoStreams = function (streams) { - if (streams && !(streams instanceof Array)) { - streams = [streams]; - } - - resetVideoStreams(streams); - }; - - function resetVideoStreams(streams) { - videos = []; - streams = streams || arrayOfMediaStreams; - - // via: @adrian-ber - streams.forEach(function (stream) { - if (!stream.getTracks().filter(function (t) { - return t.kind === 'video'; - }).length) { - return; - } - - var video = getVideo(stream); - video.stream = stream; - videos.push(video); - }); - } - - // for debugging - this.name = 'MultiStreamsMixer'; - this.toString = function () { - return this.name; - }; - - this.getMixedStream = getMixedStream; - - } - - if (typeof RecordRTC === 'undefined') { - { - module.exports = MultiStreamsMixer; - } - } - - // ______________________ - // MultiStreamRecorder.js - - /* - * Video conference recording, using captureStream API along with WebAudio and Canvas2D API. - */ - - /** - * MultiStreamRecorder can record multiple videos in single container. - * @summary Multi-videos recorder. - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @typedef MultiStreamRecorder - * @class - * @example - * var options = { - * mimeType: 'video/webm' - * } - * var recorder = new MultiStreamRecorder(ArrayOfMediaStreams, options); - * recorder.record(); - * recorder.stop(function(blob) { - * video.src = URL.createObjectURL(blob); - * - * // or - * var blob = recorder.blob; - * }); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - * @param {MediaStreams} mediaStreams - Array of MediaStreams. - * @param {object} config - {disableLogs:true, frameInterval: 1, mimeType: "video/webm"} - */ - - function MultiStreamRecorder(arrayOfMediaStreams, options) { - arrayOfMediaStreams = arrayOfMediaStreams || []; - var self = this; - - var mixer; - var mediaRecorder; - - options = options || { - elementClass: 'multi-streams-mixer', - mimeType: 'video/webm', - video: { - width: 360, - height: 240 - } - }; - - if (!options.frameInterval) { - options.frameInterval = 10; - } - - if (!options.video) { - options.video = {}; - } - - if (!options.video.width) { - options.video.width = 360; - } - - if (!options.video.height) { - options.video.height = 240; - } - - /** - * This method records all MediaStreams. - * @method - * @memberof MultiStreamRecorder - * @example - * recorder.record(); - */ - this.record = function () { - // github/muaz-khan/MultiStreamsMixer - mixer = new MultiStreamsMixer(arrayOfMediaStreams, options.elementClass || 'multi-streams-mixer'); - - if (getAllVideoTracks().length) { - mixer.frameInterval = options.frameInterval || 10; - mixer.width = options.video.width || 360; - mixer.height = options.video.height || 240; - mixer.startDrawingFrames(); - } - - if (options.previewStream && typeof options.previewStream === 'function') { - options.previewStream(mixer.getMixedStream()); - } - - // record using MediaRecorder API - mediaRecorder = new MediaStreamRecorder(mixer.getMixedStream(), options); - mediaRecorder.record(); - }; - - function getAllVideoTracks() { - var tracks = []; - arrayOfMediaStreams.forEach(function (stream) { - getTracks(stream, 'video').forEach(function (track) { - tracks.push(track); - }); - }); - return tracks; - } - - /** - * This method stops recording MediaStream. - * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. - * @method - * @memberof MultiStreamRecorder - * @example - * recorder.stop(function(blob) { - * video.src = URL.createObjectURL(blob); - * }); - */ - this.stop = function (callback) { - if (!mediaRecorder) { - return; - } - - mediaRecorder.stop(function (blob) { - self.blob = blob; - - callback(blob); - - self.clearRecordedData(); - }); - }; - - /** - * This method pauses the recording process. - * @method - * @memberof MultiStreamRecorder - * @example - * recorder.pause(); - */ - this.pause = function () { - if (mediaRecorder) { - mediaRecorder.pause(); - } - }; - - /** - * This method resumes the recording process. - * @method - * @memberof MultiStreamRecorder - * @example - * recorder.resume(); - */ - this.resume = function () { - if (mediaRecorder) { - mediaRecorder.resume(); - } - }; - - /** - * This method resets currently recorded data. - * @method - * @memberof MultiStreamRecorder - * @example - * recorder.clearRecordedData(); - */ - this.clearRecordedData = function () { - if (mediaRecorder) { - mediaRecorder.clearRecordedData(); - mediaRecorder = null; - } - - if (mixer) { - mixer.releaseStreams(); - mixer = null; - } - }; - - /** - * Add extra media-streams to existing recordings. - * @method - * @memberof MultiStreamRecorder - * @param {MediaStreams} mediaStreams - Array of MediaStreams - * @example - * recorder.addStreams([newAudioStream, newVideoStream]); - */ - this.addStreams = function (streams) { - if (!streams) { - throw 'First parameter is required.'; - } - - if (!(streams instanceof Array)) { - streams = [streams]; - } - - arrayOfMediaStreams.concat(streams); - - if (!mediaRecorder || !mixer) { - return; - } - - mixer.appendStreams(streams); - - if (options.previewStream && typeof options.previewStream === 'function') { - options.previewStream(mixer.getMixedStream()); - } - }; - - /** - * Reset videos during live recording. Replace old videos e.g. replace cameras with full-screen. - * @method - * @memberof MultiStreamRecorder - * @param {MediaStreams} mediaStreams - Array of MediaStreams - * @example - * recorder.resetVideoStreams([newVideo1, newVideo2]); - */ - this.resetVideoStreams = function (streams) { - if (!mixer) { - return; - } - - if (streams && !(streams instanceof Array)) { - streams = [streams]; - } - - mixer.resetVideoStreams(streams); - }; - - /** - * Returns MultiStreamsMixer - * @method - * @memberof MultiStreamRecorder - * @example - * let mixer = recorder.getMixer(); - * mixer.appendStreams([newStream]); - */ - this.getMixer = function () { - return mixer; - }; - - // for debugging - this.name = 'MultiStreamRecorder'; - this.toString = function () { - return this.name; - }; - } - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.MultiStreamRecorder = MultiStreamRecorder; - } - - // _____________________ - // RecordRTC.promises.js - - /** - * RecordRTCPromisesHandler adds promises support in {@link RecordRTC}. Try a {@link https://github.com/muaz-khan/RecordRTC/blob/master/simple-demos/RecordRTCPromisesHandler.html|demo here} - * @summary Promises for {@link RecordRTC} - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @typedef RecordRTCPromisesHandler - * @class - * @example - * var recorder = new RecordRTCPromisesHandler(mediaStream, options); - * recorder.startRecording() - * .then(successCB) - * .catch(errorCB); - * // Note: You can access all RecordRTC API using "recorder.recordRTC" e.g. - * recorder.recordRTC.onStateChanged = function(state) {}; - * recorder.recordRTC.setRecordingDuration(5000); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - * @param {MediaStream} mediaStream - Single media-stream object, array of media-streams, html-canvas-element, etc. - * @param {object} config - {type:"video", recorderType: MediaStreamRecorder, disableLogs: true, numberOfAudioChannels: 1, bufferSize: 0, sampleRate: 0, video: HTMLVideoElement, etc.} - * @throws Will throw an error if "new" keyword is not used to initiate "RecordRTCPromisesHandler". Also throws error if first argument "MediaStream" is missing. - * @requires {@link RecordRTC} - */ - - function RecordRTCPromisesHandler(mediaStream, options) { - if (!this) { - throw 'Use "new RecordRTCPromisesHandler()"'; - } - - if (typeof mediaStream === 'undefined') { - throw 'First argument "MediaStream" is required.'; - } - - var self = this; - - /** - * @property {Blob} blob - Access/reach the native {@link RecordRTC} object. - * @memberof RecordRTCPromisesHandler - * @example - * let internal = recorder.recordRTC.getInternalRecorder(); - * alert(internal instanceof MediaStreamRecorder); - * recorder.recordRTC.onStateChanged = function(state) {}; - */ - self.recordRTC = new RecordRTC(mediaStream, options); - - /** - * This method records MediaStream. - * @method - * @memberof RecordRTCPromisesHandler - * @example - * recorder.startRecording() - * .then(successCB) - * .catch(errorCB); - */ - this.startRecording = function () { - return new Promise(function (resolve, reject) { - try { - self.recordRTC.startRecording(); - resolve(); - } catch (e) { - reject(e); - } - }); - }; - - /** - * This method stops the recording. - * @method - * @memberof RecordRTCPromisesHandler - * @example - * recorder.stopRecording().then(function() { - * var blob = recorder.getBlob(); - * }).catch(errorCB); - */ - this.stopRecording = function () { - return new Promise(function (resolve, reject) { - try { - self.recordRTC.stopRecording(function (url) { - self.blob = self.recordRTC.getBlob(); - - if (!self.blob || !self.blob.size) { - reject('Empty blob.', self.blob); - return; - } - - resolve(url); - }); - } catch (e) { - reject(e); - } - }); - }; - - /** - * This method pauses the recording. You can resume recording using "resumeRecording" method. - * @method - * @memberof RecordRTCPromisesHandler - * @example - * recorder.pauseRecording() - * .then(successCB) - * .catch(errorCB); - */ - this.pauseRecording = function () { - return new Promise(function (resolve, reject) { - try { - self.recordRTC.pauseRecording(); - resolve(); - } catch (e) { - reject(e); - } - }); - }; - - /** - * This method resumes the recording. - * @method - * @memberof RecordRTCPromisesHandler - * @example - * recorder.resumeRecording() - * .then(successCB) - * .catch(errorCB); - */ - this.resumeRecording = function () { - return new Promise(function (resolve, reject) { - try { - self.recordRTC.resumeRecording(); - resolve(); - } catch (e) { - reject(e); - } - }); - }; - - /** - * This method returns data-url for the recorded blob. - * @method - * @memberof RecordRTCPromisesHandler - * @example - * recorder.stopRecording().then(function() { - * recorder.getDataURL().then(function(dataURL) { - * window.open(dataURL); - * }).catch(errorCB);; - * }).catch(errorCB); - */ - this.getDataURL = function (callback) { - return new Promise(function (resolve, reject) { - try { - self.recordRTC.getDataURL(function (dataURL) { - resolve(dataURL); - }); - } catch (e) { - reject(e); - } - }); - }; - - /** - * This method returns the recorded blob. - * @method - * @memberof RecordRTCPromisesHandler - * @example - * recorder.stopRecording().then(function() { - * recorder.getBlob().then(function(blob) {}) - * }).catch(errorCB); - */ - this.getBlob = function () { - return new Promise(function (resolve, reject) { - try { - resolve(self.recordRTC.getBlob()); - } catch (e) { - reject(e); - } - }); - }; - - /** - * This method returns the internal recording object. - * @method - * @memberof RecordRTCPromisesHandler - * @example - * let internalRecorder = await recorder.getInternalRecorder(); - * if(internalRecorder instanceof MultiStreamRecorder) { - * internalRecorder.addStreams([newAudioStream]); - * internalRecorder.resetVideoStreams([screenStream]); - * } - * @returns {Object} - */ - this.getInternalRecorder = function () { - return new Promise(function (resolve, reject) { - try { - resolve(self.recordRTC.getInternalRecorder()); - } catch (e) { - reject(e); - } - }); - }; - - /** - * This method resets the recorder. So that you can reuse single recorder instance many times. - * @method - * @memberof RecordRTCPromisesHandler - * @example - * await recorder.reset(); - * recorder.startRecording(); // record again - */ - this.reset = function () { - return new Promise(function (resolve, reject) { - try { - resolve(self.recordRTC.reset()); - } catch (e) { - reject(e); - } - }); - }; - - /** - * Destroy RecordRTC instance. Clear all recorders and objects. - * @method - * @memberof RecordRTCPromisesHandler - * @example - * recorder.destroy().then(successCB).catch(errorCB); - */ - this.destroy = function () { - return new Promise(function (resolve, reject) { - try { - resolve(self.recordRTC.destroy()); - } catch (e) { - reject(e); - } - }); - }; - - /** - * Get recorder's readonly state. - * @method - * @memberof RecordRTCPromisesHandler - * @example - * let state = await recorder.getState(); - * // or - * recorder.getState().then(state => { console.log(state); }) - * @returns {String} Returns recording state. - */ - this.getState = function () { - return new Promise(function (resolve, reject) { - try { - resolve(self.recordRTC.getState()); - } catch (e) { - reject(e); - } - }); - }; - - /** - * @property {Blob} blob - Recorded data as "Blob" object. - * @memberof RecordRTCPromisesHandler - * @example - * await recorder.stopRecording(); - * let blob = recorder.getBlob(); // or "recorder.recordRTC.blob" - * invokeSaveAsDialog(blob); - */ - this.blob = null; - - /** - * RecordRTC version number - * @property {String} version - Release version number. - * @memberof RecordRTCPromisesHandler - * @static - * @readonly - * @example - * alert(recorder.version); - */ - this.version = '5.6.2'; - } - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.RecordRTCPromisesHandler = RecordRTCPromisesHandler; - } - - // ______________________ - // WebAssemblyRecorder.js - - /** - * WebAssemblyRecorder lets you create webm videos in JavaScript via WebAssembly. The library consumes raw RGBA32 buffers (4 bytes per pixel) and turns them into a webm video with the given framerate and quality. This makes it compatible out-of-the-box with ImageData from a CANVAS. With realtime mode you can also use webm-wasm for streaming webm videos. - * @summary Video recording feature in Chrome, Firefox and maybe Edge. - * @license {@link https://github.com/muaz-khan/RecordRTC/blob/master/LICENSE|MIT} - * @author {@link https://MuazKhan.com|Muaz Khan} - * @typedef WebAssemblyRecorder - * @class - * @example - * var recorder = new WebAssemblyRecorder(mediaStream); - * recorder.record(); - * recorder.stop(function(blob) { - * video.src = URL.createObjectURL(blob); - * }); - * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} - * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. - * @param {object} config - {webAssemblyPath:'webm-wasm.wasm',workerPath: 'webm-worker.js', frameRate: 30, width: 1920, height: 1080, bitrate: 1024, realtime: true} - */ - function WebAssemblyRecorder(stream, config) { - // based on: github.com/GoogleChromeLabs/webm-wasm - - if (typeof ReadableStream === 'undefined' || typeof WritableStream === 'undefined') { - // because it fixes readable/writable streams issues - console.error('Following polyfill is strongly recommended: https://unpkg.com/@mattiasbuelens/web-streams-polyfill/dist/polyfill.min.js'); - } - - config = config || {}; - - config.width = config.width || 640; - config.height = config.height || 480; - config.frameRate = config.frameRate || 30; - config.bitrate = config.bitrate || 1200; - config.realtime = config.realtime || true; - - var finished; - - function cameraStream() { - return new ReadableStream({ - start: function (controller) { - var cvs = document.createElement('canvas'); - var video = document.createElement('video'); - var first = true; - video.srcObject = stream; - video.muted = true; - video.height = config.height; - video.width = config.width; - video.volume = 0; - video.onplaying = function () { - cvs.width = config.width; - cvs.height = config.height; - var ctx = cvs.getContext('2d'); - var frameTimeout = 1000 / config.frameRate; - var cameraTimer = setInterval(function f() { - if (finished) { - clearInterval(cameraTimer); - controller.close(); - } - - if (first) { - first = false; - if (config.onVideoProcessStarted) { - config.onVideoProcessStarted(); - } - } - - ctx.drawImage(video, 0, 0); - if (controller._controlledReadableStream.state !== 'closed') { - try { - controller.enqueue( - ctx.getImageData(0, 0, config.width, config.height) - ); - } catch (e) { } - } - }, frameTimeout); - }; - video.play(); - } - }); - } - - var worker; - - function startRecording(stream, buffer) { - if (!config.workerPath && !buffer) { - finished = false; - - // is it safe to use @latest ? - - fetch( - 'https://unpkg.com/webm-wasm@latest/dist/webm-worker.js' - ).then(function (r) { - r.arrayBuffer().then(function (buffer) { - startRecording(stream, buffer); - }); - }); - return; - } - - if (!config.workerPath && buffer instanceof ArrayBuffer) { - var blob = new Blob([buffer], { - type: 'text/javascript' - }); - config.workerPath = URL.createObjectURL(blob); - } - - if (!config.workerPath) { - console.error('workerPath parameter is missing.'); - } - - worker = new Worker(config.workerPath); - - worker.postMessage(config.webAssemblyPath || 'https://unpkg.com/webm-wasm@latest/dist/webm-wasm.wasm'); - worker.addEventListener('message', function (event) { - if (event.data === 'READY') { - worker.postMessage({ - width: config.width, - height: config.height, - bitrate: config.bitrate || 1200, - timebaseDen: config.frameRate || 30, - realtime: config.realtime - }); - - cameraStream().pipeTo(new WritableStream({ - write: function (image) { - if (finished) { - console.error('Got image, but recorder is finished!'); - return; - } - - worker.postMessage(image.data.buffer, [image.data.buffer]); - } - })); - } else if (!!event.data) { - if (!isPaused) { - arrayOfBuffers.push(event.data); - } - } - }); - } - - /** - * This method records video. - * @method - * @memberof WebAssemblyRecorder - * @example - * recorder.record(); - */ - this.record = function () { - arrayOfBuffers = []; - isPaused = false; - this.blob = null; - startRecording(stream); - - if (typeof config.initCallback === 'function') { - config.initCallback(); - } - }; - - var isPaused; - - /** - * This method pauses the recording process. - * @method - * @memberof WebAssemblyRecorder - * @example - * recorder.pause(); - */ - this.pause = function () { - isPaused = true; - }; - - /** - * This method resumes the recording process. - * @method - * @memberof WebAssemblyRecorder - * @example - * recorder.resume(); - */ - this.resume = function () { - isPaused = false; - }; - - function terminate(callback) { - if (!worker) { - if (callback) { - callback(); - } - - return; - } - - // Wait for null event data to indicate that the encoding is complete - worker.addEventListener('message', function (event) { - if (event.data === null) { - worker.terminate(); - worker = null; - - if (callback) { - callback(); - } - } - }); - - worker.postMessage(null); - } - - var arrayOfBuffers = []; - - /** - * This method stops recording video. - * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. - * @method - * @memberof WebAssemblyRecorder - * @example - * recorder.stop(function(blob) { - * video.src = URL.createObjectURL(blob); - * }); - */ - this.stop = function (callback) { - finished = true; - - var recorder = this; - - terminate(function () { - recorder.blob = new Blob(arrayOfBuffers, { - type: 'video/webm' - }); - - callback(recorder.blob); - }); - }; - - // for debugging - this.name = 'WebAssemblyRecorder'; - this.toString = function () { - return this.name; - }; - - /** - * This method resets currently recorded data. - * @method - * @memberof WebAssemblyRecorder - * @example - * recorder.clearRecordedData(); - */ - this.clearRecordedData = function () { - arrayOfBuffers = []; - isPaused = false; - this.blob = null; - - // todo: if recording-ON then STOP it first - }; - - /** - * @property {Blob} blob - The recorded blob object. - * @memberof WebAssemblyRecorder - * @example - * recorder.stop(function(){ - * var blob = recorder.blob; - * }); - */ - this.blob = null; - } - - if (typeof RecordRTC !== 'undefined') { - RecordRTC.WebAssemblyRecorder = WebAssemblyRecorder; - } - }); - - class RecordRTCLoader extends Emitter { - constructor(player) { - super(); - this.player = player; - this.fileName = ''; - this.fileType = player._opt.recordType || FILE_SUFFIX.webm; - this.isRecording = false; - this.recordingTimestamp = 0; - this.recordingInterval = null; - player.debug.log('Recorder', 'init'); - } - - destroy() { - this._reset(); - - this.player.debug.log('Recorder', 'destroy'); - } - - setFileName(fileName, fileType) { - this.fileName = fileName; - - if (FILE_SUFFIX.mp4 === fileType || FILE_SUFFIX.webm === fileType) { - this.fileType = fileType; - } - } - - get recording() { - return this.isRecording; - } - - get recordTime() { - return this.recordingTimestamp; - } - - startRecord() { - const debug = this.player.debug; - const options = { - type: 'video', - mimeType: 'video/webm;codecs=h264', - onTimeStamp: timestamp => { - debug.log('Recorder', 'record timestamp :' + timestamp); - }, - disableLogs: !this.player._opt.debug - }; - - try { - const stream = this.player.video.$videoElement.captureStream(25); - - if (this.player.audio && this.player.audio.mediaStreamAudioDestinationNode && this.player.audio.mediaStreamAudioDestinationNode.stream && !this.player.audio.isStateSuspended() && this.player.audio.hasAudio && this.player._opt.hasAudio) { - const audioStream = this.player.audio.mediaStreamAudioDestinationNode.stream; - - if (audioStream.getAudioTracks().length > 0) { - const audioTrack = audioStream.getAudioTracks()[0]; - - if (audioTrack && audioTrack.enabled) { - stream.addTrack(audioTrack); - } - } - } - - this.recorder = RecordRTC_1(stream, options); - } catch (e) { - debug.error('Recorder', 'startRecord error', e); - this.emit(EVENTS.recordCreateError); - } - - if (this.recorder) { - this.isRecording = true; - this.player.emit(EVENTS.recording, true); - this.recorder.startRecording(); - debug.log('Recorder', 'start recording'); - this.player.emit(EVENTS.recordStart); - this.recordingInterval = window.setInterval(() => { - this.recordingTimestamp += 1; - this.player.emit(EVENTS.recordingTimestamp, this.recordingTimestamp); - }, 1000); - } - } - - stopRecordAndSave() { - if (!this.recorder || !this.isRecording) { - return; - } - - this.recorder.stopRecording(() => { - this.player.debug.log('Recorder', 'stop recording'); - this.player.emit(EVENTS.recordEnd); - const fileName = (this.fileName || now()) + '.' + (this.fileType || FILE_SUFFIX.webm); - saveAs(this.recorder.getBlob(), fileName); - - this._reset(); - - this.player.emit(EVENTS.recording, false); - }); - } - - _reset() { - this.isRecording = false; - this.recordingTimestamp = 0; - - if (this.recorder) { - this.recorder.destroy(); - this.recorder = null; - } - - this.fileName = null; - - if (this.recordingInterval) { - clearInterval(this.recordingInterval); - } - - this.recordingInterval = null; - } - - } - - class Recorder { - constructor(player) { - const Loader = Recorder.getLoaderFactory(); - return new Loader(player); - } - - static getLoaderFactory() { - return RecordRTCLoader; - } - - } - - class DecoderWorker { - constructor(player) { - this.player = player; - this.decoderWorker = new Worker(player._opt.decoder); - - this._initDecoderWorker(); - - player.debug.log('decoderWorker', 'init'); - } - - destroy() { - this.decoderWorker.postMessage({ - cmd: WORKER_SEND_TYPE.close - }); - this.decoderWorker.terminate(); - this.decoderWorker = null; - this.player.debug.log(`decoderWorker`, 'destroy'); - } - - _initDecoderWorker() { - const { - debug, - events: { - proxy - } - } = this.player; - - this.decoderWorker.onmessage = event => { - const msg = event.data; - - switch (msg.cmd) { - case WORKER_CMD_TYPE.init: - debug.log(`decoderWorker`, 'onmessage:', WORKER_CMD_TYPE.init); - - if (!this.player.loaded) { - this.player.emit(EVENTS.load); - } - - this.player.emit(EVENTS.decoderWorkerInit); - - this._initWork(); - - break; - - case WORKER_CMD_TYPE.videoCode: - debug.log(`decoderWorker`, 'onmessage:', WORKER_CMD_TYPE.videoCode, msg.code); - - if (!this.player._times.decodeStart) { - this.player._times.decodeStart = now(); - } - - this.player.video.updateVideoInfo({ - encTypeCode: msg.code - }); - break; - - case WORKER_CMD_TYPE.audioCode: - debug.log(`decoderWorker`, 'onmessage:', WORKER_CMD_TYPE.audioCode, msg.code); - this.player.audio && this.player.audio.updateAudioInfo({ - encTypeCode: msg.code - }); - break; - - case WORKER_CMD_TYPE.initVideo: - debug.log(`decoderWorker`, 'onmessage:', WORKER_CMD_TYPE.initVideo, `width:${msg.w},height:${msg.h}`); - this.player.video.updateVideoInfo({ - width: msg.w, - height: msg.h - }); - - if (!this.player._opt.openWebglAlignment && !isWebglRenderSupport(msg.w)) { - this.player.emit(EVENTS_ERROR.webglAlignmentError); - return; - } - - this.player.video.initCanvasViewSize(); - break; - - case WORKER_CMD_TYPE.initAudio: - debug.log(`decoderWorker`, 'onmessage:', WORKER_CMD_TYPE.initAudio, `channels:${msg.channels},sampleRate:${msg.sampleRate}`); - - if (this.player.audio) { - this.player.audio.updateAudioInfo(msg); - this.player.audio.initScriptNode(msg); - } - - break; - - case WORKER_CMD_TYPE.render: - // debug.log(`decoderWorker`, 'onmessage:', WORKER_CMD_TYPE.render, `msg ts:${msg.ts}`); - this.player.handleRender(); - this.player.video.render(msg); - this.player.emit(EVENTS.timeUpdate, msg.ts); - this.player.updateStats({ - fps: true, - ts: msg.ts, - buf: msg.delay - }); - - if (!this.player._times.videoStart) { - this.player._times.videoStart = now(); - this.player.handlePlayToRenderTimes(); - } - - break; - - case WORKER_CMD_TYPE.playAudio: - // debug.log(`decoderWorker`, 'onmessage:', WORKER_CMD_TYPE.playAudio, `msg ts:${msg.ts}`); - // 只有在 playing 的时候。 - if (this.player.playing && this.player.audio) { - this.player.audio.play(msg.buffer, msg.ts); - } - - break; - - case WORKER_CMD_TYPE.wasmError: - if (msg.message) { - if (msg.message.indexOf(WASM_ERROR.invalidNalUnitSize) !== -1) { - this.player.emitError(EVENTS_ERROR.wasmDecodeError); - } - } - - break; - - default: - this.player[msg.cmd] && this.player[msg.cmd](msg); - } - }; - } - - _initWork() { - const opt = { - debug: this.player._opt.debug, - useOffscreen: this.player._opt.useOffscreen, - useWCS: this.player._opt.useWCS, - videoBuffer: this.player._opt.videoBuffer, - videoBufferDelay: this.player._opt.videoBufferDelay, - openWebglAlignment: this.player._opt.openWebglAlignment - }; - this.decoderWorker.postMessage({ - cmd: WORKER_SEND_TYPE.init, - opt: JSON.stringify(opt), - sampleRate: this.player.audio && this.player.audio.audioContext.sampleRate || 0 - }); - } - - decodeVideo(arrayBuffer, ts, isIFrame) { - const options = { - type: MEDIA_TYPE.video, - ts: Math.max(ts, 0), - isIFrame - }; // this.player.debug.log('decoderWorker', 'decodeVideo', options); - - this.decoderWorker.postMessage({ - cmd: WORKER_SEND_TYPE.decode, - buffer: arrayBuffer, - options - }, [arrayBuffer.buffer]); - } - - decodeAudio(arrayBuffer, ts) { - if (this.player._opt.useWCS) { - this._decodeAudioNoDelay(arrayBuffer, ts); - } else if (this.player._opt.useMSE) { - this._decodeAudioNoDelay(arrayBuffer, ts); - } else { - this._decodeAudio(arrayBuffer, ts); - } - } // - - - _decodeAudio(arrayBuffer, ts) { - const options = { - type: MEDIA_TYPE.audio, - ts: Math.max(ts, 0) - }; // this.player.debug.log('decoderWorker', 'decodeAudio',options); - - this.decoderWorker.postMessage({ - cmd: WORKER_SEND_TYPE.decode, - buffer: arrayBuffer, - options - }, [arrayBuffer.buffer]); - } - - _decodeAudioNoDelay(arrayBuffer, ts) { - // console.log('_decodeAudioNoDelay', arrayBuffer); - this.decoderWorker.postMessage({ - cmd: WORKER_SEND_TYPE.audioDecode, - buffer: arrayBuffer, - ts: Math.max(ts, 0) - }, [arrayBuffer.buffer]); - } - - updateWorkConfig(config) { - this.decoderWorker.postMessage({ - cmd: WORKER_SEND_TYPE.updateConfig, - key: config.key, - value: config.value - }); - } - - } - - class CommonLoader extends Emitter { - constructor(player) { - super(); - this.player = player; - this.stopId = null; - this.firstTimestamp = null; - this.startTimestamp = null; - this.delay = -1; - this.bufferList = []; - this.dropping = false; - this.initInterval(); - } - - destroy() { - if (this.stopId) { - clearInterval(this.stopId); - this.stopId = null; - } - - this.firstTimestamp = null; - this.startTimestamp = null; - this.delay = -1; - this.bufferList = []; - this.dropping = false; - this.off(); - this.player.debug.log('CommonDemux', 'destroy'); - } - - getDelay(timestamp) { - if (!timestamp) { - return -1; - } - - if (!this.firstTimestamp) { - this.firstTimestamp = timestamp; - this.startTimestamp = Date.now(); - this.delay = -1; - } else { - if (timestamp) { - const localTimestamp = Date.now() - this.startTimestamp; - const timeTimestamp = timestamp - this.firstTimestamp; - - if (localTimestamp >= timeTimestamp) { - this.delay = localTimestamp - timeTimestamp; - } else { - this.delay = timeTimestamp - localTimestamp; - } - } - } - - return this.delay; - } - - resetDelay() { - this.firstTimestamp = null; - this.startTimestamp = null; - this.delay = -1; - this.dropping = false; - } // - - - initInterval() { - this.player.debug.log('common dumex', `init Interval`); - - let _loop = () => { - let data; - const videoBuffer = this.player._opt.videoBuffer; - const videoBufferDelay = this.player._opt.videoBufferDelay; - - if (this.player._opt.useMSE && this.player.mseDecoder && this.player.mseDecoder.getSourceBufferUpdating()) { - this.player.debug.warn('CommonDemux', `_loop getSourceBufferUpdating is true and bufferList length is ${this.bufferList.length}`); - return; - } - - if (this.bufferList.length) { - if (this.dropping) { - // this.player.debug.log('common dumex', `is dropping`); - data = this.bufferList.shift(); - - if (data.type === MEDIA_TYPE.audio && data.payload[1] === 0) { - this._doDecoderDecode(data); - } - - while (!data.isIFrame && this.bufferList.length) { - data = this.bufferList.shift(); - - if (data.type === MEDIA_TYPE.audio && data.payload[1] === 0) { - this._doDecoderDecode(data); - } - } // i frame - - - if (data.isIFrame && this.getDelay(data.ts) <= Math.min(videoBuffer, 200)) { - this.dropping = false; - - this._doDecoderDecode(data); - } - } else { - data = this.bufferList[0]; - - if (this.getDelay(data.ts) === -1) { - // this.player.debug.log('common dumex', `delay is -1`); - this.bufferList.shift(); - - this._doDecoderDecode(data); - } else if (this.delay > videoBuffer + videoBufferDelay) { - // this.player.debug.log('common dumex', `delay is ${this.delay}, set dropping is true`); - this.resetDelay(); - this.dropping = true; - } else { - data = this.bufferList[0]; - - if (this.getDelay(data.ts) > videoBuffer) { - // drop frame - this.bufferList.shift(); - - this._doDecoderDecode(data); - } - } - } - } - }; - - _loop(); - - this.stopId = setInterval(_loop, 10); - } - - _doDecode(payload, type, ts, isIFrame, cts) { - const player = this.player; - let options = { - ts: ts, - cts: cts, - type: type, - isIFrame: false - }; // use offscreen - - if (player._opt.useWCS && !player._opt.useOffscreen) { - if (type === MEDIA_TYPE.video) { - options.isIFrame = isIFrame; - } - - this.pushBuffer(payload, options); - } else if (player._opt.useMSE) { - // use mse - if (type === MEDIA_TYPE.video) { - options.isIFrame = isIFrame; - } - - this.pushBuffer(payload, options); - } else { - // - if (type === MEDIA_TYPE.video) { - player.decoderWorker && player.decoderWorker.decodeVideo(payload, ts, isIFrame); - } else if (type === MEDIA_TYPE.audio) { - if (player._opt.hasAudio) { - player.decoderWorker && player.decoderWorker.decodeAudio(payload, ts); - } - } - } - } - - _doDecoderDecode(data) { - const player = this.player; - const { - webcodecsDecoder, - mseDecoder - } = player; - - if (data.type === MEDIA_TYPE.audio) { - if (player._opt.hasAudio) { - player.decoderWorker && player.decoderWorker.decodeAudio(data.payload, data.ts); - } - } else if (data.type === MEDIA_TYPE.video) { - if (player._opt.useWCS && !player._opt.useOffscreen) { - webcodecsDecoder.decodeVideo(data.payload, data.ts, data.isIFrame); - } else if (player._opt.useMSE) { - mseDecoder.decodeVideo(data.payload, data.ts, data.isIFrame, data.cts); - } - } - } - - pushBuffer(payload, options) { - // 音频 - if (options.type === MEDIA_TYPE.audio) { - this.bufferList.push({ - ts: options.ts, - payload: payload, - type: MEDIA_TYPE.audio - }); - } else if (options.type === MEDIA_TYPE.video) { - this.bufferList.push({ - ts: options.ts, - cts: options.cts, - payload: payload, - type: MEDIA_TYPE.video, - isIFrame: options.isIFrame - }); - } - } - - close() { } - - } - - class FlvLoader extends CommonLoader { - constructor(player) { - super(player); - this.input = this._inputFlv(); - this.flvDemux = this.dispatchFlvData(this.input); - player.debug.log('FlvDemux', 'init'); - } - - destroy() { - super.destroy(); - this.input = null; - this.flvDemux = null; - this.player.debug.log('FlvDemux', 'destroy'); - } - - dispatch(data) { - this.flvDemux(data); - } - - *_inputFlv() { - yield 9; - const tmp = new ArrayBuffer(4); - const tmp8 = new Uint8Array(tmp); - const tmp32 = new Uint32Array(tmp); - const player = this.player; - - while (true) { - tmp8[3] = 0; - const t = yield 15; - const type = t[4]; - tmp8[0] = t[7]; - tmp8[1] = t[6]; - tmp8[2] = t[5]; - const length = tmp32[0]; - tmp8[0] = t[10]; - tmp8[1] = t[9]; - tmp8[2] = t[8]; - let ts = tmp32[0]; - - if (ts === 0xFFFFFF) { - tmp8[3] = t[11]; - ts = tmp32[0]; - } - - const payload = yield length; - - switch (type) { - case FLV_MEDIA_TYPE.audio: - if (player._opt.hasAudio) { - player.updateStats({ - abps: payload.byteLength - }); - - if (payload.byteLength > 0) { - this._doDecode(payload, MEDIA_TYPE.audio, ts); - } - } - - break; - - case FLV_MEDIA_TYPE.video: - if (!player._times.demuxStart) { - player._times.demuxStart = now(); - } - - if (player._opt.hasVideo) { - player.updateStats({ - vbps: payload.byteLength - }); - const isIFrame = payload[0] >> 4 === 1; - - if (payload.byteLength > 0) { - tmp32[0] = payload[4]; - tmp32[1] = payload[3]; - tmp32[2] = payload[2]; - tmp32[3] = 0; - let cts = tmp32[0]; - - this._doDecode(payload, MEDIA_TYPE.video, ts, isIFrame, cts); - } - } - - break; - } - } - } - - dispatchFlvData(input) { - let need = input.next(); - let buffer = null; - return value => { - let data = new Uint8Array(value); - - if (buffer) { - let combine = new Uint8Array(buffer.length + data.length); - combine.set(buffer); - combine.set(data, buffer.length); - data = combine; - buffer = null; - } - - while (data.length >= need.value) { - let remain = data.slice(need.value); - need = input.next(data.slice(0, need.value)); - data = remain; - } - - if (data.length > 0) { - buffer = data; - } - }; - } - - close() { - this.input && this.input.return(null); - } - - } - - class M7sLoader extends CommonLoader { - constructor(player) { - super(player); - player.debug.log('M7sDemux', 'init'); - } - - destroy() { - super.destroy(); - this.player.debug.log('M7sDemux', 'destroy'); - this.player = null; - } - - dispatch(data) { - const player = this.player; - const dv = new DataView(data); - const type = dv.getUint8(0); - const ts = dv.getUint32(1, false); - - switch (type) { - case MEDIA_TYPE.audio: - if (player._opt.hasAudio) { - const payload = new Uint8Array(data, 5); - player.updateStats({ - abps: payload.byteLength - }); - - if (payload.byteLength > 0) { - this._doDecode(payload, type, ts); - } - } - - break; - - case MEDIA_TYPE.video: - if (player._opt.hasVideo) { - if (!player._times.demuxStart) { - player._times.demuxStart = now(); - } - - if (dv.byteLength > 5) { - const payload = new Uint8Array(data, 5); - const isIframe = dv.getUint8(5) >> 4 === 1; - player.updateStats({ - vbps: payload.byteLength - }); - - if (payload.byteLength > 0) { - this._doDecode(payload, type, ts, isIframe); - } - } else { - this.player.debug.warn('M7sDemux', 'dispatch', 'dv byteLength is', dv.byteLength); - } - } - - break; - } - } - - } - - class Demux { - constructor(player) { - const Loader = Demux.getLoaderFactory(player._opt.demuxType); - return new Loader(player); - } - - static getLoaderFactory(type) { - if (type === DEMUX_TYPE.m7s) { - return M7sLoader; - } else if (type === DEMUX_TYPE.flv) { - return FlvLoader; - } - } - - } - - /* - * Copyright (C) 2016 Bilibili. All Rights Reserved. - * - * @author zheng qian - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - // Exponential-Golomb buffer decoder - class ExpGolomb { - constructor(uint8array) { - this.TAG = 'ExpGolomb'; - this._buffer = uint8array; - this._buffer_index = 0; - this._total_bytes = uint8array.byteLength; - this._total_bits = uint8array.byteLength * 8; - this._current_word = 0; - this._current_word_bits_left = 0; - } - - destroy() { - this._buffer = null; - } - - _fillCurrentWord() { - let buffer_bytes_left = this._total_bytes - this._buffer_index; - - let bytes_read = Math.min(4, buffer_bytes_left); - let word = new Uint8Array(4); - word.set(this._buffer.subarray(this._buffer_index, this._buffer_index + bytes_read)); - this._current_word = new DataView(word.buffer).getUint32(0, false); - this._buffer_index += bytes_read; - this._current_word_bits_left = bytes_read * 8; - } - - readBits(bits) { - - if (bits <= this._current_word_bits_left) { - let result = this._current_word >>> 32 - bits; - this._current_word <<= bits; - this._current_word_bits_left -= bits; - return result; - } - - let result = this._current_word_bits_left ? this._current_word : 0; - result = result >>> 32 - this._current_word_bits_left; - let bits_need_left = bits - this._current_word_bits_left; - - this._fillCurrentWord(); - - let bits_read_next = Math.min(bits_need_left, this._current_word_bits_left); - let result2 = this._current_word >>> 32 - bits_read_next; - this._current_word <<= bits_read_next; - this._current_word_bits_left -= bits_read_next; - result = result << bits_read_next | result2; - return result; - } - - readBool() { - return this.readBits(1) === 1; - } - - readByte() { - return this.readBits(8); - } - - _skipLeadingZero() { - let zero_count; - - for (zero_count = 0; zero_count < this._current_word_bits_left; zero_count++) { - if (0 !== (this._current_word & 0x80000000 >>> zero_count)) { - this._current_word <<= zero_count; - this._current_word_bits_left -= zero_count; - return zero_count; - } - } - - this._fillCurrentWord(); - - return zero_count + this._skipLeadingZero(); - } - - readUEG() { - // unsigned exponential golomb - let leading_zeros = this._skipLeadingZero(); - - return this.readBits(leading_zeros + 1) - 1; - } - - readSEG() { - // signed exponential golomb - let value = this.readUEG(); - - if (value & 0x01) { - return value + 1 >>> 1; - } else { - return -1 * (value >>> 1); - } - } - - } - - /* - * Copyright (C) 2016 Bilibili. All Rights Reserved. - * - * @author zheng qian - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - class SPSParser { - static _ebsp2rbsp(uint8array) { - let src = uint8array; - let src_length = src.byteLength; - let dst = new Uint8Array(src_length); - let dst_idx = 0; - - for (let i = 0; i < src_length; i++) { - if (i >= 2) { - // Unescape: Skip 0x03 after 00 00 - if (src[i] === 0x03 && src[i - 1] === 0x00 && src[i - 2] === 0x00) { - continue; - } - } - - dst[dst_idx] = src[i]; - dst_idx++; - } - - return new Uint8Array(dst.buffer, 0, dst_idx); - } // 解析 SPS - // https://zhuanlan.zhihu.com/p/27896239 - - - static parseSPS(uint8array) { - let rbsp = SPSParser._ebsp2rbsp(uint8array); - - let gb = new ExpGolomb(rbsp); - gb.readByte(); // 标识当前H.264码流的profile。 - // 我们知道,H.264中定义了三种常用的档次profile: 基准档次:baseline profile;主要档次:main profile; 扩展档次:extended profile; - - let profile_idc = gb.readByte(); // profile_idc - - gb.readByte(); // constraint_set_flags[5] + reserved_zero[3] - // 标识当前码流的Level。编码的Level定义了某种条件下的最大视频分辨率、最大视频帧率等参数,码流所遵从的level由level_idc指定。 - - let level_idc = gb.readByte(); // level_idc - // 表示当前的序列参数集的id。通过该id值,图像参数集pps可以引用其代表的sps中的参数。 - - gb.readUEG(); // seq_parameter_set_id - - let profile_string = SPSParser.getProfileString(profile_idc); - let level_string = SPSParser.getLevelString(level_idc); - let chroma_format_idc = 1; - let chroma_format = 420; - let chroma_format_table = [0, 420, 422, 444]; - let bit_depth = 8; // - - if (profile_idc === 100 || profile_idc === 110 || profile_idc === 122 || profile_idc === 244 || profile_idc === 44 || profile_idc === 83 || profile_idc === 86 || profile_idc === 118 || profile_idc === 128 || profile_idc === 138 || profile_idc === 144) { - // - chroma_format_idc = gb.readUEG(); - - if (chroma_format_idc === 3) { - gb.readBits(1); // separate_colour_plane_flag - } - - if (chroma_format_idc <= 3) { - chroma_format = chroma_format_table[chroma_format_idc]; - } - - bit_depth = gb.readUEG() + 8; // bit_depth_luma_minus8 - - gb.readUEG(); // bit_depth_chroma_minus8 - - gb.readBits(1); // qpprime_y_zero_transform_bypass_flag - - if (gb.readBool()) { - // seq_scaling_matrix_present_flag - let scaling_list_count = chroma_format_idc !== 3 ? 8 : 12; - - for (let i = 0; i < scaling_list_count; i++) { - if (gb.readBool()) { - // seq_scaling_list_present_flag - if (i < 6) { - SPSParser._skipScalingList(gb, 16); - } else { - SPSParser._skipScalingList(gb, 64); - } - } - } - } - } // 用于计算MaxFrameNum的值。计算公式为MaxFrameNum = 2^(log2_max_frame_num_minus4 + - - - gb.readUEG(); // log2_max_frame_num_minus4 - // 表示解码picture order count(POC)的方法。POC是另一种计量图像序号的方式,与frame_num有着不同的计算方法。该语法元素的取值为0、1或2。 - - let pic_order_cnt_type = gb.readUEG(); - - if (pic_order_cnt_type === 0) { - gb.readUEG(); // log2_max_pic_order_cnt_lsb_minus_4 - } else if (pic_order_cnt_type === 1) { - gb.readBits(1); // delta_pic_order_always_zero_flag - - gb.readSEG(); // offset_for_non_ref_pic - - gb.readSEG(); // offset_for_top_to_bottom_field - - let num_ref_frames_in_pic_order_cnt_cycle = gb.readUEG(); - - for (let i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++) { - gb.readSEG(); // offset_for_ref_frame - } - } // 用于表示参考帧的最大数目。 - - - let ref_frames = gb.readUEG(); // max_num_ref_frames - // 标识位,说明frame_num中是否允许不连续的值。 - - gb.readBits(1); // gaps_in_frame_num_value_allowed_flag - // 用于计算图像的宽度。单位为宏块个数,因此图像的实际宽度为: - - let pic_width_in_mbs_minus1 = gb.readUEG(); // 使用PicHeightInMapUnits来度量视频中一帧图像的高度。 - // PicHeightInMapUnits并非图像明确的以像素或宏块为单位的高度,而需要考虑该宏块是帧编码或场编码。PicHeightInMapUnits的计算方式为: - - let pic_height_in_map_units_minus1 = gb.readUEG(); // 标识位,说明宏块的编码方式。当该标识位为0时,宏块可能为帧编码或场编码; - // 该标识位为1时,所有宏块都采用帧编码。根据该标识位取值不同,PicHeightInMapUnits的含义也不同, - // 为0时表示一场数据按宏块计算的高度,为1时表示一帧数据按宏块计算的高度。 - - let frame_mbs_only_flag = gb.readBits(1); - - if (frame_mbs_only_flag === 0) { - // 标识位,说明是否采用了宏块级的帧场自适应编码。当该标识位为0时,不存在帧编码和场编码之间的切换;当标识位为1时,宏块可能在帧编码和场编码模式之间进行选择。 - gb.readBits(1); // mb_adaptive_frame_field_flag - } // 标识位,用于B_Skip、B_Direct模式运动矢量的推导计算。 - - - gb.readBits(1); // direct_8x8_inference_flag - - let frame_crop_left_offset = 0; - let frame_crop_right_offset = 0; - let frame_crop_top_offset = 0; - let frame_crop_bottom_offset = 0; - let frame_cropping_flag = gb.readBool(); - - if (frame_cropping_flag) { - frame_crop_left_offset = gb.readUEG(); - frame_crop_right_offset = gb.readUEG(); - frame_crop_top_offset = gb.readUEG(); - frame_crop_bottom_offset = gb.readUEG(); - } - - let sar_width = 1, - sar_height = 1; - let fps = 0, - fps_fixed = true, - fps_num = 0, - fps_den = 0; // 标识位,说明SPS中是否存在VUI信息。 - - let vui_parameters_present_flag = gb.readBool(); - - if (vui_parameters_present_flag) { - if (gb.readBool()) { - // aspect_ratio_info_present_flag - let aspect_ratio_idc = gb.readByte(); - let sar_w_table = [1, 12, 10, 16, 40, 24, 20, 32, 80, 18, 15, 64, 160, 4, 3, 2]; - let sar_h_table = [1, 11, 11, 11, 33, 11, 11, 11, 33, 11, 11, 33, 99, 3, 2, 1]; - - if (aspect_ratio_idc > 0 && aspect_ratio_idc < 16) { - sar_width = sar_w_table[aspect_ratio_idc - 1]; - sar_height = sar_h_table[aspect_ratio_idc - 1]; - } else if (aspect_ratio_idc === 255) { - sar_width = gb.readByte() << 8 | gb.readByte(); - sar_height = gb.readByte() << 8 | gb.readByte(); - } - } - - if (gb.readBool()) { - // overscan_info_present_flag - gb.readBool(); // overscan_appropriate_flag - } - - if (gb.readBool()) { - // video_signal_type_present_flag - gb.readBits(4); // video_format & video_full_range_flag - - if (gb.readBool()) { - // colour_description_present_flag - gb.readBits(24); // colour_primaries & transfer_characteristics & matrix_coefficients - } - } - - if (gb.readBool()) { - // chroma_loc_info_present_flag - gb.readUEG(); // chroma_sample_loc_type_top_field - - gb.readUEG(); // chroma_sample_loc_type_bottom_field - } - - if (gb.readBool()) { - // timing_info_present_flag - let num_units_in_tick = gb.readBits(32); - let time_scale = gb.readBits(32); - fps_fixed = gb.readBool(); // fixed_frame_rate_flag - - fps_num = time_scale; - fps_den = num_units_in_tick * 2; - fps = fps_num / fps_den; - } - } - - let sarScale = 1; - - if (sar_width !== 1 || sar_height !== 1) { - sarScale = sar_width / sar_height; - } - - let crop_unit_x = 0, - crop_unit_y = 0; - - if (chroma_format_idc === 0) { - crop_unit_x = 1; - crop_unit_y = 2 - frame_mbs_only_flag; - } else { - let sub_wc = chroma_format_idc === 3 ? 1 : 2; - let sub_hc = chroma_format_idc === 1 ? 2 : 1; - crop_unit_x = sub_wc; - crop_unit_y = sub_hc * (2 - frame_mbs_only_flag); - } - - let codec_width = (pic_width_in_mbs_minus1 + 1) * 16; - let codec_height = (2 - frame_mbs_only_flag) * ((pic_height_in_map_units_minus1 + 1) * 16); - codec_width -= (frame_crop_left_offset + frame_crop_right_offset) * crop_unit_x; - codec_height -= (frame_crop_top_offset + frame_crop_bottom_offset) * crop_unit_y; - let present_width = Math.ceil(codec_width * sarScale); - gb.destroy(); - gb = null; // 解析出来的SPS 内容。 - - return { - profile_string: profile_string, - // baseline, high, high10, ... - level_string: level_string, - // 3, 3.1, 4, 4.1, 5, 5.1, ... - bit_depth: bit_depth, - // 8bit, 10bit, ... - ref_frames: ref_frames, - chroma_format: chroma_format, - // 4:2:0, 4:2:2, ... - chroma_format_string: SPSParser.getChromaFormatString(chroma_format), - frame_rate: { - fixed: fps_fixed, - fps: fps, - fps_den: fps_den, - fps_num: fps_num - }, - sar_ratio: { - width: sar_width, - height: sar_height - }, - codec_size: { - width: codec_width, - height: codec_height - }, - present_size: { - width: present_width, - height: codec_height - } - }; - } - - static _skipScalingList(gb, count) { - let last_scale = 8, - next_scale = 8; - let delta_scale = 0; - - for (let i = 0; i < count; i++) { - if (next_scale !== 0) { - delta_scale = gb.readSEG(); - next_scale = (last_scale + delta_scale + 256) % 256; - } - - last_scale = next_scale === 0 ? last_scale : next_scale; - } - } // profile_idc = 66 → baseline profile; - // profile_idc = 77 → main profile; - // profile_idc = 88 → extended profile; - // 在新版的标准中,还包括了High、High 10、High 4:2:2、High 4:4:4、High 10 Intra、High - // 4:2:2 Intra、High 4:4:4 Intra、CAVLC 4:4:4 Intra - - - static getProfileString(profile_idc) { - switch (profile_idc) { - case 66: - return 'Baseline'; - - case 77: - return 'Main'; - - case 88: - return 'Extended'; - - case 100: - return 'High'; - - case 110: - return 'High10'; - - case 122: - return 'High422'; - - case 244: - return 'High444'; - - default: - return 'Unknown'; - } - } - - static getLevelString(level_idc) { - return (level_idc / 10).toFixed(1); - } - - static getChromaFormatString(chroma) { - switch (chroma) { - case 420: - return '4:2:0'; - - case 422: - return '4:2:2'; - - case 444: - return '4:4:4'; - - default: - return 'Unknown'; - } - } - - } - - function parseAVCDecoderConfigurationRecord(arrayBuffer) { - const meta = {}; - const v = new DataView(arrayBuffer.buffer); - let version = v.getUint8(0); // configurationVersion - - let avcProfile = v.getUint8(1); // avcProfileIndication - - v.getUint8(2); // profile_compatibil - - v.getUint8(3); // AVCLevelIndication - - if (version !== 1 || avcProfile === 0) { - // this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: Invalid AVCDecoderConfigurationRecord'); - return meta; - } - - const _naluLengthSize = (v.getUint8(4) & 3) + 1; // lengthSizeMinusOne - - - if (_naluLengthSize !== 3 && _naluLengthSize !== 4) { - // holy shit!!! - // this._onError(DemuxErrors.FORMAT_ERROR, `Flv: Strange NaluLengthSizeMinusOne: ${_naluLengthSize - 1}`); - return meta; - } - - let spsCount = v.getUint8(5) & 31; // numOfSequenceParameterSets - - if (spsCount === 0) { - // this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: Invalid AVCDecoderConfigurationRecord: No SPS'); - return; - } - - let offset = 6; - - for (let i = 0; i < spsCount; i++) { - let len = v.getUint16(offset, false); // sequenceParameterSetLength - - offset += 2; - - if (len === 0) { - continue; - } // Notice: Nalu without startcode header (00 00 00 01) - - - let sps = new Uint8Array(arrayBuffer.buffer, offset, len); - offset += len; // flv.js作者选择了自己来解析这个数据结构,也是迫不得已,因为JS环境下没有ffmpeg,解析这个结构主要是为了提取 sps和pps。虽然理论上sps允许有多个,但其实一般就一个。 - // packetTtype 为 1 表示 NALU,NALU= network abstract layer unit,这是H.264的概念,网络抽象层数据单元,其实简单理解就是一帧视频数据。 - // pps的信息没什么用,所以作者只实现了sps的分析器,说明作者下了很大功夫去学习264的标准,其中的Golomb解码还是挺复杂的,能解对不容易,我在PC和手机平台都是用ffmpeg去解析的。 - // SPS里面包括了视频分辨率,帧率,profile level等视频重要信息。 - - let config = SPSParser.parseSPS(sps); - - if (i !== 0) { - // ignore other sps's config - continue; - } - - meta.codecWidth = config.codec_size.width; - meta.codecHeight = config.codec_size.height; - meta.presentWidth = config.present_size.width; - meta.presentHeight = config.present_size.height; - meta.profile = config.profile_string; - meta.level = config.level_string; - meta.bitDepth = config.bit_depth; - meta.chromaFormat = config.chroma_format; - meta.sarRatio = config.sar_ratio; - meta.frameRate = config.frame_rate; - - if (config.frame_rate.fixed === false || config.frame_rate.fps_num === 0 || config.frame_rate.fps_den === 0) { - meta.frameRate = {}; - } - - let fps_den = meta.frameRate.fps_den; - let fps_num = meta.frameRate.fps_num; - meta.refSampleDuration = meta.timescale * (fps_den / fps_num); - let codecArray = sps.subarray(1, 4); - let codecString = 'avc1.'; - - for (let j = 0; j < 3; j++) { - let h = codecArray[j].toString(16); - - if (h.length < 2) { - h = '0' + h; - } - - codecString += h; - } // codec - - - meta.codec = codecString; - } - - let ppsCount = v.getUint8(offset); // numOfPictureParameterSets - - if (ppsCount === 0) { - // this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: Invalid AVCDecoderConfigurationRecord: No PPS'); - return meta; - } - - offset++; - - for (let i = 0; i < ppsCount; i++) { - let len = v.getUint16(offset, false); // pictureParameterSetLength - - offset += 2; - - if (len === 0) { - continue; - } - - new Uint8Array(arrayBuffer.buffer, offset, len); // pps is useless for extracting video information - - offset += len; - } - - meta.videoType = 'avc'; // meta.avcc = arrayBuffer; - - return meta; - } - - class WebcodecsDecoder extends Emitter { - constructor(player) { - super(); - this.player = player; - this.hasInit = false; - this.isDecodeFirstIIframe = false; - this.isInitInfo = false; - this.decoder = null; - this.initDecoder(); - player.debug.log('Webcodecs', 'init'); - } - - destroy() { - if (this.decoder) { - if (this.decoder.state !== 'closed') { - this.decoder.close(); - } - - this.decoder = null; - } - - this.hasInit = false; - this.isInitInfo = false; - this.isDecodeFirstIIframe = false; - this.off(); - this.player.debug.log('Webcodecs', 'destroy'); - } - - initDecoder() { - const _this = this; - - this.decoder = new VideoDecoder({ - output(videoFrame) { - _this.handleDecode(videoFrame); - }, - - error(error) { - _this.handleError(error); - } - - }); - } - - handleDecode(videoFrame) { - if (!this.isInitInfo) { - this.player.video.updateVideoInfo({ - width: videoFrame.codedWidth, - height: videoFrame.codedHeight - }); - this.player.video.initCanvasViewSize(); - this.isInitInfo = true; - } - - if (!this.player._times.videoStart) { - this.player._times.videoStart = now(); - this.player.handlePlayToRenderTimes(); - } - - this.player.handleRender(); - this.player.video.render({ - videoFrame - }); - this.player.updateStats({ - fps: true, - ts: 0, - buf: this.player.demux.delay - }); - } - - handleError(error) { - this.player.debug.error('Webcodecs', 'VideoDecoder handleError', error); - } - - decodeVideo(payload, ts, isIframe) { - // this.player.debug.log('Webcodecs decoder', 'decodeVideo', ts, isIframe); - if (!this.hasInit) { - if (isIframe && payload[1] === 0) { - const videoCodec = payload[0] & 0x0F; - this.player.video.updateVideoInfo({ - encTypeCode: videoCodec - }); // 如果解码出来的是 - - if (videoCodec === VIDEO_ENC_CODE.h265) { - this.emit(EVENTS_ERROR.webcodecsH265NotSupport); - return; - } - - if (!this.player._times.decodeStart) { - this.player._times.decodeStart = now(); - } - - const config = formatVideoDecoderConfigure(payload.slice(5)); - this.decoder.configure(config); - this.hasInit = true; - } - } else { - // check width or height change - if (isIframe && payload[1] === 0) { - let data = payload.slice(5); - const config = parseAVCDecoderConfigurationRecord(data); - const videoInfo = this.player.video.videoInfo; - - if (config.codecWidth !== videoInfo.width || config.codecHeight !== videoInfo.height) { - this.player.debug.log('Webcodecs', `width or height is update, width ${videoInfo.width}-> ${config.codecWidth}, height ${videoInfo.height}-> ${config.codecHeight}`); - this.player.emit(EVENTS_ERROR.webcodecsWidthOrHeightChange); - return; - } - } // fix : Uncaught DOMException: Failed to execute 'decode' on 'VideoDecoder': A key frame is required after configure() or flush(). - - - if (!this.isDecodeFirstIIframe && isIframe) { - this.isDecodeFirstIIframe = true; - } - - if (this.isDecodeFirstIIframe) { - const chunk = new EncodedVideoChunk({ - data: payload.slice(5), - timestamp: ts, - type: isIframe ? ENCODED_VIDEO_TYPE.key : ENCODED_VIDEO_TYPE.delta - }); - this.player.emit(EVENTS.timeUpdate, ts); - - try { - if (this.isDecodeStateClosed()) { - this.player.debug.warn('Webcodecs', 'VideoDecoder isDecodeStateClosed true'); - return; - } - - this.decoder.decode(chunk); - } catch (e) { - this.player.debug.error('Webcodecs', 'VideoDecoder', e); - - if (e.toString().indexOf(WCS_ERROR.keyframeIsRequiredError) !== -1) { - this.player.emitError(EVENTS_ERROR.webcodecsDecodeError); - } else if (e.toString().indexOf(WCS_ERROR.canNotDecodeClosedCodec) !== -1) { - this.player.emitError(EVENTS_ERROR.webcodecsDecodeError); - } - } - } else { - this.player.debug.warn('Webcodecs', 'VideoDecoder isDecodeFirstIIframe false'); - } - } - } - - isDecodeStateClosed() { - return this.decoder.state === 'closed'; - } - - } - - const iconsMap = { - play: '播放', - pause: '暂停', - audio: '', - mute: '', - screenshot: '截图', - loading: '加载', - fullscreen: '全屏', - fullscreenExit: '退出全屏', - record: '录制', - recordStop: '停止录制' - }; - var icons = Object.keys(iconsMap).reduce((icons, key) => { - icons[key] = ` - - ${iconsMap[key] ? `${iconsMap[key]}` : ''} -`; - return icons; - }, {}); - - var template = ((player, control) => { - if (player._opt.hasControl && player._opt.controlAutoHide) { - player.$container.classList.add('jessibuca-controls-show-auto-hide'); - } else { - player.$container.classList.add('jessibuca-controls-show'); - } - - const options = player._opt; - const operateBtns = options.operateBtns; - player.$container.insertAdjacentHTML('beforeend', ` - ${options.background ? `
` : ''} -
- ${icons.loading} - ${options.loadingText ? `
${options.loadingText}
` : ''} -
- ${options.hasControl && operateBtns.play ? `
` : ''} - ${options.hasControl ? ` -
-
-
00:00:01
-
${icons.recordStop}
-
- ` : ''} - ${options.hasControl ? ` -
-
-
- ${options.showBandwidth ? `
` : ''} -
-
- ${operateBtns.audio ? ` -
- ${icons.audio} - ${icons.mute} -
-
-
-
-
-
-
- ` : ''} - ${operateBtns.play ? `
${icons.play}
${icons.pause}
` : ''} - ${operateBtns.screenshot ? `
${icons.screenshot}
` : ''} - ${operateBtns.record ? `
${icons.record}
${icons.recordStop}
` : ''} - ${operateBtns.fullscreen ? `
${icons.fullscreen}
${icons.fullscreenExit}
` : ''} -
-
-
- ` : ''} - - `); - Object.defineProperty(control, '$poster', { - value: player.$container.querySelector('.jessibuca-poster') - }); - Object.defineProperty(control, '$loading', { - value: player.$container.querySelector('.jessibuca-loading') - }); - Object.defineProperty(control, '$play', { - value: player.$container.querySelector('.jessibuca-play') - }); - Object.defineProperty(control, '$playBig', { - value: player.$container.querySelector('.jessibuca-play-big') - }); - Object.defineProperty(control, '$recording', { - value: player.$container.querySelector('.jessibuca-recording') - }); - Object.defineProperty(control, '$recordingTime', { - value: player.$container.querySelector('.jessibuca-recording-time') - }); - Object.defineProperty(control, '$recordingStop', { - value: player.$container.querySelector('.jessibuca-recording-stop') - }); - Object.defineProperty(control, '$pause', { - value: player.$container.querySelector('.jessibuca-pause') - }); - Object.defineProperty(control, '$controls', { - value: player.$container.querySelector('.jessibuca-controls') - }); - Object.defineProperty(control, '$fullscreen', { - value: player.$container.querySelector('.jessibuca-fullscreen') - }); - Object.defineProperty(control, '$fullscreen', { - value: player.$container.querySelector('.jessibuca-fullscreen') - }); - Object.defineProperty(control, '$volume', { - value: player.$container.querySelector('.jessibuca-volume') - }); - Object.defineProperty(control, '$volumePanelWrap', { - value: player.$container.querySelector('.jessibuca-volume-panel-wrap') - }); - Object.defineProperty(control, '$volumePanelText', { - value: player.$container.querySelector('.jessibuca-volume-panel-text') - }); - Object.defineProperty(control, '$volumePanel', { - value: player.$container.querySelector('.jessibuca-volume-panel') - }); - Object.defineProperty(control, '$volumeHandle', { - value: player.$container.querySelector('.jessibuca-volume-panel-handle') - }); - Object.defineProperty(control, '$volumeOn', { - value: player.$container.querySelector('.jessibuca-icon-audio') - }); - Object.defineProperty(control, '$volumeOff', { - value: player.$container.querySelector('.jessibuca-icon-mute') - }); - Object.defineProperty(control, '$fullscreen', { - value: player.$container.querySelector('.jessibuca-fullscreen') - }); - Object.defineProperty(control, '$fullscreenExit', { - value: player.$container.querySelector('.jessibuca-fullscreen-exit') - }); - Object.defineProperty(control, '$record', { - value: player.$container.querySelector('.jessibuca-record') - }); - Object.defineProperty(control, '$recordStop', { - value: player.$container.querySelector('.jessibuca-record-stop') - }); - Object.defineProperty(control, '$screenshot', { - value: player.$container.querySelector('.jessibuca-screenshot') - }); - Object.defineProperty(control, '$speed', { - value: player.$container.querySelector('.jessibuca-speed') - }); - }); - - var observer$1 = ((player, control) => { - const { - events: { - proxy - } - } = player; - const object = document.createElement('object'); - object.setAttribute('aria-hidden', 'true'); - object.setAttribute('tabindex', -1); - object.type = 'text/html'; - object.data = 'about:blank'; - setStyle(object, { - display: 'block', - position: 'absolute', - top: '0', - left: '0', - height: '100%', - width: '100%', - overflow: 'hidden', - pointerEvents: 'none', - zIndex: '-1' - }); - let playerWidth = player.width; - let playerHeight = player.height; - proxy(object, 'load', () => { - proxy(object.contentDocument.defaultView, 'resize', () => { - if (player.width !== playerWidth || player.height !== playerHeight) { - playerWidth = player.width; - playerHeight = player.height; - player.emit(EVENTS.resize); - screenfullH5Control(); - } - }); - }); - player.$container.appendChild(object); - player.on(EVENTS.destroy, () => { - player.$container.removeChild(object); - }); - - function setVolumeHandle(percentage) { - if (percentage === 0) { - setStyle(control.$volumeOn, 'display', 'none'); - setStyle(control.$volumeOff, 'display', 'flex'); - setStyle(control.$volumeHandle, 'top', `${48}px`); - } else { - if (control.$volumeHandle && control.$volumePanel) { - const panelHeight = getStyle(control.$volumePanel, 'height') || 60; - const handleHeight = getStyle(control.$volumeHandle, 'height'); - const top = panelHeight - (panelHeight - handleHeight) * percentage - handleHeight; - setStyle(control.$volumeHandle, 'top', `${top}px`); - setStyle(control.$volumeOn, 'display', 'flex'); - setStyle(control.$volumeOff, 'display', 'none'); - } - } - - control.$volumePanelText && (control.$volumePanelText.innerHTML = parseInt(percentage * 100)); - } - - player.on(EVENTS.volumechange, () => { - setVolumeHandle(player.volume); - }); - player.on(EVENTS.loading, flag => { - setStyle(control.$loading, 'display', flag ? 'flex' : 'none'); - setStyle(control.$poster, 'display', 'none'); - - if (flag) { - setStyle(control.$playBig, 'display', 'none'); - } - }); - - const screenfullChange = fullscreen => { - let isFullScreen = isBoolean(fullscreen) ? fullscreen : player.fullscreen; - setStyle(control.$fullscreenExit, 'display', isFullScreen ? 'flex' : 'none'); - setStyle(control.$fullscreen, 'display', isFullScreen ? 'none' : 'flex'); // control.autoSize(); - }; - - const screenfullH5Control = () => { - if (isMobile() && control.$controls && player._opt.useWebFullScreen) { - setTimeout(() => { - if (player.fullscreen) { - // console.log(player.width, player.height); - let translateX = player.height / 2 - player.width + CONTROL_HEIGHT / 2; - let translateY = player.height / 2 - CONTROL_HEIGHT / 2; - control.$controls.style.transform = `translateX(${-translateX}px) translateY(-${translateY}px) rotate(-90deg)`; - } else { - control.$controls.style.transform = `translateX(0) translateY(0) rotate(0)`; - } - }, 10); - } - }; - - try { - screenfull.on('change', screenfullChange); - player.events.destroys.push(() => { - screenfull.off('change', screenfullChange); - }); - } catch (error) {// - } // - - - player.on(EVENTS.webFullscreen, value => { - screenfullChange(value); - screenfullH5Control(); - }); - player.on(EVENTS.recording, () => { - setStyle(control.$record, 'display', player.recording ? 'none' : 'flex'); - setStyle(control.$recordStop, 'display', player.recording ? 'flex' : 'none'); - setStyle(control.$recording, 'display', player.recording ? 'flex' : 'none'); - }); // - - player.on(EVENTS.recordingTimestamp, timestamp => { - // console.log(timestamp); - control.$recordingTime && (control.$recordingTime.innerHTML = formatTimeTips(timestamp)); - }); - player.on(EVENTS.playing, flag => { - setStyle(control.$play, 'display', flag ? 'none' : 'flex'); - setStyle(control.$playBig, 'display', flag ? 'none' : 'block'); - setStyle(control.$pause, 'display', flag ? 'flex' : 'none'); - setStyle(control.$screenshot, 'display', flag ? 'flex' : 'none'); - setStyle(control.$record, 'display', flag ? 'flex' : 'none'); - setStyle(control.$qualityMenu, 'display', flag ? 'flex' : 'none'); - setStyle(control.$volume, 'display', flag ? 'flex' : 'none'); // setStyle(control.$fullscreen, 'display', flag ? 'flex' : 'none'); - - screenfullChange(); // 不在播放 - - if (!flag) { - control.$speed && (control.$speed.innerHTML = bpsSize('')); - } - }); - player.on(EVENTS.kBps, rate => { - const bps = bpsSize(rate); - control.$speed && (control.$speed.innerHTML = bps); - }); - }); - - var property = ((player, control) => { - Object.defineProperty(control, 'controlsRect', { - get: () => { - return control.$controls.getBoundingClientRect(); - } - }); - }); - - var events = ((player, control) => { - const { - events: { - proxy - }, - debug - } = player; - - function volumeChangeFromEvent(event) { - const { - bottom: panelBottom, - height: panelHeight - } = control.$volumePanel.getBoundingClientRect(); - const { - height: handleHeight - } = control.$volumeHandle.getBoundingClientRect(); - let moveLen = event.y; // if (isMobile() && player.fullscreen) { - // moveLen = event.x; - // } - - const percentage = clamp(panelBottom - moveLen - handleHeight / 2, 0, panelHeight - handleHeight / 2) / (panelHeight - handleHeight); - return percentage; - } // - - - proxy(window, ['click', 'contextmenu'], event => { - if (event.composedPath().indexOf(player.$container) > -1) { - control.isFocus = true; - } else { - control.isFocus = false; - } - }); // - - proxy(window, 'orientationchange', () => { - setTimeout(() => { - player.resize(); - }, 300); - }); - proxy(control.$controls, 'click', e => { - e.stopPropagation(); - }); - proxy(control.$pause, 'click', e => { - player.pause(); - }); // 监听 play 方法 - - proxy(control.$play, 'click', e => { - player.play(); - player.resumeAudioAfterPause(); - }); // 监听 play 方法 - - proxy(control.$playBig, 'click', e => { - player.play(); - player.resumeAudioAfterPause(); - }); - proxy(control.$volume, 'mouseover', () => { - control.$volumePanelWrap.classList.add('jessibuca-volume-panel-wrap-show'); - }); - proxy(control.$volume, 'mouseout', () => { - control.$volumePanelWrap.classList.remove('jessibuca-volume-panel-wrap-show'); - }); - proxy(control.$volumeOn, 'click', e => { - e.stopPropagation(); - setStyle(control.$volumeOn, 'display', 'none'); - setStyle(control.$volumeOff, 'display', 'block'); - const lastVolume = player.volume; - player.volume = 0; - player._lastVolume = lastVolume; - }); - proxy(control.$volumeOff, 'click', e => { - e.stopPropagation(); - setStyle(control.$volumeOn, 'display', 'block'); - setStyle(control.$volumeOff, 'display', 'none'); - player.volume = player.lastVolume || 0.5; - }); - proxy(control.$screenshot, 'click', e => { - e.stopPropagation(); - player.video.screenshot(); - }); - proxy(control.$volumePanel, 'click', event => { - event.stopPropagation(); - player.volume = volumeChangeFromEvent(event); - }); - proxy(control.$volumeHandle, 'mousedown', () => { - control.isVolumeDroging = true; - }); - proxy(control.$volumeHandle, 'mousemove', event => { - if (control.isVolumeDroging) { - player.volume = volumeChangeFromEvent(event); - } - }); - proxy(document, 'mouseup', () => { - if (control.isVolumeDroging) { - control.isVolumeDroging = false; - } - }); - proxy(control.$record, 'click', e => { - e.stopPropagation(); - player.recording = true; - }); - proxy(control.$recordStop, 'click', e => { - e.stopPropagation(); - player.recording = false; - }); - proxy(control.$recordingStop, 'click', e => { - e.stopPropagation(); - player.recording = false; - }); - proxy(control.$fullscreen, 'click', e => { - e.stopPropagation(); - player.fullscreen = true; - }); - proxy(control.$fullscreenExit, 'click', e => { - e.stopPropagation(); - player.fullscreen = false; - }); - - if (player._opt.hasControl && player._opt.controlAutoHide) { - // - proxy(player.$container, 'mouseover', () => { - if (!player.fullscreen) { - setStyle(control.$controls, 'display', 'block'); - startDelayControlHidden(); - } - }); - proxy(player.$container, 'mousemove', () => { - if (player.$container && control.$controls) { - if (!player.fullscreen) { - if (control.$controls.style.display === 'none') { - setStyle(control.$controls, 'display', 'block'); - startDelayControlHidden(); - } - } else { - if (control.$controls.style.display === 'none') { - setStyle(control.$controls, 'display', 'block'); - startDelayControlHidden(); - } - } - } - }); - proxy(player.$container, 'mouseout', () => { - stopDelayControlHidden(); - setStyle(control.$controls, 'display', 'none'); - }); - let delayHiddenTimeout = null; - - const startDelayControlHidden = () => { - stopDelayControlHidden(); - delayHiddenTimeout = setTimeout(() => { - setStyle(control.$controls, 'display', 'none'); - }, 5 * 1000); - }; - - const stopDelayControlHidden = () => { - if (delayHiddenTimeout) { - clearTimeout(delayHiddenTimeout); - delayHiddenTimeout = null; - } - }; - } - }); - - function styleInject(css, ref) { - if (ref === void 0) ref = {}; - var insertAt = ref.insertAt; - - if (!css || typeof document === 'undefined') { return; } - - var head = document.head || document.getElementsByTagName('head')[0]; - var style = document.createElement('style'); - style.type = 'text/css'; - - if (insertAt === 'top') { - if (head.firstChild) { - head.insertBefore(style, head.firstChild); - } else { - head.appendChild(style); - } - } else { - head.appendChild(style); - } - - if (style.styleSheet) { - style.styleSheet.cssText = css; - } else { - style.appendChild(document.createTextNode(css)); - } - } - - var css_248z$1 = "@keyframes rotation{0%{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(1turn)}}@keyframes magentaPulse{0%{background-color:#630030;-webkit-box-shadow:0 0 9px #333}50%{background-color:#a9014b;-webkit-box-shadow:0 0 18px #a9014b}to{background-color:#630030;-webkit-box-shadow:0 0 9px #333}}.jessibuca-container .jessibuca-icon{cursor:pointer;width:16px;height:16px}.jessibuca-container .jessibuca-poster{position:absolute;z-index:10;left:0;top:0;right:0;bottom:0;height:100%;width:100%;background-position:50%;background-repeat:no-repeat;background-size:contain;pointer-events:none}.jessibuca-container .jessibuca-play-big{position:absolute;display:none;height:100%;width:100%;background:rgba(0,0,0,.4)}.jessibuca-container .jessibuca-play-big:after{cursor:pointer;content:\"\";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);display:block;width:48px;height:48px;background-image:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACgklEQVRoQ+3ZPYsTQRjA8eeZZCFlWttAwCIkZOaZJt8hlvkeHrlccuAFT6wEG0FQOeQQLCIWih6chQgKgkkKIyqKCVYip54IWmiQkTmyYhFvd3Zn3yDb7szu/7cv7GaDkPEFM94PK0DSZ9DzDAyHw7uI2HRDlVJX5/N5r9FoHCYdr/fvCRiNRmpJ6AEidoUQ15NG+AH8BgD2n9AHANAmohdJQfwAfgGA4xF4bjabnW21Whob62ILoKNfAsAGEd2PU2ATcNSNiDf0/cE5/xAHxDpgEf0NADaJ6HLUiKgAbvcjpdSGlPJZVJCoAUfdSqkLxWLxTLlc/mkbEgtgET1TSnWklLdtIuIEuN23crlcp16vv7cBSQKgu38AwBYRXQyLSArg3hsjRDxNRE+CQhIF/BN9qVAobFYqle+mkLQAdLd+8K0T0U0TRJoAbvc9fVkJId75gaQRoLv1C2STiPTb7rFLWgE6+g0RncwyYEJEtawCvjDGmpzzp5kD6NfxfD7frtVqB17xen2a7oG3ALBm+oMoFQBEPD+dTvtBfpImDXjIGFvjnD/3c7ksG5MU4HDxWeZa0HB3XhKAXcdxOn5vUi9gnIDXSqm2lHLPK8pkfVyAbSLqm4T5HRs1YB8RO0KIid8g03FRAT4rpbpSyh3TINPxUQB2GGM9zvkn05gg420CJovLZT9ISNA5tgB9ItoOGhFmnh/AcZ/X9xhj65zzV2Eiwsz1A1j2B8dHAOgS0W6YnduY6wkYj8d3lFKn/j66Ea84jtOrVqtfbQSE3YYnYDAY5Eql0hYAnNDv6kKIx2F3anO+J8DmzqLY1goQxVE12ebqDJgcrSjGrs5AFEfVZJt/AF0m+jHzUTtnAAAAAElFTkSuQmCC\");background-repeat:no-repeat;background-position:50%}.jessibuca-container .jessibuca-play-big:hover:after{background-image:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACEElEQVRoQ+2ZXStEQRjH/3/yIXwDdz7J+i7kvdisXCk3SiFJW27kglBcSFFKbqwQSa4krykuKB09Naf2Yndn5jgzc06d53Znd36/mWfeniVyHsw5PwqB0DOonYEoijYBlOpAFwCMkHwLDS/9mwhEDUCfAAyTXA4tYSLwC6CtCegegH6S56FETAR+AHRoACcBTJAUWa+RloBAXwAYIrnt0yBNgZi7qtbHgw8RFwLC/QFglOScawlXAjH3gUqrE1cirgVi7mkAYyS/0xbxJSDcdwAGSa6nKeFTIOZeUyL3aYiEEBDuLwDjJGf+KxFKIOY+BdBL8iipSGiBmHtWbbuftiJZERBuOfgGSK7aSGRJIObeUml1ayKSRQHhlgtkiaTcdltGVgUE+ppkV54FaiS78yrwqlLoOI8Cch2XV548W7WRpTVwA6DP9kGUFYEpAOUkT9LQAvtq1M+0udKkQSgBqSlJWWYxKXj8vRACK+o6bbRIdYI+Ba7U7rKjg7L53JdAhWTZBsy0rWuBXZUuNVMg23auBF7UIl2yBbJt70JAoKV6/WwLk6R9mgKSJlJ1kLTxFmkJyCla8UZd15GJQKvyumyJ8gy8DAEvfZoINPqD41EtUjmUgoaJwAaAnjrKebVI34OSq85NBNqlCAWgE0CV5GEWwI3vQlmCbcSinYFCwPEIFDPgeIC1P1/MgHaIHDf4Aydx2TF7wnKeAAAAAElFTkSuQmCC\")}.jessibuca-container .jessibuca-recording{display:none;position:absolute;left:50%;top:0;padding:0 3px;transform:translateX(-50%);justify-content:space-around;align-items:center;width:95px;height:20px;background:#000;opacity:1;border-radius:0 0 8px 8px;z-index:1}.jessibuca-container .jessibuca-recording .jessibuca-recording-red-point{width:8px;height:8px;background:#ff1f1f;border-radius:50%;animation:magentaPulse 1s linear infinite}.jessibuca-container .jessibuca-recording .jessibuca-recording-time{font-size:14px;font-weight:500;color:#ddd}.jessibuca-container .jessibuca-recording .jessibuca-icon-recordStop{width:16px;height:16px;cursor:pointer}.jessibuca-container .jessibuca-loading{display:none;flex-direction:column;justify-content:center;align-items:center;position:absolute;z-index:20;left:0;top:0;right:0;bottom:0;width:100%;height:100%;pointer-events:none}.jessibuca-container .jessibuca-loading-text{line-height:20px;font-size:13px;color:#fff;margin-top:10px}.jessibuca-container .jessibuca-controls{background-color:#161616;box-sizing:border-box;display:flex;flex-direction:column;justify-content:flex-end;position:absolute;z-index:40;left:0;right:0;bottom:0;height:38px;width:100%;padding-left:13px;padding-right:13px;font-size:14px;color:#fff;opacity:0;visibility:hidden;transition:all .2s ease-in-out;-webkit-user-select:none;user-select:none;transition:width .5s ease-in}.jessibuca-container .jessibuca-controls .jessibuca-controls-item{position:relative;display:flex;justify-content:center;padding:0 8px}.jessibuca-container .jessibuca-controls .jessibuca-controls-item:hover .icon-title-tips{visibility:visible;opacity:1}.jessibuca-container .jessibuca-controls .jessibuca-fullscreen,.jessibuca-container .jessibuca-controls .jessibuca-fullscreen-exit,.jessibuca-container .jessibuca-controls .jessibuca-icon-audio,.jessibuca-container .jessibuca-controls .jessibuca-microphone-close,.jessibuca-container .jessibuca-controls .jessibuca-pause,.jessibuca-container .jessibuca-controls .jessibuca-play,.jessibuca-container .jessibuca-controls .jessibuca-record,.jessibuca-container .jessibuca-controls .jessibuca-record-stop,.jessibuca-container .jessibuca-controls .jessibuca-screenshot{display:none}.jessibuca-container .jessibuca-controls .jessibuca-icon-audio,.jessibuca-container .jessibuca-controls .jessibuca-icon-mute{z-index:1}.jessibuca-container .jessibuca-controls .jessibuca-controls-bottom{display:flex;justify-content:space-between;height:100%}.jessibuca-container .jessibuca-controls .jessibuca-controls-bottom .jessibuca-controls-left,.jessibuca-container .jessibuca-controls .jessibuca-controls-bottom .jessibuca-controls-right{display:flex;align-items:center}.jessibuca-container.jessibuca-controls-show .jessibuca-controls{opacity:1;visibility:visible}.jessibuca-container.jessibuca-controls-show-auto-hide .jessibuca-controls{opacity:.8;visibility:visible;display:none}.jessibuca-container.jessibuca-hide-cursor *{cursor:none!important}.jessibuca-container .jessibuca-icon-loading{width:50px;height:50px;background:url(\"data:image/png;base64,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\") no-repeat 50%;background-size:100% 100%;animation:rotation 1s linear infinite}.jessibuca-container .jessibuca-icon-screenshot{background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAE5UlEQVRoQ+1YW2sdVRT+1s7JxbsoVkEUrIIX0ouz15zYNA+N1RdtQfCltlUfvLbqL/BCwZ8grbHtizQqPojgBSr0JkiMmT2nxgapqBURtPVCq7HxJCeZJVPmxDlzZubMmXOSEsnAvOy917fXt9e39tp7E5b4R0vcfywTuNgRbBgBx3HuJqLVzPzmYjprjHkcwAlmLqXNm4XAISLaSESPaq2HF4OE67rbRGRYRA7btn1fbgLGmKsA/Azg0gBkGzO/vZAkHMd5hIiqc5wHcCMz/5k0Z2oExsfHV1QqldPAf8lORNu11m8tBAljzFYAYWxRSl1vWdZvuQj4RsYYF4AVBlgIOVVlE55HRIxt23ZuCfmGjuOsJ6LPoiAistW27XfaEYmIbOYhPc9bXywWR1oiEJDYQkR1zrYjEjGyqfqbKd8a7kJVtLgQ+30i8pht2wfyRKIdmJkJBPkQTbILfudJ7CTZNBvVpggEcgpvc/ML38zESbLJsxBNE/A9biX0rdjGyTQXgbxyapdsarb0PMlXtWnGoXbKpm0Essqp3bJpK4E0OXmed3+hUBDP8w5FI91M0rdcyLLILElOCbaZilSWeXMncRx4klTCY1spfG3dhZJWx3GcDUR0EEB3ZMw0ET2gtT6SZWWzjmlrBIJCl0hAKfWgZVmHszqXZVxbCSxpCS2JJA6umIhe8ZKKVLPbaBJ+S9toqVRa53nedgAbAKwIwH4FcAzAa0R0l4i8F7PPz189k6RFRA+LyNcAXojDV0oNW5b1eW4Cxpg9AHZkSaaa6hhzb065uDSCH2LmRB8Sk9gY4293g43Qo/1pV80m8yQMfZSZ781cB1zXHRKRZ2IMpgD8A+DamL4ZItqitX4/jbQx5iEA7wLoihn3V/ACckWMJN/QWj9b1x5tGBsbW6uUOh5pPy0iL3Z2dn6ilJqanp5ep5TaJSLhF4NppdRNaU8gPmapVLrO87yfIoXuWyJ6uVKp+HmFjo6OQSJ6FcBtYT+UUmstyxqvkWuUgDFmP4AnQu2/e563qlgs+u9DNZ8xZhRAX7VRRPbath0XuXk7Y8xeAE+FgL6fnJzsHRwcLIfBR0ZGLunq6poAsDLUvp+Zw7b1r9PGmJMAbg8Z7WDmoThZuK67WkS+DD18fcPMdzSQUBR/EzN/nIC/SUQ+DPXV4dclsTHmHAD/SfHCNzc3t7Kvr++HJKeMMacA3BL0nyuXyzcPDAxMxo0fHR29slAo/Ajg6qD/fE9Pzw29vb1/x42fmJi4vFwu+5G/LOg/y8zXNJLQ2dAES5JANMQ7mfn1jBI6ycx3NiMhItqstf4oAX+ziHwQ6qvDj5NQNIn/ALCKmX+JSeIvABRD7fuY+ekGBPYBeDI05tTMzExvf3+/vz2Hk91/ET8RSeI6/DoCpVJpjed5fmKGvzMAXpqdnT3oed5Ud3d3v4jsAqBr9Ei0Rmv9VRqBBPzvROQVETnq2xJRdRu9tRF+bCVOKWT+Kvl/TSIFk6SW/LAjKfjV5K8rZABi8dOOEv7FI7Z8x6zwEWbemLbyMfJr5qiSiJ96oclymBOR3bZtP9+M89WxxpjdAHY2sN3DzM8ljWl4I3Nd9x7/OE1ENcdpETnmH3e11n41zv0l4J8RkU+J6AAz+xtF4teQQG7PFslwmcAiLfSyhC72Qv9/I/Avns2OT7QJskoAAAAASUVORK5CYII=\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-screenshot:hover{background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAED0lEQVRoQ+2ZycsdRRTFf2ejqHFAMQqiYBTUoElUHLNx3GgCgpuYRF2o0UT9CxwQ/BMkMSbZSKLiQgQHUDCJgjiAxiEiESdEcJbEedgcKaj3UV+/6q7u/jovPPkK3qbr1ql76p5bt6qemPKmKfefeQKHOoLFCNg+H1gi6fFJOmv7VmCvpD1N87Yh8ApwNXCzpB2TIGF7DRDm2inpmt4EbB8LfAMcGUHWSHryYJKwfRMwmuMP4BRJv9TN2RgB2wuB72BWsq+V9MTBIGF7NZBiGzhJ0o+9CIRBtt8FLqgADC6nRDbpVO9Iuqi3hCKB5cDrGZDVkp4aIhIV2aSQyyW9MScCkcQqIOfsnCORkc3I31b5VtyFRmg1IQ7dt0ja3icSQ2C2JhAjUU2ykd+dE7tBNp2i2olAJJFuc+nCt564QTadF6IzgUhiVGiqyinKaQjZpJP2ItBXTkPJZhACXeU0pGwGI9BWTkPLZlACBTldG4o5EA6E1dY66edcyNrs8Q36zg1vVaTazNs7iXPgDVJJzYs7VRvHRzaDEohyugJ4CTi84sg/wHWSdnVxsGQ7aQLXS9pZcqpL/6AEplpCU5HE8YpJ9YrXUKQ6baN1+HPaRm1fBqwFQnKGK2ZoPwCvAo8Ai4FnMpPMHMwapHUj8DFwbw3+Dklv9iZgexOwvktSRduxU2VDlErwmyXV+lCbxLbDdndlCT3TX3vV7JgnKfRuSVflfMkSsL0ZuDMz4E/gL+CETN+/wCpJzzaRtn0D8DRwWMbu1/gCcnSm7zFJd1W/jxGwvQx4r2IYnlbuA14GAomQFw8B6YtBKFSnNj2BxEJ3IvB1pdB9CjwQ8yqYhcg/DJxZ8WOZpA/SbzkC24DbEqOfgPMkBRKzmu23gEuSj1sk5SI3Y2J7C3BHMuZz4FxJf6fgto8APgIWJd+3SUrHjr9O294HnJUMWi8pSGqs2V4CvJ88fH0i6eyChKr4KyS9WIO/Ang+6RvDz0XgABCeFEdtkaQv65yy/QVweuwPY0+T9FuNQ8cAXwHHxf7wdHiypN9r7BfEl8GjYv9+SceXJLQ/mSDYTh2Baog3SHq0pYT2STqno4RWSnqhBn8l8FzSN4bfJol/jkn8bXUS228DFyfft0paVyCwFbg9sQkSDEkctueZZju8iO+tJPEYfo7A0piYKd73wP3xnB+20cvjNnphxdmlkj4sEMjhfwY8COyOY0fb6Bkl/K6FLKxS+M1KpDhJY8mvrG5doRwlf66QZfGbjhLh4pEt35kV3iUp/IvTunU8qtTil/7gaHOY2yjpntaez9b5RmBDYewmSXfX2RRvZLYvbThOh+NuqMa9Ww1+yLnXgO2SwkZR24oEens2oYHzBCa00PMSOtQL/f+NwH+Hg8hAnbrYgQAAAABJRU5ErkJggg==\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-play{background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACgklEQVRoQ+3ZPYsTQRjA8eeZZCFlWttAwCIkZOaZJt8hlvkeHrlccuAFT6wEG0FQOeQQLCIWih6chQgKgkkKIyqKCVYip54IWmiQkTmyYhFvd3Zn3yDb7szu/7cv7GaDkPEFM94PK0DSZ9DzDAyHw7uI2HRDlVJX5/N5r9FoHCYdr/fvCRiNRmpJ6AEidoUQ15NG+AH8BgD2n9AHANAmohdJQfwAfgGA4xF4bjabnW21Whob62ILoKNfAsAGEd2PU2ATcNSNiDf0/cE5/xAHxDpgEf0NADaJ6HLUiKgAbvcjpdSGlPJZVJCoAUfdSqkLxWLxTLlc/mkbEgtgET1TSnWklLdtIuIEuN23crlcp16vv7cBSQKgu38AwBYRXQyLSArg3hsjRDxNRE+CQhIF/BN9qVAobFYqle+mkLQAdLd+8K0T0U0TRJoAbvc9fVkJId75gaQRoLv1C2STiPTb7rFLWgE6+g0RncwyYEJEtawCvjDGmpzzp5kD6NfxfD7frtVqB17xen2a7oG3ALBm+oMoFQBEPD+dTvtBfpImDXjIGFvjnD/3c7ksG5MU4HDxWeZa0HB3XhKAXcdxOn5vUi9gnIDXSqm2lHLPK8pkfVyAbSLqm4T5HRs1YB8RO0KIid8g03FRAT4rpbpSyh3TINPxUQB2GGM9zvkn05gg420CJovLZT9ISNA5tgB9ItoOGhFmnh/AcZ/X9xhj65zzV2Eiwsz1A1j2B8dHAOgS0W6YnduY6wkYj8d3lFKn/j66Ea84jtOrVqtfbQSE3YYnYDAY5Eql0hYAnNDv6kKIx2F3anO+J8DmzqLY1goQxVE12ebqDJgcrSjGrs5AFEfVZJt/AF0m+jHzUTtnAAAAAElFTkSuQmCC\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-play:hover{background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACEElEQVRoQ+2ZXStEQRjH/3/yIXwDdz7J+i7kvdisXCk3SiFJW27kglBcSFFKbqwQSa4krykuKB09Naf2Yndn5jgzc06d53Znd36/mWfeniVyHsw5PwqB0DOonYEoijYBlOpAFwCMkHwLDS/9mwhEDUCfAAyTXA4tYSLwC6CtCegegH6S56FETAR+AHRoACcBTJAUWa+RloBAXwAYIrnt0yBNgZi7qtbHgw8RFwLC/QFglOScawlXAjH3gUqrE1cirgVi7mkAYyS/0xbxJSDcdwAGSa6nKeFTIOZeUyL3aYiEEBDuLwDjJGf+KxFKIOY+BdBL8iipSGiBmHtWbbuftiJZERBuOfgGSK7aSGRJIObeUml1ayKSRQHhlgtkiaTcdltGVgUE+ppkV54FaiS78yrwqlLoOI8Cch2XV548W7WRpTVwA6DP9kGUFYEpAOUkT9LQAvtq1M+0udKkQSgBqSlJWWYxKXj8vRACK+o6bbRIdYI+Ba7U7rKjg7L53JdAhWTZBsy0rWuBXZUuNVMg23auBF7UIl2yBbJt70JAoKV6/WwLk6R9mgKSJlJ1kLTxFmkJyCla8UZd15GJQKvyumyJ8gy8DAEvfZoINPqD41EtUjmUgoaJwAaAnjrKebVI34OSq85NBNqlCAWgE0CV5GEWwI3vQlmCbcSinYFCwPEIFDPgeIC1P1/MgHaIHDf4Aydx2TF7wnKeAAAAAElFTkSuQmCC\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-pause{background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAABA0lEQVRoQ+1YwQqCUBAcfWXXsLr2AXWTPXno8yVB8AP6Aa3oHI+kCDqYaawJljSe133uzO44bx0M/HEG/v1gAd9mkAyQgY4I/F8LJUlyrQFtD2AtIkcNoFEU+Z7n7QD4DfFHEVlocrVmgAUAIAOl3mILPcDgEFcUhyrUKMGUUcroc3NQRimj9XJBGaWMvvPydKN0o6/9QTdKN6rZANxj6EbpRulGuZnjYqs8BbyR8Ub2Izeys+u6yyAIDpo/ehzHM2NMDsA0xFsRmWhyfTIDWSXxCEBmrd2EYXjSHJqm6bQoii2AOYBL5Z0xgFxEVppcrQvQJO0zhgX0iXbdWWSADHRE4AZQ731AhEUeNwAAAABJRU5ErkJggg==\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-pause:hover{background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAA7klEQVRoQ+2YSwrCQBBEX6HiVvxsPYDewfN7By/gD9ciQkvERQwJdBSiYs0mEDo96aruombEjy/9+P/jAj7NoBkwA28i8H8tFBFRA9oeWEo6ZgCNiDGwAYpn3TpKmmVytWbABQBmoNRbbqEHGB7iiuJYhRol2DJqGX1uDsuoZdRmLuNZSzGWUcuoZdRHSp/IylNgK2ErYSthK3FHwLcSvpXIjoLt9Jfa6TMwl3TIMBkRE2AH9BriL5KGmVyvWIltJXEfKN6tJJ0ym0bECFgDU+Ba+WZQFCdpkcnVuoBM0i5jXECXaNftZQbMwJsI3AAPN3dAQflHegAAAABJRU5ErkJggg==\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-record{background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAC+UlEQVRoQ+1ZS2sTURT+zlDJYE3XSq219QHVuEjnJDT+Bff9Abqw2voAEfGxqygUqWhVFHGl/yMLu9BwByxk5SNI66ML6U7axjhHbmhgWiftncxoOiV3FcI53z3f/e65594zhIQPSnj86BBot4IdBToKRFyBnbeFlFIScVEiuYvIWC6Xe2YK8pcC7SYA4CMzH4mDQBXAqilQBDsLQLfPf9FxnF4i8kwwmypARI+Wl5dvmIBEsUmlUkNE9NaHsVCpVAZGR0d/m+A2JSAid3K53E0TkCg2pVKpz7KseR/GfKVSGYxMAMA0M1+JEpyJb6lUOm5ZVnkrAsVisaunp+esiByr1Wp3R0ZGvmifzZK4XQQWHMc52MgBpdQuAOcAXABwuB400ZTjONdaIjA7O5u2bVsnWU1EujzP+5nP5xdMVjvIJkCBD8x8VCm1G8AYgAkAAxt8Z5j5YmgCSqlTAJ4D2OcD/AXgATNfbYVEAIFPIvKKiE4D6GuCea8xX6gtpJT6DmBvECgRFRzHeROWRAABE4iWCbwHEFhkPM/L5vP5dyaz+23+KwHXdR3P854S0YG1ILSCuthNMfNM2OC1/RYENLY+ygcBnPfht6ZAA6BYLNr6dyqVokKhsGpaNQ2TWJstreXaE2aed133sojcj41AKyvdzCdAgSXLsk4MDw9/a/i4rntbRPxFNZoC/5jAV2be759DKTUJ4FZSFFi0bbs/k8noy2R9dAjEuWU2YgXkQOK3kD6BMsysi2Z9JC2Jdcw/ALzwPO+xvmcl7Rj177JVEbkO4BARjSflFDJJuW1dBxJPoCIiL4noDIB1BS0pW6j+oJmbm+uuVqvjRKQfLr0bZHnIzJf0f6HeAybahrUJqAPruhLlcnnPysqKfpXp11n/Gv62zoHAroS+AafT6QkiGrIsazKbzX7eVIHEt1US39gCkOzWYthkjNE+tuZujDGZQ8XRXn8N4KT5lLFZ6uaYPt+nwyDuvC80YdhvB9uOAu1WoaNAR4GIK/AHvdr+QAexB7EAAAAASUVORK5CYII=\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-record:hover{background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACfUlEQVRoQ+2ZSYsUQRCFvycK4nJXXEbHBdwO4kn/gv9CD467ICIutxEFkREdFUU86T/xojcPntyQcT2INw+uISFVkD1Wd2dWlU7nUHlqisiX+fJFZGREi8yHMt8/HYG5VrBToFOg4QnMPxcyM2t4KE2nT0i6EwvylwIjQOCFpE1tEPgGfI0FamC3AFgazP8IrJL0KwZzkAI3gLMxIA1ttgCPA4w3wHpJP2NwBxG4KOlcDEgTGzNbA8wEGP57vA0CU5JONtlczFwz2wY8HUbAzBYCB4CtwCVJb33OIAXmioC70LoyBsxsEXAQOApsLIhelnS6FgEzW+5BBvwA/FS+SPJFa40KBZ5L2mxmS4AJ4IjHxCzwaUnHkgmY2V7gLrAyAPwOXJN0qg6DCgIvgQfAPsDjo2pcKddLciEz+wCs6AO6W9KjVBIVBGIgahN4BvRLMjslPYlZPbT53wR2AbeBtcUmXEFPdh5U06mbd/shBBzbr/Jx4FCAX0+BEsDMFocEYrNmFcE+BD4XsXZL0oyZnQCutkagzkn3m1NBwDe/Q9L74MAuFEqUn5op8I8JvJO0elacTALnc1HAH3Njkvwx+WeYWUegTa/pwaqIgexdyIN4uyRPmqULZRXEvulPwD3gpr+zcrtGQxfzRHYG2AAczuUWiom3kc4D2RN4BdwH9gM9CS0XFyoLGu9UuN974eIFVDiuSzruH5LqgRhtU20q8kBPV8LMlhVVmVdnYwX+SMdAZVeieAF7eeltmElJr4cpkH1bJfvGVvatxdR4bMu+teZuWxtKxWncXn8I7EldtQV7vz79fp9KwZp//9CksB8F206BuVahU6BToOEJ/Ab7+KdABdTt8AAAAABJRU5ErkJggg==\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-recordStop{background:url(\"data:image/png;base64,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\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-recordStop:hover{background:url(\"data:image/png;base64,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\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-fullscreen{background:url(\"data:image/png;base64,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\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-fullscreen:hover{background:url(\"data:image/png;base64,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\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-fullscreenExit{background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAADd0lEQVRoQ+2Zz2sdVRTHv+fJBDW6anDVXen6wZszYxYBiYgtFGst3VSDunKjpS0GpUlqfjVpsVVs6aaL0or4YxMVFCJZ2ZLdPUP+gq5bQnTxtNAkfTnlhnnlkmQy9yV9780rudt77tzv5/y4v4bQ4Y06XD/2ANodwec/AiJygJnvtdvTWfPnRkBEJAiCN8rl8kMfiPn5+Ve7u7v3rays0Orq6lJfX99/PuN2auMDoAD+BvA2M6/mTWSMOUtE48D6AjHGzN/kjdlNvy+AnWOOmQ/lTSYiEwDOWzsimgrDcCRvzG76GwGw8/zJzO9sN6GInAMwbW1UdSSKoqndCMwb6wNwGsB39Q+p6h/M/C4R2dTa1AoHYBWKyCkA1+pqiWi2Wq0e7e/vf7yRoJAAKcQggMtuJKIoOtoxACnE0/xOi/SXMAxPuhCFjUBdpIjYVWXSEf0TM3/g9BeriDMKdSPEz8z8vrU1xgwT0YXCrEJZy1iSJKOqOub0/8jMA0mSfKKqNwoPkHp7ioiGHIhRIvpHVa93BEBa2JcAfOlALAHo6RgAKzRJkk9V1S6xL7kpV4idOM31taxaIKJHqmpPnMMA9hcOQES2PDJkAT1XAAC+ZebPfWB3auNzmLObVsNRUNUXVHUujuM7OxXnMy4XwOcj29mIyOuq+lapVGrYCelKpkEQ3CyXy4tbzdN0AGPMxr2iYZ+sra3FcRybtgCIiK2BKw2rdgaUSqWoUqlIkQAepFDdAF7cBq5ERI9rtdr1OI7tmE2t6SmUEYFHAEaexYW/1QC2EF+ru5GIvg7D0D2GNJxprQY4o6qv1I/b6SpzOYqiLxpWng5oOQAzXxWRWwA+dkRfYOb1p5hGW6sBJpn5KytSRG4D+KguWFXHoyhy7xdeLC0F2ChSRL4H8OFuINoKYIUbY34gogHH3eeZef1K6tPaDpCm068A3nMEDzHzxY4BUNWSiPxORO6z5aDPPlGICNQ9bYyZIaLjjudzIQoFkKbTbwCO+UI0HcB9J/LdeY0xs0R02IGYYObRrWqiFQCfEZEtSHsfmGZm+4qxbbM/hQD8BeBNa0hEM2EYnmgLgP3lFARBT1dXly4vL//b29tbzQNIU+llAHeJaLFSqRzJes5vegR8xGbZLCwsHKzVav8z8/0sm0ID+MDvAfh4qZk2exFopnd9vv0ELrXBQO7fD10AAAAASUVORK5CYII=\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-fullscreenExit:hover{background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAC/ElEQVRoQ+2Zy49NQRCHvx+ReK6IlZ34E7CUiCAR4xEbTLCyQRATYswwb2IQZDYWgojHZpCQECts+ResiQwLj0RClNSkb9Lu3HtPz7mZc8+V6eXt6tP1VVV3VdcVbT7U5vozC9BqD/7/HjCzlZLet9rS9fbP9ICZvQPWSfqRAmFmS4ClMHm+JiR9S1mXVyYFwIBXwEZJv7I2MrPjQH8A6JN0OWtNM/OpAL7HS0mbsjYzswGgN8gNS+rJWtPM/HQAfJ9nkrY22tDMTgMjQaZH0nAzCmatTQE4ClyNPvQU2CbJQ2vKKB2Aa2hmR4DrkbbPgQ5Jv6sJSgkQILqA0dgTkjraBiBAxPHtPz2UtDuGKK0HKkqamd8qg5HS9yXtjebLdYjrHNRqiAeS9gQvnQGGSnML1bvGzOwc0BfN35PUaWYHgRulBwjW9ju+O4JwqM/AWFsABIgLwKkIYgJY1jYAAeJQuGIXVIVcKTKxh8WfBin9J+AVpx/eFWUEqFkyNACKp0rhgWYArkg6kQibSyylmPOklQdibijBX+fSLHFRJkDid+qKmdlaYENOI0zeEcBNSZ9qbVIEQHWuyGOTNZLetgrAz8ClPFpHa1ZL8rf5lFGEB2oBfAxQi4D5DeDmAP7mGJPka0oD4LnDr9imH/xFe8AP4vLIjBclxWXItCOtaIBjwOKo3HaFRyWdnLbmYUHhAJKumdkt4ECk9JCkSitmWixFAwxKOjt5uZvdBvZH2vZLit8XSSBFA/yjpJndAfY1A9FSgOCJu0BnBNErqfIkzfRCywECxCNgR6Rtt6TzmdqHBmyKXG4ZM4sTWc04NzNPWE+AuG3ZlZInSuGBinXMbBzYGVkrE6JUACGcHgPbUyGKAIj7REmZ18y897o5ghiQ5E/bltRChwE/kF7Xj0jyLkbDYWbzgBfA+iA4LmlXqwD8LydvszjAF0lfswBCKC0E3gBeP22p186f8RBKUbaejJmtAr5L+lBPptQAKfCzAClWmkmZWQ/MpHVTvv0X9iFAQGQyevIAAAAASUVORK5CYII=\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-audio{background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACrUlEQVRoQ+2ZPYgTURCAZzbBXJnCeL2Cnb87b9MEtPBUrrMQFAtrtT5/ClGs9LBWWz0RtbBUFCF4oJDsbO68wsLA2YqQSmLlvpEHu7IuMdlLcus+yUKKhJfZ+ebnvZl5CJY/aLn+MAP41x7M1QPMfFtr/crzvHfTAs8FoNPp1LTWzwHgqIg0lFLvrQHwfX8BER8DwC6jNCIecF13wwoA3/dvIuKNpLJa60Oe560XGoCZd4rICiKeTCtaeABmPg4AJmRqg6xcaABmvg4At4aFRyEBhoVM4UMoCplHADCfJTEL5YEsIVNID5iQAYCHALCYxeq5b6PMfF5EBAAEESthGK7W6/XPRpFWq7W3VCqtZg2ZcT3g+/6i4zjzIlLSWn/yPO/DIGMNLCWY2Sj/+xGRK0qpZfNDEASnROTFVi0fr8+aA8z8Ld6KEfGt67oLYwMAwEUium8EREn7OgeAjwCwPyo/nrque3YSgAtE9GDaAM1mc65arc4Zuf1+P2w0Gt9jJZl5DQAORt+fENG5wgEw8zUAMB/zbBBRwyqAIAjuiMjlSOlNItpjFUCqWl0josMzgChR/9hGAWBbknjmAdPhDdqa0gfZzAMJKyVP4v8hhJYRcSni+0JEu63ahZj5anyQici6UuqIVQDdbrfS6/UqRulyufyTiH5sF8AlIro37VpoWEHIzGZ2tM+sEZFnSqkzk9RCS0R01wjIsZz+mug53hDRia0AnI4bGgDYISItz/M2jYC8Gpp2u30MEWuO4zha665Sqp0ZYFStX/iWchRAItFGzoHSsrJ2ZFl1mHg6bfVYJeGJv85CC++BpIJZ5kSFC6G0ha0e7mYJqcJ7IOkRay84UhD2XjHFIFZf8iW9YcYoYRi+tO6aNeupOs66iU/icV46zf/MAKZpzXFk/QL+JG1PUPhRiQAAAABJRU5ErkJggg==\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-audio:hover{background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAACSElEQVRoQ+2Zu4sUQRCHf5+C+gf4yBXMfMYHGvjCzEBQDIzV+HwEohipGKupD0QNDE8UEwUFTe68wEDhTMVUMFJ+0tArzbjs9u3Ojt0wBR0M9MzUV1XdXVWNKhcq1189wP/2YKcesH1d0nPgdVvgnQDY3iTpqaT9kuaAt9UA2D4o6aGkzVHpXcByFQC2r0q60lB2D7BUNIDtjZIeSDoyRNGyAWwfiiET4n6YlAtg+7Kka2PCozyAMSHT5CkLIIbMfUlbMhdmOQCZIVOeB2LI3JN0NNPq6bTZe8D2aUmOY72kN8DnoIXt7eF5FSEzkQdsB+OEsFwr6RPwbpixhqYStoPyqVwAbkaAY5KeTWD5wStZHrD9XdJgK34FhBP9H8kFOAvciQBhn3/RAcBHSTvjfx4DJ6cBOAPcbRvA9gZJYQT5DfwYKGl7UdLu+PwIOFUiwCVJYQRZBuZqA7gh6XxUegXYVhtAmq0uAnt7gLhQm9vorBZx74Hcc6D3QLKH/z2JGyVnlYs4pCfzEe4rsLW2XehicpAtAftqAwiZbhhBfgE/ZwVwDrjddi40KiG0HXpHO+KcJ8CJaXKheeBWBOgqnf6W1BwvgcOrATieFDTrJL0HViJAVwXNgVgPrJH0BfiQDTDKtREiNK7KLSnHASQLLacP1PxcVkWWq8PU3emq2yqJJ0b1Qsv2QKpdZp+orBBqmrfq5m5mSJXtgUZI1XnB0YCo94opCal6L/ka3ghtlIXqrllzT9VJ5k19Ek/y0zbf6QHatOYk3/oDujC8QMWgjf4AAAAASUVORK5CYII=\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-mute{background:url(\"data:image/png;base64,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\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-mute:hover{background:url(\"data:image/png;base64,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\") no-repeat 50%;background-size:100% 100%}.jessibuca-container .jessibuca-icon-text{font-size:14px;width:30px}.jessibuca-container .jessibuca-speed{font-size:14px;color:#fff}.jessibuca-container .jessibuca-quality-menu-list{position:absolute;left:50%;bottom:100%;visibility:hidden;opacity:0;transform:translateX(-50%);transition:visibility .3s,opacity .3s;background-color:rgba(0,0,0,.5);border-radius:4px}.jessibuca-container .jessibuca-quality-menu-list.jessibuca-quality-menu-shown{visibility:visible;opacity:1}.jessibuca-container .icon-title-tips{pointer-events:none;position:absolute;left:50%;bottom:100%;visibility:hidden;opacity:0;transform:translateX(-50%);transition:visibility .3s ease 0s,opacity .3s ease 0s;background-color:rgba(0,0,0,.5);border-radius:4px}.jessibuca-container .icon-title{display:inline-block;padding:5px 10px;font-size:12px;white-space:nowrap;color:#fff}.jessibuca-container .jessibuca-quality-menu{padding:8px 0}.jessibuca-container .jessibuca-quality-menu-item{display:block;height:25px;margin:0;padding:0 10px;cursor:pointer;font-size:14px;text-align:center;width:50px;color:hsla(0,0%,100%,.5);transition:color .3s,background-color .3s}.jessibuca-container .jessibuca-quality-menu-item:hover{background-color:hsla(0,0%,100%,.2)}.jessibuca-container .jessibuca-quality-menu-item:focus{outline:none}.jessibuca-container .jessibuca-quality-menu-item.jessibuca-quality-menu-item-active{color:#2298fc}.jessibuca-container .jessibuca-volume-panel-wrap{position:absolute;left:50%;bottom:100%;visibility:hidden;opacity:0;transform:translateX(-50%) translateY(22%);transition:visibility .3s,opacity .3s;background-color:rgba(0,0,0,.5);border-radius:4px;height:120px;width:50px;overflow:hidden}.jessibuca-container .jessibuca-volume-panel-wrap.jessibuca-volume-panel-wrap-show{visibility:visible;opacity:1}.jessibuca-container .jessibuca-volume-panel{cursor:pointer;position:absolute;top:21px;height:60px;width:50px;overflow:hidden}.jessibuca-container .jessibuca-volume-panel-text{position:absolute;left:0;top:0;width:50px;height:20px;line-height:20px;text-align:center;color:#fff;font-size:12px}.jessibuca-container .jessibuca-volume-panel-handle{position:absolute;top:48px;left:50%;width:12px;height:12px;border-radius:12px;margin-left:-6px;background:#fff}.jessibuca-container .jessibuca-volume-panel-handle:before{bottom:-54px;background:#fff}.jessibuca-container .jessibuca-volume-panel-handle:after{bottom:6px;background:hsla(0,0%,100%,.2)}.jessibuca-container .jessibuca-volume-panel-handle:after,.jessibuca-container .jessibuca-volume-panel-handle:before{content:\"\";position:absolute;display:block;left:50%;width:3px;margin-left:-1px;height:60px}.jessibuca-container.jessibuca-fullscreen-web .jessibuca-controls{width:100vh}.jessibuca-container.jessibuca-fullscreen-web .jessibuca-play-big:after{transform:translate(-50%,-50%) rotate(270deg)}.jessibuca-container.jessibuca-fullscreen-web .jessibuca-loading{flex-direction:row}.jessibuca-container.jessibuca-fullscreen-web .jessibuca-loading-text{transform:rotate(270deg)}\n/*# sourceMappingURL=data:application/json;base64,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eWZyYW1lcyByb3RhdGlvbiB7XG4gIGZyb20ge1xuICAgIC13ZWJraXQtdHJhbnNmb3JtOiByb3RhdGUoMGRlZyk7IH1cbiAgdG8ge1xuICAgIC13ZWJraXQtdHJhbnNmb3JtOiByb3RhdGUoMzYwZGVnKTsgfSB9XG5cbkBrZXlmcmFtZXMgbWFnZW50YVB1bHNlIHtcbiAgZnJvbSB7XG4gICAgYmFja2dyb3VuZC1jb2xvcjogIzYzMDAzMDtcbiAgICAtd2Via2l0LWJveC1zaGFkb3c6IDAgMCA5cHggIzMzMzsgfVxuICA1MCUge1xuICAgIGJhY2tncm91bmQtY29sb3I6ICNhOTAxNGI7XG4gICAgLXdlYmtpdC1ib3gtc2hhZG93OiAwIDAgMThweCAjYTkwMTRiOyB9XG4gIHRvIHtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjNjMwMDMwO1xuICAgIC13ZWJraXQtYm94LXNoYWRvdzogMCAwIDlweCAjMzMzOyB9IH1cblxuLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1pY29uIHtcbiAgY3Vyc29yOiBwb2ludGVyO1xuICB3aWR0aDogMTZweDtcbiAgaGVpZ2h0OiAxNnB4OyB9XG5cbi5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtcG9zdGVyIHtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICB6LWluZGV4OiAxMDtcbiAgbGVmdDogMDtcbiAgdG9wOiAwO1xuICByaWdodDogMDtcbiAgYm90dG9tOiAwO1xuICBoZWlnaHQ6IDEwMCU7XG4gIHdpZHRoOiAxMDAlO1xuICBiYWNrZ3JvdW5kLXBvc2l0aW9uOiBjZW50ZXIgY2VudGVyO1xuICBiYWNrZ3JvdW5kLXJlcGVhdDogbm8tcmVwZWF0O1xuICBiYWNrZ3JvdW5kLXNpemU6IGNvbnRhaW47XG4gIHBvaW50ZXItZXZlbnRzOiBub25lOyB9XG5cbi5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtcGxheS1iaWcge1xuICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gIGRpc3BsYXk6IG5vbmU7XG4gIGhlaWdodDogMTAwJTtcbiAgd2lkdGg6IDEwMCU7XG4gIGJhY2tncm91bmQ6IHJnYmEoMCwgMCwgMCwgMC40KTsgfVxuICAuamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLXBsYXktYmlnOmFmdGVyIHtcbiAgICBjdXJzb3I6IHBvaW50ZXI7XG4gICAgY29udGVudDogJyc7XG4gICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgIGxlZnQ6IDUwJTtcbiAgICB0b3A6IDUwJTtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtNTAlLCAtNTAlKTtcbiAgICBkaXNwbGF5OiBibG9jaztcbiAgICB3aWR0aDogNDhweDtcbiAgICBoZWlnaHQ6IDQ4cHg7XG4gICAgYmFja2dyb3VuZC1pbWFnZTogdXJsKFwiLi4vYXNzZXRzL3BsYXkucG5nXCIpO1xuICAgIGJhY2tncm91bmQtcmVwZWF0OiBuby1yZXBlYXQ7XG4gICAgYmFja2dyb3VuZC1wb3NpdGlvbjogY2VudGVyOyB9XG4gIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtcGxheS1iaWc6aG92ZXI6YWZ0ZXIge1xuICAgIGJhY2tncm91bmQtaW1hZ2U6IHVybChcIi4uL2Fzc2V0cy9wbGF5LWhvdmVyLnBuZ1wiKTsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLXJlY29yZGluZyB7XG4gIGRpc3BsYXk6IG5vbmU7XG4gIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgbGVmdDogNTAlO1xuICB0b3A6IDA7XG4gIHBhZGRpbmc6IDAgM3B4O1xuICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVgoLTUwJSk7XG4gIGp1c3RpZnktY29udGVudDogc3BhY2UtYXJvdW5kO1xuICBhbGlnbi1pdGVtczogY2VudGVyO1xuICB3aWR0aDogOTVweDtcbiAgaGVpZ2h0OiAyMHB4O1xuICBiYWNrZ3JvdW5kOiAjMDAwMDAwO1xuICBvcGFjaXR5OiAxO1xuICBib3JkZXItcmFkaXVzOiAwcHggMHB4IDhweCA4cHg7XG4gIHotaW5kZXg6IDE7IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1yZWNvcmRpbmcgLmplc3NpYnVjYS1yZWNvcmRpbmctcmVkLXBvaW50IHtcbiAgICB3aWR0aDogOHB4O1xuICAgIGhlaWdodDogOHB4O1xuICAgIGJhY2tncm91bmQ6ICNGRjFGMUY7XG4gICAgYm9yZGVyLXJhZGl1czogNTAlO1xuICAgIGFuaW1hdGlvbjogbWFnZW50YVB1bHNlIDFzIGxpbmVhciBpbmZpbml0ZTsgfVxuICAuamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLXJlY29yZGluZyAuamVzc2lidWNhLXJlY29yZGluZy10aW1lIHtcbiAgICBmb250LXNpemU6IDE0cHg7XG4gICAgZm9udC13ZWlnaHQ6IDUwMDtcbiAgICBjb2xvcjogI0RERERERDsgfVxuICAuamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLXJlY29yZGluZyAuamVzc2lidWNhLWljb24tcmVjb3JkU3RvcCB7XG4gICAgd2lkdGg6IDE2cHg7XG4gICAgaGVpZ2h0OiAxNnB4O1xuICAgIGN1cnNvcjogcG9pbnRlcjsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWxvYWRpbmcge1xuICBkaXNwbGF5OiBub25lO1xuICBmbGV4LWRpcmVjdGlvbjogY29sdW1uO1xuICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjtcbiAgYWxpZ24taXRlbXM6IGNlbnRlcjtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICB6LWluZGV4OiAyMDtcbiAgbGVmdDogMDtcbiAgdG9wOiAwO1xuICByaWdodDogMDtcbiAgYm90dG9tOiAwO1xuICB3aWR0aDogMTAwJTtcbiAgaGVpZ2h0OiAxMDAlO1xuICBwb2ludGVyLWV2ZW50czogbm9uZTsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWxvYWRpbmctdGV4dCB7XG4gIGxpbmUtaGVpZ2h0OiAyMHB4O1xuICBmb250LXNpemU6IDEzcHg7XG4gIGNvbG9yOiAjZmZmO1xuICBtYXJnaW4tdG9wOiAxMHB4OyB9XG5cbi5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtY29udHJvbHMge1xuICBiYWNrZ3JvdW5kLWNvbG9yOiAjMTYxNjE2O1xuICBib3gtc2l6aW5nOiBib3JkZXItYm94O1xuICBkaXNwbGF5OiBmbGV4O1xuICBmbGV4LWRpcmVjdGlvbjogY29sdW1uO1xuICBqdXN0aWZ5LWNvbnRlbnQ6IGZsZXgtZW5kO1xuICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gIHotaW5kZXg6IDQwO1xuICBsZWZ0OiAwO1xuICByaWdodDogMDtcbiAgYm90dG9tOiAwO1xuICBoZWlnaHQ6IDM4cHg7XG4gIHdpZHRoOiAxMDAlO1xuICBwYWRkaW5nLWxlZnQ6IDEzcHg7XG4gIHBhZGRpbmctcmlnaHQ6IDEzcHg7XG4gIGZvbnQtc2l6ZTogMTRweDtcbiAgY29sb3I6ICNmZmY7XG4gIG9wYWNpdHk6IDA7XG4gIHZpc2liaWxpdHk6IGhpZGRlbjtcbiAgdHJhbnNpdGlvbjogYWxsIDAuMnMgZWFzZS1pbi1vdXQ7XG4gIHVzZXItc2VsZWN0OiBub25lO1xuICB0cmFuc2l0aW9uOiB3aWR0aCAuNXMgZWFzZS1pbjsgfVxuICAuamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWNvbnRyb2xzIC5qZXNzaWJ1Y2EtY29udHJvbHMtaXRlbSB7XG4gICAgcG9zaXRpb246IHJlbGF0aXZlO1xuICAgIGRpc3BsYXk6IGZsZXg7XG4gICAganVzdGlmeS1jb250ZW50OiBjZW50ZXI7XG4gICAgcGFkZGluZzogMCA4cHg7IH1cbiAgICAuamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWNvbnRyb2xzIC5qZXNzaWJ1Y2EtY29udHJvbHMtaXRlbTpob3ZlciAuaWNvbi10aXRsZS10aXBzIHtcbiAgICAgIHZpc2liaWxpdHk6IHZpc2libGU7XG4gICAgICBvcGFjaXR5OiAxOyB9XG4gIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtY29udHJvbHMgLmplc3NpYnVjYS1taWNyb3Bob25lLWNsb3NlIHtcbiAgICBkaXNwbGF5OiBub25lOyB9XG4gIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtY29udHJvbHMgLmplc3NpYnVjYS1pY29uLWF1ZGlvIHtcbiAgICBkaXNwbGF5OiBub25lOyB9XG4gIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtY29udHJvbHMgLmplc3NpYnVjYS1wbGF5IHtcbiAgICBkaXNwbGF5OiBub25lOyB9XG4gIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtY29udHJvbHMgLmplc3NpYnVjYS1wYXVzZSB7XG4gICAgZGlzcGxheTogbm9uZTsgfVxuICAuamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWNvbnRyb2xzIC5qZXNzaWJ1Y2EtZnVsbHNjcmVlbi1leGl0IHtcbiAgICBkaXNwbGF5OiBub25lOyB9XG4gIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtY29udHJvbHMgLmplc3NpYnVjYS1zY3JlZW5zaG90IHtcbiAgICBkaXNwbGF5OiBub25lOyB9XG4gIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtY29udHJvbHMgLmplc3NpYnVjYS1yZWNvcmQge1xuICAgIGRpc3BsYXk6IG5vbmU7IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1jb250cm9scyAuamVzc2lidWNhLWZ1bGxzY3JlZW4ge1xuICAgIGRpc3BsYXk6IG5vbmU7IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1jb250cm9scyAuamVzc2lidWNhLXJlY29yZC1zdG9wIHtcbiAgICBkaXNwbGF5OiBub25lOyB9XG4gIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtY29udHJvbHMgLmplc3NpYnVjYS1pY29uLWF1ZGlvLCAuamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWNvbnRyb2xzIC5qZXNzaWJ1Y2EtaWNvbi1tdXRlIHtcbiAgICB6LWluZGV4OiAxOyB9XG4gIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtY29udHJvbHMgLmplc3NpYnVjYS1jb250cm9scy1ib3R0b20ge1xuICAgIGRpc3BsYXk6IGZsZXg7XG4gICAganVzdGlmeS1jb250ZW50OiBzcGFjZS1iZXR3ZWVuO1xuICAgIGhlaWdodDogMTAwJTsgfVxuICAgIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtY29udHJvbHMgLmplc3NpYnVjYS1jb250cm9scy1ib3R0b20gLmplc3NpYnVjYS1jb250cm9scy1sZWZ0IHtcbiAgICAgIGRpc3BsYXk6IGZsZXg7XG4gICAgICBhbGlnbi1pdGVtczogY2VudGVyOyB9XG4gICAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1jb250cm9scyAuamVzc2lidWNhLWNvbnRyb2xzLWJvdHRvbSAuamVzc2lidWNhLWNvbnRyb2xzLXJpZ2h0IHtcbiAgICAgIGRpc3BsYXk6IGZsZXg7XG4gICAgICBhbGlnbi1pdGVtczogY2VudGVyOyB9XG5cbi5qZXNzaWJ1Y2EtY29udGFpbmVyLmplc3NpYnVjYS1jb250cm9scy1zaG93IC5qZXNzaWJ1Y2EtY29udHJvbHMge1xuICBvcGFjaXR5OiAxO1xuICB2aXNpYmlsaXR5OiB2aXNpYmxlOyB9XG5cbi5qZXNzaWJ1Y2EtY29udGFpbmVyLmplc3NpYnVjYS1jb250cm9scy1zaG93LWF1dG8taGlkZSAuamVzc2lidWNhLWNvbnRyb2xzIHtcbiAgb3BhY2l0eTogMC44O1xuICB2aXNpYmlsaXR5OiB2aXNpYmxlO1xuICBkaXNwbGF5OiBub25lOyB9XG5cbi5qZXNzaWJ1Y2EtY29udGFpbmVyLmplc3NpYnVjYS1oaWRlLWN1cnNvciAqIHtcbiAgY3Vyc29yOiBub25lICFpbXBvcnRhbnQ7IH1cblxuLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1pY29uLWxvYWRpbmcge1xuICB3aWR0aDogNTBweDtcbiAgaGVpZ2h0OiA1MHB4O1xuICBiYWNrZ3JvdW5kOiB1cmwoXCIuLi9hc3NldHMvbG9hZGluZy5wbmdcIikgbm8tcmVwZWF0IGNlbnRlcjtcbiAgYmFja2dyb3VuZC1zaXplOiAxMDAlIDEwMCU7XG4gIGFuaW1hdGlvbjogcm90YXRpb24gMXMgbGluZWFyIGluZmluaXRlOyB9XG5cbi5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtaWNvbi1zY3JlZW5zaG90IHtcbiAgYmFja2dyb3VuZDogdXJsKFwiLi4vYXNzZXRzL3NjcmVlbnNob3QucG5nXCIpIG5vLXJlcGVhdCBjZW50ZXI7XG4gIGJhY2tncm91bmQtc2l6ZTogMTAwJSAxMDAlOyB9XG4gIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtaWNvbi1zY3JlZW5zaG90OmhvdmVyIHtcbiAgICBiYWNrZ3JvdW5kOiB1cmwoXCIuLi9hc3NldHMvc2NyZWVuc2hvdC1ob3Zlci5wbmdcIikgbm8tcmVwZWF0IGNlbnRlcjtcbiAgICBiYWNrZ3JvdW5kLXNpemU6IDEwMCUgMTAwJTsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWljb24tcGxheSB7XG4gIGJhY2tncm91bmQ6IHVybChcIi4uL2Fzc2V0cy9wbGF5LnBuZ1wiKSBuby1yZXBlYXQgY2VudGVyO1xuICBiYWNrZ3JvdW5kLXNpemU6IDEwMCUgMTAwJTsgfVxuICAuamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWljb24tcGxheTpob3ZlciB7XG4gICAgYmFja2dyb3VuZDogdXJsKFwiLi4vYXNzZXRzL3BsYXktaG92ZXIucG5nXCIpIG5vLXJlcGVhdCBjZW50ZXI7XG4gICAgYmFja2dyb3VuZC1zaXplOiAxMDAlIDEwMCU7IH1cblxuLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1pY29uLXBhdXNlIHtcbiAgYmFja2dyb3VuZDogdXJsKFwiLi4vYXNzZXRzL3BhdXNlLnBuZ1wiKSBuby1yZXBlYXQgY2VudGVyO1xuICBiYWNrZ3JvdW5kLXNpemU6IDEwMCUgMTAwJTsgfVxuICAuamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWljb24tcGF1c2U6aG92ZXIge1xuICAgIGJhY2tncm91bmQ6IHVybChcIi4uL2Fzc2V0cy9wYXVzZS1ob3Zlci5wbmdcIikgbm8tcmVwZWF0IGNlbnRlcjtcbiAgICBiYWNrZ3JvdW5kLXNpemU6IDEwMCUgMTAwJTsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWljb24tcmVjb3JkIHtcbiAgYmFja2dyb3VuZDogdXJsKFwiLi4vYXNzZXRzL3JlY29yZC5wbmdcIikgbm8tcmVwZWF0IGNlbnRlcjtcbiAgYmFja2dyb3VuZC1zaXplOiAxMDAlIDEwMCU7IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1pY29uLXJlY29yZDpob3ZlciB7XG4gICAgYmFja2dyb3VuZDogdXJsKFwiLi4vYXNzZXRzL3JlY29yZC1ob3Zlci5wbmdcIikgbm8tcmVwZWF0IGNlbnRlcjtcbiAgICBiYWNrZ3JvdW5kLXNpemU6IDEwMCUgMTAwJTsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWljb24tcmVjb3JkU3RvcCB7XG4gIGJhY2tncm91bmQ6IHVybChcIi4uL2Fzc2V0cy9yZWNvcmQtc3RvcC5wbmdcIikgbm8tcmVwZWF0IGNlbnRlcjtcbiAgYmFja2dyb3VuZC1zaXplOiAxMDAlIDEwMCU7IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1pY29uLXJlY29yZFN0b3A6aG92ZXIge1xuICAgIGJhY2tncm91bmQ6IHVybChcIi4uL2Fzc2V0cy9yZWNvcmQtc3RvcC1ob3Zlci5wbmdcIikgbm8tcmVwZWF0IGNlbnRlcjtcbiAgICBiYWNrZ3JvdW5kLXNpemU6IDEwMCUgMTAwJTsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWljb24tZnVsbHNjcmVlbiB7XG4gIGJhY2tncm91bmQ6IHVybChcIi4uL2Fzc2V0cy9mdWxsc2NyZWVuLnBuZ1wiKSBuby1yZXBlYXQgY2VudGVyO1xuICBiYWNrZ3JvdW5kLXNpemU6IDEwMCUgMTAwJTsgfVxuICAuamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLWljb24tZnVsbHNjcmVlbjpob3ZlciB7XG4gICAgYmFja2dyb3VuZDogdXJsKFwiLi4vYXNzZXRzL2Z1bGxzY3JlZW4taG92ZXIucG5nXCIpIG5vLXJlcGVhdCBjZW50ZXI7XG4gICAgYmFja2dyb3VuZC1zaXplOiAxMDAlIDEwMCU7IH1cblxuLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1pY29uLWZ1bGxzY3JlZW5FeGl0IHtcbiAgYmFja2dyb3VuZDogdXJsKFwiLi4vYXNzZXRzL2V4aXQtZnVsbHNjcmVlbi5wbmdcIikgbm8tcmVwZWF0IGNlbnRlcjtcbiAgYmFja2dyb3VuZC1zaXplOiAxMDAlIDEwMCU7IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1pY29uLWZ1bGxzY3JlZW5FeGl0OmhvdmVyIHtcbiAgICBiYWNrZ3JvdW5kOiB1cmwoXCIuLi9hc3NldHMvZXhpdC1mdWxsc2NyZWVuLWhvdmVyLnBuZ1wiKSBuby1yZXBlYXQgY2VudGVyO1xuICAgIGJhY2tncm91bmQtc2l6ZTogMTAwJSAxMDAlOyB9XG5cbi5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtaWNvbi1hdWRpbyB7XG4gIGJhY2tncm91bmQ6IHVybChcIi4uL2Fzc2V0cy9hdWRpby5wbmdcIikgbm8tcmVwZWF0IGNlbnRlcjtcbiAgYmFja2dyb3VuZC1zaXplOiAxMDAlIDEwMCU7IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1pY29uLWF1ZGlvOmhvdmVyIHtcbiAgICBiYWNrZ3JvdW5kOiB1cmwoXCIuLi9hc3NldHMvYXVkaW8taG92ZXIucG5nXCIpIG5vLXJlcGVhdCBjZW50ZXI7XG4gICAgYmFja2dyb3VuZC1zaXplOiAxMDAlIDEwMCU7IH1cblxuLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1pY29uLW11dGUge1xuICBiYWNrZ3JvdW5kOiB1cmwoXCIuLi9hc3NldHMvbXV0ZS5wbmdcIikgbm8tcmVwZWF0IGNlbnRlcjtcbiAgYmFja2dyb3VuZC1zaXplOiAxMDAlIDEwMCU7IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1pY29uLW11dGU6aG92ZXIge1xuICAgIGJhY2tncm91bmQ6IHVybChcIi4uL2Fzc2V0cy9tdXRlLWhvdmVyLnBuZ1wiKSBuby1yZXBlYXQgY2VudGVyO1xuICAgIGJhY2tncm91bmQtc2l6ZTogMTAwJSAxMDAlOyB9XG5cbi5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtaWNvbi10ZXh0IHtcbiAgZm9udC1zaXplOiAxNHB4O1xuICB3aWR0aDogMzBweDsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLXNwZWVkIHtcbiAgZm9udC1zaXplOiAxNHB4O1xuICBjb2xvcjogI2ZmZjsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLXF1YWxpdHktbWVudS1saXN0IHtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICBsZWZ0OiA1MCU7XG4gIGJvdHRvbTogMTAwJTtcbiAgdmlzaWJpbGl0eTogaGlkZGVuO1xuICBvcGFjaXR5OiAwO1xuICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVgoLTUwJSk7XG4gIHRyYW5zaXRpb246IHZpc2liaWxpdHkgMzAwbXMsIG9wYWNpdHkgMzAwbXM7XG4gIGJhY2tncm91bmQtY29sb3I6IHJnYmEoMCwgMCwgMCwgMC41KTtcbiAgYm9yZGVyLXJhZGl1czogNHB4OyB9XG4gIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtcXVhbGl0eS1tZW51LWxpc3QuamVzc2lidWNhLXF1YWxpdHktbWVudS1zaG93biB7XG4gICAgdmlzaWJpbGl0eTogdmlzaWJsZTtcbiAgICBvcGFjaXR5OiAxOyB9XG5cbi5qZXNzaWJ1Y2EtY29udGFpbmVyIC5pY29uLXRpdGxlLXRpcHMge1xuICBwb2ludGVyLWV2ZW50czogbm9uZTtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICBsZWZ0OiA1MCU7XG4gIGJvdHRvbTogMTAwJTtcbiAgdmlzaWJpbGl0eTogaGlkZGVuO1xuICBvcGFjaXR5OiAwO1xuICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVgoLTUwJSk7XG4gIHRyYW5zaXRpb246IHZpc2liaWxpdHkgMzAwbXMgZWFzZSAwcywgb3BhY2l0eSAzMDBtcyBlYXNlIDBzO1xuICBiYWNrZ3JvdW5kLWNvbG9yOiByZ2JhKDAsIDAsIDAsIDAuNSk7XG4gIGJvcmRlci1yYWRpdXM6IDRweDsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lciAuaWNvbi10aXRsZSB7XG4gIGRpc3BsYXk6IGlubGluZS1ibG9jaztcbiAgcGFkZGluZzogNXB4IDEwcHg7XG4gIGZvbnQtc2l6ZTogMTJweDtcbiAgd2hpdGUtc3BhY2U6IG5vd3JhcDtcbiAgY29sb3I6IHdoaXRlOyB9XG5cbi5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtcXVhbGl0eS1tZW51IHtcbiAgcGFkZGluZzogOHB4IDA7IH1cblxuLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1xdWFsaXR5LW1lbnUtaXRlbSB7XG4gIGRpc3BsYXk6IGJsb2NrO1xuICBoZWlnaHQ6IDI1cHg7XG4gIG1hcmdpbjogMDtcbiAgcGFkZGluZzogMCAxMHB4O1xuICBjdXJzb3I6IHBvaW50ZXI7XG4gIGZvbnQtc2l6ZTogMTRweDtcbiAgdGV4dC1hbGlnbjogY2VudGVyO1xuICB3aWR0aDogNTBweDtcbiAgY29sb3I6IHJnYmEoMjU1LCAyNTUsIDI1NSwgMC41KTtcbiAgdHJhbnNpdGlvbjogY29sb3IgMzAwbXMsIGJhY2tncm91bmQtY29sb3IgMzAwbXM7IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1xdWFsaXR5LW1lbnUtaXRlbTpob3ZlciB7XG4gICAgYmFja2dyb3VuZC1jb2xvcjogcmdiYSgyNTUsIDI1NSwgMjU1LCAwLjIpOyB9XG4gIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2EtcXVhbGl0eS1tZW51LWl0ZW06Zm9jdXMge1xuICAgIG91dGxpbmU6IG5vbmU7IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS1xdWFsaXR5LW1lbnUtaXRlbS5qZXNzaWJ1Y2EtcXVhbGl0eS1tZW51LWl0ZW0tYWN0aXZlIHtcbiAgICBjb2xvcjogIzIyOThGQzsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLXZvbHVtZS1wYW5lbC13cmFwIHtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICBsZWZ0OiA1MCU7XG4gIGJvdHRvbTogMTAwJTtcbiAgdmlzaWJpbGl0eTogaGlkZGVuO1xuICBvcGFjaXR5OiAwO1xuICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVgoLTUwJSkgdHJhbnNsYXRlWSgyMiUpO1xuICB0cmFuc2l0aW9uOiB2aXNpYmlsaXR5IDMwMG1zLCBvcGFjaXR5IDMwMG1zO1xuICBiYWNrZ3JvdW5kLWNvbG9yOiByZ2JhKDAsIDAsIDAsIDAuNSk7XG4gIGJvcmRlci1yYWRpdXM6IDRweDtcbiAgaGVpZ2h0OiAxMjBweDtcbiAgd2lkdGg6IDUwcHg7XG4gIG92ZXJmbG93OiBoaWRkZW47IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS12b2x1bWUtcGFuZWwtd3JhcC5qZXNzaWJ1Y2Etdm9sdW1lLXBhbmVsLXdyYXAtc2hvdyB7XG4gICAgdmlzaWJpbGl0eTogdmlzaWJsZTtcbiAgICBvcGFjaXR5OiAxOyB9XG5cbi5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2Etdm9sdW1lLXBhbmVsIHtcbiAgY3Vyc29yOiBwb2ludGVyO1xuICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gIHRvcDogMjFweDtcbiAgaGVpZ2h0OiA2MHB4O1xuICB3aWR0aDogNTBweDtcbiAgb3ZlcmZsb3c6IGhpZGRlbjsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLXZvbHVtZS1wYW5lbC10ZXh0IHtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICBsZWZ0OiAwO1xuICB0b3A6IDA7XG4gIHdpZHRoOiA1MHB4O1xuICBoZWlnaHQ6IDIwcHg7XG4gIGxpbmUtaGVpZ2h0OiAyMHB4O1xuICB0ZXh0LWFsaWduOiBjZW50ZXI7XG4gIGNvbG9yOiAjZmZmO1xuICBmb250LXNpemU6IDEycHg7IH1cblxuLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS12b2x1bWUtcGFuZWwtaGFuZGxlIHtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICB0b3A6IDQ4cHg7XG4gIGxlZnQ6IDUwJTtcbiAgd2lkdGg6IDEycHg7XG4gIGhlaWdodDogMTJweDtcbiAgYm9yZGVyLXJhZGl1czogMTJweDtcbiAgbWFyZ2luLWxlZnQ6IC02cHg7XG4gIGJhY2tncm91bmQ6ICNmZmY7IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS12b2x1bWUtcGFuZWwtaGFuZGxlOjpiZWZvcmUge1xuICAgIGJvdHRvbTogLTU0cHg7XG4gICAgYmFja2dyb3VuZDogI2ZmZjsgfVxuICAuamVzc2lidWNhLWNvbnRhaW5lciAuamVzc2lidWNhLXZvbHVtZS1wYW5lbC1oYW5kbGU6OmFmdGVyIHtcbiAgICBib3R0b206IDZweDtcbiAgICBiYWNrZ3JvdW5kOiByZ2JhKDI1NSwgMjU1LCAyNTUsIDAuMik7IH1cbiAgLmplc3NpYnVjYS1jb250YWluZXIgLmplc3NpYnVjYS12b2x1bWUtcGFuZWwtaGFuZGxlOjpiZWZvcmUsIC5qZXNzaWJ1Y2EtY29udGFpbmVyIC5qZXNzaWJ1Y2Etdm9sdW1lLXBhbmVsLWhhbmRsZTo6YWZ0ZXIge1xuICAgIGNvbnRlbnQ6ICcnO1xuICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICBkaXNwbGF5OiBibG9jaztcbiAgICBsZWZ0OiA1MCU7XG4gICAgd2lkdGg6IDNweDtcbiAgICBtYXJnaW4tbGVmdDogLTFweDtcbiAgICBoZWlnaHQ6IDYwcHg7IH1cblxuLmplc3NpYnVjYS1jb250YWluZXIuamVzc2lidWNhLWZ1bGxzY3JlZW4td2ViIC5qZXNzaWJ1Y2EtY29udHJvbHMge1xuICB3aWR0aDogMTAwdmg7IH1cblxuLmplc3NpYnVjYS1jb250YWluZXIuamVzc2lidWNhLWZ1bGxzY3JlZW4td2ViIC5qZXNzaWJ1Y2EtcGxheS1iaWc6YWZ0ZXIge1xuICB0cmFuc2Zvcm06IHRyYW5zbGF0ZSgtNTAlLCAtNTAlKSByb3RhdGUoMjcwZGVnKTsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lci5qZXNzaWJ1Y2EtZnVsbHNjcmVlbi13ZWIgLmplc3NpYnVjYS1sb2FkaW5nIHtcbiAgZmxleC1kaXJlY3Rpb246IHJvdzsgfVxuXG4uamVzc2lidWNhLWNvbnRhaW5lci5qZXNzaWJ1Y2EtZnVsbHNjcmVlbi13ZWIgLmplc3NpYnVjYS1sb2FkaW5nLXRleHQge1xuICB0cmFuc2Zvcm06IHJvdGF0ZSgyNzBkZWcpOyB9XG4iXX0= */"; - styleInject(css_248z$1); - - // todo: 待定 - var hotkey = ((player, control) => { - const { - events: { - proxy - } - } = player; - const keys = {}; - - function addHotkey(key, event) { - if (keys[key]) { - keys[key].push(event); - } else { - keys[key] = [event]; - } - } // - - - addHotkey(HOT_KEY.esc, () => { - if (player.fullscreen) { - player.fullscreen = false; - } - }); // - - addHotkey(HOT_KEY.arrowUp, () => { - player.volume += 0.05; - }); // - - addHotkey(HOT_KEY.arrowDown, () => { - player.volume -= 0.05; - }); - proxy(window, 'keydown', event => { - if (control.isFocus) { - const tag = document.activeElement.tagName.toUpperCase(); - const editable = document.activeElement.getAttribute('contenteditable'); - - if (tag !== 'INPUT' && tag !== 'TEXTAREA' && editable !== '' && editable !== 'true') { - const events = keys[event.keyCode]; - - if (events) { - event.preventDefault(); - events.forEach(fn => fn()); - } - } - } - }); - }); - - class Control { - constructor(player) { - this.player = player; - template(player, this); - property(player, this); - observer$1(player, this); - events(player, this); - - if (player._opt.hotKey) { - hotkey(player, this); - } - - this.player.debug.log('Control', 'init'); - } - - destroy() { - if (this.$poster) { - const result = removeElement(this.$poster); - - if (!result) { - const $poster = this.player.$container.querySelector('.jessibuca-poster'); - - if ($poster && this.player.$container) { - this.player.$container.removeChild($poster); - } - } - } - - if (this.$loading) { - const result = removeElement(this.$loading); - - if (!result) { - const $loading = this.player.$container.querySelector('.jessibuca-loading'); - - if ($loading && this.player.$container) { - this.player.$container.removeChild($loading); - } - } - } - - if (this.$controls) { - const result = removeElement(this.$controls); - - if (!result) { - const $controls = this.player.$container.querySelector('.jessibuca-controls'); - - if ($controls && this.player.$container) { - this.player.$container.removeChild($controls); - } - } - } - - if (this.$recording) { - const result = removeElement(this.$recording); - - if (!result) { - const $recording = this.player.$container.querySelector('.jessibuca-recording'); - - if ($recording && this.player.$container) { - this.player.$container.removeChild($recording); - } - } - } - - if (this.$playBig) { - const result = removeElement(this.$playBig); - - if (!result) { - const $playBig = this.player.$container.querySelector('.jessibuca-play-big'); - - if ($playBig && this.player.$container) { - this.player.$container.removeChild($playBig); - } - } - } - - this.player.debug.log('control', 'destroy'); - } - - autoSize() { - const player = this.player; - player.$container.style.padding = '0 0'; - const playerWidth = player.width; - const playerHeight = player.height; - const playerRatio = playerWidth / playerHeight; - const canvasWidth = player.video.$videoElement.width; - const canvasHeight = player.video.$videoElement.height; - const canvasRatio = canvasWidth / canvasHeight; - - if (playerRatio > canvasRatio) { - const padding = (playerWidth - playerHeight * canvasRatio) / 2; - player.$container.style.padding = `0 ${padding}px`; - } else { - const padding = (playerHeight - playerWidth / canvasRatio) / 2; - player.$container.style.padding = `${padding}px 0`; - } - } - - } - - var css_248z = ".jessibuca-container{position:relative;display:block;width:100%;height:100%;overflow:hidden}.jessibuca-container.jessibuca-fullscreen-web{position:fixed;z-index:9999;left:0;top:0;right:0;bottom:0;width:100vw!important;height:100vh!important;background:#000}\n/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0eWxlLnNjc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEscUJBQ0UsaUJBQWtCLENBQ2xCLGFBQWMsQ0FDZCxVQUFXLENBQ1gsV0FBWSxDQUNaLGVBQWtCLENBQ2xCLDhDQUNFLGNBQWUsQ0FDZixZQUFhLENBQ2IsTUFBTyxDQUNQLEtBQU0sQ0FDTixPQUFRLENBQ1IsUUFBUyxDQUNULHFCQUF1QixDQUN2QixzQkFBd0IsQ0FDeEIsZUFBa0IiLCJmaWxlIjoic3R5bGUuc2NzcyIsInNvdXJjZXNDb250ZW50IjpbIi5qZXNzaWJ1Y2EtY29udGFpbmVyIHtcbiAgcG9zaXRpb246IHJlbGF0aXZlO1xuICBkaXNwbGF5OiBibG9jaztcbiAgd2lkdGg6IDEwMCU7XG4gIGhlaWdodDogMTAwJTtcbiAgb3ZlcmZsb3c6IGhpZGRlbjsgfVxuICAuamVzc2lidWNhLWNvbnRhaW5lci5qZXNzaWJ1Y2EtZnVsbHNjcmVlbi13ZWIge1xuICAgIHBvc2l0aW9uOiBmaXhlZDtcbiAgICB6LWluZGV4OiA5OTk5O1xuICAgIGxlZnQ6IDA7XG4gICAgdG9wOiAwO1xuICAgIHJpZ2h0OiAwO1xuICAgIGJvdHRvbTogMDtcbiAgICB3aWR0aDogMTAwdncgIWltcG9ydGFudDtcbiAgICBoZWlnaHQ6IDEwMHZoICFpbXBvcnRhbnQ7XG4gICAgYmFja2dyb3VuZDogIzAwMDsgfVxuIl19 */"; - styleInject(css_248z); - - var observer = (player => { - const { - _opt, - debug, - events: { - proxy - } - } = player; - - if (_opt.supportDblclickFullscreen) { - proxy(player.$container, 'dblclick', e => { - const target = getTarget(e); - const nodeName = target.nodeName.toLowerCase(); - - if (nodeName === 'canvas' || nodeName === 'video') { - player.fullscreen = !player.fullscreen; - } - }); - } // - - - proxy(document, 'visibilitychange', () => { - if (_opt.hiddenAutoPause) { - debug.log('visibilitychange', document.visibilityState, player._isPlayingBeforePageHidden); - - if ("visible" === document.visibilityState) { - if (player._isPlayingBeforePageHidden) { - player.play(); - } - } else { - player._isPlayingBeforePageHidden = player.playing; // hidden - - if (player.playing) { - player.pause(); - } - } - } - }); - proxy(window, 'fullscreenchange', () => { - // - if (player.keepScreenOn !== null && "visible" === document.visibilityState) { - player.enableWakeLock(); - } - }); - }); - - class MP4$1 { - static init() { - MP4$1.types = { - avc1: [], - avcC: [], - hvc1: [], - hvcC: [], - 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: [] - }; - - for (let name in MP4$1.types) { - if (MP4$1.types.hasOwnProperty(name)) { - MP4$1.types[name] = [name.charCodeAt(0), name.charCodeAt(1), name.charCodeAt(2), name.charCodeAt(3)]; - } - } - - let constants = MP4$1.constants = {}; - constants.FTYP = new Uint8Array([0x69, 0x73, 0x6F, 0x6D, // major_brand: isom - 0x0, 0x0, 0x0, 0x1, // minor_version: 0x01 - 0x69, 0x73, 0x6F, 0x6D, // isom - 0x61, 0x76, 0x63, 0x31 // avc1 - ]); - constants.STSD_PREFIX = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags - 0x00, 0x00, 0x00, 0x01 // entry_count - ]); - constants.STTS = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags - 0x00, 0x00, 0x00, 0x00 // entry_count - ]); - constants.STSC = constants.STCO = constants.STTS; - constants.STSZ = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags - 0x00, 0x00, 0x00, 0x00, // sample_size - 0x00, 0x00, 0x00, 0x00 // sample_count - ]); - constants.HDLR_VIDEO = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags - 0x00, 0x00, 0x00, 0x00, // pre_defined - 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide' - 0x00, 0x00, 0x00, 0x00, // reserved: 3 * 4 bytes - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x69, 0x64, 0x65, 0x6F, 0x48, 0x61, 0x6E, 0x64, 0x6C, 0x65, 0x72, 0x00 // name: VideoHandler - ]); - constants.HDLR_AUDIO = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags - 0x00, 0x00, 0x00, 0x00, // pre_defined - 0x73, 0x6F, 0x75, 0x6E, // handler_type: 'soun' - 0x00, 0x00, 0x00, 0x00, // reserved: 3 * 4 bytes - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x6F, 0x75, 0x6E, 0x64, 0x48, 0x61, 0x6E, 0x64, 0x6C, 0x65, 0x72, 0x00 // name: SoundHandler - ]); - constants.DREF = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags - 0x00, 0x00, 0x00, 0x01, // entry_count - 0x00, 0x00, 0x00, 0x0C, // entry_size - 0x75, 0x72, 0x6C, 0x20, // type 'url ' - 0x00, 0x00, 0x00, 0x01 // version(0) + flags - ]); // Sound media header - - constants.SMHD = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags - 0x00, 0x00, 0x00, 0x00 // balance(2) + reserved(2) - ]); // video media header - - constants.VMHD = new Uint8Array([0x00, 0x00, 0x00, 0x01, // version(0) + flags - 0x00, 0x00, // graphicsmode: 2 bytes - 0x00, 0x00, 0x00, 0x00, // opcolor: 3 * 2 bytes - 0x00, 0x00]); - } // Generate a box - - - static box(type) { - let size = 8; - let result = null; - let datas = Array.prototype.slice.call(arguments, 1); - let arrayCount = datas.length; - - for (let i = 0; i < arrayCount; i++) { - size += datas[i].byteLength; - } - - result = new Uint8Array(size); - result[0] = size >>> 24 & 0xFF; // size - - result[1] = size >>> 16 & 0xFF; - result[2] = size >>> 8 & 0xFF; - result[3] = size & 0xFF; - result.set(type, 4); // type - - let offset = 8; - - for (let i = 0; i < arrayCount; i++) { - // data body - result.set(datas[i], offset); - offset += datas[i].byteLength; - } - - return result; - } // emit ftyp & moov - - - static generateInitSegment(meta) { - let ftyp = MP4$1.box(MP4$1.types.ftyp, MP4$1.constants.FTYP); - let moov = MP4$1.moov(meta); - let result = new Uint8Array(ftyp.byteLength + moov.byteLength); - result.set(ftyp, 0); - result.set(moov, ftyp.byteLength); - return result; - } // Movie metadata box - - - static moov(meta) { - let mvhd = MP4$1.mvhd(meta.timescale, meta.duration); - let trak = MP4$1.trak(meta); - let mvex = MP4$1.mvex(meta); - return MP4$1.box(MP4$1.types.moov, mvhd, trak, mvex); - } // Movie header box - - - static mvhd(timescale, duration) { - return MP4$1.box(MP4$1.types.mvhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags - 0x00, 0x00, 0x00, 0x00, // creation_time - 0x00, 0x00, 0x00, 0x00, // modification_time - timescale >>> 24 & 0xFF, // timescale: 4 bytes - timescale >>> 16 & 0xFF, timescale >>> 8 & 0xFF, timescale & 0xFF, duration >>> 24 & 0xFF, // duration: 4 bytes - duration >>> 16 & 0xFF, duration >>> 8 & 0xFF, duration & 0xFF, 0x00, 0x01, 0x00, 0x00, // Preferred rate: 1.0 - 0x01, 0x00, 0x00, 0x00, // PreferredVolume(1.0, 2bytes) + reserved(2bytes) - 0x00, 0x00, 0x00, 0x00, // reserved: 4 + 4 bytes - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // ----begin composition matrix---- - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // ----end composition matrix---- - 0x00, 0x00, 0x00, 0x00, // ----begin pre_defined 6 * 4 bytes---- - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ----end pre_defined 6 * 4 bytes---- - 0xFF, 0xFF, 0xFF, 0xFF // next_track_ID - ])); - } // Track box - - - static trak(meta) { - return MP4$1.box(MP4$1.types.trak, MP4$1.tkhd(meta), MP4$1.mdia(meta)); - } // Track header box - - - static tkhd(meta) { - let trackId = meta.id, - duration = meta.duration; - let width = meta.presentWidth, - height = meta.presentHeight; - return MP4$1.box(MP4$1.types.tkhd, new Uint8Array([0x00, 0x00, 0x00, 0x07, // version(0) + flags - 0x00, 0x00, 0x00, 0x00, // creation_time - 0x00, 0x00, 0x00, 0x00, // modification_time - trackId >>> 24 & 0xFF, // track_ID: 4 bytes - trackId >>> 16 & 0xFF, trackId >>> 8 & 0xFF, trackId & 0xFF, 0x00, 0x00, 0x00, 0x00, // reserved: 4 bytes - duration >>> 24 & 0xFF, // duration: 4 bytes - duration >>> 16 & 0xFF, duration >>> 8 & 0xFF, duration & 0xFF, 0x00, 0x00, 0x00, 0x00, // reserved: 2 * 4 bytes - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // layer(2bytes) + alternate_group(2bytes) - 0x00, 0x00, 0x00, 0x00, // volume(2bytes) + reserved(2bytes) - 0x00, 0x01, 0x00, 0x00, // ----begin composition matrix---- - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // ----end composition matrix---- - width >>> 8 & 0xFF, // width and height - width & 0xFF, 0x00, 0x00, height >>> 8 & 0xFF, height & 0xFF, 0x00, 0x00])); - } - - static mdia(meta) { - return MP4$1.box(MP4$1.types.mdia, MP4$1.mdhd(meta), MP4$1.hdlr(meta), MP4$1.minf(meta)); - } // Media header box - - - static mdhd(meta) { - let timescale = meta.timescale; - let duration = meta.duration; - return MP4$1.box(MP4$1.types.mdhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags - 0x00, 0x00, 0x00, 0x00, // creation_time - 0x00, 0x00, 0x00, 0x00, // modification_time - timescale >>> 24 & 0xFF, // timescale: 4 bytes - timescale >>> 16 & 0xFF, timescale >>> 8 & 0xFF, timescale & 0xFF, duration >>> 24 & 0xFF, // duration: 4 bytes - duration >>> 16 & 0xFF, duration >>> 8 & 0xFF, duration & 0xFF, 0x55, 0xC4, // language: und (undetermined) - 0x00, 0x00 // pre_defined = 0 - ])); - } // Media handler reference box - - - static hdlr(meta) { - let data = null; - - if (meta.type === 'audio') { - data = MP4$1.constants.HDLR_AUDIO; - } else { - data = MP4$1.constants.HDLR_VIDEO; - } - - return MP4$1.box(MP4$1.types.hdlr, data); - } // Media infomation box - - - static minf(meta) { - let xmhd = null; - - if (meta.type === 'audio') { - xmhd = MP4$1.box(MP4$1.types.smhd, MP4$1.constants.SMHD); - } else { - xmhd = MP4$1.box(MP4$1.types.vmhd, MP4$1.constants.VMHD); - } - - return MP4$1.box(MP4$1.types.minf, xmhd, MP4$1.dinf(), MP4$1.stbl(meta)); - } // Data infomation box - - - static dinf() { - let result = MP4$1.box(MP4$1.types.dinf, MP4$1.box(MP4$1.types.dref, MP4$1.constants.DREF)); - return result; - } // Sample table box - - - static stbl(meta) { - let result = MP4$1.box(MP4$1.types.stbl, // type: stbl - MP4$1.stsd(meta), // Sample Description Table - MP4$1.box(MP4$1.types.stts, MP4$1.constants.STTS), // Time-To-Sample - MP4$1.box(MP4$1.types.stsc, MP4$1.constants.STSC), // Sample-To-Chunk - MP4$1.box(MP4$1.types.stsz, MP4$1.constants.STSZ), // Sample size - MP4$1.box(MP4$1.types.stco, MP4$1.constants.STCO) // Chunk offset - ); - return result; - } // Sample description box - - - static stsd(meta) { - if (meta.type === 'audio') { - // else: aac -> mp4a - return MP4$1.box(MP4$1.types.stsd, MP4$1.constants.STSD_PREFIX, MP4$1.mp4a(meta)); - } else { - if (meta.videoType === 'avc') { - // - return MP4$1.box(MP4$1.types.stsd, MP4$1.constants.STSD_PREFIX, MP4$1.avc1(meta)); - } else { - // - return MP4$1.box(MP4$1.types.stsd, MP4$1.constants.STSD_PREFIX, MP4$1.hvc1(meta)); - } - } - } - - static mp4a(meta) { - let channelCount = meta.channelCount; - let sampleRate = meta.audioSampleRate; - let data = new Uint8Array([0x00, 0x00, 0x00, 0x00, // reserved(4) - 0x00, 0x00, 0x00, 0x01, // reserved(2) + data_reference_index(2) - 0x00, 0x00, 0x00, 0x00, // reserved: 2 * 4 bytes - 0x00, 0x00, 0x00, 0x00, 0x00, channelCount, // channelCount(2) - 0x00, 0x10, // sampleSize(2) - 0x00, 0x00, 0x00, 0x00, // reserved(4) - sampleRate >>> 8 & 0xFF, // Audio sample rate - sampleRate & 0xFF, 0x00, 0x00]); - return MP4$1.box(MP4$1.types.mp4a, data, MP4$1.esds(meta)); - } - - static esds(meta) { - let config = meta.config || []; - let configSize = config.length; - let data = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version 0 + flags - 0x03, // descriptor_type - 0x17 + configSize, // length3 - 0x00, 0x01, // es_id - 0x00, // stream_priority - 0x04, // descriptor_type - 0x0F + configSize, // length - 0x40, // codec: mpeg4_audio - 0x15, // stream_type: Audio - 0x00, 0x00, 0x00, // buffer_size - 0x00, 0x00, 0x00, 0x00, // maxBitrate - 0x00, 0x00, 0x00, 0x00, // avgBitrate - 0x05 // descriptor_type - ].concat([configSize]).concat(config).concat([0x06, 0x01, 0x02 // GASpecificConfig - ])); - return MP4$1.box(MP4$1.types.esds, data); - } // avc - - - static avc1(meta) { - let avcc = meta.avcc; - const width = meta.codecWidth; - const height = meta.codecHeight; - let data = 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, width >>> 8 & 255, width & 255, height >>> 8 & 255, height & 255, 0, 72, 0, 0, 0, 72, 0, 0, 0, 0, 0, 0, 0, 1, 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, 0, 0, 0, 0, 0, 0, 24, 255, 255]); - return MP4$1.box(MP4$1.types.avc1, data, MP4$1.box(MP4$1.types.avcC, avcc)); - } // hvc - - - static hvc1(meta) { - let avcc = meta.avcc; - const width = meta.codecWidth; - const height = meta.codecHeight; - let data = 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, width >>> 8 & 255, width & 255, height >>> 8 & 255, height & 255, 0, 72, 0, 0, 0, 72, 0, 0, 0, 0, 0, 0, 0, 1, 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, 0, 0, 0, 0, 0, 0, 24, 255, 255]); - return MP4$1.box(MP4$1.types.hvc1, data, MP4$1.box(MP4$1.types.hvcC, avcc)); - } // Movie Extends box - - - static mvex(meta) { - return MP4$1.box(MP4$1.types.mvex, MP4$1.trex(meta)); - } // Track Extends box - - - static trex(meta) { - let trackId = meta.id; - let data = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags - trackId >>> 24 & 0xFF, // track_ID - trackId >>> 16 & 0xFF, trackId >>> 8 & 0xFF, trackId & 0xFF, 0x00, 0x00, 0x00, 0x01, // default_sample_description_index - 0x00, 0x00, 0x00, 0x00, // default_sample_duration - 0x00, 0x00, 0x00, 0x00, // default_sample_size - 0x00, 0x01, 0x00, 0x01 // default_sample_flags - ]); - return MP4$1.box(MP4$1.types.trex, data); - } // Movie fragment box - - - static moof(track, baseMediaDecodeTime) { - return MP4$1.box(MP4$1.types.moof, MP4$1.mfhd(track.sequenceNumber), MP4$1.traf(track, baseMediaDecodeTime)); - } // - - - static mfhd(sequenceNumber) { - let data = new Uint8Array([0x00, 0x00, 0x00, 0x00, sequenceNumber >>> 24 & 0xFF, // sequence_number: int32 - sequenceNumber >>> 16 & 0xFF, sequenceNumber >>> 8 & 0xFF, sequenceNumber & 0xFF]); - return MP4$1.box(MP4$1.types.mfhd, data); - } // Track fragment box - - - static traf(track, baseMediaDecodeTime) { - let trackId = track.id; // Track fragment header box - - let tfhd = MP4$1.box(MP4$1.types.tfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) & flags - trackId >>> 24 & 0xFF, // track_ID - trackId >>> 16 & 0xFF, trackId >>> 8 & 0xFF, trackId & 0xFF])); // Track Fragment Decode Time - - let tfdt = MP4$1.box(MP4$1.types.tfdt, new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) & flags - baseMediaDecodeTime >>> 24 & 0xFF, // baseMediaDecodeTime: int32 - baseMediaDecodeTime >>> 16 & 0xFF, baseMediaDecodeTime >>> 8 & 0xFF, baseMediaDecodeTime & 0xFF])); - let sdtp = MP4$1.sdtp(track); - let trun = MP4$1.trun(track, sdtp.byteLength + 16 + 16 + 8 + 16 + 8 + 8); - return MP4$1.box(MP4$1.types.traf, tfhd, tfdt, trun, sdtp); - } // Sample Dependency Type box - - - static sdtp(track) { - let data = new Uint8Array(4 + 1); - let flags = track.flags; - data[4] = flags.isLeading << 6 | flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy; - return MP4$1.box(MP4$1.types.sdtp, data); - } // trun - - - static trun(track, offset) { - let dataSize = 12 + 16; - let data = new Uint8Array(dataSize); - offset += 8 + dataSize; - data.set([0x00, 0x00, 0x0F, 0x01, // version(0) & flags - 0x00, 0x00, 0x00, 0x01, // sample_count - offset >>> 24 & 0xFF, // data_offset - offset >>> 16 & 0xFF, offset >>> 8 & 0xFF, offset & 0xFF], 0); - let duration = track.duration; - let size = track.size; - let flags = track.flags; - let cts = track.cts; - data.set([duration >>> 24 & 0xFF, // sample_duration - duration >>> 16 & 0xFF, duration >>> 8 & 0xFF, duration & 0xFF, size >>> 24 & 0xFF, // sample_size - size >>> 16 & 0xFF, size >>> 8 & 0xFF, size & 0xFF, flags.isLeading << 2 | flags.dependsOn, // sample_flags - flags.isDependedOn << 6 | flags.hasRedundancy << 4 | flags.isNonSync, 0x00, 0x00, // sample_degradation_priority - cts >>> 24 & 0xFF, // sample_composition_time_offset - cts >>> 16 & 0xFF, cts >>> 8 & 0xFF, cts & 0xFF], 12); - return MP4$1.box(MP4$1.types.trun, data); - } // mdat - - - static mdat(data) { - return MP4$1.box(MP4$1.types.mdat, data); - } - - } - - MP4$1.init(); - - class MseDecoder extends Emitter { - constructor(player) { - super(); - this.player = player; - this.isAvc = true; - this.mediaSource = new window.MediaSource(); - this.sourceBuffer = null; - this.hasInit = false; - this.isInitInfo = false; - this.cacheTrack = {}; - this.timeInit = false; - this.sequenceNumber = 0; - this.mediaSourceOpen = false; - this.dropping = false; - this.firstRenderTime = null; - this.mediaSourceAppendBufferError = false; - this.mediaSourceAppendBufferFull = false; - this.isDecodeFirstIIframe = false; - this.player.video.$videoElement.src = window.URL.createObjectURL(this.mediaSource); - const { - debug, - events: { - proxy - } - } = player; - proxy(this.mediaSource, 'sourceopen', () => { - this.mediaSourceOpen = true; - this.player.emit(EVENTS.mseSourceOpen); - }); - proxy(this.mediaSource, 'sourceclose', () => { - this.player.emit(EVENTS.mseSourceClose); - }); - player.debug.log('MediaSource', 'init'); - } - - destroy() { - this.stop(); - this.mediaSource = null; - this.mediaSourceOpen = false; - this.sourceBuffer = null; - this.hasInit = false; - this.isInitInfo = false; - this.sequenceNumber = 0; - this.cacheTrack = null; - this.timeInit = false; - this.mediaSourceAppendBufferError = false; - this.mediaSourceAppendBufferFull = false; - this.isDecodeFirstIIframe = false; - this.off(); - this.player.debug.log('MediaSource', 'destroy'); - } - - get state() { - return this.mediaSource && this.mediaSource.readyState; - } - - get isStateOpen() { - return this.state === MEDIA_SOURCE_STATE.open; - } - - get isStateClosed() { - return this.state === MEDIA_SOURCE_STATE.closed; - } - - get isStateEnded() { - return this.state === MEDIA_SOURCE_STATE.ended; - } - - get duration() { - return this.mediaSource && this.mediaSource.duration; - } - - set duration(duration) { - this.mediaSource.duration = duration; - } - - decodeVideo(payload, ts, isIframe, cts) { - const player = this.player; - - if (!player) { - return; - } - - if (!this.hasInit) { - if (isIframe && payload[1] === 0) { - const videoCodec = payload[0] & 0x0F; - player.video.updateVideoInfo({ - encTypeCode: videoCodec - }); // 如果解码出来的是 - - if (videoCodec === VIDEO_ENC_CODE.h265) { - this.emit(EVENTS_ERROR.mediaSourceH265NotSupport); - return; - } - - if (!player._times.decodeStart) { - player._times.decodeStart = now(); - } - - this._decodeConfigurationRecord(payload, ts, isIframe, videoCodec); - - this.hasInit = true; - } - } else { - if (isIframe && payload[1] === 0) { - let config = parseAVCDecoderConfigurationRecord(payload.slice(5)); - const videoInfo = this.player.video.videoInfo; - - if (videoInfo && videoInfo.width && videoInfo.height && config && config.codecWidth && config.codecHeight && (config.codecWidth !== videoInfo.width || config.codecHeight !== videoInfo.height)) { - this.player.debug.warn('MediaSource', `width or height is update, width ${videoInfo.width}-> ${config.codecWidth}, height ${videoInfo.height}-> ${config.codecHeight}`); - this.isInitInfo = false; - this.player.video.init = false; - } - } - - if (!this.isDecodeFirstIIframe && isIframe) { - this.isDecodeFirstIIframe = true; - } - - if (this.isDecodeFirstIIframe) { - if (this.firstRenderTime === null) { - this.firstRenderTime = ts; - } - - const dts = ts - this.firstRenderTime; - - this._decodeVideo(payload, dts, isIframe, cts); - } else { - this.player.debug.warn('MediaSource', 'decodeVideo isDecodeFirstIIframe false'); - } - } - } - - _decodeConfigurationRecord(payload, ts, isIframe, videoCodec) { - let data = payload.slice(5); - let config = {}; - config = parseAVCDecoderConfigurationRecord(data); - const metaData = { - id: 1, - // video tag data - type: 'video', - timescale: 1000, - duration: 0, - avcc: data, - codecWidth: config.codecWidth, - codecHeight: config.codecHeight, - videoType: config.videoType - }; // ftyp - - const metaBox = MP4$1.generateInitSegment(metaData); - this.isAvc = true; - this.appendBuffer(metaBox.buffer); - this.sequenceNumber = 0; - this.cacheTrack = null; - this.timeInit = false; - } // - - - _decodeVideo(payload, dts, isIframe, cts) { - const player = this.player; - let arrayBuffer = payload.slice(5); - let bytes = arrayBuffer.byteLength; // player.debug.log('MediaSource', '_decodeVideo', ts); - - const $video = player.video.$videoElement; - const videoBufferDelay = player._opt.videoBufferDelay; - - if ($video.buffered.length > 1) { - this.removeBuffer($video.buffered.start(0), $video.buffered.end(0)); - this.timeInit = false; - } - - if (this.dropping && dts - this.cacheTrack.dts > videoBufferDelay) { - this.dropping = false; - this.cacheTrack = {}; - } else if (this.cacheTrack && dts >= this.cacheTrack.dts) { - // 需要额外加8个size - let mdatBytes = 8 + this.cacheTrack.size; - let mdatbox = new Uint8Array(mdatBytes); - mdatbox[0] = mdatBytes >>> 24 & 255; - mdatbox[1] = mdatBytes >>> 16 & 255; - mdatbox[2] = mdatBytes >>> 8 & 255; - mdatbox[3] = mdatBytes & 255; - mdatbox.set(MP4$1.types.mdat, 4); - mdatbox.set(this.cacheTrack.data, 8); - this.cacheTrack.duration = dts - this.cacheTrack.dts; // moof - - let moofbox = MP4$1.moof(this.cacheTrack, this.cacheTrack.dts); - let result = new Uint8Array(moofbox.byteLength + mdatbox.byteLength); - result.set(moofbox, 0); - result.set(mdatbox, moofbox.byteLength); // appendBuffer - - this.appendBuffer(result.buffer); - player.handleRender(); - player.updateStats({ - fps: true, - ts: dts, - buf: player.demux && player.demux.delay || 0 - }); - - if (!player._times.videoStart) { - player._times.videoStart = now(); - player.handlePlayToRenderTimes(); - } - } else { - player.debug.log('MediaSource', 'timeInit set false , cacheTrack = {}'); - this.timeInit = false; - this.cacheTrack = {}; - } - - if (!this.cacheTrack) { - this.cacheTrack = {}; - } - - this.cacheTrack.id = 1; - this.cacheTrack.sequenceNumber = ++this.sequenceNumber; - this.cacheTrack.size = bytes; - this.cacheTrack.dts = dts; - this.cacheTrack.cts = cts; - this.cacheTrack.isKeyframe = isIframe; - this.cacheTrack.data = arrayBuffer; // - - this.cacheTrack.flags = { - isLeading: 0, - dependsOn: isIframe ? 2 : 1, - isDependedOn: isIframe ? 1 : 0, - hasRedundancy: 0, - isNonSync: isIframe ? 0 : 1 - }; // - - if (!this.timeInit && $video.buffered.length === 1) { - player.debug.log('MediaSource', 'timeInit set true'); - this.timeInit = true; - $video.currentTime = $video.buffered.end(0); - } - - if (!this.isInitInfo && $video.videoWidth > 0 && $video.videoHeight > 0) { - player.debug.log('MediaSource', `updateVideoInfo: ${$video.videoWidth},${$video.videoHeight}`); - player.video.updateVideoInfo({ - width: $video.videoWidth, - height: $video.videoHeight - }); - player.video.initCanvasViewSize(); - this.isInitInfo = true; - } - } - - appendBuffer(buffer) { - const { - debug, - events: { - proxy - } - } = this.player; - - if (this.sourceBuffer === null) { - this.sourceBuffer = this.mediaSource.addSourceBuffer(MP4_CODECS.avc); - proxy(this.sourceBuffer, 'error', error => { - this.player.emit(EVENTS.mseSourceBufferError, error); // this.dropSourceBuffer(false) - }); - } - - if (this.mediaSourceAppendBufferError) { - debug.error('MediaSource', `this.mediaSourceAppendBufferError is true`); - return; - } - - if (this.mediaSourceAppendBufferFull) { - debug.error('MediaSource', `this.mediaSourceAppendBufferFull is true`); - return; - } - - if (this.sourceBuffer.updating === false && this.isStateOpen) { - try { - this.sourceBuffer.appendBuffer(buffer); - } catch (e) { - debug.warn('MediaSource', 'this.sourceBuffer.appendBuffer()', e.code, e); - - if (e.code === 22) { - // QuotaExceededError - // The SourceBuffer is full, and cannot free space to append additional buffers - this.stop(); - this.mediaSourceAppendBufferFull = true; - this.emit(EVENTS_ERROR.mediaSourceFull); - } else if (e.code === 11) { - // Failed to execute 'appendBuffer' on 'SourceBuffer': The HTMLMediaElement.error attribute is not null. - this.stop(); - this.mediaSourceAppendBufferError = true; - this.emit(EVENTS_ERROR.mediaSourceAppendBufferError); - } else { - debug.error('MediaSource', 'appendBuffer error', e); - this.player.emit(EVENTS.mseSourceBufferError, e); - } - } - - return; - } - - if (this.isStateClosed) { - this.player.emitError(EVENTS_ERROR.mseSourceBufferError, 'mediaSource is not attached to video or mediaSource is closed'); - } else if (this.isStateEnded) { - this.player.emitError(EVENTS_ERROR.mseSourceBufferError, 'mediaSource is closed'); - } else { - if (this.sourceBuffer.updating === true) { - this.player.emit(EVENTS.mseSourceBufferBusy); // this.dropSourceBuffer(true); - } - } - } - - stop() { - this.abortSourceBuffer(); - this.removeSourceBuffer(); - this.endOfStream(); - } - - dropSourceBuffer(isDropping) { - const $video = this.player.video.$videoElement; - this.dropping = isDropping; - - if ($video.buffered.length > 0) { - if ($video.buffered.end(0) - $video.currentTime > 1) { - this.player.debug.warn('MediaSource', 'dropSourceBuffer', `$video.buffered.end(0) is ${$video.buffered.end(0)} - $video.currentTime ${$video.currentTime}`); - $video.currentTime = $video.buffered.end(0); - } - } - } - - removeBuffer(start, end) { - if (this.isStateOpen && this.sourceBuffer.updating === false) { - try { - this.sourceBuffer.remove(start, end); - } catch (e) { - this.player.debug.warn('MediaSource', 'removeBuffer() error', e); - } - } else { - this.player.debug.warn('MediaSource', 'removeBuffer() this.isStateOpen is', this.isStateOpen, 'this.sourceBuffer.updating', this.sourceBuffer.updating); - } - } - - endOfStream() { - // fix: MediaSource endOfStream before demuxer initialization completes (before HAVE_METADATA) is treated as an error - const $videoElement = this.player.video && this.player.video.$videoElement; - - if (this.isStateOpen && $videoElement && $videoElement.readyState >= 1) { - try { - this.mediaSource.endOfStream(); - } catch (e) { - this.player.debug.warn('MediaSource', 'endOfStream() error', e); - } - } - } - - abortSourceBuffer() { - if (this.isStateOpen) { - if (this.sourceBuffer) { - this.sourceBuffer.abort(); - this.sourceBuffer = null; - } - } - } - - removeSourceBuffer() { - if (!this.isStateClosed) { - if (this.mediaSource && this.sourceBuffer) { - try { - this.mediaSource.removeSourceBuffer(this.sourceBuffer); - } catch (e) { - this.player.debug.warn('MediaSource', 'removeSourceBuffer() error', e); - } - } - } - } - - getSourceBufferUpdating() { - return this.sourceBuffer && this.sourceBuffer.updating; - } - - } - - // tks: https://github.com/richtr/NoSleep.js - const WEBM = "data:video/webm;base64,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"; - const MP4 = "data:video/mp4;base64,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"; // Detect iOS browsers < version 10 - - const oldIOS = () => typeof navigator !== "undefined" && parseFloat(("" + (/CPU.*OS ([0-9_]{3,4})[0-9_]{0,1}|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0, ""])[1]).replace("undefined", "3_2").replace("_", ".").replace("_", "")) < 10 && !window.MSStream; // Detect native Wake Lock API support - - - const nativeWakeLock = () => "wakeLock" in navigator; - - class NoSleep { - constructor(player) { - this.player = player; - this.enabled = false; - - if (nativeWakeLock()) { - this._wakeLock = null; - - const handleVisibilityChange = () => { - if (this._wakeLock !== null && document.visibilityState === "visible") { - this.enable(); - } - }; - - document.addEventListener("visibilitychange", handleVisibilityChange); - document.addEventListener("fullscreenchange", handleVisibilityChange); - } else if (oldIOS()) { - this.noSleepTimer = null; - } else { - // Set up no sleep video element - this.noSleepVideo = document.createElement("video"); - this.noSleepVideo.setAttribute("title", "No Sleep"); - this.noSleepVideo.setAttribute("playsinline", ""); - - this._addSourceToVideo(this.noSleepVideo, "webm", WEBM); - - this._addSourceToVideo(this.noSleepVideo, "mp4", MP4); - - this.noSleepVideo.addEventListener("loadedmetadata", () => { - if (this.noSleepVideo.duration <= 1) { - // webm source - this.noSleepVideo.setAttribute("loop", ""); - } else { - // mp4 source - this.noSleepVideo.addEventListener("timeupdate", () => { - if (this.noSleepVideo.currentTime > 0.5) { - this.noSleepVideo.currentTime = Math.random(); - } - }); - } - }); - } - } - - _addSourceToVideo(element, type, dataURI) { - var source = document.createElement("source"); - source.src = dataURI; - source.type = `video/${type}`; - element.appendChild(source); - } - - get isEnabled() { - return this.enabled; - } - - enable() { - const debug = this.player.debug; - - if (nativeWakeLock()) { - return navigator.wakeLock.request("screen").then(wakeLock => { - this._wakeLock = wakeLock; - this.enabled = true; - debug.log('wakeLock', 'Wake Lock active.'); - - this._wakeLock.addEventListener("release", () => { - // ToDo: Potentially emit an event for the page to observe since - // Wake Lock releases happen when page visibility changes. - // (https://web.dev/wakelock/#wake-lock-lifecycle) - debug.log('wakeLock', 'Wake Lock released.'); - }); - }).catch(err => { - this.enabled = false; - debug.error('wakeLock', `${err.name}, ${err.message}`); - throw err; - }); - } else if (oldIOS()) { - this.disable(); - this.noSleepTimer = window.setInterval(() => { - if (!document.hidden) { - window.location.href = window.location.href.split("#")[0]; - window.setTimeout(window.stop, 0); - } - }, 15000); - this.enabled = true; - return Promise.resolve(); - } else { - let playPromise = this.noSleepVideo.play(); - return playPromise.then(res => { - this.enabled = true; - return res; - }).catch(err => { - this.enabled = false; - throw err; - }); - } - } - - disable() { - const debug = this.player.debug; - - if (nativeWakeLock()) { - if (this._wakeLock) { - this._wakeLock.release(); - } - - this._wakeLock = null; - } else if (oldIOS()) { - if (this.noSleepTimer) { - debug.warn('wakeLock', 'NoSleep now disabled for older iOS devices.'); - window.clearInterval(this.noSleepTimer); - this.noSleepTimer = null; - } - } else { - this.noSleepVideo.pause(); - } - - this.enabled = false; - } - - } - - class Player extends Emitter { - constructor(container, options) { - super(); - this.$container = container; - this._opt = Object.assign({}, DEFAULT_PLAYER_OPTIONS, options); - this.debug = new Debug(this); // - - if (this._opt.useWCS) { - this._opt.useWCS = supportWCS(); - } // - - - if (this._opt.useMSE) { - this._opt.useMSE = supportMSE(); - } // - - - if (this._opt.wcsUseVideoRender) { - this._opt.wcsUseVideoRender = supportMediaStreamTrack(); - } // 如果使用mse则强制不允许 webcodecs - - - if (this._opt.useMSE) { - if (this._opt.useWCS) { - this.debug.log('Player', 'useWCS set true->false'); - } - - if (!this._opt.forceNoOffscreen) { - this.debug.log('Player', 'forceNoOffscreen set false->true'); - } - - this._opt.useWCS = false; - this._opt.forceNoOffscreen = true; - } - - if (!this._opt.forceNoOffscreen) { - if (!supportOffscreenV2()) { - this._opt.forceNoOffscreen = true; - this._opt.useOffscreen = false; - } else { - this._opt.useOffscreen = true; - } - } - - if (!this._opt.hasAudio) { - this._opt.operateBtns.audio = false; - } - - this._opt.hasControl = this._hasControl(); // - - this._loading = false; - this._playing = false; - this._hasLoaded = false; // - - this._checkHeartTimeout = null; - this._checkLoadingTimeout = null; - this._checkStatsInterval = null; // - - this._startBpsTime = null; - this._isPlayingBeforePageHidden = false; - this._stats = { - buf: 0, - // 当前缓冲区时长,单位毫秒, - fps: 0, - // 当前视频帧率 - abps: 0, - // 当前音频码率,单位bit - vbps: 0, - // 当前视频码率,单位bit - ts: 0 // 当前视频帧pts,单位毫秒 - - }; // 各个步骤的时间统计 - - this._times = initPlayTimes(); // - - this._videoTimestamp = 0; - this._audioTimestamp = 0; - property$1(this); - this.events = new Events(this); - this.video = new Video(this); - - if (this._opt.hasAudio) { - this.audio = new Audio(this); - } - - this.recorder = new Recorder(this); - - if (!this._onlyMseOrWcsVideo()) { - this.decoderWorker = new DecoderWorker(this); - } else { - this.loaded = true; - } - - this.stream = null; - this.demux = null; - this._lastVolume = null; - - if (this._opt.useWCS) { - this.webcodecsDecoder = new WebcodecsDecoder(this); - this.loaded = true; - } - - if (this._opt.useMSE) { - this.mseDecoder = new MseDecoder(this); - this.loaded = true; - } // - - - this.control = new Control(this); - - if (isMobile()) { - this.keepScreenOn = new NoSleep(this); - } - - events$1(this); - observer(this); - - if (this._opt.useWCS) { - this.debug.log('Player', 'use WCS'); - } - - if (this._opt.useMSE) { - this.debug.log('Player', 'use MSE'); - } - - if (this._opt.useOffscreen) { - this.debug.log('Player', 'use offscreen'); - } - - this.debug.log('Player options', this._opt); - } - - destroy() { - this._loading = false; - this._playing = false; - this._hasLoaded = false; - this._lastVolume = null; - this._times = initPlayTimes(); - - if (this.decoderWorker) { - this.decoderWorker.destroy(); - this.decoderWorker = null; - } - - if (this.video) { - this.video.destroy(); - this.video = null; - } - - if (this.audio) { - this.audio.destroy(); - this.audio = null; - } - - if (this.stream) { - this.stream.destroy(); - this.stream = null; - } - - if (this.recorder) { - this.recorder.destroy(); - this.recorder = null; - } - - if (this.control) { - this.control.destroy(); - this.control = null; - } - - if (this.webcodecsDecoder) { - this.webcodecsDecoder.destroy(); - this.webcodecsDecoder = null; - } - - if (this.mseDecoder) { - this.mseDecoder.destroy(); - this.mseDecoder = null; - } - - if (this.demux) { - this.demux.destroy(); - this.demux = null; - } - - if (this.events) { - this.events.destroy(); - this.events = null; - } - - this.clearCheckHeartTimeout(); - this.clearCheckLoadingTimeout(); - this.clearStatsInterval(); // - - this.releaseWakeLock(); - this.keepScreenOn = null; // reset stats - - this.resetStats(); - this._audioTimestamp = 0; - this._videoTimestamp = 0; // 其他没法解耦的,通过 destroy 方式 - - this.emit('destroy'); // 接触所有绑定事件 - - this.off(); - this.debug.log('play', 'destroy end'); - } - - set fullscreen(value) { - if (isMobile() && this._opt.useWebFullScreen) { - this.emit(EVENTS.webFullscreen, value); - setTimeout(() => { - this.updateOption({ - rotate: value ? 270 : 0 - }); - this.resize(); - }, 10); - } else { - this.emit(EVENTS.fullscreen, value); - } - } - - get fullscreen() { - return isFullScreen() || this.webFullscreen; - } - - set webFullscreen(value) { - this.emit(EVENTS.webFullscreen, value); - } - - get webFullscreen() { - return this.$container.classList.contains('jessibuca-fullscreen-web'); - } - - set loaded(value) { - this._hasLoaded = value; - } - - get loaded() { - return this._hasLoaded; - } // - - - set playing(value) { - if (value) { - // 将loading 设置为 false - this.loading = false; - } - - if (this.playing !== value) { - this._playing = value; - this.emit(EVENTS.playing, value); - this.emit(EVENTS.volumechange, this.volume); - - if (value) { - this.emit(EVENTS.play); - } else { - this.emit(EVENTS.pause); - } - } - } - - get playing() { - return this._playing; - } - - get volume() { - return this.audio && this.audio.volume || 0; - } - - set volume(value) { - if (value !== this.volume) { - this.audio && this.audio.setVolume(value); - this._lastVolume = value; - } - } - - get lastVolume() { - return this._lastVolume; - } - - set loading(value) { - if (this.loading !== value) { - this._loading = value; - this.emit(EVENTS.loading, this._loading); - } - } - - get loading() { - return this._loading; - } - - set recording(value) { - if (value) { - if (this.playing) { - this.recorder && this.recorder.startRecord(); - } - } else { - this.recorder && this.recorder.stopRecordAndSave(); - } - } - - get recording() { - return this.recorder ? this.recorder.recording : false; - } - - set audioTimestamp(value) { - if (value === null) { - return; - } - - this._audioTimestamp = value; - } // - - - get audioTimestamp() { - return this._audioTimestamp; - } // - - - set videoTimestamp(value) { - if (value === null) { - return; - } - - this._videoTimestamp = value; // just for wasm - - if (!this._opt.useWCS && !this._opt.useMSE) { - if (this.audioTimestamp && this.videoTimestamp) { - this.audio && this.audio.emit(EVENTS.videoSyncAudio, { - audioTimestamp: this.audioTimestamp, - videoTimestamp: this.videoTimestamp, - diff: this.audioTimestamp - this.videoTimestamp - }); - } - } - } // - - - get videoTimestamp() { - return this._videoTimestamp; - } - - get isDebug() { - return this._opt.debug === true; - } - /** - * - * @param options - */ - - - updateOption(options) { - this._opt = Object.assign({}, this._opt, options); - } - /** - * - * @returns {Promise} - */ - - - init() { - return new Promise((resolve, reject) => { - if (!this.stream) { - this.stream = new Stream(this); - } - - if (!this.audio) { - if (this._opt.hasAudio) { - this.audio = new Audio(this); - } - } - - if (!this.demux) { - this.demux = new Demux(this); - } - - if (this._opt.useWCS) { - if (!this.webcodecsDecoder) { - this.webcodecsDecoder = new WebcodecsDecoder(this); - } - } - - if (this._opt.useMSE) { - if (!this.mseDecoder) { - this.mseDecoder = new MseDecoder(this); - } - } - - if (!this.decoderWorker && !this._onlyMseOrWcsVideo()) { - this.decoderWorker = new DecoderWorker(this); - this.once(EVENTS.decoderWorkerInit, () => { - resolve(); - }); - } else { - resolve(); - } - }); - } - /** - * - * @param url - * @returns {Promise} - */ - - - play(url, options) { - return new Promise((resolve, reject) => { - if (!url && !this._opt.url) { - return reject(); - } - - this.loading = true; - this.playing = false; - this._times.playInitStart = now(); - - if (!url) { - url = this._opt.url; - } - - this._opt.url = url; - this.clearCheckHeartTimeout(); - this.init().then(() => { - this._times.playStart = now(); // - - if (this._opt.isNotMute) { - this.mute(false); - } - - if (this.webcodecsDecoder) { - this.webcodecsDecoder.once(EVENTS_ERROR.webcodecsH265NotSupport, () => { - this.emit(EVENTS_ERROR.webcodecsH265NotSupport); - - if (!this._opt.autoWasm) { - this.emit(EVENTS.error, EVENTS_ERROR.webcodecsH265NotSupport); - } - }); - } - - if (this.mseDecoder) { - this.mseDecoder.once(EVENTS_ERROR.mediaSourceH265NotSupport, () => { - this.emit(EVENTS_ERROR.mediaSourceH265NotSupport); - - if (!this._opt.autoWasm) { - this.emit(EVENTS.error, EVENTS_ERROR.mediaSourceH265NotSupport); - } - }); - this.mseDecoder.once(EVENTS_ERROR.mediaSourceFull, () => { - this.emitError(EVENTS_ERROR.mediaSourceFull); - }); - this.mseDecoder.once(EVENTS_ERROR.mediaSourceAppendBufferError, () => { - this.emitError(EVENTS_ERROR.mediaSourceAppendBufferError); - }); - this.mseDecoder.once(EVENTS_ERROR.mediaSourceBufferListLarge, () => { - this.emitError(EVENTS_ERROR.mediaSourceBufferListLarge); - }); - this.mseDecoder.once(EVENTS_ERROR.mediaSourceAppendBufferEndTimeout, () => { - this.emitError(EVENTS_ERROR.mediaSourceAppendBufferEndTimeout); - }); - } - - this.enableWakeLock(); - this.stream.fetchStream(url, options); // - - this.checkLoadingTimeout(); // fetch error - - this.stream.once(EVENTS_ERROR.fetchError, error => { - reject(error); - }); // ws - - this.stream.once(EVENTS_ERROR.websocketError, error => { - reject(error); - }); // stream end - - this.stream.once(EVENTS.streamEnd, () => { - reject(); - }); // success - - this.stream.once(EVENTS.streamSuccess, () => { - resolve(); - this._times.streamResponse = now(); // - - this.video.play(); - this.checkStatsInterval(); - }); - }).catch(e => { - reject(e); - }); - }); - } - /** - * - */ - - - close() { - return new Promise((resolve, reject) => { - this._close().then(() => { - this.video && this.video.clearView(); - resolve(); - }); - }); - } - - resumeAudioAfterPause() { - if (this.lastVolume) { - this.volume = this.lastVolume; - } - } - - _close() { - return new Promise((resolve, reject) => { - // - if (this.stream) { - this.stream.destroy(); - this.stream = null; - } - - if (this.demux) { - this.demux.destroy(); - this.demux = null; - } // - - - if (this.decoderWorker) { - this.decoderWorker.destroy(); - this.decoderWorker = null; - } - - if (this.webcodecsDecoder) { - this.webcodecsDecoder.destroy(); - this.webcodecsDecoder = null; - } - - if (this.mseDecoder) { - this.mseDecoder.destroy(); - this.mseDecoder = null; - } - - if (this.audio) { - this.audio.destroy(); - this.audio = null; - } - - this.clearCheckHeartTimeout(); - this.clearCheckLoadingTimeout(); - this.clearStatsInterval(); - this.playing = false; - this.loading = false; - this.recording = false; - - if (this.video) { - this.video.resetInit(); - this.video.pause(true); - } // release lock - - - this.releaseWakeLock(); // reset stats - - this.resetStats(); // - - this._audioTimestamp = 0; - this._videoTimestamp = 0; // - - this._times = initPlayTimes(); // - - setTimeout(() => { - resolve(); - }, 0); - }); - } - /** - * - * @param flag {boolean} 是否清除画面 - * @returns {Promise} - */ - - - pause() { - let flag = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; - - if (flag) { - return this.close(); - } else { - return this._close(); - } - } - /** - * - * @param flag - */ - - - mute(flag) { - this.audio && this.audio.mute(flag); - } - /** - * - */ - - - resize() { - this.video.resize(); - } - /** - * - * @param fileName - * @param fileType - */ - - - startRecord(fileName, fileType) { - if (this.recording) { - return; - } - - this.recorder.setFileName(fileName, fileType); - this.recording = true; - } - /** - * - */ - - - stopRecordAndSave() { - if (this.recording) { - this.recording = false; - } - } - - _hasControl() { - let result = false; - let hasBtnShow = false; - Object.keys(this._opt.operateBtns).forEach(key => { - if (this._opt.operateBtns[key]) { - hasBtnShow = true; - } - }); - - if (this._opt.showBandwidth || this._opt.text || hasBtnShow) { - result = true; - } - - return result; - } - - _onlyMseOrWcsVideo() { - return this._opt.hasAudio === false && (this._opt.useMSE || this._opt.useWCS && !this._opt.useOffscreen); - } - - checkHeart() { - this.clearCheckHeartTimeout(); - this.checkHeartTimeout(); - } // 心跳检查,如果渲染间隔暂停了多少时间之后,就会抛出异常 - - - checkHeartTimeout() { - this._checkHeartTimeout = setTimeout(() => { - if (this.playing) { - // check again - if (this._stats.fps !== 0) { - return; - } - - this.pause().then(() => { - this.emit(EVENTS.timeout, EVENTS.delayTimeout); - this.emit(EVENTS.delayTimeout); - }); - } - }, this._opt.heartTimeout * 1000); - } - - checkStatsInterval() { - this._checkStatsInterval = setInterval(() => { - this.updateStats(); - }, 1000); - } // - - - clearCheckHeartTimeout() { - if (this._checkHeartTimeout) { - clearTimeout(this._checkHeartTimeout); - this._checkHeartTimeout = null; - } - } // loading 等待时间 - - - checkLoadingTimeout() { - this._checkLoadingTimeout = setTimeout(() => { - // check again - if (this.playing) { - return; - } - - this.pause().then(() => { - this.emit(EVENTS.timeout, EVENTS.loadingTimeout); - this.emit(EVENTS.loadingTimeout); - }); - }, this._opt.loadingTimeout * 1000); - } - - clearCheckLoadingTimeout() { - if (this._checkLoadingTimeout) { - clearTimeout(this._checkLoadingTimeout); - this._checkLoadingTimeout = null; - } - } - - clearStatsInterval() { - if (this._checkStatsInterval) { - clearInterval(this._checkStatsInterval); - this._checkStatsInterval = null; - } - } - - handleRender() { - if (this.loading) { - this.emit(EVENTS.start); - this.loading = false; - this.clearCheckLoadingTimeout(); - } - - if (!this.playing) { - this.playing = true; - } - - this.checkHeart(); - } // - - - updateStats(options) { - options = options || {}; - - if (!this._startBpsTime) { - this._startBpsTime = now(); - } - - if (isNotEmpty(options.ts)) { - this._stats.ts = options.ts; - } - - if (isNotEmpty(options.buf)) { - this._stats.buf = options.buf; - } - - if (options.fps) { - this._stats.fps += 1; - } - - if (options.abps) { - this._stats.abps += options.abps; - } - - if (options.vbps) { - this._stats.vbps += options.vbps; - } - - const _nowTime = now(); - - const timestamp = _nowTime - this._startBpsTime; - - if (timestamp < 1 * 1000) { - return; - } - - this.emit(EVENTS.stats, this._stats); - this.emit(EVENTS.performance, fpsStatus(this._stats.fps)); - this._stats.fps = 0; - this._stats.abps = 0; - this._stats.vbps = 0; - this._startBpsTime = _nowTime; - } - - resetStats() { - this._startBpsTime = null; - this._stats = { - buf: 0, - //ms - fps: 0, - abps: 0, - vbps: 0, - ts: 0 - }; - } - - enableWakeLock() { - if (this._opt.keepScreenOn) { - this.keepScreenOn && this.keepScreenOn.enable(); - } - } - - releaseWakeLock() { - if (this._opt.keepScreenOn) { - this.keepScreenOn && this.keepScreenOn.disable(); - } - } - - handlePlayToRenderTimes() { - const _times = this._times; - _times.playTimestamp = _times.playStart - _times.playInitStart; - _times.streamTimestamp = _times.streamStart - _times.playStart; - _times.streamResponseTimestamp = _times.streamResponse - _times.streamStart; - _times.demuxTimestamp = _times.demuxStart - _times.streamResponse; - _times.decodeTimestamp = _times.decodeStart - _times.demuxStart; - _times.videoTimestamp = _times.videoStart - _times.decodeStart; - _times.allTimestamp = _times.videoStart - _times.playInitStart; - this.emit(EVENTS.playToRenderTimes, _times); - } - - getOption() { - return this._opt; - } - - emitError(errorType) { - let message = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; - this.emit(EVENTS.error, errorType, message); - this.emit(errorType, message); - } - - } - - class Jessibuca extends Emitter { - constructor(options) { - super(); - let _opt = options; - let $container = options.container; - - if (typeof options.container === 'string') { - $container = document.querySelector(options.container); - } - - if (!$container) { - throw new Error('Jessibuca need container option'); - } // check container node name - - - if ($container.nodeName === 'CANVAS' || $container.nodeName === 'VIDEO') { - throw new Error(`Jessibuca container type can not be ${$container.nodeName} type`); - } - - if (_opt.videoBuffer >= _opt.heartTimeout) { - throw new Error(`Jessibuca videoBuffer ${_opt.videoBuffer}s must be less than heartTimeout ${_opt.heartTimeout}s`); - } - - $container.classList.add('jessibuca-container'); - delete _opt.container; // 禁用离屏渲染 - - _opt.forceNoOffscreen = true; // 移动端不支持自动关闭控制栏 - - if (isMobile()) { - _opt.controlAutoHide = false; - } // s -> ms - - - if (isNotEmpty(_opt.videoBuffer)) { - _opt.videoBuffer = Number(_opt.videoBuffer) * 1000; - } // setting - - - if (isNotEmpty(_opt.timeout)) { - if (isEmpty(_opt.loadingTimeout)) { - _opt.loadingTimeout = _opt.timeout; - } - - if (isEmpty(_opt.heartTimeout)) { - _opt.heartTimeout = _opt.timeout; - } - } - - this._opt = _opt; - this.$container = $container; - this._loadingTimeoutReplayTimes = 0; - this._heartTimeoutReplayTimes = 0; - this.events = new Events(this); - this.debug = new Debug(this); - - this._initPlayer($container, _opt); - } - /** - * - */ - - - destroy() { - if (this.events) { - this.events.destroy(); - this.events = null; - } - - if (this.player) { - this.player.destroy(); - this.player = null; - } - - this.$container = null; - this._opt = null; - this._loadingTimeoutReplayTimes = 0; - this._heartTimeoutReplayTimes = 0; - this.off(); - } - - _initPlayer($container, options) { - this.player = new Player($container, options); - this.debug.log('jessibuca', '_initPlayer', this.player.getOption()); - - this._bindEvents(); - } - - _resetPlayer() { - let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - this.player.destroy(); - this.player = null; - this._opt = Object.assign(this._opt, options); - this._opt.url = ''; // reset url - - this._initPlayer(this.$container, this._opt); - } - - _bindEvents() { - // 对外的事件 - Object.keys(JESSIBUCA_EVENTS).forEach(key => { - this.player.on(JESSIBUCA_EVENTS[key], value => { - this.emit(key, value); - }); - }); - } - /** - * 是否开启控制台调试打印 - * @param value {Boolean} - */ - - - setDebug(value) { - this.player.updateOption({ - debug: !!value - }); - } - /** - * - */ - - - mute() { - this.player.mute(true); - } - /** - * - */ - - - cancelMute() { - this.player.mute(false); - } - /** - * - * @param value {number} - */ - - - setVolume(value) { - this.player.volume = value; - } - /** - * - */ - - - audioResume() { - this.player.audio && this.player.audio.audioEnabled(true); - } - /** - * 设置超时时长, 单位秒 在连接成功之前和播放中途,如果超过设定时长无数据返回,则回调timeout事件 - * @param value {number} - */ - - - setTimeout(time) { - time = Number(time); - this.player.updateOption({ - timeout: time, - loadingTimeout: time, - heartTimeout: time - }); - } - /** - * - * @param type {number}: 0,1,2 - */ - - - setScaleMode(type) { - type = Number(type); - let options = { - isFullResize: false, - isResize: false - }; - - switch (type) { - case SCALE_MODE_TYPE.full: - options.isFullResize = false; - options.isResize = false; - break; - - case SCALE_MODE_TYPE.auto: - options.isFullResize = false; - options.isResize = true; - break; - - case SCALE_MODE_TYPE.fullAuto: - options.isFullResize = true; - options.isResize = true; - break; - } - - this.player.updateOption(options); - this.resize(); - } - /** - * - * @returns {Promise} - */ - - - pause() { - return new Promise((resolve, reject) => { - if (this.player) { - this.player.pause().then(() => { - resolve(); - }).catch(e => { - reject(e); - }); - } else { - reject('player is null'); - } - }); - } - /** - * - */ - - - close() { - // clear url - this._opt.url = ''; - this._opt.playOptions = {}; - return this.player.close(); - } - /** - * - */ - - - clearView() { - this.player.video.clearView(); - } - /** - * - * @param url {string} - * @param options {object} - * @returns {Promise} - */ - - - play(url) { - let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - return new Promise((resolve, reject) => { - if (!url && !this._opt.url) { - this.emit(EVENTS.error, EVENTS_ERROR.playError); - reject('play url is empty'); - return; - } - - if (url) { - // url 相等的时候。 - if (this._opt.url) { - // 存在相同的 url - if (url === this._opt.url) { - // 正在播放 - if (this.player.playing) { - resolve(); - } else { - // pause -> play - this.clearView(); - this.player.play(this._opt.url, this._opt.playOptions).then(() => { - resolve(); // 恢复下之前的音量 - - this.player.resumeAudioAfterPause(); - }).catch(e => { - this.debug.warn('jessibuca', 'pause -> play and play error', e); - this.player.pause().then(() => { - reject(e); - }); - }); - } - } else { - // url 发生改变了 - this.player.pause().then(() => { - // 清除 画面 - this.clearView(); - - this._play(url, options).then(() => { - resolve(); - }).catch(e => { - this.debug.warn('jessibuca', 'this._play error', e); - reject(e); - }); - }).catch(e => { - this.debug.warn('jessibuca', 'this._opt.url is null and pause error', e); - reject(e); - }); - } - } else { - this._play(url, options).then(() => { - resolve(); - }).catch(e => { - this.debug.warn('jessibuca', 'this._play error', e); - reject(e); - }); - } - } else { - // url 不存在的时候 - // 就是从 play -> pause -> play - this.player.play(this._opt.url, this._opt.playOptions).then(() => { - resolve(); // 恢复下之前的音量 - - this.player.resumeAudioAfterPause(); - }).catch(e => { - this.debug.warn('jessibuca', 'url is null and play error', e); - this.player.pause().then(() => { - reject(e); - }); - }); - } - }); - } - /** - * - * @param url {string} - * @param options {object} - * @returns {Promise} - * @private - */ - - - _play(url) { - let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - return new Promise((resolve, reject) => { - this._opt.url = url; - this._opt.playOptions = options; // 新的url - - const isHttp = url.indexOf("http") === 0; // - - const protocol = isHttp ? PLAYER_PLAY_PROTOCOL.fetch : PLAYER_PLAY_PROTOCOL.websocket; // - - const demuxType = isHttp || url.indexOf(".flv") !== -1 || this._opt.isFlv ? DEMUX_TYPE.flv : DEMUX_TYPE.m7s; - this.player.updateOption({ - protocol, - demuxType - }); - this.player.once(EVENTS_ERROR.webglAlignmentError, () => { - this.pause().then(() => { - this.debug.log('Jessibuca', 'webglAlignmentError'); - - this._resetPlayer({ - openWebglAlignment: true - }); - - this.play(url, options).then(() => { - // resolve(); - this.debug.log('Jessibuca', 'webglAlignmentError and play success'); - }).catch(() => { - // reject(); - this.debug.log('Jessibuca', 'webglAlignmentError and play error'); - }); - }); - }); - this.player.once(EVENTS_ERROR.mediaSourceH265NotSupport, () => { - this.pause().then(() => { - if (this.player._opt.autoWasm) { - this.debug.log('Jessibuca', 'auto wasm [mse-> wasm] reset player and play'); - - this._resetPlayer({ - useMSE: false - }); - - this.play(url, options).then(() => { - // resolve(); - this.debug.log('Jessibuca', 'auto wasm [mse-> wasm] reset player and play success'); - }).catch(() => { - // reject(); - this.debug.log('Jessibuca', 'auto wasm [mse-> wasm] reset player and play error'); - }); - } - }); - }); // media source full error - - this.player.once(EVENTS_ERROR.mediaSourceFull, () => { - this.pause().then(() => { - this.debug.log('Jessibuca', 'media source full'); - - this._resetPlayer(); - - this.play(url, options).then(() => { - // resolve(); - this.debug.log('Jessibuca', 'media source full and reset player and play success'); - }).catch(() => { - // reject(); - this.debug.warn('Jessibuca', 'media source full and reset player and play error'); - }); - }); - }); // media source append buffer error - - this.player.once(EVENTS_ERROR.mediaSourceAppendBufferError, () => { - this.pause().then(() => { - this.debug.log('Jessibuca', 'media source append buffer error'); - - this._resetPlayer(); - - this.play(url, options).then(() => { - // resolve(); - this.debug.log('Jessibuca', 'media source append buffer error and reset player and play success'); - }).catch(() => { - // reject(); - this.debug.warn('Jessibuca', 'media source append buffer error and reset player and play error'); - }); - }); - }); - this.player.once(EVENTS_ERROR.mediaSourceBufferListLarge, () => { - this.pause().then(() => { - this.debug.log('Jessibuca', 'media source buffer list large'); - - this._resetPlayer(); - - this.play(url, options).then(() => { - // resolve(); - this.debug.log('Jessibuca', 'media source buffer list large and reset player and play success'); - }).catch(() => { - // reject(); - this.debug.warn('Jessibuca', 'media source buffer list large and reset player and play error'); - }); - }); - }); - this.player.once(EVENTS_ERROR.mediaSourceAppendBufferEndTimeout, () => { - this.pause().then(() => { - this.debug.log('Jessibuca', 'media source append buffer end timeout'); - - this._resetPlayer(); - - this.play(url, options).then(() => { - // resolve(); - this.debug.log('Jessibuca', 'media source append buffer end timeout and reset player and play success'); - }).catch(() => { - // reject(); - this.debug.warn('Jessibuca', 'media source append buffer end timeout and reset player and play error'); - }); - }); - }); - this.player.once(EVENTS_ERROR.mseSourceBufferError, () => { - this.pause().then(() => { - this.debug.log('Jessibuca', 'mseSourceBufferError close success'); - }); - }); // - - this.player.once(EVENTS_ERROR.webcodecsH265NotSupport, () => { - this.pause().then(() => { - if (this.player._opt.autoWasm) { - this.debug.log('Jessibuca', 'auto wasm [wcs-> wasm] reset player and play'); - - this._resetPlayer({ - useWCS: false - }); - - this.play(url, options).then(() => { - // resolve(); - this.debug.log('Jessibuca', 'auto wasm [wcs-> wasm] reset player and play success'); - }).catch(() => { - // reject(); - this.debug.warn('Jessibuca', 'auto wasm [wcs-> wasm] reset player and play error'); - }); - } - }); - }); // webcodecs - - this.player.once(EVENTS_ERROR.webcodecsWidthOrHeightChange, () => { - this.pause().then(() => { - this.debug.log('Jessibuca', 'webcodecs Width Or Height Change reset player and play'); - - this._resetPlayer({ - useWCS: true - }); - - this.play(url, options).then(() => { - // resolve(); - this.debug.log('Jessibuca', 'webcodecs Width Or Height Change reset player and play success'); - }).catch(() => { - // reject(); - this.debug.warn('Jessibuca', 'webcodecs Width Or Height Change reset player and play error'); - }); - }); - }); // webcodecs - - this.player.once(EVENTS_ERROR.webcodecsDecodeError, () => { - this.pause().then(() => { - if (this.player._opt.autoWasm) { - this.debug.log('Jessibuca', 'webcodecs decode error reset player and play'); - - this._resetPlayer({ - useWCS: false - }); - - this.play(url, options).then(() => { - // resolve(); - this.debug.log('Jessibuca', 'webcodecs decode error reset player and play success'); - }).catch(() => { - // reject(); - this.debug.warn('Jessibuca', 'webcodecs decode error reset player and play error'); - }); - } - }); - }); // wasm。 - - this.player.once(EVENTS_ERROR.wasmDecodeError, () => { - if (this.player._opt.wasmDecodeErrorReplay) { - this.pause().then(() => { - this.debug.log('Jessibuca', 'wasm decode error and reset player and play'); - - this._resetPlayer({ - useWCS: false - }); - - this.play(url, options).then(() => { - // resolve(); - this.debug.log('Jessibuca', 'wasm decode error and reset player and play success'); - }).catch(() => { - // reject(); - this.debug.warn('Jessibuca', 'wasm decode error and reset player and play error'); - }); - }); - } - }); // 监听 delay timeout - - this.player.on(EVENTS.delayTimeout, () => { - if (this.player._opt.heartTimeoutReplay && (this._heartTimeoutReplayTimes < this.player._opt.heartTimeoutReplayTimes || this.player._opt.heartTimeoutReplayTimes === -1)) { - this.debug.log('Jessibuca', `delay timeout replay time is ${this._heartTimeoutReplayTimes}`); - this._heartTimeoutReplayTimes += 1; - this.play(url, options).then(() => { - // resolve(); - this._heartTimeoutReplayTimes = 0; - }).catch(() => {// reject(); - }); - } - }); // 监听 loading timeout - - this.player.on(EVENTS.loadingTimeout, () => { - if (this.player._opt.loadingTimeoutReplay && (this._loadingTimeoutReplayTimes < this.player._opt.loadingTimeoutReplayTimes || this.player._opt.loadingTimeoutReplayTimes === -1)) { - this.debug.log('Jessibuca', `loading timeout replay time is ${this._loadingTimeoutReplayTimes}`); - this._loadingTimeoutReplayTimes += 1; - this.play(url, options).then(() => { - // resolve(); - this._loadingTimeoutReplayTimes = 0; - }).catch(() => {// reject(); - }); - } - }); - - if (this.hasLoaded()) { - this.player.play(url, options).then(() => { - resolve(); - }).catch(e => { - this.debug.warn('Jessibuca', 'hasLoaded and play error', e); - this.player && this.player.pause().then(() => { - reject(e); - }); - }); - } else { - this.player.once(EVENTS.decoderWorkerInit, () => { - this.player.play(url, options).then(() => { - resolve(); - }).catch(e => { - this.debug.warn('Jessibuca', 'decoderWorkerInit and play error', e); - this.player && this.player.pause().then(() => { - reject(e); - }); - }); - }); - } - }); - } - /** - * - */ - - - resize() { - this.player.resize(); - } - /** - * - * @param time {number} s - */ - - - setBufferTime(time) { - time = Number(time); // s -> ms - - this.player.updateOption({ - videoBuffer: time * 1000 - }); // update worker config - - this.player.decoderWorker && this.player.decoderWorker.updateWorkConfig({ - key: 'videoBuffer', - value: time * 1000 - }); - } - /** - * - * @param deg {number} - */ - - - setRotate(deg) { - deg = parseInt(deg, 10); - const list = [0, 90, 180, 270]; - - if (this._opt.rotate === deg || list.indexOf(deg) === -1) { - return; - } - - this.player.updateOption({ - rotate: deg - }); - this.resize(); - } - /** - * - * @returns {boolean} - */ - - - hasLoaded() { - return this.player.loaded; - } - /** - * - */ - - - setKeepScreenOn() { - this.player.updateOption({ - keepScreenOn: true - }); - } - /** - * - * @param flag {Boolean} - */ - - - setFullscreen(flag) { - const fullscreen = !!flag; - - if (this.player.fullscreen !== fullscreen) { - this.player.fullscreen = fullscreen; - } - } - /** - * - * @param filename {string} - * @param format {string} - * @param quality {number} - * @param type {string} download,base64,blob - */ - - - screenshot(filename, format, quality, type) { - if (!this.player.video) { - return ''; - } - - return this.player.video.screenshot(filename, format, quality, type); - } - /** - * - * @param fileName {string} - * @param fileType {string} - * @returns {Promise} - */ - - - startRecord(fileName, fileType) { - return new Promise((resolve, reject) => { - if (this.player.playing) { - this.player.startRecord(fileName, fileType); - resolve(); - } else { - reject(); - } - }); - } - - stopRecordAndSave() { - if (this.player.recording) { - this.player.stopRecordAndSave(); - } - } - /** - * - * @returns {Boolean} - */ - - - isPlaying() { - return this.player ? this.player.playing : false; - } - /** - * 是否静音状态 - * @returns {Boolean} - */ - - - isMute() { - return this.player.audio ? this.player.audio.isMute : true; - } - /** - * 是否在录制视频 - * @returns {*} - */ - - - isRecording() { - return this.player.recorder.recording; - } - - } - - _defineProperty(Jessibuca, "ERROR", EVENTS_ERROR); - - _defineProperty(Jessibuca, "TIMEOUT", { - loadingTimeout: EVENTS.loadingTimeout, - delayTimeout: EVENTS.delayTimeout - }); - - window.Jessibuca = Jessibuca; - - return Jessibuca; - -})); -//# sourceMappingURL=jessibuca.js.map diff --git a/src/components/townDetail/topCenter.vue b/src/components/townDetail/topCenter.vue index 17c6de1..536ac7f 100644 --- a/src/components/townDetail/topCenter.vue +++ b/src/components/townDetail/topCenter.vue @@ -307,10 +307,24 @@ opacity: 0; } } +@keyframes jump { + 0% { + transform: translateY(0); + } + + 50% { + transform: translateY(-10px); + } + + 100% { + transform: translateY(0); + } +} \ No newline at end of file diff --git a/src/components/townDetail/topRight.vue b/src/components/townDetail/topRight.vue new file mode 100644 index 0000000..51aae34 --- /dev/null +++ b/src/components/townDetail/topRight.vue @@ -0,0 +1,140 @@ + + + \ No newline at end of file diff --git a/src/components/townDetail/version.js b/src/components/townDetail/version.js deleted file mode 100644 index 0e66498..0000000 --- a/src/components/townDetail/version.js +++ /dev/null @@ -1 +0,0 @@ -export const VERSION = '#VERSION#' \ No newline at end of file diff --git a/src/main.js b/src/main.js index ff021f5..ce09e00 100644 --- a/src/main.js +++ b/src/main.js @@ -2,11 +2,20 @@ import { createApp } from 'vue' import App from './App.vue' import dataV from '@jiaminghi/data-view' import router from "./router"; +import ElementPlus from 'element-plus' + + +import { + createPinia +} from 'pinia' +import 'element-plus/dist/index.css' // import '@/components/townDetail/test.js' import 'amfe-flexible' - +const pinia = createPinia() const app = createApp(App) app.use(router) app.use(dataV) +app.use(pinia) +app.use(ElementPlus) app.mount('#app') \ No newline at end of file diff --git a/src/router/index.js b/src/router/index.js index 1d0f511..d756152 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -3,41 +3,58 @@ import { createRouter, createWebHistory } from 'vue-router' const routes = [ { path: '/', - name: 'index', - component: () => import('@/view/index.vue'), + name: 'home', + component: () => import('@/view/home.vue'), + redirect: "/index", + children: [ + { + path: '/index', + name: 'index', + component: () => import('@/view/index.vue'), + }, + + { + path: '/townDetail', + name: 'townDetail', + component: () => import('@/view/townDetail.vue'), + }, + { + path: '/commodity', + name: 'commodity', + component: () => import('@/view/commodity.vue'), + }, + { + path: '/Businesses', + name: 'commBusinessesodity', + component: () => import('@/view/Businesses.vue'), + }, + { + path: '/storeLogin', + name: 'storeLogin', + component: () => import('@/view/storeLogin.vue'), + }, + { + path: '/order', + name: 'order', + component: () => import('@/view/order.vue'), + }, + { + path: '/finance', + name: 'finance', + component: () => import('@/view/finance.vue'), + }, + + + ] }, { - path: '/townDetail', - name: 'townDetail', - component: () => import('@/view/townDetail.vue'), - }, - { - path: '/commodity', - name: 'commodity', - component: () => import('@/view/commodity.vue'), - }, - { - path: '/Businesses', - name: 'commBusinessesodity', - component: () => import('@/view/Businesses.vue'), - }, - { - path: '/storeLogin', - name: 'storeLogin', - component: () => import('@/view/storeLogin.vue'), - }, - { - path: '/order', - name: 'order', - component: () => import('@/view/order.vue'), - }, - { - path: '/finance', - name: 'finance', - component: () => import('@/view/finance.vue'), + path: '/login', + name: 'login', + component: () => import('@/view/login.vue'), }, + ] const router = createRouter({ diff --git a/src/store/index.js b/src/store/index.js new file mode 100644 index 0000000..70cc495 --- /dev/null +++ b/src/store/index.js @@ -0,0 +1,19 @@ +import { + defineStore +} from 'pinia' + +export const areaObj = defineStore('counter', { + state: () => ({ + area: { + areaCode: 510524, + streetCode: 51052410 + + } + }), + getters: {}, + actions: { + changeArea(obj) { + this.area = obj + } + } +}) \ No newline at end of file diff --git a/src/view/Businesses.vue b/src/view/Businesses.vue index d7c346f..4eed99d 100644 --- a/src/view/Businesses.vue +++ b/src/view/Businesses.vue @@ -4,8 +4,8 @@
-
正常开启的商户
-
已关闭的商户
+
正常开启的商户
+
已关闭的商户
@@ -21,24 +21,24 @@ // 102B3 import { ref, reactive, onMounted } from "vue" import { useRouter } from 'vue-router' -import {merchant_listApi} from "@/api.js" +import { merchant_listApi } from "@/api.js" const route = useRouter() -let status =ref(0) +let status = ref(0) -const changeTable=(num)=>{ - - configs.data.splice(0,configs.data.length) +const changeTable = (num) => { + + configs.data.splice(0, configs.data.length) console.log(configs.data) - status.value=num + status.value = num merchant_listApi( { - areaCode: 510524, - streetCode: 510524100, - status:status.value, - limit:50 - } - ).then(res=>{ + areaCode: 510524, + streetCode: 510524100, + status: status.value, + limit: 50 + } + ).then(res => { addConfigData(res.data.list) }) @@ -58,36 +58,36 @@ const hdClick = (e) => { } const addConfigData = (data) => { - data.forEach((item,i)=>{ + data.forEach((item, i) => { if (i % 2 == 0) { configs.data.push( [ - `
${item.mer_id}
`, + `
${i + 1}
`, `
${item.mer_name}
`, `
${item.real_name}
`, `
${item.mer_address}
`, - `
${item.mark}
`, + `
${item.mark || '--'}
`, `
${item.is_best}
`, `
${item.create_time}
`, - `
sd
`, + `
${item.margin}
`, `
${item.merchantCategory.category_name}
`, - `
${item.status?'开启':'关闭'}
`, + `
${item.status ? '开启' : '关闭'}
`, `
登录
`, ] ) } else { configs.data.push( [ - `
${item.mer_id}
`, + `
${i + 1}
`, `
${item.mer_name}
`, `
${item.real_name}
`, `
${item.mer_address}
`, - `
${item.mark}
`, + `
${item.mark || "--"}
`, `
${item.is_best}
`, `
${item.create_time}
`, - `
排序
`, + `
${item.margin}
`, `
${item.merchantCategory.category_name}
`, - `
${item.status?'开启':'关闭'}
`, + `
${item.status ? '开启' : '关闭'}
`, `
登录
`, ] @@ -96,10 +96,10 @@ const addConfigData = (data) => { } }) - - } + +} const configs = reactive({ headerBGC: "linear-gradient(to right, #ff0000, #00ff00)", @@ -123,7 +123,7 @@ const configs = reactive({ ] }) onMounted(() => { - changeTable(0) + changeTable(1) }) diff --git a/src/view/commodity.vue b/src/view/commodity.vue index 552e282..bea69ac 100644 --- a/src/view/commodity.vue +++ b/src/view/commodity.vue @@ -10,15 +10,16 @@
-
出售中商品
-
仓库中商品
-
待审核商品
-
审核未通过商品
+
{{ item.name }}
+
-
+
@@ -29,12 +30,13 @@ diff --git a/src/view/finance.vue b/src/view/finance.vue index 8271bb7..c7ceacf 100644 --- a/src/view/finance.vue +++ b/src/view/finance.vue @@ -7,13 +7,14 @@
提现记录
资金记录
-
账单管理
+
账单管理
-
日账单
-
月账单
+
日账单 +
+
月账单
@@ -24,10 +25,11 @@
- - + style="width:95vw;height:78vh" /> + +
@@ -38,23 +40,24 @@
日账单 - 2011.11-06 + {{ detailData.date }} 关闭
- 订单收入总金额: 16.6 - 2笔 + 订单收入总金额: {{ detailData.income.number + }} + {{ detailData.income.count }}
-
- 订单支付: 16.6 - 2笔 +
+ {{ item[0] }} {{ + item[1].slice(0, -1) }} + {{ item[2] }}
-
+
- 充值金额: 5000 2笔 + 充值金额: {{ detailData.bill.number + }} {{ + detailData.bill.count }}
- 支出总金额: 5000 2笔 + 支出总金额: {{ detailData.expend.number + }} {{ detailData.expend.count }}
@@ -76,19 +82,19 @@
- 应付商户金额 16.6 - 2笔 + 应付商户金额 {{ + detailData.expend.data[0][1].slice(0, -1) }} + {{ detailData.expend.data[0][2] }}
- 佣金 16.6 - 2笔 + 佣金 {{ + detailData.expend.data[1][1].slice(0, -1) }} + {{ detailData.expend.data[1][2] }}
- 返回手续费 16.6 - 2笔 + 返回手续费 {{ + detailData.expend.data[2][1].slice(0, -1) }} + {{ detailData.expend.data[2][2] }}
@@ -98,14 +104,14 @@
- 优惠券补贴 16.6 - 2笔 + 优惠券补贴 {{ + detailData.expend.data[3][1].slice(0, -1) }} + {{ detailData.expend.data[3][2] }}
- 会员优惠券补贴 16.6 - 2笔 + 会员优惠券补贴 {{ + detailData.expend.data[4][1].slice(0, -1) }} + {{ detailData.expend.data[4][2] }}
返回手续费
- 平台手续费收入总金额: 16.6 元 + 平台手续费收入总金额: {{ detailData.charge.number }}
@@ -132,7 +138,13 @@ import { ref, reactive, onMounted } from "vue" import { useRouter } from 'vue-router' import Bill from "@/components/Bill.vue" +import { financial_record_titleApi, financial_recordApi, financial_record_detailApi, financial_record_detailApi2, bill_listApi, withdraw_listApi } from "@/api.js" +const billType = ref(1) +const changeBill = (num) => { + billType.value = num + changeTable(2, num) +} const billList = reactive([ { @@ -207,81 +219,67 @@ const billList = reactive([ }, ]) +financial_record_titleApi( + { + areaCode: 510524, + streetCode: 510524100 + } + +).then(res => { + res.data.stat.forEach((item, i) => { + billList[i].num = item.count + + + }) +}) +const changeTable = (type, num) => { + + if (type == 0) { + configs.data.splice(0, configs.data.length) + withdraw_listApi( + { + areaCode: 510524, + streetCode: 510524100, + limit: 50, + } + ).then(res => { + addDta(res.data.list) + }) + } + if (type == 1) { + configs1.data.splice(0, configs1.data.length) + bill_listApi( + { + areaCode: 510524, + streetCode: 510524100, + limit: 50, + } + ).then(res => { + addDta(res.data.list) + }) + } + + if (type == 2) { + configs2.data.splice(0, configs2.data.length) + financial_recordApi({ + areaCode: 510524, + streetCode: 510524100, + limit: 50, + type: num + }).then(res => { + billData = res.data.list + addDta(res.data.list) + }) + + } -const route = useRouter() -const ShwostoreType = ref(false) -const changeTable = (type) => { headerIndex.value = type addConfigHead() - a(configList[headerIndex.value], congigData[headerIndex.value]) + // a(configList[headerIndex.value], congigData[headerIndex.value]) } -const congigData = reactive( - [ - { - d: [`
排序
`, - `
排sd序
`, - `
排序
`, - `
排序
`, - `
排序
`, - `
排序
`, - `
排序
`, - `
排序
`, - `
排序
`, - `
排序
`, - `
排序
`, - `
排序
`,], - q: [`
排序
`, - `
排sd序
`, - `
排序
`, - `
排序
`, - `
排序
`, - `
排序
`, - `
排序
`, - `
排序
`, - `
排序
`, - `
排序
`, - `
排序
`, - `
排序
`,] - }, - { - d: [`
排序
`, - `
排sd序
`, - `
排序
`, - `
排序
`, - `
排序
`, - `
排序
`, - ], - q: [`
排序
`, - `
排sd序
`, - `
排序
`, - `
排序
`, - `
排序
`, - `
排序
`, - ] - }, - { - d: [`
排序
`, - `
排sd序
`, - `
排序
`, - `
排序
`, - `
排序
`, - `
详情
`, - ], - q: [`
排序
`, - `
排sd序
`, - `
排序
`, - `
排序
`, - `
排序
`, - `
详情
`, - ] - } - ] - - -) const hdClick = (e) => { if (e.columnIndex == 10) { // route.push('/storeLogin') @@ -291,21 +289,113 @@ const hdClick = (e) => { } +const addDta = (data) => { -const a = (aaa, data) => { - for (let i = 0; i < 100; i++) { - if (i % 2 == 0) { - aaa.data.push(data.d) - } else { - aaa.data.push(data.q) + data.reverse().forEach((item, i) => { + if (headerIndex.value == 0) { + if (i % 2 == 0) { + configs1.data.push( + + [ + `
${i + 1}
`, + `
${i + 1}
`, + `
${item.real_name}
`, + `
${item.number}
`, + `
${item.title}
`, + `
${item.mark}
`, + `
${item.create_time}
`, + ], + ) + } else { + configs1.data.push( + [`
${item.uid}
`, + `
${item.nickname}
`, + `
${item.number}
`, + `
${item.title}
`, + `
${item.mark}
`, + `
${item.create_time}
`, + + ] + ) + } } - } + + + + if (headerIndex.value == 1) { + + if (i % 2 == 0) { + + configs1.data.push( + + [`
${item.uid}
`, + `
${item.nickname}
`, + `
${item.number}
`, + `
${item.title}
`, + `
${item.mark}
`, + `
${item.create_time}
`, + ], + ) + } else { + configs1.data.push( + [`
${item.uid}
`, + `
${item.nickname}
`, + `
${item.number}
`, + `
${item.title}
`, + `
${item.mark}
`, + `
${item.create_time}
`, + + ] + ) + } + + + + } + if (headerIndex.value == 2) { + + if (i % 2 == 0) { + + configs2.data.push( + + [`
${i + 1}
`, + `
${item.time}
`, + `
${item.income}
`, + `
${item.expend}
`, + `
${item.charge}
`, + `
详情
`, + ], + ) + } else { + configs2.data.push( + [`
${i + 1}
`, + `
${item.time}
`, + `
${item.income}
`, + `
${item.expend}
`, + `
${item.charge}
`, + `
详情
`, + ] + ) + } + + } + + + }) + + + } + + + + const configs = reactive({ headerBGC: "linear-gradient(to right, #ff0000, #00ff00)", oddRowBGC: '', evenRowBGC: "", rowNum: 20, + columnWidth: [], header: [ ], data: [ @@ -355,10 +445,40 @@ const addConfigHead = () => { } + +let billData = [] // 详情弹窗 const showPop = ref(false) + +const detailData = reactive({}) + const hdClick3 = (e) => { if (e.columnIndex == 5) { + + if (billType.value == 1) { + financial_record_detailApi( + { areaCode: 510524, streetCode: 510524100, date: billData[e.rowIndex].time } + ).then(res => { + + for (let key in res.data) { + detailData[key] = res.data[key] + } + + }) + } + if (billType.value == 2) { + financial_record_detailApi2( + { areaCode: 510524, streetCode: 510524100, date: billData[e.rowIndex].time } + ).then(res => { + + for (let key in res.data) { + detailData[key] = res.data[key] + } + + }) + } + + showPop.value = true } @@ -367,8 +487,15 @@ const hdClick3 = (e) => { onMounted(() => { - a(configs, congigData[headerIndex.value]) + changeTable(0) addConfigHead() + financial_recordApi({ + areaCode: 510524, + streetCode: 510524100, + limit: 50 + }).then(res => { + addDta(res.data.list) + }) }) @@ -439,7 +566,6 @@ onMounted(() => { .table { width: 100%; - height: 60vh; margin-top: 2vh; } } diff --git a/src/view/home.vue b/src/view/home.vue new file mode 100644 index 0000000..1fb0fe3 --- /dev/null +++ b/src/view/home.vue @@ -0,0 +1,13 @@ + + + + + diff --git a/src/view/index.vue b/src/view/index.vue index ed130f6..ba94cf7 100644 --- a/src/view/index.vue +++ b/src/view/index.vue @@ -27,8 +27,6 @@ + diff --git a/src/view/order.vue b/src/view/order.vue index 1855707..b18ee18 100644 --- a/src/view/order.vue +++ b/src/view/order.vue @@ -4,6 +4,7 @@
+
订单列表
退款单
核销订单
@@ -13,20 +14,20 @@
-
+
+ style="width:95vw;height:70vh" /> + style="width:95vw;height:77vh" /> + style="width:95vw;height:89vh" />
@@ -39,7 +40,9 @@ // 102B3 import { ref, reactive, onMounted } from "vue" import Bill from "@/components/Bill.vue" -import { order_listApi, order_list_count_titleApi, refund_order_listApi, take_order_listApi,take_order_count_titleApi } from "@/api.js" +import { order_listApi, order_list_count_titleApi, refund_order_listApi, take_order_listApi, take_order_count_titleApi } from "@/api.js" + +const value = ref(1) const billList = reactive([ { @@ -78,6 +81,28 @@ const billList1 = reactive([ ]) +const orderStatus = (status) => { + if (status == 0) return "待发货" + if (status == 1) return "待收货" + if (status == 2) return "待评价" + if (status == 3) return "已完成" + if (status == 9) return "拼团中" + if (status == 10) return "待付尾款" + if (status == 11) return "尾款超时未付" + if (status == -1) return "已退款" +} + +const payType = (type) => { + if (type == 0) return "余额支付" + if (type == 1) return "微信支付" + if (type == 2) return "小程序支付" + if (type == 3) return "h5支付" + if (type == 4) return "支付宝支付" + if (type == 5) return "支付宝扫码支付" + if (type == 6) return "微信扫码支付" + if (type == 8) return "信用购-先货后款" +} + const btnFlag = reactive([true, false, false]) @@ -145,7 +170,7 @@ const hdClick = (i) => { streetCode: 510524100, limit: 50 } - ).then(res=>{ + ).then(res => { res.data.forEach((item, index) => { billList[index].num = item.count @@ -192,7 +217,6 @@ const tableHdClick = (e) => { const addConfigData = (data) => { data.forEach((item, i) => { - if (i % 2 == 0) { configs.data.push( [ @@ -215,7 +239,7 @@ const addConfigData = (data) => {
总单号: - 35356565656565 + ${item.groupOrder.group_order_sn}
@@ -226,7 +250,7 @@ const addConfigData = (data) => {
商品总价 - 2023.11.214 + ${item.total_price}
@@ -238,27 +262,27 @@ const addConfigData = (data) => { 用户备注 2023.11.214
-
商家备注 2023.11.214
- -
- -
`, - `
排sd序
`, - `
排序
`, - `
排序
`, + `
${item.real_name}
`, + `
${item.user.nickname}
`, `
排序
`, + `
${item.merchant.mer_name}
`, + + `
+ + ${item.orderProduct[0].cart_info.product.store_name} + ${item.orderProduct[0].cart_info.product.unit_name} +
`, `
${item.cost}
`, - `
排序
`, - `
${item.status}
`, - `
排序
`, - `
${item.pay_type}
`, + `
${orderStatus(item.status)}
`, + `
${item.user_address}
`, + `
${payType(item.pay_type)}
`, ] ) } else { @@ -275,7 +299,7 @@ const addConfigData = (data) => { top:50%; left:0 '> -
1111
+ ${item.order_sn} `, - `
排sd序
`, - `
排序
`, - `
排序
`, - `
排序
`, - `
排序
`, - `
排序
`, - `
排序
`, - `
排序
`, + `
${item.real_name}
`, + `
${item.user.nickname}
`, `
排序
`, + `
${item.merchant.mer_name}
`, + `
+ + ${item.orderProduct[0].cart_info.product.store_name} + ${item.orderProduct[0].cart_info.product.unit_name} + + + + +
`, + `
${item.cost}
`, + `
${orderStatus(item.status)}
`, + `
${item.user_address}
`, + `
${payType(item.pay_type)}
`, ] @@ -407,12 +439,20 @@ const addConfigData2 = (data) => { `
排序
`, `
排序
`, `
${item.refund_price}
`, - `
排序
`, + `
+ + + ${item.refundProduct[0].product.cart_info.product.store_name} + + + ${item.refundProduct[0].product.cart_info.product.unit_name} + +
`, `
${item.status} 退款原因: ${item.refund_message} - 状态变更时间 ${item.status_tiem} + 状态变更时间 ${item.status_time} @@ -481,11 +521,23 @@ const addConfigData2 = (data) => { `
排序
`, `
排序
`, `
排序
`, - `
排序
`, - `
排序
`, - `
${item.status} + `
${item.refund_price}
`, + `
+ + + ${item.refundProduct[0].product.cart_info.product.store_name} + + + ${item.refundProduct[0].product.cart_info.product.unit_name} + +
`, + `
+ ${item.status} 退款原因: ${item.refund_message} - 状态变更时间 ${item.status_tiem}
`, + 状态变更时间 ${item.status_time} + + +
`, ] @@ -569,13 +621,9 @@ const addConfigData3 = (data) => { `
${item.refund_price}
`, `
排序
`, `
- ${item.status} 退款原因: ${item.refund_message} 状态变更时间 ${item.status_tiem} - - -
`, `
排序
`, `
排序
`, @@ -596,8 +644,7 @@ const addConfigData3 = (data) => { top:50%; left:0 '> -
${item.refund_order_sn} - + ${item.order_sn} `, - `
排sd序
`, + `
核销订单
`, `
排序
`, `
排序
`, `
排序
`, @@ -672,6 +719,8 @@ const configs = reactive({ oddRowBGC: '', evenRowBGC: "", rowNum: 20, + + columnWidth: [250, 170, 170, 170, 170, 500, 170, 170, 170, 200], header: [ `
订单编号
`, `
用户信息
`, @@ -681,7 +730,7 @@ const configs = reactive({ `
商品信息
`, `
实际支付
`, `
订单状态
`, - `
订单数属地
`, + `
订单属地
`, `
支付类型
`, ], data: [ @@ -689,6 +738,8 @@ const configs = reactive({ }) const configs2 = reactive({ + columnWidth: [300, 150, 150, 150, 150, 150, 600, 600], + headerBGC: "linear-gradient(to right, #ff0000, #00ff00)", oddRowBGC: '', evenRowBGC: "", @@ -710,6 +761,8 @@ const configs3 = reactive({ headerBGC: "linear-gradient(to right, #ff0000, #00ff00)", oddRowBGC: '', evenRowBGC: "", + columnWidth: [300, 150, 150, 150, 150, 150, 600, 150, 150, 150], + rowNum: 20, header: [ `
订单编号
`, @@ -757,6 +810,7 @@ onMounted(() => { width: 100%; height: 100%; padding: 2vw; + // box-sizing: border-box; } } @@ -790,7 +844,6 @@ onMounted(() => { .table { width: 100%; - height: 70vh; margin-top: 3vh; } } @@ -803,6 +856,7 @@ onMounted(() => { :deep(.header-item) { padding: 0 !important; + // justify-content: space-between; } diff --git a/src/view/townDetail.vue b/src/view/townDetail.vue index ff5782f..31132e0 100644 --- a/src/view/townDetail.vue +++ b/src/view/townDetail.vue @@ -1,145 +1,21 @@