├── .editorconfig ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── package.json ├── server.js ├── src ├── ReactH265Player.js ├── common.js ├── decoder.js ├── downloader.js ├── libffmpeg.js ├── libffmpeg.wasm ├── pcm-player.js ├── player.js └── webgl.js ├── webpack.config.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | indent_size = 2 7 | 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | lib 3 | scripts 4 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 5 | 6 | # dependencies 7 | /node_modules 8 | /.pnp 9 | .pnp.js 10 | 11 | # testing 12 | /coverage 13 | 14 | # production 15 | /build 16 | 17 | # misc 18 | .DS_Store 19 | .env.local 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .editorconfig 2 | .eslintrc 3 | .travis.yml 4 | 5 | karma.conf.js 6 | server.js 7 | webpack.config.js 8 | 9 | demo 10 | scripts 11 | src 12 | 13 | 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | directories: 4 | - node_modules 5 | notifications: 6 | email: false 7 | node_js: 8 | - "iojs" 9 | - "4.1" 10 | - "4.0" 11 | - "0.12" 12 | - "0.11" 13 | - "0.10" 14 | 15 | env: 16 | global: 17 | - NODE_ENV=test 18 | 19 | before_install: 20 | - npm i -g npm@^2.0.0 21 | script: 22 | - npm test 23 | before_script: 24 | - npm prune 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Bhargav Anand 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React H265 WASM Player 2 | 3 | A higher-level react component to play h265 video 4 | 5 | 6 | ## About 7 | 8 | ## Usage 9 | 10 | ### NPM and Webpack/Browserify 11 | Install via `npm`. Use `--save` to include it in your *package.json*. 12 | 13 | ```bash 14 | npm install react-h265-wasm-player 15 | ``` 16 | 17 | Start by importing/requiring react-h265-wasm-player within your react code. 18 | 19 | ```js 20 | import ReactH265Player from 'react-h265-wasm-player'; 21 | 22 | import React, { useState } from 'react'; 23 | import ReactH265Player from './ReactH265Player' 24 | 25 | function App() { 26 | const [playerRef, setPlayerRef] = useState(null); 27 | 28 | return ( 29 |
30 |
31 | { 37 | console.log("play error " + e.error + " status " + e.status + "."); 38 | if (e.error === 1) { 39 | // finish 40 | } 41 | }} 42 | passRef={ref => setPlayerRef(ref)} /> 43 | 44 |
45 | 48 | 51 | 54 |
55 |
56 |
57 | ); 58 | } 59 | 60 | export default App; 61 | ``` 62 | 63 | ### In `index.html` 64 | 65 | Because decode job is distributed in JS worker, for better performance 66 | You need to add these to your html container. 67 | 68 | ``` 69 | 70 | 71 | 72 | 73 | ``` 74 | 75 | ## Todo 76 | - Add vendor prefixes 77 | - Find a good way for wasm loading 78 | 79 | ## Issues 80 | Feel free to contribute. Submit a Pull Request or open an issue for further discussion. 81 | 82 | ## License 83 | MIT © [tcper][tcper] 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-h265-wasm-player", 3 | "version": "0.0.1", 4 | "description": "Workable h265 player for react", 5 | "main": "src/ReactH265Player", 6 | "scripts": {}, 7 | "repository": { 8 | "type": "git", 9 | "url": "git+git@github.com:tcper/react-h265-wasm-player.git" 10 | }, 11 | "author": "whoisandie", 12 | "license": "MIT", 13 | "bugs": { 14 | "url": "https://github.com/tcper/react-h265-wasm-player/issues" 15 | }, 16 | "homepage": "https://github.com/tcper/react-h265-wasm-player", 17 | "dependencies": { 18 | "react": "^16.13.1", 19 | "react-dom": "^16.13.1" 20 | }, 21 | "devDependencies": { 22 | "webpack": "^4.42.1", 23 | "webpack-dev-server": "^3.10.3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var WebpackDevServer = require('webpack-dev-server'); 3 | var config = require('./webpack.config'); 4 | 5 | new WebpackDevServer(webpack(config), { 6 | contentBase: __dirname, 7 | hot: true, 8 | inline: true, 9 | stats: { 10 | chunkModules: false, 11 | colors: true, 12 | }, 13 | }).listen(config.port, config.ip, function (err) { 14 | if (err) { 15 | console.log(err); 16 | } 17 | 18 | console.log('Listening at ' + config.ip + ':' + config.port); 19 | }); 20 | -------------------------------------------------------------------------------- /src/ReactH265Player.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | class ReactH265Player extends Component { 4 | ref = React.createRef(); 5 | player = new window.Player(); 6 | state = { 7 | width: this.props.width, 8 | height: this.props.height 9 | }; 10 | 11 | componentDidMount() { 12 | // console.log(this.ref.current); 13 | const { 14 | passRef, 15 | url = "", 16 | bufferSize = 512 * 1024, 17 | isStream = false, 18 | errorHandler 19 | } = this.props; 20 | if (passRef) { 21 | passRef(this.player); 22 | } 23 | this.player.play(url, this.ref.current, errorHandler, bufferSize, isStream); 24 | } 25 | 26 | componentWillUnmount() { 27 | this.player.stop(); 28 | } 29 | 30 | render() { 31 | const { width, height } = this.state; 32 | return ; 33 | } 34 | } 35 | 36 | export default ReactH265Player; 37 | -------------------------------------------------------------------------------- /src/common.js: -------------------------------------------------------------------------------- 1 | //Player request. 2 | const kPlayVideoReq = 0; 3 | const kPauseVideoReq = 1; 4 | const kStopVideoReq = 2; 5 | 6 | //Player response. 7 | const kPlayVideoRsp = 0; 8 | const kAudioInfo = 1; 9 | const kVideoInfo = 2; 10 | const kAudioData = 3; 11 | const kVideoData = 4; 12 | 13 | //Downloader request. 14 | const kGetFileInfoReq = 0; 15 | const kDownloadFileReq = 1; 16 | const kCloseDownloaderReq = 2; 17 | 18 | //Downloader response. 19 | const kGetFileInfoRsp = 0; 20 | const kFileData = 1; 21 | 22 | //Downloader Protocol. 23 | const kProtoHttp = 0; 24 | const kProtoWebsocket = 1; 25 | 26 | //Decoder request. 27 | const kInitDecoderReq = 0; 28 | const kUninitDecoderReq = 1; 29 | const kOpenDecoderReq = 2; 30 | const kCloseDecoderReq = 3; 31 | const kFeedDataReq = 4; 32 | const kStartDecodingReq = 5; 33 | const kPauseDecodingReq = 6; 34 | const kSeekToReq = 7; 35 | 36 | //Decoder response. 37 | const kInitDecoderRsp = 0; 38 | const kUninitDecoderRsp = 1; 39 | const kOpenDecoderRsp = 2; 40 | const kCloseDecoderRsp = 3; 41 | const kVideoFrame = 4; 42 | const kAudioFrame = 5; 43 | const kStartDecodingRsp = 6; 44 | const kPauseDecodingRsp = 7; 45 | const kDecodeFinishedEvt = 8; 46 | const kRequestDataEvt = 9; 47 | const kSeekToRsp = 10; 48 | 49 | function Logger(module) { 50 | this.module = module; 51 | } 52 | 53 | Logger.prototype.log = function (line) { 54 | console.log("[" + this.currentTimeStr() + "][" + this.module + "]" + line); 55 | } 56 | 57 | Logger.prototype.logError = function (line) { 58 | console.log("[" + this.currentTimeStr() + "][" + this.module + "][ER] " + line); 59 | } 60 | 61 | Logger.prototype.logInfo = function (line) { 62 | console.log("[" + this.currentTimeStr() + "][" + this.module + "][IF] " + line); 63 | } 64 | 65 | Logger.prototype.logDebug = function (line) { 66 | console.log("[" + this.currentTimeStr() + "][" + this.module + "][DT] " + line); 67 | } 68 | 69 | Logger.prototype.currentTimeStr = function () { 70 | var now = new Date(Date.now()); 71 | var year = now.getFullYear(); 72 | var month = now.getMonth() + 1; 73 | var day = now.getDate(); 74 | var hour = now.getHours(); 75 | var min = now.getMinutes(); 76 | var sec = now.getSeconds(); 77 | var ms = now.getMilliseconds(); 78 | return year + "-" + month + "-" + day + " " + hour + ":" + min + ":" + sec + ":" + ms; 79 | } 80 | 81 | -------------------------------------------------------------------------------- /src/decoder.js: -------------------------------------------------------------------------------- 1 | self.Module = { 2 | onRuntimeInitialized: function () { 3 | onWasmLoaded(); 4 | } 5 | }; 6 | 7 | self.importScripts("common.js"); 8 | self.importScripts("libffmpeg.js"); 9 | 10 | function Decoder() { 11 | this.logger = new Logger("Decoder"); 12 | this.coreLogLevel = 1; 13 | this.accurateSeek = true; 14 | this.wasmLoaded = false; 15 | this.tmpReqQue = []; 16 | this.cacheBuffer = null; 17 | this.decodeTimer = null; 18 | this.videoCallback = null; 19 | this.audioCallback = null; 20 | this.requestCallback = null; 21 | } 22 | 23 | Decoder.prototype.initDecoder = function (fileSize, chunkSize) { 24 | var ret = Module._initDecoder(fileSize, this.coreLogLevel); 25 | this.logger.logInfo("initDecoder return " + ret + "."); 26 | if (0 == ret) { 27 | this.cacheBuffer = Module._malloc(chunkSize); 28 | } 29 | var objData = { 30 | t: kInitDecoderRsp, 31 | e: ret 32 | }; 33 | self.postMessage(objData); 34 | }; 35 | 36 | Decoder.prototype.uninitDecoder = function () { 37 | var ret = Module._uninitDecoder(); 38 | this.logger.logInfo("Uninit ffmpeg decoder return " + ret + "."); 39 | if (this.cacheBuffer != null) { 40 | Module._free(this.cacheBuffer); 41 | this.cacheBuffer = null; 42 | } 43 | }; 44 | 45 | Decoder.prototype.openDecoder = function () { 46 | var paramCount = 7, paramSize = 4; 47 | var paramByteBuffer = Module._malloc(paramCount * paramSize); 48 | var ret = Module._openDecoder(paramByteBuffer, paramCount, this.videoCallback, this.audioCallback, this.requestCallback); 49 | this.logger.logInfo("openDecoder return " + ret); 50 | 51 | if (ret == 0) { 52 | var paramIntBuff = paramByteBuffer >> 2; 53 | var paramArray = Module.HEAP32.subarray(paramIntBuff, paramIntBuff + paramCount); 54 | var duration = paramArray[0]; 55 | var videoPixFmt = paramArray[1]; 56 | var videoWidth = paramArray[2]; 57 | var videoHeight = paramArray[3]; 58 | var audioSampleFmt = paramArray[4]; 59 | var audioChannels = paramArray[5]; 60 | var audioSampleRate = paramArray[6]; 61 | 62 | var objData = { 63 | t: kOpenDecoderRsp, 64 | e: ret, 65 | v: { 66 | d: duration, 67 | p: videoPixFmt, 68 | w: videoWidth, 69 | h: videoHeight 70 | }, 71 | a: { 72 | f: audioSampleFmt, 73 | c: audioChannels, 74 | r: audioSampleRate 75 | } 76 | }; 77 | self.postMessage(objData); 78 | } else { 79 | var objData = { 80 | t: kOpenDecoderRsp, 81 | e: ret 82 | }; 83 | self.postMessage(objData); 84 | } 85 | Module._free(paramByteBuffer); 86 | }; 87 | 88 | Decoder.prototype.closeDecoder = function () { 89 | this.logger.logInfo("closeDecoder."); 90 | if (this.decodeTimer) { 91 | clearInterval(this.decodeTimer); 92 | this.decodeTimer = null; 93 | this.logger.logInfo("Decode timer stopped."); 94 | } 95 | 96 | var ret = Module._closeDecoder(); 97 | this.logger.logInfo("Close ffmpeg decoder return " + ret + "."); 98 | 99 | var objData = { 100 | t: kCloseDecoderRsp, 101 | e: 0 102 | }; 103 | self.postMessage(objData); 104 | }; 105 | 106 | Decoder.prototype.startDecoding = function (interval) { 107 | //this.logger.logInfo("Start decoding."); 108 | if (this.decodeTimer) { 109 | clearInterval(this.decodeTimer); 110 | } 111 | this.decodeTimer = setInterval(this.decode, interval); 112 | }; 113 | 114 | Decoder.prototype.pauseDecoding = function () { 115 | //this.logger.logInfo("Pause decoding."); 116 | if (this.decodeTimer) { 117 | clearInterval(this.decodeTimer); 118 | this.decodeTimer = null; 119 | } 120 | }; 121 | 122 | Decoder.prototype.decode = function () { 123 | var ret = Module._decodeOnePacket(); 124 | if (ret == 7) { 125 | self.decoder.logger.logInfo("Decoder finished."); 126 | self.decoder.pauseDecoding(); 127 | var objData = { 128 | t: kDecodeFinishedEvt, 129 | }; 130 | self.postMessage(objData); 131 | } 132 | 133 | while (ret == 9) { 134 | //self.decoder.logger.logInfo("One old frame"); 135 | ret = Module._decodeOnePacket(); 136 | } 137 | }; 138 | 139 | Decoder.prototype.sendData = function (data) { 140 | var typedArray = new Uint8Array(data); 141 | Module.HEAPU8.set(typedArray, this.cacheBuffer); 142 | Module._sendData(this.cacheBuffer, typedArray.length); 143 | }; 144 | 145 | Decoder.prototype.seekTo = function (ms) { 146 | var accurateSeek = this.accurateSeek ? 1 : 0; 147 | var ret = Module._seekTo(ms, accurateSeek); 148 | var objData = { 149 | t: kSeekToRsp, 150 | r: ret 151 | }; 152 | self.postMessage(objData); 153 | }; 154 | 155 | Decoder.prototype.processReq = function (req) { 156 | //this.logger.logInfo("processReq " + req.t + "."); 157 | switch (req.t) { 158 | case kInitDecoderReq: 159 | this.initDecoder(req.s, req.c); 160 | break; 161 | case kUninitDecoderReq: 162 | this.uninitDecoder(); 163 | break; 164 | case kOpenDecoderReq: 165 | this.openDecoder(); 166 | break; 167 | case kCloseDecoderReq: 168 | this.closeDecoder(); 169 | break; 170 | case kStartDecodingReq: 171 | this.startDecoding(req.i); 172 | break; 173 | case kPauseDecodingReq: 174 | this.pauseDecoding(); 175 | break; 176 | case kFeedDataReq: 177 | this.sendData(req.d); 178 | break; 179 | case kSeekToReq: 180 | this.seekTo(req.ms); 181 | break; 182 | default: 183 | this.logger.logError("Unsupport messsage " + req.t); 184 | } 185 | }; 186 | 187 | Decoder.prototype.cacheReq = function (req) { 188 | if (req) { 189 | this.tmpReqQue.push(req); 190 | } 191 | }; 192 | 193 | Decoder.prototype.onWasmLoaded = function () { 194 | this.logger.logInfo("Wasm loaded."); 195 | this.wasmLoaded = true; 196 | 197 | this.videoCallback = Module.addFunction(function (buff, size, timestamp) { 198 | var outArray = Module.HEAPU8.subarray(buff, buff + size); 199 | var data = new Uint8Array(outArray); 200 | var objData = { 201 | t: kVideoFrame, 202 | s: timestamp, 203 | d: data 204 | }; 205 | self.postMessage(objData, [objData.d.buffer]); 206 | }, 'viid'); 207 | 208 | this.audioCallback = Module.addFunction(function (buff, size, timestamp) { 209 | var outArray = Module.HEAPU8.subarray(buff, buff + size); 210 | var data = new Uint8Array(outArray); 211 | var objData = { 212 | t: kAudioFrame, 213 | s: timestamp, 214 | d: data 215 | }; 216 | self.postMessage(objData, [objData.d.buffer]); 217 | }, 'viid'); 218 | 219 | this.requestCallback = Module.addFunction(function (offset, availble) { 220 | var objData = { 221 | t: kRequestDataEvt, 222 | o: offset, 223 | a: availble 224 | }; 225 | self.postMessage(objData); 226 | }, 'vii'); 227 | 228 | while (this.tmpReqQue.length > 0) { 229 | var req = this.tmpReqQue.shift(); 230 | this.processReq(req); 231 | } 232 | }; 233 | 234 | self.decoder = new Decoder; 235 | 236 | self.onmessage = function (evt) { 237 | if (!self.decoder) { 238 | console.log("[ER] Decoder not initialized!"); 239 | return; 240 | } 241 | 242 | var req = evt.data; 243 | if (!self.decoder.wasmLoaded) { 244 | self.decoder.cacheReq(req); 245 | self.decoder.logger.logInfo("Temp cache req " + req.t + "."); 246 | return; 247 | } 248 | 249 | self.decoder.processReq(req); 250 | }; 251 | 252 | function onWasmLoaded() { 253 | if (self.decoder) { 254 | self.decoder.onWasmLoaded(); 255 | } else { 256 | console.log("[ER] No decoder!"); 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/downloader.js: -------------------------------------------------------------------------------- 1 | self.importScripts("common.js"); 2 | 3 | function Downloader() { 4 | this.logger = new Logger("Downloader"); 5 | this.ws = null; 6 | } 7 | 8 | Downloader.prototype.appendBuffer = function (buffer1, buffer2) { 9 | var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength); 10 | tmp.set(new Uint8Array(buffer1), 0); 11 | tmp.set(new Uint8Array(buffer2), buffer1.byteLength); 12 | return tmp.buffer; 13 | }; 14 | 15 | Downloader.prototype.reportFileSize = function (sz, st) { 16 | var objData = { 17 | t: kGetFileInfoRsp, 18 | i: { 19 | sz: sz, 20 | st: st 21 | } 22 | }; 23 | 24 | //this.logger.logInfo("File size " + sz + " bytes."); 25 | self.postMessage(objData); 26 | }; 27 | 28 | Downloader.prototype.reportData = function (start, end, seq, data) { 29 | var objData = { 30 | t: kFileData, 31 | s: start, 32 | e: end, 33 | d: data, 34 | q: seq 35 | }; 36 | self.postMessage(objData, [objData.d]); 37 | }; 38 | 39 | // Http implement. 40 | Downloader.prototype.getFileInfoByHttp = function (url) { 41 | this.logger.logInfo("Getting file size " + url + "."); 42 | var size = 0; 43 | var status = 0; 44 | var reported = false; 45 | 46 | var xhr = new XMLHttpRequest(); 47 | xhr.open('get', url, true); 48 | var self = this; 49 | xhr.onreadystatechange = () => { 50 | var len = xhr.getResponseHeader("Content-Length"); 51 | if (len) { 52 | size = len; 53 | } 54 | 55 | if (xhr.status) { 56 | status = xhr.status; 57 | } 58 | 59 | //Completed. 60 | if (!reported && ((size > 0 && status > 0) || xhr.readyState == 4)) { 61 | self.reportFileSize(size, status); 62 | reported = true; 63 | xhr.abort(); 64 | } 65 | }; 66 | xhr.send(); 67 | }; 68 | 69 | Downloader.prototype.downloadFileByHttp = function (url, start, end, seq) { 70 | //this.logger.logInfo("Downloading file " + url + ", bytes=" + start + "-" + end + "."); 71 | var xhr = new XMLHttpRequest; 72 | xhr.open('get', url, true); 73 | xhr.responseType = 'arraybuffer'; 74 | xhr.setRequestHeader("Range", "bytes=" + start + "-" + end); 75 | var self = this; 76 | xhr.onload = function () { 77 | self.reportData(start, end, seq, xhr.response); 78 | }; 79 | xhr.send(); 80 | }; 81 | 82 | // Websocket implement, NOTICE MUST call requestWebsocket serially, MUST wait 83 | // for result of last websocket request(cb called) for there's only one stream 84 | // exists. 85 | Downloader.prototype.requestWebsocket = function (url, msg, cb) { 86 | if (this.ws == null) { 87 | this.ws = new WebSocket(url); 88 | this.ws.binaryType = 'arraybuffer'; 89 | 90 | var self = this; 91 | this.ws.onopen = function(evt) { 92 | self.logger.logInfo("Ws connected."); 93 | self.ws.send(msg); 94 | }; 95 | 96 | this.ws.onerror = function(evt) { 97 | self.logger.logError("Ws connect error " + evt.data); 98 | } 99 | 100 | this.ws.onmessage = cb.onmessage; 101 | } else { 102 | this.ws.onmessage = cb.onmessage; 103 | this.ws.send(msg); 104 | } 105 | }; 106 | 107 | Downloader.prototype.getFileInfoByWebsocket = function (url) { 108 | //this.logger.logInfo("Getting file size " + url + "."); 109 | 110 | // TBD, consider tcp sticky package. 111 | var data = null; 112 | var expectLength = 4; 113 | var self = this; 114 | var cmd = { 115 | url : url, 116 | cmd : "size", 117 | }; 118 | this.requestWebsocket(url, JSON.stringify(cmd), { 119 | onmessage : function(evt) { 120 | if (data != null) { 121 | data = self.appendBuffer(data, evt.data); 122 | } else if (evt.data.byteLength < expectLength) { 123 | data = evt.data.slice(0); 124 | } else { 125 | data = evt.data; 126 | } 127 | 128 | // Assume 4 bytes header as file size. 129 | if (data.byteLength == expectLength) { 130 | let int32array = new Int32Array(data, 0, 1); 131 | let size = int32array[0]; 132 | self.reportFileSize(size, 200); 133 | //self.logger.logInfo("Got file size " + self.fileSize + "."); 134 | } 135 | } 136 | }); 137 | }; 138 | 139 | Downloader.prototype.downloadFileByWebsocket = function (url, start, end, seq) { 140 | //this.logger.logInfo("Downloading file " + url + ", bytes=" + start + "-" + end + "."); 141 | var data = null; 142 | var expectLength = end - start + 1; 143 | var self = this; 144 | var cmd = { 145 | url : url, 146 | cmd : "data", 147 | start : start, 148 | end : end 149 | }; 150 | this.requestWebsocket(url, JSON.stringify(cmd), { 151 | onmessage : function(evt) { 152 | if (data != null) { 153 | data = self.appendBuffer(data, evt.data); 154 | } else if (evt.data.byteLength < expectLength) { 155 | data = evt.data.slice(0); 156 | } else { 157 | data = evt.data; 158 | } 159 | 160 | // Wait for expect data length. 161 | if (data.byteLength == expectLength) { 162 | self.reportData(start, end, seq, data); 163 | } 164 | } 165 | }); 166 | }; 167 | 168 | // Interface. 169 | Downloader.prototype.getFileInfo = function (proto, url) { 170 | switch (proto) { 171 | case kProtoHttp: 172 | this.getFileInfoByHttp(url); 173 | break; 174 | case kProtoWebsocket: 175 | this.getFileInfoByWebsocket(url); 176 | break; 177 | default: 178 | this.logger.logError("Invalid protocol " + proto); 179 | break; 180 | } 181 | }; 182 | 183 | Downloader.prototype.downloadFile = function (proto, url, start, end, seq) { 184 | switch (proto) { 185 | case kProtoHttp: 186 | this.downloadFileByHttp(url, start, end, seq); 187 | break; 188 | case kProtoWebsocket: 189 | this.downloadFileByWebsocket(url, start, end, seq); 190 | break; 191 | default: 192 | this.logger.logError("Invalid protocol " + proto); 193 | break; 194 | } 195 | } 196 | 197 | self.downloader = new Downloader(); 198 | 199 | self.onmessage = function (evt) { 200 | if (!self.downloader) { 201 | console.log("[ER] Downloader not initialized!"); 202 | return; 203 | } 204 | 205 | var objData = evt.data; 206 | switch (objData.t) { 207 | case kGetFileInfoReq: 208 | self.downloader.getFileInfo(objData.p, objData.u); 209 | break; 210 | case kDownloadFileReq: 211 | self.downloader.downloadFile(objData.p, objData.u, objData.s, objData.e, objData.q); 212 | break; 213 | case kCloseDownloaderReq: 214 | //Nothing to do. 215 | break; 216 | default: 217 | self.downloader.logger.logError("Unsupport messsage " + objData.t); 218 | } 219 | }; 220 | -------------------------------------------------------------------------------- /src/libffmpeg.js: -------------------------------------------------------------------------------- 1 | var Module=typeof Module!=="undefined"?Module:{};var moduleOverrides={};var key;for(key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}var arguments_=[];var thisProgram="./this.program";var quit_=function(status,toThrow){throw toThrow};var ENVIRONMENT_IS_WEB=false;var ENVIRONMENT_IS_WORKER=false;var ENVIRONMENT_IS_NODE=false;var ENVIRONMENT_HAS_NODE=false;var ENVIRONMENT_IS_SHELL=false;ENVIRONMENT_IS_WEB=typeof window==="object";ENVIRONMENT_IS_WORKER=typeof importScripts==="function";ENVIRONMENT_HAS_NODE=typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string";ENVIRONMENT_IS_NODE=ENVIRONMENT_HAS_NODE&&!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_WORKER;ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;var nodeFS;var nodePath;if(ENVIRONMENT_IS_NODE){scriptDirectory=__dirname+"/";read_=function shell_read(filename,binary){if(!nodeFS)nodeFS=require("fs");if(!nodePath)nodePath=require("path");filename=nodePath["normalize"](filename);return nodeFS["readFileSync"](filename,binary?null:"utf8")};readBinary=function readBinary(filename){var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}assert(ret.buffer);return ret};if(process["argv"].length>1){thisProgram=process["argv"][1].replace(/\\/g,"/")}arguments_=process["argv"].slice(2);if(typeof module!=="undefined"){module["exports"]=Module}process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",abort);quit_=function(status){process["exit"](status)};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_SHELL){if(typeof read!="undefined"){read_=function shell_read(f){return read(f)}}readBinary=function readBinary(f){var data;if(typeof readbuffer==="function"){return new Uint8Array(readbuffer(f))}data=read(f,"binary");assert(typeof data==="object");return data};if(typeof scriptArgs!="undefined"){arguments_=scriptArgs}else if(typeof arguments!="undefined"){arguments_=arguments}if(typeof quit==="function"){quit_=function(status){quit(status)}}if(typeof print!=="undefined"){if(typeof console==="undefined")console={};console.log=print;console.warn=console.error=typeof printErr!=="undefined"?printErr:print}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(document.currentScript){scriptDirectory=document.currentScript.src}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=function shell_read(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=function readBinary(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=function readAsync(url,onload,onerror){var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=function xhr_onload(){if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=function(title){document.title=title}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);for(key in moduleOverrides){if(moduleOverrides.hasOwnProperty(key)){Module[key]=moduleOverrides[key]}}moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];function dynamicAlloc(size){var ret=HEAP32[DYNAMICTOP_PTR>>2];var end=ret+size+15&-16;if(end>_emscripten_get_heap_size()){abort()}HEAP32[DYNAMICTOP_PTR>>2]=end;return ret}function getNativeTypeSize(type){switch(type){case"i1":case"i8":return 1;case"i16":return 2;case"i32":return 4;case"i64":return 8;case"float":return 4;case"double":return 8;default:{if(type[type.length-1]==="*"){return 4}else if(type[0]==="i"){var bits=parseInt(type.substr(1));assert(bits%8===0,"getNativeTypeSize invalid bits "+bits+", type "+type);return bits/8}else{return 0}}}}function convertJsFunctionToWasm(func,sig){if(typeof WebAssembly.Function==="function"){var typeNames={"i":"i32","j":"i64","f":"f32","d":"f64"};var type={parameters:[],results:sig[0]=="v"?[]:[typeNames[sig[0]]]};for(var i=1;i>0]=value;break;case"i8":HEAP8[ptr>>0]=value;break;case"i16":HEAP16[ptr>>1]=value;break;case"i32":HEAP32[ptr>>2]=value;break;case"i64":tempI64=[value>>>0,(tempDouble=value,+Math_abs(tempDouble)>=1?tempDouble>0?(Math_min(+Math_floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math_ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[ptr>>2]=tempI64[0],HEAP32[ptr+4>>2]=tempI64[1];break;case"float":HEAPF32[ptr>>2]=value;break;case"double":HEAPF64[ptr>>3]=value;break;default:abort("invalid type for setValue: "+type)}}var wasmMemory;var wasmTable=new WebAssembly.Table({"initial":1731,"maximum":1731+14,"element":"anyfunc"});var ABORT=false;var EXITSTATUS=0;function assert(condition,text){if(!condition){abort("Assertion failed: "+text)}}var ALLOC_NORMAL=0;var ALLOC_NONE=3;function allocate(slab,types,allocator,ptr){var zeroinit,size;if(typeof slab==="number"){zeroinit=true;size=slab}else{zeroinit=false;size=slab.length}var singleType=typeof types==="string"?types:null;var ret;if(allocator==ALLOC_NONE){ret=ptr}else{ret=[_malloc,stackAlloc,dynamicAlloc][allocator](Math.max(size,singleType?1:types.length))}if(zeroinit){var stop;ptr=ret;assert((ret&3)==0);stop=ret+(size&~3);for(;ptr>2]=0}stop=ret+size;while(ptr>0]=0}return ret}if(singleType==="i8"){if(slab.subarray||slab.slice){HEAPU8.set(slab,ret)}else{HEAPU8.set(new Uint8Array(slab),ret)}return ret}var i=0,type,typeSize,previousType;while(i=endIdx))++endPtr;if(endPtr-idx>16&&u8Array.subarray&&UTF8Decoder){return UTF8Decoder.decode(u8Array.subarray(idx,endPtr))}else{var str="";while(idx>10,56320|ch&1023)}}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,outU8Array,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;outU8Array[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;outU8Array[outIdx++]=192|u>>6;outU8Array[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;outU8Array[outIdx++]=224|u>>12;outU8Array[outIdx++]=128|u>>6&63;outU8Array[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;outU8Array[outIdx++]=240|u>>18;outU8Array[outIdx++]=128|u>>12&63;outU8Array[outIdx++]=128|u>>6&63;outU8Array[outIdx++]=128|u&63}}outU8Array[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function lengthBytesUTF8(str){var len=0;for(var i=0;i=55296&&u<=57343)u=65536+((u&1023)<<10)|str.charCodeAt(++i)&1023;if(u<=127)++len;else if(u<=2047)len+=2;else if(u<=65535)len+=3;else len+=4}return len}var UTF16Decoder=typeof TextDecoder!=="undefined"?new TextDecoder("utf-16le"):undefined;function writeArrayToMemory(array,buffer){HEAP8.set(array,buffer)}function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer>>0]=0}var PAGE_SIZE=16384;var WASM_PAGE_SIZE=65536;var buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferAndViews(buf){buffer=buf;Module["HEAP8"]=HEAP8=new Int8Array(buf);Module["HEAP16"]=HEAP16=new Int16Array(buf);Module["HEAP32"]=HEAP32=new Int32Array(buf);Module["HEAPU8"]=HEAPU8=new Uint8Array(buf);Module["HEAPU16"]=HEAPU16=new Uint16Array(buf);Module["HEAPU32"]=HEAPU32=new Uint32Array(buf);Module["HEAPF32"]=HEAPF32=new Float32Array(buf);Module["HEAPF64"]=HEAPF64=new Float64Array(buf)}var DYNAMIC_BASE=6533824,DYNAMICTOP_PTR=1290784;var INITIAL_TOTAL_MEMORY=Module["TOTAL_MEMORY"]||67108864;if(Module["wasmMemory"]){wasmMemory=Module["wasmMemory"]}else{wasmMemory=new WebAssembly.Memory({"initial":INITIAL_TOTAL_MEMORY/WASM_PAGE_SIZE,"maximum":INITIAL_TOTAL_MEMORY/WASM_PAGE_SIZE})}if(wasmMemory){buffer=wasmMemory.buffer}INITIAL_TOTAL_MEMORY=buffer.byteLength;updateGlobalBufferAndViews(buffer);HEAP32[DYNAMICTOP_PTR>>2]=DYNAMIC_BASE;function callRuntimeCallbacks(callbacks){while(callbacks.length>0){var callback=callbacks.shift();if(typeof callback=="function"){callback();continue}var func=callback.func;if(typeof func==="number"){if(callback.arg===undefined){Module["dynCall_v"](func)}else{Module["dynCall_vi"](func,callback.arg)}}else{func(callback.arg===undefined?null:callback.arg)}}}var __ATPRERUN__=[];var __ATINIT__=[];var __ATMAIN__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;var runtimeExited=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();TTY.init();callRuntimeCallbacks(__ATINIT__)}function preMain(){FS.ignorePermissions=false;callRuntimeCallbacks(__ATMAIN__)}function exitRuntime(){runtimeExited=true}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var Math_abs=Math.abs;var Math_ceil=Math.ceil;var Math_floor=Math.floor;var Math_min=Math.min;var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}Module["preloadedImages"]={};Module["preloadedAudios"]={};function abort(what){if(Module["onAbort"]){Module["onAbort"](what)}what+="";out(what);err(what);ABORT=true;EXITSTATUS=1;what="abort("+what+"). Build with -s ASSERTIONS=1 for more info.";throw new WebAssembly.RuntimeError(what)}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return String.prototype.startsWith?filename.startsWith(dataURIPrefix):filename.indexOf(dataURIPrefix)===0}var wasmBinaryFile="libffmpeg.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(){try{if(wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(wasmBinaryFile)}else{throw"both async and sync fetching of the wasm failed"}}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)&&typeof fetch==="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary()})}return new Promise(function(resolve,reject){resolve(getBinary())})}function createWasm(){var info={"env":asmLibraryArg,"wasi_snapshot_preview1":asmLibraryArg};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiatedSource(output){receiveInstance(output["instance"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){return WebAssembly.instantiate(binary,info)}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming==="function"&&!isDataURI(wasmBinaryFile)&&typeof fetch==="function"){fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiatedSource,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");instantiateArrayBuffer(receiveInstantiatedSource)})})}else{return instantiateArrayBuffer(receiveInstantiatedSource)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);return false}}instantiateAsync();return{}}var tempDouble;var tempI64;__ATINIT__.push({func:function(){___wasm_call_ctors()}});function demangle(func){return func}function demangleAll(text){var regex=/\b_Z[\w\d_]+/g;return text.replace(regex,function(x){var y=demangle(x);return x===y?x:y+" ["+x+"]"})}function jsStackTrace(){var err=new Error;if(!err.stack){try{throw new Error(0)}catch(e){err=e}if(!err.stack){return"(no stack trace available)"}}return err.stack.toString()}function stackTrace(){var js=jsStackTrace();if(Module["extraStackTrace"])js+="\n"+Module["extraStackTrace"]();return demangleAll(js)}function ___lock(){}var PATH={splitPath:function(filename){var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:function(parts,allowAboveRoot){var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:function(path){var isAbsolute=path.charAt(0)==="/",trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(function(p){return!!p}),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:function(path){var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:function(path){if(path==="/")return"/";var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},extname:function(path){return PATH.splitPath(path)[3]},join:function(){var paths=Array.prototype.slice.call(arguments,0);return PATH.normalize(paths.join("/"))},join2:function(l,r){return PATH.normalize(l+"/"+r)}};function ___setErrNo(value){if(Module["___errno_location"])HEAP32[Module["___errno_location"]()>>2]=value;return value}var PATH_FS={resolve:function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:FS.cwd();if(typeof path!=="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=path.charAt(0)==="/"}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(function(p){return!!p}),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:function(from,to){from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i0){result=buf.slice(0,bytesRead).toString("utf-8")}else{result=null}}else if(typeof window!="undefined"&&typeof window.prompt=="function"){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else if(typeof readline=="function"){result=readline();if(result!==null){result+="\n"}}if(!result){return null}tty.input=intArrayFromString(result,true)}return tty.input.shift()},put_char:function(tty,val){if(val===null||val===10){out(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}}},default_tty1_ops:{put_char:function(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};var MEMFS={ops_table:null,mount:function(mount){return MEMFS.createNode(null,"/",16384|511,0)},createNode:function(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}if(!MEMFS.ops_table){MEMFS.ops_table={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,allocate:MEMFS.stream_ops.allocate,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}}}var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.timestamp=Date.now();if(parent){parent.contents[name]=node}return node},getFileDataAsRegularArray:function(node){if(node.contents&&node.contents.subarray){var arr=[];for(var i=0;i=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity0)node.contents.set(oldContents.subarray(0,node.usedBytes),0);return},resizeFileStorage:function(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0;return}if(!node.contents||node.contents.subarray){var oldContents=node.contents;node.contents=new Uint8Array(new ArrayBuffer(newSize));if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize;return}if(!node.contents)node.contents=[];if(node.contents.length>newSize)node.contents.length=newSize;else while(node.contents.length=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length8){throw new FS.ErrnoError(32)}var parts=PATH.normalizeArray(path.split("/").filter(function(p){return!!p}),false);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath:function(node){var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?mount+"/"+path:mount+path}path=path?node.name+"/"+path:node.name;node=node.parent}},hashName:function(parentid,name){var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode:function(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode:function(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode:function(parent,name){var err=FS.mayLookup(parent);if(err){throw new FS.ErrnoError(err,parent)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode:function(parent,name,mode,rdev){if(!FS.FSNode){FS.FSNode=function(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev};FS.FSNode.prototype={};var readMode=292|73;var writeMode=146;Object.defineProperties(FS.FSNode.prototype,{read:{get:function(){return(this.mode&readMode)===readMode},set:function(val){val?this.mode|=readMode:this.mode&=~readMode}},write:{get:function(){return(this.mode&writeMode)===writeMode},set:function(val){val?this.mode|=writeMode:this.mode&=~writeMode}},isFolder:{get:function(){return FS.isDir(this.mode)}},isDevice:{get:function(){return FS.isChrdev(this.mode)}}})}var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode:function(node){FS.hashRemoveNode(node)},isRoot:function(node){return node===node.parent},isMountpoint:function(node){return!!node.mounted},isFile:function(mode){return(mode&61440)===32768},isDir:function(mode){return(mode&61440)===16384},isLink:function(mode){return(mode&61440)===40960},isChrdev:function(mode){return(mode&61440)===8192},isBlkdev:function(mode){return(mode&61440)===24576},isFIFO:function(mode){return(mode&61440)===4096},isSocket:function(mode){return(mode&49152)===49152},flagModes:{"r":0,"rs":1052672,"r+":2,"w":577,"wx":705,"xw":705,"w+":578,"wx+":706,"xw+":706,"a":1089,"ax":1217,"xa":1217,"a+":1090,"ax+":1218,"xa+":1218},modeStringToFlags:function(str){var flags=FS.flagModes[str];if(typeof flags==="undefined"){throw new Error("Unknown file open mode: "+str)}return flags},flagsToPermissionString:function(flag){var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions:function(node,perms){if(FS.ignorePermissions){return 0}if(perms.indexOf("r")!==-1&&!(node.mode&292)){return 2}else if(perms.indexOf("w")!==-1&&!(node.mode&146)){return 2}else if(perms.indexOf("x")!==-1&&!(node.mode&73)){return 2}return 0},mayLookup:function(dir){var err=FS.nodePermissions(dir,"x");if(err)return err;if(!dir.node_ops.lookup)return 2;return 0},mayCreate:function(dir,name){try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete:function(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var err=FS.nodePermissions(dir,"wx");if(err){return err}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen:function(node,flags){if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd:function(fd_start,fd_end){fd_start=fd_start||0;fd_end=fd_end||FS.MAX_OPEN_FDS;for(var fd=fd_start;fd<=fd_end;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStream:function(fd){return FS.streams[fd]},createStream:function(stream,fd_start,fd_end){if(!FS.FSStream){FS.FSStream=function(){};FS.FSStream.prototype={};Object.defineProperties(FS.FSStream.prototype,{object:{get:function(){return this.node},set:function(val){this.node=val}},isRead:{get:function(){return(this.flags&2097155)!==1}},isWrite:{get:function(){return(this.flags&2097155)!==0}},isAppend:{get:function(){return this.flags&1024}}})}var newStream=new FS.FSStream;for(var p in stream){newStream[p]=stream[p]}stream=newStream;var fd=FS.nextfd(fd_start,fd_end);stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream:function(fd){FS.streams[fd]=null},chrdev_stream_ops:{open:function(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;if(stream.stream_ops.open){stream.stream_ops.open(stream)}},llseek:function(){throw new FS.ErrnoError(70)}},major:function(dev){return dev>>8},minor:function(dev){return dev&255},makedev:function(ma,mi){return ma<<8|mi},registerDevice:function(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:function(dev){return FS.devices[dev]},getMounts:function(mount){var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push.apply(check,m.mounts)}return mounts},syncfs:function(populate,callback){if(typeof populate==="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){console.log("warning: "+FS.syncFSRequests+" FS.syncfs operations in flight at once, probably just doing extra work")}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(err){FS.syncFSRequests--;return callback(err)}function done(err){if(err){if(!done.errored){done.errored=true;return doCallback(err)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(function(mount){if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount:function(type,opts,mountpoint){var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount:function(mountpoint){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(function(hash){var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.indexOf(current.mount)!==-1){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup:function(parent,name){return parent.node_ops.lookup(parent,name)},mknod:function(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var err=FS.mayCreate(parent,name);if(err){throw new FS.ErrnoError(err)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create:function(path,mode){mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir:function(path,mode){mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree:function(path,mode){var dirs=path.split("/");var d="";for(var i=0;ithis.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]};LazyUint8Array.prototype.setDataGetter=function LazyUint8Array_setDataGetter(getter){this.getter=getter};LazyUint8Array.prototype.cacheLength=function LazyUint8Array_cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=function(from,to){if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);if(typeof Uint8Array!="undefined")xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}else{return intArrayFromString(xhr.responseText||"",true)}};var lazyArray=this;lazyArray.setDataGetter(function(chunkNum){var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]==="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]==="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;console.log("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true};if(typeof XMLHttpRequest!=="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;Object.defineProperties(lazyArray,{length:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._length}},chunkSize:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}});var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(function(key){var fn=node.stream_ops[key];stream_ops[key]=function forceLoadLazyFile(){if(!FS.forceLoadFile(node)){throw new FS.ErrnoError(29)}return fn.apply(null,arguments)}});stream_ops.read=function stream_ops_read(stream,buffer,offset,length,position){if(!FS.forceLoadFile(node)){throw new FS.ErrnoError(29)}var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i>2]=stat.dev;HEAP32[buf+4>>2]=0;HEAP32[buf+8>>2]=stat.ino;HEAP32[buf+12>>2]=stat.mode;HEAP32[buf+16>>2]=stat.nlink;HEAP32[buf+20>>2]=stat.uid;HEAP32[buf+24>>2]=stat.gid;HEAP32[buf+28>>2]=stat.rdev;HEAP32[buf+32>>2]=0;tempI64=[stat.size>>>0,(tempDouble=stat.size,+Math_abs(tempDouble)>=1?tempDouble>0?(Math_min(+Math_floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math_ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+40>>2]=tempI64[0],HEAP32[buf+44>>2]=tempI64[1];HEAP32[buf+48>>2]=4096;HEAP32[buf+52>>2]=stat.blocks;HEAP32[buf+56>>2]=stat.atime.getTime()/1e3|0;HEAP32[buf+60>>2]=0;HEAP32[buf+64>>2]=stat.mtime.getTime()/1e3|0;HEAP32[buf+68>>2]=0;HEAP32[buf+72>>2]=stat.ctime.getTime()/1e3|0;HEAP32[buf+76>>2]=0;tempI64=[stat.ino>>>0,(tempDouble=stat.ino,+Math_abs(tempDouble)>=1?tempDouble>0?(Math_min(+Math_floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math_ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+80>>2]=tempI64[0],HEAP32[buf+84>>2]=tempI64[1];return 0},doMsync:function(addr,stream,len,flags){var buffer=new Uint8Array(HEAPU8.subarray(addr,addr+len));FS.msync(stream,buffer,0,len,flags)},doMkdir:function(path,mode){path=PATH.normalize(path);if(path[path.length-1]==="/")path=path.substr(0,path.length-1);FS.mkdir(path,mode,0);return 0},doMknod:function(path,mode,dev){switch(mode&61440){case 32768:case 8192:case 24576:case 4096:case 49152:break;default:return-28}FS.mknod(path,mode,dev);return 0},doReadlink:function(path,buf,bufsize){if(bufsize<=0)return-28;var ret=FS.readlink(path);var len=Math.min(bufsize,lengthBytesUTF8(ret));var endChar=HEAP8[buf+len];stringToUTF8(ret,buf,bufsize+1);HEAP8[buf+len]=endChar;return len},doAccess:function(path,amode){if(amode&~7){return-28}var node;var lookup=FS.lookupPath(path,{follow:true});node=lookup.node;if(!node){return-44}var perms="";if(amode&4)perms+="r";if(amode&2)perms+="w";if(amode&1)perms+="x";if(perms&&FS.nodePermissions(node,perms)){return-2}return 0},doDup:function(path,flags,suggestFD){var suggest=FS.getStream(suggestFD);if(suggest)FS.close(suggest);return FS.open(path,flags,0,suggestFD,suggestFD).fd},doReadv:function(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAP32[iov+(i*8+4)>>2];var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2];var len=HEAP32[iov+(i*8+4)>>2];var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr}return ret},varargs:0,get:function(varargs){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(){var ret=UTF8ToString(SYSCALLS.get());return ret},getStreamFromFD:function(fd){if(fd===undefined)fd=SYSCALLS.get();var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);return stream},get64:function(){var low=SYSCALLS.get(),high=SYSCALLS.get();return low},getZero:function(){SYSCALLS.get()}};function ___syscall10(which,varargs){SYSCALLS.varargs=varargs;try{var path=SYSCALLS.getStr();FS.unlink(path);return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall195(which,varargs){SYSCALLS.varargs=varargs;try{var path=SYSCALLS.getStr(),buf=SYSCALLS.get();return SYSCALLS.doStat(FS.stat,path,buf)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall196(which,varargs){SYSCALLS.varargs=varargs;try{var path=SYSCALLS.getStr(),buf=SYSCALLS.get();return SYSCALLS.doStat(FS.lstat,path,buf)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall197(which,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(),buf=SYSCALLS.get();return SYSCALLS.doStat(FS.stat,stream.path,buf)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall220(which,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(),dirp=SYSCALLS.get(),count=SYSCALLS.get();if(!stream.getdents){stream.getdents=FS.readdir(stream.path)}var struct_size=280;var pos=0;var off=FS.llseek(stream,0,1);var idx=Math.floor(off/struct_size);while(idx>>0,(tempDouble=id,+Math_abs(tempDouble)>=1?tempDouble>0?(Math_min(+Math_floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math_ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[dirp+pos>>2]=tempI64[0],HEAP32[dirp+pos+4>>2]=tempI64[1];tempI64=[(idx+1)*struct_size>>>0,(tempDouble=(idx+1)*struct_size,+Math_abs(tempDouble)>=1?tempDouble>0?(Math_min(+Math_floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math_ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[dirp+pos+8>>2]=tempI64[0],HEAP32[dirp+pos+12>>2]=tempI64[1];HEAP16[dirp+pos+16>>1]=280;HEAP8[dirp+pos+18>>0]=type;stringToUTF8(name,dirp+pos+19,256);pos+=struct_size;idx+=1}FS.llseek(stream,idx*struct_size,0);return pos}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall221(which,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(),cmd=SYSCALLS.get();switch(cmd){case 0:{var arg=SYSCALLS.get();if(arg<0){return-28}var newStream;newStream=FS.open(stream.path,stream.flags,0,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=SYSCALLS.get();stream.flags|=arg;return 0}case 12:{var arg=SYSCALLS.get();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 13:case 14:return 0;case 16:case 8:return-28;case 9:___setErrNo(28);return-1;default:{return-28}}}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall3(which,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(),buf=SYSCALLS.get(),count=SYSCALLS.get();return FS.read(stream,HEAP8,buf,count)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall33(which,varargs){SYSCALLS.varargs=varargs;try{var path=SYSCALLS.getStr(),amode=SYSCALLS.get();return SYSCALLS.doAccess(path,amode)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall38(which,varargs){SYSCALLS.varargs=varargs;try{var old_path=SYSCALLS.getStr(),new_path=SYSCALLS.getStr();FS.rename(old_path,new_path);return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall4(which,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(),buf=SYSCALLS.get(),count=SYSCALLS.get();return FS.write(stream,HEAP8,buf,count)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall40(which,varargs){SYSCALLS.varargs=varargs;try{var path=SYSCALLS.getStr();FS.rmdir(path);return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall5(which,varargs){SYSCALLS.varargs=varargs;try{var pathname=SYSCALLS.getStr(),flags=SYSCALLS.get(),mode=SYSCALLS.get();var stream=FS.open(pathname,flags,mode);return stream.fd}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall54(which,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(),op=SYSCALLS.get();switch(op){case 21509:case 21505:{if(!stream.tty)return-59;return 0}case 21510:case 21511:case 21512:case 21506:case 21507:case 21508:{if(!stream.tty)return-59;return 0}case 21519:{if(!stream.tty)return-59;var argp=SYSCALLS.get();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=SYSCALLS.get();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;return 0}case 21524:{if(!stream.tty)return-59;return 0}default:abort("bad ioctl syscall "+op)}}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___unlock(){}function _abort(){abort()}function _clock(){if(_clock.start===undefined)_clock.start=Date.now();return(Date.now()-_clock.start)*(1e6/1e3)|0}function _emscripten_get_now(){abort()}function _emscripten_get_now_is_monotonic(){return 0||ENVIRONMENT_IS_NODE||typeof dateNow!=="undefined"||1}function _clock_gettime(clk_id,tp){var now;if(clk_id===0){now=Date.now()}else if(clk_id===1&&_emscripten_get_now_is_monotonic()){now=_emscripten_get_now()}else{___setErrNo(28);return-1}HEAP32[tp>>2]=now/1e3|0;HEAP32[tp+4>>2]=now%1e3*1e3*1e3|0;return 0}function _emscripten_get_heap_size(){return HEAP8.length}function _emscripten_memcpy_big(dest,src,num){HEAPU8.set(HEAPU8.subarray(src,src+num),dest)}function abortOnCannotGrowMemory(requestedSize){abort("OOM")}function _emscripten_resize_heap(requestedSize){abortOnCannotGrowMemory(requestedSize)}var ENV={};function _emscripten_get_environ(){if(!_emscripten_get_environ.strings){var env={"USER":"web_user","LOGNAME":"web_user","PATH":"/","PWD":"/","HOME":"/home/web_user","LANG":(typeof navigator==="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8","_":thisProgram};for(var x in ENV){env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(x+"="+env[x])}_emscripten_get_environ.strings=strings}return _emscripten_get_environ.strings}function _environ_get(__environ,environ_buf){var strings=_emscripten_get_environ();var bufSize=0;strings.forEach(function(string,i){var ptr=environ_buf+bufSize;HEAP32[__environ+i*4>>2]=ptr;writeAsciiToMemory(string,ptr);bufSize+=string.length+1});return 0}function _environ_sizes_get(penviron_count,penviron_buf_size){var strings=_emscripten_get_environ();HEAP32[penviron_count>>2]=strings.length;var bufSize=0;strings.forEach(function(string){bufSize+=string.length+1});HEAP32[penviron_buf_size>>2]=bufSize;return 0}function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_fdstat_get(fd,pbuf){try{var stream=SYSCALLS.getStreamFromFD(fd);var type=stream.tty?2:FS.isDir(stream.mode)?3:FS.isLink(stream.mode)?7:4;HEAP8[pbuf>>0]=type;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_read(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=SYSCALLS.doReadv(stream,iov,iovcnt);HEAP32[pnum>>2]=num;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){try{var stream=SYSCALLS.getStreamFromFD(fd);var HIGH_OFFSET=4294967296;var offset=offset_high*HIGH_OFFSET+(offset_low>>>0);var DOUBLE_LIMIT=9007199254740992;if(offset<=-DOUBLE_LIMIT||offset>=DOUBLE_LIMIT){return-61}FS.llseek(stream,offset,whence);tempI64=[stream.position>>>0,(tempDouble=stream.position,+Math_abs(tempDouble)>=1?tempDouble>0?(Math_min(+Math_floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math_ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[newOffset>>2]=tempI64[0],HEAP32[newOffset+4>>2]=tempI64[1];if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=SYSCALLS.doWritev(stream,iov,iovcnt);HEAP32[pnum>>2]=num;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _ftime(p){var millis=Date.now();HEAP32[p>>2]=millis/1e3|0;HEAP16[p+4>>1]=millis%1e3;HEAP16[p+6>>1]=0;HEAP16[p+8>>1]=0;return 0}function _gettimeofday(ptr){var now=Date.now();HEAP32[ptr>>2]=now/1e3|0;HEAP32[ptr+4>>2]=now%1e3*1e3|0;return 0}var ___tm_timezone=(stringToUTF8("GMT",1290848,4),1290848);function _gmtime_r(time,tmPtr){var date=new Date(HEAP32[time>>2]*1e3);HEAP32[tmPtr>>2]=date.getUTCSeconds();HEAP32[tmPtr+4>>2]=date.getUTCMinutes();HEAP32[tmPtr+8>>2]=date.getUTCHours();HEAP32[tmPtr+12>>2]=date.getUTCDate();HEAP32[tmPtr+16>>2]=date.getUTCMonth();HEAP32[tmPtr+20>>2]=date.getUTCFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getUTCDay();HEAP32[tmPtr+36>>2]=0;HEAP32[tmPtr+32>>2]=0;var start=Date.UTC(date.getUTCFullYear(),0,1,0,0,0,0);var yday=(date.getTime()-start)/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr+40>>2]=___tm_timezone;return tmPtr}function _tzset(){if(_tzset.called)return;_tzset.called=true;HEAP32[__get_timezone()>>2]=(new Date).getTimezoneOffset()*60;var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);HEAP32[__get_daylight()>>2]=Number(winter.getTimezoneOffset()!=summer.getTimezoneOffset());function extractZone(date){var match=date.toTimeString().match(/\(([A-Za-z ]+)\)$/);return match?match[1]:"GMT"}var winterName=extractZone(winter);var summerName=extractZone(summer);var winterNamePtr=allocate(intArrayFromString(winterName),"i8",ALLOC_NORMAL);var summerNamePtr=allocate(intArrayFromString(summerName),"i8",ALLOC_NORMAL);if(summer.getTimezoneOffset()>2]=winterNamePtr;HEAP32[__get_tzname()+4>>2]=summerNamePtr}else{HEAP32[__get_tzname()>>2]=summerNamePtr;HEAP32[__get_tzname()+4>>2]=winterNamePtr}}function _localtime_r(time,tmPtr){_tzset();var date=new Date(HEAP32[time>>2]*1e3);HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getDay();var start=new Date(date.getFullYear(),0,1);var yday=(date.getTime()-start.getTime())/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr+36>>2]=-(date.getTimezoneOffset()*60);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dst=(summerOffset!=winterOffset&&date.getTimezoneOffset()==Math.min(winterOffset,summerOffset))|0;HEAP32[tmPtr+32>>2]=dst;var zonePtr=HEAP32[__get_tzname()+(dst?4:0)>>2];HEAP32[tmPtr+40>>2]=zonePtr;return tmPtr}function _usleep(useconds){var msec=useconds/1e3;if((ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)&&self["performance"]&&self["performance"]["now"]){var start=self["performance"]["now"]();while(self["performance"]["now"]()-start>2];var nanoseconds=HEAP32[rqtp+4>>2];if(nanoseconds<0||nanoseconds>999999999||seconds<0){___setErrNo(28);return-1}if(rmtp!==0){HEAP32[rmtp>>2]=0;HEAP32[rmtp+4>>2]=0}return _usleep(seconds*1e6+nanoseconds/1e3)}function _pthread_cond_destroy(){return 0}function _pthread_cond_init(){return 0}function _pthread_create(){return 6}function _pthread_join(){}function __isLeapYear(year){return year%4===0&&(year%100!==0||year%400===0)}function __arraySum(array,index){var sum=0;for(var i=0;i<=index;sum+=array[i++]);return sum}var __MONTH_DAYS_LEAP=[31,29,31,30,31,30,31,31,30,31,30,31];var __MONTH_DAYS_REGULAR=[31,28,31,30,31,30,31,31,30,31,30,31];function __addDays(date,days){var newDate=new Date(date.getTime());while(days>0){var leap=__isLeapYear(newDate.getFullYear());var currentMonth=newDate.getMonth();var daysInCurrentMonth=(leap?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR)[currentMonth];if(days>daysInCurrentMonth-newDate.getDate()){days-=daysInCurrentMonth-newDate.getDate()+1;newDate.setDate(1);if(currentMonth<11){newDate.setMonth(currentMonth+1)}else{newDate.setMonth(0);newDate.setFullYear(newDate.getFullYear()+1)}}else{newDate.setDate(newDate.getDate()+days);return newDate}}return newDate}function _strftime(s,maxsize,format,tm){var tm_zone=HEAP32[tm+40>>2];var date={tm_sec:HEAP32[tm>>2],tm_min:HEAP32[tm+4>>2],tm_hour:HEAP32[tm+8>>2],tm_mday:HEAP32[tm+12>>2],tm_mon:HEAP32[tm+16>>2],tm_year:HEAP32[tm+20>>2],tm_wday:HEAP32[tm+24>>2],tm_yday:HEAP32[tm+28>>2],tm_isdst:HEAP32[tm+32>>2],tm_gmtoff:HEAP32[tm+36>>2],tm_zone:tm_zone?UTF8ToString(tm_zone):""};var pattern=UTF8ToString(format);var EXPANSION_RULES_1={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"};for(var rule in EXPANSION_RULES_1){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_1[rule])}var WEEKDAYS=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];var MONTHS=["January","February","March","April","May","June","July","August","September","October","November","December"];function leadingSomething(value,digits,character){var str=typeof value==="number"?value.toString():value||"";while(str.length0?1:0}var compare;if((compare=sgn(date1.getFullYear()-date2.getFullYear()))===0){if((compare=sgn(date1.getMonth()-date2.getMonth()))===0){compare=sgn(date1.getDate()-date2.getDate())}}return compare}function getFirstWeekStartDate(janFourth){switch(janFourth.getDay()){case 0:return new Date(janFourth.getFullYear()-1,11,29);case 1:return janFourth;case 2:return new Date(janFourth.getFullYear(),0,3);case 3:return new Date(janFourth.getFullYear(),0,2);case 4:return new Date(janFourth.getFullYear(),0,1);case 5:return new Date(janFourth.getFullYear()-1,11,31);case 6:return new Date(janFourth.getFullYear()-1,11,30)}}function getWeekBasedYear(date){var thisDate=__addDays(new Date(date.tm_year+1900,0,1),date.tm_yday);var janFourthThisYear=new Date(thisDate.getFullYear(),0,4);var janFourthNextYear=new Date(thisDate.getFullYear()+1,0,4);var firstWeekStartThisYear=getFirstWeekStartDate(janFourthThisYear);var firstWeekStartNextYear=getFirstWeekStartDate(janFourthNextYear);if(compareByDay(firstWeekStartThisYear,thisDate)<=0){if(compareByDay(firstWeekStartNextYear,thisDate)<=0){return thisDate.getFullYear()+1}else{return thisDate.getFullYear()}}else{return thisDate.getFullYear()-1}}var EXPANSION_RULES_2={"%a":function(date){return WEEKDAYS[date.tm_wday].substring(0,3)},"%A":function(date){return WEEKDAYS[date.tm_wday]},"%b":function(date){return MONTHS[date.tm_mon].substring(0,3)},"%B":function(date){return MONTHS[date.tm_mon]},"%C":function(date){var year=date.tm_year+1900;return leadingNulls(year/100|0,2)},"%d":function(date){return leadingNulls(date.tm_mday,2)},"%e":function(date){return leadingSomething(date.tm_mday,2," ")},"%g":function(date){return getWeekBasedYear(date).toString().substring(2)},"%G":function(date){return getWeekBasedYear(date)},"%H":function(date){return leadingNulls(date.tm_hour,2)},"%I":function(date){var twelveHour=date.tm_hour;if(twelveHour==0)twelveHour=12;else if(twelveHour>12)twelveHour-=12;return leadingNulls(twelveHour,2)},"%j":function(date){return leadingNulls(date.tm_mday+__arraySum(__isLeapYear(date.tm_year+1900)?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR,date.tm_mon-1),3)},"%m":function(date){return leadingNulls(date.tm_mon+1,2)},"%M":function(date){return leadingNulls(date.tm_min,2)},"%n":function(){return"\n"},"%p":function(date){if(date.tm_hour>=0&&date.tm_hour<12){return"AM"}else{return"PM"}},"%S":function(date){return leadingNulls(date.tm_sec,2)},"%t":function(){return"\t"},"%u":function(date){return date.tm_wday||7},"%U":function(date){var janFirst=new Date(date.tm_year+1900,0,1);var firstSunday=janFirst.getDay()===0?janFirst:__addDays(janFirst,7-janFirst.getDay());var endDate=new Date(date.tm_year+1900,date.tm_mon,date.tm_mday);if(compareByDay(firstSunday,endDate)<0){var februaryFirstUntilEndMonth=__arraySum(__isLeapYear(endDate.getFullYear())?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR,endDate.getMonth()-1)-31;var firstSundayUntilEndJanuary=31-firstSunday.getDate();var days=firstSundayUntilEndJanuary+februaryFirstUntilEndMonth+endDate.getDate();return leadingNulls(Math.ceil(days/7),2)}return compareByDay(firstSunday,janFirst)===0?"01":"00"},"%V":function(date){var janFourthThisYear=new Date(date.tm_year+1900,0,4);var janFourthNextYear=new Date(date.tm_year+1901,0,4);var firstWeekStartThisYear=getFirstWeekStartDate(janFourthThisYear);var firstWeekStartNextYear=getFirstWeekStartDate(janFourthNextYear);var endDate=__addDays(new Date(date.tm_year+1900,0,1),date.tm_yday);if(compareByDay(endDate,firstWeekStartThisYear)<0){return"53"}if(compareByDay(firstWeekStartNextYear,endDate)<=0){return"01"}var daysDifference;if(firstWeekStartThisYear.getFullYear()=0;off=Math.abs(off)/60;off=off/60*100+off%60;return(ahead?"+":"-")+String("0000"+off).slice(-4)},"%Z":function(date){return date.tm_zone},"%%":function(){return"%"}};for(var rule in EXPANSION_RULES_2){if(pattern.indexOf(rule)>=0){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_2[rule](date))}}var bytes=intArrayFromString(pattern,false);if(bytes.length>maxsize){return 0}writeArrayToMemory(bytes,s);return bytes.length-1}function _sysconf(name){switch(name){case 30:return PAGE_SIZE;case 85:var maxHeapSize=2*1024*1024*1024-65536;maxHeapSize=HEAPU8.length;return maxHeapSize/PAGE_SIZE;case 132:case 133:case 12:case 137:case 138:case 15:case 235:case 16:case 17:case 18:case 19:case 20:case 149:case 13:case 10:case 236:case 153:case 9:case 21:case 22:case 159:case 154:case 14:case 77:case 78:case 139:case 80:case 81:case 82:case 68:case 67:case 164:case 11:case 29:case 47:case 48:case 95:case 52:case 51:case 46:return 200809;case 79:return 0;case 27:case 246:case 127:case 128:case 23:case 24:case 160:case 161:case 181:case 182:case 242:case 183:case 184:case 243:case 244:case 245:case 165:case 178:case 179:case 49:case 50:case 168:case 169:case 175:case 170:case 171:case 172:case 97:case 76:case 32:case 173:case 35:return-1;case 176:case 177:case 7:case 155:case 8:case 157:case 125:case 126:case 92:case 93:case 129:case 130:case 131:case 94:case 91:return 1;case 74:case 60:case 69:case 70:case 4:return 1024;case 31:case 42:case 72:return 32;case 87:case 26:case 33:return 2147483647;case 34:case 1:return 47839;case 38:case 36:return 99;case 43:case 37:return 2048;case 0:return 2097152;case 3:return 65536;case 28:return 32768;case 44:return 32767;case 75:return 16384;case 39:return 1e3;case 89:return 700;case 71:return 256;case 40:return 255;case 2:return 100;case 180:return 64;case 25:return 20;case 5:return 16;case 6:return 6;case 73:return 4;case 84:{if(typeof navigator==="object")return navigator["hardwareConcurrency"]||1;return 1}}___setErrNo(28);return-1}FS.staticInit();Module["FS_createFolder"]=FS.createFolder;Module["FS_createPath"]=FS.createPath;Module["FS_createDataFile"]=FS.createDataFile;Module["FS_createPreloadedFile"]=FS.createPreloadedFile;Module["FS_createLazyFile"]=FS.createLazyFile;Module["FS_createLink"]=FS.createLink;Module["FS_createDevice"]=FS.createDevice;Module["FS_unlink"]=FS.unlink;if(ENVIRONMENT_IS_NODE){_emscripten_get_now=function _emscripten_get_now_actual(){var t=process["hrtime"]();return t[0]*1e3+t[1]/1e6}}else if(typeof dateNow!=="undefined"){_emscripten_get_now=dateNow}else _emscripten_get_now=function(){return performance["now"]()};function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}var asmLibraryArg={"z":___lock,"l":___syscall10,"E":___syscall195,"G":___syscall196,"F":___syscall197,"u":___syscall220,"f":___syscall221,"w":___syscall3,"x":___syscall33,"C":___syscall38,"v":___syscall4,"k":___syscall40,"j":___syscall5,"B":___syscall54,"e":___unlock,"a":_abort,"J":_clock,"m":_clock_gettime,"q":_emscripten_memcpy_big,"r":_emscripten_resize_heap,"s":_environ_get,"t":_environ_sizes_get,"d":_fd_close,"i":_fd_fdstat_get,"y":_fd_read,"p":_fd_seek,"A":_fd_write,"L":_ftime,"I":_gettimeofday,"K":_gmtime_r,"D":_localtime_r,"memory":wasmMemory,"H":_nanosleep,"b":_pthread_cond_destroy,"c":_pthread_cond_init,"g":_pthread_create,"h":_pthread_join,"o":_strftime,"n":_sysconf,"table":wasmTable};var asm=createWasm();Module["asm"]=asm;var ___wasm_call_ctors=Module["___wasm_call_ctors"]=function(){return Module["asm"]["M"].apply(null,arguments)};var _initDecoder=Module["_initDecoder"]=function(){return Module["asm"]["N"].apply(null,arguments)};var ___errno_location=Module["___errno_location"]=function(){return Module["asm"]["O"].apply(null,arguments)};var _uninitDecoder=Module["_uninitDecoder"]=function(){return Module["asm"]["P"].apply(null,arguments)};var _openDecoder=Module["_openDecoder"]=function(){return Module["asm"]["Q"].apply(null,arguments)};var _closeDecoder=Module["_closeDecoder"]=function(){return Module["asm"]["R"].apply(null,arguments)};var _sendData=Module["_sendData"]=function(){return Module["asm"]["S"].apply(null,arguments)};var _decodeOnePacket=Module["_decodeOnePacket"]=function(){return Module["asm"]["T"].apply(null,arguments)};var _seekTo=Module["_seekTo"]=function(){return Module["asm"]["U"].apply(null,arguments)};var _main=Module["_main"]=function(){return Module["asm"]["V"].apply(null,arguments)};var _free=Module["_free"]=function(){return Module["asm"]["W"].apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return Module["asm"]["X"].apply(null,arguments)};var __get_tzname=Module["__get_tzname"]=function(){return Module["asm"]["Y"].apply(null,arguments)};var __get_daylight=Module["__get_daylight"]=function(){return Module["asm"]["Z"].apply(null,arguments)};var __get_timezone=Module["__get_timezone"]=function(){return Module["asm"]["_"].apply(null,arguments)};var stackAlloc=Module["stackAlloc"]=function(){return Module["asm"]["$"].apply(null,arguments)};var dynCall_v=Module["dynCall_v"]=function(){return Module["asm"]["aa"].apply(null,arguments)};var dynCall_vi=Module["dynCall_vi"]=function(){return Module["asm"]["ba"].apply(null,arguments)};Module["asm"]=asm;Module["getMemory"]=getMemory;Module["addRunDependency"]=addRunDependency;Module["removeRunDependency"]=removeRunDependency;Module["FS_createFolder"]=FS.createFolder;Module["FS_createPath"]=FS.createPath;Module["FS_createDataFile"]=FS.createDataFile;Module["FS_createPreloadedFile"]=FS.createPreloadedFile;Module["FS_createLazyFile"]=FS.createLazyFile;Module["FS_createLink"]=FS.createLink;Module["FS_createDevice"]=FS.createDevice;Module["FS_unlink"]=FS.unlink;Module["addFunction"]=addFunction;Module["calledRun"]=calledRun;var calledRun;function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}var calledMain=false;dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function callMain(args){var entryFunction=Module["_main"];var argc=0;var argv=0;try{var ret=entryFunction(argc,argv);exit(ret,true)}catch(e){if(e instanceof ExitStatus){return}else if(e=="unwind"){noExitRuntime=true;return}else{var toLog=e;if(e&&typeof e==="object"&&e.stack){toLog=[e,e.stack]}err("exception thrown: "+toLog);quit_(1,e)}}finally{calledMain=true}}function run(args){args=args||arguments_;if(runDependencies>0){return}preRun();if(runDependencies>0)return;function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();preMain();if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();if(shouldRunNow)callMain(args);postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}Module["run"]=run;function exit(status,implicit){if(implicit&&noExitRuntime&&status===0){return}if(noExitRuntime){}else{ABORT=true;EXITSTATUS=status;exitRuntime();if(Module["onExit"])Module["onExit"](status)}quit_(status,new ExitStatus(status))}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}var shouldRunNow=true;if(Module["noInitialRun"])shouldRunNow=false;noExitRuntime=true;run(); 2 | -------------------------------------------------------------------------------- /src/libffmpeg.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcper/react-h265-wasm-player/57e03681a08e7ccfa36a45d317a74740b8d072c1/src/libffmpeg.wasm -------------------------------------------------------------------------------- /src/pcm-player.js: -------------------------------------------------------------------------------- 1 | function PCMPlayer(option) { 2 | this.init(option); 3 | } 4 | 5 | PCMPlayer.prototype.init = function(option) { 6 | var defaults = { 7 | encoding: '16bitInt', 8 | channels: 1, 9 | sampleRate: 8000, 10 | flushingTime: 1000 11 | }; 12 | this.option = Object.assign({}, defaults, option); 13 | this.samples = new Float32Array(); 14 | this.flush = this.flush.bind(this); 15 | this.interval = setInterval(this.flush, this.option.flushingTime); 16 | this.maxValue = this.getMaxValue(); 17 | this.typedArray = this.getTypedArray(); 18 | this.createContext(); 19 | }; 20 | 21 | PCMPlayer.prototype.getMaxValue = function () { 22 | var encodings = { 23 | '8bitInt': 128, 24 | '16bitInt': 32768, 25 | '32bitInt': 2147483648, 26 | '32bitFloat': 1 27 | } 28 | 29 | return encodings[this.option.encoding] ? encodings[this.option.encoding] : encodings['16bitInt']; 30 | }; 31 | 32 | PCMPlayer.prototype.getTypedArray = function () { 33 | var typedArrays = { 34 | '8bitInt': Int8Array, 35 | '16bitInt': Int16Array, 36 | '32bitInt': Int32Array, 37 | '32bitFloat': Float32Array 38 | } 39 | 40 | return typedArrays[this.option.encoding] ? typedArrays[this.option.encoding] : typedArrays['16bitInt']; 41 | }; 42 | 43 | PCMPlayer.prototype.createContext = function() { 44 | this.audioCtx = new (window.AudioContext || window.webkitAudioContext)(); 45 | this.gainNode = this.audioCtx.createGain(); 46 | this.gainNode.gain.value = 1; 47 | this.gainNode.connect(this.audioCtx.destination); 48 | this.startTime = this.audioCtx.currentTime; 49 | }; 50 | 51 | PCMPlayer.prototype.isTypedArray = function(data) { 52 | return (data.byteLength && data.buffer && data.buffer.constructor == ArrayBuffer); 53 | }; 54 | 55 | PCMPlayer.prototype.feed = function(data) { 56 | if (!this.isTypedArray(data)) return; 57 | data = this.getFormatedValue(data); 58 | var tmp = new Float32Array(this.samples.length + data.length); 59 | tmp.set(this.samples, 0); 60 | tmp.set(data, this.samples.length); 61 | this.samples = tmp; 62 | }; 63 | 64 | PCMPlayer.prototype.getFormatedValue = function(data) { 65 | var data = new this.typedArray(data.buffer), 66 | float32 = new Float32Array(data.length), 67 | i; 68 | 69 | for (i = 0; i < data.length; i++) { 70 | float32[i] = data[i] / this.maxValue; 71 | } 72 | return float32; 73 | }; 74 | 75 | PCMPlayer.prototype.volume = function(volume) { 76 | this.gainNode.gain.value = volume; 77 | }; 78 | 79 | PCMPlayer.prototype.destroy = function() { 80 | if (this.interval) { 81 | clearInterval(this.interval); 82 | } 83 | this.samples = null; 84 | this.audioCtx.close(); 85 | this.audioCtx = null; 86 | }; 87 | 88 | PCMPlayer.prototype.flush = function() { 89 | if (!this.samples.length) return; 90 | var bufferSource = this.audioCtx.createBufferSource(), 91 | length = this.samples.length / this.option.channels, 92 | audioBuffer = this.audioCtx.createBuffer(this.option.channels, length, this.option.sampleRate), 93 | audioData, 94 | channel, 95 | offset, 96 | i, 97 | decrement; 98 | 99 | for (channel = 0; channel < this.option.channels; channel++) { 100 | audioData = audioBuffer.getChannelData(channel); 101 | offset = channel; 102 | decrement = 50; 103 | for (i = 0; i < length; i++) { 104 | audioData[i] = this.samples[offset]; 105 | /* fadein */ 106 | if (i < 50) { 107 | audioData[i] = (audioData[i] * i) / 50; 108 | } 109 | /* fadeout*/ 110 | if (i >= (length - 51)) { 111 | audioData[i] = (audioData[i] * decrement--) / 50; 112 | } 113 | offset += this.option.channels; 114 | } 115 | } 116 | 117 | if (this.startTime < this.audioCtx.currentTime) { 118 | this.startTime = this.audioCtx.currentTime; 119 | } 120 | //console.log('start vs current '+this.startTime+' vs '+this.audioCtx.currentTime+' duration: '+audioBuffer.duration); 121 | bufferSource.buffer = audioBuffer; 122 | bufferSource.connect(this.gainNode); 123 | bufferSource.start(this.startTime); 124 | this.startTime += audioBuffer.duration; 125 | this.samples = new Float32Array(); 126 | }; 127 | 128 | PCMPlayer.prototype.getTimestamp = function () { 129 | if (this.audioCtx) { 130 | return this.audioCtx.currentTime; 131 | } else { 132 | return 0; 133 | } 134 | }; 135 | 136 | PCMPlayer.prototype.play = function (data) { 137 | if (!this.isTypedArray(data)) { 138 | return; 139 | } 140 | 141 | data = this.getFormatedValue(data); 142 | if (!data.length) { 143 | return; 144 | } 145 | 146 | var bufferSource = this.audioCtx.createBufferSource(), 147 | length = data.length / this.option.channels, 148 | audioBuffer = this.audioCtx.createBuffer(this.option.channels, length, this.option.sampleRate), 149 | audioData, 150 | channel, 151 | offset, 152 | i, 153 | decrement; 154 | 155 | for (channel = 0; channel < this.option.channels; channel++) { 156 | audioData = audioBuffer.getChannelData(channel); 157 | offset = channel; 158 | decrement = 50; 159 | for (i = 0; i < length; i++) { 160 | audioData[i] = data[offset]; 161 | /* fadein */ 162 | if (i < 50) { 163 | audioData[i] = (audioData[i] * i) / 50; 164 | } 165 | /* fadeout*/ 166 | if (i >= (length - 51)) { 167 | audioData[i] = (audioData[i] * decrement--) / 50; 168 | } 169 | offset += this.option.channels; 170 | } 171 | } 172 | 173 | if (this.startTime < this.audioCtx.currentTime) { 174 | this.startTime = this.audioCtx.currentTime; 175 | } 176 | //console.log('start vs current '+this.startTime+' vs '+this.audioCtx.currentTime+' duration: '+audioBuffer.duration); 177 | bufferSource.buffer = audioBuffer; 178 | bufferSource.connect(this.gainNode); 179 | bufferSource.start(this.startTime); 180 | this.startTime += audioBuffer.duration; 181 | }; 182 | 183 | PCMPlayer.prototype.pause = function () { 184 | if (this.audioCtx.state === 'running') { 185 | this.audioCtx.suspend() 186 | } 187 | } 188 | 189 | PCMPlayer.prototype.resume = function () { 190 | if (this.audioCtx.state === 'suspended') { 191 | this.audioCtx.resume() 192 | } 193 | } 194 | 195 | 196 | -------------------------------------------------------------------------------- /src/player.js: -------------------------------------------------------------------------------- 1 | //Decoder states. 2 | const decoderStateIdle = 0; 3 | const decoderStateInitializing = 1; 4 | const decoderStateReady = 2; 5 | const decoderStateFinished = 3; 6 | 7 | //Player states. 8 | const playerStateIdle = 0; 9 | const playerStatePlaying = 1; 10 | const playerStatePausing = 2; 11 | 12 | //Constant. 13 | const maxBufferTimeLength = 1.0; 14 | const downloadSpeedByteRateCoef = 2.0; 15 | 16 | String.prototype.startWith = function(str) { 17 | var reg = new RegExp("^" + str); 18 | return reg.test(this); 19 | }; 20 | 21 | function FileInfo(url) { 22 | this.url = url; 23 | this.size = 0; 24 | this.offset = 0; 25 | this.chunkSize = 65536; 26 | } 27 | 28 | function Player() { 29 | this.fileInfo = null; 30 | this.pcmPlayer = null; 31 | this.canvas = null; 32 | this.webglPlayer = null; 33 | this.callback = null; 34 | this.waitHeaderLength = 524288; 35 | this.duration = 0; 36 | this.pixFmt = 0; 37 | this.videoWidth = 0; 38 | this.videoHeight = 0; 39 | this.yLength = 0; 40 | this.uvLength = 0; 41 | this.beginTimeOffset = 0; 42 | this.decoderState = decoderStateIdle; 43 | this.playerState = playerStateIdle; 44 | this.decoding = false; 45 | this.decodeInterval = 5; 46 | this.videoRendererTimer = null; 47 | this.downloadTimer = null; 48 | this.chunkInterval = 200; 49 | this.downloadSeqNo = 0; 50 | this.downloading = false; 51 | this.downloadProto = kProtoHttp; 52 | this.timeLabel = null; 53 | this.timeTrack = null; 54 | this.trackTimer = null; 55 | this.trackTimerInterval = 500; 56 | this.displayDuration = "00:00:00"; 57 | this.audioEncoding = ""; 58 | this.audioChannels = 0; 59 | this.audioSampleRate = 0; 60 | this.seeking = false; // Flag to preventing multi seek from track. 61 | this.justSeeked = false; // Flag to preventing multi seek from ffmpeg. 62 | this.urgent = false; 63 | this.seekWaitLen = 524288; // Default wait for 512K, will be updated in onVideoParam. 64 | this.seekReceivedLen = 0; 65 | this.loadingDiv = null; 66 | this.buffering = false; 67 | this.frameBuffer = []; 68 | this.isStream = false; 69 | this.streamReceivedLen = 0; 70 | this.firstAudioFrame = true; 71 | this.fetchController = null; 72 | this.streamPauseParam = null; 73 | this.logger = new Logger("Player"); 74 | this.initDownloadWorker(); 75 | this.initDecodeWorker(); 76 | } 77 | 78 | Player.prototype.initDownloadWorker = function () { 79 | var self = this; 80 | this.downloadWorker = new Worker("downloader.js"); 81 | this.downloadWorker.onmessage = function (evt) { 82 | var objData = evt.data; 83 | switch (objData.t) { 84 | case kGetFileInfoRsp: 85 | self.onGetFileInfo(objData.i); 86 | break; 87 | case kFileData: 88 | self.onFileData(objData.d, objData.s, objData.e, objData.q); 89 | break; 90 | } 91 | } 92 | }; 93 | 94 | Player.prototype.initDecodeWorker = function () { 95 | var self = this; 96 | this.decodeWorker = new Worker("decoder.js"); 97 | this.decodeWorker.onmessage = function (evt) { 98 | var objData = evt.data; 99 | switch (objData.t) { 100 | case kInitDecoderRsp: 101 | self.onInitDecoder(objData); 102 | break; 103 | case kOpenDecoderRsp: 104 | self.onOpenDecoder(objData); 105 | break; 106 | case kVideoFrame: 107 | self.onVideoFrame(objData); 108 | break; 109 | case kAudioFrame: 110 | self.onAudioFrame(objData); 111 | break; 112 | case kDecodeFinishedEvt: 113 | self.onDecodeFinished(objData); 114 | break; 115 | case kRequestDataEvt: 116 | self.onRequestData(objData.o, objData.a); 117 | break; 118 | case kSeekToRsp: 119 | self.onSeekToRsp(objData.r); 120 | break; 121 | } 122 | } 123 | }; 124 | 125 | Player.prototype.play = function (url, canvas, callback, waitHeaderLength, isStream) { 126 | this.logger.logInfo("Play " + url + "."); 127 | 128 | var ret = { 129 | e: 0, 130 | m: "Success" 131 | }; 132 | 133 | var success = true; 134 | do { 135 | if (this.playerState == playerStatePausing) { 136 | ret = this.resume(); 137 | break; 138 | } 139 | 140 | if (this.playerState == playerStatePlaying) { 141 | break; 142 | } 143 | 144 | if (!url) { 145 | ret = { 146 | e: -1, 147 | m: "Invalid url" 148 | }; 149 | success = false; 150 | this.logger.logError("[ER] playVideo error, url empty."); 151 | break; 152 | } 153 | 154 | if (!canvas) { 155 | ret = { 156 | e: -2, 157 | m: "Canvas not set" 158 | }; 159 | success = false; 160 | this.logger.logError("[ER] playVideo error, canvas empty."); 161 | break; 162 | } 163 | 164 | if (!this.downloadWorker) { 165 | ret = { 166 | e: -3, 167 | m: "Downloader not initialized" 168 | }; 169 | success = false; 170 | this.logger.logError("[ER] Downloader not initialized."); 171 | break 172 | } 173 | 174 | if (!this.decodeWorker) { 175 | ret = { 176 | e: -4, 177 | m: "Decoder not initialized" 178 | }; 179 | success = false; 180 | this.logger.logError("[ER] Decoder not initialized."); 181 | break 182 | } 183 | 184 | if (url.startWith("ws://") || url.startWith("wss://")) { 185 | this.downloadProto = kProtoWebsocket; 186 | } else { 187 | this.downloadProto = kProtoHttp; 188 | } 189 | 190 | this.fileInfo = new FileInfo(url); 191 | this.canvas = canvas; 192 | this.callback = callback; 193 | this.waitHeaderLength = waitHeaderLength || this.waitHeaderLength; 194 | this.playerState = playerStatePlaying; 195 | this.isStream = isStream; 196 | this.startTrackTimer(); 197 | this.displayLoop(); 198 | 199 | //var playCanvasContext = playCanvas.getContext("2d"); //If get 2d, webgl will be disabled. 200 | this.webglPlayer = new WebGLPlayer(this.canvas, { 201 | preserveDrawingBuffer: false 202 | }); 203 | 204 | if (!this.isStream) { 205 | var req = { 206 | t: kGetFileInfoReq, 207 | u: url, 208 | p: this.downloadProto 209 | }; 210 | this.downloadWorker.postMessage(req); 211 | } else { 212 | this.requestStream(url); 213 | this.onGetFileInfo({ 214 | sz: -1, 215 | st: 200 216 | }); 217 | } 218 | 219 | var self = this; 220 | this.registerVisibilityEvent(function(visible) { 221 | if (visible) { 222 | self.resume(); 223 | } else { 224 | self.pause(); 225 | } 226 | }); 227 | 228 | this.buffering = true; 229 | this.showLoading(); 230 | } while (false); 231 | 232 | return ret; 233 | }; 234 | 235 | Player.prototype.pauseStream = function () { 236 | if (this.playerState != playerStatePlaying) { 237 | var ret = { 238 | e: -1, 239 | m: "Not playing" 240 | }; 241 | return ret; 242 | } 243 | 244 | this.streamPauseParam = { 245 | url: this.fileInfo.url, 246 | canvas: this.canvas, 247 | callback: this.callback, 248 | waitHeaderLength: this.waitHeaderLength 249 | } 250 | 251 | this.logger.logInfo("Stop in stream pause."); 252 | this.stop(); 253 | 254 | var ret = { 255 | e: 0, 256 | m: "Success" 257 | }; 258 | 259 | return ret; 260 | } 261 | 262 | Player.prototype.pause = function () { 263 | if (this.isStream) { 264 | return this.pauseStream(); 265 | } 266 | 267 | this.logger.logInfo("Pause."); 268 | 269 | if (this.playerState != playerStatePlaying) { 270 | var ret = { 271 | e: -1, 272 | m: "Not playing" 273 | }; 274 | return ret; 275 | } 276 | 277 | //Pause video rendering and audio flushing. 278 | this.playerState = playerStatePausing; 279 | 280 | //Pause audio context. 281 | if (this.pcmPlayer) { 282 | this.pcmPlayer.pause(); 283 | } 284 | 285 | //Pause decoding. 286 | this.pauseDecoding(); 287 | 288 | //Stop track timer. 289 | this.stopTrackTimer(); 290 | 291 | //Do not stop downloader for background buffering. 292 | var ret = { 293 | e: 0, 294 | m: "Success" 295 | }; 296 | 297 | return ret; 298 | }; 299 | 300 | Player.prototype.resumeStream = function () { 301 | if (this.playerState != playerStateIdle || !this.streamPauseParam) { 302 | var ret = { 303 | e: -1, 304 | m: "Not pausing" 305 | }; 306 | return ret; 307 | } 308 | 309 | this.logger.logInfo("Play in stream resume."); 310 | this.play(this.streamPauseParam.url, 311 | this.streamPauseParam.canvas, 312 | this.streamPauseParam.callback, 313 | this.streamPauseParam.waitHeaderLength, 314 | true); 315 | this.streamPauseParam = null; 316 | 317 | var ret = { 318 | e: 0, 319 | m: "Success" 320 | }; 321 | 322 | return ret; 323 | } 324 | 325 | Player.prototype.resume = function (fromSeek) { 326 | if (this.isStream) { 327 | return this.resumeStream(); 328 | } 329 | 330 | this.logger.logInfo("Resume."); 331 | 332 | if (this.playerState != playerStatePausing) { 333 | var ret = { 334 | e: -1, 335 | m: "Not pausing" 336 | }; 337 | return ret; 338 | } 339 | 340 | if (!fromSeek) { 341 | //Resume audio context. 342 | this.pcmPlayer.resume(); 343 | } 344 | 345 | //If there's a flying video renderer op, interrupt it. 346 | if (this.videoRendererTimer != null) { 347 | clearTimeout(this.videoRendererTimer); 348 | this.videoRendererTimer = null; 349 | } 350 | 351 | //Restart video rendering and audio flushing. 352 | this.playerState = playerStatePlaying; 353 | 354 | //Restart decoding. 355 | this.startDecoding(); 356 | 357 | //Restart track timer. 358 | if (!this.seeking) { 359 | this.startTrackTimer(); 360 | } 361 | 362 | var ret = { 363 | e: 0, 364 | m: "Success" 365 | }; 366 | return ret; 367 | }; 368 | 369 | Player.prototype.stop = function () { 370 | this.logger.logInfo("Stop."); 371 | if (this.playerState == playerStateIdle) { 372 | var ret = { 373 | e: -1, 374 | m: "Not playing" 375 | }; 376 | return ret; 377 | } 378 | 379 | if (this.videoRendererTimer != null) { 380 | clearTimeout(this.videoRendererTimer); 381 | this.videoRendererTimer = null; 382 | this.logger.logInfo("Video renderer timer stopped."); 383 | } 384 | 385 | this.stopDownloadTimer(); 386 | this.stopTrackTimer(); 387 | this.hideLoading(); 388 | 389 | this.fileInfo = null; 390 | this.canvas = null; 391 | this.webglPlayer = null; 392 | this.callback = null; 393 | this.duration = 0; 394 | this.pixFmt = 0; 395 | this.videoWidth = 0; 396 | this.videoHeight = 0; 397 | this.yLength = 0; 398 | this.uvLength = 0; 399 | this.beginTimeOffset = 0; 400 | this.decoderState = decoderStateIdle; 401 | this.playerState = playerStateIdle; 402 | this.decoding = false; 403 | this.frameBuffer = []; 404 | this.buffering = false; 405 | this.streamReceivedLen = 0; 406 | this.firstAudioFrame = true; 407 | this.urgent = false; 408 | this.seekReceivedLen = 0; 409 | 410 | if (this.pcmPlayer) { 411 | this.pcmPlayer.destroy(); 412 | this.pcmPlayer = null; 413 | this.logger.logInfo("Pcm player released."); 414 | } 415 | 416 | if (this.timeTrack) { 417 | this.timeTrack.value = 0; 418 | } 419 | if (this.timeLabel) { 420 | this.timeLabel.innerHTML = this.formatTime(0) + "/" + this.displayDuration; 421 | } 422 | 423 | this.logger.logInfo("Closing decoder."); 424 | this.decodeWorker.postMessage({ 425 | t: kCloseDecoderReq 426 | }); 427 | 428 | 429 | this.logger.logInfo("Uniniting decoder."); 430 | this.decodeWorker.postMessage({ 431 | t: kUninitDecoderReq 432 | }); 433 | 434 | if (this.fetchController) { 435 | this.fetchController.abort(); 436 | this.fetchController = null; 437 | } 438 | 439 | return ret; 440 | }; 441 | 442 | Player.prototype.seekTo = function(ms) { 443 | if (this.isStream) { 444 | return; 445 | } 446 | 447 | // Pause playing. 448 | this.pause(); 449 | 450 | // Stop download. 451 | this.stopDownloadTimer(); 452 | 453 | // Clear frame buffer. 454 | this.frameBuffer.length = 0; 455 | 456 | // Request decoder to seek. 457 | this.decodeWorker.postMessage({ 458 | t: kSeekToReq, 459 | ms: ms 460 | }); 461 | 462 | // Reset begin time offset. 463 | this.beginTimeOffset = ms / 1000; 464 | this.logger.logInfo("seekTo beginTimeOffset " + this.beginTimeOffset); 465 | 466 | this.seeking = true; 467 | this.justSeeked = true; 468 | this.urgent = true; 469 | this.seekReceivedLen = 0; 470 | this.startBuffering(); 471 | }; 472 | 473 | Player.prototype.fullscreen = function () { 474 | if (this.webglPlayer) { 475 | this.webglPlayer.fullscreen(); 476 | } 477 | }; 478 | 479 | Player.prototype.getState = function () { 480 | return this.playerState; 481 | }; 482 | 483 | Player.prototype.setTrack = function (timeTrack, timeLabel) { 484 | this.timeTrack = timeTrack; 485 | this.timeLabel = timeLabel; 486 | 487 | if (this.timeTrack) { 488 | var self = this; 489 | this.timeTrack.oninput = function () { 490 | if (!self.seeking) { 491 | self.seekTo(self.timeTrack.value); 492 | } 493 | } 494 | this.timeTrack.onchange = function () { 495 | if (!self.seeking) { 496 | self.seekTo(self.timeTrack.value); 497 | } 498 | } 499 | } 500 | }; 501 | 502 | Player.prototype.onGetFileInfo = function (info) { 503 | if (this.playerState == playerStateIdle) { 504 | return; 505 | } 506 | 507 | this.logger.logInfo("Got file size rsp:" + info.st + " size:" + info.sz + " byte."); 508 | if (info.st == 200) { 509 | this.fileInfo.size = Number(info.sz); 510 | this.logger.logInfo("Initializing decoder."); 511 | var req = { 512 | t: kInitDecoderReq, 513 | s: this.fileInfo.size, 514 | c: this.fileInfo.chunkSize 515 | }; 516 | this.decodeWorker.postMessage(req); 517 | } else { 518 | this.reportPlayError(-1, info.st); 519 | } 520 | }; 521 | 522 | Player.prototype.onFileData = function (data, start, end, seq) { 523 | //this.logger.logInfo("Got data bytes=" + start + "-" + end + "."); 524 | this.downloading = false; 525 | 526 | if (this.playerState == playerStateIdle) { 527 | return; 528 | } 529 | 530 | if (seq != this.downloadSeqNo) { 531 | return; // Old data. 532 | } 533 | 534 | if (this.playerState == playerStatePausing) { 535 | if (this.seeking) { 536 | this.seekReceivedLen += data.byteLength; 537 | let left = this.fileInfo.size - this.fileInfo.offset; 538 | let seekWaitLen = Math.min(left, this.seekWaitLen); 539 | if (this.seekReceivedLen >= seekWaitLen) { 540 | this.logger.logInfo("Resume in seek now"); 541 | setTimeout(() => { 542 | this.resume(true); 543 | }, 0); 544 | } 545 | } else { 546 | return; 547 | } 548 | } 549 | 550 | var len = end - start + 1; 551 | this.fileInfo.offset += len; 552 | 553 | var objData = { 554 | t: kFeedDataReq, 555 | d: data 556 | }; 557 | this.decodeWorker.postMessage(objData, [objData.d]); 558 | 559 | switch (this.decoderState) { 560 | case decoderStateIdle: 561 | this.onFileDataUnderDecoderIdle(); 562 | break; 563 | case decoderStateInitializing: 564 | this.onFileDataUnderDecoderInitializing(); 565 | break; 566 | case decoderStateReady: 567 | this.onFileDataUnderDecoderReady(); 568 | break; 569 | } 570 | 571 | if (this.urgent) { 572 | setTimeout(() => { 573 | this.downloadOneChunk(); 574 | }, 0); 575 | } 576 | }; 577 | 578 | Player.prototype.onFileDataUnderDecoderIdle = function () { 579 | if (this.fileInfo.offset >= this.waitHeaderLength || (!this.isStream && this.fileInfo.offset == this.fileInfo.size)) { 580 | this.logger.logInfo("Opening decoder."); 581 | this.decoderState = decoderStateInitializing; 582 | var req = { 583 | t: kOpenDecoderReq 584 | }; 585 | this.decodeWorker.postMessage(req); 586 | } 587 | 588 | this.downloadOneChunk(); 589 | }; 590 | 591 | Player.prototype.onFileDataUnderDecoderInitializing = function () { 592 | this.downloadOneChunk(); 593 | }; 594 | 595 | Player.prototype.onFileDataUnderDecoderReady = function () { 596 | //this.downloadOneChunk(); 597 | }; 598 | 599 | Player.prototype.onInitDecoder = function (objData) { 600 | if (this.playerState == playerStateIdle) { 601 | return; 602 | } 603 | 604 | this.logger.logInfo("Init decoder response " + objData.e + "."); 605 | if (objData.e == 0) { 606 | if (!this.isStream) { 607 | this.downloadOneChunk(); 608 | } 609 | } else { 610 | this.reportPlayError(objData.e); 611 | } 612 | }; 613 | 614 | Player.prototype.onOpenDecoder = function (objData) { 615 | if (this.playerState == playerStateIdle) { 616 | return; 617 | } 618 | 619 | this.logger.logInfo("Open decoder response " + objData.e + "."); 620 | if (objData.e == 0) { 621 | this.onVideoParam(objData.v); 622 | this.onAudioParam(objData.a); 623 | this.decoderState = decoderStateReady; 624 | this.logger.logInfo("Decoder ready now."); 625 | this.startDecoding(); 626 | } else { 627 | this.reportPlayError(objData.e); 628 | } 629 | }; 630 | 631 | Player.prototype.onVideoParam = function (v) { 632 | if (this.playerState == playerStateIdle) { 633 | return; 634 | } 635 | 636 | this.logger.logInfo("Video param duation:" + v.d + " pixFmt:" + v.p + " width:" + v.w + " height:" + v.h + "."); 637 | this.duration = v.d; 638 | this.pixFmt = v.p; 639 | //this.canvas.width = v.w; 640 | //this.canvas.height = v.h; 641 | this.videoWidth = v.w; 642 | this.videoHeight = v.h; 643 | this.yLength = this.videoWidth * this.videoHeight; 644 | this.uvLength = (this.videoWidth / 2) * (this.videoHeight / 2); 645 | 646 | /* 647 | //var playCanvasContext = playCanvas.getContext("2d"); //If get 2d, webgl will be disabled. 648 | this.webglPlayer = new WebGLPlayer(this.canvas, { 649 | preserveDrawingBuffer: false 650 | }); 651 | */ 652 | 653 | if (this.timeTrack) { 654 | this.timeTrack.min = 0; 655 | this.timeTrack.max = this.duration; 656 | this.timeTrack.value = 0; 657 | this.displayDuration = this.formatTime(this.duration / 1000); 658 | } 659 | 660 | var byteRate = 1000 * this.fileInfo.size / this.duration; 661 | var targetSpeed = downloadSpeedByteRateCoef * byteRate; 662 | var chunkPerSecond = targetSpeed / this.fileInfo.chunkSize; 663 | this.chunkInterval = 1000 / chunkPerSecond; 664 | this.seekWaitLen = byteRate * maxBufferTimeLength * 2; 665 | this.logger.logInfo("Seek wait len " + this.seekWaitLen); 666 | 667 | if (!this.isStream) { 668 | this.startDownloadTimer(); 669 | } 670 | 671 | this.logger.logInfo("Byte rate:" + byteRate + " target speed:" + targetSpeed + " chunk interval:" + this.chunkInterval + "."); 672 | }; 673 | 674 | Player.prototype.onAudioParam = function (a) { 675 | if (this.playerState == playerStateIdle) { 676 | return; 677 | } 678 | 679 | this.logger.logInfo("Audio param sampleFmt:" + a.f + " channels:" + a.c + " sampleRate:" + a.r + "."); 680 | 681 | var sampleFmt = a.f; 682 | var channels = a.c; 683 | var sampleRate = a.r; 684 | 685 | var encoding = "16bitInt"; 686 | switch (sampleFmt) { 687 | case 0: 688 | encoding = "8bitInt"; 689 | break; 690 | case 1: 691 | encoding = "16bitInt"; 692 | break; 693 | case 2: 694 | encoding = "32bitInt"; 695 | break; 696 | case 3: 697 | encoding = "32bitFloat"; 698 | break; 699 | default: 700 | this.logger.logError("Unsupported audio sampleFmt " + sampleFmt + "!"); 701 | } 702 | this.logger.logInfo("Audio encoding " + encoding + "."); 703 | 704 | this.pcmPlayer = new PCMPlayer({ 705 | encoding: encoding, 706 | channels: channels, 707 | sampleRate: sampleRate, 708 | flushingTime: 5000 709 | }); 710 | 711 | this.audioEncoding = encoding; 712 | this.audioChannels = channels; 713 | this.audioSampleRate = sampleRate; 714 | }; 715 | 716 | Player.prototype.restartAudio = function () { 717 | if (this.pcmPlayer) { 718 | this.pcmPlayer.destroy(); 719 | this.pcmPlayer = null; 720 | } 721 | 722 | this.pcmPlayer = new PCMPlayer({ 723 | encoding: this.audioEncoding, 724 | channels: this.audioChannels, 725 | sampleRate: this.audioSampleRate, 726 | flushingTime: 5000 727 | }); 728 | }; 729 | 730 | Player.prototype.bufferFrame = function (frame) { 731 | // If not decoding, it may be frame before seeking, should be discarded. 732 | if (!this.decoding) { 733 | return; 734 | } 735 | this.frameBuffer.push(frame); 736 | //this.logger.logInfo("bufferFrame " + frame.s + ", seq " + frame.q); 737 | if (this.getBufferTimerLength() >= maxBufferTimeLength || this.decoderState == decoderStateFinished) { 738 | if (this.decoding) { 739 | //this.logger.logInfo("Frame buffer time length >= " + maxBufferTimeLength + ", pause decoding."); 740 | this.pauseDecoding(); 741 | } 742 | if (this.buffering) { 743 | this.stopBuffering(); 744 | } 745 | } 746 | } 747 | 748 | Player.prototype.displayAudioFrame = function (frame) { 749 | if (this.playerState != playerStatePlaying) { 750 | return false; 751 | } 752 | 753 | if (this.seeking) { 754 | this.restartAudio(); 755 | this.startTrackTimer(); 756 | this.hideLoading(); 757 | this.seeking = false; 758 | this.urgent = false; 759 | } 760 | 761 | if (this.isStream && this.firstAudioFrame) { 762 | this.firstAudioFrame = false; 763 | this.beginTimeOffset = frame.s; 764 | } 765 | 766 | this.pcmPlayer.play(new Uint8Array(frame.d)); 767 | return true; 768 | }; 769 | 770 | Player.prototype.onAudioFrame = function (frame) { 771 | this.bufferFrame(frame); 772 | }; 773 | 774 | Player.prototype.onDecodeFinished = function (objData) { 775 | this.pauseDecoding(); 776 | this.decoderState = decoderStateFinished; 777 | }; 778 | 779 | Player.prototype.getBufferTimerLength = function() { 780 | if (!this.frameBuffer || this.frameBuffer.length == 0) { 781 | return 0; 782 | } 783 | 784 | let oldest = this.frameBuffer[0]; 785 | let newest = this.frameBuffer[this.frameBuffer.length - 1]; 786 | return newest.s - oldest.s; 787 | }; 788 | 789 | Player.prototype.onVideoFrame = function (frame) { 790 | this.bufferFrame(frame); 791 | }; 792 | 793 | Player.prototype.displayVideoFrame = function (frame) { 794 | if (this.playerState != playerStatePlaying) { 795 | return false; 796 | } 797 | 798 | if (this.seeking) { 799 | this.restartAudio(); 800 | this.startTrackTimer(); 801 | this.hideLoading(); 802 | this.seeking = false; 803 | this.urgent = false; 804 | } 805 | 806 | var audioCurTs = this.pcmPlayer.getTimestamp(); 807 | var audioTimestamp = audioCurTs + this.beginTimeOffset; 808 | var delay = frame.s - audioTimestamp; 809 | 810 | //this.logger.logInfo("displayVideoFrame delay=" + delay + "=" + " " + frame.s + " - (" + audioCurTs + " + " + this.beginTimeOffset + ")" + "->" + audioTimestamp); 811 | 812 | if (audioTimestamp <= 0 || delay <= 0) { 813 | var data = new Uint8Array(frame.d); 814 | this.renderVideoFrame(data); 815 | return true; 816 | } 817 | return false; 818 | }; 819 | 820 | Player.prototype.onSeekToRsp = function (ret) { 821 | if (ret != 0) { 822 | this.justSeeked = false; 823 | this.seeking = false; 824 | } 825 | }; 826 | 827 | Player.prototype.onRequestData = function (offset, available) { 828 | if (this.justSeeked) { 829 | this.logger.logInfo("Request data " + offset + ", available " + available); 830 | if (offset == -1) { 831 | // Hit in buffer. 832 | let left = this.fileInfo.size - this.fileInfo.offset; 833 | if (available >= left) { 834 | this.logger.logInfo("No need to wait"); 835 | this.resume(); 836 | } else { 837 | this.startDownloadTimer(); 838 | } 839 | } else { 840 | if (offset >= 0 && offset < this.fileInfo.size) { 841 | this.fileInfo.offset = offset; 842 | } 843 | this.startDownloadTimer(); 844 | } 845 | 846 | //this.restartAudio(); 847 | this.justSeeked = false; 848 | } 849 | }; 850 | 851 | Player.prototype.displayLoop = function() { 852 | if (this.playerState !== playerStateIdle) { 853 | requestAnimationFrame(this.displayLoop.bind(this)); 854 | } 855 | if (this.playerState != playerStatePlaying) { 856 | return; 857 | } 858 | 859 | if (this.frameBuffer.length == 0) { 860 | return; 861 | } 862 | 863 | if (this.buffering) { 864 | return; 865 | } 866 | 867 | // requestAnimationFrame may be 60fps, if stream fps too large, 868 | // we need to render more frames in one loop, otherwise display 869 | // fps won't catch up with source fps, leads to memory increasing, 870 | // set to 2 now. 871 | for (i = 0; i < 2; ++i) { 872 | var frame = this.frameBuffer[0]; 873 | switch (frame.t) { 874 | case kAudioFrame: 875 | if (this.displayAudioFrame(frame)) { 876 | this.frameBuffer.shift(); 877 | } 878 | break; 879 | case kVideoFrame: 880 | if (this.displayVideoFrame(frame)) { 881 | this.frameBuffer.shift(); 882 | } 883 | break; 884 | default: 885 | return; 886 | } 887 | 888 | if (this.frameBuffer.length == 0) { 889 | break; 890 | } 891 | } 892 | 893 | if (this.getBufferTimerLength() < maxBufferTimeLength / 2) { 894 | if (!this.decoding) { 895 | //this.logger.logInfo("Buffer time length < " + maxBufferTimeLength / 2 + ", restart decoding."); 896 | this.startDecoding(); 897 | } 898 | } 899 | 900 | if (this.bufferFrame.length == 0) { 901 | if (this.decoderState == decoderStateFinished) { 902 | this.reportPlayError(1, 0, "Finished"); 903 | this.stop(); 904 | } else { 905 | this.startBuffering(); 906 | } 907 | } 908 | }; 909 | 910 | Player.prototype.startBuffering = function () { 911 | this.buffering = true; 912 | this.showLoading(); 913 | this.pause(); 914 | } 915 | 916 | Player.prototype.stopBuffering = function () { 917 | this.buffering = false; 918 | this.hideLoading(); 919 | this.resume(); 920 | } 921 | 922 | Player.prototype.renderVideoFrame = function (data) { 923 | this.webglPlayer.renderFrame(data, this.videoWidth, this.videoHeight, this.yLength, this.uvLength); 924 | }; 925 | 926 | Player.prototype.downloadOneChunk = function () { 927 | if (this.downloading || this.isStream) { 928 | return; 929 | } 930 | 931 | var start = this.fileInfo.offset; 932 | if (start >= this.fileInfo.size) { 933 | this.logger.logError("Reach file end."); 934 | this.stopDownloadTimer(); 935 | return; 936 | } 937 | 938 | var end = this.fileInfo.offset + this.fileInfo.chunkSize - 1; 939 | if (end >= this.fileInfo.size) { 940 | end = this.fileInfo.size - 1; 941 | } 942 | 943 | var len = end - start + 1; 944 | if (len > this.fileInfo.chunkSize) { 945 | console.log("Error: request len:" + len + " > chunkSize:" + this.fileInfo.chunkSize); 946 | return; 947 | } 948 | 949 | var req = { 950 | t: kDownloadFileReq, 951 | u: this.fileInfo.url, 952 | s: start, 953 | e: end, 954 | q: this.downloadSeqNo, 955 | p: this.downloadProto 956 | }; 957 | this.downloadWorker.postMessage(req); 958 | this.downloading = true; 959 | }; 960 | 961 | Player.prototype.startDownloadTimer = function () { 962 | var self = this; 963 | this.downloadSeqNo++; 964 | this.downloadTimer = setInterval(function () { 965 | self.downloadOneChunk(); 966 | }, this.chunkInterval); 967 | }; 968 | 969 | Player.prototype.stopDownloadTimer = function () { 970 | if (this.downloadTimer != null) { 971 | clearInterval(this.downloadTimer); 972 | this.downloadTimer = null; 973 | } 974 | this.downloading = false; 975 | }; 976 | 977 | Player.prototype.startTrackTimer = function () { 978 | var self = this; 979 | this.trackTimer = setInterval(function () { 980 | self.updateTrackTime(); 981 | }, this.trackTimerInterval); 982 | }; 983 | 984 | Player.prototype.stopTrackTimer = function () { 985 | if (this.trackTimer != null) { 986 | clearInterval(this.trackTimer); 987 | this.trackTimer = null; 988 | } 989 | }; 990 | 991 | Player.prototype.updateTrackTime = function () { 992 | if (this.playerState == playerStatePlaying && this.pcmPlayer) { 993 | var currentPlayTime = this.pcmPlayer.getTimestamp() + this.beginTimeOffset; 994 | if (this.timeTrack) { 995 | this.timeTrack.value = 1000 * currentPlayTime; 996 | } 997 | 998 | if (this.timeLabel) { 999 | this.timeLabel.innerHTML = this.formatTime(currentPlayTime) + "/" + this.displayDuration; 1000 | } 1001 | } 1002 | }; 1003 | 1004 | Player.prototype.startDecoding = function () { 1005 | var req = { 1006 | t: kStartDecodingReq, 1007 | i: this.urgent ? 0 : this.decodeInterval, 1008 | }; 1009 | this.decodeWorker.postMessage(req); 1010 | this.decoding = true; 1011 | }; 1012 | 1013 | Player.prototype.pauseDecoding = function () { 1014 | var req = { 1015 | t: kPauseDecodingReq 1016 | }; 1017 | this.decodeWorker.postMessage(req); 1018 | this.decoding = false; 1019 | }; 1020 | 1021 | Player.prototype.formatTime = function (s) { 1022 | var h = Math.floor(s / 3600) < 10 ? '0' + Math.floor(s / 3600) : Math.floor(s / 3600); 1023 | var m = Math.floor((s / 60 % 60)) < 10 ? '0' + Math.floor((s / 60 % 60)) : Math.floor((s / 60 % 60)); 1024 | var s = Math.floor((s % 60)) < 10 ? '0' + Math.floor((s % 60)) : Math.floor((s % 60)); 1025 | return result = h + ":" + m + ":" + s; 1026 | }; 1027 | 1028 | Player.prototype.reportPlayError = function (error, status, message) { 1029 | var e = { 1030 | error: error || 0, 1031 | status: status || 0, 1032 | message: message 1033 | }; 1034 | 1035 | if (this.callback) { 1036 | this.callback(e); 1037 | } 1038 | }; 1039 | 1040 | Player.prototype.setLoadingDiv = function (loadingDiv) { 1041 | this.loadingDiv = loadingDiv; 1042 | } 1043 | 1044 | Player.prototype.hideLoading = function () { 1045 | if (this.loadingDiv != null) { 1046 | loading.style.display = "none"; 1047 | } 1048 | }; 1049 | 1050 | Player.prototype.showLoading = function () { 1051 | if (this.loadingDiv != null) { 1052 | loading.style.display = "block"; 1053 | } 1054 | }; 1055 | 1056 | Player.prototype.registerVisibilityEvent = function (cb) { 1057 | var hidden = "hidden"; 1058 | 1059 | // Standards: 1060 | if (hidden in document) { 1061 | document.addEventListener("visibilitychange", onchange); 1062 | } else if ((hidden = "mozHidden") in document) { 1063 | document.addEventListener("mozvisibilitychange", onchange); 1064 | } else if ((hidden = "webkitHidden") in document) { 1065 | document.addEventListener("webkitvisibilitychange", onchange); 1066 | } else if ((hidden = "msHidden") in document) { 1067 | document.addEventListener("msvisibilitychange", onchange); 1068 | } else if ("onfocusin" in document) { 1069 | // IE 9 and lower. 1070 | document.onfocusin = document.onfocusout = onchange; 1071 | } else { 1072 | // All others. 1073 | window.onpageshow = window.onpagehide = window.onfocus = window.onblur = onchange; 1074 | } 1075 | 1076 | function onchange (evt) { 1077 | var v = true; 1078 | var h = false; 1079 | var evtMap = { 1080 | focus:v, 1081 | focusin:v, 1082 | pageshow:v, 1083 | blur:h, 1084 | focusout:h, 1085 | pagehide:h 1086 | }; 1087 | 1088 | evt = evt || window.event; 1089 | var visible = v; 1090 | if (evt.type in evtMap) { 1091 | visible = evtMap[evt.type]; 1092 | } else { 1093 | visible = this[hidden] ? h : v; 1094 | } 1095 | cb(visible); 1096 | } 1097 | 1098 | // set the initial state (but only if browser supports the Page Visibility API) 1099 | if( document[hidden] !== undefined ) { 1100 | onchange({type: document[hidden] ? "blur" : "focus"}); 1101 | } 1102 | } 1103 | 1104 | Player.prototype.onStreamDataUnderDecoderIdle = function (length) { 1105 | if (this.streamReceivedLen >= this.waitHeaderLength) { 1106 | this.logger.logInfo("Opening decoder."); 1107 | this.decoderState = decoderStateInitializing; 1108 | var req = { 1109 | t: kOpenDecoderReq 1110 | }; 1111 | this.decodeWorker.postMessage(req); 1112 | } else { 1113 | this.streamReceivedLen += length; 1114 | } 1115 | }; 1116 | 1117 | Player.prototype.requestStream = function (url) { 1118 | var self = this; 1119 | this.fetchController = new AbortController(); 1120 | const signal = this.fetchController.signal; 1121 | 1122 | fetch(url, {signal}).then(async function respond(response) { 1123 | const reader = response.body.getReader(); 1124 | reader.read().then(function processData({done, value}) { 1125 | if (done) { 1126 | self.logger.logInfo("Stream done."); 1127 | return; 1128 | } 1129 | 1130 | if (self.playerState != playerStatePlaying) { 1131 | return; 1132 | } 1133 | 1134 | var dataLength = value.byteLength; 1135 | var offset = 0; 1136 | if (dataLength > self.fileInfo.chunkSize) { 1137 | do { 1138 | let len = Math.min(self.fileInfo.chunkSize, dataLength); 1139 | var data = value.buffer.slice(offset, offset + len); 1140 | dataLength -= len; 1141 | offset += len; 1142 | var objData = { 1143 | t: kFeedDataReq, 1144 | d: data 1145 | }; 1146 | self.decodeWorker.postMessage(objData, [objData.d]); 1147 | } while (dataLength > 0) 1148 | } else { 1149 | var objData = { 1150 | t: kFeedDataReq, 1151 | d: value.buffer 1152 | }; 1153 | self.decodeWorker.postMessage(objData, [objData.d]); 1154 | } 1155 | 1156 | if (self.decoderState == decoderStateIdle) { 1157 | self.onStreamDataUnderDecoderIdle(dataLength); 1158 | } 1159 | 1160 | return reader.read().then(processData); 1161 | }); 1162 | }).catch(err => { 1163 | }); 1164 | }; 1165 | -------------------------------------------------------------------------------- /src/webgl.js: -------------------------------------------------------------------------------- 1 | function Texture(gl) { 2 | this.gl = gl; 3 | this.texture = gl.createTexture(); 4 | gl.bindTexture(gl.TEXTURE_2D, this.texture); 5 | 6 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 7 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 8 | 9 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 10 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 11 | } 12 | 13 | Texture.prototype.bind = function (n, program, name) { 14 | var gl = this.gl; 15 | gl.activeTexture([gl.TEXTURE0, gl.TEXTURE1, gl.TEXTURE2][n]); 16 | gl.bindTexture(gl.TEXTURE_2D, this.texture); 17 | gl.uniform1i(gl.getUniformLocation(program, name), n); 18 | }; 19 | 20 | Texture.prototype.fill = function (width, height, data) { 21 | var gl = this.gl; 22 | gl.bindTexture(gl.TEXTURE_2D, this.texture); 23 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, width, height, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, data); 24 | }; 25 | 26 | function WebGLPlayer(canvas, options) { 27 | this.canvas = canvas; 28 | this.gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"); 29 | this.initGL(options); 30 | } 31 | 32 | WebGLPlayer.prototype.initGL = function (options) { 33 | if (!this.gl) { 34 | console.log("[ER] WebGL not supported."); 35 | return; 36 | } 37 | 38 | var gl = this.gl; 39 | gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); 40 | var program = gl.createProgram(); 41 | var vertexShaderSource = [ 42 | "attribute highp vec4 aVertexPosition;", 43 | "attribute vec2 aTextureCoord;", 44 | "varying highp vec2 vTextureCoord;", 45 | "void main(void) {", 46 | " gl_Position = aVertexPosition;", 47 | " vTextureCoord = aTextureCoord;", 48 | "}" 49 | ].join("\n"); 50 | var vertexShader = gl.createShader(gl.VERTEX_SHADER); 51 | gl.shaderSource(vertexShader, vertexShaderSource); 52 | gl.compileShader(vertexShader); 53 | var fragmentShaderSource = [ 54 | "precision highp float;", 55 | "varying lowp vec2 vTextureCoord;", 56 | "uniform sampler2D YTexture;", 57 | "uniform sampler2D UTexture;", 58 | "uniform sampler2D VTexture;", 59 | "const mat4 YUV2RGB = mat4", 60 | "(", 61 | " 1.1643828125, 0, 1.59602734375, -.87078515625,", 62 | " 1.1643828125, -.39176171875, -.81296875, .52959375,", 63 | " 1.1643828125, 2.017234375, 0, -1.081390625,", 64 | " 0, 0, 0, 1", 65 | ");", 66 | "void main(void) {", 67 | " gl_FragColor = vec4( texture2D(YTexture, vTextureCoord).x, texture2D(UTexture, vTextureCoord).x, texture2D(VTexture, vTextureCoord).x, 1) * YUV2RGB;", 68 | "}" 69 | ].join("\n"); 70 | 71 | var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); 72 | gl.shaderSource(fragmentShader, fragmentShaderSource); 73 | gl.compileShader(fragmentShader); 74 | gl.attachShader(program, vertexShader); 75 | gl.attachShader(program, fragmentShader); 76 | gl.linkProgram(program); 77 | gl.useProgram(program); 78 | if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { 79 | console.log("[ER] Shader link failed."); 80 | } 81 | var vertexPositionAttribute = gl.getAttribLocation(program, "aVertexPosition"); 82 | gl.enableVertexAttribArray(vertexPositionAttribute); 83 | var textureCoordAttribute = gl.getAttribLocation(program, "aTextureCoord"); 84 | gl.enableVertexAttribArray(textureCoordAttribute); 85 | 86 | var verticesBuffer = gl.createBuffer(); 87 | gl.bindBuffer(gl.ARRAY_BUFFER, verticesBuffer); 88 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 1.0, -1.0, 0.0, -1.0, -1.0, 0.0]), gl.STATIC_DRAW); 89 | gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0); 90 | var texCoordBuffer = gl.createBuffer(); 91 | gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer); 92 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0]), gl.STATIC_DRAW); 93 | gl.vertexAttribPointer(textureCoordAttribute, 2, gl.FLOAT, false, 0, 0); 94 | 95 | gl.y = new Texture(gl); 96 | gl.u = new Texture(gl); 97 | gl.v = new Texture(gl); 98 | gl.y.bind(0, program, "YTexture"); 99 | gl.u.bind(1, program, "UTexture"); 100 | gl.v.bind(2, program, "VTexture"); 101 | } 102 | 103 | WebGLPlayer.prototype.renderFrame = function (videoFrame, width, height, uOffset, vOffset) { 104 | if (!this.gl) { 105 | console.log("[ER] Render frame failed due to WebGL not supported."); 106 | return; 107 | } 108 | 109 | var gl = this.gl; 110 | gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); 111 | gl.clearColor(0.0, 0.0, 0.0, 0.0); 112 | gl.clear(gl.COLOR_BUFFER_BIT); 113 | 114 | gl.y.fill(width, height, videoFrame.subarray(0, uOffset)); 115 | gl.u.fill(width >> 1, height >> 1, videoFrame.subarray(uOffset, uOffset + vOffset)); 116 | gl.v.fill(width >> 1, height >> 1, videoFrame.subarray(uOffset + vOffset, videoFrame.length)); 117 | 118 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 119 | }; 120 | 121 | WebGLPlayer.prototype.fullscreen = function () { 122 | var canvas = this.canvas; 123 | if (canvas.RequestFullScreen) { 124 | canvas.RequestFullScreen(); 125 | } else if (canvas.webkitRequestFullScreen) { 126 | canvas.webkitRequestFullScreen(); 127 | } else if (canvas.mozRequestFullScreen) { 128 | canvas.mozRequestFullScreen(); 129 | } else if (canvas.msRequestFullscreen) { 130 | canvas.msRequestFullscreen(); 131 | } else { 132 | alert("This browser doesn't supporter fullscreen"); 133 | } 134 | }; 135 | 136 | WebGLPlayer.prototype.exitfullscreen = function (){ 137 | if (document.exitFullscreen) { 138 | document.exitFullscreen(); 139 | } else if (document.webkitExitFullscreen) { 140 | document.webkitExitFullscreen(); 141 | } else if (document.mozCancelFullScreen) { 142 | document.mozCancelFullScreen(); 143 | } else if (document.msExitFullscreen) { 144 | document.msExitFullscreen(); 145 | } else { 146 | alert("Exit fullscreen doesn't work"); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var webpack = require('webpack'); 5 | var merge = require('webpack-merge'); 6 | 7 | var HtmlPlugin = require('html-webpack-plugin'); 8 | 9 | var TARGET = process.env.npm_lifecycle_event; 10 | var ROOT_PATH = path.resolve(__dirname); 11 | 12 | var config; 13 | var paths = { 14 | src: path.join(ROOT_PATH, 'src'), 15 | demo: path.join(ROOT_PATH, 'demo'), 16 | }; 17 | 18 | var common = { 19 | entry: path.resolve(paths.demo, 'index'), 20 | 21 | resolve: { 22 | extensions: ['', '.js', '.less'], 23 | } 24 | }; 25 | 26 | if (TARGET === 'start' || !TARGET) { 27 | config = start(); 28 | } else if (TARGET === 'build' || !TARGET) { 29 | config = build(); 30 | } else if (TARGET === 'deploy' || !TARGET) { 31 | config = deploy(); 32 | } 33 | 34 | function start() { 35 | const IP = '0.0.0.0'; 36 | const PORT = 3000; 37 | 38 | return merge(common, { 39 | ip: IP, 40 | port: PORT, 41 | devtool: 'eval-source-map', 42 | 43 | entry: [ 44 | 'webpack-dev-server/client?http://' + IP + ':' + PORT, 45 | 'webpack/hot/only-dev-server', 46 | path.join(paths.demo, 'index'), 47 | ], 48 | 49 | output: { 50 | path: __dirname, 51 | filename: 'bundle.js' 52 | }, 53 | 54 | resolve: { 55 | alias: { 56 | 'react-blocks$': paths.src, 57 | }, 58 | }, 59 | 60 | module: { 61 | preLoaders: [ 62 | { 63 | test: /\.js$/, 64 | loaders: ['eslint'], 65 | include: [paths.demo, paths.src], 66 | } 67 | ], 68 | loaders: [ 69 | { 70 | test: /\.js?$/, 71 | loaders: ['react-hot', 'babel?stage=0'], 72 | include: [paths.demo, paths.src], 73 | }, 74 | { 75 | test: /\.less$/, 76 | exclude: /node_modules/, 77 | loaders: ['style', 'css', 'less'], 78 | }, 79 | ] 80 | }, 81 | 82 | plugins: [ 83 | new webpack.DefinePlugin({ 84 | 'process.env': { 85 | 'NODE_ENV': JSON.stringify('development'), 86 | } 87 | }), 88 | new HtmlPlugin({ 89 | title: 'React Blocks', 90 | inject: true, 91 | }), 92 | new webpack.NoErrorsPlugin(), 93 | new webpack.HotModuleReplacementPlugin(), 94 | ], 95 | }); 96 | } 97 | 98 | function build() { 99 | return merge(common, { 100 | entry: path.resolve(paths.src, 'ReactH265Player'), 101 | 102 | output: { 103 | library: 'ReactH265WasmPlayer', 104 | libraryTarget: 'umd' 105 | }, 106 | 107 | module: { 108 | loaders: [ 109 | { 110 | test: /\.js?$/, 111 | exclude: /node_modules/, 112 | loader: 'babel?stage=0', 113 | } 114 | ] 115 | }, 116 | 117 | externals: [ 118 | { 119 | "react": { 120 | root: "React", 121 | commonjs2: "react", 122 | commonjs: "react", 123 | amd: "react" 124 | } 125 | } 126 | ] 127 | }); 128 | } 129 | 130 | function deploy() { 131 | return merge(common, { 132 | entry: path.resolve(paths.demo, 'index'), 133 | 134 | output: { 135 | path: 'deploy', 136 | filename: 'bundle.js' 137 | }, 138 | 139 | resolve: { 140 | alias: { 141 | 'react-blocks$': paths.src, 142 | }, 143 | }, 144 | 145 | module: { 146 | loaders: [ 147 | { 148 | test: /\.js?$/, 149 | exclude: /node_modules/, 150 | loaders: ['babel?stage=0'], 151 | }, 152 | { 153 | test: /\.less$/, 154 | exclude: /node_modules/, 155 | loaders: ['style', 'css', 'less'] 156 | } 157 | ] 158 | }, 159 | 160 | plugins: [ 161 | new HtmlPlugin({ 162 | title: 'React Blocks', 163 | inject: true, 164 | }), 165 | new webpack.optimize.UglifyJsPlugin({ 166 | compress: { 167 | warnings: false 168 | } 169 | }), 170 | new webpack.optimize.OccurenceOrderPlugin(), 171 | ] 172 | }); 173 | } 174 | 175 | module.exports = config; 176 | --------------------------------------------------------------------------------