├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── admin ├── admin.d.ts ├── index_m.html ├── rtsp.png ├── style.css ├── tsconfig.json └── words.js ├── appveyor.yml ├── gulpfile.js ├── io-package.json ├── lib ├── adapter-config.d.ts └── tools.js ├── main.js ├── package.json ├── test ├── lib │ └── setup.js ├── testAdapter.js └── testPackageFiles.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | .git 2 | .idea 3 | node_modules 4 | Thumbs.db 5 | package-lock.json -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git 2 | .idea 3 | node_modules 4 | Thumbs.db 5 | package-lock.json 6 | .test 7 | test 8 | appveyor.yml 9 | .travis.yml 10 | tsconfig.json 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | - osx 4 | - windows 5 | language: node_js 6 | node_js: 7 | - '8' 8 | - '10' 9 | - '12' 10 | before_script: 11 | - export NPMVERSION=$(echo "$($(which npm) -v)"|cut -c1) 12 | - 'if [[ $NPMVERSION == 5 ]]; then npm install -g npm@5; fi' 13 | - npm -v 14 | - npm install winston@3.2.1 15 | - 'npm install https://github.com/ioBroker/ioBroker.js-controller/tarball/master --production' 16 | env: 17 | - CXX=g++-6 18 | addons: 19 | apt: 20 | sources: 21 | - ubuntu-toolchain-r-test 22 | packages: 23 | - g++-6 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Author 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ioBroker.rtspstream 2 | 3 | ## rtsp Adapter for ioBroker 4 | 5 | ### Case Study 6 | This adapter is currently in "pre-Alpha"-State. It should be seen as a "case Study" to see 7 | if this Adapter is needed and what features are needed. If you are looking for some features, please post them either in the forum or as an issue on github.
8 | It is also not yet sure if the adapter will be developed up to a stable state or just will be rejected... 9 | ## Installation 10 | Currently the adapter only Supports linux.
11 | To use this adapter, you first need to install ffmpeg: 12 | ``` 13 | sudo apt-get install ffmpeg 14 | ``` 15 | 16 | Then install the adapter with the "cat" from github (Install from own url).
17 | Enter the stream url, for example (highway stream):
18 | ``` 19 | rtsp://170.93.143.139/rtplive/470011e600ef003a004ee33696235daa 20 | ``` 21 | Enter a **port** and save configuration. 22 | 23 | Multiple Streams are supported, please use different ports for all streams for both ports! 24 | 25 | ## Using the Adapter 26 | ### Access the stream 27 | The adapter opens a webserver which provides you with the right js and html. 28 | Just open: 29 | ``` 30 | http://ip.of.your.iobroker:port 31 | Example: 32 | http://192.168.1.100:8084 33 | ``` 34 | The port is the port you have been storing in the configuration of the Adapter. 35 | You can use this address in a iframe of your VIS-Project. 36 | ### Stop streaming 37 | The adapter creates a state "rtspstream.0.[Streamname].startStream". Set it to false will stop streaming and eating your cpu. 38 | ## Known Problems 39 | * Currently only http is supported 40 | * Only works on Linux with ffmpeg installed (maybe will try to include autoinstaller and windows) 41 | * I have no clue if this works with other cameras than the highway-camera, please report ;) 42 | 43 | ## Contributors 44 | * dbweb-ch 45 | * wawyo (Resolution) 46 | 47 | ## Changelog 48 | ### 0.0.3 49 | * (dbwb-ch) Lowercase adapter name 50 | ### 0.0.2 51 | * (dbweb-ch) Multistreams 52 | * (wawyo) Set resolution, 3 streams 53 | ### 0.0.1 54 | * (dbweb-ch) initial implementation with 1 stream 55 | 56 | ## License 57 | MIT License 58 | 59 | Copyright (c) 2021 Dominic Blattmann 60 | 61 | Permission is hereby granted, free of charge, to any person obtaining a copy 62 | of this software and associated documentation files (the "Software"), to deal 63 | in the Software without restriction, including without limitation the rights 64 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 65 | copies of the Software, and to permit persons to whom the Software is 66 | furnished to do so, subject to the following conditions: 67 | 68 | The above copyright notice and this permission notice shall be included in all 69 | copies or substantial portions of the Software. 70 | 71 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 72 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 73 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 74 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 75 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 76 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 77 | SOFTWARE. -------------------------------------------------------------------------------- /admin/admin.d.ts: -------------------------------------------------------------------------------- 1 | declare let systemDictionary: Record>; 2 | -------------------------------------------------------------------------------- /admin/index_m.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 116 | 117 | 118 | 119 | 120 | 121 |
122 | 123 |
124 |
125 | 126 |
127 |
128 |
129 |
130 | 131 |

132 |
133 |
134 | 135 |

136 |
137 |
138 | 139 |

140 |
141 |
142 | 143 |

144 |
145 |
146 | 147 |

148 |
149 |
150 | 151 |

