├── .eslintignore
├── package.cjs
├── .gitattributes
├── static
├── favicon.ico
└── index.html
├── CONTRIBUTING.md
├── .browserslistrc
├── .editorconfig
├── .github
├── dependabot.yml
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
├── workflows
│ ├── deploy2GhPages.yml
│ ├── nodeTestCI.yml
│ └── release.yml
└── PULL_REQUEST_TEMPLATE.md
├── .babelrc
├── release.config.cjs
├── tasks
├── copyFiles.js
└── handlePackageJson.js
├── rollup
├── rollup.config.prod.mjs
├── rollup.config.dev.mjs
└── rollup.common.mjs
├── .eslintrc
├── src
├── utils.js
├── theme
│ └── style.scss
├── buttonView.js
├── lib
│ ├── ajax.js
│ ├── fetch.js
│ ├── websocket.js
│ ├── decoder.js
│ ├── canvas2d.js
│ ├── mp2-wasm.js
│ ├── ajax-progressive.js
│ ├── mpeg1-wasm.js
│ ├── webaudio.js
│ ├── buffer.js
│ ├── wasm-module.js
│ ├── ts.js
│ ├── video-element.js
│ ├── webgl.js
│ ├── player.js
│ ├── mp2.js
│ └── wasm
│ │ └── WASM_BINARY.js
└── index.js
├── .gitignore
├── LICENSE
├── package.json
├── CODE_OF_CONDUCT.md
├── docs
└── CHANGELOG.md
└── README.md
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist/*
2 | coverage/*
3 | .release/*
4 |
--------------------------------------------------------------------------------
/package.cjs:
--------------------------------------------------------------------------------
1 | module.exports = require('./package.json');
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
--------------------------------------------------------------------------------
/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cycjimmy/jsmpeg-player/HEAD/static/favicon.ico
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 | The repository is released under the MIT license, and follows a standard Github development process, using Github tracker for issues and merging pull requests into main.
3 |
--------------------------------------------------------------------------------
/.browserslistrc:
--------------------------------------------------------------------------------
1 | [production staging]
2 | ie >= 10
3 | ie_mob >= 10
4 | ff >= 30
5 | chrome >= 40
6 | safari >= 8
7 | opera >= 23
8 | ios >= 8
9 | android >= 4.4
10 |
11 | [development]
12 | last 2 versions
13 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: github-actions
4 | directory: /
5 | schedule:
6 | interval: daily
7 |
8 | - package-ecosystem: npm
9 | directory: /
10 | schedule:
11 | interval: daily
12 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "loose": true,
7 | "modules": false
8 | }
9 | ]
10 | ],
11 | "plugins": [
12 | "@babel/plugin-syntax-dynamic-import",
13 | "@babel/plugin-transform-object-assign"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/release.config.cjs:
--------------------------------------------------------------------------------
1 | /* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */
2 | const makeConfig = require('@cycjimmy/config-lib/cjs/semanticRelease/19.x/makeConfigWithPgkRootForLibrary.cjs').default;
3 | const pkg = require('./package.json');
4 |
5 | module.exports = makeConfig({
6 | githubOptions: {
7 | assets: [pkg.browser],
8 | },
9 | });
10 |
--------------------------------------------------------------------------------
/tasks/copyFiles.js:
--------------------------------------------------------------------------------
1 | /* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */
2 | /* eslint no-console: 0 */
3 | import path from 'path';
4 | import fs from 'fs-extra';
5 |
6 | const { copySync } = fs;
7 |
8 | copySync(
9 | path.resolve('dist'),
10 | path.resolve('.release', 'dist'),
11 | );
12 | copySync(
13 | path.resolve('README.md'),
14 | path.resolve('.release', 'README.md'),
15 | );
16 | copySync(
17 | path.resolve('LICENSE'),
18 | path.resolve('.release', 'LICENSE'),
19 | );
20 |
21 | console.log('copyFiles success!');
22 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/rollup/rollup.config.prod.mjs:
--------------------------------------------------------------------------------
1 | /* eslint import/extensions: ["error", "ignorePackages", {"mjs": off}] */
2 | import pkg from '../package.cjs';
3 |
4 | import {
5 | banner, input, name, plugins, terserPlugins,
6 | } from './rollup.common.mjs';
7 |
8 | export default [
9 | {
10 | input,
11 | output: [
12 | { file: pkg.main, format: 'cjs', exports: 'default' },
13 | { file: pkg.module, format: 'es', exports: 'default' },
14 | ],
15 | plugins,
16 | },
17 | {
18 | input,
19 | output: {
20 | name,
21 | file: pkg.browser,
22 | format: 'umd',
23 | banner,
24 | exports: 'default',
25 | },
26 | plugins: [...plugins, terserPlugins],
27 | },
28 | ];
29 |
--------------------------------------------------------------------------------
/.github/workflows/deploy2GhPages.yml:
--------------------------------------------------------------------------------
1 | name: Deploy To gh-pages
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - main
8 | jobs:
9 | deploy:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout
13 | uses: actions/checkout@v6
14 |
15 | - name: Setup Node.js
16 | uses: actions/setup-node@v5
17 | with:
18 | node-version: 18
19 |
20 | - name: Pre-built
21 | run: |
22 | npm ci
23 | npm run build:deployment
24 |
25 | - name: Deploy to GitHub Pages
26 | uses: JamesIves/github-pages-deploy-action@releases/v4
27 | with:
28 | branch: gh-pages
29 | folder: .publish
30 | token: ${{ secrets.ACCESS_TOKEN }}
31 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | **Type of Change**
2 |
3 | - [ ] feat: A new feature
4 | - [ ] fix: A bug fix
5 | - [ ] docs: Documentation only changes
6 | - [ ] style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
7 | - [ ] refactor: A code change that neither fixes a bug nor adds a feature
8 | - [ ] perf: A code change that improves performance
9 | - [ ] test: Adding missing or correcting existing tests
10 | - [ ] chore: Changes to the build process or auxiliary tools and libraries such as documentation generation
11 |
12 | **Resolves**
13 | - Fixes #[Add issue number here.]
14 |
15 | **Describe Changes**
16 |
17 | _Describe what this Pull Request does_
18 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "extends": "airbnb-base",
4 | "env": {
5 | "browser": true,
6 | "node": true
7 | },
8 | "rules": {
9 | "import/no-cycle": 0,
10 | "no-param-reassign": 0,
11 | "no-bitwise": [
12 | "error",
13 | {
14 | "allow": [
15 | "|",
16 | "|=",
17 | "&",
18 | "<<",
19 | "<<=",
20 | ">>",
21 | ">>="
22 | ]
23 | }
24 | ],
25 | "no-console": [
26 | "error",
27 | {
28 | "allow": [
29 | "warn",
30 | "error"
31 | ]
32 | }
33 | ],
34 | // "line-comment-position": 0,
35 | "no-underscore-dangle": 0,
36 | "no-multi-assign": 0,
37 | "no-plusplus": 0,
38 | "camelcase": 0,
39 | "no-unused-expressions": 0
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | import VideoElement from './lib/video-element';
2 |
3 | export const Now = () => (window.performance ? window.performance.now() / 1000 : Date.now() / 1000);
4 |
5 | export const CreateVideoElements = () => {
6 | const elements = document.querySelectorAll('.jsmpeg');
7 | for (let i = 0; i < elements.length; i++) {
8 | // eslint-disable-next-line no-new
9 | new VideoElement(elements[i]);
10 | }
11 | };
12 |
13 | export const Fill = (array, value) => {
14 | if (array.fill) {
15 | array.fill(value);
16 | } else {
17 | for (let i = 0; i < array.length; i++) {
18 | array[i] = value;
19 | }
20 | }
21 | };
22 |
23 | export const Base64ToArrayBuffer = (base64) => {
24 | const binary = window.atob(base64);
25 | const { length } = binary;
26 | const bytes = new Uint8Array(length);
27 | for (let i = 0; i < length; i++) {
28 | bytes[i] = binary.charCodeAt(i);
29 | }
30 | return bytes.buffer;
31 | };
32 |
--------------------------------------------------------------------------------
/tasks/handlePackageJson.js:
--------------------------------------------------------------------------------
1 | /* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */
2 | /* eslint no-console: 0 */
3 | import path from 'path';
4 | import fs from 'fs-extra';
5 |
6 | const { readJsonSync, outputJsonSync } = fs;
7 |
8 | const jsonData = readJsonSync(
9 | path.resolve('package.json'),
10 | );
11 |
12 | if (jsonData.scripts) {
13 | delete jsonData.scripts;
14 | }
15 |
16 | if (jsonData.dependencies) {
17 | delete jsonData.dependencies;
18 | }
19 |
20 | if (jsonData.devDependencies) {
21 | delete jsonData.devDependencies;
22 | }
23 |
24 | if (jsonData.publishConfig) {
25 | delete jsonData.publishConfig;
26 | }
27 |
28 | if (jsonData.config) {
29 | delete jsonData.config;
30 | }
31 |
32 | if (jsonData.engines) {
33 | delete jsonData.engines;
34 | }
35 |
36 | outputJsonSync(
37 | path.resolve('.release', 'package.json'),
38 | jsonData,
39 | {
40 | spaces: 2,
41 | },
42 | );
43 |
44 | console.log('handlePackageJson success!');
45 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Windows image file caches
2 | Thumbs.db
3 | ehthumbs.db
4 |
5 | # Folder config file
6 | Desktop.ini
7 |
8 | # Recycle Bin used on file shares
9 | $RECYCLE.BIN/
10 |
11 | # Windows Installer files
12 | *.cab
13 | *.msi
14 | *.msm
15 | *.msp
16 |
17 | # Windows shortcuts
18 | *.lnk
19 |
20 | # =========================
21 | # Operating System Files
22 | # =========================
23 |
24 | # OSX
25 | # =========================
26 |
27 | .DS_Store
28 | .AppleDouble
29 | .LSOverride
30 |
31 | # Thumbnails
32 | ._*
33 |
34 | # Files that might appear in the root of a volume
35 | .DocumentRevisions-V100
36 | .fseventsd
37 | .Spotlight-V100
38 | .TemporaryItems
39 | .Trashes
40 | .VolumeIcon.icns
41 |
42 | # Directories potentially created on remote AFP share
43 | .AppleDB
44 | .AppleDesktop
45 | Network Trash Folder
46 | Temporary Items
47 | .apdisk
48 |
49 | # IDE
50 | .idea
51 |
52 | # node
53 | node_modules/
54 | *.log
55 |
56 | # project
57 | .publish/
58 | dist/
59 | temp/
60 | coverage/
61 | .release/
62 |
--------------------------------------------------------------------------------
/src/theme/style.scss:
--------------------------------------------------------------------------------
1 | @use "~@cycjimmy/sass-lib" as *;
2 |
3 | .canvas,
4 | .poster {
5 | @extend %full-container;
6 | display: block;
7 | }
8 |
9 | .poster {
10 | &.hidden {
11 | display: none;
12 | }
13 | }
14 |
15 | // buttons
16 | %button-common {
17 | @extend %full-container;
18 |
19 | opacity: .7;
20 | cursor: pointer;
21 | -webkit-tap-highlight-color: rgba(255, 0, 0, 0);
22 |
23 | &.hidden {
24 | display: none;
25 | }
26 | }
27 |
28 | .playButton {
29 | @extend %button-common;
30 | @extend %flex-center;
31 | z-index: 10;
32 |
33 | > svg {
34 | width: 12vw;
35 | height: 12vw;
36 | max-width: 60px;
37 | max-height: 60px;
38 | fill: #fff;
39 | }
40 | }
41 |
42 | .unmuteButton {
43 | @extend %button-common;
44 | z-index: 10;
45 | display: flex;
46 | justify-content: flex-end;
47 | align-items: flex-end;
48 |
49 | > svg {
50 | margin: 0 15px 15px 0;
51 | width: 9vw;
52 | height: 9vw;
53 | max-width: 40px;
54 | max-height: 40px;
55 | fill: #fff;
56 | }
57 | }
58 |
59 |
--------------------------------------------------------------------------------
/src/buttonView.js:
--------------------------------------------------------------------------------
1 | /**
2 | * PLAY_BUTTON HTML
3 | * @type {string}
4 | */
5 | export const PLAY_BUTTON = `
6 |
9 | `;
10 |
11 | /**
12 | * UNMUTE_BUTTON HTML
13 | * @type {string}
14 | */
15 | export const UNMUTE_BUTTON = `
16 |
19 | `;
20 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Online demo**
38 | If applicable, provide an online demo to help explain your problem.
39 |
40 | **Additional context**
41 | Add any other context about the problem here.
42 |
--------------------------------------------------------------------------------
/rollup/rollup.config.dev.mjs:
--------------------------------------------------------------------------------
1 | /* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */
2 | /* eslint import/extensions: ["error", "ignorePackages", {"mjs": off}] */
3 | import browsersync from 'rollup-plugin-browsersync';
4 | import copy from 'rollup-plugin-copy';
5 |
6 | import pkg from '../package.cjs';
7 |
8 | import {
9 | input, IS_DEVELOPMENT, IS_DEPLOYMENT, name, plugins,
10 | } from './rollup.common.mjs';
11 |
12 | export default [
13 | {
14 | input,
15 | output: {
16 | name,
17 | file: pkg.browser.replace('.min.js', '.js'),
18 | format: 'umd',
19 | exports: 'default',
20 | },
21 | plugins: [
22 | ...plugins,
23 |
24 | IS_DEPLOYMENT
25 | && copy({
26 | hook: 'writeBundle',
27 | targets: [
28 | {
29 | src: ['static/**/*', 'dist/**.umd.js'],
30 | dest: '.publish',
31 | },
32 | ],
33 | }),
34 | IS_DEVELOPMENT
35 | && browsersync({
36 | server: ['static', 'dist'],
37 | watch: true,
38 | }),
39 | ],
40 | },
41 | ];
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017-2022 cycjimmy
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 |
--------------------------------------------------------------------------------
/.github/workflows/nodeTestCI.yml:
--------------------------------------------------------------------------------
1 | name: Test CI
2 | on:
3 | push:
4 |
5 | pull_request:
6 | branches:
7 | - master
8 | - main
9 | - next
10 | - next-major
11 | - alpha
12 | - beta
13 |
14 | schedule:
15 | - cron: 0 2 * * 0
16 |
17 | jobs:
18 | test:
19 | runs-on: ${{ matrix.os }}
20 | strategy:
21 | matrix:
22 | node: [ 18 ]
23 | os: [ubuntu-latest, windows-latest, macOS-latest]
24 |
25 | steps:
26 | - uses: actions/checkout@v6
27 | - uses: actions/setup-node@v5
28 | with:
29 | node-version: ${{ matrix.node }}
30 | - run: npm ci
31 | - run: npm run package
32 |
33 | - name: Semantic Release Test
34 | uses: cycjimmy/semantic-release-action@v5
35 | with:
36 | dry_run: true
37 | branches: |
38 | [
39 | 'master',
40 | 'main',
41 | 'next',
42 | 'next-major',
43 | {
44 | name: 'beta',
45 | prerelease: true
46 | },
47 | {
48 | name: 'alpha',
49 | prerelease: true
50 | }
51 | ]
52 | extra_plugins: |
53 | @semantic-release/git
54 | @semantic-release/changelog
55 | @semantic-release/exec
56 | env:
57 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
58 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
59 |
--------------------------------------------------------------------------------
/rollup/rollup.common.mjs:
--------------------------------------------------------------------------------
1 | /* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */
2 | /* eslint import/extensions: ["error", "ignorePackages", {"js": off}] */
3 | import eslint from '@rollup/plugin-eslint';
4 | import json from '@rollup/plugin-json';
5 | import resolve from '@rollup/plugin-node-resolve';
6 | import { babel } from '@rollup/plugin-babel';
7 | import terser from '@rollup/plugin-terser';
8 | import postcss from 'rollup-plugin-postcss';
9 | import autoprefixer from 'autoprefixer';
10 |
11 | import myBanner from '@cycjimmy/config-lib/esm/chore/myBanner.js';
12 | import terserOption from '@cycjimmy/config-lib/esm/terser/4.x/production.js';
13 |
14 | import pkg from '../package.cjs';
15 |
16 | export const IS_DEVELOPMENT = process.env.NODE_ENV === 'development';
17 | export const IS_PRODUCTION = process.env.NODE_ENV === 'production';
18 | export const IS_DEPLOYMENT = process.env.NODE_ENV === 'deployment';
19 |
20 | export const input = './src/index.js';
21 | export const name = 'JSMpeg';
22 | export const banner = myBanner(pkg);
23 |
24 | export const plugins = [
25 | json(),
26 | postcss({
27 | modules: {
28 | generateScopedName: IS_PRODUCTION ? '[hash:base64:10]' : '[name]__[local]',
29 | },
30 | autoModules: false,
31 | minimize: true,
32 | plugins: [autoprefixer],
33 | }),
34 | eslint({
35 | fix: true,
36 | exclude: [
37 | 'node_modules/**',
38 | '**/*.(css|scss)',
39 | ],
40 | }),
41 | resolve(),
42 | babel({ babelHelpers: 'bundled' }),
43 | ];
44 |
45 | export const terserPlugins = IS_PRODUCTION && terser(terserOption);
46 |
--------------------------------------------------------------------------------
/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | JSMpeg Video Demo
7 |
8 |
35 |
36 |
37 |
38 |
39 |
45 |
46 |
47 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/src/lib/ajax.js:
--------------------------------------------------------------------------------
1 | /* eslint class-methods-use-this: ["error", { "exceptMethods": ["resume"] }] */
2 | export default class AjaxSource {
3 | constructor(url, options) {
4 | this.url = url;
5 | this.destination = null;
6 | this.request = null;
7 | this.streaming = false;
8 |
9 | this.completed = false;
10 | this.established = false;
11 | this.progress = 0;
12 |
13 | this.onEstablishedCallback = options.onSourceEstablished;
14 | this.onCompletedCallback = options.onSourceCompleted;
15 |
16 | if (options.hookOnEstablished) {
17 | this.hookOnEstablished = options.hookOnEstablished;
18 | }
19 | }
20 |
21 | connect(destination) {
22 | this.destination = destination;
23 | }
24 |
25 | start() {
26 | this.request = new XMLHttpRequest();
27 |
28 | // eslint-disable-next-line func-names
29 | this.request.onreadystatechange = function () {
30 | if (this.request.readyState === this.request.DONE && this.request.status === 200) {
31 | this.onLoad(this.request.response);
32 | }
33 | }.bind(this);
34 |
35 | this.request.onprogress = this.onProgress.bind(this);
36 | this.request.open('GET', this.url);
37 | this.request.responseType = 'arraybuffer';
38 | this.request.send();
39 | }
40 |
41 | resume() {
42 | // Nothing to do here
43 | }
44 |
45 | destroy() {
46 | this.request.abort();
47 | }
48 |
49 | onProgress(ev) {
50 | this.progress = ev.loaded / ev.total;
51 | }
52 |
53 | onLoad(data) {
54 | this.established = true;
55 | this.completed = true;
56 | this.progress = 1;
57 |
58 | if (this.hookOnEstablished) {
59 | this.hookOnEstablished();
60 | }
61 |
62 | if (this.onEstablishedCallback) {
63 | this.onEstablishedCallback(this);
64 | }
65 | if (this.onCompletedCallback) {
66 | this.onCompletedCallback(this);
67 | }
68 |
69 | if (this.destination) {
70 | this.destination.write(data);
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/lib/fetch.js:
--------------------------------------------------------------------------------
1 | /* eslint class-methods-use-this: ["error", { "exceptMethods": ["resume"] }] */
2 | export default class FetchSource {
3 | constructor(url, options) {
4 | this.url = url;
5 | this.destination = null;
6 | this.request = null;
7 | this.streaming = true;
8 |
9 | this.completed = false;
10 | this.established = false;
11 | this.progress = 0;
12 | this.aborted = false;
13 |
14 | this.onEstablishedCallback = options.onSourceEstablished;
15 | this.onCompletedCallback = options.onSourceCompleted;
16 |
17 | if (options.hookOnEstablished) {
18 | this.hookOnEstablished = options.hookOnEstablished;
19 | }
20 | }
21 |
22 | connect(destination) {
23 | this.destination = destination;
24 | }
25 |
26 | start() {
27 | const params = {
28 | method: 'GET',
29 | headers: new Headers(),
30 | cache: 'default',
31 | };
32 |
33 | // eslint-disable-next-line no-restricted-globals
34 | self
35 | .fetch(this.url, params)
36 | // eslint-disable-next-line consistent-return
37 | .then((res) => {
38 | if (res.ok && res.status >= 200 && res.status <= 299) {
39 | this.progress = 1;
40 | this.established = true;
41 | return this.pump(res.body.getReader());
42 | }
43 | // error
44 | })
45 | .catch((err) => {
46 | throw err;
47 | });
48 | }
49 |
50 | pump(reader) {
51 | return (
52 | reader
53 | .read()
54 | // eslint-disable-next-line consistent-return
55 | .then((result) => {
56 | if (result.done) {
57 | this.completed = true;
58 | } else {
59 | if (this.aborted) {
60 | return reader.cancel();
61 | }
62 |
63 | if (this.destination) {
64 | this.destination.write(result.value.buffer);
65 | }
66 |
67 | return this.pump(reader);
68 | }
69 | })
70 | .catch((err) => {
71 | throw err;
72 | })
73 | );
74 | }
75 |
76 | resume() {
77 | // Nothing to do here
78 | }
79 |
80 | abort() {
81 | this.aborted = true;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/lib/websocket.js:
--------------------------------------------------------------------------------
1 | export default class WSSource {
2 | constructor(url, options) {
3 | this.url = url;
4 | this.options = options;
5 | this.socket = null;
6 | this.streaming = true;
7 |
8 | this.callbacks = { connect: [], data: [] };
9 | this.destination = null;
10 |
11 | this.reconnectInterval = options.reconnectInterval !== undefined
12 | ? options.reconnectInterval
13 | : 5;
14 | this.shouldAttemptReconnect = !!this.reconnectInterval;
15 |
16 | this.completed = false;
17 | this.established = false;
18 | this.progress = 0;
19 |
20 | this.reconnectTimeoutId = 0;
21 |
22 | this.onEstablishedCallback = options.onSourceEstablished;
23 | this.onCompletedCallback = options.onSourceCompleted; // Never used
24 |
25 | if (options.hookOnEstablished) {
26 | this.hookOnEstablished = options.hookOnEstablished;
27 | }
28 | }
29 |
30 | connect(destination) {
31 | this.destination = destination;
32 | }
33 |
34 | destroy() {
35 | clearTimeout(this.reconnectTimeoutId);
36 | this.shouldAttemptReconnect = false;
37 | this.socket.close();
38 | }
39 |
40 | start() {
41 | this.shouldAttemptReconnect = !!this.reconnectInterval;
42 | this.progress = 0;
43 | this.established = false;
44 |
45 | if (this.options.protocols) {
46 | this.socket = new WebSocket(this.url, this.options.protocols);
47 | } else {
48 | this.socket = new WebSocket(this.url);
49 | }
50 | this.socket.binaryType = 'arraybuffer';
51 | this.socket.onmessage = this.onMessage.bind(this);
52 | this.socket.onopen = this.onOpen.bind(this);
53 | this.socket.onerror = this.onClose.bind(this);
54 | this.socket.onclose = this.onClose.bind(this);
55 | }
56 |
57 | // eslint-disable-next-line class-methods-use-this
58 | resume() {
59 | // Nothing to do here
60 | }
61 |
62 | onOpen() {
63 | this.progress = 1;
64 | }
65 |
66 | onClose() {
67 | if (this.shouldAttemptReconnect) {
68 | clearTimeout(this.reconnectTimeoutId);
69 | this.reconnectTimeoutId = setTimeout(() => {
70 | this.start();
71 | }, this.reconnectInterval * 1000);
72 | }
73 | }
74 |
75 | onMessage(ev) {
76 | const isFirstChunk = !this.established;
77 | this.established = true;
78 |
79 | if (isFirstChunk && this.hookOnEstablished) {
80 | this.hookOnEstablished();
81 | }
82 |
83 | if (isFirstChunk && this.onEstablishedCallback) {
84 | this.onEstablishedCallback(this);
85 | }
86 |
87 | if (this.destination) {
88 | this.destination.write(ev.data);
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - main
8 | - next
9 | - next-major
10 | - alpha
11 | - beta
12 |
13 | jobs:
14 | test:
15 | name: test
16 | runs-on: ubuntu-latest
17 | steps:
18 | - name: Checkout
19 | uses: actions/checkout@v6
20 |
21 | - name: Setup Node.js
22 | uses: actions/setup-node@v5
23 | with:
24 | node-version: '18'
25 |
26 | - name: Install dependencies
27 | run: npm ci
28 |
29 | - run: npm run package
30 |
31 | release:
32 | name: release
33 | runs-on: ubuntu-latest
34 | needs: test
35 | steps:
36 | - name: Checkout
37 | uses: actions/checkout@v6
38 |
39 | - name: Setup Node.js
40 | uses: actions/setup-node@v5
41 | with:
42 | node-version: 18
43 |
44 | - name: Install dependencies
45 | run: npm ci
46 |
47 | - name: Build package
48 | run: npm run package
49 |
50 | - name: Semantic Release
51 | uses: cycjimmy/semantic-release-action@v5
52 | id: semantic
53 | with:
54 | branches: |
55 | [
56 | '+([0-9])?(.{+([0-9]),x}).x',
57 | 'master',
58 | 'main',
59 | 'next',
60 | 'next-major',
61 | {
62 | name: 'beta',
63 | prerelease: true
64 | },
65 | {
66 | name: 'alpha',
67 | prerelease: true
68 | }
69 | ]
70 | extra_plugins: |
71 | @semantic-release/git
72 | @semantic-release/changelog
73 | @semantic-release/exec
74 | env:
75 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
76 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
77 |
78 | - name: Setup Node.js with GitHub Package Registry
79 | uses: actions/setup-node@v5
80 | with:
81 | node-version: 18
82 | registry-url: 'https://npm.pkg.github.com'
83 | scope: 'cycjimmy'
84 |
85 | - name: Publish To GitHub Package Registry
86 | if: steps.semantic.outputs.new_release_published == 'true'
87 | run: npm publish ./.release
88 | env:
89 | NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
90 |
91 | - name: Push updates to branch for major version
92 | if: steps.semantic.outputs.new_release_published == 'true'
93 | run: git push https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git HEAD:refs/heads/v${{steps.semantic.outputs.new_release_major_version}}
94 | env:
95 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
96 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@cycjimmy/jsmpeg-player",
3 | "version": "6.1.2",
4 | "description": "MPEG1 Video Player Based On JSMpeg",
5 | "type": "module",
6 | "main": "dist/jsmpeg-player.cjs",
7 | "module": "dist/jsmpeg-player.esm.js",
8 | "browser": "dist/jsmpeg-player.umd.min.js",
9 | "exports": {
10 | "require": "./dist/jsmpeg-player.cjs",
11 | "import": "./dist/jsmpeg-player.esm.js"
12 | },
13 | "scripts": {
14 | "start": "npm run dev",
15 | "lint": "eslint --ext .js,.cjs,.mjs -c .eslintrc .",
16 | "lint:fix": "eslint --ext .js,.cjs,.mjs --fix -c .eslintrc . --fix",
17 | "dev": "cross-env NODE_ENV=development rollup -c rollup/rollup.config.dev.mjs --watch",
18 | "build": "trash dist && npm run build:prod:umd && npm run build:prod",
19 | "build:prod": "cross-env NODE_ENV=production rollup -c rollup/rollup.config.prod.mjs",
20 | "build:prod:umd": "cross-env NODE_ENV=production rollup -c rollup/rollup.config.dev.mjs",
21 | "build:deployment": "cross-env NODE_ENV=deployment rollup -c rollup/rollup.config.dev.mjs",
22 | "package": "trash .release && npm run build && node tasks/copyFiles.js && node tasks/handlePackageJson.js"
23 | },
24 | "repository": {
25 | "type": "git",
26 | "url": "git+https://github.com/cycjimmy/jsmpeg-player.git"
27 | },
28 | "keywords": [
29 | "jsmpeg",
30 | "TS"
31 | ],
32 | "author": "cycjimmy (https://github.com/cycjimmy)",
33 | "license": "MIT",
34 | "bugs": {
35 | "url": "https://github.com/cycjimmy/jsmpeg-player/issues"
36 | },
37 | "homepage": "https://github.com/cycjimmy/jsmpeg-player#readme",
38 | "engines": {
39 | "node": ">=16"
40 | },
41 | "publishConfig": {
42 | "access": "public"
43 | },
44 | "dependencies": {
45 | "@cycjimmy/awesome-js-funcs": "^4.0.9",
46 | "@cycjimmy/sass-lib": "^3.0.1"
47 | },
48 | "devDependencies": {
49 | "@babel/core": "^7.24.9",
50 | "@babel/plugin-syntax-dynamic-import": "^7.8.3",
51 | "@babel/plugin-transform-object-assign": "^7.24.7",
52 | "@babel/preset-env": "^7.24.8",
53 | "@cycjimmy/config-lib": "^3.2.1",
54 | "@rollup/plugin-babel": "^6.0.4",
55 | "@rollup/plugin-eslint": "^9.0.5",
56 | "@rollup/plugin-json": "^6.1.0",
57 | "@rollup/plugin-node-resolve": "^16.0.0",
58 | "@rollup/plugin-terser": "^0.4.4",
59 | "autoprefixer": "^10.4.19",
60 | "cross-env": "^10.0.0",
61 | "eslint": "^8.57.0",
62 | "eslint-config-airbnb-base": "^15.0.0",
63 | "eslint-plugin-import": "^2.29.1",
64 | "fs-extra": "^11.2.0",
65 | "rollup": "^4.18.1",
66 | "rollup-plugin-browsersync": "^1.3.3",
67 | "rollup-plugin-copy": "^3.5.0",
68 | "rollup-plugin-postcss": "^4.0.2",
69 | "sass": "^1.77.8",
70 | "trash-cli": "^6.0.0"
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/lib/decoder.js:
--------------------------------------------------------------------------------
1 | /* eslint class-methods-use-this: ["error", { "exceptMethods": ["destroy"] }] */
2 | export default class BaseDecoder {
3 | constructor(options) {
4 | this.destination = null;
5 | this.canPlay = false;
6 |
7 | this.collectTimestamps = !options.streaming;
8 | this.bytesWritten = 0;
9 | this.timestamps = [];
10 | this.timestampIndex = 0;
11 |
12 | this.startTime = 0;
13 | this.decodedTime = 0;
14 |
15 | Object.defineProperty(this, 'currentTime', { get: this.getCurrentTime });
16 | }
17 |
18 | destroy() {}
19 |
20 | connect(destination) {
21 | this.destination = destination;
22 | }
23 |
24 | bufferGetIndex() {
25 | return this.bits.index;
26 | }
27 |
28 | bufferSetIndex(index) {
29 | this.bits.index = index;
30 | }
31 |
32 | bufferWrite(buffers) {
33 | return this.bits.write(buffers);
34 | }
35 |
36 | write(pts, buffers) {
37 | if (this.collectTimestamps) {
38 | if (this.timestamps.length === 0) {
39 | this.startTime = pts;
40 | this.decodedTime = pts;
41 | }
42 | this.timestamps.push({ index: this.bytesWritten << 3, time: pts });
43 | }
44 |
45 | this.bytesWritten += this.bufferWrite(buffers);
46 | this.canPlay = true;
47 | }
48 |
49 | seek(time) {
50 | if (!this.collectTimestamps) {
51 | return;
52 | }
53 |
54 | this.timestampIndex = 0;
55 | for (let i = 0; i < this.timestamps.length; i++) {
56 | if (this.timestamps[i].time > time) {
57 | break;
58 | }
59 | this.timestampIndex = i;
60 | }
61 |
62 | const ts = this.timestamps[this.timestampIndex];
63 | if (ts) {
64 | this.bufferSetIndex(ts.index);
65 | this.decodedTime = ts.time;
66 | } else {
67 | this.bufferSetIndex(0);
68 | this.decodedTime = this.startTime;
69 | }
70 | }
71 |
72 | decode() {
73 | this.advanceDecodedTime(0);
74 | }
75 |
76 | advanceDecodedTime(seconds) {
77 | if (this.collectTimestamps) {
78 | let newTimestampIndex = -1;
79 | const currentIndex = this.bufferGetIndex();
80 | for (let i = this.timestampIndex; i < this.timestamps.length; i++) {
81 | if (this.timestamps[i].index > currentIndex) {
82 | break;
83 | }
84 | newTimestampIndex = i;
85 | }
86 |
87 | // Did we find a new PTS, different from the last? If so, we don't have
88 | // to advance the decoded time manually and can instead sync it exactly
89 | // to the PTS.
90 | if (newTimestampIndex !== -1 && newTimestampIndex !== this.timestampIndex) {
91 | this.timestampIndex = newTimestampIndex;
92 | this.decodedTime = this.timestamps[this.timestampIndex].time;
93 | return;
94 | }
95 | }
96 |
97 | this.decodedTime += seconds;
98 | }
99 |
100 | getCurrentTime() {
101 | return this.decodedTime;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at cycjimmy@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/src/lib/canvas2d.js:
--------------------------------------------------------------------------------
1 | import { Fill } from '../utils';
2 |
3 | /* eslint class-methods-use-this: ["error", { "exceptMethods": ["destroy"] }] */
4 | export default class CanvasRenderer {
5 | constructor(options) {
6 | if (options.canvas) {
7 | this.canvas = options.canvas;
8 | this.ownsCanvasElement = false;
9 | } else {
10 | this.canvas = document.createElement('canvas');
11 | this.ownsCanvasElement = true;
12 | }
13 | this.width = this.canvas.width;
14 | this.height = this.canvas.height;
15 | this.enabled = true;
16 |
17 | this.context = this.canvas.getContext('2d');
18 | }
19 |
20 | destroy() {
21 | if (this.ownsCanvasElement) {
22 | this.canvas.remove();
23 | }
24 | }
25 |
26 | resize(width, height) {
27 | this.width = width | 0;
28 | this.height = height | 0;
29 |
30 | this.canvas.width = this.width;
31 | this.canvas.height = this.height;
32 |
33 | this.imageData = this.context.getImageData(0, 0, this.width, this.height);
34 | Fill(this.imageData.data, 255);
35 | }
36 |
37 | renderProgress(progress) {
38 | const w = this.canvas.width;
39 | const h = this.canvas.height;
40 | const ctx = this.context;
41 |
42 | ctx.fillStyle = '#222';
43 | ctx.fillRect(0, 0, w, h);
44 | ctx.fillStyle = '#fff';
45 | ctx.fillRect(0, h - h * progress, w, h * progress);
46 | }
47 |
48 | render(y, cb, cr) {
49 | this.YCbCrToRGBA(y, cb, cr, this.imageData.data);
50 | this.context.putImageData(this.imageData, 0, 0);
51 | }
52 |
53 | YCbCrToRGBA(y, cb, cr, rgba) {
54 | if (!this.enabled) {
55 | return;
56 | }
57 |
58 | // Chroma values are the same for each block of 4 pixels, so we proccess
59 | // 2 lines at a time, 2 neighboring pixels each.
60 | // I wish we could use 32bit writes to the RGBA buffer instead of writing
61 | // each byte separately, but we need the automatic clamping of the RGBA
62 | // buffer.
63 |
64 | const w = ((this.width + 15) >> 4) << 4;
65 | const w2 = w >> 1;
66 |
67 | let yIndex1 = 0;
68 | let yIndex2 = w;
69 | const yNext2Lines = w + (w - this.width);
70 |
71 | let cIndex = 0;
72 | const cNextLine = w2 - (this.width >> 1);
73 |
74 | let rgbaIndex1 = 0;
75 | let rgbaIndex2 = this.width * 4;
76 | const rgbaNext2Lines = this.width * 4;
77 |
78 | const cols = this.width >> 1;
79 | const rows = this.height >> 1;
80 |
81 | let ccb;
82 | let ccr;
83 | let r;
84 | let g;
85 | let b;
86 |
87 | for (let row = 0; row < rows; row++) {
88 | for (let col = 0; col < cols; col++) {
89 | ccb = cb[cIndex];
90 | ccr = cr[cIndex];
91 | cIndex++;
92 |
93 | r = ccb + ((ccb * 103) >> 8) - 179;
94 | g = ((ccr * 88) >> 8) - 44 + ((ccb * 183) >> 8) - 91;
95 | b = ccr + ((ccr * 198) >> 8) - 227;
96 |
97 | // Line 1
98 | const y1 = y[yIndex1++];
99 | const y2 = y[yIndex1++];
100 | rgba[rgbaIndex1] = y1 + r;
101 | rgba[rgbaIndex1 + 1] = y1 - g;
102 | rgba[rgbaIndex1 + 2] = y1 + b;
103 | rgba[rgbaIndex1 + 4] = y2 + r;
104 | rgba[rgbaIndex1 + 5] = y2 - g;
105 | rgba[rgbaIndex1 + 6] = y2 + b;
106 | rgbaIndex1 += 8;
107 |
108 | // Line 2
109 | const y3 = y[yIndex2++];
110 | const y4 = y[yIndex2++];
111 | rgba[rgbaIndex2] = y3 + r;
112 | rgba[rgbaIndex2 + 1] = y3 - g;
113 | rgba[rgbaIndex2 + 2] = y3 + b;
114 | rgba[rgbaIndex2 + 4] = y4 + r;
115 | rgba[rgbaIndex2 + 5] = y4 - g;
116 | rgba[rgbaIndex2 + 6] = y4 + b;
117 | rgbaIndex2 += 8;
118 | }
119 |
120 | yIndex1 += yNext2Lines;
121 | yIndex2 += yNext2Lines;
122 | rgbaIndex1 += rgbaNext2Lines;
123 | rgbaIndex2 += rgbaNext2Lines;
124 | cIndex += cNextLine;
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/lib/mp2-wasm.js:
--------------------------------------------------------------------------------
1 | // Based on kjmp2 by Martin J. Fiedler
2 | // http://keyj.emphy.de/kjmp2/
3 |
4 | import { Now } from '../utils';
5 |
6 | import BaseDecoder from './decoder';
7 | import BitBuffer from './buffer';
8 |
9 | class MP2WASM extends BaseDecoder {
10 | constructor(options) {
11 | super(options);
12 |
13 | this.onDecodeCallback = options.onAudioDecode;
14 | this.module = options.wasmModule;
15 |
16 | this.bufferSize = options.audioBufferSize || 128 * 1024;
17 | this.bufferMode = options.streaming ? BitBuffer.MODE.EVICT : BitBuffer.MODE.EXPAND;
18 |
19 | this.sampleRate = 0;
20 | }
21 |
22 | initializeWasmDecoder() {
23 | if (!this.module.instance) {
24 | console.warn('JSMpeg: WASM module not compiled yet');
25 | return;
26 | }
27 | this.instance = this.module.instance;
28 | this.functions = this.module.instance.exports;
29 | this.decoder = this.functions._mp2_decoder_create(this.bufferSize, this.bufferMode);
30 | }
31 |
32 | destroy() {
33 | if (!this.decoder) {
34 | return;
35 | }
36 | this.functions._mp2_decoder_destroy(this.decoder);
37 | }
38 |
39 | bufferGetIndex() {
40 | if (!this.decoder) {
41 | return;
42 | }
43 | // eslint-disable-next-line consistent-return
44 | return this.functions._mp2_decoder_get_index(this.decoder);
45 | }
46 |
47 | bufferSetIndex(index) {
48 | if (!this.decoder) {
49 | return;
50 | }
51 | this.functions._mp2_decoder_set_index(this.decoder, index);
52 | }
53 |
54 | bufferWrite(buffers) {
55 | if (!this.decoder) {
56 | this.initializeWasmDecoder();
57 | }
58 |
59 | let totalLength = 0;
60 | for (let i = 0; i < buffers.length; i++) {
61 | totalLength += buffers[i].length;
62 | }
63 |
64 | let ptr = this.functions._mp2_decoder_get_write_ptr(this.decoder, totalLength);
65 | for (let i = 0; i < buffers.length; i++) {
66 | this.instance.heapU8.set(buffers[i], ptr);
67 | ptr += buffers[i].length;
68 | }
69 |
70 | this.functions._mp2_decoder_did_write(this.decoder, totalLength);
71 | return totalLength;
72 | }
73 |
74 | decode() {
75 | const startTime = Now();
76 |
77 | if (!this.decoder) {
78 | return false;
79 | }
80 |
81 | const decodedBytes = this.functions._mp2_decoder_decode(this.decoder);
82 | if (decodedBytes === 0) {
83 | return false;
84 | }
85 |
86 | if (!this.sampleRate) {
87 | this.sampleRate = this.functions._mp2_decoder_get_sample_rate(this.decoder);
88 | }
89 |
90 | if (this.destination) {
91 | // Create a Float32 View into the modules output channel data
92 | const leftPtr = this.functions._mp2_decoder_get_left_channel_ptr(this.decoder);
93 | const rightPtr = this.functions._mp2_decoder_get_right_channel_ptr(this.decoder);
94 |
95 | const leftOffset = leftPtr / Float32Array.BYTES_PER_ELEMENT;
96 | const rightOffset = rightPtr / Float32Array.BYTES_PER_ELEMENT;
97 |
98 | const left = this.instance.heapF32.subarray(
99 | leftOffset,
100 | leftOffset + MP2WASM.SAMPLES_PER_FRAME,
101 | );
102 | const right = this.instance.heapF32.subarray(
103 | rightOffset,
104 | rightOffset + MP2WASM.SAMPLES_PER_FRAME,
105 | );
106 |
107 | this.destination.play(this.sampleRate, left, right);
108 | }
109 |
110 | this.advanceDecodedTime(MP2WASM.SAMPLES_PER_FRAME / this.sampleRate);
111 |
112 | const elapsedTime = Now() - startTime;
113 | if (this.onDecodeCallback) {
114 | this.onDecodeCallback(this, elapsedTime);
115 | }
116 | return true;
117 | }
118 |
119 | getCurrentTime() {
120 | const enqueuedTime = this.destination ? this.destination.enqueuedTime : 0;
121 | return this.decodedTime - enqueuedTime;
122 | }
123 | }
124 |
125 | MP2WASM.SAMPLES_PER_FRAME = 1152;
126 |
127 | export default MP2WASM;
128 |
--------------------------------------------------------------------------------
/src/lib/ajax-progressive.js:
--------------------------------------------------------------------------------
1 | import { Now } from '../utils';
2 |
3 | export default class AjaxProgressiveSource {
4 | constructor(url, options) {
5 | this.url = url;
6 | this.destination = null;
7 | this.request = null;
8 | this.streaming = false;
9 |
10 | this.completed = false;
11 | this.established = false;
12 | this.progress = 0;
13 |
14 | this.fileSize = 0;
15 | this.loadedSize = 0;
16 | this.chunkSize = options.chunkSize || 1024 * 1024;
17 |
18 | this.isLoading = false;
19 | this.loadStartTime = 0;
20 | this.throttled = options.throttled !== false;
21 | this.aborted = false;
22 |
23 | this.onEstablishedCallback = options.onSourceEstablished;
24 | this.onCompletedCallback = options.onSourceCompleted;
25 |
26 | if (options.hookOnEstablished) {
27 | this.hookOnEstablished = options.hookOnEstablished;
28 | }
29 | }
30 |
31 | connect(destination) {
32 | this.destination = destination;
33 | }
34 |
35 | start() {
36 | this.request = new XMLHttpRequest();
37 |
38 | this.request.onreadystatechange = () => {
39 | if (this.request.readyState === this.request.DONE) {
40 | this.fileSize = parseInt(this.request.getResponseHeader('Content-Length'), 10);
41 | this.loadNextChunk();
42 | }
43 | };
44 |
45 | this.request.onprogress = this.onProgress.bind(this);
46 | this.request.open('HEAD', this.url);
47 | this.request.send();
48 | }
49 |
50 | resume(secondsHeadroom) {
51 | if (this.isLoading || !this.throttled) {
52 | return;
53 | }
54 |
55 | // Guess the worst case loading time with lots of safety margin. This is
56 | // somewhat arbitrary...
57 | const worstCaseLoadingTime = this.loadTime * 8 + 2;
58 | if (worstCaseLoadingTime > secondsHeadroom) {
59 | this.loadNextChunk();
60 | }
61 | }
62 |
63 | destroy() {
64 | this.request.abort();
65 | this.aborted = true;
66 | }
67 |
68 | loadNextChunk() {
69 | const start = this.loadedSize;
70 | const end = Math.min(this.loadedSize + this.chunkSize - 1, this.fileSize - 1);
71 |
72 | if (start >= this.fileSize || this.aborted) {
73 | this.completed = true;
74 | if (this.onCompletedCallback) {
75 | this.onCompletedCallback(this);
76 | }
77 | return;
78 | }
79 |
80 | this.isLoading = true;
81 | this.loadStartTime = Now();
82 | this.request = new XMLHttpRequest();
83 |
84 | this.request.onreadystatechange = () => {
85 | if (
86 | this.request.readyState === this.request.DONE
87 | && this.request.status >= 200
88 | && this.request.status < 300
89 | ) {
90 | this.onChunkLoad(this.request.response);
91 | } else if (this.request.readyState === this.request.DONE) {
92 | // Retry?
93 | if (this.loadFails++ < 3) {
94 | this.loadNextChunk();
95 | }
96 | }
97 | };
98 |
99 | if (start === 0) {
100 | this.request.onprogress = this.onProgress.bind(this);
101 | }
102 |
103 | this.request.open('GET', `${this.url}?${start}-${end}`);
104 | this.request.setRequestHeader('Range', `bytes=${start}-${end}`);
105 | this.request.responseType = 'arraybuffer';
106 | this.request.send();
107 | }
108 |
109 | onProgress(ev) {
110 | this.progress = ev.loaded / ev.total;
111 | }
112 |
113 | onChunkLoad(data) {
114 | const isFirstChunk = !this.established;
115 | this.established = true;
116 | this.progress = 1;
117 |
118 | this.loadedSize += data.byteLength;
119 | this.loadFails = 0;
120 | this.isLoading = false;
121 |
122 | if (isFirstChunk && this.hookOnEstablished) {
123 | this.hookOnEstablished();
124 | }
125 |
126 | if (isFirstChunk && this.onEstablishedCallback) {
127 | this.onEstablishedCallback(this);
128 | }
129 |
130 | if (this.destination) {
131 | this.destination.write(data);
132 | }
133 |
134 | this.loadTime = Now() - this.loadStartTime;
135 | if (!this.throttled) {
136 | this.loadNextChunk();
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/lib/mpeg1-wasm.js:
--------------------------------------------------------------------------------
1 | import { Now } from '../utils';
2 |
3 | import BaseDecoder from './decoder';
4 | import BitBuffer from './buffer';
5 |
6 | export default class MPEG1WASM extends BaseDecoder {
7 | constructor(options) {
8 | super(options);
9 |
10 | this.onDecodeCallback = options.onVideoDecode;
11 | this.module = options.wasmModule;
12 |
13 | this.bufferSize = options.videoBufferSize || 512 * 1024;
14 | this.bufferMode = options.streaming ? BitBuffer.MODE.EVICT : BitBuffer.MODE.EXPAND;
15 |
16 | this.decodeFirstFrame = options.decodeFirstFrame !== false;
17 | this.hasSequenceHeader = false;
18 | }
19 |
20 | initializeWasmDecoder() {
21 | if (!this.module.instance) {
22 | console.warn('JSMpeg: WASM module not compiled yet');
23 | return;
24 | }
25 | this.instance = this.module.instance;
26 | this.functions = this.module.instance.exports;
27 | this.decoder = this.functions._mpeg1_decoder_create(this.bufferSize, this.bufferMode);
28 | }
29 |
30 | destroy() {
31 | if (!this.decoder) {
32 | return;
33 | }
34 | this.functions._mpeg1_decoder_destroy(this.decoder);
35 | }
36 |
37 | bufferGetIndex() {
38 | if (!this.decoder) {
39 | return;
40 | }
41 | // eslint-disable-next-line consistent-return
42 | return this.functions._mpeg1_decoder_get_index(this.decoder);
43 | }
44 |
45 | bufferSetIndex(index) {
46 | if (!this.decoder) {
47 | return;
48 | }
49 | this.functions._mpeg1_decoder_set_index(this.decoder, index);
50 | }
51 |
52 | bufferWrite(buffers) {
53 | if (!this.decoder) {
54 | this.initializeWasmDecoder();
55 | }
56 |
57 | let totalLength = 0;
58 | for (let i = 0; i < buffers.length; i++) {
59 | totalLength += buffers[i].length;
60 | }
61 |
62 | let ptr = this.functions._mpeg1_decoder_get_write_ptr(this.decoder, totalLength);
63 | for (let i = 0; i < buffers.length; i++) {
64 | this.instance.heapU8.set(buffers[i], ptr);
65 | ptr += buffers[i].length;
66 | }
67 |
68 | this.functions._mpeg1_decoder_did_write(this.decoder, totalLength);
69 | return totalLength;
70 | }
71 |
72 | write(pts, buffers) {
73 | BaseDecoder.prototype.write.call(this, pts, buffers);
74 |
75 | if (
76 | !this.hasSequenceHeader
77 | && this.functions._mpeg1_decoder_has_sequence_header(this.decoder)
78 | ) {
79 | this.loadSequenceHeader();
80 | }
81 | }
82 |
83 | loadSequenceHeader() {
84 | this.hasSequenceHeader = true;
85 | this.frameRate = this.functions._mpeg1_decoder_get_frame_rate(this.decoder);
86 | this.codedSize = this.functions._mpeg1_decoder_get_coded_size(this.decoder);
87 |
88 | if (this.destination) {
89 | const w = this.functions._mpeg1_decoder_get_width(this.decoder);
90 | const h = this.functions._mpeg1_decoder_get_height(this.decoder);
91 | this.destination.resize(w, h);
92 | }
93 |
94 | if (this.decodeFirstFrame) {
95 | this.decode();
96 | }
97 | }
98 |
99 | decode() {
100 | const startTime = Now();
101 |
102 | if (!this.decoder) {
103 | return false;
104 | }
105 |
106 | const didDecode = this.functions._mpeg1_decoder_decode(this.decoder);
107 | if (!didDecode) {
108 | return false;
109 | }
110 |
111 | // Invoke decode callbacks
112 | if (this.destination) {
113 | const ptrY = this.functions._mpeg1_decoder_get_y_ptr(this.decoder);
114 | const ptrCr = this.functions._mpeg1_decoder_get_cr_ptr(this.decoder);
115 | const ptrCb = this.functions._mpeg1_decoder_get_cb_ptr(this.decoder);
116 |
117 | const dy = this.instance.heapU8.subarray(ptrY, ptrY + this.codedSize);
118 | const dcr = this.instance.heapU8.subarray(ptrCr, ptrCr + (this.codedSize >> 2));
119 | const dcb = this.instance.heapU8.subarray(ptrCb, ptrCb + (this.codedSize >> 2));
120 |
121 | this.destination.render(dy, dcr, dcb, false);
122 | }
123 |
124 | this.advanceDecodedTime(1 / this.frameRate);
125 |
126 | const elapsedTime = Now() - startTime;
127 | if (this.onDecodeCallback) {
128 | this.onDecodeCallback(this, elapsedTime);
129 | }
130 | return true;
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/lib/webaudio.js:
--------------------------------------------------------------------------------
1 | import { Now } from '../utils';
2 |
3 | class WebAudioOut {
4 | constructor() {
5 | this.context = WebAudioOut.CachedContext = WebAudioOut.CachedContext
6 | || new (window.AudioContext || window.webkitAudioContext)();
7 |
8 | this.gain = this.context.createGain();
9 | this.destination = this.gain;
10 |
11 | // Keep track of the number of connections to this AudioContext, so we
12 | // can safely close() it when we're the only one connected to it.
13 | this.gain.connect(this.context.destination);
14 | this.context._connections = (this.context._connections || 0) + 1;
15 |
16 | this.startTime = 0;
17 | this.buffer = null;
18 | this.wallclockStartTime = 0;
19 | this.volume = 1;
20 | this.enabled = true;
21 |
22 | this.unlocked = !WebAudioOut.NeedsUnlocking();
23 |
24 | Object.defineProperty(this, 'enqueuedTime', { get: this.getEnqueuedTime });
25 | }
26 |
27 | destroy() {
28 | this.gain.disconnect();
29 | this.context._connections--;
30 |
31 | if (this.context._connections === 0) {
32 | this.context.close();
33 | WebAudioOut.CachedContext = null;
34 | }
35 | }
36 |
37 | play(sampleRate, left, right) {
38 | if (!this.enabled) {
39 | return;
40 | }
41 |
42 | // If the context is not unlocked yet, we simply advance the start time
43 | // to "fake" actually playing audio. This will keep the video in sync.
44 | if (!this.unlocked) {
45 | const ts = Now();
46 | if (this.wallclockStartTime < ts) {
47 | this.wallclockStartTime = ts;
48 | }
49 | this.wallclockStartTime += left.length / sampleRate;
50 | return;
51 | }
52 |
53 | this.gain.gain.value = this.volume;
54 |
55 | const buffer = this.context.createBuffer(2, left.length, sampleRate);
56 | buffer.getChannelData(0).set(left);
57 | buffer.getChannelData(1).set(right);
58 |
59 | const source = this.context.createBufferSource();
60 | source.buffer = buffer;
61 | source.connect(this.destination);
62 |
63 | const now = this.context.currentTime;
64 | const { duration } = buffer;
65 | if (this.startTime < now) {
66 | this.startTime = now;
67 | this.wallclockStartTime = Now();
68 | }
69 |
70 | source.start(this.startTime);
71 | this.startTime += duration;
72 | this.wallclockStartTime += duration;
73 | }
74 |
75 | stop() {
76 | // Meh; there seems to be no simple way to get a list of currently
77 | // active source nodes from the Audio Context, and maintaining this
78 | // list ourselfs would be a pain, so we just set the gain to 0
79 | // to cut off all enqueued audio instantly.
80 | this.gain.gain.value = 0;
81 | }
82 |
83 | getEnqueuedTime() {
84 | // The AudioContext.currentTime is only updated every so often, so if we
85 | // want to get exact timing, we need to rely on the system time.
86 | return Math.max(this.wallclockStartTime - Now(), 0);
87 | }
88 |
89 | resetEnqueuedTime() {
90 | this.startTime = this.context.currentTime;
91 | this.wallclockStartTime = Now();
92 | }
93 |
94 | unlock(callback) {
95 | if (this.unlocked) {
96 | if (callback) {
97 | callback();
98 | }
99 | return;
100 | }
101 |
102 | this.unlockCallback = callback;
103 |
104 | // Create empty buffer and play it
105 | const buffer = this.context.createBuffer(1, 1, 22050);
106 | const source = this.context.createBufferSource();
107 | source.buffer = buffer;
108 | source.connect(this.destination);
109 |
110 | // polyfill source.start(0);
111 | if (source.start) {
112 | source.start(0);
113 | } else {
114 | source.noteOn(0);
115 | }
116 |
117 | setTimeout(this.checkIfUnlocked.bind(this, source, 0), 0);
118 | }
119 |
120 | checkIfUnlocked(source, attempt) {
121 | if (
122 | source.playbackState === source.PLAYING_STATE
123 | || source.playbackState === source.FINISHED_STATE
124 | ) {
125 | this.unlocked = true;
126 | if (this.unlockCallback) {
127 | this.unlockCallback();
128 | this.unlockCallback = null;
129 | }
130 | } else if (attempt < 10) {
131 | // Jeez, what a shit show. Thanks iOS!
132 | setTimeout(this.checkIfUnlocked.bind(this, source, attempt + 1), 100);
133 | }
134 | }
135 |
136 | static NeedsUnlocking() {
137 | return /iPhone|iPad|iPod/i.test(navigator.userAgent);
138 | }
139 |
140 | static IsSupported() {
141 | return window.AudioContext || window.webkitAudioContext;
142 | }
143 | }
144 |
145 | WebAudioOut.CachedContext = null;
146 |
147 | export default WebAudioOut;
148 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * According to jsmpeg project(https://github.com/phoboslab/jsmpeg)
3 | */
4 |
5 | // ES6 modular
6 | import Player from './lib/player';
7 | import VideoElement from './lib/video-element';
8 | import BitBuffer from './lib/buffer';
9 | import AjaxSource from './lib/ajax';
10 | import FetchSource from './lib/fetch';
11 | import AjaxProgressiveSource from './lib/ajax-progressive';
12 | import WSSource from './lib/websocket';
13 | import TS from './lib/ts';
14 | import BaseDecoder from './lib/decoder';
15 | import MPEG1 from './lib/mpeg1';
16 | import MPEG1WASM from './lib/mpeg1-wasm';
17 | import MP2 from './lib/mp2';
18 | import MP2WASM from './lib/mp2-wasm';
19 | import WebGLRenderer from './lib/webgl';
20 | import CanvasRenderer from './lib/canvas2d';
21 | import WebAudioOut from './lib/webaudio';
22 | import {
23 | Now, CreateVideoElements, Fill, Base64ToArrayBuffer,
24 | } from './utils';
25 | import WASMModule from './lib/wasm-module';
26 | import WASM_BINARY from './lib/wasm/WASM_BINARY';
27 |
28 | // This sets up the JSMpeg "Namespace". The object is empty apart from the Now()
29 | // utility function and the automatic CreateVideoElements() after DOMReady.
30 | const JSMpeg = {
31 | // The Player sets up the connections between source, demuxer, decoders,
32 | // renderer and audio output. It ties everything together, is responsible
33 | // of scheduling decoding and provides some convenience methods for
34 | // external users.
35 | Player,
36 |
37 | // A Video Element wraps the Player, shows HTML controls to start/pause
38 | // the video and handles Audio unlocking on iOS. VideoElements can be
39 | // created directly in HTML using the tag.
40 | VideoElement,
41 |
42 | // The BitBuffer wraps a Uint8Array and allows reading an arbitrary number
43 | // of bits at a time. On writing, the BitBuffer either expands its
44 | // internal buffer (for static files) or deletes old data (for streaming).
45 | BitBuffer,
46 |
47 | // A Source provides raw data from HTTP, a WebSocket connection or any
48 | // other mean. Sources must support the following API:
49 | // .connect(destinationNode)
50 | // .write(buffer)
51 | // .start() - start reading
52 | // .resume(headroom) - continue reading; headroom to play pos in seconds
53 | // .established - boolean, true after connection is established
54 | // .completed - boolean, true if the source is completely loaded
55 | // .progress - float 0-1
56 | Source: {
57 | Ajax: AjaxSource,
58 | AjaxProgressive: AjaxProgressiveSource,
59 | WebSocket: WSSource,
60 | Fetch: FetchSource,
61 | },
62 |
63 | // A Demuxer may sit between a Source and a Decoder. It separates the
64 | // incoming raw data into Video, Audio and other Streams. API:
65 | // .connect(streamId, destinationNode)
66 | // .write(buffer)
67 | // .currentTime – float, in seconds
68 | // .startTime - float, in seconds
69 | Demuxer: {
70 | TS,
71 | },
72 |
73 | // A Decoder accepts an incoming Stream of raw Audio or Video data, buffers
74 | // it and upon `.decode()` decodes a single frame of data. Video decoders
75 | // call `destinationNode.render(Y, Cr, CB)` with the decoded pixel data;
76 | // Audio decoders call `destinationNode.play(left, right)` with the decoded
77 | // PCM data. API:
78 | // .connect(destinationNode)
79 | // .write(pts, buffer)
80 | // .decode()
81 | // .seek(time)
82 | // .currentTime - float, in seconds
83 | // .startTime - float, in seconds
84 | Decoder: {
85 | Base: BaseDecoder,
86 | MPEG1Video: MPEG1,
87 | MPEG1VideoWASM: MPEG1WASM,
88 | MP2Audio: MP2,
89 | MP2AudioWASM: MP2WASM,
90 | },
91 |
92 | // A Renderer accepts raw YCrCb data in 3 separate buffers via the render()
93 | // method. Renderers typically convert the data into the RGBA color space
94 | // and draw it on a Canvas, but other output - such as writing PNGs - would
95 | // be conceivable. API:
96 | // .render(y, cr, cb) - pixel data as Uint8Arrays
97 | // .enabled - wether the renderer does anything upon receiving data
98 | Renderer: {
99 | WebGL: WebGLRenderer,
100 | Canvas2D: CanvasRenderer,
101 | },
102 |
103 | // Audio Outputs accept raw Stero PCM data in 2 separate buffers via the
104 | // play() method. Outputs typically play the audio on the user's device.
105 | // API:
106 | // .play(sampleRate, left, right) - rate in herz; PCM data as Uint8Arrays
107 | // .stop()
108 | // .enqueuedTime - float, in seconds
109 | // .enabled - wether the output does anything upon receiving data
110 | AudioOutput: {
111 | WebAudio: WebAudioOut,
112 | },
113 |
114 | WASMModule,
115 |
116 | // functions
117 | Now,
118 | CreateVideoElements,
119 | Fill,
120 | Base64ToArrayBuffer,
121 |
122 | // The build process may append `JSMpeg.WASM_BINARY_INLINED = base64data;`
123 | // to the minified source.
124 | // If this property is present, jsmpeg will use the inlined binary data
125 | // instead of trying to load a jsmpeg.wasm file via Ajax.
126 | WASM_BINARY_INLINED: WASM_BINARY,
127 | };
128 |
129 | export default JSMpeg;
130 |
--------------------------------------------------------------------------------
/src/lib/buffer.js:
--------------------------------------------------------------------------------
1 | class BitBuffer {
2 | constructor(bufferOrLength, mode) {
3 | if (typeof bufferOrLength === 'object') {
4 | this.bytes = bufferOrLength instanceof Uint8Array
5 | ? bufferOrLength
6 | : new Uint8Array(bufferOrLength);
7 |
8 | this.byteLength = this.bytes.length;
9 | } else {
10 | this.bytes = new Uint8Array(bufferOrLength || 1024 * 1024);
11 | this.byteLength = 0;
12 | }
13 |
14 | this.mode = mode || BitBuffer.MODE.EXPAND;
15 | this.index = 0;
16 | }
17 |
18 | resize(size) {
19 | const newBytes = new Uint8Array(size);
20 | if (this.byteLength !== 0) {
21 | this.byteLength = Math.min(this.byteLength, size);
22 | newBytes.set(this.bytes, 0, this.byteLength);
23 | }
24 | this.bytes = newBytes;
25 | this.index = Math.min(this.index, this.byteLength << 3);
26 | }
27 |
28 | evict(sizeNeeded) {
29 | const bytePos = this.index >> 3;
30 | const available = this.bytes.length - this.byteLength;
31 |
32 | // If the current index is the write position, we can simply reset both
33 | // to 0. Also reset (and throw away yet unread data) if we won't be able
34 | // to fit the new data in even after a normal eviction.
35 | if (
36 | this.index === this.byteLength << 3
37 | || sizeNeeded > available + bytePos // emergency evac
38 | ) {
39 | this.byteLength = 0;
40 | this.index = 0;
41 | return;
42 | } if (bytePos === 0) {
43 | // Nothing read yet - we can't evict anything
44 | return;
45 | }
46 |
47 | // Some browsers don't support copyWithin() yet - we may have to do
48 | // it manually using set and a subarray
49 | if (this.bytes.copyWithin) {
50 | this.bytes.copyWithin(0, bytePos, this.byteLength);
51 | } else {
52 | this.bytes.set(this.bytes.subarray(bytePos, this.byteLength));
53 | }
54 |
55 | this.byteLength -= bytePos;
56 | this.index -= bytePos << 3;
57 | }
58 |
59 | write(buffers) {
60 | const isArrayOfBuffers = typeof buffers[0] === 'object';
61 | let totalLength = 0;
62 | const available = this.bytes.length - this.byteLength;
63 | // Calculate total byte length
64 | if (isArrayOfBuffers) {
65 | totalLength = 0;
66 | for (let i = 0; i < buffers.length; i++) {
67 | totalLength += buffers[i].byteLength;
68 | }
69 | } else {
70 | totalLength = buffers.byteLength;
71 | }
72 |
73 | // Do we need to resize or evict?
74 | if (totalLength > available) {
75 | if (this.mode === BitBuffer.MODE.EXPAND) {
76 | const newSize = Math.max(this.bytes.length * 2, totalLength - available);
77 | this.resize(newSize);
78 | } else {
79 | this.evict(totalLength);
80 | }
81 | }
82 |
83 | if (isArrayOfBuffers) {
84 | for (let i = 0; i < buffers.length; i++) {
85 | this.appendSingleBuffer(buffers[i]);
86 | }
87 | } else {
88 | this.appendSingleBuffer(buffers);
89 | }
90 |
91 | return totalLength;
92 | }
93 |
94 | appendSingleBuffer(buffer) {
95 | buffer = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
96 |
97 | this.bytes.set(buffer, this.byteLength);
98 | this.byteLength += buffer.length;
99 | }
100 |
101 | findNextStartCode() {
102 | for (let i = (this.index + 7) >> 3; i < this.byteLength; i++) {
103 | if (this.bytes[i] === 0x00 && this.bytes[i + 1] === 0x00 && this.bytes[i + 2] === 0x01) {
104 | this.index = (i + 4) << 3;
105 | return this.bytes[i + 3];
106 | }
107 | }
108 | this.index = this.byteLength << 3;
109 | return -1;
110 | }
111 |
112 | findStartCode(code) {
113 | const current = this.findNextStartCode();
114 | if (current === code || current === -1) {
115 | return current;
116 | }
117 | return -1;
118 | }
119 |
120 | nextBytesAreStartCode() {
121 | const i = (this.index + 7) >> 3;
122 | return (
123 | i >= this.byteLength
124 | || (this.bytes[i] === 0x00 && this.bytes[i + 1] === 0x00 && this.bytes[i + 2] === 0x01)
125 | );
126 | }
127 |
128 | peek(count) {
129 | let offset = this.index;
130 | let value = 0;
131 | while (count) {
132 | const currentByte = this.bytes[offset >> 3];
133 | const remaining = 8 - (offset & 7); // remaining bits in byte
134 | const read = remaining < count ? remaining : count; // bits in this run
135 | const shift = remaining - read;
136 | const mask = 0xff >> (8 - read);
137 |
138 | value = (value << read) | ((currentByte & (mask << shift)) >> shift);
139 |
140 | offset += read;
141 | count -= read;
142 | }
143 |
144 | return value;
145 | }
146 |
147 | read(count) {
148 | const value = this.peek(count);
149 | this.index += count;
150 | return value;
151 | }
152 |
153 | skip(count) {
154 | // eslint-disable-next-line no-return-assign
155 | return (this.index += count);
156 | }
157 |
158 | rewind(count) {
159 | this.index = Math.max(this.index - count, 0);
160 | }
161 |
162 | has(count) {
163 | return (this.byteLength << 3) - this.index >= count;
164 | }
165 | }
166 |
167 | BitBuffer.MODE = {
168 | EVICT: 1,
169 | EXPAND: 2,
170 | };
171 |
172 | export default BitBuffer;
173 |
--------------------------------------------------------------------------------
/docs/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [6.1.2](https://github.com/cycjimmy/jsmpeg-player/compare/v6.1.1...v6.1.2) (2024-10-25)
2 |
3 |
4 | ### Bug Fixes
5 |
6 | * use @cycjimmy/sass-lib@3 ([5c3e8ba](https://github.com/cycjimmy/jsmpeg-player/commit/5c3e8bae89cde7d03f6cb0b9fd6d47335138198e))
7 |
8 | ## [6.1.1](https://github.com/cycjimmy/jsmpeg-player/compare/v6.1.0...v6.1.1) (2024-07-16)
9 |
10 |
11 | ### Bug Fixes
12 |
13 | * upgrade rollup to v4 ([8de1ea0](https://github.com/cycjimmy/jsmpeg-player/commit/8de1ea0288dff53f39d4c443ac802e8fc6403b63))
14 |
15 | # [6.1.0](https://github.com/cycjimmy/jsmpeg-player/compare/v6.0.5...v6.1.0) (2024-07-16)
16 |
17 |
18 | ### Features
19 |
20 | * use dart-sass instead of node sass ([cbc2457](https://github.com/cycjimmy/jsmpeg-player/commit/cbc2457eef46f20a83c998fe527f2d241171baad))
21 |
22 | ## [6.0.5](https://github.com/cycjimmy/jsmpeg-player/compare/v6.0.4...v6.0.5) (2022-09-22)
23 |
24 |
25 | ### Bug Fixes
26 |
27 | * fix problems with destroying WebGL context ([558c02e](https://github.com/cycjimmy/jsmpeg-player/commit/558c02e5dd13ead15d7eabf9a130de2adaba1c05))
28 |
29 | ## [6.0.4](https://github.com/cycjimmy/jsmpeg-player/compare/v6.0.3...v6.0.4) (2022-09-05)
30 |
31 |
32 | ### Bug Fixes
33 |
34 | * catch lost context when creating the WebGL renderer ([6a6fcc6](https://github.com/cycjimmy/jsmpeg-player/commit/6a6fcc6c75363f43f04ed2d3c980338c6095d9f6))
35 | * fix WebSocket constructor chocking on empty protocol string ([4caa818](https://github.com/cycjimmy/jsmpeg-player/commit/4caa8181bdabd32565ce2b8c44b4282460bc0e60))
36 |
37 | ## [6.0.3](https://github.com/cycjimmy/jsmpeg-player/compare/v6.0.2...v6.0.3) (2022-06-21)
38 |
39 |
40 | ### Bug Fixes
41 |
42 | * remove @rollup/plugin-commonjs ([15d5cfe](https://github.com/cycjimmy/jsmpeg-player/commit/15d5cfe8afb817b83541d44448f6d345e0f8acb8))
43 |
44 | ## [6.0.2](https://github.com/cycjimmy/jsmpeg-player/compare/v6.0.1...v6.0.2) (2022-03-26)
45 |
46 |
47 | ### Bug Fixes
48 |
49 | * fix root version after releasing ([7cf298a](https://github.com/cycjimmy/jsmpeg-player/commit/7cf298abe67e664651a1122f70dac638187cfed1))
50 |
51 | ## [6.0.1](https://github.com/cycjimmy/jsmpeg-player/compare/v6.0.0...v6.0.1) (2022-03-25)
52 |
53 |
54 | ### Bug Fixes
55 |
56 | * fix root version after releasing ([d586ed2](https://github.com/cycjimmy/jsmpeg-player/commit/d586ed2ff98784c5d686dbf82665ef61bb1f4899))
57 |
58 | # [6.0.0](https://github.com/cycjimmy/jsmpeg-player/compare/v5.1.1...v6.0.0) (2022-03-25)
59 |
60 |
61 | ### Features
62 |
63 | * change to module mode ([cff7d05](https://github.com/cycjimmy/jsmpeg-player/commit/cff7d057d4a33cb6eeb35669638f2b17c8bec33f))
64 |
65 |
66 | ### BREAKING CHANGES
67 |
68 | * change to module mode
69 |
70 | ## [5.1.1](https://github.com/cycjimmy/jsmpeg-player/compare/v5.1.0...v5.1.1) (2022-03-15)
71 |
72 |
73 | ### Bug Fixes
74 |
75 | * fix race condition where WASM-Module is instantiated twice; and rebuild ([490801a](https://github.com/cycjimmy/jsmpeg-player/commit/490801a9605c883944b5ff53c3900d9c56ea469c))
76 | * fix typo (https://github.com/phoboslab/jsmpeg/commit/55886464d289623af9c9dd39e8080a29a0719591) ([d3cc3a5](https://github.com/cycjimmy/jsmpeg-player/commit/d3cc3a5a580170c0d3847dbc46a1904f59f77a8a))
77 | * handle WebGL contextLost ([b04df7e](https://github.com/cycjimmy/jsmpeg-player/commit/b04df7e128be71c87fbc95b4e22acf8776f668fc))
78 |
79 | # [5.1.0](https://github.com/cycjimmy/jsmpeg-player/compare/v5.0.1...v5.1.0) (2022-03-15)
80 |
81 |
82 | ### Features
83 |
84 | * **deps:** upgrade deps ([2cfdb8e](https://github.com/cycjimmy/jsmpeg-player/commit/2cfdb8e26a58c3c1f6e4f6b66a6b36bde388c1b9))
85 |
86 | ## [5.0.1](https://github.com/cycjimmy/jsmpeg-player/compare/v5.0.0...v5.0.1) (2020-05-12)
87 |
88 |
89 | ### Bug Fixes
90 |
91 | * **mp2:** fix audio only using the right channel for volume ([628844f](https://github.com/cycjimmy/jsmpeg-player/commit/628844febcc75ed6857e421becfbf8fafe72216d)), closes [#24](https://github.com/cycjimmy/jsmpeg-player/issues/24) [#24](https://github.com/cycjimmy/jsmpeg-player/issues/24)
92 |
93 | # [5.0.0](https://github.com/cycjimmy/jsmpeg-player/compare/v4.0.4...v5.0.0) (2020-01-19)
94 |
95 |
96 | ### Features
97 |
98 | * use rollup refactor project ([1d04cd5](https://github.com/cycjimmy/jsmpeg-player/commit/1d04cd5b1589e7481207ca4c45d4a39eddbd673c))
99 |
100 |
101 | ### BREAKING CHANGES
102 |
103 | * use rollup refactor project
104 |
105 | ## [4.0.4](https://github.com/cycjimmy/jsmpeg-player/compare/v4.0.3...v4.0.4) (2020-01-09)
106 |
107 |
108 | ### Bug Fixes
109 |
110 | * fix error `JSMpeg is not defined` ([556be62](https://github.com/cycjimmy/jsmpeg-player/commit/556be621890382d2cebdff89a15ace30af1bd364))
111 | * **upgrade:** update from origin jsmpeg ([9dd8098](https://github.com/cycjimmy/jsmpeg-player/commit/9dd8098c46d88161efdf6334ddc81c621be02b93))
112 |
113 | ## [4.0.3](https://github.com/cycjimmy/jsmpeg-player/compare/v4.0.2...v4.0.3) (2019-10-24)
114 |
115 |
116 | ### Bug Fixes
117 |
118 | * **build:** replace "uglifyjs" with "terser" ([b55be86](https://github.com/cycjimmy/jsmpeg-player/commit/b55be862c794d41ce5c88898f7f54406dc9bc3e3))
119 |
120 | ## [4.0.2](https://github.com/cycjimmy/jsmpeg-player/compare/v4.0.1...v4.0.2) (2019-10-23)
121 |
122 |
123 | ### Bug Fixes
124 |
125 | * **release:** start to use semantic release ([fa9c554](https://github.com/cycjimmy/jsmpeg-player/commit/fa9c554cb9a0e4c2bb161e47c7267009387452ec))
126 |
--------------------------------------------------------------------------------
/src/lib/wasm-module.js:
--------------------------------------------------------------------------------
1 | import AjaxSource from './ajax';
2 |
3 | export default class WASM {
4 | constructor() {
5 | this.stackSize = 5 * 1024 * 1024; // emscripten default
6 | this.pageSize = 64 * 1024; // wasm page size
7 | this.onInitCallbacks = [];
8 | this.ready = false;
9 | this.loadingFromFileStarted = false;
10 | this.loadingFromBufferStarted = false;
11 | }
12 |
13 | write(buffer) {
14 | this.loadFromBuffer(buffer);
15 | }
16 |
17 | loadFromFile(url, callback) {
18 | if (callback) {
19 | this.onInitCallbacks.push(callback);
20 | }
21 |
22 | // Make sure this WASM Module is only instantiated once. If loadFromFile()
23 | // was already called, bail out here. On instantiation all pending
24 | // onInitCallbacks will be called.
25 | if (this.loadingFromFileStarted) {
26 | return;
27 | }
28 | this.loadingFromFileStarted = true;
29 |
30 | this.onInitCallback = callback;
31 | const ajax = new AjaxSource(url, {});
32 | ajax.connect(this);
33 | ajax.start();
34 | }
35 |
36 | loadFromBuffer(buffer, callback) {
37 | if (callback) {
38 | this.onInitCallbacks.push(callback);
39 | }
40 |
41 | // Make sure this WASM Module is only instantiated once. If loadFromBuffer()
42 | // was already called, bail out here. On instantiation all pending
43 | // onInitCallbacks will be called.
44 | if (this.loadingFromBufferStarted) {
45 | return;
46 | }
47 | this.loadingFromBufferStarted = true;
48 |
49 | this.moduleInfo = this.readDylinkSection(buffer);
50 | if (!this.moduleInfo) {
51 | for (let i = 0; i < this.onInitCallbacks.length; i++) {
52 | this.onInitCallbacks[i](null);
53 | }
54 | return;
55 | }
56 |
57 | this.memory = new WebAssembly.Memory({ initial: 256 });
58 | const env = {
59 | memory: this.memory,
60 | memoryBase: 0,
61 | __memory_base: 0,
62 | table: new WebAssembly.Table({ initial: this.moduleInfo.tableSize, element: 'anyfunc' }),
63 | tableBase: 0,
64 | __table_base: 0,
65 | abort: this.c_abort.bind(this),
66 | ___assert_fail: this.c_assertFail.bind(this),
67 | _sbrk: this.c_sbrk.bind(this),
68 | };
69 |
70 | this.brk = this.align(this.moduleInfo.memorySize + this.stackSize);
71 | WebAssembly.instantiate(buffer, { env }).then((results) => {
72 | this.instance = results.instance;
73 | if (this.instance.exports.__post_instantiate) {
74 | this.instance.exports.__post_instantiate();
75 | }
76 | this.createHeapViews();
77 | this.ready = true;
78 | for (let i = 0; i < this.onInitCallbacks.length; i++) {
79 | this.onInitCallbacks[i](this);
80 | }
81 | });
82 | }
83 |
84 | createHeapViews() {
85 | this.instance.heapU8 = new Uint8Array(this.memory.buffer);
86 | this.instance.heapU32 = new Uint32Array(this.memory.buffer);
87 | this.instance.heapF32 = new Float32Array(this.memory.buffer);
88 | }
89 |
90 | align(addr) {
91 | // eslint-disable-next-line no-restricted-properties
92 | const a = 2 ** this.moduleInfo.memoryAlignment;
93 | return Math.ceil(addr / a) * a;
94 | }
95 |
96 | c_sbrk(size) {
97 | const previousBrk = this.brk;
98 | this.brk += size;
99 |
100 | if (this.brk > this.memory.buffer.byteLength) {
101 | const bytesNeeded = this.brk - this.memory.buffer.byteLength;
102 | const pagesNeeded = Math.ceil(bytesNeeded / this.pageSize);
103 | this.memory.grow(pagesNeeded);
104 | this.createHeapViews();
105 | }
106 | return previousBrk;
107 | }
108 |
109 | // eslint-disable-next-line no-unused-vars,class-methods-use-this
110 | c_abort(size) {
111 | // eslint-disable-next-line prefer-rest-params
112 | console.warn('JSMPeg: WASM abort', arguments);
113 | }
114 |
115 | // eslint-disable-next-line no-unused-vars,class-methods-use-this
116 | c_assertFail(size) {
117 | // eslint-disable-next-line prefer-rest-params
118 | console.warn('JSMPeg: WASM ___assert_fail', arguments);
119 | }
120 |
121 | // eslint-disable-next-line class-methods-use-this
122 | readDylinkSection(buffer) {
123 | // Read the WASM header and dylink section of the .wasm binary data
124 | // to get the needed table size and static data size.
125 |
126 | // https://github.com/WebAssembly/tool-conventions/blob/master/DynamicLinking.md
127 | // https://github.com/kripken/emscripten/blob/20602efb955a7c6c20865a495932427e205651d2/src/support.js
128 |
129 | const bytes = new Uint8Array(buffer);
130 | let next = 0;
131 |
132 | const readVarUint = () => {
133 | let ret = 0;
134 | let mul = 1;
135 | // eslint-disable-next-line no-constant-condition
136 | while (1) {
137 | const byte = bytes[next++];
138 | ret += (byte & 0x7f) * mul;
139 | mul *= 0x80;
140 | if (!(byte & 0x80)) {
141 | return ret;
142 | }
143 | }
144 | };
145 |
146 | const matchNextBytes = (expected) => {
147 | for (let i = 0; i < expected.length; i++) {
148 | const b = typeof expected[i] === 'string' ? expected[i].charCodeAt(0) : expected[i];
149 | if (bytes[next++] !== b) {
150 | return false;
151 | }
152 | }
153 | return true;
154 | };
155 |
156 | // Make sure we have a wasm header
157 | if (!matchNextBytes([0, 'a', 's', 'm'])) {
158 | console.warn('JSMpeg: WASM header not found');
159 | return null;
160 | }
161 |
162 | // Make sure we have a dylink section
163 | next = 9;
164 | // eslint-disable-next-line no-unused-vars
165 | const sectionSize = readVarUint();
166 | if (!matchNextBytes([6, 'd', 'y', 'l', 'i', 'n', 'k'])) {
167 | console.warn('JSMpeg: No dylink section found in WASM');
168 | return null;
169 | }
170 |
171 | return {
172 | memorySize: readVarUint(),
173 | memoryAlignment: readVarUint(),
174 | tableSize: readVarUint(),
175 | tableAlignment: readVarUint(),
176 | };
177 | }
178 |
179 | static IsSupported() {
180 | return !!window.WebAssembly;
181 | }
182 |
183 | static GetModule() {
184 | WASM.CACHED_MODULE = WASM.CACHED_MODULE || new WASM();
185 | return WASM.CACHED_MODULE;
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JSMpeg Player(TS Player)
2 |
3 | ![][workflows-badge-image]
4 | [![libraries dependency status][libraries-status-image]][libraries-status-url]
5 | [![libraries sourcerank][libraries-sourcerank-image]][libraries-sourcerank-url]
6 | [![Release date][release-date-image]][release-url]
7 | [![rollup][rollup-image]][rollup-url]
8 | [![semantic-release][semantic-image]][semantic-url]
9 | [![npm license][license-image]][download-url]
10 |
11 |
12 | * **[jsmpeg-player](https://github.com/cycdpo/jsmpeg-player) has been renamed to @cycjimmy/jsmpeg-player for scoped NPM package.**
13 | * JSMpeg player is based on [jsmpeg](https://github.com/phoboslab/jsmpeg).
14 | * The video must be compressed into the TS format of MPEG1 / MP2.
15 | * Apple device automatically plays without sound, you need to guide the user to click on the video in the lower right corner of the video icon to unlock the sound. (no similar problem in non-autoplay mode)
16 | * [Demo][github-pages-url]
17 |
18 | ## How to use
19 | ### Install
20 | [![NPM version][npm-image]][npm-url]
21 | [![NPM bundle size][npm-bundle-size-image]][npm-url]
22 | [![npm download][download-image]][download-url]
23 |
24 | ```shell
25 | $ npm install @cycjimmy/jsmpeg-player --save
26 | # or
27 | $ yarn add @cycjimmy/jsmpeg-player
28 | ```
29 |
30 | ### Usage
31 | ```javascript
32 | import JSMpeg from '@cycjimmy/jsmpeg-player';
33 | # OR
34 | const JSMpeg = require('@cycjimmy/jsmpeg-player');
35 | ```
36 |
37 | ```javascript
38 | new JSMpeg.VideoElement(videoWrapper, videoUrl [, options] [, overlayOptions])
39 | ```
40 |
41 | * `JSMpeg.VideoElement` config:
42 | * `videoWrapper`: [String | Element] The wrapper of the video. The height and width of the wrapper are recommended to be initialized.
43 | * `videoUrl`: [String] A URL to an MPEG .ts file
44 | * `options`: [Object] support:
45 | * `canvas`: [String | Element] The HTML canvas element to use for video rendering. If none is given, the renderer will create its own canvas element. Default `''`.
46 | * `poster`: [String] URL to an image to use as the poster to show before the video plays. (Recommended to set it manually)
47 | * `autoplay`: [Boolean] Whether to start playing immediately. Default `false`.
48 | * `autoSetWrapperSize`: [Boolean] Whether to set the wrapper element size automatically when the video loaded. Default `false`.
49 | * `loop`: [Boolean] Whether to loop the video (static files only). Default `false`.**[overwrite]**
50 | * `control`: [Boolean] Whether the user can control. Default `true`.
51 | * `decodeFirstFrame`: [Boolean] Whether to decode and display the first frame of the video. Default `true`.
52 | * `picMode`: [Boolean] Picture mode (no playButton). Default `false`.
53 | * `progressive`: [Boolean] whether to load data in chunks (static files only). Default `true`.
54 | * `chunkSize` [Number] The chunk size in bytes to load at a time. Default `1024*1024` (1mb).
55 | * `hooks`: [Object] The hook function
56 | * `play`: [Function] The hook function when the video play.
57 | * `pause`: [Function] The hook function when the video pause.
58 | * `stop`: [Function] The hook function when the video stop.
59 | * `load`: [Function] The hook function when the video established.
60 | * `overlayOptions`: [Object] More options can view the [jsmpeg options](https://github.com/phoboslab/jsmpeg#usage)
61 |
62 | * `JSMpeg.VideoElement` instance supports the following methods:
63 | * `play()`: Start playback
64 | * `pause()`: Pause playback
65 | * `stop()`: Stop playback and seek to the beginning
66 | * `destroy()`: Stop playback and empty video wrapper
67 | * `JSMpeg.VideoElement.player` instance API can view the [JSMpeg.Player API](https://github.com/phoboslab/jsmpeg#jsmpegplayer-api)
68 |
69 | ### Use in browser
70 | [![jsdelivr][jsdelivr-image]][jsdelivr-url]
71 |
72 | ```html
73 |
74 |
75 |
79 | ```
80 |
81 | ## CDN
82 | To use via a CDN include this in your HTML:
83 | ```text
84 |
85 | ```
86 |
87 | ## Encoding Video/Audio for [jsmpeg](https://github.com/phoboslab/jsmpeg) by [ffmpeg](https://ffmpeg.org/). E.g:
88 | ```shell
89 | $ ffmpeg -i input.mp4 -f mpegts \
90 | -codec:v mpeg1video -s 640x360 -b:v 700k -r 25 -bf 0 \
91 | -codec:a mp2 -ar 44100 -ac 1 -b:a 64k \
92 | output.ts
93 | ```
94 |
95 | * options
96 | * `-s`: video size
97 | * `-b:v`: video bit rate
98 | * `-r`: frame rate
99 | * `-ar`: sampling rate
100 | * `-ac`: number of audio channels
101 | * `-b:a`: audio bit rate
102 |
103 |
104 |
105 | [npm-image]: https://img.shields.io/npm/v/@cycjimmy/jsmpeg-player
106 | [npm-url]: https://npmjs.org/package/@cycjimmy/jsmpeg-player
107 | [npm-bundle-size-image]: https://img.shields.io/bundlephobia/min/@cycjimmy/jsmpeg-player
108 |
109 | [download-image]: https://img.shields.io/npm/dt/@cycjimmy/jsmpeg-player
110 | [download-url]: https://npmjs.org/package/@cycjimmy/jsmpeg-player
111 |
112 | [jsdelivr-image]: https://img.shields.io/jsdelivr/npm/hy/@cycjimmy/jsmpeg-player
113 | [jsdelivr-url]: https://www.jsdelivr.com/package/npm/@cycjimmy/jsmpeg-player
114 |
115 | [workflows-badge-image]: https://github.com/cycjimmy/jsmpeg-player/workflows/Test%20CI/badge.svg
116 |
117 | [libraries-status-image]: https://img.shields.io/librariesio/release/npm/@cycjimmy/jsmpeg-player
118 | [libraries-sourcerank-image]: https://img.shields.io/librariesio/sourcerank/npm/@cycjimmy/jsmpeg-player
119 | [libraries-status-url]: https://libraries.io/github/cycjimmy/jsmpeg-player
120 | [libraries-sourcerank-url]: https://libraries.io/npm/@cycjimmy%2Fjsmpeg-player
121 |
122 | [release-date-image]: https://img.shields.io/github/release-date/cycjimmy/jsmpeg-player
123 | [release-url]: https://github.com/cycjimmy/jsmpeg-player/releases
124 |
125 | [rollup-image]: https://img.shields.io/github/package-json/dependency-version/cycjimmy/jsmpeg-player/dev/rollup
126 | [rollup-url]: https://github.com/rollup/rollup
127 |
128 | [semantic-image]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
129 | [semantic-url]: https://github.com/semantic-release/semantic-release
130 |
131 | [license-image]: https://img.shields.io/npm/l/@cycjimmy/jsmpeg-player
132 |
133 | [github-pages-url]: https://cycjimmy.github.io/jsmpeg-player/
134 |
--------------------------------------------------------------------------------
/src/lib/ts.js:
--------------------------------------------------------------------------------
1 | import BitBuffer from './buffer';
2 |
3 | class TS {
4 | constructor() {
5 | this.bits = null;
6 | this.leftoverBytes = null;
7 |
8 | this.guessVideoFrameEnd = true;
9 | this.pidsToStreamIds = {};
10 |
11 | this.pesPacketInfo = {};
12 | this.startTime = 0;
13 | this.currentTime = 0;
14 | }
15 |
16 | connect(streamId, destination) {
17 | this.pesPacketInfo[streamId] = {
18 | destination,
19 | currentLength: 0,
20 | totalLength: 0,
21 | pts: 0,
22 | buffers: [],
23 | };
24 | }
25 |
26 | write(buffer) {
27 | if (this.leftoverBytes) {
28 | const totalLength = buffer.byteLength + this.leftoverBytes.byteLength;
29 | this.bits = new BitBuffer(totalLength);
30 | this.bits.write([this.leftoverBytes, buffer]);
31 | } else {
32 | this.bits = new BitBuffer(buffer);
33 | }
34 |
35 | // eslint-disable-next-line no-empty
36 | while (this.bits.has(188 << 3) && this.parsePacket()) {}
37 |
38 | const leftoverCount = this.bits.byteLength - (this.bits.index >> 3);
39 | this.leftoverBytes = leftoverCount > 0 ? this.bits.bytes.subarray(this.bits.index >> 3) : null;
40 | }
41 |
42 | parsePacket() {
43 | // Check if we're in sync with packet boundaries; attempt to resync if not.
44 | if (this.bits.read(8) !== 0x47) {
45 | if (!this.resync()) {
46 | // Couldn't resync; maybe next time...
47 | return false;
48 | }
49 | }
50 |
51 | const end = (this.bits.index >> 3) + 187;
52 | // eslint-disable-next-line no-unused-vars
53 | const transportError = this.bits.read(1);
54 | const payloadStart = this.bits.read(1);
55 | // eslint-disable-next-line no-unused-vars
56 | const transportPriority = this.bits.read(1);
57 | const pid = this.bits.read(13);
58 | // eslint-disable-next-line no-unused-vars
59 | const transportScrambling = this.bits.read(2);
60 | const adaptationField = this.bits.read(2);
61 | // eslint-disable-next-line no-unused-vars
62 | const continuityCounter = this.bits.read(4);
63 |
64 | // If this is the start of a new payload; signal the end of the previous
65 | // frame, if we didn't do so already.
66 | let streamId = this.pidsToStreamIds[pid];
67 | if (payloadStart && streamId) {
68 | const pi = this.pesPacketInfo[streamId];
69 | if (pi && pi.currentLength) {
70 | this.packetComplete(pi);
71 | }
72 | }
73 |
74 | // Extract current payload
75 | if (adaptationField & 0x1) {
76 | if (adaptationField & 0x2) {
77 | const adaptationFieldLength = this.bits.read(8);
78 | this.bits.skip(adaptationFieldLength << 3);
79 | }
80 |
81 | if (payloadStart && this.bits.nextBytesAreStartCode()) {
82 | this.bits.skip(24);
83 | streamId = this.bits.read(8);
84 | this.pidsToStreamIds[pid] = streamId;
85 |
86 | const packetLength = this.bits.read(16);
87 | this.bits.skip(8);
88 | const ptsDtsFlag = this.bits.read(2);
89 | this.bits.skip(6);
90 | const headerLength = this.bits.read(8);
91 | const payloadBeginIndex = this.bits.index + (headerLength << 3);
92 |
93 | const pi = this.pesPacketInfo[streamId];
94 | if (pi) {
95 | let pts = 0;
96 | if (ptsDtsFlag & 0x2) {
97 | // The Presentation Timestamp is encoded as 33(!) bit
98 | // integer, but has a "marker bit" inserted at weird places
99 | // in between, making the whole thing 5 bytes in size.
100 | // You can't make this shit up...
101 | this.bits.skip(4);
102 | const p32_30 = this.bits.read(3);
103 | this.bits.skip(1);
104 | const p29_15 = this.bits.read(15);
105 | this.bits.skip(1);
106 | const p14_0 = this.bits.read(15);
107 | this.bits.skip(1);
108 |
109 | // Can't use bit shifts here; we need 33 bits of precision,
110 | // so we're using JavaScript's double number type. Also
111 | // divide by the 90khz clock to get the pts in seconds.
112 | pts = (p32_30 * 1073741824 + p29_15 * 32768 + p14_0) / 90000;
113 |
114 | this.currentTime = pts;
115 | if (this.startTime === -1) {
116 | this.startTime = pts;
117 | }
118 | }
119 |
120 | const payloadLength = packetLength ? packetLength - headerLength - 3 : 0;
121 | this.packetStart(pi, pts, payloadLength);
122 | }
123 |
124 | // Skip the rest of the header without parsing it
125 | this.bits.index = payloadBeginIndex;
126 | }
127 |
128 | if (streamId) {
129 | // Attempt to detect if the PES packet is complete. For Audio (and
130 | // other) packets, we received a total packet length with the PES
131 | // header, so we can check the current length.
132 |
133 | // For Video packets, we have to guess the end by detecting if this
134 | // TS packet was padded - there's no good reason to pad a TS packet
135 | // in between, but it might just fit exactly. If this fails, we can
136 | // only wait for the next PES header for that stream.
137 |
138 | const pi = this.pesPacketInfo[streamId];
139 | if (pi) {
140 | const start = this.bits.index >> 3;
141 | const complete = this.packetAddData(pi, start, end);
142 |
143 | const hasPadding = !payloadStart && adaptationField & 0x2;
144 | if (complete || (this.guessVideoFrameEnd && hasPadding)) {
145 | this.packetComplete(pi);
146 | }
147 | }
148 | }
149 | }
150 |
151 | this.bits.index = end << 3;
152 | return true;
153 | }
154 |
155 | resync() {
156 | // Check if we have enough data to attempt a resync. We need 5 full packets.
157 | if (!this.bits.has((188 * 6) << 3)) {
158 | return false;
159 | }
160 |
161 | const byteIndex = this.bits.index >> 3;
162 |
163 | // Look for the first sync token in the first 187 bytes
164 | for (let i = 0; i < 187; i++) {
165 | if (this.bits.bytes[byteIndex + i] === 0x47) {
166 | // Look for 4 more sync tokens, each 188 bytes appart
167 | let foundSync = true;
168 | for (let j = 1; j < 5; j++) {
169 | if (this.bits.bytes[byteIndex + i + 188 * j] !== 0x47) {
170 | foundSync = false;
171 | break;
172 | }
173 | }
174 |
175 | if (foundSync) {
176 | this.bits.index = (byteIndex + i + 1) << 3;
177 | return true;
178 | }
179 | }
180 | }
181 |
182 | // In theory, we shouldn't arrive here. If we do, we had enough data but
183 | // still didn't find sync - this can only happen if we were fed garbage
184 | // data. Check your source!
185 | console.warn('JSMpeg: Possible garbage data. Skipping.');
186 | this.bits.skip(187 << 3);
187 | return false;
188 | }
189 |
190 | // eslint-disable-next-line class-methods-use-this
191 | packetStart(pi, pts, payloadLength) {
192 | pi.totalLength = payloadLength;
193 | pi.currentLength = 0;
194 | pi.pts = pts;
195 | }
196 |
197 | packetAddData(pi, start, end) {
198 | pi.buffers.push(this.bits.bytes.subarray(start, end));
199 | pi.currentLength += end - start;
200 |
201 | return pi.totalLength !== 0 && pi.currentLength >= pi.totalLength;
202 | }
203 |
204 | // eslint-disable-next-line class-methods-use-this
205 | packetComplete(pi) {
206 | pi.destination.write(pi.pts, pi.buffers);
207 | pi.totalLength = 0;
208 | pi.currentLength = 0;
209 | pi.buffers = [];
210 | }
211 | }
212 |
213 | TS.STREAM = {
214 | PACK_HEADER: 0xba,
215 | SYSTEM_HEADER: 0xbb,
216 | PROGRAM_MAP: 0xbc,
217 | PRIVATE_1: 0xbd,
218 | PADDING: 0xbe,
219 | PRIVATE_2: 0xbf,
220 | AUDIO_1: 0xc0,
221 | VIDEO_1: 0xe0,
222 | DIRECTORY: 0xff,
223 | };
224 |
225 | export default TS;
226 |
--------------------------------------------------------------------------------
/src/lib/video-element.js:
--------------------------------------------------------------------------------
1 | // utils
2 | import isString from '@cycjimmy/awesome-js-funcs/esm/judgeBasic/isString';
3 | import functionToPromise from '@cycjimmy/awesome-js-funcs/esm/typeConversion/functionToPromise';
4 |
5 | // style
6 | import _style from '../theme/style.scss';
7 | // button view
8 | import { PLAY_BUTTON, UNMUTE_BUTTON } from '../buttonView';
9 |
10 | // service
11 | import Player from './player';
12 |
13 | export default class VideoElement {
14 | constructor(
15 | wrapper,
16 | videoUrl,
17 | {
18 | canvas = '',
19 | poster = '',
20 | autoplay = false,
21 | autoSetWrapperSize = false,
22 | loop = false,
23 | control = true,
24 | decodeFirstFrame = true,
25 | picMode = false,
26 | progressive = true,
27 | chunkSize = 1024 * 1024,
28 | hooks = {},
29 | } = {},
30 | overlayOptions = {},
31 | ) {
32 | this.options = {
33 | videoUrl,
34 | canvas,
35 | poster,
36 | picMode,
37 | autoplay,
38 | autoSetWrapperSize,
39 | loop,
40 | control,
41 | decodeFirstFrame,
42 | progressive,
43 | chunkSize,
44 | hooks: {
45 | play: () => {},
46 | pause: () => {},
47 | stop: () => {},
48 | load: () => {},
49 | ...hooks,
50 | },
51 | ...overlayOptions,
52 | };
53 |
54 | this.options.needPlayButton = this.options.control && !this.options.picMode;
55 |
56 | this.player = null;
57 |
58 | // Setup canvas and play button
59 | this.els = {
60 | wrapper: isString(wrapper) ? document.querySelector(wrapper) : wrapper,
61 | canvas: null,
62 | playButton: document.createElement('div'),
63 | unmuteButton: null,
64 | poster: null,
65 | };
66 |
67 | if (window.getComputedStyle(this.els.wrapper).getPropertyValue('position') === 'static') {
68 | this.els.wrapper.style.position = 'relative';
69 | }
70 |
71 | this.els.wrapper.clientRect = this.els.wrapper.getBoundingClientRect();
72 |
73 | this.initCanvas();
74 | this.initPlayButton();
75 | this.initPlayer();
76 | }
77 |
78 | initCanvas() {
79 | if (this.options.canvas) {
80 | this.els.canvas = isString(this.options.canvas)
81 | ? document.querySelector(this.options.canvas)
82 | : this.options.canvas;
83 | } else {
84 | this.els.canvas = document.createElement('canvas');
85 | this.els.canvas.classList.add(_style.canvas);
86 | this.els.wrapper.appendChild(this.els.canvas);
87 | }
88 | }
89 |
90 | initPlayer() {
91 | // Parse the data-options - we try to decode the values as json. This way
92 | // we can get proper boolean and number values. If JSON.parse() fails,
93 | // treat it as a string.
94 | this.options = Object.assign(this.options, {
95 | canvas: this.els.canvas,
96 | });
97 |
98 | // eslint-disable-next-line no-underscore-dangle
99 | const _options = { ...this.options, autoplay: false };
100 |
101 | // Create the player instance
102 | this.player = new Player(this.options.videoUrl, _options, {
103 | play: () => {
104 | if (this.options.needPlayButton) {
105 | this.els.playButton.classList.add(_style.hidden);
106 | }
107 |
108 | if (this.els.poster) {
109 | this.els.poster.classList.add(_style.hidden);
110 | }
111 |
112 | this.options.hooks.play();
113 | },
114 | pause: () => {
115 | if (this.options.needPlayButton) {
116 | this.els.playButton.classList.remove(_style.hidden);
117 | }
118 |
119 | this.options.hooks.pause();
120 | },
121 | stop: () => {
122 | if (this.els.poster) {
123 | this.els.poster.classList.remove(_style.hidden);
124 | }
125 |
126 | this.options.hooks.stop();
127 | },
128 | load: () => {
129 | if (this.options.autoplay) {
130 | this.play();
131 | }
132 |
133 | this._autoSetWrapperSize();
134 | this.options.hooks.load();
135 | },
136 | });
137 |
138 | this._copyPlayerFuncs();
139 | this.els.wrapper.playerInstance = this.player;
140 |
141 | // Setup the poster element, if any
142 | if (this.options.poster && !this.options.autoplay && !this.player.options.streaming) {
143 | this.options.decodeFirstFrame = false;
144 | this.els.poster = new Image();
145 | this.els.poster.src = this.options.poster;
146 | this.els.poster.classList.add(_style.poster);
147 | this.els.wrapper.appendChild(this.els.poster);
148 | }
149 |
150 | // Add the click handler if this video is pausable
151 | if (!this.player.options.streaming) {
152 | this.els.wrapper.addEventListener('click', this.onClick.bind(this));
153 | }
154 |
155 | // Hide the play button if this video immediately begins playing
156 | if (this.options.autoplay || this.player.options.streaming) {
157 | this.els.playButton.classList.add(_style.hidden);
158 | }
159 |
160 | // Set up the unlock audio button for iOS devices. iOS only allows us to
161 | // play audio after a user action has initiated playing. For autoplay or
162 | // streaming players we set up a muted speaker icon as the button. For all
163 | // others, we can simply use the play button.
164 | if (this.player.audioOut && !this.player.audioOut.unlocked) {
165 | let unlockAudioElement = this.els.wrapper;
166 |
167 | if (this.options.autoplay || this.player.options.streaming) {
168 | this.els.unmuteButton = document.createElement('div');
169 | this.els.unmuteButton.innerHTML = UNMUTE_BUTTON;
170 | this.els.unmuteButton.classList.add(_style.unmuteButton);
171 | this.els.wrapper.appendChild(this.els.unmuteButton);
172 | unlockAudioElement = this.els.unmuteButton;
173 | }
174 |
175 | this.unlockAudioBound = this.onUnlockAudio.bind(this, unlockAudioElement);
176 | unlockAudioElement.addEventListener('touchstart', this.unlockAudioBound, false);
177 | unlockAudioElement.addEventListener('click', this.unlockAudioBound, true);
178 | }
179 | }
180 |
181 | initPlayButton() {
182 | if (!this.options.needPlayButton) {
183 | return;
184 | }
185 |
186 | this.els.playButton.classList.add(_style.playButton);
187 | this.els.playButton.innerHTML = PLAY_BUTTON;
188 | this.els.wrapper.appendChild(this.els.playButton);
189 | }
190 |
191 | _autoSetWrapperSize() {
192 | if (!this.options.autoSetWrapperSize) {
193 | return Promise.resolve();
194 | }
195 |
196 | const { destination } = this.player.video;
197 |
198 | if (!destination) {
199 | return Promise.resolve();
200 | }
201 |
202 | return Promise.resolve().then(() => functionToPromise(() => {
203 | this.els.wrapper.style.width = `${destination.width}px`;
204 | this.els.wrapper.style.height = `${destination.height}px`;
205 | }));
206 | }
207 |
208 | onUnlockAudio(element, ev) {
209 | if (this.els.unmuteButton) {
210 | ev.preventDefault();
211 | ev.stopPropagation();
212 | }
213 | this.player.audioOut.unlock(() => {
214 | if (this.els.unmuteButton) {
215 | this.els.unmuteButton.classList.add(_style.hidden);
216 | }
217 | element.removeEventListener('touchstart', this.unlockAudioBound);
218 | element.removeEventListener('click', this.unlockAudioBound);
219 | });
220 | }
221 |
222 | onClick() {
223 | if (!this.options.control) {
224 | return;
225 | }
226 |
227 | if (this.player.isPlaying) {
228 | this.pause();
229 | } else {
230 | this.play();
231 | }
232 | }
233 |
234 | /**
235 | * copy player functions
236 | * @private
237 | */
238 | _copyPlayerFuncs() {
239 | this.play = () => this.player.play();
240 | this.pause = () => this.player.pause();
241 | this.stop = () => this.player.stop();
242 | this.destroy = () => {
243 | this.player.destroy();
244 | this.els.wrapper.innerHTML = '';
245 | this.els.wrapper.playerInstance = null;
246 | };
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/src/lib/webgl.js:
--------------------------------------------------------------------------------
1 | class WebGLRenderer {
2 | constructor(options) {
3 | if (options.canvas) {
4 | this.canvas = options.canvas;
5 | this.ownsCanvasElement = false;
6 | } else {
7 | this.canvas = document.createElement('canvas');
8 | this.ownsCanvasElement = true;
9 | }
10 | this.width = this.canvas.width;
11 | this.height = this.canvas.height;
12 | this.enabled = true;
13 |
14 | this.hasTextureData = {};
15 |
16 | const contextCreateOptions = {
17 | preserveDrawingBuffer: !!options.preserveDrawingBuffer,
18 | alpha: false,
19 | depth: false,
20 | stencil: false,
21 | antialias: false,
22 | premultipliedAlpha: false,
23 | };
24 |
25 | this.gl = this.canvas.getContext('webgl', contextCreateOptions)
26 | || this.canvas.getContext('experimental-webgl', contextCreateOptions);
27 |
28 | if (!this.gl) {
29 | throw new Error('Failed to get WebGL Context');
30 | }
31 |
32 | this.handleContextLostBound = this.handleContextLost.bind(this);
33 | this.handleContextRestoredBound = this.handleContextRestored.bind(this);
34 |
35 | this.canvas.addEventListener('webglcontextlost', this.handleContextLostBound, false);
36 | this.canvas.addEventListener('webglcontextrestored', this.handleContextRestoredBound, false);
37 |
38 | this.initGL();
39 | }
40 |
41 | initGL() {
42 | this.hasTextureData = {};
43 |
44 | const { gl } = this;
45 | let vertexAttr = null;
46 |
47 | gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
48 |
49 | // Init buffers
50 | this.vertexBuffer = gl.createBuffer();
51 | const vertexCoords = new Float32Array([0, 0, 0, 1, 1, 0, 1, 1]);
52 | gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
53 | gl.bufferData(gl.ARRAY_BUFFER, vertexCoords, gl.STATIC_DRAW);
54 |
55 | // Setup the main YCrCbToRGBA shader
56 | this.program = this.createProgram(
57 | WebGLRenderer.SHADER.VERTEX_IDENTITY,
58 | WebGLRenderer.SHADER.FRAGMENT_YCRCB_TO_RGBA,
59 | );
60 | vertexAttr = gl.getAttribLocation(this.program, 'vertex');
61 | gl.enableVertexAttribArray(vertexAttr);
62 | gl.vertexAttribPointer(vertexAttr, 2, gl.FLOAT, false, 0, 0);
63 |
64 | this.textureY = this.createTexture(0, 'textureY');
65 | this.textureCb = this.createTexture(1, 'textureCb');
66 | this.textureCr = this.createTexture(2, 'textureCr');
67 |
68 | // Setup the loading animation shader
69 | this.loadingProgram = this.createProgram(
70 | WebGLRenderer.SHADER.VERTEX_IDENTITY,
71 | WebGLRenderer.SHADER.FRAGMENT_LOADING,
72 | );
73 | vertexAttr = gl.getAttribLocation(this.loadingProgram, 'vertex');
74 | gl.enableVertexAttribArray(vertexAttr);
75 | gl.vertexAttribPointer(vertexAttr, 2, gl.FLOAT, false, 0, 0);
76 |
77 | this.shouldCreateUnclampedViews = !this.allowsClampedTextureData();
78 | }
79 |
80 | handleContextLost(ev) {
81 | ev.preventDefault();
82 | this.contextLost = true;
83 | }
84 |
85 | handleContextRestored() {
86 | this.initGL();
87 | }
88 |
89 | destroy() {
90 | const { gl } = this;
91 |
92 | this.deleteTexture(gl.TEXTURE0, this.textureY);
93 | this.deleteTexture(gl.TEXTURE1, this.textureCb);
94 | this.deleteTexture(gl.TEXTURE2, this.textureCr);
95 |
96 | gl.useProgram(null);
97 | gl.deleteProgram(this.program);
98 | gl.deleteProgram(this.loadingProgram);
99 |
100 | gl.bindBuffer(gl.ARRAY_BUFFER, null);
101 | gl.deleteBuffer(this.vertexBuffer);
102 |
103 | this.canvas.removeEventListener('webglcontextlost', this.handleContextLostBound, false);
104 | this.canvas.removeEventListener('webglcontextrestored', this.handleContextRestoredBound, false);
105 |
106 | if (this.ownsCanvasElement) {
107 | this.canvas.remove();
108 | }
109 | }
110 |
111 | resize(width, height) {
112 | this.width = width | 0;
113 | this.height = height | 0;
114 |
115 | this.canvas.width = this.width;
116 | this.canvas.height = this.height;
117 |
118 | this.gl.useProgram(this.program);
119 | const codedWidth = ((this.width + 15) >> 4) << 4;
120 | this.gl.viewport(0, 0, codedWidth, this.height);
121 | }
122 |
123 | createTexture(index, name) {
124 | const { gl } = this;
125 | const texture = gl.createTexture();
126 |
127 | gl.bindTexture(gl.TEXTURE_2D, texture);
128 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
129 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
130 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
131 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
132 | gl.uniform1i(gl.getUniformLocation(this.program, name), index);
133 |
134 | return texture;
135 | }
136 |
137 | createProgram(vsh, fsh) {
138 | const { gl } = this;
139 | const program = gl.createProgram();
140 |
141 | gl.attachShader(program, this.compileShader(gl.VERTEX_SHADER, vsh));
142 | gl.attachShader(program, this.compileShader(gl.FRAGMENT_SHADER, fsh));
143 | gl.linkProgram(program);
144 | gl.useProgram(program);
145 |
146 | return program;
147 | }
148 |
149 | compileShader(type, source) {
150 | const { gl } = this;
151 | const shader = gl.createShader(type);
152 | gl.shaderSource(shader, source);
153 | gl.compileShader(shader);
154 |
155 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
156 | throw new Error(gl.getShaderInfoLog(shader));
157 | }
158 |
159 | return shader;
160 | }
161 |
162 | allowsClampedTextureData() {
163 | const { gl } = this;
164 | const texture = gl.createTexture();
165 |
166 | gl.bindTexture(gl.TEXTURE_2D, texture);
167 | gl.texImage2D(
168 | gl.TEXTURE_2D,
169 | 0,
170 | gl.LUMINANCE,
171 | 1,
172 | 1,
173 | 0,
174 | gl.LUMINANCE,
175 | gl.UNSIGNED_BYTE,
176 | new Uint8ClampedArray([0]),
177 | );
178 | return gl.getError() === 0;
179 | }
180 |
181 | renderProgress(progress) {
182 | const { gl } = this;
183 |
184 | gl.useProgram(this.loadingProgram);
185 |
186 | const loc = gl.getUniformLocation(this.loadingProgram, 'progress');
187 | gl.uniform1f(loc, progress);
188 |
189 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
190 | }
191 |
192 | render(y, cb, cr, isClampedArray) {
193 | if (!this.enabled) {
194 | return;
195 | }
196 |
197 | const { gl } = this;
198 | const w = ((this.width + 15) >> 4) << 4;
199 | const h = this.height;
200 | const w2 = w >> 1;
201 | const h2 = h >> 1;
202 |
203 | // In some browsers WebGL doesn't like Uint8ClampedArrays (this is a bug
204 | // and should be fixed soon-ish), so we have to create a Uint8Array view
205 | // for each plane.
206 | if (isClampedArray && this.shouldCreateUnclampedViews) {
207 | y = new Uint8Array(y.buffer);
208 | cb = new Uint8Array(cb.buffer);
209 | cr = new Uint8Array(cr.buffer);
210 | }
211 |
212 | gl.useProgram(this.program);
213 |
214 | this.updateTexture(gl.TEXTURE0, this.textureY, w, h, y);
215 | this.updateTexture(gl.TEXTURE1, this.textureCb, w2, h2, cb);
216 | this.updateTexture(gl.TEXTURE2, this.textureCr, w2, h2, cr);
217 |
218 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
219 | }
220 |
221 | updateTexture(unit, texture, w, h, data) {
222 | const { gl } = this;
223 | gl.activeTexture(unit);
224 | gl.bindTexture(gl.TEXTURE_2D, texture);
225 |
226 | if (this.hasTextureData[unit]) {
227 | gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, w, h, gl.LUMINANCE, gl.UNSIGNED_BYTE, data);
228 | } else {
229 | this.hasTextureData[unit] = true;
230 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, w, h, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, data);
231 | }
232 | }
233 |
234 | deleteTexture(unit, texture) {
235 | const { gl } = this;
236 | gl.activeTexture(unit);
237 | gl.bindTexture(gl.TEXTURE_2D, null);
238 | gl.deleteTexture(texture);
239 | }
240 |
241 | static IsSupported() {
242 | try {
243 | if (!window.WebGLRenderingContext) {
244 | return false;
245 | }
246 |
247 | const canvas = document.createElement('canvas');
248 | return !!(canvas.getContext('webgl') || canvas.getContext('experimental-webgl'));
249 | } catch (err) {
250 | return false;
251 | }
252 | }
253 | }
254 |
255 | WebGLRenderer.SHADER = {
256 | FRAGMENT_YCRCB_TO_RGBA: [
257 | 'precision mediump float;',
258 | 'uniform sampler2D textureY;',
259 | 'uniform sampler2D textureCb;',
260 | 'uniform sampler2D textureCr;',
261 | 'varying vec2 texCoord;',
262 |
263 | 'mat4 rec601 = mat4(',
264 | '1.16438, 0.00000, 1.59603, -0.87079,',
265 | '1.16438, -0.39176, -0.81297, 0.52959,',
266 | '1.16438, 2.01723, 0.00000, -1.08139,',
267 | '0, 0, 0, 1',
268 | ');',
269 |
270 | 'void main() {',
271 | 'float y = texture2D(textureY, texCoord).r;',
272 | 'float cb = texture2D(textureCb, texCoord).r;',
273 | 'float cr = texture2D(textureCr, texCoord).r;',
274 |
275 | 'gl_FragColor = vec4(y, cr, cb, 1.0) * rec601;',
276 | '}',
277 | ].join('\n'),
278 |
279 | FRAGMENT_LOADING: [
280 | 'precision mediump float;',
281 | 'uniform float progress;',
282 | 'varying vec2 texCoord;',
283 |
284 | 'void main() {',
285 | 'float c = ceil(progress-(1.0-texCoord.y));',
286 | 'gl_FragColor = vec4(c,c,c,1);',
287 | '}',
288 | ].join('\n'),
289 |
290 | VERTEX_IDENTITY: [
291 | 'attribute vec2 vertex;',
292 | 'varying vec2 texCoord;',
293 |
294 | 'void main() {',
295 | 'texCoord = vertex;',
296 | 'gl_Position = vec4((vertex * 2.0 - 1.0) * vec2(1, -1), 0.0, 1.0);',
297 | '}',
298 | ].join('\n'),
299 | };
300 |
301 | export default WebGLRenderer;
302 |
--------------------------------------------------------------------------------
/src/lib/player.js:
--------------------------------------------------------------------------------
1 | import { Now, Base64ToArrayBuffer } from '../utils';
2 |
3 | import AjaxSource from './ajax';
4 | import AjaxProgressiveSource from './ajax-progressive';
5 | import WSSource from './websocket';
6 | import TS from './ts';
7 | import MPEG1 from './mpeg1';
8 | import MPEG1WASM from './mpeg1-wasm';
9 | import MP2 from './mp2';
10 | import MP2WASM from './mp2-wasm';
11 | import WebGLRenderer from './webgl';
12 | import CanvasRenderer from './canvas2d';
13 | import WebAudioOut from './webaudio';
14 | import WASMModule from './wasm-module';
15 | import WASM_BINARY from './wasm/WASM_BINARY';
16 |
17 | export default class Player {
18 | /**
19 | * @param url
20 | * @param options
21 | * @param hooks (play: function, pause: function, stop: function) 插入UI回调
22 | * @constructor
23 | */
24 | constructor(url, options = {}, hooks = {}) {
25 | this.options = options;
26 |
27 | this.hooks = hooks;
28 | this.options.hookOnEstablished = () => {
29 | if (this.hooks.load) {
30 | this.hooks.load();
31 | }
32 | };
33 |
34 | if (options.source) {
35 | // eslint-disable-next-line new-cap
36 | this.source = new options.source(url, this.options);
37 | options.streaming = !!this.source.streaming;
38 | } else if (url.match(/^wss?:\/\//)) {
39 | this.source = new WSSource(url, this.options);
40 | options.streaming = true;
41 | } else if (options.progressive) {
42 | this.source = new AjaxProgressiveSource(url, this.options);
43 | options.streaming = false;
44 | } else {
45 | this.source = new AjaxSource(url, this.options);
46 | options.streaming = false;
47 | }
48 |
49 | this.maxAudioLag = options.maxAudioLag || 0.25;
50 | this.loop = options.loop !== false;
51 | this.autoplay = !!options.autoplay || options.streaming;
52 |
53 | this.demuxer = new TS(options);
54 | this.source.connect(this.demuxer);
55 |
56 | if (!options.disableWebAssembly && WASMModule.IsSupported()) {
57 | this.wasmModule = WASMModule.GetModule();
58 | options.wasmModule = this.wasmModule;
59 | }
60 |
61 | if (options.video !== false) {
62 | this.video = options.wasmModule ? new MPEG1WASM(options) : new MPEG1(options);
63 |
64 | this.renderer = !options.disableGl && WebGLRenderer.IsSupported()
65 | ? new WebGLRenderer(options)
66 | : new CanvasRenderer(options);
67 |
68 | this.demuxer.connect(TS.STREAM.VIDEO_1, this.video);
69 | this.video.connect(this.renderer);
70 | }
71 |
72 | if (options.audio !== false && WebAudioOut.IsSupported()) {
73 | this.audio = options.wasmModule ? new MP2WASM(options) : new MP2(options);
74 | this.audioOut = new WebAudioOut(options);
75 | this.demuxer.connect(TS.STREAM.AUDIO_1, this.audio);
76 | this.audio.connect(this.audioOut);
77 | }
78 |
79 | Object.defineProperty(this, 'currentTime', {
80 | get: this.getCurrentTime,
81 | set: this.setCurrentTime,
82 | });
83 | Object.defineProperty(this, 'volume', {
84 | get: this.getVolume,
85 | set: this.setVolume,
86 | });
87 |
88 | this.paused = true;
89 | this.unpauseOnShow = false;
90 | if (options.pauseWhenHidden !== false) {
91 | document.addEventListener('visibilitychange', this.showHide.bind(this));
92 | }
93 |
94 | // If we have WebAssembly support, wait until the module is compiled before
95 | // loading the source. Otherwise the decoders won't know what to do with
96 | // the source data.
97 | if (this.wasmModule) {
98 | if (this.wasmModule.ready) {
99 | this.startLoading();
100 | } else if (WASM_BINARY) {
101 | const wasm = Base64ToArrayBuffer(WASM_BINARY);
102 | this.wasmModule.loadFromBuffer(wasm, this.startLoading.bind(this));
103 | } else {
104 | this.wasmModule.loadFromFile('jsmpeg.wasm', this.startLoading.bind(this));
105 | }
106 | } else {
107 | this.startLoading();
108 | }
109 | }
110 |
111 | startLoading() {
112 | this.source.start();
113 | if (this.autoplay) {
114 | this.play();
115 | }
116 | }
117 |
118 | showHide() {
119 | if (document.visibilityState === 'hidden') {
120 | this.unpauseOnShow = this.wantsToPlay;
121 | this.pause();
122 | } else if (this.unpauseOnShow) {
123 | this.play();
124 | }
125 | }
126 |
127 | play() {
128 | if (this.animationId) {
129 | return;
130 | }
131 |
132 | this.animationId = requestAnimationFrame(this.update.bind(this));
133 | this.wantsToPlay = true;
134 | this.paused = false;
135 | }
136 |
137 | pause() {
138 | if (this.paused) {
139 | return;
140 | }
141 |
142 | cancelAnimationFrame(this.animationId);
143 | this.animationId = null;
144 | this.wantsToPlay = false;
145 | this.isPlaying = false;
146 | this.paused = true;
147 |
148 | if (this.audio && this.audio.canPlay) {
149 | // Seek to the currentTime again - audio may already be enqueued a bit
150 | // further, so we have to rewind it.
151 | this.audioOut.stop();
152 | this.seek(this.currentTime);
153 | }
154 |
155 | if (this.hooks.pause) {
156 | this.hooks.pause();
157 | }
158 |
159 | if (this.options.onPause) {
160 | this.options.onPause(this);
161 | }
162 | }
163 |
164 | getVolume() {
165 | return this.audioOut ? this.audioOut.volume : 0;
166 | }
167 |
168 | setVolume(volume) {
169 | if (this.audioOut) {
170 | this.audioOut.volume = volume;
171 | }
172 | }
173 |
174 | stop() {
175 | this.pause();
176 | this.seek(0);
177 | if (this.video && this.options.decodeFirstFrame !== false) {
178 | this.video.decode();
179 | }
180 |
181 | if (this.hooks.stop) {
182 | this.hooks.stop();
183 | }
184 | }
185 |
186 | destroy() {
187 | this.pause();
188 | this.source.destroy();
189 | this.video && this.video.destroy();
190 | this.renderer && this.renderer.destroy();
191 | this.audio && this.audio.destroy();
192 | this.audioOut && this.audioOut.destroy();
193 | }
194 |
195 | seek(time) {
196 | const startOffset = this.audio && this.audio.canPlay
197 | ? this.audio.startTime
198 | : this.video.startTime;
199 |
200 | if (this.video) {
201 | this.video.seek(time + startOffset);
202 | }
203 | if (this.audio) {
204 | this.audio.seek(time + startOffset);
205 | }
206 |
207 | this.startTime = Now() - time;
208 | }
209 |
210 | getCurrentTime() {
211 | return this.audio && this.audio.canPlay
212 | ? this.audio.currentTime - this.audio.startTime
213 | : this.video.currentTime - this.video.startTime;
214 | }
215 |
216 | setCurrentTime(time) {
217 | this.seek(time);
218 | }
219 |
220 | update() {
221 | this.animationId = requestAnimationFrame(this.update.bind(this));
222 |
223 | if (!this.source.established) {
224 | if (this.renderer) {
225 | this.renderer.renderProgress(this.source.progress);
226 | }
227 | return;
228 | }
229 |
230 | if (!this.isPlaying) {
231 | this.isPlaying = true;
232 | this.startTime = Now() - this.currentTime;
233 |
234 | if (this.hooks.play) {
235 | this.hooks.play();
236 | }
237 |
238 | if (this.options.onPlay) {
239 | this.options.onPlay(this);
240 | }
241 | }
242 |
243 | if (this.options.streaming) {
244 | this.updateForStreaming();
245 | } else {
246 | this.updateForStaticFile();
247 | }
248 | }
249 |
250 | nextFrame() {
251 | if (this.source.established && this.video) {
252 | return this.video.decode();
253 | }
254 | return false;
255 | }
256 |
257 | updateForStreaming() {
258 | // When streaming, immediately decode everything we have buffered up until
259 | // now to minimize playback latency.
260 |
261 | if (this.video) {
262 | this.video.decode();
263 | }
264 |
265 | if (this.audio) {
266 | let decoded = false;
267 | do {
268 | // If there's a lot of audio enqueued already, disable output and
269 | // catch up with the encoding.
270 | if (this.audioOut.enqueuedTime > this.maxAudioLag) {
271 | this.audioOut.resetEnqueuedTime();
272 | this.audioOut.enabled = false;
273 | }
274 | decoded = this.audio.decode();
275 | } while (decoded);
276 | this.audioOut.enabled = true;
277 | }
278 | }
279 |
280 | updateForStaticFile() {
281 | let notEnoughData = false;
282 | let headroom = 0;
283 |
284 | // If we have an audio track, we always try to sync the video to the audio.
285 | // Gaps and discontinuities are far more percetable in audio than in video.
286 |
287 | if (this.audio && this.audio.canPlay) {
288 | // Do we have to decode and enqueue some more audio data?
289 | while (!notEnoughData && this.audio.decodedTime - this.audio.currentTime < 0.25) {
290 | notEnoughData = !this.audio.decode();
291 | }
292 |
293 | // Sync video to audio
294 | if (this.video && this.video.currentTime < this.audio.currentTime) {
295 | notEnoughData = !this.video.decode();
296 | }
297 |
298 | headroom = this.demuxer.currentTime - this.audio.currentTime;
299 | } else if (this.video) {
300 | // Video only - sync it to player's wallclock
301 | const targetTime = Now() - this.startTime + this.video.startTime;
302 | const lateTime = targetTime - this.video.currentTime;
303 | const frameTime = 1 / this.video.frameRate;
304 |
305 | if (this.video && lateTime > 0) {
306 | // If the video is too far behind (>2 frames), simply reset the
307 | // target time to the next frame instead of trying to catch up.
308 | if (lateTime > frameTime * 2) {
309 | this.startTime += lateTime;
310 | }
311 |
312 | notEnoughData = !this.video.decode();
313 | }
314 |
315 | headroom = this.demuxer.currentTime - targetTime;
316 | }
317 |
318 | // Notify the source of the playhead headroom, so it can decide whether to
319 | // continue loading further data.
320 | this.source.resume(headroom);
321 |
322 | // If we failed to decode and the source is complete, it means we reached
323 | // the end of our data. We may want to loop.
324 | if (notEnoughData && this.source.completed) {
325 | if (this.loop) {
326 | this.seek(0);
327 | } else {
328 | // this.pause();
329 | this.stop();
330 |
331 | if (this.options.onEnded) {
332 | this.options.onEnded(this);
333 | }
334 | }
335 | } else if (notEnoughData && this.options.onStalled) {
336 | // If there's not enough data and the source is not completed, we have
337 | // just stalled.
338 | this.options.onStalled(this);
339 | }
340 | }
341 | }
342 |
--------------------------------------------------------------------------------
/src/lib/mp2.js:
--------------------------------------------------------------------------------
1 | // Based on kjmp2 by Martin J. Fiedler
2 | // http://keyj.emphy.de/kjmp2/
3 |
4 | import { Now, Fill } from '../utils';
5 |
6 | import BaseDecoder from './decoder';
7 | import BitBuffer from './buffer';
8 |
9 | class MP2 extends BaseDecoder {
10 | constructor(options) {
11 | super(options);
12 |
13 | this.onDecodeCallback = options.onAudioDecode;
14 |
15 | const bufferSize = options.audioBufferSize || 128 * 1024;
16 | const bufferMode = options.streaming ? BitBuffer.MODE.EVICT : BitBuffer.MODE.EXPAND;
17 |
18 | this.bits = new BitBuffer(bufferSize, bufferMode);
19 |
20 | this.left = new Float32Array(1152);
21 | this.right = new Float32Array(1152);
22 | this.sampleRate = 44100;
23 |
24 | this.D = new Float32Array(1024);
25 | this.D.set(MP2.SYNTHESIS_WINDOW, 0);
26 | this.D.set(MP2.SYNTHESIS_WINDOW, 512);
27 | this.V = [new Float32Array(1024), new Float32Array(1024)];
28 | this.U = new Int32Array(32);
29 | this.VPos = 0;
30 |
31 | this.allocation = [new Array(32), new Array(32)];
32 | this.scaleFactorInfo = [new Uint8Array(32), new Uint8Array(32)];
33 | this.scaleFactor = [new Array(32), new Array(32)];
34 | this.sample = [new Array(32), new Array(32)];
35 |
36 | for (let j = 0; j < 2; j++) {
37 | for (let i = 0; i < 32; i++) {
38 | this.scaleFactor[j][i] = [0, 0, 0];
39 | this.sample[j][i] = [0, 0, 0];
40 | }
41 | }
42 | }
43 |
44 | decode() {
45 | const startTime = Now();
46 |
47 | const pos = this.bits.index >> 3;
48 | if (pos >= this.bits.byteLength) {
49 | return false;
50 | }
51 |
52 | const decoded = this.decodeFrame(this.left, this.right);
53 | this.bits.index = (pos + decoded) << 3;
54 |
55 | if (!decoded) {
56 | return false;
57 | }
58 |
59 | if (this.destination) {
60 | this.destination.play(this.sampleRate, this.left, this.right);
61 | }
62 |
63 | this.advanceDecodedTime(this.left.length / this.sampleRate);
64 |
65 | const elapsedTime = Now() - startTime;
66 | if (this.onDecodeCallback) {
67 | this.onDecodeCallback(this, elapsedTime);
68 | }
69 |
70 | return true;
71 | }
72 |
73 | getCurrentTime() {
74 | const enqueuedTime = this.destination ? this.destination.enqueuedTime : 0;
75 | return this.decodedTime - enqueuedTime;
76 | }
77 |
78 | decodeFrame(left, right) {
79 | // Check for valid header: syncword OK, MPEG-Audio Layer 2
80 | const sync = this.bits.read(11);
81 | const version = this.bits.read(2);
82 | const layer = this.bits.read(2);
83 | const hasCRC = !this.bits.read(1);
84 |
85 | if (sync !== MP2.FRAME_SYNC || version !== MP2.VERSION.MPEG_1 || layer !== MP2.LAYER.II) {
86 | // Invalid header or unsupported version
87 | return 0;
88 | }
89 |
90 | let bitrateIndex = this.bits.read(4) - 1;
91 | if (bitrateIndex > 13) {
92 | // Invalid bit rate or 'free format'
93 | return 0;
94 | }
95 |
96 | let sampleRateIndex = this.bits.read(2);
97 | let sampleRate = MP2.SAMPLE_RATE[sampleRateIndex];
98 | if (sampleRateIndex === 3) {
99 | // Invalid sample rate
100 | return 0;
101 | }
102 | if (version === MP2.VERSION.MPEG_2) {
103 | sampleRateIndex += 4;
104 | bitrateIndex += 14;
105 | }
106 | const padding = this.bits.read(1);
107 | // eslint-disable-next-line no-unused-vars
108 | const privat = this.bits.read(1);
109 | const mode = this.bits.read(2);
110 |
111 | // Parse the mode_extension, set up the stereo bound
112 | let bound = 0;
113 | if (mode === MP2.MODE.JOINT_STEREO) {
114 | bound = (this.bits.read(2) + 1) << 2;
115 | } else {
116 | this.bits.skip(2);
117 | bound = mode === MP2.MODE.MONO ? 0 : 32;
118 | }
119 |
120 | // Discard the last 4 bits of the header and the CRC value, if present
121 | this.bits.skip(4);
122 | if (hasCRC) {
123 | this.bits.skip(16);
124 | }
125 |
126 | // Compute the frame size
127 | const bitrate = MP2.BIT_RATE[bitrateIndex];
128 | sampleRate = MP2.SAMPLE_RATE[sampleRateIndex];
129 | const frameSize = ((144000 * bitrate) / sampleRate + padding) | 0;
130 |
131 | // Prepare the quantizer table lookups
132 | let tab3 = 0;
133 | let sblimit = 0;
134 | if (version === MP2.VERSION.MPEG_2) {
135 | // MPEG-2 (LSR)
136 | tab3 = 2;
137 | sblimit = 30;
138 | } else {
139 | // MPEG-1
140 | const tab1 = mode === MP2.MODE.MONO ? 0 : 1;
141 | const tab2 = MP2.QUANT_LUT_STEP_1[tab1][bitrateIndex];
142 | tab3 = MP2.QUANT_LUT_STEP_2[tab2][sampleRateIndex];
143 | sblimit = tab3 & 63;
144 | tab3 >>= 6;
145 | }
146 |
147 | if (bound > sblimit) {
148 | bound = sblimit;
149 | }
150 |
151 | // Read the allocation information
152 | for (let sb = 0; sb < bound; sb++) {
153 | this.allocation[0][sb] = this.readAllocation(sb, tab3);
154 | this.allocation[1][sb] = this.readAllocation(sb, tab3);
155 | }
156 |
157 | for (let sb = bound; sb < sblimit; sb++) {
158 | this.allocation[0][sb] = this.allocation[1][sb] = this.readAllocation(sb, tab3);
159 | }
160 |
161 | // Read scale factor selector information
162 | const channels = mode === MP2.MODE.MONO ? 1 : 2;
163 | for (let sb = 0; sb < sblimit; sb++) {
164 | for (let ch = 0; ch < channels; ch++) {
165 | if (this.allocation[ch][sb]) {
166 | this.scaleFactorInfo[ch][sb] = this.bits.read(2);
167 | }
168 | }
169 | if (mode === MP2.MODE.MONO) {
170 | this.scaleFactorInfo[1][sb] = this.scaleFactorInfo[0][sb];
171 | }
172 | }
173 |
174 | // Read scale factors
175 | for (let sb = 0; sb < sblimit; sb++) {
176 | for (let ch = 0; ch < channels; ch++) {
177 | if (this.allocation[ch][sb]) {
178 | const sf = this.scaleFactor[ch][sb];
179 | switch (this.scaleFactorInfo[ch][sb]) {
180 | case 0:
181 | sf[0] = this.bits.read(6);
182 | sf[1] = this.bits.read(6);
183 | sf[2] = this.bits.read(6);
184 | break;
185 | case 1:
186 | sf[0] = sf[1] = this.bits.read(6);
187 | sf[2] = this.bits.read(6);
188 | break;
189 | case 2:
190 | sf[0] = sf[1] = sf[2] = this.bits.read(6);
191 | break;
192 | case 3:
193 | sf[0] = this.bits.read(6);
194 | sf[1] = sf[2] = this.bits.read(6);
195 | break;
196 | default:
197 | }
198 | }
199 | }
200 | if (mode === MP2.MODE.MONO) {
201 | // eslint-disable-next-line prefer-destructuring
202 | this.scaleFactor[1][sb][0] = this.scaleFactor[0][sb][0];
203 | // eslint-disable-next-line prefer-destructuring
204 | this.scaleFactor[1][sb][1] = this.scaleFactor[0][sb][1];
205 | // eslint-disable-next-line prefer-destructuring
206 | this.scaleFactor[1][sb][2] = this.scaleFactor[0][sb][2];
207 | }
208 | }
209 |
210 | // Coefficient input and reconstruction
211 | let outPos = 0;
212 | for (let part = 0; part < 3; part++) {
213 | for (let granule = 0; granule < 4; granule++) {
214 | // Read the samples
215 | for (let sb = 0; sb < bound; sb++) {
216 | this.readSamples(0, sb, part);
217 | this.readSamples(1, sb, part);
218 | }
219 | for (let sb = bound; sb < sblimit; sb++) {
220 | this.readSamples(0, sb, part);
221 | // eslint-disable-next-line prefer-destructuring
222 | this.sample[1][sb][0] = this.sample[0][sb][0];
223 | // eslint-disable-next-line prefer-destructuring
224 | this.sample[1][sb][1] = this.sample[0][sb][1];
225 | // eslint-disable-next-line prefer-destructuring
226 | this.sample[1][sb][2] = this.sample[0][sb][2];
227 | }
228 | for (let sb = sblimit; sb < 32; sb++) {
229 | this.sample[0][sb][0] = 0;
230 | this.sample[0][sb][1] = 0;
231 | this.sample[0][sb][2] = 0;
232 | this.sample[1][sb][0] = 0;
233 | this.sample[1][sb][1] = 0;
234 | this.sample[1][sb][2] = 0;
235 | }
236 |
237 | // Synthesis loop
238 | for (let p = 0; p < 3; p++) {
239 | // Shifting step
240 | this.VPos = (this.VPos - 64) & 1023;
241 |
242 | for (let ch = 0; ch < 2; ch++) {
243 | MP2.MatrixTransform(this.sample[ch], p, this.V[ch], this.VPos);
244 |
245 | // Build U, windowing, calculate output
246 | Fill(this.U, 0);
247 |
248 | let dIndex = 512 - (this.VPos >> 1);
249 | let vIndex = this.VPos % 128 >> 1;
250 | while (vIndex < 1024) {
251 | for (let i = 0; i < 32; ++i) {
252 | this.U[i] += this.D[dIndex++] * this.V[ch][vIndex++];
253 | }
254 |
255 | vIndex += 128 - 32;
256 | dIndex += 64 - 32;
257 | }
258 |
259 | vIndex = 128 - 32 + 1024 - vIndex;
260 | dIndex -= 512 - 32;
261 | while (vIndex < 1024) {
262 | for (let i = 0; i < 32; ++i) {
263 | this.U[i] += this.D[dIndex++] * this.V[ch][vIndex++];
264 | }
265 |
266 | vIndex += 128 - 32;
267 | dIndex += 64 - 32;
268 | }
269 |
270 | // Output samples
271 | const outChannel = ch === 0 ? left : right;
272 | for (let j = 0; j < 32; j++) {
273 | outChannel[outPos + j] = this.U[j] / 2147418112;
274 | }
275 | } // End of synthesis channel loop
276 | outPos += 32;
277 | } // End of synthesis sub-block loop
278 | } // Decoding of the granule finished
279 | }
280 |
281 | this.sampleRate = sampleRate;
282 | return frameSize;
283 | }
284 |
285 | readAllocation(sb, tab3) {
286 | const tab4 = MP2.QUANT_LUT_STEP_3[tab3][sb];
287 | const qtab = MP2.QUANT_LUT_STEP4[tab4 & 15][this.bits.read(tab4 >> 4)];
288 | return qtab ? MP2.QUANT_TAB[qtab - 1] : 0;
289 | }
290 |
291 | readSamples(ch, sb, part) {
292 | const q = this.allocation[ch][sb];
293 | let sf = this.scaleFactor[ch][sb][part];
294 | const sample = this.sample[ch][sb];
295 | let val = 0;
296 |
297 | if (!q) {
298 | // No bits allocated for this subband
299 | sample[0] = sample[1] = sample[2] = 0;
300 | return;
301 | }
302 |
303 | // Resolve scalefactor
304 | if (sf === 63) {
305 | sf = 0;
306 | } else {
307 | const shift = (sf / 3) | 0;
308 | sf = (MP2.SCALEFACTOR_BASE[sf % 3] + ((1 << shift) >> 1)) >> shift;
309 | }
310 |
311 | // Decode samples
312 | let adj = q.levels;
313 | if (q.group) {
314 | // Decode grouped samples
315 | val = this.bits.read(q.bits);
316 | sample[0] = val % adj;
317 | val = (val / adj) | 0;
318 | sample[1] = val % adj;
319 | sample[2] = (val / adj) | 0;
320 | } else {
321 | // Decode direct samples
322 | sample[0] = this.bits.read(q.bits);
323 | sample[1] = this.bits.read(q.bits);
324 | sample[2] = this.bits.read(q.bits);
325 | }
326 |
327 | // Postmultiply samples
328 | const scale = (65536 / (adj + 1)) | 0;
329 | adj = ((adj + 1) >> 1) - 1;
330 |
331 | val = (adj - sample[0]) * scale;
332 | sample[0] = (val * (sf >> 12) + ((val * (sf & 4095) + 2048) >> 12)) >> 12;
333 |
334 | val = (adj - sample[1]) * scale;
335 | sample[1] = (val * (sf >> 12) + ((val * (sf & 4095) + 2048) >> 12)) >> 12;
336 |
337 | val = (adj - sample[2]) * scale;
338 | sample[2] = (val * (sf >> 12) + ((val * (sf & 4095) + 2048) >> 12)) >> 12;
339 | }
340 |
341 | static MatrixTransform(s, ss, d, dp) {
342 | let t01;
343 | let t02;
344 | let t03;
345 | let t04;
346 | let t05;
347 | let t06;
348 | let t07;
349 | let t08;
350 | let t09;
351 | let t10;
352 | let t11;
353 | let t12;
354 | let t13;
355 | let t14;
356 | let t15;
357 | let t16;
358 | let t17;
359 | let t18;
360 | let t19;
361 | let t20;
362 | let t21;
363 | let t22;
364 | let t23;
365 | let t24;
366 | let t25;
367 | let t26;
368 | let t27;
369 | let t28;
370 | let t29;
371 | let t30;
372 | let t31;
373 | let t32;
374 | let t33;
375 |
376 | t01 = s[0][ss] + s[31][ss];
377 | t02 = (s[0][ss] - s[31][ss]) * 0.500602998235;
378 | t03 = s[1][ss] + s[30][ss];
379 | t04 = (s[1][ss] - s[30][ss]) * 0.505470959898;
380 | t05 = s[2][ss] + s[29][ss];
381 | t06 = (s[2][ss] - s[29][ss]) * 0.515447309923;
382 | t07 = s[3][ss] + s[28][ss];
383 | t08 = (s[3][ss] - s[28][ss]) * 0.53104259109;
384 | t09 = s[4][ss] + s[27][ss];
385 | t10 = (s[4][ss] - s[27][ss]) * 0.553103896034;
386 | t11 = s[5][ss] + s[26][ss];
387 | t12 = (s[5][ss] - s[26][ss]) * 0.582934968206;
388 | t13 = s[6][ss] + s[25][ss];
389 | t14 = (s[6][ss] - s[25][ss]) * 0.622504123036;
390 | t15 = s[7][ss] + s[24][ss];
391 | t16 = (s[7][ss] - s[24][ss]) * 0.674808341455;
392 | t17 = s[8][ss] + s[23][ss];
393 | t18 = (s[8][ss] - s[23][ss]) * 0.744536271002;
394 | t19 = s[9][ss] + s[22][ss];
395 | t20 = (s[9][ss] - s[22][ss]) * 0.839349645416;
396 | t21 = s[10][ss] + s[21][ss];
397 | t22 = (s[10][ss] - s[21][ss]) * 0.972568237862;
398 | t23 = s[11][ss] + s[20][ss];
399 | t24 = (s[11][ss] - s[20][ss]) * 1.16943993343;
400 | t25 = s[12][ss] + s[19][ss];
401 | t26 = (s[12][ss] - s[19][ss]) * 1.48416461631;
402 | t27 = s[13][ss] + s[18][ss];
403 | t28 = (s[13][ss] - s[18][ss]) * 2.05778100995;
404 | t29 = s[14][ss] + s[17][ss];
405 | t30 = (s[14][ss] - s[17][ss]) * 3.40760841847;
406 | t31 = s[15][ss] + s[16][ss];
407 | t32 = (s[15][ss] - s[16][ss]) * 10.1900081235;
408 |
409 | t33 = t01 + t31;
410 | t31 = (t01 - t31) * 0.502419286188;
411 | t01 = t03 + t29;
412 | t29 = (t03 - t29) * 0.52249861494;
413 | t03 = t05 + t27;
414 | t27 = (t05 - t27) * 0.566944034816;
415 | t05 = t07 + t25;
416 | t25 = (t07 - t25) * 0.64682178336;
417 | t07 = t09 + t23;
418 | t23 = (t09 - t23) * 0.788154623451;
419 | t09 = t11 + t21;
420 | t21 = (t11 - t21) * 1.06067768599;
421 | t11 = t13 + t19;
422 | t19 = (t13 - t19) * 1.72244709824;
423 | t13 = t15 + t17;
424 | t17 = (t15 - t17) * 5.10114861869;
425 | t15 = t33 + t13;
426 | t13 = (t33 - t13) * 0.509795579104;
427 | t33 = t01 + t11;
428 | t01 = (t01 - t11) * 0.601344886935;
429 | t11 = t03 + t09;
430 | t09 = (t03 - t09) * 0.899976223136;
431 | t03 = t05 + t07;
432 | t07 = (t05 - t07) * 2.56291544774;
433 | t05 = t15 + t03;
434 | t15 = (t15 - t03) * 0.541196100146;
435 | t03 = t33 + t11;
436 | t11 = (t33 - t11) * 1.30656296488;
437 | t33 = t05 + t03;
438 | t05 = (t05 - t03) * 0.707106781187;
439 | t03 = t15 + t11;
440 | t15 = (t15 - t11) * 0.707106781187;
441 | t03 += t15;
442 | t11 = t13 + t07;
443 | t13 = (t13 - t07) * 0.541196100146;
444 | t07 = t01 + t09;
445 | t09 = (t01 - t09) * 1.30656296488;
446 | t01 = t11 + t07;
447 | t07 = (t11 - t07) * 0.707106781187;
448 | t11 = t13 + t09;
449 | t13 = (t13 - t09) * 0.707106781187;
450 | t11 += t13;
451 | t01 += t11;
452 | t11 += t07;
453 | t07 += t13;
454 | t09 = t31 + t17;
455 | t31 = (t31 - t17) * 0.509795579104;
456 | t17 = t29 + t19;
457 | t29 = (t29 - t19) * 0.601344886935;
458 | t19 = t27 + t21;
459 | t21 = (t27 - t21) * 0.899976223136;
460 | t27 = t25 + t23;
461 | t23 = (t25 - t23) * 2.56291544774;
462 | t25 = t09 + t27;
463 | t09 = (t09 - t27) * 0.541196100146;
464 | t27 = t17 + t19;
465 | t19 = (t17 - t19) * 1.30656296488;
466 | t17 = t25 + t27;
467 | t27 = (t25 - t27) * 0.707106781187;
468 | t25 = t09 + t19;
469 | t19 = (t09 - t19) * 0.707106781187;
470 | t25 += t19;
471 | t09 = t31 + t23;
472 | t31 = (t31 - t23) * 0.541196100146;
473 | t23 = t29 + t21;
474 | t21 = (t29 - t21) * 1.30656296488;
475 | t29 = t09 + t23;
476 | t23 = (t09 - t23) * 0.707106781187;
477 | t09 = t31 + t21;
478 | t31 = (t31 - t21) * 0.707106781187;
479 | t09 += t31;
480 | t29 += t09;
481 | t09 += t23;
482 | t23 += t31;
483 | t17 += t29;
484 | t29 += t25;
485 | t25 += t09;
486 | t09 += t27;
487 | t27 += t23;
488 | t23 += t19;
489 | t19 += t31;
490 | t21 = t02 + t32;
491 | t02 = (t02 - t32) * 0.502419286188;
492 | t32 = t04 + t30;
493 | t04 = (t04 - t30) * 0.52249861494;
494 | t30 = t06 + t28;
495 | t28 = (t06 - t28) * 0.566944034816;
496 | t06 = t08 + t26;
497 | t08 = (t08 - t26) * 0.64682178336;
498 | t26 = t10 + t24;
499 | t10 = (t10 - t24) * 0.788154623451;
500 | t24 = t12 + t22;
501 | t22 = (t12 - t22) * 1.06067768599;
502 | t12 = t14 + t20;
503 | t20 = (t14 - t20) * 1.72244709824;
504 | t14 = t16 + t18;
505 | t16 = (t16 - t18) * 5.10114861869;
506 | t18 = t21 + t14;
507 | t14 = (t21 - t14) * 0.509795579104;
508 | t21 = t32 + t12;
509 | t32 = (t32 - t12) * 0.601344886935;
510 | t12 = t30 + t24;
511 | t24 = (t30 - t24) * 0.899976223136;
512 | t30 = t06 + t26;
513 | t26 = (t06 - t26) * 2.56291544774;
514 | t06 = t18 + t30;
515 | t18 = (t18 - t30) * 0.541196100146;
516 | t30 = t21 + t12;
517 | t12 = (t21 - t12) * 1.30656296488;
518 | t21 = t06 + t30;
519 | t30 = (t06 - t30) * 0.707106781187;
520 | t06 = t18 + t12;
521 | t12 = (t18 - t12) * 0.707106781187;
522 | t06 += t12;
523 | t18 = t14 + t26;
524 | t26 = (t14 - t26) * 0.541196100146;
525 | t14 = t32 + t24;
526 | t24 = (t32 - t24) * 1.30656296488;
527 | t32 = t18 + t14;
528 | t14 = (t18 - t14) * 0.707106781187;
529 | t18 = t26 + t24;
530 | t24 = (t26 - t24) * 0.707106781187;
531 | t18 += t24;
532 | t32 += t18;
533 | t18 += t14;
534 | t26 = t14 + t24;
535 | t14 = t02 + t16;
536 | t02 = (t02 - t16) * 0.509795579104;
537 | t16 = t04 + t20;
538 | t04 = (t04 - t20) * 0.601344886935;
539 | t20 = t28 + t22;
540 | t22 = (t28 - t22) * 0.899976223136;
541 | t28 = t08 + t10;
542 | t10 = (t08 - t10) * 2.56291544774;
543 | t08 = t14 + t28;
544 | t14 = (t14 - t28) * 0.541196100146;
545 | t28 = t16 + t20;
546 | t20 = (t16 - t20) * 1.30656296488;
547 | t16 = t08 + t28;
548 | t28 = (t08 - t28) * 0.707106781187;
549 | t08 = t14 + t20;
550 | t20 = (t14 - t20) * 0.707106781187;
551 | t08 += t20;
552 | t14 = t02 + t10;
553 | t02 = (t02 - t10) * 0.541196100146;
554 | t10 = t04 + t22;
555 | t22 = (t04 - t22) * 1.30656296488;
556 | t04 = t14 + t10;
557 | t10 = (t14 - t10) * 0.707106781187;
558 | t14 = t02 + t22;
559 | t02 = (t02 - t22) * 0.707106781187;
560 | t14 += t02;
561 | t04 += t14;
562 | t14 += t10;
563 | t10 += t02;
564 | t16 += t04;
565 | t04 += t08;
566 | t08 += t14;
567 | t14 += t28;
568 | t28 += t10;
569 | t10 += t20;
570 | t20 += t02;
571 | t21 += t16;
572 | t16 += t32;
573 | t32 += t04;
574 | t04 += t06;
575 | t06 += t08;
576 | t08 += t18;
577 | t18 += t14;
578 | t14 += t30;
579 | t30 += t28;
580 | t28 += t26;
581 | t26 += t10;
582 | t10 += t12;
583 | t12 += t20;
584 | t20 += t24;
585 | t24 += t02;
586 |
587 | d[dp + 48] = -t33;
588 | d[dp + 49] = d[dp + 47] = -t21;
589 | d[dp + 50] = d[dp + 46] = -t17;
590 | d[dp + 51] = d[dp + 45] = -t16;
591 | d[dp + 52] = d[dp + 44] = -t01;
592 | d[dp + 53] = d[dp + 43] = -t32;
593 | d[dp + 54] = d[dp + 42] = -t29;
594 | d[dp + 55] = d[dp + 41] = -t04;
595 | d[dp + 56] = d[dp + 40] = -t03;
596 | d[dp + 57] = d[dp + 39] = -t06;
597 | d[dp + 58] = d[dp + 38] = -t25;
598 | d[dp + 59] = d[dp + 37] = -t08;
599 | d[dp + 60] = d[dp + 36] = -t11;
600 | d[dp + 61] = d[dp + 35] = -t18;
601 | d[dp + 62] = d[dp + 34] = -t09;
602 | d[dp + 63] = d[dp + 33] = -t14;
603 | d[dp + 32] = -t05;
604 | d[dp + 0] = t05;
605 | d[dp + 31] = -t30;
606 | d[dp + 1] = t30;
607 | d[dp + 30] = -t27;
608 | d[dp + 2] = t27;
609 | d[dp + 29] = -t28;
610 | d[dp + 3] = t28;
611 | d[dp + 28] = -t07;
612 | d[dp + 4] = t07;
613 | d[dp + 27] = -t26;
614 | d[dp + 5] = t26;
615 | d[dp + 26] = -t23;
616 | d[dp + 6] = t23;
617 | d[dp + 25] = -t10;
618 | d[dp + 7] = t10;
619 | d[dp + 24] = -t15;
620 | d[dp + 8] = t15;
621 | d[dp + 23] = -t12;
622 | d[dp + 9] = t12;
623 | d[dp + 22] = -t19;
624 | d[dp + 10] = t19;
625 | d[dp + 21] = -t20;
626 | d[dp + 11] = t20;
627 | d[dp + 20] = -t13;
628 | d[dp + 12] = t13;
629 | d[dp + 19] = -t24;
630 | d[dp + 13] = t24;
631 | d[dp + 18] = -t31;
632 | d[dp + 14] = t31;
633 | d[dp + 17] = -t02;
634 | d[dp + 15] = t02;
635 | d[dp + 16] = 0.0;
636 | }
637 | }
638 |
639 | MP2.FRAME_SYNC = 0x7ff;
640 |
641 | MP2.VERSION = {
642 | MPEG_2_5: 0x0,
643 | MPEG_2: 0x2,
644 | MPEG_1: 0x3,
645 | };
646 |
647 | MP2.LAYER = {
648 | III: 0x1,
649 | II: 0x2,
650 | I: 0x3,
651 | };
652 |
653 | MP2.MODE = {
654 | STEREO: 0x0,
655 | JOINT_STEREO: 0x1,
656 | DUAL_CHANNEL: 0x2,
657 | MONO: 0x3,
658 | };
659 |
660 | MP2.SAMPLE_RATE = new Uint16Array([
661 | 44100,
662 | 48000,
663 | 32000,
664 | 0, // MPEG-1
665 | 22050,
666 | 24000,
667 | 16000,
668 | 0, // MPEG-2
669 | ]);
670 |
671 | MP2.BIT_RATE = new Uint16Array([
672 | 32,
673 | 48,
674 | 56,
675 | 64,
676 | 80,
677 | 96,
678 | 112,
679 | 128,
680 | 160,
681 | 192,
682 | 224,
683 | 256,
684 | 320,
685 | 384, // MPEG-1
686 | 8,
687 | 16,
688 | 24,
689 | 32,
690 | 40,
691 | 48,
692 | 56,
693 | 64,
694 | 80,
695 | 96,
696 | 112,
697 | 128,
698 | 144,
699 | 160, // MPEG-2
700 | ]);
701 |
702 | MP2.SCALEFACTOR_BASE = new Uint32Array([0x02000000, 0x01965fea, 0x01428a30]);
703 |
704 | MP2.SYNTHESIS_WINDOW = new Float32Array([
705 | 0.0, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -1.0, -1.0, -1.0, -1.0, -1.5, -1.5, -2.0, -2.0, -2.5,
706 | -2.5, -3.0, -3.5, -3.5, -4.0, -4.5, -5.0, -5.5, -6.5, -7.0, -8.0, -8.5, -9.5, -10.5, -12.0, -13.0,
707 | -14.5, -15.5, -17.5, -19.0, -20.5, -22.5, -24.5, -26.5, -29.0, -31.5, -34.0, -36.5, -39.5, -42.5,
708 | -45.5, -48.5, -52.0, -55.5, -58.5, -62.5, -66.0, -69.5, -73.5, -77.0, -80.5, -84.5, -88.0, -91.5,
709 | -95.0, -98.0, -101.0, -104.0, 106.5, 109.0, 111.0, 112.5, 113.5, 114.0, 114.0, 113.5, 112.0,
710 | 110.5, 107.5, 104.0, 100.0, 94.5, 88.5, 81.5, 73.0, 63.5, 53.0, 41.5, 28.5, 14.5, -1.0, -18.0,
711 | -36.0, -55.5, -76.5, -98.5, -122.0, -147.0, -173.5, -200.5, -229.5, -259.5, -290.5, -322.5,
712 | -355.5, -389.5, -424.0, -459.5, -495.5, -532.0, -568.5, -605.0, -641.5, -678.0, -714.0, -749.0,
713 | -783.5, -817.0, -849.0, -879.5, -908.5, -935.0, -959.5, -981.0, -1000.5, -1016.0, -1028.5,
714 | -1037.5, -1042.5, -1043.5, -1040.0, -1031.5, 1018.5, 1000.0, 976.0, 946.5, 911.0, 869.5, 822.0,
715 | 767.5, 707.0, 640.0, 565.5, 485.0, 397.0, 302.5, 201.0, 92.5, -22.5, -144.0, -272.5, -407.0,
716 | -547.5, -694.0, -846.0, -1003.0, -1165.0, -1331.5, -1502.0, -1675.5, -1852.5, -2031.5, -2212.5,
717 | -2394.0, -2576.5, -2758.5, -2939.5, -3118.5, -3294.5, -3467.5, -3635.5, -3798.5, -3955.0, -4104.5,
718 | -4245.5, -4377.5, -4499.0, -4609.5, -4708.0, -4792.5, -4863.5, -4919.0, -4958.0, -4979.5, -4983.0,
719 | -4967.5, -4931.5, -4875.0, -4796.0, -4694.5, -4569.5, -4420.0, -4246.0, -4046.0, -3820.0, -3567.0,
720 | 3287.0, 2979.5, 2644.0, 2280.5, 1888.0, 1467.5, 1018.5, 541.0, 35.0, -499.0, -1061.0, -1650.0,
721 | -2266.5, -2909.0, -3577.0, -4270.0, -4987.5, -5727.5, -6490.0, -7274.0, -8077.5, -8899.5, -9739.0,
722 | -10594.5, -11464.5, -12347.0, -13241.0, -14144.5, -15056.0, -15973.5, -16895.5, -17820.0,
723 | -18744.5, -19668.0, -20588.0, -21503.0, -22410.5, -23308.5, -24195.0, -25068.5, -25926.5,
724 | -26767.0, -27589.0, -28389.0, -29166.5, -29919.0, -30644.5, -31342.0, -32009.5, -32645.0,
725 | -33247.0, -33814.5, -34346.0, -34839.5, -35295.0, -35710.0, -36084.5, -36417.5, -36707.5,
726 | -36954.0, -37156.5, -37315.0, -37428.0, -37496.0, 37519.0, 37496.0, 37428.0, 37315.0, 37156.5,
727 | 36954.0, 36707.5, 36417.5, 36084.5, 35710.0, 35295.0, 34839.5, 34346.0, 33814.5, 33247.0, 32645.0,
728 | 32009.5, 31342.0, 30644.5, 29919.0, 29166.5, 28389.0, 27589.0, 26767.0, 25926.5, 25068.5, 24195.0,
729 | 23308.5, 22410.5, 21503.0, 20588.0, 19668.0, 18744.5, 17820.0, 16895.5, 15973.5, 15056.0, 14144.5,
730 | 13241.0, 12347.0, 11464.5, 10594.5, 9739.0, 8899.5, 8077.5, 7274.0, 6490.0, 5727.5, 4987.5,
731 | 4270.0, 3577.0, 2909.0, 2266.5, 1650.0, 1061.0, 499.0, -35.0, -541.0, -1018.5, -1467.5, -1888.0,
732 | -2280.5, -2644.0, -2979.5, 3287.0, 3567.0, 3820.0, 4046.0, 4246.0, 4420.0, 4569.5, 4694.5, 4796.0,
733 | 4875.0, 4931.5, 4967.5, 4983.0, 4979.5, 4958.0, 4919.0, 4863.5, 4792.5, 4708.0, 4609.5, 4499.0,
734 | 4377.5, 4245.5, 4104.5, 3955.0, 3798.5, 3635.5, 3467.5, 3294.5, 3118.5, 2939.5, 2758.5, 2576.5,
735 | 2394.0, 2212.5, 2031.5, 1852.5, 1675.5, 1502.0, 1331.5, 1165.0, 1003.0, 846.0, 694.0, 547.5,
736 | 407.0, 272.5, 144.0, 22.5, -92.5, -201.0, -302.5, -397.0, -485.0, -565.5, -640.0, -707.0, -767.5,
737 | -822.0, -869.5, -911.0, -946.5, -976.0, -1000.0, 1018.5, 1031.5, 1040.0, 1043.5, 1042.5, 1037.5,
738 | 1028.5, 1016.0, 1000.5, 981.0, 959.5, 935.0, 908.5, 879.5, 849.0, 817.0, 783.5, 749.0, 714.0,
739 | 678.0, 641.5, 605.0, 568.5, 532.0, 495.5, 459.5, 424.0, 389.5, 355.5, 322.5, 290.5, 259.5, 229.5,
740 | 200.5, 173.5, 147.0, 122.0, 98.5, 76.5, 55.5, 36.0, 18.0, 1.0, -14.5, -28.5, -41.5, -53.0, -63.5,
741 | -73.0, -81.5, -88.5, -94.5, -100.0, -104.0, -107.5, -110.5, -112.0, -113.5, -114.0, -114.0,
742 | -113.5, -112.5, -111.0, -109.0, 106.5, 104.0, 101.0, 98.0, 95.0, 91.5, 88.0, 84.5, 80.5, 77.0,
743 | 73.5, 69.5, 66.0, 62.5, 58.5, 55.5, 52.0, 48.5, 45.5, 42.5, 39.5, 36.5, 34.0, 31.5, 29.0, 26.5,
744 | 24.5, 22.5, 20.5, 19.0, 17.5, 15.5, 14.5, 13.0, 12.0, 10.5, 9.5, 8.5, 8.0, 7.0, 6.5, 5.5, 5.0,
745 | 4.5, 4.0, 3.5, 3.5, 3.0, 2.5, 2.5, 2.0, 2.0, 1.5, 1.5, 1.0, 1.0, 1.0, 1.0, 0.5, 0.5, 0.5, 0.5,
746 | 0.5, 0.5,
747 | ]);
748 |
749 | // Quantizer lookup, step 1: bitrate classes
750 | MP2.QUANT_LUT_STEP_1 = [
751 | // 32, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,384 <- bitrate
752 | [0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2], // mono
753 | // 16, 24, 28, 32, 40, 48, 56, 64, 80, 96,112,128,160,192 <- bitrate / chan
754 | [0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2], // stereo
755 | ];
756 |
757 | // Quantizer lookup, step 2: bitrate class, sample rate -> B2 table idx, sblimit
758 | MP2.QUANT_TAB = {
759 | A: 27 | 64, // Table 3-B.2a: high-rate, sblimit = 27
760 | B: 30 | 64, // Table 3-B.2b: high-rate, sblimit = 30
761 | C: 8, // Table 3-B.2c: low-rate, sblimit = 8
762 | D: 12, // Table 3-B.2d: low-rate, sblimit = 12
763 | };
764 |
765 | MP2.QUANT_LUT_STEP_2 = [
766 | // 44.1 kHz, 48 kHz, 32 kHz
767 | [MP2.QUANT_TAB.C, MP2.QUANT_TAB.C, MP2.QUANT_TAB.D], // 32 - 48 kbit/sec/ch
768 | [MP2.QUANT_TAB.A, MP2.QUANT_TAB.A, MP2.QUANT_TAB.A], // 56 - 80 kbit/sec/ch
769 | [MP2.QUANT_TAB.B, MP2.QUANT_TAB.A, MP2.QUANT_TAB.B], // 96+ kbit/sec/ch
770 | ];
771 |
772 | // Quantizer lookup, step 3: B2 table, subband -> nbal, row index
773 | // (upper 4 bits: nbal, lower 4 bits: row index)
774 | MP2.QUANT_LUT_STEP_3 = [
775 | // Low-rate table (3-B.2c and 3-B.2d)
776 | [0x44, 0x44, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34],
777 | // High-rate table (3-B.2a and 3-B.2b)
778 | [
779 | 0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x31, 0x31, 0x31, 0x31, 0x31,
780 | 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
781 | ],
782 | // MPEG-2 LSR table (B.2 in ISO 13818-3)
783 | [
784 | 0x45, 0x45, 0x45, 0x45, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x24, 0x24, 0x24, 0x24, 0x24,
785 | 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24,
786 | ],
787 | ];
788 |
789 | // Quantizer lookup, step 4: table row, allocation[] value -> quant table index
790 | MP2.QUANT_LUT_STEP4 = [
791 | [0, 1, 2, 17],
792 | [0, 1, 2, 3, 4, 5, 6, 17],
793 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17],
794 | [0, 1, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17],
795 | [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17],
796 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
797 | ];
798 |
799 | MP2.QUANT_TAB = [
800 | { levels: 3, group: 1, bits: 5 }, // 1
801 | { levels: 5, group: 1, bits: 7 }, // 2
802 | { levels: 7, group: 0, bits: 3 }, // 3
803 | { levels: 9, group: 1, bits: 10 }, // 4
804 | { levels: 15, group: 0, bits: 4 }, // 5
805 | { levels: 31, group: 0, bits: 5 }, // 6
806 | { levels: 63, group: 0, bits: 6 }, // 7
807 | { levels: 127, group: 0, bits: 7 }, // 8
808 | { levels: 255, group: 0, bits: 8 }, // 9
809 | { levels: 511, group: 0, bits: 9 }, // 10
810 | { levels: 1023, group: 0, bits: 10 }, // 11
811 | { levels: 2047, group: 0, bits: 11 }, // 12
812 | { levels: 4095, group: 0, bits: 12 }, // 13
813 | { levels: 8191, group: 0, bits: 13 }, // 14
814 | { levels: 16383, group: 0, bits: 14 }, // 15
815 | { levels: 32767, group: 0, bits: 15 }, // 16
816 | { levels: 65535, group: 0, bits: 16 }, // 17
817 | ];
818 |
819 | export default MP2;
820 |
--------------------------------------------------------------------------------
/src/lib/wasm/WASM_BINARY.js:
--------------------------------------------------------------------------------
1 | // get from jsmpeg
2 | export default 'AGFzbQEAAAAADwZkeWxpbmuA0MACBAAAAAE0CWAEf39/fwBgAX8Bf2ACf38Bf2ABfwBgAn9/AGABfwF9YAZ/f39/f38AYAN/f38Bf2AAAAJGBANlbnYOX19fYXNzZXJ0X2ZhaWwAAANlbnYFX3NicmsAAQNlbnYNX19tZW1vcnlfYmFzZQN/AANlbnYGbWVtb3J5AgCAAgM9PAIDAgEEBAMBBQEBAQEBAQEDBAMGAwQAAwAAAAIDAgEEBAEBAQEBAAACAwIBAgECAQEBAQMEAwMCBwcHCAYLAn8BQQALfwFBAAsHggYfEl9fcG9zdF9pbnN0YW50aWF0ZQA9BV9mcmVlADgHX21hbGxvYwAxB19tZW1jcHkAOghfbWVtbW92ZQA7B19tZW1zZXQAPBNfbXAyX2RlY29kZXJfY3JlYXRlAB0TX21wMl9kZWNvZGVyX2RlY29kZQAmFF9tcDJfZGVjb2Rlcl9kZXN0cm95AB4WX21wMl9kZWNvZGVyX2RpZF93cml0ZQAiFl9tcDJfZGVjb2Rlcl9nZXRfaW5kZXgAICFfbXAyX2RlY29kZXJfZ2V0X2xlZnRfY2hhbm5lbF9wdHIAJCJfbXAyX2RlY29kZXJfZ2V0X3JpZ2h0X2NoYW5uZWxfcHRyACUcX21wMl9kZWNvZGVyX2dldF9zYW1wbGVfcmF0ZQAjGl9tcDJfZGVjb2Rlcl9nZXRfd3JpdGVfcHRyAB8WX21wMl9kZWNvZGVyX3NldF9pbmRleAAhFV9tcGVnMV9kZWNvZGVyX2NyZWF0ZQACFV9tcGVnMV9kZWNvZGVyX2RlY29kZQARFl9tcGVnMV9kZWNvZGVyX2Rlc3Ryb3kAAxhfbXBlZzFfZGVjb2Rlcl9kaWRfd3JpdGUABxlfbXBlZzFfZGVjb2Rlcl9nZXRfY2JfcHRyABAdX21wZWcxX2RlY29kZXJfZ2V0X2NvZGVkX3NpemUACxlfbXBlZzFfZGVjb2Rlcl9nZXRfY3JfcHRyAA8dX21wZWcxX2RlY29kZXJfZ2V0X2ZyYW1lX3JhdGUAChlfbXBlZzFfZGVjb2Rlcl9nZXRfaGVpZ2h0AA0YX21wZWcxX2RlY29kZXJfZ2V0X2luZGV4AAUYX21wZWcxX2RlY29kZXJfZ2V0X3dpZHRoAAwcX21wZWcxX2RlY29kZXJfZ2V0X3dy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BQJ/IANBzQBHDQIgACgCgAFBBhAwIQMCQAJAIAAoAoABQQgQMCIEIgYEQCAGQYABRgRADAIFDAMLAAsgACgCgAFBCBAwDAILIAAoAoABQQgQMEGAfmoMAQsgBEGAfmogBCAEQYABShsLCyEEDAELIAZB/wFxIgNBACADayAAKAKAAUEBEDBFGyEEIAZBCHUhAwsgAiADaiIGIwBBQGtqLQAAIgMgBWotAAAgACgCREEAIARBAXQiAkEfdUEBciAAKAJcGyACamxsIgJBBHVBAEEBQX8gAkEPShsgAkEQcRtrIgJBgHAgAkGAcEobIQIgAEGcAWogA0ECdGogAyMAQZA1amotAAAgAkH/DyACQf8PSBtsNgIAIAZBAWohAgwBCwsgAUEESARAIABBhAFqIQUgAUEDdEEIcSAAKAJUIAAoAhgiAyAAKAJQbGpBBHRyIANBA3RBACABQQJxG2ohBAUgAEGMAWogAEGIAWogAUEERhshBSAAKAJQIAAoAhgiAUECdGwgACgCVEEDdGohBCABQQF1IQMLIANBeGohAyAFKAIAIQUgAkEBRiECIABBnAFqIQEgACgCXARAIAIEQCABKAIAQYABakEIdSAFIAQgAxAYIAFBADYCAAUgARAZIAEgBSAEIAMQGiABQQBBgAIQPBoLBSACBEAgASgCAEGAAWpBCHUgBSAEIAMQGyABQQA2AgAFIAEQGSABIAUgBCADEBwgAUEAQYACEDwaCwsL7AYAIAEgAmogAEEAIABBAEobIgBB/wEgAEH/AUgbQf8BcSIAOgAAIAEgAkEBamogADoAACABIAJBAmpqIAA6AAAgASACQQNqaiAAOgAAIAEgAkEEamogADoAACABIAJBBWpqIAA6AAAgASACQQZqaiAAOgAAIAEgAkEHamogADoAACABIAIgA0EIaiIDaiICaiAAOgAAIAEgAkEBamogADoAACABIAJBAmpqIAA6AAAgASACQQNqaiAAOgAAIAEgAkEEamogADoAACABIAJBBWpqIAA6AAAgASACQQZqaiAAOgAAIAEgAkEHamogADoAACABIAIgA2oiAmogADoAACABIAJBAWpqIAA6AAAgASACQQJqaiAAOgAAIAEgAkEDamogADoAACABIAJBBGpqIAA6AAAgASACQQVqaiAAOgAAIAEgAkEGamogADoAACABIAJBB2pqIAA6AAAgASACIANqIgJqIAA6AAAgASACQQFqaiAAOgAAIAEgAkECamogADoAACABIAJBA2pqIAA6AAAgASACQQRqaiAAOgAAIAEgAkEFamogADoAACABIAJBBmpqIAA6AAAgASACQQdqaiAAOgAAIAEgAiADaiICaiAAOgAAIAEgAkEBamogADoAACABIAJBAmpqIAA6AAAgASACQQNqaiAAOgAAIAEgAkEEamogADoAACABIAJBBWpqIAA6AAAgASACQQZqaiAAOgAAIAEgAkEHamogADoAACABIAIgA2oiAmogADoAACABIAJBAWpqIAA6AAAgASACQQJqaiAAOgAAIAEgAkEDamogADoAACABIAJBBGpqIAA6AAAgASACQQVqaiAAOgAAIAEgAkEGamogADoAACABIAJBB2pqIAA6AAAgASACIANqIgJqIAA6AAAgASACQQFqaiAAOgAAIAEgAkECamogADoAACABIAJBA2pqIAA6AAAgASACQQRqaiAAOgAAIAEgAkEFamogADoAACABIAJBBmpqIAA6AAAgASACQQdqaiAAOgAAIAEgAiADaiICaiAAOgAAIAEgAkEBamogADoAACABIAJBAmpqIAA6AAAgASACQQNqaiAAOgAAIAEgAkEEamogADoAACABIAJBBWpqIAA6AAAgASACQQZqaiAAOgAAIAEgAkEHamogADoAAAubBgEUfwNAIAFBEGpBAnQgAGoiBygCACIGIAFBMGpBAnQgAGoiDSgCACIJaiEFIAFBCGpBAnQgAGoiDigCACICIAFBOGpBAnQgAGoiDygCACIDaiEEIAIgA2siEEHZA2wgAUEoakECdCAAaiIKKAIAIgIgAUEYakECdCAAaiIRKAIAIgNrIgtBvH5sQYABampBCHUgBCACIANqIghqIgJrIgMgBCAIa0HqAmxBgAFqQQh1ayEEIAFBAnQgAGoiCCgCACIMIAFBIGpBAnQgAGoiEigCACITayIUIAYgCWtB6gJsQYABakEIdSAFayIJaiEGIAggAiAMIBNqIgggBWoiDGo2AgAgDiADIAZqNgIAIAcgFCAJayIHIARrNgIAIBEgC0HZA2xBgAFqIBBBxAFsakEIdSAEaiIJIAggBWsiBWo2AgAgEiAFIAlrNgIAIAogBCAHajYCACANIAYgA2s2AgAgDyAMIAJr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aiABQQxsaiAAKAIIQQYQMCICNgLUAiAAIANBgANsaiABQQxsaiACNgLQAiAFIAI2AgAMAgsgBSAAKAIIQQYQMDYCACAAIANBgANsaiABQQxsaiAAKAIIQQYQMCICNgLUAiAAIANBgANsaiABQQxsaiACNgLQAgsLCyADQQFqIgMgCEkNAAsgAUEBaiIBIAtHDQALCwsLaiALQSBJIRsgAEHMtgFqIQQgAEHMCGohHCAAQcz2AGohHSAAQcwLaiEeIABBzJYBaiEfA0BBACEWIBQhAwNAIBgEQEEAIQEDQCAAQQAgASAOECggAEEBIAEgDhAoIAFBAWoiASANSA0ACwsgGQRAIA0hAQNAIABBACABIA4QKCAAQcwLaiABQQxsaiAAQcwIaiABQQxsaigCADYCACAAIAFBDGxqQdALaiAAIAFBDGxqQdAIaigCADYCACAAIAFBDGxqQdQLaiAAIAFBDGxqQdQIaigCADYCACABQQFqIgEgC0gNAAsLIBsEQCALIQEDQCAAQcwIaiABQQxsakEANgIAIAAgAUEMbGpB0AhqQQA2AgAgACABQQxsakHUCGpBADYCACAAQcwLaiABQQxsakEANgIAIAAgAUEMbGpB0AtqQQA2AgAgACABQQxsakHUC2pBADYCACABQQFqIgFBIEcNAAsLIAAoAgQhASADIRFBACESA0AgACABQcAHakH/B3EiATYCBCAcIBIgHSABECkgBEIANwIAIARCADcCCCAEQgA3AhAgBEIANwIYIARCADcCICAEQgA3AiggBEIANwIwIARCADcCOCAEQUBrQgA3AgAgBEIANwJIIARCADcCUCAEQgA3AlggBEIANwJgIARCADcCaCAEQgA3AnAgBEIANwJ4IAAoAgQiD0EBdSETQf8HIA9BgAFvQQF1IgxrIgFBgH9xIRUgAUEHdkEGdEHABGohEEGABCATayEGIAwhAQNAIAYhB0EAIQkgASECA0AgB0EBaiEIIAJBAWohBSAAQcy2AWogCUECdGoiCiAAQczWAGogB0ECdGoqAgAgAEHM9gBqIAJBAnRqKgIAlCAKKAIAspKoNgIAIAlBAWoiCUEgRwRAIAghByAFIQIMAQsLIAFBgAFqIQIgBkFAayEGIAFBgAdIBEAgAiEBDAELC0HgByAMIBVqayIBQYAISARAIBAgE2tBoHxqIQYD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IkogWCBLk7tEujBFka7n9D+itiJLk7tEuEt/Zp6g5j+itiI6kiEqIANBMGpBAnQgAmogTSA8kow4AgAgA0EvakECdCACaiApICiSIEwgSZIgJJIiKJKMIik4AgAgA0ExakECdCACaiApOAIAIANBLmpBAnQgAmogRiBFkiAjkowiKTgCACADQTJqQQJ0IAJqICk4AgAgA0EtakECdCACaiAwIC2SIEAgPpIgJpIiKZIiPCAokowiKDgCACADQTNqQQJ0IAJqICg4AgAgA0EsakECdCACaiA2IE+SIFEgUJIgNZIiKJKMIjY4AgAgA0E0akECdCACaiA2OAIAIANBK2pBAnQgAmogPCA/IDiSIDOSIjwgJJIiJJKMIjY4AgAgA0E1akECdCACaiA2OAIAIANBKmpBAnQgAmogPSBSkiA7kiI9ICOSjCIjOAIAIANBNmpBAnQgAmogIzgCACADQSlqQQJ0IAJqIEsgSpIgOpIiIyAkkowiJDgCACADQTdqQQJ0IAJqICQ4AgAgA0EoakECdCACaiBIIE6SIDGSjCIkOAIAIANBOGpBAnQgAmogJDgCACADQSdqQQJ0IAJqICMgPCArkiIjkowiJDgCACADQTlqQQJ0IAJqICQ4AgAgA0EmakECdCACaiA9ICeSjCIkOAIAIANBOmpBAnQgAmogJDgCACADQSVqQQJ0IAJqICkgLSAwk7tEuEt/Zp6g5j+itiIpkiItICOSjCIjOAIAIANBO2pBAnQgAmogIzgCACADQSRqQQJ0IAJqICggR5KMIiM4AgAgA0E8akECdCACaiAjOAIAIANBI2pBAnQgAmogLSArIDKSIiOSjCIoOAIAIANBPWpBAnQgAmogKDgCACADQSJqQQJ0IAJqICcgU5KMIic4AgAgA0E+akECdCACaiAnOAIAIANBIWpBAnQgAmogIyBCkowiIzgCACADQT9qQQJ0IAJqICM4AgAgA0EgakECdCACaiA0jDgCACADQQJ0IAJqIDQ4AgAgA0EfakECdCACaiAsjDgCACADQQFqQQJ0IAJqICw4AgAgA0EeakECdCACaiA5jDgCACADQQJqQQJ0IAJqIDk4AgAgA0EdakECdCACaiApICaSIjQgQZIiI4w4AgAgA0EDakECdCACaiAjOAIAIANBHGpBAnQgAmogL4w4AgAgA0EEakECdCACaiAvOAIAIANBG2pBAnQgAmogNCA3IDOSIjSSIi+MOAIAIANBBWpBAnQgAmogLzgCACADQRpqQQJ0IAJqIEQgO5IiL4w4AgAgA0EGakECdCACaiAvOAIAIANBGWpBAnQgAmogNCA6kiI0jDgCACADQQdqQQJ0IAJqIDQ4AgAgA0EYakECdCACaiAxjDgCACADQQhqQQJ0IAJqIDE4AgAgA0EXakECdCACaiAqjDgCACADQQlqQQJ0IAJqICo4AgAgA0EWakECdCACaiA7IC6SIjGMOAIAIANBCmpBAnQgAmogMTgCACADQRVqQQJ0IAJqIEMgJpIiMYw4AgAgA0ELakECdCACaiAxOAIAIANBFGpBAnQgAmogNYw4AgAgA0EMakECdCACaiA1OAIAIANBE2pBAnQgAmogJiAlkiImjDgCACADQQ1qQQJ0IAJqICY4AgAgA0ESakECdCACaiAujDgCACADQQ5qQQJ0IAJqIC44AgAgA0ERakECdCACaiAljDgCACADQQ9qQQJ0IAJqICU4AgAgA0EQakECdCACakMAAAAAOAIACy8BAX9BFBAxIgIgATYCECACIAAQMTYCACACIAA2AgggAkEANgIMIAJBADYCBCACCw0AIAAoAgAQOCAAEDgL6QEBBH8gACgCCCIDIAAoAgwiAmsiBCABSQRAAkAgACgCEEECRgRAIAAgACgCACABIARrIANBAXQiAiACIARqIAFJGyIBEDk2AgAgACABNgIIIAAoAgQgACgCDCIBQQN0IgJNDQEgACACNgIEDAELIAIgACgCBCIFQQN2IgNGIAMgBGogAUlyBEAgAEEANgIMIABBADYCBEEAIQEMAQsgAwRAIAAoAgAiASABIANqIAIgA2sQOxogACAAKAIMIANrIgE2AgwgACAAKAIEIAVBeHFrNgIEBSACIQELCwUgAiEBCyAAKAIAIAFqC40BAQR/IAAoAgRBB2pBA3YiASAAKAIMIgRJBEACQCAAKAIAIQIDQAJAIAFBAWohAyABIAJqLAAARQRAIAIgA2osAABFBEAgAiABQQJqaiwAAEEBRg0CCwsgAyAETw0CIAMhAQwBCwsgACABQQN0QSBqNgIEIAIgAUEDamotAAAPCwsgACAEQQN0NgIEQX8LqQEBBH8gACgCBEEHakEDdiICIAAoAgwiBUkEQAJAIAAoAgAh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aiIGQRBLDQALIAMgCWohAyAIayECIQEMAgsgAyABKAIAIgU6AAAgAyABLAABOgABIAJBbGpBcHEiCEESaiIJIAFqIAJBbmogA0ECaiEEIAJBfmohBiABQQJqIQIgBSEBA0AgBCACKAICIgVBEHQgAUEQdnI2AgAgBCACKAIGIgFBEHQgBUEQdnI2AgQgBCACKAIKIgVBEHQgAUEQdnI2AgggBCACKAIOIgFBEHQgBUEQdnI2AgwgAkEQaiECIARBEGohBCAGQXBqIgZBEUsNAAsgAyAJaiEDIAhrIQIhAQwBCyADIAEoAgAiBToAACACQWxqQXBxIghBEWoiCSABaiACQW9qIANBAWohBCACQX9qIQYgAUEBaiECIAUhAQNAIAQgAigCAyIFQRh0IAFBCHZyNgIAIAQgAigCByIBQRh0IAVBCHZyNgIEIAQgAigCCyIFQRh0IAFBCHZyNgIIIAQgAigCDyIBQRh0IAVBCHZyNgIMIAJBEGohAiAEQRBqIQQgBkFwaiIGQRJLDQALIAMgCWohAyAIayECIQELCyACQRBxBEAgAyABLAAAOgAAIAMgASwAAToAASADIAEsAAI6AAIgAyABLAADOgADIAMgASwABDoABCADIAEsAAU6AAUgAyABLAAGOgAGIAMgASwABzoAByADIAEsAAg6AAggAyABLAAJOgAJIAMgASwACjoACiADIAEsAAs6AAsgAyABLAAMOgAMIAMgASwADToADSADIAEsAA46AA4gAyABLAAPOgAPIANBEGohAyABQRBqIQELIAJBCHEEQCADIAEsAAA6AAAgAyABLAABOgABIAMgASwAAjoAAiADIAEsAAM6AAMgAyABLAAEOgAEIAMgASwABToABSADIAEsAAY6AAYgAyABLAAHOgAHIANBCGohAyABQQhqIQELIAJBBHEEQCADIAEsAAA6AAAgAyABLAABOgABIAMgASwAAjoAAiADIAEsAAM6AAMgA0EEaiEDIAFBBGohAQsgAkECcQRAIAMgASwAADoAACADIAEsAAE6AAEgA0ECaiEDIAFBAmohAQsgAkEBcUUEQCAADwsgAyABLAAAOgAAIAALwAMBBn8gACABRgRAIAAPCyABIAJqIABLIAAgAmoiBSABS3FFBEAgACABIAIQOhogAA8LIAEgACIDc0EDcUUhBCADIAFJBH8g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JCQkJCQkJCQkJCQkJCQkJCQAAAABAhEAAAAAAAAAAAAAAAAAAQIDBAUGEQAAAAAAAAAAAAECAwQFBgcICQoLDA0OEQABAwUGBwgJCgsMDQ4PEBEAAQIEBQYHCAkKCwwNDg8RAAECAwQFBgcICQoLDA0ODwMAAQUFAAEHBwAAAwkAAQoPAAAEHwAABT8AAAZ/AAAH/wAACP8BAAn/AwAK/wcAC/8PAAz/HwAN/z8ADv9/AA///wAQAAAAAAAAAAAAAAAAAAAAAupflgEwikIBCAgMW1tbXltec2l6ZSA+IDAAL2hvbWUvZG9taW5pYy9wcm9qZWN0cy9lbXNkay9mYXN0Y29tcC9lbXNjcmlwdGVuL3N5c3RlbS9saWIvZW1tYWxsb2MuY3BwAGdldEZyZWVMaXN0SW5kZXgATUlOX0ZSRUVMSVNUX0lOREVYIDw9IGluZGV4ICYmIGluZGV4IDwgTUFYX0ZSRUVMSVNUX0lOREVYAGdldEFmdGVyKHJlZ2lvbikgPD0gc2JyaygwKQBlbW1hbGxvY19tYWxsb2MAcmVnaW9uLT5nZXRVc2VkKCkAZ2V0UGF5bG9hZAAoY2hhciopZXh0cmFQdHIgPT0gKGNoYXIqKXB0ciArIHNicmtTaXplAGFsbG9jYXRlUmVnaW9uACFsYXN0UmVnaW9uACFmaXJzdFJlZ2lvbgBmaXJzdFJlZ2lvbgBwdHIgPT0gZ2V0QWZ0ZXIobGFzdFJlZ2lvbikAZXh0ZW5kTGFzdFJlZ2lvbgBhZGRUb0ZyZWVMaXN0AGdldEJpZ0Vub3VnaEZyZWVMaXN0SW5kZXgAcGF5bG9hZFNpemUgPj0gc2l6ZQBwb3NzaWJseVNwbGl0UmVtYWluZGVyAGV4dHJhID49IE1JTl9SRUdJT05fU0laRQB0b3RhbFNwbGl0U2l6ZSA+PSBNSU5fUkVHSU9OX1NJWkUAbWVyZ2VJbnRvRXhpc3RpbmdGcmVlUmVnaW9uAHJlZ2lvbiA9PSBsYXN0UmVnaW9uAGVtbWFsbG9jX3JlYWxsb2M=';
3 |
--------------------------------------------------------------------------------