├── .babelrc ├── .esdoc.json ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE.md └── workflows │ └── main.yml ├── .gitignore ├── .htmlhintrc ├── .ncurc.json ├── .npmignore ├── CHANGES.md ├── LICENSE ├── README.md ├── build-config ├── fragments │ ├── common.js │ ├── dev.js │ ├── main.js │ └── prod.js ├── webpack.dev.main.js └── webpack.prod.main.js ├── docs ├── .nojekyll ├── README.md ├── _sidebar.md ├── angular.md ├── api │ ├── ast │ │ └── source │ │ │ ├── .external-ecmascript.js.json │ │ │ └── js │ │ │ ├── defaults.js.json │ │ │ ├── event.js.json │ │ │ ├── middleware.js.json │ │ │ ├── utils │ │ │ ├── format-time.js.json │ │ │ └── log.js.json │ │ │ └── videojs.wavesurfer.js.json │ ├── badge.svg │ ├── class │ │ └── src │ │ │ └── js │ │ │ ├── event.js~Event.html │ │ │ └── videojs.wavesurfer.js~Wavesurfer.html │ ├── coverage.json │ ├── css │ │ ├── github.css │ │ ├── identifiers.css │ │ ├── manual.css │ │ ├── prettify-tomorrow.css │ │ ├── search.css │ │ ├── source.css │ │ ├── style.css │ │ └── test.css │ ├── file │ │ └── src │ │ │ └── js │ │ │ ├── defaults.js.html │ │ │ ├── event.js.html │ │ │ ├── middleware.js.html │ │ │ ├── utils │ │ │ ├── format-time.js.html │ │ │ └── log.js.html │ │ │ └── videojs.wavesurfer.js.html │ ├── function │ │ └── index.html │ ├── identifiers.html │ ├── image │ │ ├── badge.svg │ │ ├── esdoc-logo-mini-black.png │ │ ├── esdoc-logo-mini.png │ │ ├── github.png │ │ ├── manual-badge.svg │ │ └── search.png │ ├── index.html │ ├── index.json │ ├── lint.json │ ├── script │ │ ├── inherited-summary.js │ │ ├── inner-link.js │ │ ├── manual.js │ │ ├── patch-for-local.js │ │ ├── prettify │ │ │ ├── Apache-License-2.0.txt │ │ │ └── prettify.js │ │ ├── pretty-print.js │ │ ├── search.js │ │ ├── search_index.js │ │ └── test-summary.js │ ├── source.html │ └── variable │ │ └── index.html ├── change-device.md ├── controls.md ├── css │ └── style.css ├── demo │ ├── fluid.html │ ├── index.html │ ├── input.html │ ├── live.html │ ├── media │ │ ├── example.mp4 │ │ ├── hal-peaks.json │ │ ├── hal.mp3 │ │ ├── hal.vtt │ │ └── hal.wav │ ├── multi.html │ ├── output.html │ ├── peaks.html │ ├── plugin.html │ ├── react │ │ ├── index.html │ │ └── index.js │ ├── safari-workaround.js │ ├── texttrack.html │ └── video.html ├── development.md ├── donate.md ├── events.md ├── examples.md ├── img │ ├── screenshot.png │ └── text-tracks.png ├── index.html ├── install.md ├── methods.md ├── microphone.md ├── options.md ├── peaks.md ├── plugins.md ├── react.md ├── responsive.md ├── text-tracks.md ├── tools │ └── update-videojs.js ├── usage.md ├── vue.md └── webpack.md ├── eslint.config.js ├── examples ├── fluid.html ├── index.html ├── input.html ├── live.html ├── media │ ├── example.mp4 │ ├── hal-peaks.json │ ├── hal.vtt │ └── hal.wav ├── multi.html ├── output.html ├── peaks.html ├── plugin.html ├── react │ ├── index.html │ └── index.js ├── safari-workaround.js ├── texttrack.html └── video.html ├── karma.conf.js ├── package-lock.json ├── package.json ├── src ├── css │ ├── fluid.scss │ ├── main.scss │ └── videojs.wavesurfer.scss └── js │ ├── defaults.js │ ├── event.js │ ├── middleware.js │ ├── utils │ ├── format-time.js │ └── log.js │ └── videojs.wavesurfer.js └── test ├── defaults.spec.js ├── fluid.spec.js ├── live.spec.js ├── options.spec.js ├── support ├── demo-peaks-invalid.json ├── demo-peaks.json ├── demo.vtt ├── demo.wav └── stars.mp4 ├── test-helpers.js ├── tracks.spec.js ├── utils.spec.js ├── video.spec.js └── videojs.wavesurfer.spec.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { 4 | "targets": "last 3 version", 5 | "debug": false, 6 | "modules": "commonjs" 7 | }] 8 | ], 9 | "plugins": ["add-module-exports"], 10 | "env": { 11 | "test": { 12 | "plugins": [ 13 | ["istanbul", { 14 | "exclude": [ 15 | "test/**/*.spec.js" 16 | ] 17 | }] 18 | ] 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.esdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": "./src", 3 | "destination": "./docs/api", 4 | "plugins": [{"name": "esdoc-standard-plugin"}] 5 | } 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: thijs-triemstra 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | Briefly describe the issue. 3 | 4 | ## Steps to reproduce 5 | Explain in detail the exact steps necessary to reproduce the issue. 6 | 7 | ## Results 8 | ### Expected 9 | Please describe what you expected to see. 10 | 11 | ### Actual 12 | Please describe what actually happened. 13 | 14 | ### Error output 15 | If there are any errors at all, please include them here. 16 | 17 | ## Versions 18 | Make sure to include the following versions: 19 | ### videojs/wavesurfer 20 | what version of video.js, videojs-wavesurfer and wavesurfer.js does this occur with? 21 | ### Browsers 22 | what browser(s) are affected? Make sure to test with all third-party browser **extensions disabled**. 23 | ### OSes 24 | what platforms (operating systems and devices) are affected? 25 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: videojs-wavesurfer 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: 15 | - ubuntu-latest 16 | - windows-latest 17 | node-version: 18 | - 20 19 | architecture: 20 | - x64 21 | name: ${{ matrix.os }} - Node ${{ matrix.node-version }} (${{ matrix.architecture }}) 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Update system 25 | if: runner.os == 'Linux' 26 | run: sudo apt-get update 27 | - name: Install system dependencies 28 | if: runner.os == 'Linux' 29 | run: sudo apt-get install -y ubuntu-restricted-addons chromium-codecs-ffmpeg-extra gstreamer1.0-libav gstreamer1.0-plugins-ugly gstreamer1.0-vaapi 30 | - name: Using Node.js ${{ matrix.node-version }} 31 | uses: actions/setup-node@v4 32 | with: 33 | node-version: ${{ matrix.node-version }} 34 | architecture: ${{ matrix.architecture }} 35 | - name: Get npm cache directory 36 | id: npm-cache-dir 37 | shell: bash 38 | run: echo "dir=$(npm config get cache)" >> ${GITHUB_OUTPUT} 39 | - name: Cache npm modules 40 | uses: actions/cache@v4 41 | id: npm-cache # use this to check for `cache-hit` ==> if: steps.npm-cache.outputs.cache-hit != 'true' 42 | with: 43 | path: ${{ steps.npm-cache-dir.outputs.dir }} 44 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 45 | restore-keys: | 46 | ${{ runner.os }}-node- 47 | - name: Install Node.js modules 48 | run: npm install 49 | - name: Build 50 | run: npm run build 51 | - name: Test 52 | run: npm run test 53 | - name: Coveralls 54 | if: runner.os == 'Linux' 55 | uses: coverallsapp/github-action@v2 56 | with: 57 | path-to-lcov: ${{ github.workspace }}/coverage/lcov/lcov.info 58 | github-token: ${{ secrets.GITHUB_TOKEN }} 59 | - name: Documentation 60 | run: npm run docs 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # osx 2 | .DS_Store 3 | 4 | # IDE 5 | .project 6 | .pydevproject 7 | .vscode 8 | 9 | *.pid 10 | *.log 11 | 12 | # examples 13 | examples/media/test 14 | 15 | # Dependency-related directories 16 | node_modules 17 | bower_components 18 | 19 | # Build-related 20 | dist/ 21 | tmp 22 | coverage/ 23 | .build_cache/ 24 | 25 | # Test 26 | .chrome 27 | -------------------------------------------------------------------------------- /.htmlhintrc: -------------------------------------------------------------------------------- 1 | { 2 | "tagname-lowercase": true, 3 | "attr-lowercase": true, 4 | "attr-value-double-quotes": false, 5 | "doctype-first": true, 6 | "tag-pair": true, 7 | "spec-char-escape": true, 8 | "id-unique": true, 9 | "src-not-empty": true, 10 | "attr-no-duplication": true, 11 | "title-require": true 12 | } 13 | -------------------------------------------------------------------------------- /.ncurc.json: -------------------------------------------------------------------------------- 1 | { 2 | "reject": [ 3 | "parse-ms", 4 | "video.js", 5 | "wavesurfer.js" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Intentionally left blank, so that npm does not ignore anything by default, 2 | # but relies on the package.json "files" array to explicitly define what ends 3 | # up in the package. 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2024 Collab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | videojs-wavesurfer 2 | ================== 3 | 4 | A [video.js](https://www.videojs.com/) plugin that adds a navigable waveform 5 | for audio and video files, using the [wavesurfer.js](https://github.com/katspaugh/wavesurfer.js) 6 | library. Includes support for fullscreen mode and [real-time visualization of microphone 7 | input](https://collab-project.github.io/videojs-wavesurfer/#/microphone). 8 | 9 | ![Screenshot](https://raw.githubusercontent.com/collab-project/videojs-wavesurfer/master/docs/img/screenshot.png?raw=true "Screenshot") 10 | 11 | [![npm version](https://img.shields.io/npm/v/videojs-wavesurfer.svg?style=flat)](https://www.npmjs.com/package/videojs-wavesurfer) 12 | [![npm](https://img.shields.io/npm/dm/videojs-wavesurfer.svg)](https://github.com/collab-project/videojs-wavesurfer/releases) 13 | [![License](https://img.shields.io/npm/l/videojs-wavesurfer.svg)](LICENSE) 14 | [![Build Status](https://github.com/collab-project/videojs-wavesurfer/workflows/videojs-wavesurfer/badge.svg?branch=master)](https://github.com/collab-project/videojs-wavesurfer/actions?workflow=videojs-wavesurfer) 15 | [![Coverage Status](https://coveralls.io/repos/github/collab-project/videojs-wavesurfer/badge.svg?branch=master)](https://coveralls.io/github/collab-project/videojs-wavesurfer?branch=master) 16 | ![Size](https://img.shields.io/bundlephobia/minzip/videojs-wavesurfer.svg?style=flat) 17 | ![Stars](https://img.shields.io/github/stars/collab-project/videojs-wavesurfer.svg?style=social) 18 | 19 | ## Documentation 20 | 21 | The documentation and examples can be found on: https://collab-project.github.io/videojs-wavesurfer 22 | 23 | ## License 24 | 25 | This work is licensed under the [MIT License](LICENSE). 26 | -------------------------------------------------------------------------------- /build-config/fragments/common.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file common.js 3 | * @since 2.3.0 4 | */ 5 | 6 | const path = require('path'); 7 | const webpack = require('webpack'); 8 | const datefns = require('date-fns'); 9 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 10 | 11 | const date = datefns.format(new Date(), 'yyyy'); 12 | const rootDir = path.resolve(__dirname, '..', '..'); 13 | const pckg = require(path.join(__dirname, '..', '..', 'package.json')); 14 | 15 | process.traceDeprecation = true; 16 | 17 | // add JS banner with copyright and version info 18 | let jsBanner = `${pckg.name} 19 | @version ${pckg.version} 20 | @see ${pckg.homepage} 21 | @copyright 2014-${date} ${pckg.author} 22 | @license ${pckg.license}`; 23 | let jsBannerPlugin = new webpack.BannerPlugin({ 24 | banner: jsBanner, 25 | test: /\.js$/ 26 | }); 27 | 28 | // add CSS banner with version info 29 | let cssBanner = `/*! 30 | Default styles for ${pckg.name} ${pckg.version} 31 | */`; 32 | let cssBannerPlugin = new webpack.BannerPlugin({ 33 | banner: cssBanner, 34 | raw: true, 35 | test: /\.css$/ 36 | }); 37 | 38 | // inject JS version number 39 | let jsVersionPlugin = new webpack.DefinePlugin({ 40 | '__VERSION__': JSON.stringify(pckg.version) 41 | }); 42 | 43 | module.exports = { 44 | devtool: false, 45 | context: rootDir, 46 | output: { 47 | libraryTarget: 'umd', 48 | umdNamedDefine: true 49 | }, 50 | performance: { 51 | hints: false 52 | }, 53 | stats: { 54 | colors: true 55 | }, 56 | // specify dependencies for the library that are not resolved by webpack, 57 | // but become dependencies of the output: they are imported from the 58 | // environment during runtime and never directly included in the 59 | // videojs-wavesurfer library 60 | externals: { 61 | 'video.js': { 62 | commonjs: 'video.js', 63 | commonjs2: 'video.js', 64 | amd: 'video.js', 65 | root: 'videojs' // indicates global variable 66 | }, 67 | 'wavesurfer.js': { 68 | commonjs: 'wavesurfer.js', 69 | commonjs2: 'wavesurfer.js', 70 | amd: 'wavesurfer.js', 71 | root: 'WaveSurfer' // indicates global variable 72 | } 73 | }, 74 | module: { 75 | rules: [ 76 | { 77 | // javascript 78 | test: /\.js$/, 79 | include: path.resolve(rootDir, 'src', 'js'), 80 | exclude: /(node_modules|bower_components|test)/, 81 | use: { 82 | loader: 'babel-loader', 83 | options: { 84 | comments: false 85 | } 86 | } 87 | }, 88 | { 89 | // scss 90 | test: /\.scss$/, 91 | include: path.resolve(rootDir, 'src', 'css'), 92 | exclude: /(node_modules|bower_components|test)/, 93 | use: [ 94 | MiniCssExtractPlugin.loader, 95 | 'css-loader', 96 | 'sass-loader' 97 | ] 98 | } 99 | ] 100 | }, 101 | plugins: [ 102 | jsBannerPlugin, 103 | jsVersionPlugin, 104 | cssBannerPlugin 105 | ] 106 | }; 107 | -------------------------------------------------------------------------------- /build-config/fragments/dev.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file dev.js 3 | * @since 2.3.0 4 | */ 5 | 6 | const path = require('path'); 7 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 8 | const RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts'); 9 | 10 | const contentBase = path.resolve(__dirname, '..', '..'); 11 | 12 | module.exports = { 13 | mode: 'development', 14 | devServer: { 15 | port: 8080, 16 | static: [ 17 | { 18 | directory: contentBase, 19 | staticOptions: {}, 20 | publicPath: '/', 21 | serveIndex: true, 22 | watch: { 23 | ignored: [ 24 | /.build_cache/, 25 | /.chrome/, 26 | /docs/, 27 | /node_modules/, 28 | /bower_components/, 29 | /coverage/, 30 | /build-config/, 31 | /test/, 32 | /vendor/ 33 | ] 34 | } 35 | } 36 | ] 37 | }, 38 | plugins: [ 39 | new RemoveEmptyScriptsPlugin(), 40 | new MiniCssExtractPlugin({ 41 | filename: 'css/videojs.wavesurfer.css', 42 | chunkFilename: 'css/[id].css' 43 | }) 44 | ] 45 | }; 46 | -------------------------------------------------------------------------------- /build-config/fragments/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file main.js 3 | * @since 2.3.0 4 | */ 5 | 6 | const path = require('path'); 7 | const rootDir = path.resolve(__dirname, '..', '..'); 8 | const sourceDir = path.join(rootDir, 'src'); 9 | 10 | module.exports = { 11 | entry: { 12 | code: { 13 | import: path.join(sourceDir, 'js', 'videojs.wavesurfer.js'), 14 | filename: 'videojs.wavesurfer.js' 15 | }, 16 | style: { 17 | import: path.join(sourceDir, 'css', 'videojs.wavesurfer.scss') 18 | } 19 | }, 20 | output: { 21 | path: path.join(rootDir, 'dist'), 22 | library: 'VideojsWavesurfer' 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /build-config/fragments/prod.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file prod.js 3 | * @since 2.3.0 4 | */ 5 | 6 | const TerserPlugin = require('terser-webpack-plugin'); 7 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 8 | const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); 9 | const RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts'); 10 | 11 | module.exports = { 12 | mode: 'production', 13 | optimization: { 14 | minimize: true, 15 | minimizer: [ 16 | new TerserPlugin({ 17 | parallel: true, 18 | extractComments: false, 19 | terserOptions: { 20 | output: { 21 | // preserve license comments 22 | comments: /@license/i 23 | } 24 | } 25 | }), 26 | new CssMinimizerPlugin() 27 | ] 28 | }, 29 | plugins: [ 30 | new RemoveEmptyScriptsPlugin(), 31 | new MiniCssExtractPlugin({ 32 | filename: 'css/videojs.wavesurfer.min.css', 33 | chunkFilename: 'css/[id].css' 34 | }) 35 | ] 36 | }; 37 | -------------------------------------------------------------------------------- /build-config/webpack.dev.main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2.3.0 3 | */ 4 | 5 | const { merge } = require('webpack-merge'); 6 | const path = require('path'); 7 | 8 | const common = require('./fragments/common'); 9 | const dev = require('./fragments/dev'); 10 | const main = require('./fragments/main'); 11 | 12 | module.exports = merge(common, dev, main); 13 | -------------------------------------------------------------------------------- /build-config/webpack.prod.main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2.3.0 3 | */ 4 | 5 | const { merge } = require('webpack-merge'); 6 | const path = require('path'); 7 | 8 | const common = require('./fragments/common'); 9 | const prod = require('./fragments/prod'); 10 | const main = require('./fragments/main'); 11 | 12 | module.exports = merge(common, prod, main, { 13 | entry: { 14 | code: { 15 | filename: 'videojs.wavesurfer.min.js' 16 | } 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collab-project/videojs-wavesurfer/cd3284bf8e2da59039fe59af8ce33003d69bd6ae/docs/.nojekyll -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # videojs-wavesurfer 2 | 3 | A [video.js](https://www.videojs.com/) plugin that adds a navigable waveform 4 | for audio and video files, using the [wavesurfer.js](https://github.com/katspaugh/wavesurfer.js) 5 | library. 6 | 7 | Includes support for fullscreen mode and [real-time visualization of microphone 8 | input](microphone.md). 9 | 10 | ![Screenshot](img/screenshot.png?raw=true "videojs-wavesurfer") 11 | -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | - Getting started 2 | - [Installation](install.md) 3 | - [Usage](usage.md) 4 | - [Examples](examples.md) 5 | 6 | - Guide 7 | - [Options](options.md) 8 | - [Methods](methods.md) 9 | - [Events](events.md) 10 | - [Customizing controls](controls.md) 11 | - [Reponsive layout](responsive.md) 12 | - [Text tracks](text-tracks.md) 13 | - [Microphone plugin](microphone.md) 14 | - [Using peaks for large audio files](peaks.md) 15 | - [Change audio device](change-device.md) 16 | - [More features using other plugins](plugins.md) 17 | - [API documentation](https://collab-project.github.io/videojs-wavesurfer/api/) 18 | 19 | - Frameworks 20 | - [Webpack](webpack.md) 21 | - [React](react.md) 22 | - [Angular](angular.md) 23 | - [Vue](vue.md) 24 | 25 | - [Development](development.md) 26 | - [Changelog](/changelog) 27 | - [Github](https://github.com/collab-project/videojs-wavesurfer) 28 | - [License](https://github.com/collab-project/videojs-wavesurfer/blob/master/LICENSE) 29 | - [Donate](donate.md) 30 | -------------------------------------------------------------------------------- /docs/api/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | document 13 | document 14 | 60% 15 | 60% 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/api/class/src/js/event.js~Event.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Event | videojs-wavesurfer 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | 17 | Reference 18 | Source 19 | 20 | 27 |
28 | 29 | 42 | 43 |
44 | 45 | public 46 | class 47 | 48 | 49 | | since 2.8.0 50 | | source 51 |
52 | 53 |
54 |

Event

55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 |
96 | 97 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /docs/api/coverage.json: -------------------------------------------------------------------------------- 1 | { 2 | "coverage": "60.34%", 3 | "expectCount": 58, 4 | "actualCount": 35, 5 | "files": { 6 | "src/js/defaults.js": { 7 | "expectCount": 1, 8 | "actualCount": 1, 9 | "undocumentLines": [] 10 | }, 11 | "src/js/event.js": { 12 | "expectCount": 1, 13 | "actualCount": 1, 14 | "undocumentLines": [] 15 | }, 16 | "src/js/middleware.js": { 17 | "expectCount": 1, 18 | "actualCount": 1, 19 | "undocumentLines": [] 20 | }, 21 | "src/js/utils/format-time.js": { 22 | "expectCount": 1, 23 | "actualCount": 1, 24 | "undocumentLines": [] 25 | }, 26 | "src/js/utils/log.js": { 27 | "expectCount": 3, 28 | "actualCount": 1, 29 | "undocumentLines": [ 30 | 8, 31 | 9 32 | ] 33 | }, 34 | "src/js/videojs.wavesurfer.js": { 35 | "expectCount": 51, 36 | "actualCount": 30, 37 | "undocumentLines": [ 38 | 18, 39 | 20, 40 | 21, 41 | 22, 42 | 25, 43 | 26, 44 | 27, 45 | 55, 46 | 56, 47 | 57, 48 | 58, 49 | 59, 50 | 60, 51 | 61, 52 | 140, 53 | 162, 54 | 164, 55 | 165, 56 | 199, 57 | 200, 58 | 832 59 | ] 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /docs/api/css/github.css: -------------------------------------------------------------------------------- 1 | /* github markdown */ 2 | .github-markdown { 3 | font-size: 16px; 4 | } 5 | 6 | .github-markdown h1, 7 | .github-markdown h2, 8 | .github-markdown h3, 9 | .github-markdown h4, 10 | .github-markdown h5 { 11 | margin-top: 1em; 12 | margin-bottom: 16px; 13 | font-weight: bold; 14 | padding: 0; 15 | } 16 | 17 | .github-markdown h1:nth-of-type(1) { 18 | margin-top: 0; 19 | } 20 | 21 | .github-markdown h1 { 22 | font-size: 2em; 23 | padding-bottom: 0.3em; 24 | } 25 | 26 | .github-markdown h2 { 27 | font-size: 1.75em; 28 | padding-bottom: 0.3em; 29 | } 30 | 31 | .github-markdown h3 { 32 | font-size: 1.5em; 33 | } 34 | 35 | .github-markdown h4 { 36 | font-size: 1.25em; 37 | } 38 | 39 | .github-markdown h5 { 40 | font-size: 1em; 41 | } 42 | 43 | .github-markdown ul, .github-markdown ol { 44 | padding-left: 2em; 45 | } 46 | 47 | .github-markdown pre > code { 48 | font-size: 0.85em; 49 | } 50 | 51 | .github-markdown table { 52 | margin-bottom: 1em; 53 | border-collapse: collapse; 54 | border-spacing: 0; 55 | } 56 | 57 | .github-markdown table tr { 58 | background-color: #fff; 59 | border-top: 1px solid #ccc; 60 | } 61 | 62 | .github-markdown table th, 63 | .github-markdown table td { 64 | padding: 6px 13px; 65 | border: 1px solid #ddd; 66 | } 67 | 68 | .github-markdown table tr:nth-child(2n) { 69 | background-color: #f8f8f8; 70 | } 71 | 72 | .github-markdown hr { 73 | border-right: 0; 74 | border-bottom: 1px solid #e5e5e5; 75 | border-left: 0; 76 | border-top: 0; 77 | } 78 | 79 | /** badge(.svg) does not have border */ 80 | .github-markdown img:not([src*=".svg"]) { 81 | max-width: 100%; 82 | box-shadow: 1px 1px 1px rgba(0,0,0,0.5); 83 | } 84 | -------------------------------------------------------------------------------- /docs/api/css/identifiers.css: -------------------------------------------------------------------------------- 1 | .identifiers-wrap { 2 | display: flex; 3 | align-items: flex-start; 4 | } 5 | 6 | .identifier-dir-tree { 7 | background: #fff; 8 | border: solid 1px #ddd; 9 | border-radius: 0.25em; 10 | top: 52px; 11 | position: -webkit-sticky; 12 | position: sticky; 13 | max-height: calc(100vh - 155px); 14 | overflow-y: scroll; 15 | min-width: 200px; 16 | margin-left: 1em; 17 | } 18 | 19 | .identifier-dir-tree-header { 20 | padding: 0.5em; 21 | background-color: #fafafa; 22 | border-bottom: solid 1px #ddd; 23 | } 24 | 25 | .identifier-dir-tree-content { 26 | padding: 0 0.5em 0; 27 | } 28 | 29 | .identifier-dir-tree-content > div { 30 | padding-top: 0.25em; 31 | padding-bottom: 0.25em; 32 | } 33 | 34 | .identifier-dir-tree-content a { 35 | color: inherit; 36 | } 37 | 38 | -------------------------------------------------------------------------------- /docs/api/css/manual.css: -------------------------------------------------------------------------------- 1 | .github-markdown .manual-toc { 2 | padding-left: 0; 3 | } 4 | 5 | .manual-index .manual-cards { 6 | display: flex; 7 | flex-wrap: wrap; 8 | } 9 | 10 | .manual-index .manual-card-wrap { 11 | width: 280px; 12 | padding: 10px 20px 10px 0; 13 | box-sizing: border-box; 14 | } 15 | 16 | .manual-index .manual-card-wrap > h1 { 17 | margin: 0; 18 | font-size: 1em; 19 | font-weight: 600; 20 | padding: 0.2em 0 0.2em 0.5em; 21 | border-radius: 0.1em 0.1em 0 0; 22 | border: none; 23 | } 24 | 25 | .manual-index .manual-card-wrap > h1 span { 26 | color: #555; 27 | } 28 | 29 | .manual-index .manual-card { 30 | height: 200px; 31 | overflow: hidden; 32 | border: solid 1px rgba(230, 230, 230, 0.84); 33 | border-radius: 0 0 0.1em 0.1em; 34 | padding: 8px; 35 | position: relative; 36 | } 37 | 38 | .manual-index .manual-card > div { 39 | transform: scale(0.4); 40 | transform-origin: 0 0; 41 | width: 250%; 42 | } 43 | 44 | .manual-index .manual-card > a { 45 | position: absolute; 46 | top: 0; 47 | left: 0; 48 | width: 100%; 49 | height: 100%; 50 | background: rgba(210, 210, 210, 0.1); 51 | } 52 | 53 | .manual-index .manual-card > a:hover { 54 | background: none; 55 | } 56 | 57 | .manual-index .manual-badge { 58 | margin: 0; 59 | } 60 | 61 | .manual-index .manual-user-index { 62 | margin-bottom: 1em; 63 | border-bottom: solid 1px #ddd; 64 | } 65 | 66 | .manual-root .navigation { 67 | padding-left: 4px; 68 | margin-top: 4px; 69 | } 70 | 71 | .navigation .manual-toc-root > div { 72 | padding-left: 0.25em; 73 | padding-right: 0.75em; 74 | } 75 | 76 | .github-markdown .manual-toc-title a { 77 | color: inherit; 78 | } 79 | 80 | .manual-breadcrumb-list { 81 | font-size: 0.8em; 82 | margin-bottom: 1em; 83 | } 84 | 85 | .manual-toc-title a:hover { 86 | color: #039BE5; 87 | } 88 | 89 | .manual-toc li { 90 | margin: 0.75em 0; 91 | list-style-type: none; 92 | } 93 | 94 | .navigation .manual-toc [class^="indent-h"] a { 95 | color: #666; 96 | } 97 | 98 | .navigation .manual-toc .indent-h1 a { 99 | color: #555; 100 | font-weight: 600; 101 | display: block; 102 | } 103 | 104 | .manual-toc .indent-h1 { 105 | display: block; 106 | margin: 0.4em 0 0 0.25em; 107 | padding: 0.2em 0 0.2em 0.5em; 108 | border-radius: 0.1em; 109 | } 110 | 111 | .manual-root .navigation .manual-toc li:not(.indent-h1) { 112 | margin-top: 0.5em; 113 | } 114 | 115 | .manual-toc .indent-h2 { 116 | display: none; 117 | margin-left: 1.5em; 118 | } 119 | .manual-toc .indent-h3 { 120 | display: none; 121 | margin-left: 2.5em; 122 | } 123 | .manual-toc .indent-h4 { 124 | display: none; 125 | margin-left: 3.5em; 126 | } 127 | .manual-toc .indent-h5 { 128 | display: none; 129 | margin-left: 4.5em; 130 | } 131 | 132 | .manual-nav li { 133 | margin: 0.75em 0; 134 | } 135 | -------------------------------------------------------------------------------- /docs/api/css/prettify-tomorrow.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Theme */ 2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 3 | /* Pretty printing styles. Used with prettify.js. */ 4 | /* SPAN elements with the classes below are added by prettyprint. */ 5 | /* plain text */ 6 | .pln { 7 | color: #4d4d4c; } 8 | 9 | @media screen { 10 | /* string content */ 11 | .str { 12 | color: #718c00; } 13 | 14 | /* a keyword */ 15 | .kwd { 16 | color: #8959a8; } 17 | 18 | /* a comment */ 19 | .com { 20 | color: #8e908c; } 21 | 22 | /* a type name */ 23 | .typ { 24 | color: #4271ae; } 25 | 26 | /* a literal value */ 27 | .lit { 28 | color: #f5871f; } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #4d4d4c; } 33 | 34 | /* lisp open bracket */ 35 | .opn { 36 | color: #4d4d4c; } 37 | 38 | /* lisp close bracket */ 39 | .clo { 40 | color: #4d4d4c; } 41 | 42 | /* a markup tag name */ 43 | .tag { 44 | color: #c82829; } 45 | 46 | /* a markup attribute name */ 47 | .atn { 48 | color: #f5871f; } 49 | 50 | /* a markup attribute value */ 51 | .atv { 52 | color: #3e999f; } 53 | 54 | /* a declaration */ 55 | .dec { 56 | color: #f5871f; } 57 | 58 | /* a variable name */ 59 | .var { 60 | color: #c82829; } 61 | 62 | /* a function name */ 63 | .fun { 64 | color: #4271ae; } } 65 | /* Use higher contrast and text-weight for printable form. */ 66 | @media print, projection { 67 | .str { 68 | color: #060; } 69 | 70 | .kwd { 71 | color: #006; 72 | font-weight: bold; } 73 | 74 | .com { 75 | color: #600; 76 | font-style: italic; } 77 | 78 | .typ { 79 | color: #404; 80 | font-weight: bold; } 81 | 82 | .lit { 83 | color: #044; } 84 | 85 | .pun, .opn, .clo { 86 | color: #440; } 87 | 88 | .tag { 89 | color: #006; 90 | font-weight: bold; } 91 | 92 | .atn { 93 | color: #404; } 94 | 95 | .atv { 96 | color: #060; } } 97 | /* Style */ 98 | /* 99 | pre.prettyprint { 100 | background: white; 101 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 102 | font-size: 12px; 103 | line-height: 1.5; 104 | border: 1px solid #ccc; 105 | padding: 10px; } 106 | */ 107 | 108 | /* Specify class=linenums on a pre to get line numbering */ 109 | ol.linenums { 110 | margin-top: 0; 111 | margin-bottom: 0; } 112 | 113 | /* IE indents via margin-left */ 114 | li.L0, 115 | li.L1, 116 | li.L2, 117 | li.L3, 118 | li.L4, 119 | li.L5, 120 | li.L6, 121 | li.L7, 122 | li.L8, 123 | li.L9 { 124 | /* */ } 125 | 126 | /* Alternate shading for lines */ 127 | li.L1, 128 | li.L3, 129 | li.L5, 130 | li.L7, 131 | li.L9 { 132 | /* */ } 133 | -------------------------------------------------------------------------------- /docs/api/css/search.css: -------------------------------------------------------------------------------- 1 | /* search box */ 2 | .search-box { 3 | position: absolute; 4 | top: 10px; 5 | right: 50px; 6 | padding-right: 8px; 7 | padding-bottom: 10px; 8 | line-height: normal; 9 | font-size: 12px; 10 | } 11 | 12 | .search-box img { 13 | width: 20px; 14 | vertical-align: top; 15 | } 16 | 17 | .search-input { 18 | display: inline; 19 | visibility: hidden; 20 | width: 0; 21 | padding: 2px; 22 | height: 1.5em; 23 | outline: none; 24 | background: transparent; 25 | border: 1px #0af; 26 | border-style: none none solid none; 27 | vertical-align: bottom; 28 | } 29 | 30 | .search-input-edge { 31 | display: none; 32 | width: 1px; 33 | height: 5px; 34 | background-color: #0af; 35 | vertical-align: bottom; 36 | } 37 | 38 | .search-result { 39 | position: absolute; 40 | display: none; 41 | height: 600px; 42 | width: 100%; 43 | padding: 0; 44 | margin-top: 5px; 45 | margin-left: 24px; 46 | background: white; 47 | box-shadow: 1px 1px 4px rgb(0,0,0); 48 | white-space: nowrap; 49 | overflow-y: scroll; 50 | } 51 | 52 | .search-result-import-path { 53 | color: #aaa; 54 | font-size: 12px; 55 | } 56 | 57 | .search-result li { 58 | list-style: none; 59 | padding: 2px 4px; 60 | } 61 | 62 | .search-result li a { 63 | display: block; 64 | } 65 | 66 | .search-result li.selected { 67 | background: #ddd; 68 | } 69 | 70 | .search-result li.search-separator { 71 | background: rgb(37, 138, 175); 72 | color: white; 73 | } 74 | 75 | .search-box.active .search-input { 76 | visibility: visible; 77 | transition: width 0.2s ease-out; 78 | width: 300px; 79 | } 80 | 81 | .search-box.active .search-input-edge { 82 | display: inline-block; 83 | } 84 | 85 | -------------------------------------------------------------------------------- /docs/api/css/source.css: -------------------------------------------------------------------------------- 1 | table.files-summary { 2 | width: 100%; 3 | margin: 10px 0; 4 | border-spacing: 0; 5 | border: 0; 6 | border-collapse: collapse; 7 | text-align: right; 8 | } 9 | 10 | table.files-summary tbody tr:hover { 11 | background: #eee; 12 | } 13 | 14 | table.files-summary td:first-child, 15 | table.files-summary td:nth-of-type(2) { 16 | text-align: left; 17 | } 18 | 19 | table.files-summary[data-use-coverage="false"] td.coverage { 20 | display: none; 21 | } 22 | 23 | table.files-summary thead { 24 | background: #fafafa; 25 | } 26 | 27 | table.files-summary td { 28 | border: solid 1px #ddd; 29 | padding: 4px 10px; 30 | vertical-align: top; 31 | } 32 | 33 | table.files-summary td.identifiers > span { 34 | display: block; 35 | margin-top: 4px; 36 | } 37 | table.files-summary td.identifiers > span:first-child { 38 | margin-top: 0; 39 | } 40 | 41 | table.files-summary .coverage-count { 42 | font-size: 12px; 43 | color: #aaa; 44 | display: inline-block; 45 | min-width: 40px; 46 | } 47 | 48 | .total-coverage-count { 49 | position: relative; 50 | bottom: 2px; 51 | font-size: 12px; 52 | color: #666; 53 | font-weight: 500; 54 | padding-left: 5px; 55 | } 56 | -------------------------------------------------------------------------------- /docs/api/css/test.css: -------------------------------------------------------------------------------- 1 | table.test-summary thead { 2 | background: #fafafa; 3 | } 4 | 5 | table.test-summary thead .test-description { 6 | width: 50%; 7 | } 8 | 9 | table.test-summary { 10 | width: 100%; 11 | margin: 10px 0; 12 | border-spacing: 0; 13 | border: 0; 14 | border-collapse: collapse; 15 | } 16 | 17 | table.test-summary thead .test-count { 18 | width: 3em; 19 | } 20 | 21 | table.test-summary tbody tr:hover { 22 | background-color: #eee; 23 | } 24 | 25 | table.test-summary td { 26 | border: solid 1px #ddd; 27 | padding: 4px 10px; 28 | vertical-align: top; 29 | } 30 | 31 | table.test-summary td p { 32 | margin: 0; 33 | } 34 | 35 | table.test-summary tr.test-interface .toggle { 36 | display: inline-block; 37 | float: left; 38 | margin-right: 4px; 39 | cursor: pointer; 40 | font-size: 0.8em; 41 | padding-top: 0.25em; 42 | } 43 | 44 | table.test-summary tr.test-interface .toggle.opened:before { 45 | content: '▼'; 46 | } 47 | 48 | table.test-summary tr.test-interface .toggle.closed:before { 49 | content: '▶'; 50 | } 51 | 52 | table.test-summary .test-target > span { 53 | display: block; 54 | margin-top: 4px; 55 | } 56 | table.test-summary .test-target > span:first-child { 57 | margin-top: 0; 58 | } 59 | -------------------------------------------------------------------------------- /docs/api/file/src/js/defaults.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/js/defaults.js | videojs-wavesurfer 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | 17 | Reference 18 | Source 19 | 20 | 27 |
28 | 29 | 42 | 43 |

src/js/defaults.js

44 |
/**
45 |  * @file defaults.js
46 |  * @since 2.0.0
47 |  */
48 | 
49 | // plugin defaults
50 | const pluginDefaultOptions = {
51 |     // Display console log messages.
52 |     debug: false,
53 |     // Boolean indicating if milliseconds should be included,
54 |     // e.g. "00:00:000" vs "00:00".
55 |     displayMilliseconds: true
56 | };
57 | 
58 | export default pluginDefaultOptions;
59 | 
60 | 61 |
62 | 63 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /docs/api/file/src/js/event.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/js/event.js | videojs-wavesurfer 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | 17 | Reference 18 | Source 19 | 20 | 27 |
28 | 29 | 42 | 43 |

src/js/event.js

44 |
/**
45 |  * @file event.js
46 |  * @since 2.8.0
47 |  */
48 | 
49 | class Event {}
50 | 
51 | // video.js
52 | Event.READY = 'ready';
53 | Event.ERROR = 'error';
54 | Event.VOLUMECHANGE = 'volumechange';
55 | Event.FULLSCREENCHANGE = 'fullscreenchange';
56 | Event.TIMEUPDATE = 'timeupdate';
57 | Event.ENDED = 'ended';
58 | Event.PAUSE = 'pause';
59 | 
60 | // wavesurfer.js
61 | Event.FINISH = 'finish';
62 | Event.SEEK = 'seek';
63 | Event.REDRAW = 'redraw';
64 | Event.AUDIOPROCESS = 'audioprocess';
65 | Event.DEVICE_READY = 'deviceReady';
66 | Event.DEVICE_ERROR = 'deviceError';
67 | 
68 | // videojs-wavesurfer
69 | Event.AUDIO_OUTPUT_READY = 'audioOutputReady';
70 | Event.WAVE_READY = 'waveReady';
71 | Event.PLAYBACK_FINISH = 'playbackFinish';
72 | Event.ABORT = 'abort';
73 | 
74 | // dom
75 | Event.RESIZE = 'resize';
76 | 
77 | // after the freeze, any attempts of altering the class will have no result
78 | Object.freeze(Event);
79 | 
80 | export default Event;
81 | 
82 | 83 |
84 | 85 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /docs/api/file/src/js/middleware.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/js/middleware.js | videojs-wavesurfer 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | 17 | Reference 18 | Source 19 | 20 | 27 |
28 | 29 | 42 | 43 |

src/js/middleware.js

44 |
/**
 45 |  * @file middleware.js
 46 |  * @since 3.0.0
 47 |  */
 48 | 
 49 | const WavesurferMiddleware = {
 50 |     /**
 51 |      * Setup the routing between a specific source and middleware
 52 |      * and eventually set the source on the Tech.
 53 |      *
 54 |      * @param {Tech~SourceObject} [srcObj] - Source object to manipulate.
 55 |      * @param {Function} [next] - The next middleware to run.
 56 |      */
 57 |     setSource(srcObj, next) {
 58 |         // check if this player is using the videojs-wavesurfer plugin
 59 |         if (this.player.usingPlugin('wavesurfer')) {
 60 |             let backend = this.player.wavesurfer().surfer.params.backend;
 61 |             let src = srcObj.src;
 62 |             let peaks = srcObj.peaks;
 63 | 
 64 |             switch (backend) {
 65 |                 case 'WebAudio':
 66 |                     // load url into wavesurfer
 67 |                     this.player.wavesurfer().load(src);
 68 |                     break;
 69 | 
 70 |                 default:
 71 |                     // load source into video.js
 72 |                     next(null, srcObj);
 73 | 
 74 |                     // load media element into wavesurfer
 75 |                     let element = this.player.tech_.el();
 76 |                     if (peaks === undefined) {
 77 |                         // element without peaks
 78 |                         this.player.wavesurfer().load(element);
 79 |                     } else {
 80 |                         // element with peaks
 81 |                         this.player.wavesurfer().load(element, peaks);
 82 |                     }
 83 |                     break;
 84 |             }
 85 |         } else {
 86 |             // ignore middleware (this player isn't using the videojs-wavesurfer
 87 |             // plugin) and load source into video.js
 88 |             next(null, srcObj);
 89 |         }
 90 |     }
 91 | };
 92 | 
 93 | export default WavesurferMiddleware;
 94 | 
95 | 96 |
97 | 98 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /docs/api/file/src/js/utils/log.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/js/utils/log.js | videojs-wavesurfer 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | 17 | Reference 18 | Source 19 | 20 | 27 |
28 | 29 | 42 | 43 |

src/js/utils/log.js

44 |
/**
45 |  * @file log.js
46 |  * @since 2.0.0
47 |  */
48 | 
49 | import videojs from 'video.js';
50 | 
51 | const ERROR = 'error';
52 | const WARN = 'warn';
53 | 
54 | /**
55 |  * Log message (if the debug option is enabled).
56 |  *
57 |  * @private
58 |  * @param {Array} args - The arguments to be passed to the matching console
59 |  *     method.
60 |  * @param {string} logType - The name of the console method to use.
61 |  * @param {boolean} debug - Whether or not the debug option is enabled or not.
62 |  */
63 | const log = function(args, logType, debug)
64 | {
65 |     if (debug === true) {
66 |         if (logType === ERROR) {
67 |             videojs.log.error(args);
68 |         } else if (logType === WARN) {
69 |             videojs.log.warn(args);
70 |         } else {
71 |             videojs.log(args);
72 |         }
73 |     }
74 | };
75 | 
76 | export default log;
77 | 
78 | 79 |
80 | 81 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /docs/api/image/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | document 13 | document 14 | @ratio@ 15 | @ratio@ 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/api/image/esdoc-logo-mini-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collab-project/videojs-wavesurfer/cd3284bf8e2da59039fe59af8ce33003d69bd6ae/docs/api/image/esdoc-logo-mini-black.png -------------------------------------------------------------------------------- /docs/api/image/esdoc-logo-mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collab-project/videojs-wavesurfer/cd3284bf8e2da59039fe59af8ce33003d69bd6ae/docs/api/image/esdoc-logo-mini.png -------------------------------------------------------------------------------- /docs/api/image/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collab-project/videojs-wavesurfer/cd3284bf8e2da59039fe59af8ce33003d69bd6ae/docs/api/image/github.png -------------------------------------------------------------------------------- /docs/api/image/manual-badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | manual 13 | manual 14 | @value@ 15 | @value@ 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/api/image/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collab-project/videojs-wavesurfer/cd3284bf8e2da59039fe59af8ce33003d69bd6ae/docs/api/image/search.png -------------------------------------------------------------------------------- /docs/api/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Home | videojs-wavesurfer 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Home 16 | 17 | Reference 18 | Source 19 | 20 | 27 |
28 | 29 | 42 | 43 |

videojs-wavesurfer

A video.js plugin that adds a navigable waveform 44 | for audio and video files, using the wavesurfer.js 45 | library. Includes support for fullscreen mode and real-time visualization of microphone 46 | input.

47 |

Screenshot

48 |

npm version 49 | npm 50 | License 51 | Build Status 52 | Coverage Status 53 | Size 54 | Stars

55 |

Documentation

The documentation and examples can be found on: https://collab-project.github.io/videojs-wavesurfer

56 |

License

This work is licensed under the MIT License.

57 |
58 |
59 | 60 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /docs/api/lint.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /docs/api/script/inherited-summary.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | function toggle(ev) { 3 | var button = ev.target; 4 | var parent = ev.target.parentElement; 5 | while(parent) { 6 | if (parent.tagName === 'TABLE' && parent.classList.contains('summary')) break; 7 | parent = parent.parentElement; 8 | } 9 | 10 | if (!parent) return; 11 | 12 | var tbody = parent.querySelector('tbody'); 13 | if (button.classList.contains('opened')) { 14 | button.classList.remove('opened'); 15 | button.classList.add('closed'); 16 | tbody.style.display = 'none'; 17 | } else { 18 | button.classList.remove('closed'); 19 | button.classList.add('opened'); 20 | tbody.style.display = 'block'; 21 | } 22 | } 23 | 24 | var buttons = document.querySelectorAll('.inherited-summary thead .toggle'); 25 | for (var i = 0; i < buttons.length; i++) { 26 | buttons[i].addEventListener('click', toggle); 27 | } 28 | })(); 29 | -------------------------------------------------------------------------------- /docs/api/script/inner-link.js: -------------------------------------------------------------------------------- 1 | // inner link(#foo) can not correctly scroll, because page has fixed header, 2 | // so, I manually scroll. 3 | (function(){ 4 | var matched = location.hash.match(/errorLines=([\d,]+)/); 5 | if (matched) return; 6 | 7 | function adjust() { 8 | window.scrollBy(0, -55); 9 | var el = document.querySelector('.inner-link-active'); 10 | if (el) el.classList.remove('inner-link-active'); 11 | 12 | // ``[ ] . ' " @`` are not valid in DOM id. so must escape these. 13 | var id = location.hash.replace(/([\[\].'"@$])/g, '\\$1'); 14 | var el = document.querySelector(id); 15 | if (el) el.classList.add('inner-link-active'); 16 | } 17 | 18 | window.addEventListener('hashchange', adjust); 19 | 20 | if (location.hash) { 21 | setTimeout(adjust, 0); 22 | } 23 | })(); 24 | 25 | (function(){ 26 | var els = document.querySelectorAll('[href^="#"]'); 27 | var href = location.href.replace(/#.*$/, ''); // remove existed hash 28 | for (var i = 0; i < els.length; i++) { 29 | var el = els[i]; 30 | el.href = href + el.getAttribute('href'); // because el.href is absolute path 31 | } 32 | })(); 33 | -------------------------------------------------------------------------------- /docs/api/script/manual.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var matched = location.pathname.match(/\/(manual\/.*\.html)$/); 3 | if (!matched) return; 4 | 5 | var currentName = matched[1]; 6 | var cssClass = '.navigation .manual-toc li[data-link="' + currentName + '"]'; 7 | var styleText = cssClass + '{ display: block; }\n'; 8 | styleText += cssClass + '.indent-h1 a { color: #039BE5 }'; 9 | var style = document.createElement('style'); 10 | style.textContent = styleText; 11 | document.querySelector('head').appendChild(style); 12 | })(); 13 | -------------------------------------------------------------------------------- /docs/api/script/patch-for-local.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | if (location.protocol === 'file:') { 3 | var elms = document.querySelectorAll('a[href="./"]'); 4 | for (var i = 0; i < elms.length; i++) { 5 | elms[i].href = './index.html'; 6 | } 7 | } 8 | })(); 9 | -------------------------------------------------------------------------------- /docs/api/script/pretty-print.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | prettyPrint(); 3 | var lines = document.querySelectorAll('.prettyprint.linenums li[class^="L"]'); 4 | for (var i = 0; i < lines.length; i++) { 5 | lines[i].id = 'lineNumber' + (i + 1); 6 | } 7 | 8 | var matched = location.hash.match(/errorLines=([\d,]+)/); 9 | if (matched) { 10 | var lines = matched[1].split(','); 11 | for (var i = 0; i < lines.length; i++) { 12 | var id = '#lineNumber' + lines[i]; 13 | var el = document.querySelector(id); 14 | el.classList.add('error-line'); 15 | } 16 | return; 17 | } 18 | 19 | if (location.hash) { 20 | // ``[ ] . ' " @`` are not valid in DOM id. so must escape these. 21 | var id = location.hash.replace(/([\[\].'"@$])/g, '\\$1'); 22 | var line = document.querySelector(id); 23 | if (line) line.classList.add('active'); 24 | } 25 | })(); 26 | -------------------------------------------------------------------------------- /docs/api/script/search.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var searchIndex = window.esdocSearchIndex; 3 | var searchBox = document.querySelector('.search-box'); 4 | var input = document.querySelector('.search-input'); 5 | var result = document.querySelector('.search-result'); 6 | var selectedIndex = -1; 7 | var prevText; 8 | 9 | // active search box and focus when mouse enter on search box. 10 | searchBox.addEventListener('mouseenter', function(){ 11 | searchBox.classList.add('active'); 12 | input.focus(); 13 | }); 14 | 15 | // search with text when key is upped. 16 | input.addEventListener('keyup', function(ev){ 17 | var text = ev.target.value.toLowerCase(); 18 | if (!text) { 19 | result.style.display = 'none'; 20 | result.innerHTML = ''; 21 | return; 22 | } 23 | 24 | if (text === prevText) return; 25 | prevText = text; 26 | 27 | var html = {class: [], method: [], member: [], function: [], variable: [], typedef: [], external: [], file: [], test: [], testFile: []}; 28 | var len = searchIndex.length; 29 | var kind; 30 | for (var i = 0; i < len; i++) { 31 | var pair = searchIndex[i]; 32 | if (pair[0].indexOf(text) !== -1) { 33 | kind = pair[3]; 34 | html[kind].push('
  • ' + pair[2] + '
  • '); 35 | } 36 | } 37 | 38 | var innerHTML = ''; 39 | for (kind in html) { 40 | var list = html[kind]; 41 | if (!list.length) continue; 42 | innerHTML += '
  • ' + kind + '
  • \n' + list.join('\n'); 43 | } 44 | result.innerHTML = innerHTML; 45 | if (innerHTML) result.style.display = 'block'; 46 | selectedIndex = -1; 47 | }); 48 | 49 | // down, up and enter key are pressed, select search result. 50 | input.addEventListener('keydown', function(ev){ 51 | if (ev.keyCode === 40) { 52 | // arrow down 53 | var current = result.children[selectedIndex]; 54 | var selected = result.children[selectedIndex + 1]; 55 | if (selected && selected.classList.contains('search-separator')) { 56 | var selected = result.children[selectedIndex + 2]; 57 | selectedIndex++; 58 | } 59 | 60 | if (selected) { 61 | if (current) current.classList.remove('selected'); 62 | selectedIndex++; 63 | selected.classList.add('selected'); 64 | } 65 | } else if (ev.keyCode === 38) { 66 | // arrow up 67 | var current = result.children[selectedIndex]; 68 | var selected = result.children[selectedIndex - 1]; 69 | if (selected && selected.classList.contains('search-separator')) { 70 | var selected = result.children[selectedIndex - 2]; 71 | selectedIndex--; 72 | } 73 | 74 | if (selected) { 75 | if (current) current.classList.remove('selected'); 76 | selectedIndex--; 77 | selected.classList.add('selected'); 78 | } 79 | } else if (ev.keyCode === 13) { 80 | // enter 81 | var current = result.children[selectedIndex]; 82 | if (current) { 83 | var link = current.querySelector('a'); 84 | if (link) location.href = link.href; 85 | } 86 | } else { 87 | return; 88 | } 89 | 90 | ev.preventDefault(); 91 | }); 92 | 93 | // select search result when search result is mouse over. 94 | result.addEventListener('mousemove', function(ev){ 95 | var current = result.children[selectedIndex]; 96 | if (current) current.classList.remove('selected'); 97 | 98 | var li = ev.target; 99 | while (li) { 100 | if (li.nodeName === 'LI') break; 101 | li = li.parentElement; 102 | } 103 | 104 | if (li) { 105 | selectedIndex = Array.prototype.indexOf.call(result.children, li); 106 | li.classList.add('selected'); 107 | } 108 | }); 109 | 110 | // clear search result when body is clicked. 111 | document.body.addEventListener('click', function(ev){ 112 | selectedIndex = -1; 113 | result.style.display = 'none'; 114 | result.innerHTML = ''; 115 | }); 116 | 117 | })(); 118 | -------------------------------------------------------------------------------- /docs/api/script/test-summary.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | function toggle(ev) { 3 | var button = ev.target; 4 | var parent = ev.target.parentElement; 5 | while(parent) { 6 | if (parent.tagName === 'TR' && parent.classList.contains('test-interface')) break; 7 | parent = parent.parentElement; 8 | } 9 | 10 | if (!parent) return; 11 | 12 | var direction; 13 | if (button.classList.contains('opened')) { 14 | button.classList.remove('opened'); 15 | button.classList.add('closed'); 16 | direction = 'closed'; 17 | } else { 18 | button.classList.remove('closed'); 19 | button.classList.add('opened'); 20 | direction = 'opened'; 21 | } 22 | 23 | var targetDepth = parseInt(parent.dataset.testDepth, 10) + 1; 24 | var nextElement = parent.nextElementSibling; 25 | while (nextElement) { 26 | var depth = parseInt(nextElement.dataset.testDepth, 10); 27 | if (depth >= targetDepth) { 28 | if (direction === 'opened') { 29 | if (depth === targetDepth) nextElement.style.display = ''; 30 | } else if (direction === 'closed') { 31 | nextElement.style.display = 'none'; 32 | var innerButton = nextElement.querySelector('.toggle'); 33 | if (innerButton && innerButton.classList.contains('opened')) { 34 | innerButton.classList.remove('opened'); 35 | innerButton.classList.add('closed'); 36 | } 37 | } 38 | } else { 39 | break; 40 | } 41 | nextElement = nextElement.nextElementSibling; 42 | } 43 | } 44 | 45 | var buttons = document.querySelectorAll('.test-summary tr.test-interface .toggle'); 46 | for (var i = 0; i < buttons.length; i++) { 47 | buttons[i].addEventListener('click', toggle); 48 | } 49 | 50 | var topDescribes = document.querySelectorAll('.test-summary tr[data-test-depth="0"]'); 51 | for (var i = 0; i < topDescribes.length; i++) { 52 | topDescribes[i].style.display = ''; 53 | } 54 | })(); 55 | -------------------------------------------------------------------------------- /docs/change-device.md: -------------------------------------------------------------------------------- 1 | # Change device 2 | 3 | ## Output 4 | 5 | ### Example 6 | 7 | - [online demo](https://collab-project.github.io/videojs-wavesurfer/demo/output.html) 8 | - [demo source](https://github.com/collab-project/videojs-wavesurfer/blob/master/examples/output.html) 9 | 10 | 11 | ### Usage 12 | 13 | If your device has multiple audio output devices, use `setAudioOutput(deviceId)` to change 14 | the active audio output device, and listen for the `audioOutputReady` event to be notified 15 | when the new output device is active. 16 | 17 | ```javascript 18 | // change audio output device 19 | player.wavesurfer().setAudioOutput(deviceId); 20 | ``` 21 | 22 | ## Input 23 | 24 | If your device has multiple audio input devices and you want to display 25 | these devices and allow the user to choose one, check out the input example. 26 | 27 | #### Example 28 | 29 | - [online demo](https://collab-project.github.io/videojs-wavesurfer/demo/input.html) 30 | - [demo source](https://github.com/collab-project/videojs-wavesurfer/blob/master/examples/input.html) 31 | -------------------------------------------------------------------------------- /docs/controls.md: -------------------------------------------------------------------------------- 1 | # Customizing controls 2 | 3 | To disable and hide specific controls, use the video.js `controlBar` option: 4 | 5 | ```javascript 6 | controlBar: { 7 | // hide fullscreen control 8 | fullscreenToggle: false 9 | } 10 | ``` 11 | 12 | For more information, see the video.js [component options](https://github.com/videojs/video.js/blob/master/docs/guides/options.md#component-options). 13 | -------------------------------------------------------------------------------- /docs/css/style.css: -------------------------------------------------------------------------------- 1 | .markdown-section code { 2 | color: #2e8a60; 3 | background-color: #e1f4f4; 4 | } 5 | 6 | .content { 7 | padding-top: 0px; 8 | } 9 | 10 | .markdown-section h2 { 11 | font-size: 1.75rem; 12 | margin: 25px 0 .8rem; 13 | } 14 | 15 | .sidebar-toggle { 16 | bottom: unset; 17 | top: 0; 18 | } 19 | 20 | -------------------------------------------------------------------------------- /docs/demo/fluid.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Wavesurfer Plugin for Video.js Fluid Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /docs/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Wavesurfer Plugin for Video.js Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /docs/demo/input.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Audio Input Example - Wavesurfer Plugin for Video.js 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 35 | 36 | 37 | 38 | 39 |
    40 | 41 | 42 |
    43 | 44 | 47 |
    48 |
    49 | 50 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /docs/demo/live.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Wavesurfer Microphone Plugin for Video.js Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 31 | 32 | 33 | 34 | 35 | 36 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /docs/demo/media/example.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collab-project/videojs-wavesurfer/cd3284bf8e2da59039fe59af8ce33003d69bd6ae/docs/demo/media/example.mp4 -------------------------------------------------------------------------------- /docs/demo/media/hal.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collab-project/videojs-wavesurfer/cd3284bf8e2da59039fe59af8ce33003d69bd6ae/docs/demo/media/hal.mp3 -------------------------------------------------------------------------------- /docs/demo/media/hal.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | NOTE Paragraph 4 | 5 | 00:00:01.137 --> 00:00:05.250 6 | This mission is too important for me to allow you to jeopardize it 7 | 8 | 00:00:06.500 --> 00:00:08.002 9 | I don't know what you're talking about, Hal 10 | -------------------------------------------------------------------------------- /docs/demo/media/hal.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collab-project/videojs-wavesurfer/cd3284bf8e2da59039fe59af8ce33003d69bd6ae/docs/demo/media/hal.wav -------------------------------------------------------------------------------- /docs/demo/multi.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | videojs-wavesurfer with multiple players on single page 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 37 | 38 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /docs/demo/output.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Audio Output Example - Wavesurfer Plugin for Video.js 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 33 | 34 | 35 | 36 | 37 |
    38 | 39 | 40 |
    41 | 42 | 45 |
    46 |
    47 | 48 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /docs/demo/peaks.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Peaks example - Wavesurfer Plugin for Video.js 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /docs/demo/plugin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Plugin Example - Wavesurfer Plugin for Video.js 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
    30 | 31 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /docs/demo/react/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Wavesurfer Plugin for Video.js React Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 28 | 29 | 30 |
    31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /docs/demo/react/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React example. 3 | */ 4 | 5 | class VideojsWavesurferPlayer extends React.Component { 6 | componentDidMount() { 7 | // instantiate Video.js 8 | this.player = videojs(this.audioNode, this.props, () => { 9 | // print version information at startup 10 | const version_info = 'Using video.js ' + videojs.VERSION + 11 | ' with videojs-wavesurfer ' + videojs.getPluginVersion('wavesurfer') + 12 | ' and wavesurfer.js ' + WaveSurfer.VERSION; 13 | videojs.log(version_info); 14 | 15 | // load file 16 | this.player.src({src: '../media/hal.wav', type: 'audio/wav'}); 17 | }); 18 | 19 | this.player.on('waveReady', (event) => { 20 | console.log('waveform: ready!'); 21 | }); 22 | 23 | this.player.on('playbackFinish', (event) => { 24 | console.log('playback finished.'); 25 | }); 26 | 27 | // error handling 28 | this.player.on('error', (element, error) => { 29 | console.warn(error); 30 | }); 31 | } 32 | 33 | // destroy player on unmount 34 | componentWillUnmount() { 35 | if (this.player) { 36 | this.player.dispose(); 37 | } 38 | } 39 | 40 | // wrap the player in a div with a `data-vjs-player` attribute 41 | // so videojs won't create additional wrapper in the DOM 42 | // see https://github.com/videojs/video.js/pull/3856 43 | render() { 44 | return ( 45 |
    46 | 47 |
    48 | ) 49 | } 50 | } 51 | 52 | const videoJsOptions = { 53 | controls: true, 54 | autoplay: false, 55 | fluid: false, 56 | width: 600, 57 | height: 300, 58 | bigPlayButton: false, 59 | plugins: { 60 | wavesurfer: { 61 | backend: 'MediaElement', 62 | displayMilliseconds: true, 63 | debug: true, 64 | waveColor: 'white', 65 | progressColor: 'black', 66 | cursorColor: 'black', 67 | hideScrollbar: true 68 | } 69 | } 70 | }; 71 | 72 | ReactDOM.render(, document.getElementById('root')); 73 | -------------------------------------------------------------------------------- /docs/demo/safari-workaround.js: -------------------------------------------------------------------------------- 1 | /* workaround safari issues when using the WebAudio backend in wavesurfer.js */ 2 | 3 | var isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); 4 | 5 | function addStartButton() { 6 | var btn = document.createElement('BUTTON'); 7 | var t = document.createTextNode('Show player'); 8 | btn.onclick = createPlayer; 9 | btn.appendChild(t); 10 | document.body.appendChild(btn); 11 | } 12 | 13 | function updateContext(opts) { 14 | // Safari 11 or newer automatically suspends new AudioContext's that aren't 15 | // created in response to a user-gesture, like a click or tap, so create one 16 | // here (inc. the script processor) 17 | var AudioContext = window.AudioContext || window.webkitAudioContext; 18 | var context = new AudioContext(); 19 | var processor = context.createScriptProcessor(1024, 1, 1); 20 | 21 | opts.plugins.wavesurfer.audioContext = context; 22 | opts.plugins.wavesurfer.audioScriptProcessor = processor; 23 | } 24 | 25 | function enableTextTracks(opts) { 26 | // workaround for video.js issue with Safari text tracks 27 | // see https://github.com/videojs/video.js/issues/7015 28 | opts.html5 = { 29 | nativeTextTracks: false, 30 | vhs: { 31 | overrideNative: true 32 | } 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /docs/demo/texttrack.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Text tracks - Wavesurfer Plugin for Video.js Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /docs/demo/video.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Wavesurfer Plugin Video Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 29 | 30 | 31 | 32 | 33 | 34 |
    35 | 36 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /docs/development.md: -------------------------------------------------------------------------------- 1 | # Development 2 | 3 | Install dependencies using npm: 4 | 5 | ``` 6 | npm install 7 | ``` 8 | 9 | Build development and minified versions of the library and stylesheets: 10 | 11 | ``` 12 | npm run build 13 | ``` 14 | 15 | Generated files are placed in the `dist` directory. 16 | 17 | During development: 18 | 19 | ``` 20 | npm run start 21 | ``` 22 | 23 | This will watch the source directory and rebuild when any changes 24 | are detected. It will also serve the files on http://127.0.0.1:8080. 25 | 26 | Generate the API documentation (placed in the `docs` directory): 27 | 28 | ``` 29 | npm run docs 30 | ``` 31 | 32 | All commands for development are listed in the `package.json` file and 33 | are run using: 34 | 35 | ``` 36 | npm run 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/donate.md: -------------------------------------------------------------------------------- 1 | # Donate 2 | 3 | Please consider donating if you like this project. Bitcoin is accepted 4 | and can be sent to `3PmXCqUggtq7KUWPbpN8WhMnb1Mfb1jbq8`. 5 | -------------------------------------------------------------------------------- /docs/events.md: -------------------------------------------------------------------------------- 1 | # Events 2 | 3 | The events for this plugin are available on the video.js player instance. 4 | 5 | For example: 6 | 7 | ```javascript 8 | player.on('waveReady', function(event) { 9 | console.log('waveform is ready!'); 10 | }); 11 | ``` 12 | 13 | | Event | Description | 14 | | ----- | ----------- | 15 | | `waveReady` | Audio is loaded, decoded and the waveform is drawn. | 16 | | `playbackFinish` | Audio playback finished. | 17 | | `audioOutputReady` | Audio output was changed and is now active. | 18 | | `abort` | Audio loading process was interrupted and cancelled. | 19 | | `error` | Error occurred. | 20 | -------------------------------------------------------------------------------- /docs/examples.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | ## Online demos 4 | 5 | View the examples online: 6 | 7 | | Example | Description | Source | 8 | | --- | --- | --- | 9 | | [Audio](https://collab-project.github.io/videojs-wavesurfer/demo/index.html) | Basic audio example | [example source](https://github.com/collab-project/videojs-wavesurfer/blob/master/examples/index.html) | 10 | | [Video](https://collab-project.github.io/videojs-wavesurfer/demo/video.html) | Basic video example | [example source](https://github.com/collab-project/videojs-wavesurfer/blob/master/examples/video.html) | 11 | | [Responsive](https://collab-project.github.io/videojs-wavesurfer/demo/fluid.html) | Enable [responsive layout](responsive.md) | [example source](https://github.com/collab-project/videojs-wavesurfer/blob/master/examples/fluid.html) | 12 | | [Text tracks](https://collab-project.github.io/videojs-wavesurfer/demo/texttrack.html) | Display [text tracks](text-tracks.md) | [example source](https://github.com/collab-project/videojs-wavesurfer/blob/master/examples/texttrack.html) | 13 | | [Microphone](https://collab-project.github.io/videojs-wavesurfer/demo/live.html) | Real-time waveform rendering of [microphone](microphone.md) | [example source](https://github.com/collab-project/videojs-wavesurfer/blob/master/examples/live.html) | 14 | | [Peaks](https://collab-project.github.io/videojs-wavesurfer/demo/peaks.html) | Use JSON [peaks data](peaks.md) to render waveform | [example source](https://github.com/collab-project/videojs-wavesurfer/blob/master/examples/peaks.html) | 15 | | [Output device](https://collab-project.github.io/videojs-wavesurfer/demo/output.html) | Change audio [output device](change-device.md) | [example source](https://github.com/collab-project/videojs-wavesurfer/blob/master/examples/output.html) | 16 | | [Input device](https://collab-project.github.io/videojs-wavesurfer/demo/input.html) | Change audio [input device](change-device.md) | [example source](https://github.com/collab-project/videojs-wavesurfer/blob/master/examples/input.html) | 17 | | [Plugin](https://collab-project.github.io/videojs-wavesurfer/demo/plugin.html) | Enable additional [wavesurfer.js plugins](plugins.md) | [example source](https://github.com/collab-project/videojs-wavesurfer/blob/master/examples/plugin.html) | 18 | | [React](https://collab-project.github.io/videojs-wavesurfer/demo/react/index.html) | Basic [React](react.md) example | [example source](https://github.com/collab-project/videojs-wavesurfer/blob/master/examples/react/index.html) | 19 | | [Multi](https://collab-project.github.io/videojs-wavesurfer/demo/multi.html) | Using multiple players on single page | [example source](https://github.com/collab-project/videojs-wavesurfer/blob/master/examples/multi.html) | 20 | 21 | ## Local setup 22 | 23 | To try out the examples locally either: 24 | 25 | - download the [zipfile](https://github.com/collab-project/videojs-wavesurfer/archive/master.zip) and unpack it 26 | - or checkout the repository with Git: 27 | ```console 28 | git clone https://github.com/collab-project/videojs-wavesurfer.git 29 | ``` 30 | 31 | 1. Install the dependencies: 32 | 33 | ```console 34 | cd /path/to/videojs-wavesurfer 35 | npm install 36 | ``` 37 | 38 | 2. Build the library and assets once: 39 | 40 | ```console 41 | npm run build 42 | ``` 43 | 44 | 3. And start the local examples webserver: 45 | 46 | ```console 47 | npm run start 48 | ``` 49 | 50 | Open http://localhost:8080/examples/index.html in a browser. 51 | -------------------------------------------------------------------------------- /docs/img/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collab-project/videojs-wavesurfer/cd3284bf8e2da59039fe59af8ce33003d69bd6ae/docs/img/screenshot.png -------------------------------------------------------------------------------- /docs/img/text-tracks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collab-project/videojs-wavesurfer/cd3284bf8e2da59039fe59af8ce33003d69bd6ae/docs/img/text-tracks.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | videojs-wavesurfer 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
    14 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /docs/install.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ## NPM 4 | 5 | You can use [npm](https://www.npmjs.org) to install the plugin: 6 | 7 | ``` 8 | npm install videojs-wavesurfer 9 | ``` 10 | 11 | Or [download it from Github](https://github.com/collab-project/videojs-wavesurfer/releases). 12 | 13 | ## CDN 14 | 15 | Using the [unpkg.com](https://unpkg.com) CDN: 16 | 17 | ```html 18 | 19 | 20 | 21 | 22 | 23 | ``` 24 | 25 | Alternative CDN locations: 26 | 27 | - https://cdn.jsdelivr.net/npm/videojs-wavesurfer/ 28 | - https://cdnjs.com/libraries/videojs-wavesurfer 29 | - https://www.bootcdn.cn/videojs-wavesurfer/ 30 | 31 | ## Upgrade 32 | 33 | Since v3.0 this plugin is compatible with: 34 | 35 | - video.js 7.0.5 or newer 36 | - wavesurfer.js 6.3.0 or newer. wavesurfer.js 7.x is not supported. 37 | 38 | If you want to use this plugin with an older video.js or wavesurfer.js version, 39 | check the [archived releases](https://github.com/collab-project/videojs-wavesurfer/releases) 40 | for an older release of this plugin. 41 | 42 | Also take a look at the [changelog](changelog.md) when upgrading from a previous 43 | version of videojs-wavesurfer. 44 | -------------------------------------------------------------------------------- /docs/methods.md: -------------------------------------------------------------------------------- 1 | # Methods 2 | 3 | Methods for this plugin are documented below. These are available on the 4 | `wavesurfer` plugin instance of the video.js player. 5 | 6 | For example: 7 | 8 | ```javascript 9 | player.on('ready', function() { 10 | player.wavesurfer().destroy(); 11 | }); 12 | ``` 13 | 14 | # Plugin 15 | 16 | | Method | Description | 17 | | ------ | ----------- | 18 | | `destroy` | Destroys the wavesurfer instance and children (including the video.js player). | 19 | | `load(url)` | Load the clip at `url`. Also supports loading [File](https://developer.mozilla.org/nl/docs/Web/API/File) or [Blob](https://developer.mozilla.org/nl/docs/Web/API/Blob) objects. | 20 | | `setVolume(level)` | Set the volume level (value between 0.0 and 1.0). | 21 | | `play` | Start playback. | 22 | | `pause` | Pause playback. | 23 | | `getDuration` | Get the length of the stream in seconds. Returns 0 if no stream is available (yet). | 24 | | `getCurrentTime` | Get the current time (in seconds) of the stream during playback. Returns 0 if no stream is available (yet). | 25 | | `setFormatTime(impl)` | Change the current `formatTime` implementation with a custom implementation. | 26 | | `exportImage(format, quality, type)` | Save waveform image as Blob or data URI. Default `format` is `'image/png'`, `quality` is 1 and `type` is `blob`. | 27 | | `setAudioOutput(deviceId)` | Change the audio output device using its [deviceId](https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo/deviceId). | 28 | 29 | ## wavesurfer.js 30 | 31 | You can access the wavesurfer instance, for example to call the 32 | wavesurfer.js `seekTo` method, by using the `surfer` property of the 33 | `wavesurfer` plugin instance: 34 | 35 | ```javascript 36 | player.wavesurfer().surfer.seekTo(1); 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/microphone.md: -------------------------------------------------------------------------------- 1 | # Microphone plugin 2 | 3 | It's also possible to use a microphone for real-time rendering of the audio waveform. This 4 | uses the [microphone plugin](https://wavesurfer-js.org/plugins/microphone.html) that comes 5 | with wavesurfer.js. 6 | 7 | ## Example 8 | 9 | - [online demo](https://collab-project.github.io/videojs-wavesurfer/demo/live.html) 10 | - [demo source](https://github.com/collab-project/videojs-wavesurfer/blob/master/examples/live.html) 11 | 12 | ## Usage 13 | 14 | Include the additional `wavesurfer.microphone.js` plugin on your page. 15 | 16 | ```html 17 | 18 | ``` 19 | 20 | Add an `audio` element: 21 | 22 | ```html 23 | 24 | ``` 25 | 26 | Hide irrelevant controls, specify the `WebAudio` backend and enable the microphone plugin: 27 | 28 | ```javascript 29 | let options = { 30 | controls: true, 31 | width: 600, 32 | height: 300, 33 | // hide irrelevant controls 34 | bigPlayButton: false, 35 | controlBar: { 36 | currentTimeDisplay: false, 37 | timeDivider: false, 38 | durationDisplay: false, 39 | remainingTimeDisplay: false, 40 | volumePanel: false, 41 | progressControl: false 42 | }, 43 | plugins: { 44 | // enable videojs-wavesurfer plugin 45 | wavesurfer: { 46 | debug: true, 47 | backend: 'WebAudio', 48 | waveColor: 'black', 49 | cursorWidth: 0, 50 | interact: false, 51 | hideScrollbar: true, 52 | plugins: [ 53 | // enable microphone plugin 54 | WaveSurfer.microphone.create({ 55 | bufferSize: 4096, 56 | numberOfInputChannels: 1, 57 | numberOfOutputChannels: 1, 58 | constraints: { 59 | video: false, 60 | audio: true 61 | } 62 | }) 63 | ] 64 | } 65 | } 66 | }; 67 | 68 | let player = videojs('myLiveAudio', options); 69 | ``` 70 | 71 | The wavesurfer.js microphone plugin has additional configuration 72 | [options](https://wavesurfer-js.org/plugins/microphone.html). 73 | -------------------------------------------------------------------------------- /docs/options.md: -------------------------------------------------------------------------------- 1 | # Options 2 | 3 | Configure the player with: 4 | 5 | - [video.js options](https://github.com/videojs/video.js/blob/master/docs/guides/options.md) 6 | - [wavesurfer.js options](https://wavesurfer-js.org/docs/options.html) 7 | 8 | Additional options for this plugin are: 9 | 10 | | option | type | default | description | 11 | | ------ | ---- | ------- | ----------- | 12 | | `debug` | boolean | `false` | Display internal log messages using the `videojs.log` method. | 13 | | `displayMilliseconds` | boolean | `true` | Indicates if milliseconds should be included in time displays, e.g. `00:00:000` vs `00:00`. | 14 | | `formatTime` | function | builtin `formatTime` | Use a custom time format function. For example: ```(seconds, guide) => `test:${seconds}:${guide} ``` | 15 | -------------------------------------------------------------------------------- /docs/peaks.md: -------------------------------------------------------------------------------- 1 | # Using peaks for large audio files 2 | 3 | When you're dealing with long audio files, it's sometimes useful to generate the waveform data, 4 | called peaks, on the server. This allows wavesurfer.js to load the peaks JSON data and create the 5 | waveform from that pre-rendered peak data. This JSON file can be generated using the 6 | [bbc/audiowaveform](https://github.com/bbc/audiowaveform) utility. 7 | 8 | For more information, see the wavesurfer.js [FAQ](https://wavesurfer-js.org/faq/). 9 | 10 | ## Example 11 | 12 | - [online demo](https://collab-project.github.io/videojs-wavesurfer/demo/peaks.html) 13 | - [demo source](https://github.com/collab-project/videojs-wavesurfer/blob/master/examples/peaks.html) 14 | 15 | ## Usage 16 | 17 | Load peaks data: 18 | 19 | ```javascript 20 | // load file with peaks 21 | player.src({ 22 | src: 'media/hal.wav', 23 | type: 'audio/wav', 24 | // Use peaks from JSON file. See https://wavesurfer-js.org/faq/ 25 | // for instructions on how to generate peaks 26 | peaks: 'media/hal-peaks.json' 27 | }); 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/plugins.md: -------------------------------------------------------------------------------- 1 | # More features using other plugins 2 | 3 | ## video.js 4 | 5 | The Video.js community created 6 | [lots of plugins](https://github.com/videojs/video.js/wiki/Plugins) 7 | that can be used to enhance the player's functionality. 8 | 9 | Plugins actually tested with videojs-wavesurfer include: 10 | 11 | - [videojs-record](https://github.com/collab-project/videojs-record) - Adds 12 | support for recording audio/video/image files. 13 | 14 | ## wavesurfer.js 15 | 16 | The plugin example extends videojs-wavesurfer with the wavesurfer.js 17 | [timeline](https://wavesurfer-js.org/example/timeline/index.html) and 18 | [regions](https://wavesurfer-js.org/example/regions/index.html) plugins: 19 | 20 | - [online demo](https://collab-project.github.io/videojs-wavesurfer/demo/plugin.html) 21 | - [demo source](https://github.com/collab-project/videojs-wavesurfer/blob/master/examples/plugin.html) 22 | -------------------------------------------------------------------------------- /docs/react.md: -------------------------------------------------------------------------------- 1 | # React 2 | 3 | This guide shows you how to get started with [React](https://reactjs.org) and 4 | videojs-wavesurfer using [create-react-app](https://github.com/facebook/create-react-app). 5 | 6 | For more information, check the video.js [documentation](https://videojs.com/guides/react/) 7 | for React. 8 | 9 | ## Installation 10 | 11 | Create an example React application called `videojs-wavesurfer-react`: 12 | 13 | ```console 14 | npx create-react-app videojs-wavesurfer-react 15 | ``` 16 | 17 | Install videojs-wavesurfer: 18 | 19 | ```console 20 | cd videojs-wavesurfer-react 21 | npm install --save videojs-wavesurfer 22 | ``` 23 | 24 | ## Application 25 | 26 | Replace content of `src/App.js` with: 27 | 28 | ```javascript 29 | import './App.css'; 30 | import React from 'react'; 31 | 32 | import VideoJSComponent from './VideoJSComponent'; 33 | 34 | function App() { 35 | const playerRef = React.useRef(null); 36 | const videoJsOptions = { 37 | controls: true, 38 | bigPlayButton: false, 39 | inactivityTimeout: 0, 40 | width: 600, 41 | height: 300, 42 | fluid: false, 43 | plugins: { 44 | wavesurfer: { 45 | backend: 'MediaElement', 46 | displayMilliseconds: true, 47 | debug: true, 48 | waveColor: '#163b5b', 49 | progressColor: 'black', 50 | cursorColor: 'black', 51 | hideScrollbar: true 52 | } 53 | } 54 | }; 55 | 56 | const handlePlayerReady = (player) => { 57 | playerRef.current = player; 58 | 59 | // handle player events 60 | player.on('waveReady', (event) => { 61 | console.log('waveform: ready!'); 62 | }); 63 | 64 | player.on('playbackFinish', (event) => { 65 | console.log('playback finished.'); 66 | }); 67 | 68 | // error handling 69 | player.on('error', (element, error) => { 70 | console.error(error); 71 | }); 72 | }; 73 | 74 | return ( 75 |
    76 | 77 |
    78 | ); 79 | } 80 | 81 | export default App; 82 | ``` 83 | 84 | Add the following to `src/App.css`: 85 | 86 | ```css 87 | /* change player background color */ 88 | .App video-js { 89 | background-color: #ACB2F2; 90 | } 91 | ``` 92 | 93 | Create `src/VideoJSComponent.js`: 94 | 95 | ```javascript 96 | import React from 'react'; 97 | import videojs from 'video.js'; 98 | import 'video.js/dist/video-js.css'; 99 | import WaveSurfer from 'wavesurfer.js'; 100 | 101 | /* 102 | // the following imports are only needed when you're using 103 | // the microphone plugin 104 | import 'webrtc-adapter'; 105 | 106 | import MicrophonePlugin from 'wavesurfer.js/dist/plugin/wavesurfer.microphone.js'; 107 | WaveSurfer.microphone = MicrophonePlugin; 108 | */ 109 | 110 | // register videojs-wavesurfer plugin with this import 111 | import 'videojs-wavesurfer/dist/css/videojs.wavesurfer.css'; 112 | import Wavesurfer from 'videojs-wavesurfer/dist/videojs.wavesurfer.js'; 113 | 114 | export const VideoJSComponent = (props) => { 115 | const videoRef = React.useRef(null); 116 | const playerRef = React.useRef(null); 117 | const {options, onReady} = props; 118 | 119 | React.useEffect(() => { 120 | 121 | // Make sure Video.js player is only initialized once 122 | if (!playerRef.current) { 123 | // The Video.js player needs to be _inside_ the component el for React 18 Strict Mode. 124 | const videoElement = document.createElement("video-js"); 125 | 126 | videoElement.className = 'video-js vjs-default-skin'; 127 | videoRef.current.appendChild(videoElement); 128 | 129 | const player = playerRef.current = videojs(videoElement, options, () => { 130 | // print version information at startup 131 | const version_info = 'Using video.js ' + videojs.VERSION + 132 | ' with videojs-wavesurfer ' + videojs.getPluginVersion('wavesurfer') + 133 | ', wavesurfer.js ' + WaveSurfer.VERSION + ' and React ' + React.version; 134 | videojs.log(version_info); 135 | 136 | onReady && onReady(player); 137 | 138 | // load track 139 | player.src({src: 'hal.wav', type: 'audio/wav'}); 140 | }); 141 | 142 | // You could update an existing player in the `else` block here 143 | // on prop change, for example: 144 | } else { 145 | //const player = playerRef.current; 146 | //player.src({src: 'hal.wav', type: 'audio/wav'}); 147 | } 148 | }, [options, videoRef]); 149 | 150 | // Dispose the Video.js player when the functional component unmounts 151 | React.useEffect(() => { 152 | const player = playerRef.current; 153 | 154 | return () => { 155 | if (player && !player.isDisposed()) { 156 | player.dispose(); 157 | playerRef.current = null; 158 | } 159 | }; 160 | }, [playerRef]); 161 | 162 | return ( 163 |
    164 |
    165 |
    166 | ); 167 | } 168 | 169 | export default VideoJSComponent; 170 | ``` 171 | 172 | ## Media 173 | 174 | Download the [example audio file](https://github.com/collab-project/videojs-wavesurfer/raw/master/examples/media/hal.wav) 175 | and place it in the `public` directory. 176 | 177 | ## Run 178 | 179 | Start the development server: 180 | 181 | ```console 182 | npm start 183 | ``` 184 | 185 | And open http://localhost:3000 in a browser. 186 | -------------------------------------------------------------------------------- /docs/responsive.md: -------------------------------------------------------------------------------- 1 | # Responsive layout 2 | 3 | The `fluid` option for video.js will resize the player according to the size 4 | of the window. 5 | 6 | ## Example 7 | 8 | - [online demo](https://collab-project.github.io/videojs-wavesurfer/demo/fluid.html) 9 | - [demo source](https://github.com/collab-project/videojs-wavesurfer/blob/master/examples/fluid.html) 10 | 11 | ## Usage 12 | 13 | Configure the player and enable the video.js `fluid` option: 14 | 15 | ```javascript 16 | fluid: true 17 | ``` 18 | 19 | For more information, see the video.js [layout documentation](https://github.com/videojs/video.js/blob/master/docs/guides/layout.md). 20 | -------------------------------------------------------------------------------- /docs/text-tracks.md: -------------------------------------------------------------------------------- 1 | # Text Tracks 2 | 3 | Text tracks (or captions/subtitles) are a feature of HTML5 for displaying 4 | time-triggered text to the user. Video.js offers a cross-browser implementation 5 | of text tracks. 6 | 7 | For more information, check the video.js 8 | [text tracks documentation](https://github.com/videojs/video.js/blob/master/docs/guides/text-tracks.md). 9 | 10 | ## Example 11 | 12 | - [online demo](https://collab-project.github.io/videojs-wavesurfer/demo/texttrack.html) 13 | - [demo source](https://github.com/collab-project/videojs-wavesurfer/blob/master/examples/texttrack.html) 14 | 15 | ## Usage 16 | 17 | Create an array for the text track(s) you want to use: 18 | 19 | ```javascript 20 | let textTracks = [ 21 | { 22 | kind: 'captions', 23 | srclang: 'en', 24 | label: 'English', 25 | src: 'media/hal.vtt', 26 | mode: 'showing', 27 | default: true 28 | } 29 | ]; 30 | ``` 31 | 32 | And pass it to the video.js `tracks` option: 33 | 34 | ```javascript 35 | const options = { 36 | tracks: textTracks, 37 | plugins: { 38 | // etc... 39 | } 40 | }; 41 | ``` 42 | 43 | ![Text tracks screenshot](img/text-tracks.png?raw=true "Text tracks screenshot") 44 | -------------------------------------------------------------------------------- /docs/tools/update-videojs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Update video.js version in demo files. 3 | * 4 | * @since 3.0.0 5 | */ 6 | 7 | const replace = require('replace-in-file'); 8 | const path = require('path'); 9 | 10 | const OLD_VERSION = "video.js@7.21.1"; 11 | const NEW_VERSION = "video.js@8.10.0"; 12 | 13 | const options = { 14 | files: path.resolve(__dirname, '..', 'demo') + '/**/*.html', 15 | from: new RegExp(OLD_VERSION, 'g'), 16 | to: NEW_VERSION, 17 | dry: false 18 | }; 19 | 20 | console.log(); 21 | console.log(`Updating from ${OLD_VERSION} to ${NEW_VERSION} in ${path.relative('.', options.files)}`); 22 | console.log(); 23 | 24 | replace(options) 25 | .then(results => { 26 | let changes = false; 27 | results.forEach(item => { 28 | if (item.hasChanged) { 29 | console.log("Updated " + path.relative('.', item.file)); 30 | changes = true; 31 | } 32 | }); 33 | if (!changes) { 34 | console.log("No files updated."); 35 | } 36 | }) 37 | .catch(error => { 38 | console.error('Error occurred:', error); 39 | }); 40 | -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | The plugin depends on the video.js and wavesurfer.js 6.x libraries: 4 | 5 | ```javascript 6 | // style 7 | import 'video.js/dist/video-js.min.css'; 8 | import 'videojs-wavesurfer/dist/css/videojs.wavesurfer.css'; 9 | 10 | // libraries 11 | import videojs from 'video.js'; 12 | import WaveSurfer from 'wavesurfer.js'; 13 | ``` 14 | 15 | The videojs-wavesurfer plugin automatically registers itself after importing it: 16 | 17 | ```javascript 18 | import Wavesurfer from 'videojs-wavesurfer/dist/videojs.wavesurfer.js'; 19 | ``` 20 | 21 | Add an `audio` element: 22 | 23 | ```html 24 | 25 | ``` 26 | 27 | Or `video` element: 28 | 29 | ```html 30 | 31 | ``` 32 | 33 | Define the player configuration and enable the videojs-wavesurfer plugin by 34 | adding a `wavesurfer` entry: 35 | 36 | ```javascript 37 | // configuration for video.js 38 | let options = { 39 | controls: true, 40 | bigPlayButton: false, 41 | autoplay: false, 42 | loop: false, 43 | fluid: false, 44 | width: 600, 45 | height: 300, 46 | plugins: { 47 | // enable videojs-wavesurfer plugin 48 | wavesurfer: { 49 | // configure videojs-wavesurfer 50 | backend: 'MediaElement', 51 | displayMilliseconds: true, 52 | debug: true, 53 | waveColor: 'grey', 54 | progressColor: 'black', 55 | cursorColor: 'black', 56 | hideScrollbar: true 57 | } 58 | } 59 | }; 60 | ``` 61 | 62 | Finally, create the player and load a file: 63 | 64 | ```javascript 65 | let player = videojs('myClip', options, function() { 66 | // print version information at startup 67 | let msg = 'Using video.js ' + videojs.VERSION + 68 | ' with videojs-wavesurfer ' + 69 | videojs.getPluginVersion('wavesurfer') + 70 | ' and wavesurfer.js ' + WaveSurfer.VERSION; 71 | videojs.log(msg); 72 | 73 | // load wav file from url 74 | player.src({src: 'media/hal.wav', type: 'audio/wav'}); 75 | }); 76 | ``` 77 | 78 | Check the [options](options.md), [methods](methods.md) and [events](events.md) documentation 79 | for more information. 80 | -------------------------------------------------------------------------------- /docs/vue.md: -------------------------------------------------------------------------------- 1 | # Vue 2 | 3 | This page shows how to get started with [Vue.js](https://vuejs.org/) and videojs-wavesurfer. 4 | 5 | For more information, check the video.js [documentation](https://videojs.com/guides/vue/) 6 | for Vue.js. 7 | 8 | ## Installation 9 | 10 | Install the [Vue.js CLI](https://cli.vuejs.org/guide/) globally: 11 | 12 | ```console 13 | npm install -g @vue/cli 14 | ``` 15 | 16 | Create a new application, e.g. `videojs-wavesurfer-app`: 17 | 18 | ```console 19 | vue create --default --packageManager npm videojs-wavesurfer-app 20 | ``` 21 | 22 | Install videojs-wavesurfer: 23 | 24 | ```console 25 | cd videojs-wavesurfer-app 26 | npm install --save videojs-wavesurfer 27 | ``` 28 | 29 | ## Application 30 | 31 | Create `src/components/VideoJSWavesurfer.vue`: 32 | 33 | ```html 34 | 37 | 38 | 108 | ``` 109 | 110 | Change `src/App.vue` to: 111 | 112 | ```html 113 | 118 | 119 | 129 | 130 | 136 | ``` 137 | 138 | ## Media 139 | 140 | Download the [example audio file](https://github.com/collab-project/videojs-wavesurfer/raw/master/examples/media/hal.wav) 141 | and place it in the `public` directory. 142 | 143 | ## Run 144 | 145 | Start the Vue.js development server: 146 | 147 | ```console 148 | npm run serve 149 | ``` 150 | 151 | And open http://localhost:8080 in a browser. 152 | -------------------------------------------------------------------------------- /docs/webpack.md: -------------------------------------------------------------------------------- 1 | # Webpack 2 | 3 | This document describes how to setup [Webpack](https://webpack.js.org/) with videojs-wavesurfer. 4 | 5 | ## Installation 6 | 7 | Create a project directory: 8 | 9 | ```console 10 | mkdir videojs-wavesurfer-webpack 11 | cd videojs-wavesurfer-webpack 12 | ``` 13 | 14 | Install Webpack: 15 | 16 | ```console 17 | npm install -D webpack webpack-dev-server webpack-cli css-loader style-loader 18 | ``` 19 | 20 | Install videojs-wavesurfer: 21 | 22 | ```console 23 | npm install --save videojs-wavesurfer 24 | ``` 25 | 26 | ## Configuration 27 | 28 | Create the Webpack config file called `webpack.config.js`: 29 | 30 | ```javascript 31 | const path = require('path'); 32 | const basePath = path.resolve(__dirname); 33 | 34 | module.exports = { 35 | mode: 'development', 36 | context: path.join(basePath, 'src'), 37 | entry: { 38 | app: './app.js' 39 | }, 40 | output: { 41 | path: path.join(basePath, 'dist'), 42 | filename: '[name].bundle.js', 43 | publicPath: '/dist' 44 | }, 45 | devServer: { 46 | static: { 47 | directory: basePath, 48 | serveIndex: true, 49 | watch: true, 50 | } 51 | }, 52 | module: { 53 | rules: [{ 54 | test: /\.css$/, 55 | use: ['style-loader', 'css-loader'], 56 | }] 57 | } 58 | }; 59 | ``` 60 | 61 | ## Application 62 | 63 | Create `src/index.html` containing: 64 | 65 | ```html 66 | 67 | 68 | 69 | 70 | Webpack videojs-wavesurfer example 71 | 72 | 73 | 74 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | ``` 88 | 89 | And create `src/app.js`: 90 | 91 | ```javascript 92 | import video_css from 'video.js/dist/video-js.min.css'; 93 | import videojs from 'video.js'; 94 | import WaveSurfer from 'wavesurfer.js'; 95 | 96 | /* 97 | // the following imports are only required when using the 98 | // videojs-wavesurfer 'live' mode with the microphone plugin 99 | // make sure to import them before importing videojs-wavesurfer 100 | import 'webrtc-adapter'; 101 | import MicrophonePlugin from 'wavesurfer.js/dist/plugin/wavesurfer.microphone.js'; 102 | WaveSurfer.microphone = MicrophonePlugin; 103 | */ 104 | 105 | // register videojs-wavesurfer plugin 106 | import wavesurfer_css from 'videojs-wavesurfer/dist/css/videojs.wavesurfer.css'; 107 | import Wavesurfer from 'videojs-wavesurfer/dist/videojs.wavesurfer.js'; 108 | 109 | let player; 110 | const elementId = 'myAudio'; 111 | let src = {src: '/hal.wav', type: 'audio/wav'}; 112 | 113 | const playerOptions = { 114 | controls: true, 115 | bigPlayButton: false, 116 | autoplay: false, 117 | fluid: false, 118 | loop: false, 119 | width: 600, 120 | height: 300, 121 | plugins: { 122 | // configure videojs-wavesurfer plugin 123 | wavesurfer: { 124 | backend: 'MediaElement', 125 | displayMilliseconds: true, 126 | debug: true, 127 | waveColor: '#4A4A22', 128 | progressColor: 'black', 129 | cursorColor: 'black', 130 | hideScrollbar: true 131 | } 132 | } 133 | }; 134 | 135 | // wait till DOM is ready 136 | document.addEventListener('DOMContentLoaded', function() { 137 | // create player 138 | player = videojs(elementId, playerOptions, function() { 139 | console.log('player ready! id:', elementId); 140 | 141 | // print version information at startup 142 | const msg = 'Using video.js ' + videojs.VERSION + 143 | ' with videojs-wavesurfer ' + videojs.getPluginVersion('wavesurfer') + 144 | ' and wavesurfer.js ' + WaveSurfer.VERSION; 145 | videojs.log(msg); 146 | 147 | // load file 148 | player.src(src); 149 | }); 150 | 151 | player.on('waveReady', function(event) { 152 | console.log('waveform is ready!'); 153 | }); 154 | 155 | player.on('playbackFinish', function(event) { 156 | console.log('playback finished.'); 157 | }); 158 | 159 | // error handling 160 | player.on('error', function(element, error) { 161 | console.error('ERROR:', error); 162 | }); 163 | }); 164 | ``` 165 | 166 | ## Media 167 | 168 | Download the [example audio file](https://github.com/collab-project/videojs-wavesurfer/raw/master/examples/media/hal.wav) 169 | and place it in the root directory. 170 | 171 | ## Run 172 | 173 | Start the Webpack development server: 174 | 175 | ``` 176 | ./node_modules/.bin/webpack serve --config=webpack.config.js 177 | ``` 178 | 179 | And open http://localhost:8080/src/index.html in a browser. 180 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | const jsdoc = require('eslint-plugin-jsdoc'); 2 | 3 | module.exports = [ 4 | { 5 | // configuration included in plugin 6 | //jsdoc.configs['flat/recommended'], 7 | "languageOptions": { 8 | "ecmaVersion": 6, 9 | "sourceType": "module" 10 | }, 11 | "plugins": {jsdoc: jsdoc}, 12 | "rules": { 13 | "no-trailing-spaces": 2, 14 | "no-mixed-spaces-and-tabs": 2, 15 | "no-multi-spaces": 2, 16 | "no-tabs": 2, 17 | "no-extra-bind": 2, 18 | "eqeqeq": 2, 19 | "indent": [ 20 | 2, 21 | 4, 22 | { 23 | "SwitchCase": 1 24 | } 25 | ], 26 | "semi-spacing": [ 27 | 2, 28 | { 29 | "before": false, 30 | "after": true 31 | } 32 | ], 33 | "semi": [ 34 | 2, 35 | "always" 36 | ], 37 | "comma-dangle": ["error", "never"], 38 | "keyword-spacing": ["error", { "before": true }], 39 | "space-infix-ops": 2, 40 | "prefer-arrow-callback": 2, 41 | "jsdoc/require-return": "off", 42 | "jsdoc/require-returntype": "off", 43 | "no-console": 2, 44 | "no-dupe-args": 2, 45 | "no-dupe-keys": 2, 46 | "no-extra-semi": 2, 47 | "no-fallthrough": 2, 48 | "use-isnan": 2, 49 | "valid-typeof": 2, 50 | "no-var": 2 51 | }, 52 | }, 53 | { 54 | files: ["test-*.js", "*.spec.js", "test-helpers.js"], 55 | rules: { 56 | "valid-jsdoc": "off", 57 | "require-jsdoc": "off" 58 | } 59 | }, 60 | { 61 | files: ["build-config/**/*.js"], 62 | rules: { 63 | "no-console": "off" 64 | } 65 | } 66 | ]; 67 | -------------------------------------------------------------------------------- /examples/fluid.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Fluid Example - Wavesurfer Plugin for Video.js 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Wavesurfer Plugin for Video.js Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /examples/live.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Microphone Example - Wavesurfer Plugin for Video.js 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 31 | 32 | 33 | 34 | 35 | 36 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /examples/media/example.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collab-project/videojs-wavesurfer/cd3284bf8e2da59039fe59af8ce33003d69bd6ae/examples/media/example.mp4 -------------------------------------------------------------------------------- /examples/media/hal.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | NOTE Paragraph 4 | 5 | 00:00:01.123 --> 00:00:05.250 6 | This mission is too important for me to allow you to jeopardize it 7 | 8 | 00:00:06.485 --> 00:00:08.002 9 | I don't know what you're talking about, Hal 10 | -------------------------------------------------------------------------------- /examples/media/hal.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collab-project/videojs-wavesurfer/cd3284bf8e2da59039fe59af8ce33003d69bd6ae/examples/media/hal.wav -------------------------------------------------------------------------------- /examples/multi.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | videojs-wavesurfer with multiple players on single page 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 37 | 38 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /examples/output.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Audio Output Example - Wavesurfer Plugin for Video.js 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 33 | 34 | 35 | 36 | 37 |
    38 | 39 | 40 |
    41 | 42 | 45 |
    46 |
    47 | 48 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /examples/peaks.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Peaks Example - Wavesurfer Plugin for Video.js 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /examples/plugin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Plugin Example - Wavesurfer Plugin for Video.js 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
    35 | 36 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /examples/react/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Wavesurfer Plugin for Video.js React Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 26 | 27 | 28 | 29 |
    30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/react/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React example. 3 | */ 4 | 5 | class VideojsWavesurferPlayer extends React.Component { 6 | componentDidMount() { 7 | // instantiate Video.js 8 | this.player = videojs(this.audioNode, this.props, () => { 9 | // print version information at startup 10 | var version_info = 'Using video.js ' + videojs.VERSION + 11 | ' with videojs-wavesurfer ' + videojs.getPluginVersion('wavesurfer') + 12 | ' and wavesurfer.js ' + WaveSurfer.VERSION; 13 | videojs.log(version_info); 14 | 15 | // load file 16 | this.player.src({src: '../media/hal.wav', type: 'audio/wav'}); 17 | }); 18 | 19 | this.player.on('waveReady', (event) => { 20 | console.log('waveform: ready!'); 21 | }); 22 | 23 | this.player.on('playbackFinish', (event) => { 24 | console.log('playback finished.'); 25 | }); 26 | 27 | // error handling 28 | this.player.on('error', (element, error) => { 29 | console.warn(error); 30 | }); 31 | } 32 | 33 | // destroy player on unmount 34 | componentWillUnmount() { 35 | if (this.player) { 36 | this.player.dispose(); 37 | } 38 | } 39 | 40 | // wrap the player in a div with a `data-vjs-player` attribute 41 | // so videojs won't create additional wrapper in the DOM 42 | // see https://github.com/videojs/video.js/pull/3856 43 | render() { 44 | return ( 45 |
    46 | 47 |
    48 | ) 49 | } 50 | } 51 | 52 | const videoJsOptions = { 53 | controls: true, 54 | autoplay: false, 55 | loop: false, 56 | muted: false, 57 | fluid: false, 58 | width: 600, 59 | height: 300, 60 | bigPlayButton: false, 61 | plugins: { 62 | wavesurfer: { 63 | backend: 'MediaElement', 64 | displayMilliseconds: true, 65 | debug: true, 66 | waveColor: 'white', 67 | progressColor: 'black', 68 | cursorColor: 'black', 69 | hideScrollbar: true 70 | } 71 | } 72 | }; 73 | 74 | ReactDOM.render(, document.getElementById('root')); 75 | -------------------------------------------------------------------------------- /examples/safari-workaround.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | /* workaround safari issues */ 4 | 5 | var isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); 6 | 7 | function addStartButton() { 8 | var btn = document.createElement('BUTTON'); 9 | var t = document.createTextNode('Show player'); 10 | btn.onclick = createPlayer; 11 | btn.appendChild(t); 12 | document.body.appendChild(btn); 13 | } 14 | 15 | function updateContext(opts) { 16 | // Safari 11 or newer automatically suspends new AudioContext's that aren't 17 | // created in response to a user-gesture, like a click or tap, so create one 18 | // here (inc. the script processor) 19 | var AudioContext = window.AudioContext || window.webkitAudioContext; 20 | var context = new AudioContext(); 21 | var processor = context.createScriptProcessor(1024, 1, 1); 22 | 23 | opts.plugins.wavesurfer.audioContext = context; 24 | opts.plugins.wavesurfer.audioScriptProcessor = processor; 25 | } 26 | 27 | function enableTextTracks(opts) { 28 | // workaround for video.js issue with Safari text tracks 29 | // see https://github.com/videojs/video.js/issues/7015 30 | opts.html5 = { 31 | nativeTextTracks: false, 32 | vhs: { 33 | overrideNative: true 34 | } 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /examples/texttrack.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Text-track Example - Wavesurfer Plugin for Video.js 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /examples/video.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Video Example - Wavesurfer Plugin Video 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 29 | 30 | 31 | 32 | 33 | 34 |
    35 | 36 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "videojs-wavesurfer", 3 | "version": "3.10.0", 4 | "description": "video.js plugin that adds a navigable waveform for audio and video files.", 5 | "author": "Collab", 6 | "license": "MIT", 7 | "readmeFilename": "README.md", 8 | "bugs": { 9 | "url": "https://github.com/collab-project/videojs-wavesurfer/issues" 10 | }, 11 | "homepage": "https://github.com/collab-project/videojs-wavesurfer", 12 | "main": "dist/videojs.wavesurfer.js", 13 | "style": "dist/css/videojs.wavesurfer.css", 14 | "sass": "src/css/videojs.wavesurfer.scss", 15 | "directories": { 16 | "docs": "./docs", 17 | "lib": "./src", 18 | "example": "./examples", 19 | "test": "./test" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/collab-project/videojs-wavesurfer.git" 24 | }, 25 | "scripts": { 26 | "clean": "rimraf dist", 27 | "build": "npm run clean && npm run lint && npm run build:dev && npm run build:min", 28 | "build:dev": "webpack --config ./build-config/webpack.dev.main.js", 29 | "build:min": "webpack --config ./build-config/webpack.prod.main.js", 30 | "docs": "npm-run-all docs:*", 31 | "docs:api": "rimraf docs/api && esdoc", 32 | "lint": "npm-run-all lint:*", 33 | "lint:js": "eslint src/js test build-config", 34 | "lint:html": "htmlhint examples docs/demo", 35 | "start": "npm run build && webpack serve --config ./build-config/webpack.dev.main.js", 36 | "start-doc": "docsify serve docs", 37 | "test": "karma start karma.conf.js", 38 | "prepublishOnly": "not-in-install && npm run build || in-install" 39 | }, 40 | "files": [ 41 | "dist/", 42 | "src/" 43 | ], 44 | "keywords": [ 45 | "waveform", 46 | "audio", 47 | "video", 48 | "wavesurfer", 49 | "videojs", 50 | "videojs-plugin", 51 | "player" 52 | ], 53 | "dependencies": { 54 | "video.js": ">=7.0.5", 55 | "wavesurfer.js": ">=6.3.0 <7.0.0" 56 | }, 57 | "devDependencies": { 58 | "@babel/core": "^7.26.0", 59 | "@babel/preset-env": "^7.26.0", 60 | "@babel/register": "^7.25.9", 61 | "@chiragrupani/karma-chromium-edge-launcher": "^2.4.1", 62 | "@jsdevtools/host-environment": "^2.1.2", 63 | "@jsdevtools/karma-host-environment": "^3.0.3", 64 | "add-zero": "^1.0.0", 65 | "babel-loader": "^9.2.1", 66 | "babel-plugin-add-module-exports": "^1.0.4", 67 | "babel-plugin-istanbul": "^7.0.0", 68 | "browserslist": "^4.24.3", 69 | "css-loader": "^7.1.2", 70 | "css-minimizer-webpack-plugin": "^7.0.0", 71 | "date-fns": "^4.1.0", 72 | "docsify-cli": "^4.4.4", 73 | "esdoc": "^1.1.0", 74 | "esdoc-standard-plugin": "^1.0.0", 75 | "eslint": "^9.17.0", 76 | "eslint-plugin-jsdoc": "^50.6.1", 77 | "htmlhint": "^1.1.4", 78 | "in-publish": "^2.0.1", 79 | "jasmine-core": "^5.5.0", 80 | "karma": "^6.4.4", 81 | "karma-chrome-launcher": "^3.2.0", 82 | "karma-coverage": "^2.2.1", 83 | "karma-detect-browsers": "^2.3.3", 84 | "karma-firefox-launcher": "^2.1.3", 85 | "karma-jasmine": "^5.1.0", 86 | "karma-jasmine-matchers": "^5.0.0", 87 | "karma-verbose-reporter": "0.0.8", 88 | "karma-webpack": "^5.0.1", 89 | "mini-css-extract-plugin": "^2.9.2", 90 | "npm-run-all": "^4.1.5", 91 | "parse-ms": "^3.0.0", 92 | "replace-in-file": "^8.2.0", 93 | "rimraf": "^6.0.1", 94 | "sass": "^1.83.0", 95 | "sass-loader": "^16.0.4", 96 | "style-loader": "^4.0.0", 97 | "webpack": "^5.97.1", 98 | "webpack-cli": "^5.1.4", 99 | "webpack-dev-server": "^5.2.0", 100 | "webpack-merge": "^6.0.1", 101 | "webpack-remove-empty-scripts": "^1.0.4", 102 | "webrtc-adapter": "^9.0.1" 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/css/fluid.scss: -------------------------------------------------------------------------------- 1 | /* Handle responsive / fluid view. 2 | -------------------------------------------------------------------------------- 3 | */ 4 | .vjs-wavesurfer.vjs-fluid wave.vjs-wavedisplay { 5 | top: 0; 6 | position: absolute!important; 7 | width: 100%; 8 | min-width: 100%; 9 | max-width: 100%; 10 | height: 100%; 11 | } 12 | -------------------------------------------------------------------------------- /src/css/main.scss: -------------------------------------------------------------------------------- 1 | /* Ensure custom controls are always visible because 2 | the plugin hides and replace the video.js native mobile 3 | controls. 4 | -------------------------------------------------------------------------------- 5 | */ 6 | .vjs-wavesurfer .vjs-using-native-controls .vjs-control-bar { 7 | display: flex !important; 8 | } 9 | 10 | /* Ensure that vjs menus and interfaces can be interacted with (such as the 11 | progress control). 12 | -------------------------------------------------------------------------------- 13 | */ 14 | .vjs-wavesurfer .vjs-menu-content, .vjs-progress-control { 15 | z-index: 4; 16 | } 17 | 18 | .vjs-wavesurfer .vjs-modal-dialog, .vjs-text-track-display { 19 | z-index: 4; 20 | } -------------------------------------------------------------------------------- /src/css/videojs.wavesurfer.scss: -------------------------------------------------------------------------------- 1 | @use "main"; 2 | @use "fluid"; 3 | -------------------------------------------------------------------------------- /src/js/defaults.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file defaults.js 3 | * @since 2.0.0 4 | */ 5 | 6 | // plugin defaults 7 | const pluginDefaultOptions = { 8 | // Display console log messages. 9 | debug: false, 10 | // Boolean indicating if milliseconds should be included, 11 | // e.g. "00:00:000" vs "00:00". 12 | displayMilliseconds: true 13 | }; 14 | 15 | export default pluginDefaultOptions; 16 | -------------------------------------------------------------------------------- /src/js/event.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file event.js 3 | * @since 2.8.0 4 | */ 5 | 6 | class Event {} 7 | 8 | // video.js 9 | Event.READY = 'ready'; 10 | Event.ERROR = 'error'; 11 | Event.VOLUMECHANGE = 'volumechange'; 12 | Event.FULLSCREENCHANGE = 'fullscreenchange'; 13 | Event.TIMEUPDATE = 'timeupdate'; 14 | Event.ENDED = 'ended'; 15 | Event.PAUSE = 'pause'; 16 | 17 | // wavesurfer.js 18 | Event.FINISH = 'finish'; 19 | Event.SEEK = 'seek'; 20 | Event.REDRAW = 'redraw'; 21 | Event.AUDIOPROCESS = 'audioprocess'; 22 | Event.DEVICE_READY = 'deviceReady'; 23 | Event.DEVICE_ERROR = 'deviceError'; 24 | 25 | // videojs-wavesurfer 26 | Event.AUDIO_OUTPUT_READY = 'audioOutputReady'; 27 | Event.WAVE_READY = 'waveReady'; 28 | Event.PLAYBACK_FINISH = 'playbackFinish'; 29 | Event.ABORT = 'abort'; 30 | 31 | // dom 32 | Event.RESIZE = 'resize'; 33 | 34 | // after the freeze, any attempts of altering the class will have no result 35 | Object.freeze(Event); 36 | 37 | export default Event; 38 | -------------------------------------------------------------------------------- /src/js/middleware.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file middleware.js 3 | * @since 3.0.0 4 | */ 5 | 6 | const WavesurferMiddleware = { 7 | /** 8 | * Setup the routing between a specific source and middleware 9 | * and eventually set the source on the Tech. 10 | * 11 | * @param {Tech~SourceObject} [srcObj] - Source object to manipulate. 12 | * @param {Function} [next] - The next middleware to run. 13 | */ 14 | setSource(srcObj, next) { 15 | // check if this player is using the videojs-wavesurfer plugin 16 | if (this.player.usingPlugin('wavesurfer')) { 17 | let backend = this.player.wavesurfer().surfer.params.backend; 18 | let src = srcObj.src; 19 | let peaks = srcObj.peaks; 20 | 21 | switch (backend) { 22 | case 'WebAudio': 23 | // load url into wavesurfer 24 | this.player.wavesurfer().load(src); 25 | break; 26 | 27 | default: 28 | // load source into video.js 29 | next(null, srcObj); 30 | 31 | // load media element into wavesurfer 32 | let element = this.player.tech_.el(); 33 | if (peaks === undefined) { 34 | // element without peaks 35 | this.player.wavesurfer().load(element); 36 | } else { 37 | // element with peaks 38 | this.player.wavesurfer().load(element, peaks); 39 | } 40 | break; 41 | } 42 | } else { 43 | // ignore middleware (this player isn't using the videojs-wavesurfer 44 | // plugin) and load source into video.js 45 | next(null, srcObj); 46 | } 47 | } 48 | }; 49 | 50 | export default WavesurferMiddleware; 51 | -------------------------------------------------------------------------------- /src/js/utils/format-time.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file format-time.js 3 | * @since 2.0.0 4 | */ 5 | 6 | import addZero from 'add-zero'; 7 | import parseMilliseconds from 'parse-ms'; 8 | 9 | /** 10 | * Format seconds as a duration string. 11 | * 12 | * Either formatted as: 13 | * 14 | * - DD:HH:MM:SS (> 24 hours) 15 | * - HH:MM:SS (> 1 hour) 16 | * - MM:SS:MSS (`displayMilliseconds = true`) 17 | * - MM:SS (`displayMilliseconds = false`) 18 | * 19 | * Supplying a guide (in seconds) will force a number of leading zeros 20 | * to cover the length of the guide. 21 | * 22 | * @param {number} seconds - Number of seconds to be turned into a 23 | * string. 24 | * @param {number} guide - Number (in seconds) to model the string after. 25 | * @param {boolean} displayMilliseconds - Display milliseconds or not. 26 | * @return {string} Formatted duration time, e.g '00:12:653'. 27 | * @private 28 | */ 29 | const formatTime = function(seconds, guide, displayMilliseconds = true) { 30 | seconds = seconds < 0 ? 0 : seconds; 31 | if (isNaN(seconds) || seconds === Infinity) { 32 | seconds = 0; 33 | } 34 | const inputTime = parseMilliseconds(seconds * 1000); 35 | let guideTime = inputTime; 36 | if (guide !== undefined) { 37 | guideTime = parseMilliseconds(guide * 1000); 38 | } 39 | const hr = addZero(inputTime.hours); 40 | const min = addZero(inputTime.minutes); 41 | const sec = addZero(inputTime.seconds); 42 | const ms = addZero(inputTime.milliseconds, 3); 43 | 44 | if (inputTime.days > 0 || guideTime.days > 0) { 45 | const day = addZero(inputTime.days); 46 | return `${day}:${hr}:${min}:${sec}`; 47 | } 48 | if (inputTime.hours > 0 || guideTime.hours > 0) { 49 | return `${hr}:${min}:${sec}`; 50 | } 51 | if (displayMilliseconds) { 52 | return `${min}:${sec}:${ms}`; 53 | } 54 | 55 | return `${min}:${sec}`; 56 | }; 57 | 58 | export default formatTime; 59 | -------------------------------------------------------------------------------- /src/js/utils/log.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file log.js 3 | * @since 2.0.0 4 | */ 5 | 6 | import videojs from 'video.js'; 7 | 8 | const ERROR = 'error'; 9 | const WARN = 'warn'; 10 | 11 | /** 12 | * Log message (if the debug option is enabled). 13 | * 14 | * @private 15 | * @param {Array} args - The arguments to be passed to the matching console 16 | * method. 17 | * @param {string} logType - The name of the console method to use. 18 | * @param {boolean} debug - Whether or not the debug option is enabled or not. 19 | */ 20 | const log = function(args, logType, debug) 21 | { 22 | if (debug === true) { 23 | if (logType === ERROR) { 24 | videojs.log.error(args); 25 | } else if (logType === WARN) { 26 | videojs.log.warn(args); 27 | } else { 28 | videojs.log(args); 29 | } 30 | } 31 | }; 32 | 33 | export default log; 34 | -------------------------------------------------------------------------------- /test/defaults.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2.3.0 3 | */ 4 | 5 | import pluginDefaultOptions from '../src/js/defaults.js'; 6 | 7 | /** @test {defaults} */ 8 | describe('pluginDefaultOptions', () => { 9 | 10 | /** @test {pluginDefaultOptions} */ 11 | it('returns a non-empty object', () => { 12 | expect(pluginDefaultOptions).toBeNonEmptyObject(); 13 | }); 14 | 15 | /** @test {pluginDefaultOptions} */ 16 | it('contains correct default values', () => { 17 | expect(pluginDefaultOptions).toEqual({ 18 | debug: false, 19 | displayMilliseconds: true 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/fluid.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2.3.0 3 | */ 4 | 5 | import window from 'global/window'; 6 | 7 | import Event from '../src/js/event.js'; 8 | 9 | import TestHelpers from './test-helpers.js'; 10 | 11 | 12 | let player; 13 | 14 | function fluid_test(backend) { 15 | /** @test {Wavesurfer#redrawWaveform} */ 16 | it('redraws the waveform', (done) => { 17 | let options = { 18 | fluid: true, 19 | plugins: { 20 | wavesurfer: { 21 | backend: backend 22 | } 23 | } 24 | }; 25 | player = TestHelpers.makePlayer(options); 26 | player.one(Event.WAVE_READY, () => { 27 | // class is present 28 | expect(player.hasClass('vjs-fluid')).toBeTrue(); 29 | 30 | done(); 31 | }); 32 | 33 | // load file 34 | player.src(TestHelpers.EXAMPLE_AUDIO_SRC); 35 | }); 36 | } 37 | 38 | /** @test {Wavesurfer} */ 39 | describe('Wavesurfer Fluid', () => { 40 | afterEach(() => { 41 | // destroy player 42 | player.dispose(); 43 | }); 44 | 45 | fluid_test(TestHelpers.MEDIA_ELEMENT_BACKEND); 46 | fluid_test(TestHelpers.WEB_AUDIO_BACKEND); 47 | fluid_test(TestHelpers.MEDIA_ELEMENT_WEB_AUDIO_BACKEND); 48 | }); -------------------------------------------------------------------------------- /test/live.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2.3.0 3 | */ 4 | 5 | import Event from '../src/js/event.js'; 6 | 7 | import TestHelpers from './test-helpers.js'; 8 | 9 | import MicrophonePlugin from 'wavesurfer.js/dist/plugin/wavesurfer.microphone'; 10 | 11 | /** @test {Wavesurfer} */ 12 | describe('Wavesurfer Live', () => { 13 | let player; 14 | 15 | afterEach(() => { 16 | // delete player 17 | player.dispose(); 18 | }); 19 | 20 | /** @test {Wavesurfer} */ 21 | it('microphone plugin is enabled', (done) => { 22 | player = TestHelpers.makePlayer({ 23 | plugins: { 24 | wavesurfer: { 25 | backend: 'WebAudio', 26 | waveColor: 'black', 27 | cursorWidth: 0, 28 | interact: false, 29 | plugins: [ 30 | // enable microphone plugin (for wavesurfer.js) 31 | MicrophonePlugin.create({ 32 | bufferSize: 4096, 33 | numberOfInputChannels: 1, 34 | numberOfOutputChannels: 1, 35 | constraints: { 36 | video: false, 37 | audio: true 38 | } 39 | }) 40 | ] 41 | } 42 | } 43 | }); 44 | 45 | player.one(Event.READY, () => { 46 | expect(player.wavesurfer().liveMode).toBeTrue(); 47 | expect(player.wavesurfer().waveReady).toBeTrue(); 48 | expect(player.wavesurfer().surfer.microphone).toBeDefined(); 49 | 50 | player.wavesurfer().surfer.microphone.once('deviceReady', () => { 51 | // device is ready 52 | expect(player.wavesurfer().surfer.microphone.active).toBeTrue(); 53 | 54 | // increase test coverage 55 | player.wavesurfer().pause(); 56 | player.wavesurfer().play(); 57 | 58 | done(); 59 | }); 60 | 61 | // start microphone 62 | player.wavesurfer().play(); 63 | }); 64 | }); 65 | 66 | }); -------------------------------------------------------------------------------- /test/options.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2.0.0 3 | */ 4 | 5 | import host from "@jsdevtools/host-environment"; 6 | 7 | import Event from '../src/js/event.js'; 8 | 9 | import TestHelpers from './test-helpers.js'; 10 | 11 | 12 | let player; 13 | 14 | function ws_options_test(backend) { 15 | /** @test {Wavesurfer} */ 16 | it('accepts waveformHeight option', (done) => { 17 | let height = 139; 18 | // create player 19 | player = TestHelpers.makePlayer({ 20 | plugins: { 21 | wavesurfer: { 22 | backend: backend, 23 | waveformHeight: height 24 | } 25 | } 26 | }); 27 | 28 | player.one(Event.WAVE_READY, () => { 29 | expect(player.wavesurfer().surfer.getHeight()).toEqual(height); 30 | 31 | done(); 32 | }); 33 | 34 | // load file 35 | player.src(TestHelpers.EXAMPLE_AUDIO_SRC); 36 | }); 37 | 38 | /** @test {Wavesurfer} */ 39 | it('accepts splitChannels option', (done) => { 40 | player = TestHelpers.makePlayer({ 41 | height: 100, 42 | plugins: { 43 | wavesurfer: { 44 | backend: backend, 45 | splitChannels: true 46 | } 47 | } 48 | }); 49 | 50 | player.one(Event.WAVE_READY, () => { 51 | expect(player.wavesurfer().surfer.getHeight()).toEqual(35); 52 | 53 | done(); 54 | }); 55 | 56 | // load file 57 | player.src(TestHelpers.EXAMPLE_AUDIO_SRC); 58 | }); 59 | 60 | /** @test {Wavesurfer} */ 61 | it('accepts autoplay option', (done) => { 62 | player = TestHelpers.makePlayer({ 63 | autoplay: true, 64 | plugins: { 65 | wavesurfer: { 66 | backend: backend 67 | } 68 | } 69 | }); 70 | 71 | // skip test in firefox until autoplay in headless browser is figured out 72 | if (host.browser.firefox) { 73 | done(); 74 | } 75 | 76 | player.one(Event.ERROR, (element, error) => { 77 | fail(error); 78 | }); 79 | player.one(Event.ENDED, done); 80 | 81 | // load file 82 | player.src(TestHelpers.EXAMPLE_AUDIO_SRC); 83 | }); 84 | 85 | /** @test {Wavesurfer} */ 86 | it('accepts formatTime option', (done) => { 87 | player = TestHelpers.makePlayer({ 88 | height: 100, 89 | plugins: { 90 | wavesurfer: { 91 | backend: backend, 92 | formatTime: (seconds, guide) => `foo:${seconds}:${guide}` 93 | } 94 | } 95 | }); 96 | 97 | player.one(Event.WAVE_READY, () => { 98 | expect(player.controlBar.currentTimeDisplay.formattedTime_).toEqual('foo:0:0'); 99 | expect(player.controlBar.durationDisplay.formattedTime_.substring(0, 5)).toEqual('foo:0'); 100 | done(); 101 | }); 102 | 103 | // load file 104 | player.src(TestHelpers.EXAMPLE_AUDIO_SRC); 105 | }); 106 | } 107 | 108 | /** @test {Wavesurfer} */ 109 | describe('Wavesurfer options', () => { 110 | afterEach(() => { 111 | // delete player 112 | player.dispose(); 113 | }); 114 | 115 | // run tests for each wavesurfer.js backend 116 | ws_options_test(TestHelpers.MEDIA_ELEMENT_BACKEND); 117 | ws_options_test(TestHelpers.MEDIA_ELEMENT_WEB_AUDIO_BACKEND); 118 | ws_options_test(TestHelpers.WEB_AUDIO_BACKEND); 119 | }); -------------------------------------------------------------------------------- /test/support/demo-peaks-invalid.json: -------------------------------------------------------------------------------- 1 | {"invalid": [-0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.031496062992125984, 0.03937007874015748, -1.0078740157480315, 1.0, -0.015748031496062992, 0.015748031496062992, -0.007874015748031496, 0.0, -0.06299212598425197, 0.09448818897637795, -0.7952755905511811, 0.7480314960629921, -1.0078740157480315, 1.0, -0.889763779527559, 0.7086614173228346, -1.0078740157480315, 1.0, -1.0078740157480315, 1.0, -0.12598425196850394, 0.23622047244094488, -0.10236220472440945, 0.11023622047244094, -0.05511811023622047, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.015748031496062992, 0.0, -0.25196850393700787, 0.2125984251968504, -0.5354330708661418, 0.3228346456692913, -0.06299212598425197, 0.09448818897637795, -0.10236220472440945, 0.09448818897637795, -0.031496062992125984, 0.08661417322834646, -0.6692913385826772, 0.9212598425196851, -1.0078740157480315, 1.0, -1.0078740157480315, 1.0, -0.2283464566929134, 0.16535433070866143, -0.11023622047244094, 0.11023622047244094, -0.06299212598425197, 0.08661417322834646, -0.05511811023622047, 0.047244094488188976, -0.015748031496062992, 0.015748031496062992, -0.07874015748031496, 0.08661417322834646, -0.007874015748031496, 0.015748031496062992, -0.015748031496062992, 0.015748031496062992, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.14960629921259844, 0.13385826771653545, -0.25196850393700787, 0.1889763779527559, -0.007874015748031496, 0.015748031496062992, -0.5118110236220472, 0.3937007874015748, -1.0078740157480315, 1.0, -0.05511811023622047, 0.06299212598425197, -0.015748031496062992, 0.015748031496062992, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0]} -------------------------------------------------------------------------------- /test/support/demo-peaks.json: -------------------------------------------------------------------------------- 1 | {"bits": 8, "length": 68, "sample_rate": 22050, "samples_per_pixel": 256, "data": [-0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.031496062992125984, 0.03937007874015748, -1.0078740157480315, 1.0, -0.015748031496062992, 0.015748031496062992, -0.007874015748031496, 0.0, -0.06299212598425197, 0.09448818897637795, -0.7952755905511811, 0.7480314960629921, -1.0078740157480315, 1.0, -0.889763779527559, 0.7086614173228346, -1.0078740157480315, 1.0, -1.0078740157480315, 1.0, -0.12598425196850394, 0.23622047244094488, -0.10236220472440945, 0.11023622047244094, -0.05511811023622047, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.015748031496062992, 0.0, -0.25196850393700787, 0.2125984251968504, -0.5354330708661418, 0.3228346456692913, -0.06299212598425197, 0.09448818897637795, -0.10236220472440945, 0.09448818897637795, -0.031496062992125984, 0.08661417322834646, -0.6692913385826772, 0.9212598425196851, -1.0078740157480315, 1.0, -1.0078740157480315, 1.0, -0.2283464566929134, 0.16535433070866143, -0.11023622047244094, 0.11023622047244094, -0.06299212598425197, 0.08661417322834646, -0.05511811023622047, 0.047244094488188976, -0.015748031496062992, 0.015748031496062992, -0.07874015748031496, 0.08661417322834646, -0.007874015748031496, 0.015748031496062992, -0.015748031496062992, 0.015748031496062992, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.14960629921259844, 0.13385826771653545, -0.25196850393700787, 0.1889763779527559, -0.007874015748031496, 0.015748031496062992, -0.5118110236220472, 0.3937007874015748, -1.0078740157480315, 1.0, -0.05511811023622047, 0.06299212598425197, -0.015748031496062992, 0.015748031496062992, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0, -0.007874015748031496, 0.0]} -------------------------------------------------------------------------------- /test/support/demo.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | NOTE Paragraph 4 | 5 | 00:00:00.137 --> 00:00:00.780 6 | Blip 7 | -------------------------------------------------------------------------------- /test/support/demo.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collab-project/videojs-wavesurfer/cd3284bf8e2da59039fe59af8ce33003d69bd6ae/test/support/demo.wav -------------------------------------------------------------------------------- /test/support/stars.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collab-project/videojs-wavesurfer/cd3284bf8e2da59039fe59af8ce33003d69bd6ae/test/support/stars.mp4 -------------------------------------------------------------------------------- /test/test-helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2.3.0 3 | */ 4 | 5 | import document from 'global/document'; 6 | 7 | import {Player, obj} from 'video.js'; 8 | 9 | const TestHelpers = { 10 | 11 | /** wavesurer.js backends to test against */ 12 | MEDIA_ELEMENT_BACKEND: 'MediaElement', 13 | MEDIA_ELEMENT_WEB_AUDIO_BACKEND: 'MediaElementWebAudio', 14 | WEB_AUDIO_BACKEND: 'WebAudio', 15 | 16 | /** Example audio clip */ 17 | EXAMPLE_AUDIO_FILE: '/base/test/support/demo.wav', 18 | 19 | /** Example audio clip mime-type */ 20 | EXAMPLE_AUDIO_TYPE: 'audio/wav', 21 | 22 | /** Example audio src object */ 23 | EXAMPLE_AUDIO_SRC: { 24 | src: '/base/test/support/demo.wav', 25 | type: 'audio/wav' 26 | }, 27 | 28 | /** Length of example audio clip */ 29 | EXAMPLE_AUDIO_DURATION: 0.782312925170068, 30 | 31 | /** Peaks data for example audio clip */ 32 | EXAMPLE_AUDIO_PEAKS_FILE: '/base/test/support/demo-peaks.json', 33 | 34 | /** File with invalid peaks data */ 35 | EXAMPLE_AUDIO_PEAKS_INVALID_FILE: '/base/test/support/demo-peaks-invalid.json', 36 | 37 | /** Example VTT clip */ 38 | EXAMPLE_VTT_FILE: '/base/test/support/demo.vtt', 39 | 40 | /** Example video clip */ 41 | EXAMPLE_VIDEO_FILE: '/base/test/support/stars.mp4', 42 | 43 | /** Example video clip mime-type */ 44 | EXAMPLE_VIDEO_TYPE: 'video/mp4', 45 | 46 | /** 47 | * Create DOM element. 48 | */ 49 | makeElement(element_type, id_name) { 50 | if (element_type === undefined) { 51 | element_type = 'audio'; 52 | } 53 | if (id_name === undefined) { 54 | id_name = 'myAudio'; 55 | } 56 | const element = document.createElement(element_type); 57 | element.id = id_name; 58 | element.muted = true; 59 | element.className = 'video-js vjs-default-skin'; 60 | element.style = 'background-color: #F2E68A;'; 61 | 62 | return element; 63 | }, 64 | 65 | /** 66 | * Create a test player containing the videojs-wavesurfer plugin. 67 | * 68 | * @param {Object} playerOptions 69 | * @param {Element|String} elementTag 70 | */ 71 | makePlayer(playerOptions, elementTag) { 72 | elementTag = elementTag || TestHelpers.makeElement(); 73 | 74 | // add to dom 75 | document.getElementsByTagName('body')[0].appendChild(elementTag); 76 | 77 | // default options 78 | let opts = obj.merge({ 79 | controls: true, 80 | autoplay: false, 81 | fluid: false, 82 | loop: false, 83 | width: 600, 84 | height: 300, 85 | plugins: { 86 | wavesurfer: { 87 | backend: 'MediaElement', 88 | msDisplayMax: 10, 89 | debug: true, 90 | waveColor: 'blue', 91 | progressColor: 'black', 92 | cursorColor: 'black', 93 | hideScrollbar: true, 94 | xhr: {} 95 | } 96 | } 97 | }, playerOptions || {}); 98 | 99 | return videojs(elementTag.id, opts); 100 | }, 101 | 102 | /** 103 | * Dispose all players. 104 | */ 105 | cleanup() { 106 | for (const playerId in Player.players) { 107 | if (Player.players[playerId] !== null) { 108 | Player.players[playerId].dispose(); 109 | } 110 | delete Player.players[playerId]; 111 | } 112 | }, 113 | 114 | /** 115 | * Triggers an event on a DOM node natively. 116 | * 117 | * @param {Element} element 118 | * @param {string} eventType 119 | */ 120 | triggerDomEvent(element, eventType) { 121 | let event; 122 | 123 | if (document.createEvent) { 124 | event = document.createEvent('HTMLEvents'); 125 | event.initEvent(eventType, true, true); 126 | } else { 127 | event = document.createEventObject(); 128 | event.eventType = eventType; 129 | } 130 | 131 | event.eventName = eventType; 132 | 133 | if (document.createEvent) { 134 | element.dispatchEvent(event); 135 | } else { 136 | element.fireEvent('on' + event.eventType, event); 137 | } 138 | } 139 | }; 140 | 141 | export default TestHelpers; -------------------------------------------------------------------------------- /test/tracks.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2.5.0 3 | */ 4 | 5 | import Event from '../src/js/event.js'; 6 | 7 | import TestHelpers from './test-helpers.js'; 8 | 9 | /** @test {Wavesurfer} */ 10 | describe('Wavesurfer TextTracks', () => { 11 | let player; 12 | 13 | beforeEach(() => { 14 | // create audio element with nested text track element 15 | const element = TestHelpers.makeElement('audio', 'myAudioTextTracks'); 16 | const track = document.createElement('track'); 17 | track.kind = 'captions'; 18 | track.src = TestHelpers.EXAMPLE_VTT_FILE; 19 | track.srclang = 'en-US'; 20 | track.default = true; 21 | element.appendChild(track); 22 | 23 | // create new player 24 | player = TestHelpers.makePlayer({}, element); 25 | }); 26 | 27 | afterEach(() => { 28 | // delete player 29 | player.dispose(); 30 | }); 31 | 32 | /** @test {Wavesurfer} */ 33 | it('displays interface elements', (done) => { 34 | 35 | player.one(Event.WAVE_READY, () => { 36 | // text tracks UI is visible 37 | expect(player.controlBar.subsCapsButton.hasClass('vjs-hidden')).toBeFalse(); 38 | 39 | // text track is present 40 | expect(player.textTracks().length).toEqual(1); 41 | 42 | done(); 43 | }); 44 | 45 | player.src(TestHelpers.EXAMPLE_AUDIO_SRC); 46 | }); 47 | }); -------------------------------------------------------------------------------- /test/utils.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 2.3.0 3 | */ 4 | 5 | import formatTime from '../src/js/utils/format-time.js'; 6 | import log from '../src/js/utils/log.js'; 7 | 8 | /** @test {format-time} */ 9 | describe('formatTime', () => { 10 | 11 | /** @test {formatTime} */ 12 | it('returns a formatted string for seconds', () => { 13 | let time = formatTime(10); 14 | expect(time).toEqual('00:10:000'); 15 | 16 | time = formatTime(11); 17 | expect(time).toEqual('00:11:000'); 18 | 19 | time = formatTime(121); 20 | expect(time).toEqual('02:01:000'); 21 | 22 | time = formatTime(3661); 23 | expect(time).toEqual('01:01:01'); 24 | 25 | // 300 days, 1 hour and 1 second 26 | time = formatTime(25923601); 27 | expect(time).toEqual('300:01:00:01'); 28 | }); 29 | 30 | /** @test {formatTime} */ 31 | it('returns a formatted string using a guide', () => { 32 | let time = formatTime(4.121, 10); 33 | expect(time).toEqual('00:04:121'); 34 | 35 | // using one hour as guide 36 | time = formatTime(4.121, 3600); 37 | expect(time).toEqual('00:00:04'); 38 | 39 | // using one day as guide 40 | time = formatTime(4.121, 86400); 41 | expect(time).toEqual('00:00:00:04'); 42 | }); 43 | 44 | /** @test {formatTime} */ 45 | it('returns a formatted string using displayMilliseconds option', () => { 46 | let time = formatTime(123.652, 10, false); 47 | expect(time).toEqual('02:03'); 48 | 49 | time = formatTime(7.652, 4.652, false); 50 | expect(time).toEqual('00:07'); 51 | 52 | // using one day as guide (will ignore option) 53 | time = formatTime(12.034, 86400, true); 54 | expect(time).toEqual('00:00:00:12'); 55 | }); 56 | 57 | /** @test {formatTime} */ 58 | it('returns a string when no arguments are received', () => { 59 | let time = formatTime(); 60 | 61 | expect(time).toEqual('00:00:000'); 62 | }); 63 | 64 | /** @test {formatTime} */ 65 | it('defaults to 0 when a negative value is received', () => { 66 | let time = formatTime(-2); 67 | 68 | expect(time).toEqual('00:00:000'); 69 | }); 70 | }); 71 | 72 | /** @test {log} */ 73 | describe('log', () => { 74 | 75 | /** @test {log} */ 76 | it('does not work when debug is false', () => { 77 | let test = log('foo', 'error', false); 78 | expect(test).toBeUndefined(); 79 | }); 80 | 81 | /** @test {log} */ 82 | it('only works when debug is true', () => { 83 | let test = log('foo', 'error', true); 84 | expect(test).toBeUndefined(); 85 | 86 | test = log('foo', 'warn', true); 87 | expect(test).toBeUndefined(); 88 | 89 | test = log('foo', 'bar', true); 90 | expect(test).toBeUndefined(); 91 | }); 92 | }); -------------------------------------------------------------------------------- /test/video.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 3.0.0 3 | */ 4 | 5 | import window from 'global/window'; 6 | 7 | import Event from '../src/js/event.js'; 8 | 9 | import TestHelpers from './test-helpers.js'; 10 | 11 | let player, element; 12 | 13 | function video_test(test_backend) { 14 | /** @test {Wavesurfer#redrawWaveform} */ 15 | it('draw waveform for video', (done) => { 16 | let opts = { 17 | plugins: { 18 | wavesurfer: { 19 | backend: test_backend 20 | } 21 | } 22 | }; 23 | element = TestHelpers.makeElement('video', 'testVideo'); 24 | player = TestHelpers.makePlayer(opts, element); 25 | player.one(Event.ERROR, (element, error) => { 26 | fail(error); 27 | }); 28 | player.one(Event.WAVE_READY, done); 29 | 30 | // load file 31 | player.src({ 32 | src: TestHelpers.EXAMPLE_VIDEO_FILE, 33 | type: TestHelpers.EXAMPLE_VIDEO_TYPE 34 | }); 35 | }); 36 | } 37 | 38 | /** @test {Wavesurfer} */ 39 | describe('Wavesurfer Video', () => { 40 | 41 | afterEach(() => { 42 | // destroy player 43 | player.dispose(); 44 | }); 45 | 46 | video_test(TestHelpers.MEDIA_ELEMENT_BACKEND); 47 | video_test(TestHelpers.MEDIA_ELEMENT_WEB_AUDIO_BACKEND); 48 | video_test(TestHelpers.WEB_AUDIO_BACKEND); 49 | }); --------------------------------------------------------------------------------