152 |
153 | 158 |
159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 |
NameURLPortResolutionTcp Portffmpeg Options
173 |
174 |
175 |
176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /admin/rtsp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbweb-ch/ioBroker.rtspstream/adff9611d3ab1871cd94d2e779b9fd4fcc937353/admin/rtsp.png -------------------------------------------------------------------------------- /admin/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box 3 | } 4 | .m { 5 | /* Don't cut off dropdowns! */ 6 | overflow: initial; 7 | } 8 | .m .input-field>label:read-only { 9 | transform: translateY(-14px) scale(.8); 10 | } -------------------------------------------------------------------------------- /admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "include": [ 4 | "./**/*.d.ts", 5 | "./**/*.js" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /admin/words.js: -------------------------------------------------------------------------------- 1 | /*global systemDictionary:true */ 2 | 'use strict'; 3 | 4 | systemDictionary = { 5 | "Rtsp Stream": { 6 | "en": "Rtsp Stream", 7 | "de": "Rtsp Stream" 8 | } 9 | }; -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 'test-{build}' 2 | environment: 3 | matrix: 4 | - nodejs_version: '8' 5 | - nodejs_version: '10' 6 | - nodejs_version: '12' 7 | platform: 8 | - x86 9 | - x64 10 | clone_folder: 'c:\projects\%APPVEYOR_PROJECT_NAME%' 11 | install: 12 | - ps: 'Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) $env:platform' 13 | - ps: '$NpmVersion = (npm -v).Substring(0,1)' 14 | - ps: 'if($NpmVersion -eq 5) { npm install -g npm@5 }' 15 | - ps: npm --version 16 | - npm install 17 | - npm install winston@3.2.1 18 | - 'npm install https://github.com/ioBroker/ioBroker.js-controller/tarball/master --production' 19 | test_script: 20 | - echo %cd% 21 | - node --version 22 | - npm --version 23 | - npm test 24 | build: 'off' 25 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * ioBroker gulpfile 3 | * Date: 2019-01-28 4 | */ 5 | 'use strict'; 6 | 7 | const gulp = require('gulp'); 8 | const fs = require('fs'); 9 | const pkg = require('./package.json'); 10 | const iopackage = require('./io-package.json'); 11 | const version = (pkg && pkg.version) ? pkg.version : iopackage.common.version; 12 | const fileName = 'words.js'; 13 | const EMPTY = ''; 14 | const translate = require('./lib/tools').translateText; 15 | const languages = { 16 | en: {}, 17 | de: {}, 18 | ru: {}, 19 | pt: {}, 20 | nl: {}, 21 | fr: {}, 22 | it: {}, 23 | es: {}, 24 | pl: {}, 25 | 'zh-cn': {} 26 | }; 27 | 28 | function lang2data(lang, isFlat) { 29 | let str = isFlat ? '' : '{\n'; 30 | let count = 0; 31 | for (const w in lang) { 32 | if (lang.hasOwnProperty(w)) { 33 | count++; 34 | if (isFlat) { 35 | str += (lang[w] === '' ? (isFlat[w] || w) : lang[w]) + '\n'; 36 | } else { 37 | const key = ' "' + w.replace(/"/g, '\\"') + '": '; 38 | str += key + '"' + lang[w].replace(/"/g, '\\"') + '",\n'; 39 | } 40 | } 41 | } 42 | if (!count) 43 | return isFlat ? '' : '{\n}'; 44 | if (isFlat) { 45 | return str; 46 | } else { 47 | return str.substring(0, str.length - 2) + '\n}'; 48 | } 49 | } 50 | 51 | function readWordJs(src) { 52 | try { 53 | let words; 54 | if (fs.existsSync(src + 'js/' + fileName)) { 55 | words = fs.readFileSync(src + 'js/' + fileName).toString(); 56 | } else { 57 | words = fs.readFileSync(src + fileName).toString(); 58 | } 59 | words = words.substring(words.indexOf('{'), words.length); 60 | words = words.substring(0, words.lastIndexOf(';')); 61 | 62 | const resultFunc = new Function('return ' + words + ';'); 63 | 64 | return resultFunc(); 65 | } catch (e) { 66 | return null; 67 | } 68 | } 69 | 70 | function padRight(text, totalLength) { 71 | return text + (text.length < totalLength ? new Array(totalLength - text.length).join(' ') : ''); 72 | } 73 | 74 | function writeWordJs(data, src) { 75 | let text = ''; 76 | text += '/*global systemDictionary:true */\n'; 77 | text += "'use strict';\n\n"; 78 | text += 'systemDictionary = {\n'; 79 | for (const word in data) { 80 | if (data.hasOwnProperty(word)) { 81 | text += ' ' + padRight('"' + word.replace(/"/g, '\\"') + '": {', 50); 82 | let line = ''; 83 | for (const lang in data[word]) { 84 | if (data[word].hasOwnProperty(lang)) { 85 | line += '"' + lang + '": "' + padRight(data[word][lang].replace(/"/g, '\\"') + '",', 50) + ' '; 86 | } 87 | } 88 | if (line) { 89 | line = line.trim(); 90 | line = line.substring(0, line.length - 1); 91 | } 92 | text += line + '},\n'; 93 | } 94 | } 95 | text += '};'; 96 | if (fs.existsSync(src + 'js/' + fileName)) { 97 | fs.writeFileSync(src + 'js/' + fileName, text); 98 | } else { 99 | fs.writeFileSync(src + '' + fileName, text); 100 | } 101 | } 102 | 103 | function words2languages(src) { 104 | const langs = Object.assign({}, languages); 105 | const data = readWordJs(src); 106 | if (data) { 107 | for (const word in data) { 108 | if (data.hasOwnProperty(word)) { 109 | for (const lang in data[word]) { 110 | if (data[word].hasOwnProperty(lang)) { 111 | langs[lang][word] = data[word][lang]; 112 | // pre-fill all other languages 113 | for (const j in langs) { 114 | if (langs.hasOwnProperty(j)) { 115 | langs[j][word] = langs[j][word] || EMPTY; 116 | } 117 | } 118 | } 119 | } 120 | } 121 | } 122 | if (!fs.existsSync(src + 'i18n/')) { 123 | fs.mkdirSync(src + 'i18n/'); 124 | } 125 | for (const l in langs) { 126 | if (!langs.hasOwnProperty(l)) 127 | continue; 128 | const keys = Object.keys(langs[l]); 129 | keys.sort(); 130 | const obj = {}; 131 | for (let k = 0; k < keys.length; k++) { 132 | obj[keys[k]] = langs[l][keys[k]]; 133 | } 134 | if (!fs.existsSync(src + 'i18n/' + l)) { 135 | fs.mkdirSync(src + 'i18n/' + l); 136 | } 137 | 138 | fs.writeFileSync(src + 'i18n/' + l + '/translations.json', lang2data(obj)); 139 | } 140 | } else { 141 | console.error('Cannot read or parse ' + fileName); 142 | } 143 | } 144 | 145 | function words2languagesFlat(src) { 146 | const langs = Object.assign({}, languages); 147 | const data = readWordJs(src); 148 | if (data) { 149 | for (const word in data) { 150 | if (data.hasOwnProperty(word)) { 151 | for (const lang in data[word]) { 152 | if (data[word].hasOwnProperty(lang)) { 153 | langs[lang][word] = data[word][lang]; 154 | // pre-fill all other languages 155 | for (const j in langs) { 156 | if (langs.hasOwnProperty(j)) { 157 | langs[j][word] = langs[j][word] || EMPTY; 158 | } 159 | } 160 | } 161 | } 162 | } 163 | } 164 | const keys = Object.keys(langs.en); 165 | keys.sort(); 166 | for (const l in langs) { 167 | if (!langs.hasOwnProperty(l)) 168 | continue; 169 | const obj = {}; 170 | for (let k = 0; k < keys.length; k++) { 171 | obj[keys[k]] = langs[l][keys[k]]; 172 | } 173 | langs[l] = obj; 174 | } 175 | if (!fs.existsSync(src + 'i18n/')) { 176 | fs.mkdirSync(src + 'i18n/'); 177 | } 178 | for (const ll in langs) { 179 | if (!langs.hasOwnProperty(ll)) 180 | continue; 181 | if (!fs.existsSync(src + 'i18n/' + ll)) { 182 | fs.mkdirSync(src + 'i18n/' + ll); 183 | } 184 | 185 | fs.writeFileSync(src + 'i18n/' + ll + '/flat.txt', lang2data(langs[ll], langs.en)); 186 | } 187 | fs.writeFileSync(src + 'i18n/flat.txt', keys.join('\n')); 188 | } else { 189 | console.error('Cannot read or parse ' + fileName); 190 | } 191 | } 192 | 193 | function languagesFlat2words(src) { 194 | const dirs = fs.readdirSync(src + 'i18n/'); 195 | const langs = {}; 196 | const bigOne = {}; 197 | const order = Object.keys(languages); 198 | dirs.sort(function (a, b) { 199 | const posA = order.indexOf(a); 200 | const posB = order.indexOf(b); 201 | if (posA === -1 && posB === -1) { 202 | if (a > b) 203 | return 1; 204 | if (a < b) 205 | return -1; 206 | return 0; 207 | } else if (posA === -1) { 208 | return -1; 209 | } else if (posB === -1) { 210 | return 1; 211 | } else { 212 | if (posA > posB) 213 | return 1; 214 | if (posA < posB) 215 | return -1; 216 | return 0; 217 | } 218 | }); 219 | const keys = fs.readFileSync(src + 'i18n/flat.txt').toString().split('\n'); 220 | 221 | for (const lang of dirs) { 222 | if (lang === 'flat.txt') 223 | continue; 224 | const values = fs.readFileSync(src + 'i18n/' + lang + '/flat.txt').toString().split('\n'); 225 | langs[lang] = {}; 226 | keys.forEach(function (word, i) { 227 | langs[lang][word] = values[i]; 228 | }); 229 | 230 | const words = langs[lang]; 231 | for (const word in words) { 232 | if (words.hasOwnProperty(word)) { 233 | bigOne[word] = bigOne[word] || {}; 234 | if (words[word] !== EMPTY) { 235 | bigOne[word][lang] = words[word]; 236 | } 237 | } 238 | } 239 | } 240 | // read actual words.js 241 | const aWords = readWordJs(); 242 | 243 | const temporaryIgnore = ['flat.txt']; 244 | if (aWords) { 245 | // Merge words together 246 | for (const w in aWords) { 247 | if (aWords.hasOwnProperty(w)) { 248 | if (!bigOne[w]) { 249 | console.warn('Take from actual words.js: ' + w); 250 | bigOne[w] = aWords[w]; 251 | } 252 | dirs.forEach(function (lang) { 253 | if (temporaryIgnore.indexOf(lang) !== -1) 254 | return; 255 | if (!bigOne[w][lang]) { 256 | console.warn('Missing "' + lang + '": ' + w); 257 | } 258 | }); 259 | } 260 | } 261 | 262 | } 263 | 264 | writeWordJs(bigOne, src); 265 | } 266 | 267 | function languages2words(src) { 268 | const dirs = fs.readdirSync(src + 'i18n/'); 269 | const langs = {}; 270 | const bigOne = {}; 271 | const order = Object.keys(languages); 272 | dirs.sort(function (a, b) { 273 | const posA = order.indexOf(a); 274 | const posB = order.indexOf(b); 275 | if (posA === -1 && posB === -1) { 276 | if (a > b) 277 | return 1; 278 | if (a < b) 279 | return -1; 280 | return 0; 281 | } else if (posA === -1) { 282 | return -1; 283 | } else if (posB === -1) { 284 | return 1; 285 | } else { 286 | if (posA > posB) 287 | return 1; 288 | if (posA < posB) 289 | return -1; 290 | return 0; 291 | } 292 | }); 293 | for (const lang of dirs) { 294 | if (lang === 'flat.txt') 295 | continue; 296 | langs[lang] = fs.readFileSync(src + 'i18n/' + lang + '/translations.json').toString(); 297 | langs[lang] = JSON.parse(langs[lang]); 298 | const words = langs[lang]; 299 | for (const word in words) { 300 | if (words.hasOwnProperty(word)) { 301 | bigOne[word] = bigOne[word] || {}; 302 | if (words[word] !== EMPTY) { 303 | bigOne[word][lang] = words[word]; 304 | } 305 | } 306 | } 307 | } 308 | // read actual words.js 309 | const aWords = readWordJs(); 310 | 311 | const temporaryIgnore = ['flat.txt']; 312 | if (aWords) { 313 | // Merge words together 314 | for (const w in aWords) { 315 | if (aWords.hasOwnProperty(w)) { 316 | if (!bigOne[w]) { 317 | console.warn('Take from actual words.js: ' + w); 318 | bigOne[w] = aWords[w]; 319 | } 320 | dirs.forEach(function (lang) { 321 | if (temporaryIgnore.indexOf(lang) !== -1) 322 | return; 323 | if (!bigOne[w][lang]) { 324 | console.warn('Missing "' + lang + '": ' + w); 325 | } 326 | }); 327 | } 328 | } 329 | 330 | } 331 | 332 | writeWordJs(bigOne, src); 333 | } 334 | 335 | async function translateNotExisting(obj, baseText, yandex) { 336 | let t = obj['en']; 337 | if (!t) { 338 | t = baseText; 339 | } 340 | 341 | if (t) { 342 | for (let l in languages) { 343 | if (!obj[l]) { 344 | const time = new Date().getTime(); 345 | obj[l] = await translate(t, l, yandex); 346 | console.log('en -> ' + l + ' ' + (new Date().getTime() - time) + ' ms'); 347 | } 348 | } 349 | } 350 | } 351 | 352 | //TASKS 353 | 354 | gulp.task('adminWords2languages', function (done) { 355 | words2languages('./admin/'); 356 | done(); 357 | }); 358 | 359 | gulp.task('adminWords2languagesFlat', function (done) { 360 | words2languagesFlat('./admin/'); 361 | done(); 362 | }); 363 | 364 | gulp.task('adminLanguagesFlat2words', function (done) { 365 | languagesFlat2words('./admin/'); 366 | done(); 367 | }); 368 | 369 | gulp.task('adminLanguages2words', function (done) { 370 | languages2words('./admin/'); 371 | done(); 372 | }); 373 | 374 | gulp.task('updatePackages', function (done) { 375 | iopackage.common.version = pkg.version; 376 | iopackage.common.news = iopackage.common.news || {}; 377 | if (!iopackage.common.news[pkg.version]) { 378 | const news = iopackage.common.news; 379 | const newNews = {}; 380 | 381 | newNews[pkg.version] = { 382 | en: 'news', 383 | de: 'neues', 384 | ru: 'новое', 385 | pt: 'novidades', 386 | nl: 'nieuws', 387 | fr: 'nouvelles', 388 | it: 'notizie', 389 | es: 'noticias', 390 | pl: 'nowości', 391 | 'zh-cn': '新' 392 | }; 393 | iopackage.common.news = Object.assign(newNews, news); 394 | } 395 | fs.writeFileSync('io-package.json', JSON.stringify(iopackage, null, 4)); 396 | done(); 397 | }); 398 | 399 | gulp.task('updateReadme', function (done) { 400 | const readme = fs.readFileSync('README.md').toString(); 401 | const pos = readme.indexOf('## Changelog\n'); 402 | if (pos !== -1) { 403 | const readmeStart = readme.substring(0, pos + '## Changelog\n'.length); 404 | const readmeEnd = readme.substring(pos + '## Changelog\n'.length); 405 | 406 | if (readme.indexOf(version) === -1) { 407 | const timestamp = new Date(); 408 | const date = timestamp.getFullYear() + '-' + 409 | ('0' + (timestamp.getMonth() + 1).toString(10)).slice(-2) + '-' + 410 | ('0' + (timestamp.getDate()).toString(10)).slice(-2); 411 | 412 | let news = ''; 413 | if (iopackage.common.news && iopackage.common.news[pkg.version]) { 414 | news += '* ' + iopackage.common.news[pkg.version].en; 415 | } 416 | 417 | fs.writeFileSync('README.md', readmeStart + '### ' + version + ' (' + date + ')\n' + (news ? news + '\n\n' : '\n') + readmeEnd); 418 | } 419 | } 420 | done(); 421 | }); 422 | 423 | gulp.task('translate', async function (done) { 424 | 425 | let yandex; 426 | const i = process.argv.indexOf('--yandex'); 427 | if (i > -1) { 428 | yandex = process.argv[i + 1]; 429 | } 430 | 431 | if (iopackage && iopackage.common) { 432 | if (iopackage.common.news) { 433 | console.log('Translate News'); 434 | for (let k in iopackage.common.news) { 435 | console.log('News: ' + k); 436 | let nw = iopackage.common.news[k]; 437 | await translateNotExisting(nw, null, yandex); 438 | } 439 | } 440 | if (iopackage.common.titleLang) { 441 | console.log('Translate Title'); 442 | await translateNotExisting(iopackage.common.titleLang, iopackage.common.title, yandex); 443 | } 444 | if (iopackage.common.desc) { 445 | console.log('Translate Description'); 446 | await translateNotExisting(iopackage.common.desc, null, yandex); 447 | } 448 | 449 | if (fs.existsSync('./admin/i18n/en/translations.json')) { 450 | let enTranslations = require('./admin/i18n/en/translations.json'); 451 | for (let l in languages) { 452 | console.log('Translate Text: ' + l); 453 | let existing = {}; 454 | if (fs.existsSync('./admin/i18n/' + l + '/translations.json')) { 455 | existing = require('./admin/i18n/' + l + '/translations.json'); 456 | } 457 | for (let t in enTranslations) { 458 | if (!existing[t]) { 459 | existing[t] = await translate(enTranslations[t], l, yandex); 460 | } 461 | } 462 | if (!fs.existsSync('./admin/i18n/' + l + '/')) { 463 | fs.mkdirSync('./admin/i18n/' + l + '/'); 464 | } 465 | fs.writeFileSync('./admin/i18n/' + l + '/translations.json', JSON.stringify(existing, null, 4)); 466 | } 467 | } 468 | 469 | } 470 | fs.writeFileSync('io-package.json', JSON.stringify(iopackage, null, 4)); 471 | }); 472 | 473 | gulp.task('translateAndUpdateWordsJS', gulp.series('translate', 'adminLanguages2words', 'adminWords2languages')); 474 | 475 | gulp.task('default', gulp.series('updatePackages', 'updateReadme')); -------------------------------------------------------------------------------- /io-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "common": { 3 | "name": "rtspstream", 4 | "version": "0.0.4", 5 | "news": { 6 | "0.0.3": { 7 | "en": "Lowercase adapter name", 8 | "de": "Adaptername klein geschrieben" 9 | }, 10 | "0.0.2": { 11 | "en": "Multiple rtsp-Streams, set video resolution", 12 | "de": "Mehrere Streams möglich, Auflösung setzen" 13 | }, 14 | "0.0.1": { 15 | "en": "initial Commit", 16 | "de": "Erstveröffentlichung" 17 | } 18 | }, 19 | "title": "Rtsp Stream", 20 | "titleLang": { 21 | "en": "Rtsp Stream", 22 | "de": "Rtsp Stream" 23 | }, 24 | "desc": { 25 | "en": "Stream rtsp", 26 | "de": "Adapter für rtsp-Streams" 27 | }, 28 | "authors": [ 29 | "Dominic Blattmann " 30 | ], 31 | "keywords": [ 32 | "ioBroker", 33 | "rtsp", 34 | "camera", 35 | "Smart Home", 36 | "home automation" 37 | ], 38 | "license": "MIT", 39 | "platform": "Javascript/Node.js", 40 | "main": "main.js", 41 | "icon": "rtsp.png", 42 | "enabled": true, 43 | "extIcon": "https://raw.githubusercontent.com/dbweb-ch/ioBroker.rtspstream/master/admin/rtsp.png", 44 | "readme": "https://github.com/dbweb-ch/ioBroker.rtspstream/blob/master/README.md", 45 | "loglevel": "info", 46 | "messagebox": true, 47 | "subscribe": "messagebox", 48 | "restartAdapters": [ 49 | "rtspstream" 50 | ], 51 | "mode": "daemon", 52 | "type": "general", 53 | "compact": true, 54 | "materialize": true, 55 | "materializeTab": false, 56 | "supportCustoms": false, 57 | "dependencies": [ 58 | { 59 | "js-controller": ">=1.4.2" 60 | } 61 | ] 62 | }, 63 | "native": { 64 | "rtspUrl": "", 65 | "port": "" 66 | }, 67 | "objects": [], 68 | "instanceObjects": [ 69 | { 70 | "_id": "startStream", 71 | "type": "state", 72 | "common": { 73 | "name": "Start the stream", 74 | "type": "boolean", 75 | "role": "state", 76 | "write": true, 77 | "def": false 78 | }, 79 | "native": {} 80 | } 81 | ] 82 | } -------------------------------------------------------------------------------- /lib/adapter-config.d.ts: -------------------------------------------------------------------------------- 1 | // This file extends the AdapterConfig type from "@types/iobroker" 2 | // using the actual properties present in io-package.json 3 | // in order to provide typings for adapter.config properties 4 | 5 | import { native } from '../io-package.json'; 6 | 7 | type _AdapterConfig = typeof native; 8 | 9 | // Augment the globally declared type ioBroker.AdapterConfig 10 | declare global { 11 | namespace ioBroker { 12 | interface AdapterConfig extends _AdapterConfig { 13 | // Do not enter anything here! 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /lib/tools.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | 3 | /** 4 | * Tests whether the given variable is a real object and not an Array 5 | * @param {any} it The variable to test 6 | * @returns {it is Record} 7 | */ 8 | function isObject(it) { 9 | // This is necessary because: 10 | // typeof null === 'object' 11 | // typeof [] === 'object' 12 | // [] instanceof Object === true 13 | return Object.prototype.toString.call(it) === '[object Object]'; 14 | } 15 | 16 | /** 17 | * Tests whether the given variable is really an Array 18 | * @param {any} it The variable to test 19 | * @returns {it is any[]} 20 | */ 21 | function isArray(it) { 22 | if (typeof Array.isArray === 'function') return Array.isArray(it); 23 | return Object.prototype.toString.call(it) === '[object Array]'; 24 | } 25 | 26 | /** 27 | * Translates text to the target language. Automatically chooses the right translation API. 28 | * @param {string} text The text to translate 29 | * @param {string} targetLang The target languate 30 | * @param {string} [yandexApiKey] The yandex API key. You can create one for free at https://translate.yandex.com/developers 31 | * @returns {Promise} 32 | */ 33 | async function translateText(text, targetLang, yandexApiKey) { 34 | if (targetLang === 'en') { 35 | return text; 36 | } 37 | if (yandexApiKey) { 38 | return await translateYandex(text, targetLang, yandexApiKey); 39 | } else { 40 | return await translateGoogle(text, targetLang); 41 | } 42 | } 43 | 44 | /** 45 | * Translates text with Yandex API 46 | * @param {string} text The text to translate 47 | * @param {string} targetLang The target languate 48 | * @param {string} [apiKey] The yandex API key. You can create one for free at https://translate.yandex.com/developers 49 | * @returns {Promise} 50 | */ 51 | async function translateYandex(text, targetLang, apiKey) { 52 | if (targetLang === 'zh-cn') { 53 | targetLang = 'zh'; 54 | } 55 | try { 56 | const url = `https://translate.yandex.net/api/v1.5/tr.json/translate?key=${apiKey}&text=${encodeURIComponent(text)}&lang=en-${targetLang}`; 57 | const response = await axios({url, timeout: 15000}); 58 | if (response.data && response.data['text']) { 59 | return response.data['text'][0]; 60 | } 61 | throw new Error('Invalid response for translate request'); 62 | } catch (e) { 63 | throw new Error(`Could not translate to "${targetLang}": ${e}`); 64 | } 65 | } 66 | 67 | /** 68 | * Translates text with Google API 69 | * @param {string} text The text to translate 70 | * @param {string} targetLang The target languate 71 | * @returns {Promise} 72 | */ 73 | async function translateGoogle(text, targetLang) { 74 | try { 75 | const url = `http://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=${targetLang}&dt=t&q=${encodeURIComponent(text)}&ie=UTF-8&oe=UTF-8`; 76 | const response = await axios({url, timeout: 15000}); 77 | if (isArray(response.data)) { 78 | // we got a valid response 79 | return response.data[0][0][0]; 80 | } 81 | throw new Error('Invalid response for translate request'); 82 | } catch (e) { 83 | throw new Error(`Could not translate to "${targetLang}": ${e}`); 84 | } 85 | } 86 | 87 | module.exports = { 88 | isArray, 89 | isObject, 90 | translateText 91 | }; 92 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const utils = require('@iobroker/adapter-core'); 4 | const Stream = require('node-rtsp-stream'); 5 | const commandExists = require('command-exists'); 6 | const http = require('http'); 7 | 8 | 9 | 10 | let streams = {}; 11 | 12 | class rtspstream extends utils.Adapter { 13 | 14 | /** 15 | * @param {Partial} [options={}] 16 | */ 17 | constructor(options){ 18 | super({ 19 | ...options, 20 | name: 'rtspstream', 21 | }); 22 | this.on('ready', this.onReady.bind(this)); 23 | this.on('stateChange', this.onStateChange.bind(this)); 24 | this.on('message', this.onMessage.bind(this)); 25 | // todo INIT variablen 26 | } 27 | 28 | /** 29 | * Is called when databases are connected and adapter received configuration. 30 | */ 31 | async onReady(){ // 32 | this.log.debug('Starting rtsp Stream'); 33 | const Adapter = this; 34 | await Adapter.setStateAsync('info.connection', true, true); 35 | 36 | // Check if ffmpeg exists 37 | // invoked without a callback, it returns a promise 38 | commandExists('ffmpeg').then(function (command) { 39 | // proceed 40 | }).catch(function () { 41 | Adapter.log.error("ffmpeg not installed, please install ffmeg first: apt-get install ffmpeg"); 42 | Adapter.disable(); 43 | return; 44 | }); 45 | 46 | this.subscribeStates('*'); // TODO: Subscribe right things... 47 | 48 | let channels = await Adapter.getChannelsOfAsync(''); 49 | Adapter.log.info('Number of streams: ' + channels.length); 50 | for(let idx = 0; idx < channels.length; idx++){ 51 | let TcpPort = await Adapter.getStateAsync(channels[idx].common.name + '.tcpPort'); 52 | let Port = await Adapter.getStateAsync(channels[idx].common.name + '.port'); 53 | let JSMpeg = 'var JSMpeg={Player:null,VideoElement:null,BitBuffer:null,Source:{},Demuxer:{},Decoder:{},Renderer:{},AudioOutput:{},Now:function(){return window.performance?window.performance.now()/1e3:Date.now()/1e3},CreateVideoElements:function(){var elements=document.querySelectorAll(".jsmpeg");for(var i=0;i\'+\'\'+\'\'+"";VideoElement.UNMUTE_BUTTON=\'\'+\'\'+\'\'+\'\'+\'\'+""+"";return VideoElement}();JSMpeg.Player=function(){"use strict";var Player=function(url,options){this.options=options||{};if(options.source){this.source=new options.source(url,options);options.streaming=!!this.source.streaming}else if(url.match(/^wss?:\\/\\//)){this.source=new JSMpeg.Source.WebSocket(url,options);options.streaming=true}else if(options.progressive!==false){this.source=new JSMpeg.Source.AjaxProgressive(url,options);options.streaming=false}else{this.source=new JSMpeg.Source.Ajax(url,options);options.streaming=false}this.maxAudioLag=options.maxAudioLag||.25;this.loop=options.loop!==false;this.autoplay=!!options.autoplay||options.streaming;this.demuxer=new JSMpeg.Demuxer.TS(options);this.source.connect(this.demuxer);if(!options.disableWebAssembly&&JSMpeg.WASMModule.IsSupported()){this.wasmModule=JSMpeg.WASMModule.GetModule();options.wasmModule=this.wasmModule}if(options.video!==false){this.video=options.wasmModule?new JSMpeg.Decoder.MPEG1VideoWASM(options):new JSMpeg.Decoder.MPEG1Video(options);this.renderer=!options.disableGl&&JSMpeg.Renderer.WebGL.IsSupported()?new JSMpeg.Renderer.WebGL(options):new JSMpeg.Renderer.Canvas2D(options);this.demuxer.connect(JSMpeg.Demuxer.TS.STREAM.VIDEO_1,this.video);this.video.connect(this.renderer)}if(options.audio!==false&&JSMpeg.AudioOutput.WebAudio.IsSupported()){this.audio=options.wasmModule?new JSMpeg.Decoder.MP2AudioWASM(options):new JSMpeg.Decoder.MP2Audio(options);this.audioOut=new JSMpeg.AudioOutput.WebAudio(options);this.demuxer.connect(JSMpeg.Demuxer.TS.STREAM.AUDIO_1,this.audio);this.audio.connect(this.audioOut)}Object.defineProperty(this,"currentTime",{get:this.getCurrentTime,set:this.setCurrentTime});Object.defineProperty(this,"volume",{get:this.getVolume,set:this.setVolume});this.paused=true;this.unpauseOnShow=false;if(options.pauseWhenHidden!==false){document.addEventListener("visibilitychange",this.showHide.bind(this))}if(this.wasmModule){if(this.wasmModule.ready){this.startLoading()}else if(JSMpeg.WASM_BINARY_INLINED){var wasm=JSMpeg.Base64ToArrayBuffer(JSMpeg.WASM_BINARY_INLINED);this.wasmModule.loadFromBuffer(wasm,this.startLoading.bind(this))}else{this.wasmModule.loadFromFile("jsmpeg.wasm",this.startLoading.bind(this))}}else{this.startLoading()}};Player.prototype.startLoading=function(){this.source.start();if(this.autoplay){this.play()}};Player.prototype.showHide=function(ev){if(document.visibilityState==="hidden"){this.unpauseOnShow=this.wantsToPlay;this.pause()}else if(this.unpauseOnShow){this.play()}};Player.prototype.play=function(ev){if(this.animationId){return}this.animationId=requestAnimationFrame(this.update.bind(this));this.wantsToPlay=true;this.paused=false};Player.prototype.pause=function(ev){if(this.paused){return}cancelAnimationFrame(this.animationId);this.animationId=null;this.wantsToPlay=false;this.isPlaying=false;this.paused=true;if(this.audio&&this.audio.canPlay){this.audioOut.stop();this.seek(this.currentTime)}if(this.options.onPause){this.options.onPause(this)}};Player.prototype.getVolume=function(){return this.audioOut?this.audioOut.volume:0};Player.prototype.setVolume=function(volume){if(this.audioOut){this.audioOut.volume=volume}};Player.prototype.stop=function(ev){this.pause();this.seek(0);if(this.video&&this.options.decodeFirstFrame!==false){this.video.decode()}};Player.prototype.destroy=function(){this.pause();this.source.destroy();this.video&&this.video.destroy();this.renderer&&this.renderer.destroy();this.audio&&this.audio.destroy();this.audioOut&&this.audioOut.destroy()};Player.prototype.seek=function(time){var startOffset=this.audio&&this.audio.canPlay?this.audio.startTime:this.video.startTime;if(this.video){this.video.seek(time+startOffset)}if(this.audio){this.audio.seek(time+startOffset)}this.startTime=JSMpeg.Now()-time};Player.prototype.getCurrentTime=function(){return this.audio&&this.audio.canPlay?this.audio.currentTime-this.audio.startTime:this.video.currentTime-this.video.startTime};Player.prototype.setCurrentTime=function(time){this.seek(time)};Player.prototype.update=function(){this.animationId=requestAnimationFrame(this.update.bind(this));if(!this.source.established){if(this.renderer){this.renderer.renderProgress(this.source.progress)}return}if(!this.isPlaying){this.isPlaying=true;this.startTime=JSMpeg.Now()-this.currentTime;if(this.options.onPlay){this.options.onPlay(this)}}if(this.options.streaming){this.updateForStreaming()}else{this.updateForStaticFile()}};Player.prototype.updateForStreaming=function(){if(this.video){this.video.decode()}if(this.audio){var decoded=false;do{if(this.audioOut.enqueuedTime>this.maxAudioLag){this.audioOut.resetEnqueuedTime();this.audioOut.enabled=false}decoded=this.audio.decode()}while(decoded);this.audioOut.enabled=true}};Player.prototype.nextFrame=function(){if(this.source.established&&this.video){return this.video.decode()}return false};Player.prototype.updateForStaticFile=function(){var notEnoughData=false,headroom=0;if(this.audio&&this.audio.canPlay){while(!notEnoughData&&this.audio.decodedTime-this.audio.currentTime<.25){notEnoughData=!this.audio.decode()}if(this.video&&this.video.currentTime0){if(lateTime>frameTime*2){this.startTime+=lateTime}notEnoughData=!this.video.decode()}headroom=this.demuxer.currentTime-targetTime}this.source.resume(headroom);if(notEnoughData&&this.source.completed){if(this.loop){this.seek(0)}else{this.pause();if(this.options.onEnded){this.options.onEnded(this)}}}else if(notEnoughData&&this.options.onStalled){this.options.onStalled(this)}};return Player}();JSMpeg.BitBuffer=function(){"use strict";var BitBuffer=function(bufferOrLength,mode){if(typeof bufferOrLength==="object"){this.bytes=bufferOrLength instanceof Uint8Array?bufferOrLength:new Uint8Array(bufferOrLength);this.byteLength=this.bytes.length}else{this.bytes=new Uint8Array(bufferOrLength||1024*1024);this.byteLength=0}this.mode=mode||BitBuffer.MODE.EXPAND;this.index=0};BitBuffer.prototype.resize=function(size){var newBytes=new Uint8Array(size);if(this.byteLength!==0){this.byteLength=Math.min(this.byteLength,size);newBytes.set(this.bytes,0,this.byteLength)}this.bytes=newBytes;this.index=Math.min(this.index,this.byteLength<<3)};BitBuffer.prototype.evict=function(sizeNeeded){var bytePos=this.index>>3,available=this.bytes.length-this.byteLength;if(this.index===this.byteLength<<3||sizeNeeded>available+bytePos){this.byteLength=0;this.index=0;return}else if(bytePos===0){return}if(this.bytes.copyWithin){this.bytes.copyWithin(0,bytePos,this.byteLength)}else{this.bytes.set(this.bytes.subarray(bytePos,this.byteLength))}this.byteLength=this.byteLength-bytePos;this.index-=bytePos<<3;return};BitBuffer.prototype.write=function(buffers){var isArrayOfBuffers=typeof buffers[0]==="object",totalLength=0,available=this.bytes.length-this.byteLength;if(isArrayOfBuffers){var totalLength=0;for(var i=0;iavailable){if(this.mode===BitBuffer.MODE.EXPAND){var newSize=Math.max(this.bytes.length*2,totalLength-available);this.resize(newSize)}else{this.evict(totalLength)}}if(isArrayOfBuffers){for(var i=0;i>3;i>3;return i>=this.byteLength||this.bytes[i]==0&&this.bytes[i+1]==0&&this.bytes[i+2]==1};BitBuffer.prototype.peek=function(count){var offset=this.index;var value=0;while(count){var currentByte=this.bytes[offset>>3],remaining=8-(offset&7),read=remaining>8-read;value=value<>shift;offset+=read;count-=read}return value};BitBuffer.prototype.read=function(count){var value=this.peek(count);this.index+=count;return value};BitBuffer.prototype.skip=function(count){return this.index+=count};BitBuffer.prototype.rewind=function(count){this.index=Math.max(this.index-count,0)};BitBuffer.prototype.has=function(count){return(this.byteLength<<3)-this.index>=count};BitBuffer.MODE={EVICT:1,EXPAND:2};return BitBuffer}();JSMpeg.Source.Ajax=function(){"use strict";var AjaxSource=function(url,options){this.url=url;this.destination=null;this.request=null;this.streaming=false;this.completed=false;this.established=false;this.progress=0;this.onEstablishedCallback=options.onSourceEstablished;this.onCompletedCallback=options.onSourceCompleted};AjaxSource.prototype.connect=function(destination){this.destination=destination};AjaxSource.prototype.start=function(){this.request=new XMLHttpRequest;this.request.onreadystatechange=function(){if(this.request.readyState===this.request.DONE&&this.request.status===200){this.onLoad(this.request.response)}}.bind(this);this.request.onprogress=this.onProgress.bind(this);this.request.open("GET",this.url);this.request.responseType="arraybuffer";this.request.send()};AjaxSource.prototype.resume=function(secondsHeadroom){};AjaxSource.prototype.destroy=function(){this.request.abort()};AjaxSource.prototype.onProgress=function(ev){this.progress=ev.loaded/ev.total};AjaxSource.prototype.onLoad=function(data){this.established=true;this.completed=true;this.progress=1;if(this.onEstablishedCallback){this.onEstablishedCallback(this)}if(this.onCompletedCallback){this.onCompletedCallback(this)}if(this.destination){this.destination.write(data)}};return AjaxSource}();JSMpeg.Source.Fetch=function(){"use strict";var FetchSource=function(url,options){this.url=url;this.destination=null;this.request=null;this.streaming=true;this.completed=false;this.established=false;this.progress=0;this.aborted=false;this.onEstablishedCallback=options.onSourceEstablished;this.onCompletedCallback=options.onSourceCompleted};FetchSource.prototype.connect=function(destination){this.destination=destination};FetchSource.prototype.start=function(){var params={method:"GET",headers:new Headers,cache:"default"};self.fetch(this.url,params).then(function(res){if(res.ok&&(res.status>=200&&res.status<=299)){this.progress=1;this.established=true;return this.pump(res.body.getReader())}else{}}.bind(this)).catch(function(err){throw err})};FetchSource.prototype.pump=function(reader){return reader.read().then(function(result){if(result.done){this.completed=true}else{if(this.aborted){return reader.cancel()}if(this.destination){this.destination.write(result.value.buffer)}return this.pump(reader)}}.bind(this)).catch(function(err){throw err})};FetchSource.prototype.resume=function(secondsHeadroom){};FetchSource.prototype.abort=function(){this.aborted=true};return FetchSource}();JSMpeg.Source.AjaxProgressive=function(){"use strict";var AjaxProgressiveSource=function(url,options){this.url=url;this.destination=null;this.request=null;this.streaming=false;this.completed=false;this.established=false;this.progress=0;this.fileSize=0;this.loadedSize=0;this.chunkSize=options.chunkSize||1024*1024;this.isLoading=false;this.loadStartTime=0;this.throttled=options.throttled!==false;this.aborted=false;this.onEstablishedCallback=options.onSourceEstablished;this.onCompletedCallback=options.onSourceCompleted};AjaxProgressiveSource.prototype.connect=function(destination){this.destination=destination};AjaxProgressiveSource.prototype.start=function(){this.request=new XMLHttpRequest;this.request.onreadystatechange=function(){if(this.request.readyState===this.request.DONE){this.fileSize=parseInt(this.request.getResponseHeader("Content-Length"));this.loadNextChunk()}}.bind(this);this.request.onprogress=this.onProgress.bind(this);this.request.open("HEAD",this.url);this.request.send()};AjaxProgressiveSource.prototype.resume=function(secondsHeadroom){if(this.isLoading||!this.throttled){return}var worstCaseLoadingTime=this.loadTime*8+2;if(worstCaseLoadingTime>secondsHeadroom){this.loadNextChunk()}};AjaxProgressiveSource.prototype.destroy=function(){this.request.abort();this.aborted=true};AjaxProgressiveSource.prototype.loadNextChunk=function(){var start=this.loadedSize,end=Math.min(this.loadedSize+this.chunkSize-1,this.fileSize-1);if(start>=this.fileSize||this.aborted){this.completed=true;if(this.onCompletedCallback){this.onCompletedCallback(this)}return}this.isLoading=true;this.loadStartTime=JSMpeg.Now();this.request=new XMLHttpRequest;this.request.onreadystatechange=function(){if(this.request.readyState===this.request.DONE&&this.request.status>=200&&this.request.status<300){this.onChunkLoad(this.request.response)}else if(this.request.readyState===this.request.DONE){if(this.loadFails++<3){this.loadNextChunk()}}}.bind(this);if(start===0){this.request.onprogress=this.onProgress.bind(this)}this.request.open("GET",this.url+"?"+start+"-"+end);this.request.setRequestHeader("Range","bytes="+start+"-"+end);this.request.responseType="arraybuffer";this.request.send()};AjaxProgressiveSource.prototype.onProgress=function(ev){this.progress=ev.loaded/ev.total};AjaxProgressiveSource.prototype.onChunkLoad=function(data){var isFirstChunk=!this.established;this.established=true;this.progress=1;this.loadedSize+=data.byteLength;this.loadFails=0;this.isLoading=false;if(isFirstChunk&&this.onEstablishedCallback){this.onEstablishedCallback(this)}if(this.destination){this.destination.write(data)}this.loadTime=JSMpeg.Now()-this.loadStartTime;if(!this.throttled){this.loadNextChunk()}};return AjaxProgressiveSource}();JSMpeg.Source.WebSocket=function(){"use strict";var WSSource=function(url,options){this.url=url;this.options=options;this.socket=null;this.streaming=true;this.callbacks={connect:[],data:[]};this.destination=null;this.reconnectInterval=options.reconnectInterval!==undefined?options.reconnectInterval:5;this.shouldAttemptReconnect=!!this.reconnectInterval;this.completed=false;this.established=false;this.progress=0;this.reconnectTimeoutId=0;this.onEstablishedCallback=options.onSourceEstablished;this.onCompletedCallback=options.onSourceCompleted};WSSource.prototype.connect=function(destination){this.destination=destination};WSSource.prototype.destroy=function(){clearTimeout(this.reconnectTimeoutId);this.shouldAttemptReconnect=false;this.socket.close()};WSSource.prototype.start=function(){this.shouldAttemptReconnect=!!this.reconnectInterval;this.progress=0;this.established=false;this.socket=new WebSocket(this.url,this.options.protocols||null);this.socket.binaryType="arraybuffer";this.socket.onmessage=this.onMessage.bind(this);this.socket.onopen=this.onOpen.bind(this);this.socket.onerror=this.onClose.bind(this);this.socket.onclose=this.onClose.bind(this)};WSSource.prototype.resume=function(secondsHeadroom){};WSSource.prototype.onOpen=function(){this.progress=1};WSSource.prototype.onClose=function(){if(this.shouldAttemptReconnect){clearTimeout(this.reconnectTimeoutId);this.reconnectTimeoutId=setTimeout(function(){this.start()}.bind(this),this.reconnectInterval*1e3)}};WSSource.prototype.onMessage=function(ev){var isFirstChunk=!this.established;this.established=true;if(isFirstChunk&&this.onEstablishedCallback){this.onEstablishedCallback(this)}if(this.destination){this.destination.write(ev.data)}};return WSSource}();JSMpeg.Demuxer.TS=function(){"use strict";var TS=function(options){this.bits=null;this.leftoverBytes=null;this.guessVideoFrameEnd=true;this.pidsToStreamIds={};this.pesPacketInfo={};this.startTime=0;this.currentTime=0};TS.prototype.connect=function(streamId,destination){this.pesPacketInfo[streamId]={destination:destination,currentLength:0,totalLength:0,pts:0,buffers:[]}};TS.prototype.write=function(buffer){if(this.leftoverBytes){var totalLength=buffer.byteLength+this.leftoverBytes.byteLength;this.bits=new JSMpeg.BitBuffer(totalLength);this.bits.write([this.leftoverBytes,buffer])}else{this.bits=new JSMpeg.BitBuffer(buffer)}while(this.bits.has(188<<3)&&this.parsePacket()){}var leftoverCount=this.bits.byteLength-(this.bits.index>>3);this.leftoverBytes=leftoverCount>0?this.bits.bytes.subarray(this.bits.index>>3):null};TS.prototype.parsePacket=function(){if(this.bits.read(8)!==71){if(!this.resync()){return false}}var end=(this.bits.index>>3)+187;var transportError=this.bits.read(1),payloadStart=this.bits.read(1),transportPriority=this.bits.read(1),pid=this.bits.read(13),transportScrambling=this.bits.read(2),adaptationField=this.bits.read(2),continuityCounter=this.bits.read(4);var streamId=this.pidsToStreamIds[pid];if(payloadStart&&streamId){var pi=this.pesPacketInfo[streamId];if(pi&&pi.currentLength){this.packetComplete(pi)}}if(adaptationField&1){if(adaptationField&2){var adaptationFieldLength=this.bits.read(8);this.bits.skip(adaptationFieldLength<<3)}if(payloadStart&&this.bits.nextBytesAreStartCode()){this.bits.skip(24);streamId=this.bits.read(8);this.pidsToStreamIds[pid]=streamId;var packetLength=this.bits.read(16);this.bits.skip(8);var ptsDtsFlag=this.bits.read(2);this.bits.skip(6);var headerLength=this.bits.read(8);var payloadBeginIndex=this.bits.index+(headerLength<<3);var pi=this.pesPacketInfo[streamId];if(pi){var pts=0;if(ptsDtsFlag&2){this.bits.skip(4);var p32_30=this.bits.read(3);this.bits.skip(1);var p29_15=this.bits.read(15);this.bits.skip(1);var p14_0=this.bits.read(15);this.bits.skip(1);pts=(p32_30*1073741824+p29_15*32768+p14_0)/9e4;this.currentTime=pts;if(this.startTime===-1){this.startTime=pts}}var payloadLength=packetLength?packetLength-headerLength-3:0;this.packetStart(pi,pts,payloadLength)}this.bits.index=payloadBeginIndex}if(streamId){var pi=this.pesPacketInfo[streamId];if(pi){var start=this.bits.index>>3;var complete=this.packetAddData(pi,start,end);var hasPadding=!payloadStart&&adaptationField&2;if(complete||this.guessVideoFrameEnd&&hasPadding){this.packetComplete(pi)}}}}this.bits.index=end<<3;return true};TS.prototype.resync=function(){if(!this.bits.has(188*6<<3)){return false}var byteIndex=this.bits.index>>3;for(var i=0;i<187;i++){if(this.bits.bytes[byteIndex+i]===71){var foundSync=true;for(var j=1;j<5;j++){if(this.bits.bytes[byteIndex+i+188*j]!==71){foundSync=false;break}}if(foundSync){this.bits.index=byteIndex+i+1<<3;return true}}}console.warn("JSMpeg: Possible garbage data. Skipping.");this.bits.skip(187<<3);return false};TS.prototype.packetStart=function(pi,pts,payloadLength){pi.totalLength=payloadLength;pi.currentLength=0;pi.pts=pts};TS.prototype.packetAddData=function(pi,start,end){pi.buffers.push(this.bits.bytes.subarray(start,end));pi.currentLength+=end-start;var complete=pi.totalLength!==0&&pi.currentLength>=pi.totalLength;return complete};TS.prototype.packetComplete=function(pi){pi.destination.write(pi.pts,pi.buffers);pi.totalLength=0;pi.currentLength=0;pi.buffers=[]};TS.STREAM={PACK_HEADER:186,SYSTEM_HEADER:187,PROGRAM_MAP:188,PRIVATE_1:189,PADDING:190,PRIVATE_2:191,AUDIO_1:192,VIDEO_1:224,DIRECTORY:255};return TS}();JSMpeg.Decoder.Base=function(){"use strict";var BaseDecoder=function(options){this.destination=null;this.canPlay=false;this.collectTimestamps=!options.streaming;this.bytesWritten=0;this.timestamps=[];this.timestampIndex=0;this.startTime=0;this.decodedTime=0;Object.defineProperty(this,"currentTime",{get:this.getCurrentTime})};BaseDecoder.prototype.destroy=function(){};BaseDecoder.prototype.connect=function(destination){this.destination=destination};BaseDecoder.prototype.bufferGetIndex=function(){return this.bits.index};BaseDecoder.prototype.bufferSetIndex=function(index){this.bits.index=index};BaseDecoder.prototype.bufferWrite=function(buffers){return this.bits.write(buffers)};BaseDecoder.prototype.write=function(pts,buffers){if(this.collectTimestamps){if(this.timestamps.length===0){this.startTime=pts;this.decodedTime=pts}this.timestamps.push({index:this.bytesWritten<<3,time:pts})}this.bytesWritten+=this.bufferWrite(buffers);this.canPlay=true};BaseDecoder.prototype.seek=function(time){if(!this.collectTimestamps){return}this.timestampIndex=0;for(var i=0;itime){break}this.timestampIndex=i}var ts=this.timestamps[this.timestampIndex];if(ts){this.bufferSetIndex(ts.index);this.decodedTime=ts.time}else{this.bufferSetIndex(0);this.decodedTime=this.startTime}};BaseDecoder.prototype.decode=function(){this.advanceDecodedTime(0)};BaseDecoder.prototype.advanceDecodedTime=function(seconds){if(this.collectTimestamps){var newTimestampIndex=-1;var currentIndex=this.bufferGetIndex();for(var i=this.timestampIndex;icurrentIndex){break}newTimestampIndex=i}if(newTimestampIndex!==-1&&newTimestampIndex!==this.timestampIndex){this.timestampIndex=newTimestampIndex;this.decodedTime=this.timestamps[this.timestampIndex].time;return}}this.decodedTime+=seconds};BaseDecoder.prototype.getCurrentTime=function(){return this.decodedTime};return BaseDecoder}();JSMpeg.Decoder.MPEG1Video=function(){"use strict";var MPEG1=function(options){JSMpeg.Decoder.Base.call(this,options);this.onDecodeCallback=options.onVideoDecode;var bufferSize=options.videoBufferSize||512*1024;var bufferMode=options.streaming?JSMpeg.BitBuffer.MODE.EVICT:JSMpeg.BitBuffer.MODE.EXPAND;this.bits=new JSMpeg.BitBuffer(bufferSize,bufferMode);this.customIntraQuantMatrix=new Uint8Array(64);this.customNonIntraQuantMatrix=new Uint8Array(64);this.blockData=new Int32Array(64);this.currentFrame=0;this.decodeFirstFrame=options.decodeFirstFrame!==false};MPEG1.prototype=Object.create(JSMpeg.Decoder.Base.prototype);MPEG1.prototype.constructor=MPEG1;MPEG1.prototype.write=function(pts,buffers){JSMpeg.Decoder.Base.prototype.write.call(this,pts,buffers);if(!this.hasSequenceHeader){if(this.bits.findStartCode(MPEG1.START.SEQUENCE)===-1){return false}this.decodeSequenceHeader();if(this.decodeFirstFrame){this.decode()}}};MPEG1.prototype.decode=function(){var startTime=JSMpeg.Now();if(!this.hasSequenceHeader){return false}if(this.bits.findStartCode(MPEG1.START.PICTURE)===-1){var bufferedBytes=this.bits.byteLength-(this.bits.index>>3);return false}this.decodePicture();this.advanceDecodedTime(1/this.frameRate);var elapsedTime=JSMpeg.Now()-startTime;if(this.onDecodeCallback){this.onDecodeCallback(this,elapsedTime)}return true};MPEG1.prototype.readHuffman=function(codeTable){var state=0;do{state=codeTable[state+this.bits.read(1)]}while(state>=0&&codeTable[state]!==0);return codeTable[state+2]};MPEG1.prototype.frameRate=30;MPEG1.prototype.decodeSequenceHeader=function(){var newWidth=this.bits.read(12),newHeight=this.bits.read(12);this.bits.skip(4);this.frameRate=MPEG1.PICTURE_RATE[this.bits.read(4)];this.bits.skip(18+1+10+1);if(newWidth!==this.width||newHeight!==this.height){this.width=newWidth;this.height=newHeight;this.initBuffers();if(this.destination){this.destination.resize(newWidth,newHeight)}}if(this.bits.read(1)){for(var i=0;i<64;i++){this.customIntraQuantMatrix[MPEG1.ZIG_ZAG[i]]=this.bits.read(8)}this.intraQuantMatrix=this.customIntraQuantMatrix}if(this.bits.read(1)){for(var i=0;i<64;i++){var idx=MPEG1.ZIG_ZAG[i];this.customNonIntraQuantMatrix[idx]=this.bits.read(8)}this.nonIntraQuantMatrix=this.customNonIntraQuantMatrix}this.hasSequenceHeader=true};MPEG1.prototype.initBuffers=function(){this.intraQuantMatrix=MPEG1.DEFAULT_INTRA_QUANT_MATRIX;this.nonIntraQuantMatrix=MPEG1.DEFAULT_NON_INTRA_QUANT_MATRIX;this.mbWidth=this.width+15>>4;this.mbHeight=this.height+15>>4;this.mbSize=this.mbWidth*this.mbHeight;this.codedWidth=this.mbWidth<<4;this.codedHeight=this.mbHeight<<4;this.codedSize=this.codedWidth*this.codedHeight;this.halfWidth=this.mbWidth<<3;this.halfHeight=this.mbHeight<<3;this.currentY=new Uint8ClampedArray(this.codedSize);this.currentY32=new Uint32Array(this.currentY.buffer);this.currentCr=new Uint8ClampedArray(this.codedSize>>2);this.currentCr32=new Uint32Array(this.currentCr.buffer);this.currentCb=new Uint8ClampedArray(this.codedSize>>2);this.currentCb32=new Uint32Array(this.currentCb.buffer);this.forwardY=new Uint8ClampedArray(this.codedSize);this.forwardY32=new Uint32Array(this.forwardY.buffer);this.forwardCr=new Uint8ClampedArray(this.codedSize>>2);this.forwardCr32=new Uint32Array(this.forwardCr.buffer);this.forwardCb=new Uint8ClampedArray(this.codedSize>>2);this.forwardCb32=new Uint32Array(this.forwardCb.buffer)};MPEG1.prototype.currentY=null;MPEG1.prototype.currentCr=null;MPEG1.prototype.currentCb=null;MPEG1.prototype.pictureType=0;MPEG1.prototype.forwardY=null;MPEG1.prototype.forwardCr=null;MPEG1.prototype.forwardCb=null;MPEG1.prototype.fullPelForward=false;MPEG1.prototype.forwardFCode=0;MPEG1.prototype.forwardRSize=0;MPEG1.prototype.forwardF=0;MPEG1.prototype.decodePicture=function(skipOutput){this.currentFrame++;this.bits.skip(10);this.pictureType=this.bits.read(3);this.bits.skip(16);if(this.pictureType<=0||this.pictureType>=MPEG1.PICTURE_TYPE.B){return}if(this.pictureType===MPEG1.PICTURE_TYPE.PREDICTIVE){this.fullPelForward=this.bits.read(1);this.forwardFCode=this.bits.read(3);if(this.forwardFCode===0){return}this.forwardRSize=this.forwardFCode-1;this.forwardF=1<=MPEG1.START.SLICE_FIRST&&code<=MPEG1.START.SLICE_LAST){this.decodeSlice(code&255);code=this.bits.findNextStartCode()}if(code!==-1){this.bits.rewind(32)}if(this.destination){this.destination.render(this.currentY,this.currentCr,this.currentCb,true)}if(this.pictureType===MPEG1.PICTURE_TYPE.INTRA||this.pictureType===MPEG1.PICTURE_TYPE.PREDICTIVE){var tmpY=this.forwardY,tmpY32=this.forwardY32,tmpCr=this.forwardCr,tmpCr32=this.forwardCr32,tmpCb=this.forwardCb,tmpCb32=this.forwardCb32;this.forwardY=this.currentY;this.forwardY32=this.currentY32;this.forwardCr=this.currentCr;this.forwardCr32=this.currentCr32;this.forwardCb=this.currentCb;this.forwardCb32=this.currentCb32;this.currentY=tmpY;this.currentY32=tmpY32;this.currentCr=tmpCr;this.currentCr32=tmpCr32;this.currentCb=tmpCb;this.currentCb32=tmpCb32}};MPEG1.prototype.quantizerScale=0;MPEG1.prototype.sliceBegin=false;MPEG1.prototype.decodeSlice=function(slice){this.sliceBegin=true;this.macroblockAddress=(slice-1)*this.mbWidth-1;this.motionFwH=this.motionFwHPrev=0;this.motionFwV=this.motionFwVPrev=0;this.dcPredictorY=128;this.dcPredictorCr=128;this.dcPredictorCb=128;this.quantizerScale=this.bits.read(5);while(this.bits.read(1)){this.bits.skip(8)}do{this.decodeMacroblock()}while(!this.bits.nextBytesAreStartCode())};MPEG1.prototype.macroblockAddress=0;MPEG1.prototype.mbRow=0;MPEG1.prototype.mbCol=0;MPEG1.prototype.macroblockType=0;MPEG1.prototype.macroblockIntra=false;MPEG1.prototype.macroblockMotFw=false;MPEG1.prototype.motionFwH=0;MPEG1.prototype.motionFwV=0;MPEG1.prototype.motionFwHPrev=0;MPEG1.prototype.motionFwVPrev=0;MPEG1.prototype.decodeMacroblock=function(){var increment=0,t=this.readHuffman(MPEG1.MACROBLOCK_ADDRESS_INCREMENT);while(t===34){t=this.readHuffman(MPEG1.MACROBLOCK_ADDRESS_INCREMENT);\n' + 54 | '\n' + 55 | '}while(t===35){increment+=33;t=this.readHuffman(MPEG1.MACROBLOCK_ADDRESS_INCREMENT)}increment+=t;if(this.sliceBegin){this.sliceBegin=false;this.macroblockAddress+=increment}else{if(this.macroblockAddress+increment>=this.mbSize){return}if(increment>1){this.dcPredictorY=128;this.dcPredictorCr=128;this.dcPredictorCb=128;if(this.pictureType===MPEG1.PICTURE_TYPE.PREDICTIVE){this.motionFwH=this.motionFwHPrev=0;this.motionFwV=this.motionFwVPrev=0}}while(increment>1){this.macroblockAddress++;this.mbRow=this.macroblockAddress/this.mbWidth|0;this.mbCol=this.macroblockAddress%this.mbWidth;this.copyMacroblock(this.motionFwH,this.motionFwV,this.forwardY,this.forwardCr,this.forwardCb);increment--}this.macroblockAddress++}this.mbRow=this.macroblockAddress/this.mbWidth|0;this.mbCol=this.macroblockAddress%this.mbWidth;var mbTable=MPEG1.MACROBLOCK_TYPE[this.pictureType];this.macroblockType=this.readHuffman(mbTable);this.macroblockIntra=this.macroblockType&1;this.macroblockMotFw=this.macroblockType&8;if((this.macroblockType&16)!==0){this.quantizerScale=this.bits.read(5)}if(this.macroblockIntra){this.motionFwH=this.motionFwHPrev=0;this.motionFwV=this.motionFwVPrev=0}else{this.dcPredictorY=128;this.dcPredictorCr=128;this.dcPredictorCb=128;this.decodeMotionVectors();this.copyMacroblock(this.motionFwH,this.motionFwV,this.forwardY,this.forwardCr,this.forwardCb)}var cbp=(this.macroblockType&2)!==0?this.readHuffman(MPEG1.CODE_BLOCK_PATTERN):this.macroblockIntra?63:0;for(var block=0,mask=32;block<6;block++){if((cbp&mask)!==0){this.decodeBlock(block)}mask>>=1}};MPEG1.prototype.decodeMotionVectors=function(){var code,d,r=0;if(this.macroblockMotFw){code=this.readHuffman(MPEG1.MOTION);if(code!==0&&this.forwardF!==1){r=this.bits.read(this.forwardRSize);d=(Math.abs(code)-1<(this.forwardF<<4)-1){this.motionFwHPrev-=this.forwardF<<5}else if(this.motionFwHPrev<-this.forwardF<<4){this.motionFwHPrev+=this.forwardF<<5}this.motionFwH=this.motionFwHPrev;if(this.fullPelForward){this.motionFwH<<=1}code=this.readHuffman(MPEG1.MOTION);if(code!==0&&this.forwardF!==1){r=this.bits.read(this.forwardRSize);d=(Math.abs(code)-1<(this.forwardF<<4)-1){this.motionFwVPrev-=this.forwardF<<5}else if(this.motionFwVPrev<-this.forwardF<<4){this.motionFwVPrev+=this.forwardF<<5}this.motionFwV=this.motionFwVPrev;if(this.fullPelForward){this.motionFwV<<=1}}else if(this.pictureType===MPEG1.PICTURE_TYPE.PREDICTIVE){this.motionFwH=this.motionFwHPrev=0;this.motionFwV=this.motionFwVPrev=0}};MPEG1.prototype.copyMacroblock=function(motionH,motionV,sY,sCr,sCb){var width,scan,H,V,oddH,oddV,src,dest,last;var dY=this.currentY32,dCb=this.currentCb32,dCr=this.currentCr32;width=this.codedWidth;scan=width-16;H=motionH>>1;V=motionV>>1;oddH=(motionH&1)===1;oddV=(motionV&1)===1;src=((this.mbRow<<4)+V)*width+(this.mbCol<<4)+H;dest=this.mbRow*width+this.mbCol<<2;last=dest+(width<<2);var x,y1,y2,y;if(oddH){if(oddV){while(dest>2&255;y1=sY[src]+sY[src+width];src++;y|=y1+y2+2<<6&65280;y2=sY[src]+sY[src+width];src++;y|=y1+y2+2<<14&16711680;y1=sY[src]+sY[src+width];src++;y|=y1+y2+2<<22&4278190080;dY[dest++]=y}dest+=scan>>2;src+=scan-1}}else{while(dest>1&255;y1=sY[src++];y|=y1+y2+1<<7&65280;y2=sY[src++];y|=y1+y2+1<<15&16711680;y1=sY[src++];y|=y1+y2+1<<23&4278190080;dY[dest++]=y}dest+=scan>>2;src+=scan-1}}}else{if(oddV){while(dest>1&255;src++;y|=sY[src]+sY[src+width]+1<<7&65280;src++;y|=sY[src]+sY[src+width]+1<<15&16711680;src++;y|=sY[src]+sY[src+width]+1<<23&4278190080;src++;dY[dest++]=y}dest+=scan>>2;src+=scan}}else{while(dest>2;src+=scan}}}width=this.halfWidth;scan=width-8;H=motionH/2>>1;V=motionV/2>>1;oddH=(motionH/2&1)===1;oddV=(motionV/2&1)===1;src=((this.mbRow<<3)+V)*width+(this.mbCol<<3)+H;dest=this.mbRow*width+this.mbCol<<1;last=dest+(width<<1);var cr1,cr2,cr,cb1,cb2,cb;if(oddH){if(oddV){while(dest>2&255;cb=cb1+cb2+2>>2&255;cr1=sCr[src]+sCr[src+width];cb1=sCb[src]+sCb[src+width];src++;cr|=cr1+cr2+2<<6&65280;cb|=cb1+cb2+2<<6&65280;cr2=sCr[src]+sCr[src+width];cb2=sCb[src]+sCb[src+width];src++;cr|=cr1+cr2+2<<14&16711680;cb|=cb1+cb2+2<<14&16711680;cr1=sCr[src]+sCr[src+width];cb1=sCb[src]+sCb[src+width];src++;cr|=cr1+cr2+2<<22&4278190080;cb|=cb1+cb2+2<<22&4278190080;dCr[dest]=cr;dCb[dest]=cb;dest++}dest+=scan>>2;src+=scan-1}}else{while(dest>1&255;cb=cb1+cb2+1>>1&255;cr1=sCr[src];cb1=sCb[src++];cr|=cr1+cr2+1<<7&65280;cb|=cb1+cb2+1<<7&65280;cr2=sCr[src];cb2=sCb[src++];cr|=cr1+cr2+1<<15&16711680;cb|=cb1+cb2+1<<15&16711680;cr1=sCr[src];cb1=sCb[src++];cr|=cr1+cr2+1<<23&4278190080;cb|=cb1+cb2+1<<23&4278190080;dCr[dest]=cr;dCb[dest]=cb;dest++}dest+=scan>>2;src+=scan-1}}}else{if(oddV){while(dest>1&255;cb=sCb[src]+sCb[src+width]+1>>1&255;src++;cr|=sCr[src]+sCr[src+width]+1<<7&65280;cb|=sCb[src]+sCb[src+width]+1<<7&65280;src++;cr|=sCr[src]+sCr[src+width]+1<<15&16711680;cb|=sCb[src]+sCb[src+width]+1<<15&16711680;src++;cr|=sCr[src]+sCr[src+width]+1<<23&4278190080;cb|=sCb[src]+sCb[src+width]+1<<23&4278190080;src++;dCr[dest]=cr;dCb[dest]=cb;dest++}dest+=scan>>2;src+=scan}}else{while(dest>2;src+=scan}}}};MPEG1.prototype.dcPredictorY=0;MPEG1.prototype.dcPredictorCr=0;MPEG1.prototype.dcPredictorCb=0;MPEG1.prototype.blockData=null;MPEG1.prototype.decodeBlock=function(block){var n=0,quantMatrix;if(this.macroblockIntra){var predictor,dctSize;if(block<4){predictor=this.dcPredictorY;dctSize=this.readHuffman(MPEG1.DCT_DC_SIZE_LUMINANCE)}else{predictor=block===4?this.dcPredictorCr:this.dcPredictorCb;dctSize=this.readHuffman(MPEG1.DCT_DC_SIZE_CHROMINANCE)}if(dctSize>0){var differential=this.bits.read(dctSize);if((differential&1<0&&this.bits.read(1)===0){break}if(coeff===65535){run=this.bits.read(6);level=this.bits.read(8);if(level===0){level=this.bits.read(8)}else if(level===128){level=this.bits.read(8)-256}else if(level>128){level=level-256}}else{run=coeff>>8;level=coeff&255;if(this.bits.read(1)){level=-level}}n+=run;var dezigZagged=MPEG1.ZIG_ZAG[n];n++;level<<=1;if(!this.macroblockIntra){level+=level<0?-1:1}level=level*this.quantizerScale*quantMatrix[dezigZagged]>>4;if((level&1)===0){level-=level>0?1:-1}if(level>2047){level=2047}else if(level<-2048){level=-2048}this.blockData[dezigZagged]=level*MPEG1.PREMULTIPLIER_MATRIX[dezigZagged]}var destArray,destIndex,scan;if(block<4){destArray=this.currentY;scan=this.codedWidth-8;destIndex=this.mbRow*this.codedWidth+this.mbCol<<4;if((block&1)!==0){destIndex+=8}if((block&2)!==0){destIndex+=this.codedWidth<<3}}else{destArray=block===4?this.currentCb:this.currentCr;scan=(this.codedWidth>>1)-8;destIndex=(this.mbRow*this.codedWidth<<2)+(this.mbCol<<3)}if(this.macroblockIntra){if(n===1){MPEG1.CopyValueToDestination(this.blockData[0]+128>>8,destArray,destIndex,scan);this.blockData[0]=0}else{MPEG1.IDCT(this.blockData);MPEG1.CopyBlockToDestination(this.blockData,destArray,destIndex,scan);JSMpeg.Fill(this.blockData,0)}}else{if(n===1){MPEG1.AddValueToDestination(this.blockData[0]+128>>8,destArray,destIndex,scan);this.blockData[0]=0}else{MPEG1.IDCT(this.blockData);MPEG1.AddBlockToDestination(this.blockData,destArray,destIndex,scan);JSMpeg.Fill(this.blockData,0)}}n=0};MPEG1.CopyBlockToDestination=function(block,dest,index,scan){for(var n=0;n<64;n+=8,index+=scan+8){dest[index+0]=block[n+0];dest[index+1]=block[n+1];dest[index+2]=block[n+2];dest[index+3]=block[n+3];dest[index+4]=block[n+4];dest[index+5]=block[n+5];dest[index+6]=block[n+6];dest[index+7]=block[n+7]}};MPEG1.AddBlockToDestination=function(block,dest,index,scan){for(var n=0;n<64;n+=8,index+=scan+8){dest[index+0]+=block[n+0];dest[index+1]+=block[n+1];dest[index+2]+=block[n+2];dest[index+3]+=block[n+3];dest[index+4]+=block[n+4];dest[index+5]+=block[n+5];dest[index+6]+=block[n+6];dest[index+7]+=block[n+7]}};MPEG1.CopyValueToDestination=function(value,dest,index,scan){for(var n=0;n<64;n+=8,index+=scan+8){dest[index+0]=value;dest[index+1]=value;dest[index+2]=value;dest[index+3]=value;dest[index+4]=value;dest[index+5]=value;dest[index+6]=value;dest[index+7]=value}};MPEG1.AddValueToDestination=function(value,dest,index,scan){for(var n=0;n<64;n+=8,index+=scan+8){dest[index+0]+=value;dest[index+1]+=value;dest[index+2]+=value;dest[index+3]+=value;dest[index+4]+=value;dest[index+5]+=value;dest[index+6]+=value;dest[index+7]+=value}};MPEG1.IDCT=function(block){var b1,b3,b4,b6,b7,tmp1,tmp2,m0,x0,x1,x2,x3,x4,y3,y4,y5,y6,y7;for(var i=0;i<8;++i){b1=block[4*8+i];b3=block[2*8+i]+block[6*8+i];b4=block[5*8+i]-block[3*8+i];tmp1=block[1*8+i]+block[7*8+i];tmp2=block[3*8+i]+block[5*8+i];b6=block[1*8+i]-block[7*8+i];b7=tmp1+tmp2;m0=block[0*8+i];x4=(b6*473-b4*196+128>>8)-b7;x0=x4-((tmp1-tmp2)*362+128>>8);x1=m0-b1;x2=((block[2*8+i]-block[6*8+i])*362+128>>8)-b3;x3=m0+b1;y3=x1+x2;y4=x3+b3;y5=x1-x2;y6=x3-b3;y7=-x0-(b4*473+b6*196+128>>8);block[0*8+i]=b7+y4;block[1*8+i]=x4+y3;block[2*8+i]=y5-x0;block[3*8+i]=y6-y7;block[4*8+i]=y6+y7;block[5*8+i]=x0+y5;block[6*8+i]=y3-x4;block[7*8+i]=y4-b7}for(var i=0;i<64;i+=8){b1=block[4+i];b3=block[2+i]+block[6+i];b4=block[5+i]-block[3+i];tmp1=block[1+i]+block[7+i];tmp2=block[3+i]+block[5+i];b6=block[1+i]-block[7+i];b7=tmp1+tmp2;m0=block[0+i];x4=(b6*473-b4*196+128>>8)-b7;x0=x4-((tmp1-tmp2)*362+128>>8);x1=m0-b1;x2=((block[2+i]-block[6+i])*362+128>>8)-b3;x3=m0+b1;y3=x1+x2;y4=x3+b3;y5=x1-x2;y6=x3-b3;y7=-x0-(b4*473+b6*196+128>>8);block[0+i]=b7+y4+128>>8;block[1+i]=x4+y3+128>>8;block[2+i]=y5-x0+128>>8;block[3+i]=y6-y7+128>>8;block[4+i]=y6+y7+128>>8;block[5+i]=x0+y5+128>>8;block[6+i]=y3-x4+128>>8;block[7+i]=y4-b7+128>>8}};MPEG1.PICTURE_RATE=[0,23.976,24,25,29.97,30,50,59.94,60,0,0,0,0,0,0,0];MPEG1.ZIG_ZAG=new Uint8Array([0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63]);MPEG1.DEFAULT_INTRA_QUANT_MATRIX=new Uint8Array([8,16,19,22,26,27,29,34,16,16,22,24,27,29,34,37,19,22,26,27,29,34,34,38,22,22,26,27,29,34,37,40,22,26,27,29,32,35,40,48,26,27,29,32,35,40,48,58,26,27,29,34,38,46,56,69,27,29,35,38,46,56,69,83]);MPEG1.DEFAULT_NON_INTRA_QUANT_MATRIX=new Uint8Array([16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16]);MPEG1.PREMULTIPLIER_MATRIX=new Uint8Array([32,44,42,38,32,25,17,9,44,62,58,52,44,35,24,12,42,58,55,49,42,33,23,12,38,52,49,44,38,30,20,10,32,44,42,38,32,25,17,9,25,35,33,30,25,20,14,7,17,24,23,20,17,14,9,5,9,12,12,10,9,7,5,2]);MPEG1.MACROBLOCK_ADDRESS_INCREMENT=new Int16Array([1*3,2*3,0,3*3,4*3,0,0,0,1,5*3,6*3,0,7*3,8*3,0,9*3,10*3,0,11*3,12*3,0,0,0,3,0,0,2,13*3,14*3,0,15*3,16*3,0,0,0,5,0,0,4,17*3,18*3,0,19*3,20*3,0,0,0,7,0,0,6,21*3,22*3,0,23*3,24*3,0,25*3,26*3,0,27*3,28*3,0,-1,29*3,0,-1,30*3,0,31*3,32*3,0,33*3,34*3,0,35*3,36*3,0,37*3,38*3,0,0,0,9,0,0,8,39*3,40*3,0,41*3,42*3,0,43*3,44*3,0,45*3,46*3,0,0,0,15,0,0,14,0,0,13,0,0,12,0,0,11,0,0,10,47*3,-1,0,-1,48*3,0,49*3,50*3,0,51*3,52*3,0,53*3,54*3,0,55*3,56*3,0,57*3,58*3,0,59*3,60*3,0,61*3,-1,0,-1,62*3,0,63*3,64*3,0,65*3,66*3,0,67*3,68*3,0,69*3,70*3,0,71*3,72*3,0,73*3,74*3,0,0,0,21,0,0,20,0,0,19,0,0,18,0,0,17,0,0,16,0,0,35,0,0,34,0,0,33,0,0,32,0,0,31,0,0,30,0,0,29,0,0,28,0,0,27,0,0,26,0,0,25,0,0,24,0,0,23,0,0,22]);MPEG1.MACROBLOCK_TYPE_INTRA=new Int8Array([1*3,2*3,0,-1,3*3,0,0,0,1,0,0,17]);MPEG1.MACROBLOCK_TYPE_PREDICTIVE=new Int8Array([1*3,2*3,0,3*3,4*3,0,0,0,10,5*3,6*3,0,0,0,2,7*3,8*3,0,0,0,8,9*3,10*3,0,11*3,12*3,0,-1,13*3,0,0,0,18,0,0,26,0,0,1,0,0,17]);MPEG1.MACROBLOCK_TYPE_B=new Int8Array([1*3,2*3,0,3*3,5*3,0,4*3,6*3,0,8*3,7*3,0,0,0,12,9*3,10*3,0,0,0,14,13*3,14*3,0,12*3,11*3,0,0,0,4,0,0,6,18*3,16*3,0,15*3,17*3,0,0,0,8,0,0,10,-1,19*3,0,0,0,1,20*3,21*3,0,0,0,30,0,0,17,0,0,22,0,0,26]);MPEG1.MACROBLOCK_TYPE=[null,MPEG1.MACROBLOCK_TYPE_INTRA,MPEG1.MACROBLOCK_TYPE_PREDICTIVE,MPEG1.MACROBLOCK_TYPE_B];MPEG1.CODE_BLOCK_PATTERN=new Int16Array([2*3,1*3,0,3*3,6*3,0,4*3,5*3,0,8*3,11*3,0,12*3,13*3,0,9*3,7*3,0,10*3,14*3,0,20*3,19*3,0,18*3,16*3,0,23*3,17*3,0,27*3,25*3,0,21*3,28*3,0,15*3,22*3,0,24*3,26*3,0,0,0,60,35*3,40*3,0,44*3,48*3,0,38*3,36*3,0,42*3,47*3,0,29*3,31*3,0,39*3,32*3,0,0,0,32,45*3,46*3,0,33*3,41*3,0,43*3,34*3,0,0,0,4,30*3,37*3,0,0,0,8,0,0,16,0,0,44,50*3,56*3,0,0,0,28,0,0,52,0,0,62,61*3,59*3,0,52*3,60*3,0,0,0,1,55*3,54*3,0,0,0,61,0,0,56,57*3,58*3,0,0,0,2,0,0,40,51*3,62*3,0,0,0,48,64*3,63*3,0,49*3,53*3,0,0,0,20,0,0,12,80*3,83*3,0,0,0,63,77*3,75*3,0,65*3,73*3,0,84*3,66*3,0,0,0,24,0,0,36,0,0,3,69*3,87*3,0,81*3,79*3,0,68*3,71*3,0,70*3,78*3,0,67*3,76*3,0,72*3,74*3,0,86*3,85*3,0,88*3,82*3,0,-1,94*3,0,95*3,97*3,0,0,0,33,0,0,9,106*3,110*3,0,102*3,116*3,0,0,0,5,0,0,10,93*3,89*3,0,0,0,6,0,0,18,0,0,17,0,0,34,113*3,119*3,0,103*3,104*3,0,90*3,92*3,0,109*3,107*3,0,117*3,118*3,0,101*3,99*3,0,98*3,96*3,0,100*3,91*3,0,114*3,115*3,0,105*3,108*3,0,112*3,111*3,0,121*3,125*3,0,0,0,41,0,0,14,0,0,21,124*3,122*3,0,120*3,123*3,0,0,0,11,0,0,19,0,0,7,0,0,35,0,0,13,0,0,50,0,0,49,0,0,58,0,0,37,0,0,25,0,0,45,0,0,57,0,0,26,0,0,29,0,0,38,0,0,53,0,0,23,0,0,43,0,0,46,0,0,42,0,0,22,0,0,54,0,0,51,0,0,15,0,0,30,0,0,39,0,0,47,0,0,55,0,0,27,0,0,59,0,0,31]);MPEG1.MOTION=new Int16Array([1*3,2*3,0,4*3,3*3,0,0,0,0,6*3,5*3,0,8*3,7*3,0,0,0,-1,0,0,1,9*3,10*3,0,12*3,11*3,0,0,0,2,0,0,-2,14*3,15*3,0,16*3,13*3,0,20*3,18*3,0,0,0,3,0,0,-3,17*3,19*3,0,-1,23*3,0,27*3,25*3,0,26*3,21*3,0,24*3,22*3,0,32*3,28*3,0,29*3,31*3,0,-1,33*3,0,36*3,35*3,0,0,0,-4,30*3,34*3,0,0,0,4,0,0,-7,0,0,5,37*3,41*3,0,0,0,-5,0,0,7,38*3,40*3,0,42*3,39*3,0,0,0,-6,0,0,6,51*3,54*3,0,50*3,49*3,0,45*3,46*3,0,52*3,47*3,0,43*3,53*3,0,44*3,48*3,0,0,0,10,0,0,9,0,0,8,0,0,-8,57*3,66*3,0,0,0,-9,60*3,64*3,0,56*3,61*3,0,55*3,62*3,0,58*3,63*3,0,0,0,-10,59*3,65*3,0,0,0,12,0,0,16,0,0,13,0,0,14,0,0,11,0,0,15,0,0,-16,0,0,-12,0,0,-14,0,0,-15,0,0,-11,0,0,-13]);MPEG1.DCT_DC_SIZE_LUMINANCE=new Int8Array([2*3,1*3,0,6*3,5*3,0,3*3,4*3,0,0,0,1,0,0,2,9*3,8*3,0,7*3,10*3,0,0,0,0,12*3,11*3,0,0,0,4,0,0,3,13*3,14*3,0,0,0,5,0,0,6,16*3,15*3,0,17*3,-1,0,0,0,7,0,0,8]);MPEG1.DCT_DC_SIZE_CHROMINANCE=new Int8Array([2*3,1*3,0,4*3,3*3,0,6*3,5*3,0,8*3,7*3,0,0,0,2,0,0,1,0,0,0,10*3,9*3,0,0,0,3,12*3,11*3,0,0,0,4,14*3,13*3,0,0,0,5,16*3,15*3,0,0,0,6,17*3,-1,0,0,0,7,0,0,8]);MPEG1.DCT_COEFF=new Int32Array([1*3,2*3,0,4*3,3*3,0,0,0,1,7*3,8*3,0,6*3,5*3,0,13*3,9*3,0,11*3,10*3,0,14*3,12*3,0,0,0,257,20*3,22*3,0,18*3,21*3,0,16*3,19*3,0,0,0,513,17*3,15*3,0,0,0,2,0,0,3,27*3,25*3,0,29*3,31*3,0,24*3,26*3,0,32*3,30*3,0,0,0,1025,23*3,28*3,0,0,0,769,0,0,258,0,0,1793,0,0,65535,0,0,1537,37*3,36*3,0,0,0,1281,35*3,34*3,0,39*3,38*3,0,33*3,42*3,0,40*3,41*3,0,52*3,50*3,0,54*3,53*3,0,48*3,49*3,0,43*3,45*3,0,46*3,44*3,0,0,0,2049,0,0,4,0,0,514,0,0,2305,51*3,47*3,0,55*3,57*3,0,60*3,56*3,0,59*3,58*3,0,61*3,62*3,0,0,0,2561,0,0,3329,0,0,6,0,0,259,0,0,5,0,0,770,0,0,2817,0,0,3073,76*3,75*3,0,67*3,70*3,0,73*3,71*3,0,78*3,74*3,0,72*3,77*3,0,69*3,64*3,0,68*3,63*3,0,66*3,65*3,0,81*3,87*3,0,91*3,80*3,0,82*3,79*3,0,83*3,86*3,0,93*3,92*3,0,84*3,85*3,0,90*3,94*3,0,88*3,89*3,0,0,0,515,0,0,260,0,0,7,0,0,1026,0,0,1282,0,0,4097,0,0,3841,0,0,3585,105*3,107*3,0,111*3,114*3,0,104*3,97*3,0,125*3,119*3,0,96*3,98*3,0,-1,123*3,0,95*3,101*3,0,106*3,121*3,0,99*3,102*3,0,113*3,103*3,0,112*3,116*3,0,110*3,100*3,0,124*3,115*3,0,117*3,122*3,0,109*3,118*3,0,120*3,108*3,0,127*3,136*3,0,139*3,140*3,0,130*3,126*3,0,145*3,146*3,0,128*3,129*3,0,0,0,2050,132*3,134*3,0,155*3,154*3,0,0,0,8,137*3,133*3,0,143*3,144*3,0,151*3,138*3,0,142*3,141*3,0,0,0,10,0,0,9,0,0,11,0,0,5377,0,0,1538,0,0,771,0,0,5121,0,0,1794,0,0,4353,0,0,4609,0,0,4865,148*3,152*3,0,0,0,1027,153*3,150*3,0,0,0,261,131*3,135*3,0,0,0,516,149*3,147*3,0,172*3,173*3,0,162*3,158*3,0,170*3,161*3,0,168*3,166*3,0,157*3,179*3,0,169*3,167*3,0,174*3,171*3,0,178*3,177*3,0,156*3,159*3,0,164*3,165*3,0,183*3,182*3,0,175*3,176*3,0,0,0,263,0,0,2562,0,0,2306,0,0,5633,0,0,5889,0,0,6401,0,0,6145,0,0,1283,0,0,772,0,0,13,0,0,12,0,0,14,0,0,15,0,0,517,0,0,6657,0,0,262,180*3,181*3,0,160*3,163*3,0,196*3,199*3,0,0,0,27,203*3,185*3,0,202*3,201*3,0,0,0,19,0,0,22,197*3,207*3,0,0,0,18,191*3,192*3,0,188*3,190*3,0,0,0,20,184*3,194*3,0,0,0,21,186*3,193*3,0,0,0,23,204*3,198*3,0,0,0,25,0,0,24,200*3,205*3,0,0,0,31,0,0,30,0,0,28,0,0,29,0,0,26,0,0,17,0,0,16,189*3,206*3,0,187*3,195*3,0,218*3,211*3,0,0,0,37,215*3,216*3,0,0,0,36,210*3,212*3,0,0,0,34,213*3,209*3,0,221*3,222*3,0,219*3,208*3,0,217*3,214*3,0,223*3,220*3,0,0,0,35,0,0,267,0,0,40,0,0,268,0,0,266,0,0,32,0,0,264,0,0,265,0,0,38,0,0,269,0,0,270,0,0,33,0,0,39,0,0,7937,0,0,6913,0,0,7681,0,0,4098,0,0,7425,0,0,7169,0,0,271,0,0,274,0,0,273,0,0,272,0,0,1539,0,0,2818,0,0,3586,0,0,3330,0,0,3074,0,0,3842]);MPEG1.PICTURE_TYPE={INTRA:1,PREDICTIVE:2,B:3};MPEG1.START={SEQUENCE:179,SLICE_FIRST:1,SLICE_LAST:175,PICTURE:0,EXTENSION:181,USER_DATA:178};return MPEG1}();JSMpeg.Decoder.MPEG1VideoWASM=function(){"use strict";var MPEG1WASM=function(options){JSMpeg.Decoder.Base.call(this,options);this.onDecodeCallback=options.onVideoDecode;this.module=options.wasmModule;this.bufferSize=options.videoBufferSize||512*1024;this.bufferMode=options.streaming?JSMpeg.BitBuffer.MODE.EVICT:JSMpeg.BitBuffer.MODE.EXPAND;this.decodeFirstFrame=options.decodeFirstFrame!==false;this.hasSequenceHeader=false};MPEG1WASM.prototype=Object.create(JSMpeg.Decoder.Base.prototype);MPEG1WASM.prototype.constructor=MPEG1WASM;MPEG1WASM.prototype.initializeWasmDecoder=function(){if(!this.module.instance){console.warn("JSMpeg: WASM module not compiled yet");return}this.instance=this.module.instance;this.functions=this.module.instance.exports;this.decoder=this.functions._mpeg1_decoder_create(this.bufferSize,this.bufferMode)};MPEG1WASM.prototype.destroy=function(){if(!this.decoder){return}this.functions._mpeg1_decoder_destroy(this.decoder)};MPEG1WASM.prototype.bufferGetIndex=function(){if(!this.decoder){return}return this.functions._mpeg1_decoder_get_index(this.decoder)};MPEG1WASM.prototype.bufferSetIndex=function(index){if(!this.decoder){return}this.functions._mpeg1_decoder_set_index(this.decoder,index)};MPEG1WASM.prototype.bufferWrite=function(buffers){if(!this.decoder){this.initializeWasmDecoder()}var totalLength=0;for(var i=0;i>2));var dcb=this.instance.heapU8.subarray(ptrCb,ptrCb+(this.codedSize>>2));this.destination.render(dy,dcr,dcb,false)}this.advanceDecodedTime(1/this.frameRate);var elapsedTime=JSMpeg.Now()-startTime;if(this.onDecodeCallback){this.onDecodeCallback(this,elapsedTime)}return true};return MPEG1WASM}();JSMpeg.Decoder.MP2Audio=function(){"use strict";var MP2=function(options){JSMpeg.Decoder.Base.call(this,options);this.onDecodeCallback=options.onAudioDecode;var bufferSize=options.audioBufferSize||128*1024;var bufferMode=options.streaming?JSMpeg.BitBuffer.MODE.EVICT:JSMpeg.BitBuffer.MODE.EXPAND;this.bits=new JSMpeg.BitBuffer(bufferSize,bufferMode);this.left=new Float32Array(1152);this.right=new Float32Array(1152);this.sampleRate=44100;this.D=new Float32Array(1024);this.D.set(MP2.SYNTHESIS_WINDOW,0);this.D.set(MP2.SYNTHESIS_WINDOW,512);this.V=new Float32Array(1024);this.U=new Int32Array(32);this.VPos=0;this.allocation=[new Array(32),new Array(32)];this.scaleFactorInfo=[new Uint8Array(32),new Uint8Array(32)];this.scaleFactor=[new Array(32),new Array(32)];this.sample=[new Array(32),new Array(32)];for(var j=0;j<2;j++){for(var i=0;i<32;i++){this.scaleFactor[j][i]=[0,0,0];this.sample[j][i]=[0,0,0]}}};MP2.prototype=Object.create(JSMpeg.Decoder.Base.prototype);MP2.prototype.constructor=MP2;MP2.prototype.decode=function(){var startTime=JSMpeg.Now();var pos=this.bits.index>>3;if(pos>=this.bits.byteLength){return false}var decoded=this.decodeFrame(this.left,this.right);this.bits.index=pos+decoded<<3;if(!decoded){return false}if(this.destination){this.destination.play(this.sampleRate,this.left,this.right)}this.advanceDecodedTime(this.left.length/this.sampleRate);var elapsedTime=JSMpeg.Now()-startTime;if(this.onDecodeCallback){this.onDecodeCallback(this,elapsedTime)}return true};MP2.prototype.getCurrentTime=function(){var enqueuedTime=this.destination?this.destination.enqueuedTime:0;return this.decodedTime-enqueuedTime};MP2.prototype.decodeFrame=function(left,right){var sync=this.bits.read(11),version=this.bits.read(2),layer=this.bits.read(2),hasCRC=!this.bits.read(1);if(sync!==MP2.FRAME_SYNC||version!==MP2.VERSION.MPEG_1||layer!==MP2.LAYER.II){return 0}var bitrateIndex=this.bits.read(4)-1;if(bitrateIndex>13){return 0}var sampleRateIndex=this.bits.read(2);var sampleRate=MP2.SAMPLE_RATE[sampleRateIndex];if(sampleRateIndex===3){return 0}if(version===MP2.VERSION.MPEG_2){sampleRateIndex+=4;bitrateIndex+=14}var padding=this.bits.read(1),privat=this.bits.read(1),mode=this.bits.read(2);var bound=0;if(mode===MP2.MODE.JOINT_STEREO){bound=this.bits.read(2)+1<<2}else{this.bits.skip(2);bound=mode===MP2.MODE.MONO?0:32}this.bits.skip(4);if(hasCRC){this.bits.skip(16)}var bitrate=MP2.BIT_RATE[bitrateIndex],sampleRate=MP2.SAMPLE_RATE[sampleRateIndex],frameSize=144e3*bitrate/sampleRate+padding|0;var tab3=0;var sblimit=0;if(version===MP2.VERSION.MPEG_2){tab3=2;sblimit=30}else{var tab1=mode===MP2.MODE.MONO?0:1;var tab2=MP2.QUANT_LUT_STEP_1[tab1][bitrateIndex];tab3=MP2.QUANT_LUT_STEP_2[tab2][sampleRateIndex];sblimit=tab3&63;tab3>>=6}if(bound>sblimit){bound=sblimit}for(var sb=0;sb>1);var vIndex=this.VPos%128>>1;while(vIndex<1024){for(var i=0;i<32;++i){this.U[i]+=this.D[dIndex++]*this.V[vIndex++]}vIndex+=128-32;dIndex+=64-32}vIndex=128-32+1024-vIndex;dIndex-=512-32;while(vIndex<1024){for(var i=0;i<32;++i){this.U[i]+=this.D[dIndex++]*this.V[vIndex++]}vIndex+=128-32;dIndex+=64-32}var outChannel=ch===0?left:right;for(var j=0;j<32;j++){outChannel[outPos+j]=this.U[j]/2147418112}}outPos+=32}}}this.sampleRate=sampleRate;return frameSize};MP2.prototype.readAllocation=function(sb,tab3){var tab4=MP2.QUANT_LUT_STEP_3[tab3][sb];var qtab=MP2.QUANT_LUT_STEP4[tab4&15][this.bits.read(tab4>>4)];return qtab?MP2.QUANT_TAB[qtab-1]:0};MP2.prototype.readSamples=function(ch,sb,part){var q=this.allocation[ch][sb],sf=this.scaleFactor[ch][sb][part],sample=this.sample[ch][sb],val=0;if(!q){sample[0]=sample[1]=sample[2]=0;return}if(sf===63){sf=0}else{var shift=sf/3|0;sf=MP2.SCALEFACTOR_BASE[sf%3]+(1<>1)>>shift}var adj=q.levels;if(q.group){val=this.bits.read(q.bits);sample[0]=val%adj;val=val/adj|0;sample[1]=val%adj;sample[2]=val/adj|0}else{sample[0]=this.bits.read(q.bits);sample[1]=this.bits.read(q.bits);sample[2]=this.bits.read(q.bits)}var scale=65536/(adj+1)|0;adj=(adj+1>>1)-1;val=(adj-sample[0])*scale;sample[0]=val*(sf>>12)+(val*(sf&4095)+2048>>12)>>12;val=(adj-sample[1])*scale;sample[1]=val*(sf>>12)+(val*(sf&4095)+2048>>12)>>12;val=(adj-sample[2])*scale;sample[2]=val*(sf>>12)+(val*(sf&4095)+2048>>12)>>12};MP2.MatrixTransform=function(s,ss,d,dp){var t01,t02,t03,t04,t05,t06,t07,t08,t09,t10,t11,t12,t13,t14,t15,t16,t17,t18,t19,t20,t21,t22,t23,t24,t25,t26,t27,t28,t29,t30,t31,t32,t33;t01=s[0][ss]+s[31][ss];t02=(s[0][ss]-s[31][ss])*.500602998235;t03=s[1][ss]+s[30][ss];t04=(s[1][ss]-s[30][ss])*.505470959898;t05=s[2][ss]+s[29][ss];t06=(s[2][ss]-s[29][ss])*.515447309923;t07=s[3][ss]+s[28][ss];t08=(s[3][ss]-s[28][ss])*.53104259109;t09=s[4][ss]+s[27][ss];t10=(s[4][ss]-s[27][ss])*.553103896034;t11=s[5][ss]+s[26][ss];t12=(s[5][ss]-s[26][ss])*.582934968206;t13=s[6][ss]+s[25][ss];t14=(s[6][ss]-s[25][ss])*.622504123036;t15=s[7][ss]+s[24][ss];t16=(s[7][ss]-s[24][ss])*.674808341455;t17=s[8][ss]+s[23][ss];t18=(s[8][ss]-s[23][ss])*.744536271002;t19=s[9][ss]+s[22][ss];t20=(s[9][ss]-s[22][ss])*.839349645416;t21=s[10][ss]+s[21][ss];t22=(s[10][ss]-s[21][ss])*.972568237862;t23=s[11][ss]+s[20][ss];t24=(s[11][ss]-s[20][ss])*1.16943993343;t25=s[12][ss]+s[19][ss];t26=(s[12][ss]-s[19][ss])*1.48416461631;t27=s[13][ss]+s[18][ss];t28=(s[13][ss]-s[18][ss])*2.05778100995;t29=s[14][ss]+s[17][ss];t30=(s[14][ss]-s[17][ss])*3.40760841847;t31=s[15][ss]+s[16][ss];t32=(s[15][ss]-s[16][ss])*10.1900081235;t33=t01+t31;t31=(t01-t31)*.502419286188;t01=t03+t29;t29=(t03-t29)*.52249861494;t03=t05+t27;t27=(t05-t27)*.566944034816;t05=t07+t25;t25=(t07-t25)*.64682178336;t07=t09+t23;t23=(t09-t23)*.788154623451;t09=t11+t21;t21=(t11-t21)*1.06067768599;t11=t13+t19;t19=(t13-t19)*1.72244709824;t13=t15+t17;t17=(t15-t17)*5.10114861869;t15=t33+t13;t13=(t33-t13)*.509795579104;t33=t01+t11;t01=(t01-t11)*.601344886935;t11=t03+t09;t09=(t03-t09)*.899976223136;t03=t05+t07;t07=(t05-t07)*2.56291544774;t05=t15+t03;t15=(t15-t03)*.541196100146;t03=t33+t11;t11=(t33-t11)*1.30656296488;t33=t05+t03;t05=(t05-t03)*.707106781187;t03=t15+t11;t15=(t15-t11)*.707106781187;t03+=t15;t11=t13+t07;t13=(t13-t07)*.541196100146;t07=t01+t09;t09=(t01-t09)*1.30656296488;t01=t11+t07;t07=(t11-t07)*.707106781187;t11=t13+t09;t13=(t13-t09)*.707106781187;t11+=t13;t01+=t11;t11+=t07;t07+=t13;t09=t31+t17;t31=(t31-t17)*.509795579104;t17=t29+t19;t29=(t29-t19)*.601344886935;t19=t27+t21;t21=(t27-t21)*.899976223136;t27=t25+t23;t23=(t25-t23)*2.56291544774;t25=t09+t27;t09=(t09-t27)*.541196100146;t27=t17+t19;t19=(t17-t19)*1.30656296488;t17=t25+t27;t27=(t25-t27)*.707106781187;t25=t09+t19;t19=(t09-t19)*.707106781187;t25+=t19;t09=t31+t23;t31=(t31-t23)*.541196100146;t23=t29+t21;t21=(t29-t21)*1.30656296488;t29=t09+t23;t23=(t09-t23)*.707106781187;t09=t31+t21;t31=(t31-t21)*.707106781187;t09+=t31;t29+=t09;t09+=t23;t23+=t31;t17+=t29;t29+=t25;t25+=t09;t09+=t27;t27+=t23;t23+=t19;t19+=t31;t21=t02+t32;t02=(t02-t32)*.502419286188;t32=t04+t30;t04=(t04-t30)*.52249861494;t30=t06+t28;t28=(t06-t28)*.566944034816;t06=t08+t26;t08=(t08-t26)*.64682178336;t26=t10+t24;t10=(t10-t24)*.788154623451;t24=t12+t22;t22=(t12-t22)*1.06067768599;t12=t14+t20;t20=(t14-t20)*1.72244709824;t14=t16+t18;t16=(t16-t18)*5.10114861869;t18=t21+t14;t14=(t21-t14)*.509795579104;t21=t32+t12;t32=(t32-t12)*.601344886935;t12=t30+t24;t24=(t30-t24)*.899976223136;t30=t06+t26;t26=(t06-t26)*2.56291544774;t06=t18+t30;t18=(t18-t30)*.541196100146;t30=t21+t12;t12=(t21-t12)*1.30656296488;t21=t06+t30;t30=(t06-t30)*.707106781187;t06=t18+t12;t12=(t18-t12)*.707106781187;t06+=t12;t18=t14+t26;t26=(t14-t26)*.541196100146;t14=t32+t24;t24=(t32-t24)*1.30656296488;t32=t18+t14;t14=(t18-t14)*.707106781187;t18=t26+t24;t24=(t26-t24)*.707106781187;t18+=t24;t32+=t18;t18+=t14;t26=t14+t24;t14=t02+t16;t02=(t02-t16)*.509795579104;t16=t04+t20;t04=(t04-t20)*.601344886935;t20=t28+t22;t22=(t28-t22)*.899976223136;t28=t08+t10;t10=(t08-t10)*2.56291544774;t08=t14+t28;t14=(t14-t28)*.541196100146;t28=t16+t20;t20=(t16-t20)*1.30656296488;t16=t08+t28;t28=(t08-t28)*.707106781187;t08=t14+t20;t20=(t14-t20)*.707106781187;t08+=t20;t14=t02+t10;t02=(t02-t10)*.541196100146;t10=t04+t22;t22=(t04-t22)*1.30656296488;t04=t14+t10;t10=(t14-t10)*.707106781187;t14=t02+t22;t02=(t02-t22)*.707106781187;t14+=t02;t04+=t14;t14+=t10;t10+=t02;t16+=t04;t04+=t08;t08+=t14;t14+=t28;t28+=t10;t10+=t20;t20+=t02;t21+=t16;t16+=t32;t32+=t04;t04+=t06;t06+=t08;t08+=t18;t18+=t14;t14+=t30;t30+=t28;t28+=t26;t26+=t10;t10+=t12;t12+=t20;t20+=t24;t24+=t02;d[dp+48]=-t33;d[dp+49]=d[dp+47]=-t21;d[dp+50]=d[dp+46]=-t17;d[dp+51]=d[dp+45]=-t16;d[dp+52]=d[dp+44]=-t01;d[dp+53]=d[dp+43]=-t32;d[dp+54]=d[dp+42]=-t29;d[dp+55]=d[dp+41]=-t04;d[dp+56]=d[dp+40]=-t03;d[dp+57]=d[dp+39]=-t06;d[dp+58]=d[dp+38]=-t25;d[dp+59]=d[dp+37]=-t08;d[dp+60]=d[dp+36]=-t11;d[dp+61]=d[dp+35]=-t18;d[dp+62]=d[dp+34]=-t09;d[dp+63]=d[dp+33]=-t14;d[dp+32]=-t05;d[dp+0]=t05;d[dp+31]=-t30;d[dp+1]=t30;d[dp+30]=-t27;d[dp+2]=t27;d[dp+29]=-t28;d[dp+3]=t28;d[dp+28]=-t07;d[dp+4]=t07;d[dp+27]=-t26;d[dp+5]=t26;d[dp+26]=-t23;d[dp+6]=t23;\n' + 56 | '\n' + 57 | 'd[dp+25]=-t10;d[dp+7]=t10;d[dp+24]=-t15;d[dp+8]=t15;d[dp+23]=-t12;d[dp+9]=t12;d[dp+22]=-t19;d[dp+10]=t19;d[dp+21]=-t20;d[dp+11]=t20;d[dp+20]=-t13;d[dp+12]=t13;d[dp+19]=-t24;d[dp+13]=t24;d[dp+18]=-t31;d[dp+14]=t31;d[dp+17]=-t02;d[dp+15]=t02;d[dp+16]=0};MP2.FRAME_SYNC=2047;MP2.VERSION={MPEG_2_5:0,MPEG_2:2,MPEG_1:3};MP2.LAYER={III:1,II:2,I:3};MP2.MODE={STEREO:0,JOINT_STEREO:1,DUAL_CHANNEL:2,MONO:3};MP2.SAMPLE_RATE=new Uint16Array([44100,48e3,32e3,0,22050,24e3,16e3,0]);MP2.BIT_RATE=new Uint16Array([32,48,56,64,80,96,112,128,160,192,224,256,320,384,8,16,24,32,40,48,56,64,80,96,112,128,144,160]);MP2.SCALEFACTOR_BASE=new Uint32Array([33554432,26632170,21137968]);MP2.SYNTHESIS_WINDOW=new Float32Array([0,-.5,-.5,-.5,-.5,-.5,-.5,-1,-1,-1,-1,-1.5,-1.5,-2,-2,-2.5,-2.5,-3,-3.5,-3.5,-4,-4.5,-5,-5.5,-6.5,-7,-8,-8.5,-9.5,-10.5,-12,-13,-14.5,-15.5,-17.5,-19,-20.5,-22.5,-24.5,-26.5,-29,-31.5,-34,-36.5,-39.5,-42.5,-45.5,-48.5,-52,-55.5,-58.5,-62.5,-66,-69.5,-73.5,-77,-80.5,-84.5,-88,-91.5,-95,-98,-101,-104,106.5,109,111,112.5,113.5,114,114,113.5,112,110.5,107.5,104,100,94.5,88.5,81.5,73,63.5,53,41.5,28.5,14.5,-1,-18,-36,-55.5,-76.5,-98.5,-122,-147,-173.5,-200.5,-229.5,-259.5,-290.5,-322.5,-355.5,-389.5,-424,-459.5,-495.5,-532,-568.5,-605,-641.5,-678,-714,-749,-783.5,-817,-849,-879.5,-908.5,-935,-959.5,-981,-1000.5,-1016,-1028.5,-1037.5,-1042.5,-1043.5,-1040,-1031.5,1018.5,1e3,976,946.5,911,869.5,822,767.5,707,640,565.5,485,397,302.5,201,92.5,-22.5,-144,-272.5,-407,-547.5,-694,-846,-1003,-1165,-1331.5,-1502,-1675.5,-1852.5,-2031.5,-2212.5,-2394,-2576.5,-2758.5,-2939.5,-3118.5,-3294.5,-3467.5,-3635.5,-3798.5,-3955,-4104.5,-4245.5,-4377.5,-4499,-4609.5,-4708,-4792.5,-4863.5,-4919,-4958,-4979.5,-4983,-4967.5,-4931.5,-4875,-4796,-4694.5,-4569.5,-4420,-4246,-4046,-3820,-3567,3287,2979.5,2644,2280.5,1888,1467.5,1018.5,541,35,-499,-1061,-1650,-2266.5,-2909,-3577,-4270,-4987.5,-5727.5,-6490,-7274,-8077.5,-8899.5,-9739,-10594.5,-11464.5,-12347,-13241,-14144.5,-15056,-15973.5,-16895.5,-17820,-18744.5,-19668,-20588,-21503,-22410.5,-23308.5,-24195,-25068.5,-25926.5,-26767,-27589,-28389,-29166.5,-29919,-30644.5,-31342,-32009.5,-32645,-33247,-33814.5,-34346,-34839.5,-35295,-35710,-36084.5,-36417.5,-36707.5,-36954,-37156.5,-37315,-37428,-37496,37519,37496,37428,37315,37156.5,36954,36707.5,36417.5,36084.5,35710,35295,34839.5,34346,33814.5,33247,32645,32009.5,31342,30644.5,29919,29166.5,28389,27589,26767,25926.5,25068.5,24195,23308.5,22410.5,21503,20588,19668,18744.5,17820,16895.5,15973.5,15056,14144.5,13241,12347,11464.5,10594.5,9739,8899.5,8077.5,7274,6490,5727.5,4987.5,4270,3577,2909,2266.5,1650,1061,499,-35,-541,-1018.5,-1467.5,-1888,-2280.5,-2644,-2979.5,3287,3567,3820,4046,4246,4420,4569.5,4694.5,4796,4875,4931.5,4967.5,4983,4979.5,4958,4919,4863.5,4792.5,4708,4609.5,4499,4377.5,4245.5,4104.5,3955,3798.5,3635.5,3467.5,3294.5,3118.5,2939.5,2758.5,2576.5,2394,2212.5,2031.5,1852.5,1675.5,1502,1331.5,1165,1003,846,694,547.5,407,272.5,144,22.5,-92.5,-201,-302.5,-397,-485,-565.5,-640,-707,-767.5,-822,-869.5,-911,-946.5,-976,-1e3,1018.5,1031.5,1040,1043.5,1042.5,1037.5,1028.5,1016,1000.5,981,959.5,935,908.5,879.5,849,817,783.5,749,714,678,641.5,605,568.5,532,495.5,459.5,424,389.5,355.5,322.5,290.5,259.5,229.5,200.5,173.5,147,122,98.5,76.5,55.5,36,18,1,-14.5,-28.5,-41.5,-53,-63.5,-73,-81.5,-88.5,-94.5,-100,-104,-107.5,-110.5,-112,-113.5,-114,-114,-113.5,-112.5,-111,-109,106.5,104,101,98,95,91.5,88,84.5,80.5,77,73.5,69.5,66,62.5,58.5,55.5,52,48.5,45.5,42.5,39.5,36.5,34,31.5,29,26.5,24.5,22.5,20.5,19,17.5,15.5,14.5,13,12,10.5,9.5,8.5,8,7,6.5,5.5,5,4.5,4,3.5,3.5,3,2.5,2.5,2,2,1.5,1.5,1,1,1,1,.5,.5,.5,.5,.5,.5]);MP2.QUANT_LUT_STEP_1=[[0,0,1,1,1,2,2,2,2,2,2,2,2,2],[0,0,0,0,0,0,1,1,1,2,2,2,2,2]];MP2.QUANT_TAB={A:27|64,B:30|64,C:8,D:12};MP2.QUANT_LUT_STEP_2=[[MP2.QUANT_TAB.C,MP2.QUANT_TAB.C,MP2.QUANT_TAB.D],[MP2.QUANT_TAB.A,MP2.QUANT_TAB.A,MP2.QUANT_TAB.A],[MP2.QUANT_TAB.B,MP2.QUANT_TAB.A,MP2.QUANT_TAB.B]];MP2.QUANT_LUT_STEP_3=[[68,68,52,52,52,52,52,52,52,52,52,52],[67,67,67,66,66,66,66,66,66,66,66,49,49,49,49,49,49,49,49,49,49,49,49,32,32,32,32,32,32,32],[69,69,69,69,52,52,52,52,52,52,52,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36]];MP2.QUANT_LUT_STEP4=[[0,1,2,17],[0,1,2,3,4,5,6,17],[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,17],[0,1,3,5,6,7,8,9,10,11,12,13,14,15,16,17],[0,1,2,4,5,6,7,8,9,10,11,12,13,14,15,17],[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]];MP2.QUANT_TAB=[{levels:3,group:1,bits:5},{levels:5,group:1,bits:7},{levels:7,group:0,bits:3},{levels:9,group:1,bits:10},{levels:15,group:0,bits:4},{levels:31,group:0,bits:5},{levels:63,group:0,bits:6},{levels:127,group:0,bits:7},{levels:255,group:0,bits:8},{levels:511,group:0,bits:9},{levels:1023,group:0,bits:10},{levels:2047,group:0,bits:11},{levels:4095,group:0,bits:12},{levels:8191,group:0,bits:13},{levels:16383,group:0,bits:14},{levels:32767,group:0,bits:15},{levels:65535,group:0,bits:16}];return MP2}();JSMpeg.Decoder.MP2AudioWASM=function(){"use strict";var MP2WASM=function(options){JSMpeg.Decoder.Base.call(this,options);this.onDecodeCallback=options.onAudioDecode;this.module=options.wasmModule;this.bufferSize=options.audioBufferSize||128*1024;this.bufferMode=options.streaming?JSMpeg.BitBuffer.MODE.EVICT:JSMpeg.BitBuffer.MODE.EXPAND;this.sampleRate=0};MP2WASM.prototype=Object.create(JSMpeg.Decoder.Base.prototype);MP2WASM.prototype.constructor=MP2WASM;MP2WASM.prototype.initializeWasmDecoder=function(){if(!this.module.instance){console.warn("JSMpeg: WASM module not compiled yet");return}this.instance=this.module.instance;this.functions=this.module.instance.exports;this.decoder=this.functions._mp2_decoder_create(this.bufferSize,this.bufferMode)};MP2WASM.prototype.destroy=function(){if(!this.decoder){return}this.functions._mp2_decoder_destroy(this.decoder)};MP2WASM.prototype.bufferGetIndex=function(){if(!this.decoder){return}return this.functions._mp2_decoder_get_index(this.decoder)};MP2WASM.prototype.bufferSetIndex=function(index){if(!this.decoder){return}this.functions._mp2_decoder_set_index(this.decoder,index)};MP2WASM.prototype.bufferWrite=function(buffers){if(!this.decoder){this.initializeWasmDecoder()}var totalLength=0;for(var i=0;i>4<<4;this.gl.viewport(0,0,codedWidth,this.height)};WebGLRenderer.prototype.createTexture=function(index,name){var gl=this.gl;var texture=gl.createTexture();gl.bindTexture(gl.TEXTURE_2D,texture);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,gl.LINEAR);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE);gl.uniform1i(gl.getUniformLocation(this.program,name),index);return texture};WebGLRenderer.prototype.createProgram=function(vsh,fsh){var gl=this.gl;var program=gl.createProgram();gl.attachShader(program,this.compileShader(gl.VERTEX_SHADER,vsh));gl.attachShader(program,this.compileShader(gl.FRAGMENT_SHADER,fsh));gl.linkProgram(program);gl.useProgram(program);return program};WebGLRenderer.prototype.compileShader=function(type,source){var gl=this.gl;var shader=gl.createShader(type);gl.shaderSource(shader,source);gl.compileShader(shader);if(!gl.getShaderParameter(shader,gl.COMPILE_STATUS)){throw new Error(gl.getShaderInfoLog(shader))}return shader};WebGLRenderer.prototype.allowsClampedTextureData=function(){var gl=this.gl;var texture=gl.createTexture();gl.bindTexture(gl.TEXTURE_2D,texture);gl.texImage2D(gl.TEXTURE_2D,0,gl.LUMINANCE,1,1,0,gl.LUMINANCE,gl.UNSIGNED_BYTE,new Uint8ClampedArray([0]));return gl.getError()===0};WebGLRenderer.prototype.renderProgress=function(progress){var gl=this.gl;gl.useProgram(this.loadingProgram);var loc=gl.getUniformLocation(this.loadingProgram,"progress");gl.uniform1f(loc,progress);gl.drawArrays(gl.TRIANGLE_STRIP,0,4)};WebGLRenderer.prototype.render=function(y,cb,cr,isClampedArray){if(!this.enabled){return}var gl=this.gl;var w=this.width+15>>4<<4,h=this.height,w2=w>>1,h2=h>>1;if(isClampedArray&&this.shouldCreateUnclampedViews){y=new Uint8Array(y.buffer),cb=new Uint8Array(cb.buffer),cr=new Uint8Array(cr.buffer)}gl.useProgram(this.program);this.updateTexture(gl.TEXTURE0,this.textureY,w,h,y);this.updateTexture(gl.TEXTURE1,this.textureCb,w2,h2,cb);this.updateTexture(gl.TEXTURE2,this.textureCr,w2,h2,cr);gl.drawArrays(gl.TRIANGLE_STRIP,0,4)};WebGLRenderer.prototype.updateTexture=function(unit,texture,w,h,data){var gl=this.gl;gl.activeTexture(unit);gl.bindTexture(gl.TEXTURE_2D,texture);if(this.hasTextureData[unit]){gl.texSubImage2D(gl.TEXTURE_2D,0,0,0,w,h,gl.LUMINANCE,gl.UNSIGNED_BYTE,data)}else{this.hasTextureData[unit]=true;gl.texImage2D(gl.TEXTURE_2D,0,gl.LUMINANCE,w,h,0,gl.LUMINANCE,gl.UNSIGNED_BYTE,data)}};WebGLRenderer.prototype.deleteTexture=function(unit,texture){var gl=this.gl;gl.activeTexture(unit);gl.bindTexture(gl.TEXTURE_2D,null);gl.deleteTexture(texture)};WebGLRenderer.IsSupported=function(){try{if(!window.WebGLRenderingContext){return false}var canvas=document.createElement("canvas");return!!(canvas.getContext("webgl")||canvas.getContext("experimental-webgl"))}catch(err){return false}};WebGLRenderer.SHADER={FRAGMENT_YCRCB_TO_RGBA:["precision mediump float;","uniform sampler2D textureY;","uniform sampler2D textureCb;","uniform sampler2D textureCr;","varying vec2 texCoord;","mat4 rec601 = mat4(","1.16438, 0.00000, 1.59603, -0.87079,","1.16438, -0.39176, -0.81297, 0.52959,","1.16438, 2.01723, 0.00000, -1.08139,","0, 0, 0, 1",");","void main() {","float y = texture2D(textureY, texCoord).r;","float cb = texture2D(textureCb, texCoord).r;","float cr = texture2D(textureCr, texCoord).r;","gl_FragColor = vec4(y, cr, cb, 1.0) * rec601;","}"].join("\\n"),FRAGMENT_LOADING:["precision mediump float;","uniform float progress;","varying vec2 texCoord;","void main() {","float c = ceil(progress-(1.0-texCoord.y));","gl_FragColor = vec4(c,c,c,1);","}"].join("\\n"),VERTEX_IDENTITY:["attribute vec2 vertex;","varying vec2 texCoord;","void main() {","texCoord = vertex;","gl_Position = vec4((vertex * 2.0 - 1.0) * vec2(1, -1), 0.0, 1.0);","}"].join("\\n")};return WebGLRenderer}();JSMpeg.Renderer.Canvas2D=function(){"use strict";var CanvasRenderer=function(options){this.canvas=options.canvas||document.createElement("canvas");this.width=this.canvas.width;this.height=this.canvas.height;this.enabled=true;this.context=this.canvas.getContext("2d")};CanvasRenderer.prototype.destroy=function(){this.canvas.remove()};CanvasRenderer.prototype.resize=function(width,height){this.width=width|0;this.height=height|0;this.canvas.width=this.width;this.canvas.height=this.height;this.imageData=this.context.getImageData(0,0,this.width,this.height);JSMpeg.Fill(this.imageData.data,255)};CanvasRenderer.prototype.renderProgress=function(progress){var w=this.canvas.width,h=this.canvas.height,ctx=this.context;ctx.fillStyle="#222";ctx.fillRect(0,0,w,h);ctx.fillStyle="#fff";ctx.fillRect(0,h-h*progress,w,h*progress)};CanvasRenderer.prototype.render=function(y,cb,cr){this.YCbCrToRGBA(y,cb,cr,this.imageData.data);this.context.putImageData(this.imageData,0,0)};CanvasRenderer.prototype.YCbCrToRGBA=function(y,cb,cr,rgba){if(!this.enabled){return}var w=this.width+15>>4<<4,w2=w>>1;var yIndex1=0,yIndex2=w,yNext2Lines=w+(w-this.width);var cIndex=0,cNextLine=w2-(this.width>>1);var rgbaIndex1=0,rgbaIndex2=this.width*4,rgbaNext2Lines=this.width*4;var cols=this.width>>1,rows=this.height>>1;var ccb,ccr,r,g,b;for(var row=0;row>8)-179;g=(ccr*88>>8)-44+(ccb*183>>8)-91;b=ccr+(ccr*198>>8)-227;var y1=y[yIndex1++];var y2=y[yIndex1++];rgba[rgbaIndex1]=y1+r;rgba[rgbaIndex1+1]=y1-g;rgba[rgbaIndex1+2]=y1+b;rgba[rgbaIndex1+4]=y2+r;rgba[rgbaIndex1+5]=y2-g;rgba[rgbaIndex1+6]=y2+b;rgbaIndex1+=8;var y3=y[yIndex2++];var y4=y[yIndex2++];rgba[rgbaIndex2]=y3+r;rgba[rgbaIndex2+1]=y3-g;rgba[rgbaIndex2+2]=y3+b;rgba[rgbaIndex2+4]=y4+r;rgba[rgbaIndex2+5]=y4-g;rgba[rgbaIndex2+6]=y4+b;rgbaIndex2+=8}yIndex1+=yNext2Lines;yIndex2+=yNext2Lines;rgbaIndex1+=rgbaNext2Lines;rgbaIndex2+=rgbaNext2Lines;cIndex+=cNextLine}};return CanvasRenderer}();JSMpeg.AudioOutput.WebAudio=function(){"use strict";var WebAudioOut=function(options){this.context=WebAudioOut.CachedContext=WebAudioOut.CachedContext||new(window.AudioContext||window.webkitAudioContext);this.gain=this.context.createGain();this.destination=this.gain;this.gain.connect(this.context.destination);this.context._connections=(this.context._connections||0)+1;this.startTime=0;this.buffer=null;this.wallclockStartTime=0;this.volume=1;this.enabled=true;this.unlocked=!WebAudioOut.NeedsUnlocking();Object.defineProperty(this,"enqueuedTime",{get:this.getEnqueuedTime})};WebAudioOut.prototype.destroy=function(){this.gain.disconnect();this.context._connections--;if(this.context._connections===0){this.context.close();WebAudioOut.CachedContext=null}};WebAudioOut.prototype.play=function(sampleRate,left,right){if(!this.enabled){return}if(!this.unlocked){var ts=JSMpeg.Now();if(this.wallclockStartTimethis.memory.buffer.byteLength){var bytesNeeded=this.brk-this.memory.buffer.byteLength;var pagesNeeded=Math.ceil(bytesNeeded/this.pageSize);this.memory.grow(pagesNeeded);this.createHeapViews()}return previousBrk};WASM.prototype.c_abort=function(size){console.warn("JSMPeg: WASM abort",arguments)};WASM.prototype.c_assertFail=function(size){console.warn("JSMPeg: WASM ___assert_fail",arguments)};WASM.prototype.readDylinkSection=function(buffer){var bytes=new Uint8Array(buffer);var next=0;var readVarUint=function(){var ret=0;var mul=1;while(1){var byte=bytes[next++];ret+=(byte&127)*mul;mul*=128;if(!(byte&128)){return ret}}};var matchNextBytes=function(expected){for(var i=0;i\';}'; 63 | let dataPointView1 = ''; 64 | Adapter.log.info('Stream ' + channels[idx].common.name + ' starting Server on Port ' + Port.val); 65 | let server = http.createServer(function (req, res) { 66 | res.write(dataPointView1); 67 | res.end(); 68 | }).listen(Port.val); 69 | streams[channels[idx].common.name] = {server: server, videoPort: VideoPort}; 70 | 71 | // trigger state change if they were turned on 72 | let streamState = await Adapter.getStateAsync(channels[idx].common.name + '.startStream'); 73 | if(streamState.val){ 74 | await this.setStateAsync(channels[idx].common.name + '.startStream',true,false); 75 | } 76 | } 77 | } 78 | 79 | onUnload(callback){ 80 | const Adapter = this; 81 | try{ 82 | for(let stream in streams){ 83 | streams[stream].server.close(); 84 | } 85 | this.log.info('cleaned everything up...'); 86 | callback(); 87 | }catch(e){ 88 | callback(); 89 | } 90 | } 91 | 92 | /** 93 | * Is called if a subscribed state changes 94 | * @param {string} id 95 | * @param {ioBroker.State | null | undefined} state 96 | */ 97 | async onStateChange(id, state){ 98 | const Adapter = this; 99 | if(!state) return; 100 | Adapter.log.warn("State Change: " + id + " to " + state.val + " ack " + state.ack); 101 | 102 | const currentId = id.substring(Adapter.namespace.length + 1); 103 | const action = currentId.substr(currentId.indexOf('.')+1); 104 | const name = currentId.substr(0,currentId.indexOf('.')); 105 | Adapter.log.debug("Action: "+ action+ ' Name: '+name + "State" + state.val); 106 | if(state && !state.ack){ 107 | if(action === 'startStream' && state.val){ 108 | let resolution = await Adapter.getStateAsync(Adapter.namespace + '.' + name + '.resolution'); 109 | let rtspUrl = await Adapter.getStateAsync(Adapter.namespace + '.' + name + '.rtspUrl'); 110 | let width, height, size = resolution.val.match(/\d+x\d+/); 111 | 112 | if(size != null){ 113 | size = size[0].split('x'); 114 | } 115 | if(size != null){ 116 | width = parseInt(size[0], 10); 117 | height = parseInt(size[1], 10); 118 | } 119 | let ffmpegOptions = {'-stats': '', '-r': 30}; 120 | if(size != null && width != undefined && height != undefined){ 121 | ffmpegOptions['-vf'] = ' scale=w=' + width + ':h=' + height + ':force_original_aspect_ratio=decrease'; 122 | Adapter.log.info("Setting stream Resolution to " + width + " x " + height); 123 | } 124 | let UserFfmpegOptions; 125 | let str = await Adapter.getStateAsync(Adapter.namespace + '.' + name + '.ffmpegOptions'); 126 | try { 127 | UserFfmpegOptions = JSON.parse(str.val); 128 | } 129 | catch(e){ 130 | UserFfmpegOptions = {}; 131 | if(str.val.length > 0){ 132 | Adapter.log.warn("User ffmpeg options rejected, it contains invalid JSON string: " + str.val); 133 | 134 | } 135 | } 136 | ffmpegOptions = {...ffmpegOptions, ...UserFfmpegOptions}; 137 | if(ffmpegOptions['-r'] == undefined || ffmpegOptions['-r'] < 10){ 138 | ffmpegOptions['-r'] = 30; 139 | } 140 | Adapter.log.info("Start stream on " + rtspUrl.val + "with port " + streams[name].videoPort + JSON.stringify(ffmpegOptions)); 141 | streams[name]["stream"] = new Stream({ 142 | name: 'name', 143 | streamUrl: rtspUrl.val, 144 | wsPort: parseInt(streams[name].videoPort), 145 | ffmpegOptions: ffmpegOptions 146 | }); 147 | } 148 | else { 149 | if(streams[name]["stream"] !== undefined){ 150 | streams[name]["stream"].stop(); 151 | } 152 | } 153 | } 154 | } 155 | 156 | async onMessage(msg){ 157 | const Adapter = this; 158 | Adapter.log.debug('Got a Message: ' + msg.command); 159 | if(msg.command === 'getPort'){ 160 | let port = await Adapter.getPortAsync(8082); 161 | Adapter.sendTo(msg.from, msg.command, {port: port}, msg.callback); 162 | } 163 | 164 | if(msg.command === 'addStream'){ 165 | // Add stream 166 | Adapter.createDeviceAsync(msg.message.streamName); 167 | await this.setObjectNotExistsAsync(msg.message.streamName, { 168 | type: "channel", 169 | common: { 170 | name: msg.message.streamName, 171 | write: false 172 | }, 173 | native: {} 174 | }); 175 | await this.setObjectNotExistsAsync(msg.message.streamName + '.rtspUrl', { 176 | type: "state", 177 | common: { 178 | name: 'Rtsp Url', 179 | desc: 'Url of rtsp Stream', 180 | type: 'string', 181 | role: 'text.url', 182 | write: false 183 | }, 184 | native: {} 185 | }); 186 | Adapter.setStateAsync(msg.message.streamName + '.rtspUrl', msg.message.rtspUrl, true); 187 | await this.setObjectNotExistsAsync(msg.message.streamName + '.port', { 188 | type: "state", 189 | common: { 190 | name: 'Port', 191 | desc: 'Port to access the stream', 192 | type: 'number', 193 | role: 'info.port', 194 | write: false 195 | }, 196 | native: {} 197 | }); 198 | Adapter.setStateAsync(msg.message.streamName + '.port', msg.message.port, true); 199 | await this.setObjectNotExistsAsync(msg.message.streamName + '.resolution', { 200 | type: "state", 201 | common: { 202 | name: 'Resolution', 203 | desc: 'Resolusion (width x height)', 204 | type: 'string', 205 | role: 'text', 206 | write: false 207 | }, 208 | native: {} 209 | }); 210 | Adapter.setStateAsync(msg.message.streamName + '.resolution', msg.message.resolution, true); 211 | await this.setObjectNotExistsAsync(msg.message.streamName + '.tcpPort', { 212 | type: "state", 213 | common: { 214 | name: 'Tcp Port', 215 | desc: 'TCP Port of the Camera', 216 | type: 'number', 217 | role: 'info.port', 218 | write: false 219 | }, 220 | native: {} 221 | }); 222 | Adapter.setStateAsync(msg.message.streamName + '.tcpPort', msg.message.tcpPort, true); 223 | await this.setObjectNotExistsAsync(msg.message.streamName + '.ffmpegOptions', { 224 | type: "state", 225 | common: { 226 | name: 'Tcp Port', 227 | desc: 'TCP Port of the Camera', 228 | type: 'text', 229 | role: 'text', 230 | write: false 231 | }, 232 | native: {} 233 | }); 234 | Adapter.setStateAsync(msg.message.streamName + '.ffmpegOptions', msg.message.ffmpegOptions, true); 235 | await this.setObjectNotExistsAsync(msg.message.streamName + '.startStream', { 236 | type: "state", 237 | common: { 238 | name: 'startStream', 239 | desc: 'Push to start this stream', 240 | type: 'boolean', 241 | role: 'button', 242 | write: true 243 | }, 244 | native: {} 245 | }); 246 | Adapter.setStateAsync(msg.message.streamName + '.startStream', false, true); 247 | Adapter.sendTo(msg.from, msg.command, {success: true}, msg.callback); 248 | } 249 | if(msg.command === 'getStreams'){ 250 | let streams = {}; 251 | let channels = await Adapter.getChannelsOfAsync(''); 252 | for(let idx = 0; idx < channels.length; idx++){ 253 | streams[channels[idx].common.name] = await Adapter.getStatesAsync(channels[idx].common.name + '.*'); 254 | } 255 | 256 | Adapter.sendTo(msg.from, msg.command, {streams: streams}, msg.callback); 257 | } 258 | if(msg.command === 'deleteStream'){ 259 | Adapter.deleteChannelAsync('',msg.message.device); 260 | Adapter.sendTo(msg.from, msg.command, {success: true}, msg.callback); 261 | } 262 | } 263 | } 264 | 265 | // @ts-ignore parent is a valid property on module 266 | if(module.parent){ 267 | // Export the constructor in compact mode 268 | module.exports = (options) => new rtspstream(options); 269 | } 270 | else{ 271 | // otherwise start the instance directly 272 | new rtspstream(); 273 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iobroker.rtspstream", 3 | "version": "0.0.4", 4 | "description": "Adapter to get rtsp-Streams", 5 | "author": { 6 | "name": "Dominic Blattmann", 7 | "email": "nick@dbweb.ch" 8 | }, 9 | "homepage": "https://github.com/dbweb-ch/ioBroker.rtspstream", 10 | "license": "MIT", 11 | "keywords": [ 12 | "ioBroker", 13 | "rtsp", 14 | "camera", 15 | "Smart Home", 16 | "home automation" 17 | ], 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/dbweb-ch/ioBroker.rtspstream" 21 | }, 22 | "dependencies": { 23 | "@iobroker/adapter-core": "^1.0.3", 24 | "node-rtsp-stream": "^0.0.8", 25 | "command-exists": "^1.2.8" 26 | }, 27 | "devDependencies": { 28 | "gulp": "^4.0.0", 29 | "mocha": "^6.0.2", 30 | "chai": "^4.1.2" 31 | }, 32 | "main": "main.js", 33 | "scripts": { 34 | "test": "node node_modules/mocha/bin/mocha --exit", 35 | "lint": "eslint" 36 | }, 37 | "bugs": { 38 | "url": "https://github.com/dbweb-ch/iobroker.rtspstream/issues" 39 | }, 40 | "readmeFilename": "README.md" 41 | } 42 | -------------------------------------------------------------------------------- /test/lib/setup.js: -------------------------------------------------------------------------------- 1 | /* jshint -W097 */// jshint strict:false 2 | /*jslint node: true */ 3 | // check if tmp directory exists 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | var child_process = require('child_process'); 7 | var rootDir = path.normalize(__dirname + '/../../'); 8 | var pkg = require(rootDir + 'package.json'); 9 | var debug = typeof v8debug === 'object'; 10 | pkg.main = pkg.main || 'main.js'; 11 | 12 | var adapterName = path.normalize(rootDir).replace(/\\/g, '/').split('/'); 13 | adapterName = adapterName[adapterName.length - 2]; 14 | var adapterStarted = false; 15 | 16 | function getAppName() { 17 | var parts = __dirname.replace(/\\/g, '/').split('/'); 18 | return parts[parts.length - 3].split('.')[0]; 19 | } 20 | 21 | var appName = getAppName().toLowerCase(); 22 | 23 | var objects; 24 | var states; 25 | 26 | var pid = null; 27 | 28 | function copyFileSync(source, target) { 29 | 30 | var targetFile = target; 31 | 32 | //if target is a directory a new file with the same name will be created 33 | if (fs.existsSync(target)) { 34 | if ( fs.lstatSync( target ).isDirectory() ) { 35 | targetFile = path.join(target, path.basename(source)); 36 | } 37 | } 38 | 39 | try{ 40 | fs.writeFileSync(targetFile, fs.readFileSync(source)); 41 | } 42 | catch(err){ 43 | console.log("file copy error: " + source + " -> " + targetFile + " (error ignored)"); 44 | } 45 | } 46 | 47 | function copyFolderRecursiveSync(source, target, ignore) { 48 | var files = []; 49 | 50 | var base = path.basename(source); 51 | if (base === adapterName) { 52 | base = pkg.name; 53 | } 54 | //check if folder needs to be created or integrated 55 | var targetFolder = path.join(target, base); 56 | if (!fs.existsSync(targetFolder)) { 57 | fs.mkdirSync(targetFolder); 58 | } 59 | 60 | //copy 61 | if (fs.lstatSync(source).isDirectory()) { 62 | files = fs.readdirSync(source); 63 | files.forEach(function (file) { 64 | if (ignore && ignore.indexOf(file) !== -1) { 65 | return; 66 | } 67 | 68 | var curSource = path.join(source, file); 69 | var curTarget = path.join(targetFolder, file); 70 | if (fs.lstatSync(curSource).isDirectory()) { 71 | // ignore grunt files 72 | if (file.indexOf('grunt') !== -1) return; 73 | if (file === 'chai') return; 74 | if (file === 'mocha') return; 75 | copyFolderRecursiveSync(curSource, targetFolder, ignore); 76 | } else { 77 | copyFileSync(curSource, curTarget); 78 | } 79 | }); 80 | } 81 | } 82 | 83 | if (!fs.existsSync(rootDir + 'tmp')) { 84 | fs.mkdirSync(rootDir + 'tmp'); 85 | } 86 | 87 | function storeOriginalFiles() { 88 | console.log('Store original files...'); 89 | var dataDir = rootDir + 'tmp/' + appName + '-data/'; 90 | 91 | var f = fs.readFileSync(dataDir + 'objects.json'); 92 | var objects = JSON.parse(f.toString()); 93 | if (objects['system.adapter.admin.0'] && objects['system.adapter.admin.0'].common) { 94 | objects['system.adapter.admin.0'].common.enabled = false; 95 | } 96 | if (objects['system.adapter.admin.1'] && objects['system.adapter.admin.1'].common) { 97 | objects['system.adapter.admin.1'].common.enabled = false; 98 | } 99 | 100 | fs.writeFileSync(dataDir + 'objects.json.original', JSON.stringify(objects)); 101 | try{ 102 | f = fs.readFileSync(dataDir + 'states.json'); 103 | fs.writeFileSync(dataDir + 'states.json.original', f); 104 | } 105 | catch(err){ 106 | console.log('no states.json found - ignore'); 107 | } 108 | } 109 | 110 | function restoreOriginalFiles() { 111 | console.log('restoreOriginalFiles...'); 112 | var dataDir = rootDir + 'tmp/' + appName + '-data/'; 113 | 114 | var f = fs.readFileSync(dataDir + 'objects.json.original'); 115 | fs.writeFileSync(dataDir + 'objects.json', f); 116 | try{ 117 | f = fs.readFileSync(dataDir + 'states.json.original'); 118 | fs.writeFileSync(dataDir + 'states.json', f); 119 | } 120 | catch(err){ 121 | console.log('no states.json.original found - ignore'); 122 | } 123 | 124 | } 125 | 126 | function checkIsAdapterInstalled(cb, counter, customName){ 127 | customName = customName || pkg.name.split('.').pop(); 128 | counter = counter || 0; 129 | var dataDir = rootDir + 'tmp/' + appName + '-data/'; 130 | console.log('checkIsAdapterInstalled...'); 131 | 132 | try { 133 | var f = fs.readFileSync(dataDir + 'objects.json'); 134 | var objects = JSON.parse(f.toString()); 135 | if(objects['system.adapter.' + customName + '.0']){ 136 | console.log('checkIsAdapterInstalled: ready!'); 137 | setTimeout(function () { 138 | if (cb) cb(); 139 | }, 100); 140 | return; 141 | } else { 142 | console.warn('checkIsAdapterInstalled: still not ready'); 143 | } 144 | } catch (err) { 145 | 146 | } 147 | 148 | if (counter > 20) { 149 | console.error('checkIsAdapterInstalled: Cannot install!'); 150 | if (cb) cb('Cannot install'); 151 | } else { 152 | console.log('checkIsAdapterInstalled: wait...'); 153 | setTimeout(function() { 154 | checkIsAdapterInstalled(cb, counter + 1); 155 | }, 1000); 156 | } 157 | } 158 | 159 | function checkIsControllerInstalled(cb, counter) { 160 | counter = counter || 0; 161 | var dataDir = rootDir + 'tmp/' + appName + '-data/'; 162 | 163 | console.log('checkIsControllerInstalled...'); 164 | try { 165 | var f = fs.readFileSync(dataDir + 'objects.json'); 166 | var objects = JSON.parse(f.toString()); 167 | if(objects['system.certificates']){ 168 | console.log('checkIsControllerInstalled: installed!'); 169 | setTimeout(function () { 170 | if (cb) cb(); 171 | }, 100); 172 | return; 173 | } 174 | } catch (err) { 175 | 176 | } 177 | 178 | if (counter > 20) { 179 | console.log('checkIsControllerInstalled: Cannot install!'); 180 | if (cb) cb('Cannot install'); 181 | } else { 182 | console.log('checkIsControllerInstalled: wait...'); 183 | setTimeout(function() { 184 | checkIsControllerInstalled(cb, counter + 1); 185 | }, 1000); 186 | } 187 | } 188 | 189 | function installAdapter(customName, cb){ 190 | if(typeof customName === 'function'){ 191 | cb = customName; 192 | customName = null; 193 | } 194 | customName = customName || pkg.name.split('.').pop(); 195 | console.log('Install adapter...'); 196 | var startFile = 'node_modules/' + appName + '.js-controller/' + appName + '.js'; 197 | // make first install 198 | if (debug) { 199 | child_process.execSync('node ' + startFile + ' add ' + customName + ' --enabled false', { 200 | cwd: rootDir + 'tmp', 201 | stdio: [0, 1, 2] 202 | }); 203 | checkIsAdapterInstalled(function (error) { 204 | if (error) console.error(error); 205 | console.log('Adapter installed.'); 206 | if (cb) cb(); 207 | }); 208 | } else { 209 | // add controller 210 | var _pid = child_process.fork(startFile, ['add', customName, '--enabled', 'false'], { 211 | cwd: rootDir + 'tmp', 212 | stdio: [0, 1, 2, 'ipc'] 213 | }); 214 | 215 | waitForEnd(_pid, function () { 216 | checkIsAdapterInstalled(function (error) { 217 | if (error) console.error(error); 218 | console.log('Adapter installed.'); 219 | if (cb) cb(); 220 | }); 221 | }); 222 | } 223 | } 224 | 225 | function waitForEnd(_pid, cb) { 226 | if (!_pid) { 227 | cb(-1, -1); 228 | return; 229 | } 230 | _pid.on('exit', function (code, signal) { 231 | if (_pid) { 232 | _pid = null; 233 | cb(code, signal); 234 | } 235 | }); 236 | _pid.on('close', function (code, signal) { 237 | if (_pid) { 238 | _pid = null; 239 | cb(code, signal); 240 | } 241 | }); 242 | } 243 | 244 | function installJsController(cb) { 245 | console.log('installJsController...'); 246 | if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller') || 247 | !fs.existsSync(rootDir + 'tmp/' + appName + '-data')) { 248 | // try to detect appName.js-controller in node_modules/appName.js-controller 249 | // travis CI installs js-controller into node_modules 250 | if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller')) { 251 | console.log('installJsController: no js-controller => copy it from "' + rootDir + 'node_modules/' + appName + '.js-controller"'); 252 | // copy all 253 | // stop controller 254 | console.log('Stop controller if running...'); 255 | var _pid; 256 | if (debug) { 257 | // start controller 258 | _pid = child_process.exec('node ' + appName + '.js stop', { 259 | cwd: rootDir + 'node_modules/' + appName + '.js-controller', 260 | stdio: [0, 1, 2] 261 | }); 262 | } else { 263 | _pid = child_process.fork(appName + '.js', ['stop'], { 264 | cwd: rootDir + 'node_modules/' + appName + '.js-controller', 265 | stdio: [0, 1, 2, 'ipc'] 266 | }); 267 | } 268 | 269 | waitForEnd(_pid, function () { 270 | // copy all files into 271 | if (!fs.existsSync(rootDir + 'tmp')) fs.mkdirSync(rootDir + 'tmp'); 272 | if (!fs.existsSync(rootDir + 'tmp/node_modules')) fs.mkdirSync(rootDir + 'tmp/node_modules'); 273 | 274 | if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')){ 275 | console.log('Copy js-controller...'); 276 | copyFolderRecursiveSync(rootDir + 'node_modules/' + appName + '.js-controller', rootDir + 'tmp/node_modules/'); 277 | } 278 | 279 | console.log('Setup js-controller...'); 280 | var __pid; 281 | if (debug) { 282 | // start controller 283 | _pid = child_process.exec('node ' + appName + '.js setup first --console', { 284 | cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', 285 | stdio: [0, 1, 2] 286 | }); 287 | } else { 288 | __pid = child_process.fork(appName + '.js', ['setup', 'first', '--console'], { 289 | cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', 290 | stdio: [0, 1, 2, 'ipc'] 291 | }); 292 | } 293 | waitForEnd(__pid, function () { 294 | checkIsControllerInstalled(function () { 295 | // change ports for object and state DBs 296 | var config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json'); 297 | config.objects.port = 19001; 298 | config.states.port = 19000; 299 | fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2)); 300 | console.log('Setup finished.'); 301 | 302 | copyAdapterToController(); 303 | 304 | installAdapter(function () { 305 | storeOriginalFiles(); 306 | if (cb) cb(true); 307 | }); 308 | }); 309 | }); 310 | }); 311 | } else { 312 | // check if port 9000 is free, else admin adapter will be added to running instance 313 | var client = new require('net').Socket(); 314 | client.on('error', () => { 315 | }); 316 | client.connect(9000, '127.0.0.1', function() { 317 | console.error('Cannot initiate fisrt run of test, because one instance of application is running on this PC. Stop it and repeat.'); 318 | process.exit(0); 319 | }); 320 | 321 | setTimeout(function () { 322 | client.destroy(); 323 | if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')) { 324 | console.log('installJsController: no js-controller => install from git'); 325 | 326 | child_process.execSync('npm install https://github.com/' + appName + '/' + appName + '.js-controller/tarball/master --prefix ./ --production', { 327 | cwd: rootDir + 'tmp/', 328 | stdio: [0, 1, 2] 329 | }); 330 | } else { 331 | console.log('Setup js-controller...'); 332 | var __pid; 333 | if (debug) { 334 | // start controller 335 | child_process.exec('node ' + appName + '.js setup first', { 336 | cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', 337 | stdio: [0, 1, 2] 338 | }); 339 | } else { 340 | child_process.fork(appName + '.js', ['setup', 'first'], { 341 | cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', 342 | stdio: [0, 1, 2, 'ipc'] 343 | }); 344 | } 345 | } 346 | 347 | // let npm install admin and run setup 348 | checkIsControllerInstalled(function () { 349 | var _pid; 350 | 351 | if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller/' + appName + '.js')) { 352 | _pid = child_process.fork(appName + '.js', ['stop'], { 353 | cwd: rootDir + 'node_modules/' + appName + '.js-controller', 354 | stdio: [0, 1, 2, 'ipc'] 355 | }); 356 | } 357 | 358 | waitForEnd(_pid, function () { 359 | // change ports for object and state DBs 360 | var config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json'); 361 | config.objects.port = 19001; 362 | config.states.port = 19000; 363 | fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2)); 364 | 365 | copyAdapterToController(); 366 | 367 | installAdapter(function () { 368 | storeOriginalFiles(); 369 | if (cb) cb(true); 370 | }); 371 | }); 372 | }); 373 | }, 1000); 374 | } 375 | } else { 376 | setTimeout(function () { 377 | console.log('installJsController: js-controller installed'); 378 | if (cb) cb(false); 379 | }, 0); 380 | } 381 | } 382 | 383 | function copyAdapterToController() { 384 | console.log('Copy adapter...'); 385 | // Copy adapter to tmp/node_modules/appName.adapter 386 | copyFolderRecursiveSync(rootDir, rootDir + 'tmp/node_modules/', ['.idea', 'test', 'tmp', '.git', appName + '.js-controller']); 387 | console.log('Adapter copied.'); 388 | } 389 | 390 | function clearControllerLog() { 391 | var dirPath = rootDir + 'tmp/log'; 392 | var files; 393 | try { 394 | if (fs.existsSync(dirPath)) { 395 | console.log('Clear controller log...'); 396 | files = fs.readdirSync(dirPath); 397 | } else { 398 | console.log('Create controller log directory...'); 399 | files = []; 400 | fs.mkdirSync(dirPath); 401 | } 402 | } catch(e) { 403 | console.error('Cannot read "' + dirPath + '"'); 404 | return; 405 | } 406 | if (files.length > 0) { 407 | try { 408 | for (var i = 0; i < files.length; i++) { 409 | var filePath = dirPath + '/' + files[i]; 410 | fs.unlinkSync(filePath); 411 | } 412 | console.log('Controller log cleared'); 413 | } catch (err) { 414 | console.error('cannot clear log: ' + err); 415 | } 416 | } 417 | } 418 | 419 | function clearDB() { 420 | var dirPath = rootDir + 'tmp/iobroker-data/sqlite'; 421 | var files; 422 | try { 423 | if (fs.existsSync(dirPath)) { 424 | console.log('Clear sqlite DB...'); 425 | files = fs.readdirSync(dirPath); 426 | } else { 427 | console.log('Create controller log directory...'); 428 | files = []; 429 | fs.mkdirSync(dirPath); 430 | } 431 | } catch(e) { 432 | console.error('Cannot read "' + dirPath + '"'); 433 | return; 434 | } 435 | if (files.length > 0) { 436 | try { 437 | for (var i = 0; i < files.length; i++) { 438 | var filePath = dirPath + '/' + files[i]; 439 | fs.unlinkSync(filePath); 440 | } 441 | console.log('Clear sqlite DB'); 442 | } catch (err) { 443 | console.error('cannot clear DB: ' + err); 444 | } 445 | } 446 | } 447 | 448 | function setupController(cb) { 449 | installJsController(function (isInited) { 450 | clearControllerLog(); 451 | clearDB(); 452 | 453 | if (!isInited) { 454 | restoreOriginalFiles(); 455 | copyAdapterToController(); 456 | } 457 | // read system.config object 458 | var dataDir = rootDir + 'tmp/' + appName + '-data/'; 459 | 460 | var objs; 461 | try{ 462 | objs = fs.readFileSync(dataDir + 'objects.json'); 463 | objs = JSON.parse(objs); 464 | } 465 | catch(e){ 466 | console.log('ERROR reading/parsing system configuration. Ignore'); 467 | objs = {'system.config': {}}; 468 | } 469 | if(!objs || !objs['system.config']){ 470 | objs = {'system.config': {}}; 471 | } 472 | 473 | if(cb) cb(objs['system.config']); 474 | }); 475 | } 476 | 477 | function startAdapter(objects, states, callback) { 478 | if(adapterStarted){ 479 | console.log('Adapter already started ...'); 480 | if(callback) callback(objects, states); 481 | return; 482 | } 483 | adapterStarted = true; 484 | console.log('startAdapter...'); 485 | if (fs.existsSync(rootDir + 'tmp/node_modules/' + pkg.name + '/' + pkg.main)) { 486 | try { 487 | if (debug) { 488 | // start controller 489 | pid = child_process.exec('node node_modules/' + pkg.name + '/' + pkg.main + ' --console silly', { 490 | cwd: rootDir + 'tmp', 491 | stdio: [0, 1, 2] 492 | }); 493 | } else { 494 | // start controller 495 | pid = child_process.fork('node_modules/' + pkg.name + '/' + pkg.main, ['--console', 'silly'], { 496 | cwd: rootDir + 'tmp', 497 | stdio: [0, 1, 2, 'ipc'] 498 | }); 499 | } 500 | } catch (error) { 501 | console.error(JSON.stringify(error)); 502 | } 503 | } else { 504 | console.error('Cannot find: ' + rootDir + 'tmp/node_modules/' + pkg.name + '/' + pkg.main); 505 | } 506 | if (callback) callback(objects, states); 507 | } 508 | 509 | function startController(isStartAdapter, onObjectChange, onStateChange, callback) { 510 | if (typeof isStartAdapter === 'function') { 511 | callback = onStateChange; 512 | onStateChange = onObjectChange; 513 | onObjectChange = isStartAdapter; 514 | isStartAdapter = true; 515 | } 516 | 517 | if (onStateChange === undefined) { 518 | callback = onObjectChange; 519 | onObjectChange = undefined; 520 | } 521 | 522 | if (pid) { 523 | console.error('Controller is already started!'); 524 | } else { 525 | console.log('startController...'); 526 | adapterStarted = false; 527 | var isObjectConnected; 528 | var isStatesConnected; 529 | 530 | var Objects = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/objects/objectsInMemServer'); 531 | objects = new Objects({ 532 | connection: { 533 | "type" : "file", 534 | "host" : "127.0.0.1", 535 | "port" : 19001, 536 | "user" : "", 537 | "pass" : "", 538 | "noFileCache": false, 539 | "connectTimeout": 2000 540 | }, 541 | logger: { 542 | silly: function(msg){ 543 | console.log(msg); 544 | }, 545 | debug: function (msg) { 546 | console.log(msg); 547 | }, 548 | info: function (msg) { 549 | console.log(msg); 550 | }, 551 | warn: function (msg) { 552 | console.warn(msg); 553 | }, 554 | error: function (msg) { 555 | console.error(msg); 556 | } 557 | }, 558 | connected: function () { 559 | isObjectConnected = true; 560 | if (isStatesConnected) { 561 | console.log('startController: started!'); 562 | if (isStartAdapter) { 563 | startAdapter(objects, states, callback); 564 | } else { 565 | if(callback){ 566 | callback(objects, states); 567 | callback = null; 568 | } 569 | } 570 | } 571 | }, 572 | change: onObjectChange 573 | }); 574 | 575 | // Just open in memory DB itself 576 | var States = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/states/statesInMemServer'); 577 | states = new States({ 578 | connection: { 579 | type: 'file', 580 | host: '127.0.0.1', 581 | port: 19000, 582 | options: { 583 | auth_pass: null, 584 | retry_max_delay: 15000 585 | } 586 | }, 587 | logger: { 588 | silly: function(msg){ 589 | console.log(msg); 590 | }, 591 | debug: function (msg) { 592 | console.log(msg); 593 | }, 594 | info: function (msg) { 595 | console.log(msg); 596 | }, 597 | warn: function (msg) { 598 | console.log(msg); 599 | }, 600 | error: function (msg) { 601 | console.log(msg); 602 | } 603 | }, 604 | connected: function () { 605 | isStatesConnected = true; 606 | if (isObjectConnected) { 607 | console.log('startController: started!!'); 608 | if(isStartAdapter){ 609 | startAdapter(objects, states, callback); 610 | } 611 | else{ 612 | if(callback){ 613 | callback(objects, states); 614 | callback = null; 615 | } 616 | } 617 | } 618 | }, 619 | change: onStateChange 620 | }); 621 | } 622 | } 623 | 624 | function stopAdapter(cb) { 625 | if (!pid) { 626 | console.error('Controller is not running!'); 627 | if (cb) { 628 | setTimeout(function () { 629 | cb(false); 630 | }, 0); 631 | } 632 | } else { 633 | adapterStarted = false; 634 | pid.on('exit', function (code, signal) { 635 | if (pid) { 636 | console.log('child process terminated due to receipt of signal ' + signal); 637 | if (cb) cb(); 638 | pid = null; 639 | } 640 | }); 641 | 642 | pid.on('close', function (code, signal) { 643 | if (pid) { 644 | if (cb) cb(); 645 | pid = null; 646 | } 647 | }); 648 | 649 | pid.kill('SIGTERM'); 650 | } 651 | } 652 | 653 | function _stopController() { 654 | if (objects) { 655 | objects.destroy(); 656 | objects = null; 657 | } 658 | if (states) { 659 | states.destroy(); 660 | states = null; 661 | } 662 | } 663 | 664 | function stopController(cb) { 665 | var timeout; 666 | if (objects) { 667 | console.log('Set system.adapter.' + pkg.name + '.0'); 668 | objects.setObject('system.adapter.' + pkg.name + '.0', { 669 | common:{ 670 | enabled: false 671 | } 672 | }); 673 | } 674 | 675 | stopAdapter(function () { 676 | if (timeout) { 677 | clearTimeout(timeout); 678 | timeout = null; 679 | } 680 | 681 | _stopController(); 682 | 683 | if (cb) { 684 | cb(true); 685 | cb = null; 686 | } 687 | }); 688 | 689 | timeout = setTimeout(function () { 690 | timeout = null; 691 | console.log('child process NOT terminated'); 692 | 693 | _stopController(); 694 | 695 | if (cb) { 696 | cb(false); 697 | cb = null; 698 | } 699 | pid = null; 700 | }, 5000); 701 | } 702 | 703 | // Setup the adapter 704 | function setAdapterConfig(common, native, instance) { 705 | var objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString()); 706 | var id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0); 707 | if (common) objects[id].common = common; 708 | if (native) objects[id].native = native; 709 | fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/objects.json', JSON.stringify(objects)); 710 | } 711 | 712 | // Read config of the adapter 713 | function getAdapterConfig(instance) { 714 | var objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString()); 715 | var id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0); 716 | return objects[id]; 717 | } 718 | 719 | if (typeof module !== undefined && module.parent) { 720 | module.exports.getAdapterConfig = getAdapterConfig; 721 | module.exports.setAdapterConfig = setAdapterConfig; 722 | module.exports.startController = startController; 723 | module.exports.stopController = stopController; 724 | module.exports.setupController = setupController; 725 | module.exports.stopAdapter = stopAdapter; 726 | module.exports.startAdapter = startAdapter; 727 | module.exports.installAdapter = installAdapter; 728 | module.exports.appName = appName; 729 | module.exports.adapterName = adapterName; 730 | module.exports.adapterStarted = adapterStarted; 731 | } 732 | -------------------------------------------------------------------------------- /test/testAdapter.js: -------------------------------------------------------------------------------- 1 | /* jshint -W097 */// jshint strict:false 2 | /*jslint node: true */ 3 | var expect = require('chai').expect; 4 | var setup = require(__dirname + '/lib/setup'); 5 | 6 | var objects = null; 7 | var states = null; 8 | var onStateChanged = null; 9 | var onObjectChanged = null; 10 | var sendToID = 1; 11 | 12 | var adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf('.') + 1); 13 | var runningMode = require(__dirname + '/../io-package.json').common.mode; 14 | 15 | function checkConnectionOfAdapter(cb, counter) { 16 | counter = counter || 0; 17 | console.log('Try check #' + counter); 18 | if (counter > 30) { 19 | if (cb) cb('Cannot check connection'); 20 | return; 21 | } 22 | 23 | states.getState('system.adapter.' + adapterShortName + '.0.alive', function (err, state) { 24 | if (err) console.error(err); 25 | if (state && state.val) { 26 | if (cb) cb(); 27 | } else { 28 | setTimeout(function () { 29 | checkConnectionOfAdapter(cb, counter + 1); 30 | }, 1000); 31 | } 32 | }); 33 | } 34 | 35 | function checkValueOfState(id, value, cb, counter) { 36 | counter = counter || 0; 37 | if (counter > 20) { 38 | if (cb) cb('Cannot check value Of State ' + id); 39 | return; 40 | } 41 | 42 | states.getState(id, function (err, state) { 43 | if (err) console.error(err); 44 | if (value === null && !state) { 45 | if (cb) cb(); 46 | } else 47 | if (state && (value === undefined || state.val === value)) { 48 | if (cb) cb(); 49 | } else { 50 | setTimeout(function () { 51 | checkValueOfState(id, value, cb, counter + 1); 52 | }, 500); 53 | } 54 | }); 55 | } 56 | 57 | function sendTo(target, command, message, callback) { 58 | onStateChanged = function (id, state) { 59 | if (id === 'messagebox.system.adapter.test.0') { 60 | callback(state.message); 61 | } 62 | }; 63 | 64 | states.pushMessage('system.adapter.' + target, { 65 | command: command, 66 | message: message, 67 | from: 'system.adapter.test.0', 68 | callback: { 69 | message: message, 70 | id: sendToID++, 71 | ack: false, 72 | time: (new Date()).getTime() 73 | } 74 | }); 75 | } 76 | 77 | describe('Test ' + adapterShortName + ' adapter', function() { 78 | before('Test ' + adapterShortName + ' adapter: Start js-controller', function (_done) { 79 | this.timeout(600000); // because of first install from npm 80 | 81 | setup.setupController(function () { 82 | var config = setup.getAdapterConfig(); 83 | // enable adapter 84 | config.common.enabled = true; 85 | config.common.loglevel = 'debug'; 86 | 87 | //config.native.dbtype = 'sqlite'; 88 | 89 | setup.setAdapterConfig(config.common, config.native); 90 | 91 | setup.startController(true, function(id, obj) {}, function (id, state) { 92 | if (onStateChanged) onStateChanged(id, state); 93 | }, 94 | function (_objects, _states) { 95 | objects = _objects; 96 | states = _states; 97 | _done(); 98 | }); 99 | }); 100 | }); 101 | 102 | it('Test ' + adapterShortName + ' instance object: it must exists', function (done) { 103 | objects.getObject('system.adapter.' + adapterShortName + '.0', function (err, obj) { 104 | expect(err).to.be.null; 105 | expect(obj).to.be.an('object'); 106 | expect(obj).not.to.be.null; 107 | done(); 108 | }); 109 | }); 110 | 111 | it('Test ' + adapterShortName + ' adapter: Check if adapter started', function (done) { 112 | this.timeout(60000); 113 | checkConnectionOfAdapter(function (res) { 114 | if (res) console.log(res); 115 | if (runningMode === 'daemon') { 116 | expect(res).not.to.be.equal('Cannot check connection'); 117 | } else { 118 | //?? 119 | } 120 | done(); 121 | }); 122 | }); 123 | /**/ 124 | 125 | /* 126 | PUT YOUR OWN TESTS HERE USING 127 | it('Testname', function ( done) { 128 | ... 129 | }); 130 | 131 | You can also use "sendTo" method to send messages to the started adapter 132 | */ 133 | 134 | after('Test ' + adapterShortName + ' adapter: Stop js-controller', function (done) { 135 | this.timeout(10000); 136 | 137 | setup.stopController(function (normalTerminated) { 138 | console.log('Adapter normal terminated: ' + normalTerminated); 139 | done(); 140 | }); 141 | }); 142 | }); -------------------------------------------------------------------------------- /test/testPackageFiles.js: -------------------------------------------------------------------------------- 1 | /* jshint -W097 */ 2 | /* jshint strict:false */ 3 | /* jslint node: true */ 4 | /* jshint expr: true */ 5 | 'use strict'; 6 | 7 | const expect = require('chai').expect; 8 | const fs = require('fs'); 9 | 10 | describe('Test package.json and io-package.json', () => { 11 | it('Test package files', done => { 12 | console.log(); 13 | 14 | const fileContentIOPackage = fs.readFileSync(__dirname + '/../io-package.json', 'utf8'); 15 | const ioPackage = JSON.parse(fileContentIOPackage); 16 | 17 | const fileContentNPMPackage = fs.readFileSync(__dirname + '/../package.json', 'utf8'); 18 | const npmPackage = JSON.parse(fileContentNPMPackage); 19 | 20 | expect(ioPackage).to.be.an('object'); 21 | expect(npmPackage).to.be.an('object'); 22 | 23 | expect(ioPackage.common.version, 'ERROR: Version number in io-package.json needs to exist').to.exist; 24 | expect(npmPackage.version, 'ERROR: Version number in package.json needs to exist').to.exist; 25 | 26 | expect(ioPackage.common.version, 'ERROR: Version numbers in package.json and io-package.json needs to match').to.be.equal(npmPackage.version); 27 | 28 | if(!ioPackage.common.news || !ioPackage.common.news[ioPackage.common.version]){ 29 | console.log('WARNING: No news entry for current version exists in io-package.json, no rollback in Admin possible!'); 30 | console.log(); 31 | } 32 | 33 | expect(npmPackage.author, 'ERROR: Author in package.json needs to exist').to.exist; 34 | expect(ioPackage.common.authors, 'ERROR: Authors in io-package.json needs to exist').to.exist; 35 | 36 | expect(ioPackage.common.license, 'ERROR: License missing in io-package in common.license').to.exist; 37 | 38 | if(ioPackage.common.name.indexOf('template') !== 0){ 39 | if(Array.isArray(ioPackage.common.authors)){ 40 | expect(ioPackage.common.authors.length, 'ERROR: Author in io-package.json needs to be set').to.not.be.equal(0); 41 | if(ioPackage.common.authors.length === 1){ 42 | expect(ioPackage.common.authors[0], 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name '); 43 | } 44 | } 45 | else{ 46 | expect(ioPackage.common.authors, 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name '); 47 | } 48 | } 49 | else{ 50 | console.log('WARNING: Testing for set authors field in io-package skipped because template adapter'); 51 | console.log(); 52 | } 53 | expect(fs.existsSync(__dirname + '/../README.md'), 'ERROR: README.md needs to exist! Please create one with description, detail information and changelog. English is mandatory.').to.be.true; 54 | if(!ioPackage.common.titleLang || typeof ioPackage.common.titleLang !== 'object'){ 55 | console.log('WARNING: titleLang is not existing in io-package.json. Please add'); 56 | console.log(); 57 | } 58 | if( 59 | ioPackage.common.title.indexOf('iobroker') !== -1 || 60 | ioPackage.common.title.indexOf('ioBroker') !== -1 || 61 | ioPackage.common.title.indexOf('adapter') !== -1 || 62 | ioPackage.common.title.indexOf('Adapter') !== -1 63 | ){ 64 | console.log('WARNING: title contains Adapter or ioBroker. It is clear anyway, that it is adapter for ioBroker.'); 65 | console.log(); 66 | } 67 | 68 | if(!ioPackage.common.controller && !ioPackage.common.onlyWWW && !ioPackage.common.noConfig){ 69 | if(!ioPackage.common.materialize || !fs.existsSync(__dirname + '/../admin/index_m.html') || !fs.existsSync(__dirname + '/../gulpfile.js')){ 70 | console.log('WARNING: Admin3 support is missing! Please add it'); 71 | console.log(); 72 | } 73 | if(ioPackage.common.materialize){ 74 | expect(fs.existsSync(__dirname + '/../admin/index_m.html'), 'Admin3 support is enabled in io-package.json, but index_m.html is missing!').to.be.true; 75 | } 76 | } 77 | 78 | const licenseFileExists = fs.existsSync(__dirname + '/../LICENSE'); 79 | const fileContentReadme = fs.readFileSync(__dirname + '/../README.md', 'utf8'); 80 | if(fileContentReadme.indexOf('## Changelog') === -1){ 81 | console.log('Warning: The README.md should have a section ## Changelog'); 82 | console.log(); 83 | } 84 | expect((licenseFileExists || fileContentReadme.indexOf('## License') !== -1), 'A LICENSE must exist as LICENSE file or as part of the README.md').to.be.true; 85 | if(!licenseFileExists){ 86 | console.log('Warning: The License should also exist as LICENSE file'); 87 | console.log(); 88 | } 89 | if(fileContentReadme.indexOf('## License') === -1){ 90 | console.log('Warning: The README.md should also have a section ## License to be shown in Admin3'); 91 | console.log(); 92 | } 93 | done(); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | // do not compile anything, this file is just to configure type checking 5 | "noEmit": true, 6 | 7 | // check JS files 8 | "allowJs": true, 9 | "checkJs": true, 10 | 11 | "module": "commonjs", 12 | "moduleResolution": "node", 13 | // this is necessary for the automatic typing of the adapter config 14 | "resolveJsonModule": true, 15 | 16 | // Set this to false if you want to disable the very strict rules (not recommended) 17 | "strict": true, 18 | // Or enable some of those features for more fine-grained control 19 | // "strictNullChecks": true, 20 | // "strictPropertyInitialization": true, 21 | // "strictBindCallApply": true, 22 | "noImplicitAny": false, 23 | // "noUnusedLocals": true, 24 | // "noUnusedParameters": true, 25 | 26 | // Consider targetting es2017 or higher if you require the new NodeJS 8+ features 27 | "target": "es2015" 28 | 29 | }, 30 | "include": [ 31 | "**/*.js", 32 | "**/*.d.ts" 33 | ], 34 | "exclude": [ 35 | "node_modules/**", 36 | "admin/**" 37 | ] 38 | } --------------------------------------------------------------------------------