diff --git a/.babelrc b/.babelrc new file mode 100644 index 000000000..1918abd3d --- /dev/null +++ b/.babelrc @@ -0,0 +1,22 @@ +{ + "presets": [ + ["@babel/preset-env", { + "targets": "last 3 version", + "debug": false, + "modules": "commonjs" + }] + ], + "plugins": ["@babel/plugin-proposal-class-properties", "add-module-exports"], + + "env": { + "test": { + "plugins": [ + ["istanbul", { + "exclude": [ + "spec/**/*.spec.js" + ] + }] + ] + } + } +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..48a18f15f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +# http://editorconfig.org + +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.esdoc.json b/.esdoc.json new file mode 100644 index 000000000..1b66744c8 --- /dev/null +++ b/.esdoc.json @@ -0,0 +1,51 @@ +{ + "source": "./src", + "destination": "./doc", + "plugins": [ + { + "name": "esdoc-standard-plugin" + }, + { + "name": "esdoc-brand-plugin" + }, + { + "name": "esdoc-accessor-plugin", + "option": { + "access": ["public", "protected"], + "autoPrivate": true + } + }, + { + "name": "esdoc-ecmascript-proposal-plugin", + "option": { + "classProperties": true, + "objectRestSpread": true, + "exportExtensions": true + } + }, + { + "name": "esdoc-coverage-plugin", + "option": { + "enable": true, + "kind": [ + "class", + "method", + "member", + "get", + "set", + "constructor", + "function", + "variable" + ] + } + }, + { + "name": "esdoc-integrate-test-plugin", + "option": { + "source": "./spec/", + "interfaces": ["describe", "it", "context", "suite", "test"], + "includes": ["(spec|Spec|test|Test)\\.js$"] + } + } + ] +} diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..0ea8beafa --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,59 @@ +/* eslint-disable */ +module.exports = { + extends: ['eslint:recommended'], // extending recommended config and config derived from eslint-config-prettier + parser: '@babel/eslint-parser', + globals: { + WaveSurfer: true, + Float32Array: true, + Uint32Array: true, + Promise: true, + Proxy: true, + Symbol: true, + Uint8Array: true, + ArrayBuffer: true, + __VERSION__: true + }, + env: { + browser: true, + commonjs: true + }, + rules: { + eqeqeq: 'off', + 'semi': 2, + 'curly': "error", + "indent": ["error", 4, { + "ignoredNodes": ["TemplateLiteral"], + "SwitchCase": 1 + }], + 'comma-dangle': ["error", "never"], + 'comma-spacing': ["error", { "before": false, "after": true }], + 'no-console': 1, + 'no-unused-vars': 'off', + 'no-var': 'error', + 'no-unreachable': 2, + 'no-extra-semi': "error", + 'no-multi-spaces': "error", + 'no-multiple-empty-lines': "error", + 'space-infix-ops': "error", + 'valid-jsdoc': [2, { + 'requireReturn': false, + 'requireReturnType': false + }], + 'no-trailing-spaces': "error", + 'no-dupe-keys': "error", + 'require-jsdoc': 2, + 'no-duplicate-imports': "error", + 'space-before-function-paren': ["error", "never"], + 'keyword-spacing': ["error", {"before": true}] + }, + 'overrides': [ + { + 'files': ['example/**/*.js', 'spec/**/*.js'], + 'rules': { + 'no-var': 'off', + 'no-console': 'off', + 'require-jsdoc': 0, + 'valid-jsdoc': 0 + } + }] +}; diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..1b228c556 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [wavesurfer-js] diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..9a984ed64 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,35 @@ +# Hey, thank you for using and contributing to wavesurfer.js! + +## Need help with wavesurfer.js in your app? + +Please start a Q&A dicussion in our forum: https://github.com/wavesurfer-js/wavesurfer.js/discussions/new?category=q-a + +## Have an idea for a feature? + +Consider sharing it in the forum: https://github.com/wavesurfer-js/wavesurfer.js/discussions/new?category=ideas + +## You think you found a bug? + +### Please make sure you can check all of these points below before opening an issue: + +(You don't have to post this section) + +- [ ] I have checked [the FAQ](https://wavesurfer-js.org/faq/) and it doesn't solve my problem. +- [ ] I have checked [the documentation](https://wavesurfer-js.org/docs/) and it doesn't solve my problem +- [ ] I have searched for [already open issues](https://github.com/wavesurfer-js/wavesurfer.js/issues) which desribe my problem. + +## Please make sure to provide the following information (if applicable): + +### wavesurfer.js version(s): + + +### Browser and operating system version(s): + + +### Code needed to reproduce the issue: + +(Please reduce your code as much as possible and only post the minimum code needed to reproduce the issue. [A Code pen](http://codepen.io/) is an excellent way to share such code) + + +### (Optional) Steps in the UI to reproduce the issue: + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..f9002b067 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,21 @@ +# Hey, thank you for contributing to wavesurfer.js! + +To review/merge open PRs it is very helpful to know as much as possible about the changes which are being introduced. Reviewing PRs is very time consuming, please be patient, it can take some time to do properly. + +**Title:** Please make sure the name of your PR is as descriptive as possible (Describe the feature that is introduced or the bug that is being fixed). + +## Please make sure you provide the information below: + +### Short description of changes: + + +### Breaking in the external API: + + +### Breaking changes in the internal API: + + +### Todos/Notes: + + +### Related Issues and other PRs: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..1af02fc0e --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,50 @@ +name: Lint & tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [16] + + steps: + - uses: actions/checkout@v3 + - name: Update system + run: sudo apt-get update + - name: Install system dependencies + run: sudo apt-get install -y ubuntu-restricted-addons chromium-codecs-ffmpeg-extra gstreamer1.0-libav gstreamer1.0-plugins-ugly gstreamer1.0-vaapi + - name: Using Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + - name: Cache Node.js modules + uses: actions/cache@v3 + with: + # npm cache files are stored in `~/.npm` on Linux/macOS + path: ~/.npm + key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.OS }}-node- + ${{ runner.OS }}- + - name: Install Node.js modules + run: npm install + - name: Lint + run: npm run lint + - name: Build + run: npm run build + - name: Test + run: npm run test + - name: Coveralls + uses: coverallsapp/github-action@master + with: + path-to-lcov: ./coverage/lcov/lcov.info + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: Documentation + run: npm run doc diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..6553e0e3e --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,66 @@ +name: Release + +on: + push: + branches: + - master + +permissions: + contents: write + +jobs: + publish-npm: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Extract version + id: version + run: | + OLD_VERSION=$(npm show wavesurfer.js version) + NEW_VERSION=$(node -p 'require("./package.json").version') + if [ $NEW_VERSION != $OLD_VERSION ]; then + echo "New version $NEW_VERSION detected" + echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT + git log "$OLD_VERSION"..HEAD --pretty=format:"* %s" > TEMP_CHANGELOG.md + else + echo "Version $OLD_VERSION hasn't changed, skipping the release" + fi + + - name: Create a git tag + if: ${{ steps.version.outputs.version }} + run: git tag $NEW_VERSION && git push --tags + env: + NEW_VERSION: ${{ steps.version.outputs.version }} + + - name: GitHub release + if: ${{ steps.version.outputs.version }} + uses: actions/create-release@v1 + id: create_release + with: + draft: false + prerelease: false + release_name: ${{ steps.version.outputs.version }} + tag_name: ${{ steps.version.outputs.version }} + body_path: TEMP_CHANGELOG.md + env: + GITHUB_TOKEN: ${{ github.token }} + + - uses: actions/setup-node@v3 + if: ${{ steps.version.outputs.version }} + with: + node-version: '16.x' + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + if: ${{ steps.version.outputs.version }} + run: npm install + + - name: Publish to NPM + if: ${{ steps.version.outputs.version }} + env: + NODE_AUTH_TOKEN: ${{ secrets.npm_token }} + run: npm publish diff --git a/.gitignore b/.gitignore index 561f4da4f..01bc09791 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,19 @@ -dist/wavesurfer.dev.js -dist/plugin/*.js -!/dist/plugin/*.min.js +/dist + /node_modules /bower_components /npm-debug.log -/.grunt /coverage /_SpecRunner.html +/doc +/_site + .DS_Store .idea +.project +.chrome +.build_cache +.pydevproject + +package-lock.json +.package.json.swp diff --git a/.htmlhintrc b/.htmlhintrc new file mode 100644 index 000000000..ef864113a --- /dev/null +++ b/.htmlhintrc @@ -0,0 +1,12 @@ +{ + "tagname-lowercase": true, + "attr-lowercase": true, + "attr-value-double-quotes": false, + "doctype-first": false, + "tag-pair": true, + "spec-char-escape": false, + "id-unique": true, + "src-not-empty": true, + "attr-no-duplication": true, + "title-require": true +} diff --git a/.jscsrc b/.jscsrc deleted file mode 100644 index 0ca4de394..000000000 --- a/.jscsrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "disallowTrailingWhitespace": true, - "disallowMixedSpacesAndTabs": true, - "requirePaddingNewLinesAfterUseStrict": true, - "requireSpaceAfterComma": true, - "requireSpaceBetweenArguments": true -} \ No newline at end of file diff --git a/.npmignore b/.npmignore index 8e8929995..ea8c75d02 100644 --- a/.npmignore +++ b/.npmignore @@ -1,3 +1,3 @@ -/dist/wavesurfer.min.js.map -/dist/wavesurfer.amd.js -/dist/wavesurfer.min.js +# Intentionally left blank, so that npm does not ignore anything by default, +# but relies on the package.json "files" array to explicitly define what ends +# up in the package. diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 93ef71058..000000000 --- a/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: node_js -sudo: false -before_install: - - npm install -g grunt-cli - - export CHROME_BIN=chromium-browser - - export DISPLAY=:99.0 - - sh -e /etc/init.d/xvfb start -install: npm install -script: - - grunt diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..d118a10a5 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,18 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "url": "http://localhost:8080", + "webRoot": "${workspaceFolder}", + "breakOnLoad": true, + "sourceMaps": true, + "sourceMapPathOverrides": { + "webpack://WaveSurfer.[name]/./*": "${webRoot}/*", + "webpack://WaveSurfer/./*": "${webRoot}/*", + } + } + ] +} diff --git a/CHANGES.md b/CHANGES.md index aad51d344..cc40315b2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,8 +1,519 @@ wavesurfer.js changelog ======================= +6.6.3 (04.04.2023) +------------------ +- Markers plugin: + - allow removing markers by passing a marker object (#2749) + - add getMarkers function (#2743) +- Docs: version the unpkg script in readme (#2746) +- Refactor: remove scriptNode (#2706) + +6.6.2 (24.03.2023) +------------------ +- Revert "Zoom optimisation for Waves and matching implementation for Spectrograms (#2646)" +- Fix: avoid exit 1 in CI script (#2734) + +6.6.1 (18.03.2023) +------------------ +- Fix: NPM publish in the CI job (#2727) + - Fix: avoid exit 1 in CI script (#2734) +- Docs: add a video tutorial link to the readme (#2724) + +6.6.0 (14.03.2023) +------------------ +- Zoom optimisation for Waves and matching implementation for Spectrograms (#2646) +- Fix: release workflow permissions (#2709) + +6.5.2 (12.03.2023) +------------------ +- Regions plugin: + - Fix undefined content element on remove (#2713) + +6.5.0 (11.03.2023) +------------------ +- Regions plugin: + - Improved delta calculation (resize end) (#2641) + - Editable text in regions (#2521): + - 2 new params: + - `{contentEditable: true}` allows to display/add/edit text in regions upon the waveform + - `{removeButton: true}` displays remove button in regions + - New example -- video annotation using `{contentEditable: true}` and `{removeButton: true}` +- Fix iphone silent switch webaudio mute (#2667) +- Respect mute state when changing volume during mute (#2502) +- Proxy HTMLMediaElement's 'waiting' event through MediaElement backend. (#2691) +- Chores: + - Docs: update the issue template and wavesurfer links (#2671) + - Add FUNDING.yml to enable GitHub Sponsors (#2668) + - Update development dependencies + +6.4.0 (05.11.2022) +------------------ +- Markers plugin: + - Check for event after every add/remove (#2560) + - Add tooltip (#2595) +- Cursor plugin: + - Fix crash when `destroy` is called before `ready` event fired (#2606) + +6.3.0 (03.10.2022) +------------------ +- Fix `barMinHeight` option not setting the minimum bar height properly (#2522) +- Regions plugin: + - Restore support for one drag selection for all channels (#2529) +- Markers plugin: + - Add support for a context menu event on a marker (#2546) +- Spectrogram plugin: Make labels have position: absolute instead of fixed (#2542) + +6.2.0 (16.05.2022) +------------------ +- Fix `clientWidth` error in responsive mode (#2498) +- Cursor plugin: + - Fix `TypeError` when `showTime: undefined` (#2501) +- Spectrogram plugin: + - Fix to have consistent CSS height regardless of device pixel ratio (#2507) + - Added `height` configuration option to control CSS height of the view, + which will scale to fill + - Frequency label display is `fixed` instead of `absolute` to enable + consistent size on hi-dpi displays + +6.1.0 (31.03.2022) +------------------ +- Fix many calls to `setSinkId` resulting in no sound (#2481) +- Optimize responsive resize to avoid unnecessarily firing redraw on unpainted waveforms (#2485) +- Minimap plugin: + - Remove waveform of previous audio when starting to load new audio (#2479) + - Changed regions function name to resolve ambiguities (#2482) + +6.0.4 (09.03.2022) +------------------ +- Spectrogram plugin: + - Add `frequencyMin`, `frequencyMax` option to scale frequency axis. + And set default 12kHz range to draw spectrogram like 5.x (#2455) +- Timeline plugin: + - Fix rendering issue for negative `offset` values (#2463) + +6.0.3 (01.03.2022) +------------------ +- Cursor plugin: + - Fix type documentation for `followCursorY` and `opacity` options (#2459) + - Fix destroying cursor and showTime dom nodes (#2460) + +6.0.2 (20.02.2022) +------------------ +- Fix regression and restore support for passing a `CanvasGradient` to + `setWaveColor()` (#2448) +- Regions plugin: + - Fixed the type annotation of `maxRegions` in the regions plugin (#2454) + +6.0.1 (13.02.2022) +------------------ +- Fixed a regression that broke bars rendering when using a certain format for + the peaks array (#2439) + +6.0.0 (07.02.2022) +------------------ +- Add additional type to `waveColor` and `progressColor` parameters to support linear + gradients (#2345) +- Add `hideCursor` option to hide the mouse cursor when hovering over the waveform (#2367) +- Add optional `channelIdx` parameter to `setWaveColor`, `getWaveColor`, `setProgressColor` + and `getProgressColor` methods (#2391) +- Improved drawing waveform with bars, now bars height is the maximum peak value in + range (#2428) +- Workaround for `seekTo` occasionally crashing on Firefox (#1228, #2431) +- Markers plugin: Add the ability to set markers as draggable using param `draggable=true`, + `marker-drag` and `marker-drop` events will be triggered (#2398) +- Regions plugin: + - Increase region z-index to fix stacking inconsistencies (#2353) + - Check `maxLength` before resizing region (#2374) + - Add support for drag selection to be separated for each channel (#2380) + - Allow `formatTimeCallback` from plugin params to be used (#2294) + - Use of default `edgeScrollWidth` value no longer dependent on regions being created via + plugin params (#2401) + - Disable `region-remove` event emission during plugin teardown (#2403) +- Spectrogram plugin: + - Remove inaccurate frequency doubling of spectrogram (#2232) + - Support for `splitChannels` option to draw spectrogram for each channel (#2424) + +5.2.0 (16.08.2021) +------------------ +- Add `ignoreSilenceMode` option to ignore iOS hardware silence switch when using the + `WebAudio` backend (#1864) +- Fixed unhandled `Failed to execute 'stop' on 'AudioScheduledSourceNode'` error (#1473) +- Fixed unhandled `Cannot read property 'decodeArrayBuffer' of null` error (#2279) +- Timeline plugin: fixed unhandled `null is not an object (evaluating context.canvas)` + error in Safari v14 (#2333) +- Regions plugin: add `direction` and `action` fields to the `region-updated` event + params (#2339) + +5.1.0 (20.06.2021) +------------------ +- Markers plugin: + - Add the ability to use custom HTML elements in place of the default marker icon by + passing the new `markerElement` parameter to the marker constructor (#2269) + - Custom HTML elements are now centered over the marker line (#2298) + - Trigger `marker-click` event on wavesurfer (#2287) +- Regions plugin: handle rollover cursor bug fix (#2293) +- Timeline plugin: prevent calling `Canvas` context methods on `null` values (#2299) +- Spectrogram plugin: prevent calling `Canvas` context methods on `null` values (#2299) + +5.0.1 (05.05.2021) +------------------ +- Fix removing DOM element on `destroy()` (#2258) + +5.0.0 (02.05.2021) +------------------ +- Add new `vertical` parameter enabling displaying waveforms vertically (#2195) +- Fixed `exportPCM()` to return a Promise containing valid JSON data with `noWindow` + (#1896, #1954) +- Nullify `onaudioprocess` on remove to not execute in background (#2218) +- Playhead plugin: add a new plugin that allows the setting of a independent + "play head", or song-start position. (#2209) +- Markers plugin: fix a bug where markers at the end of a track would cause + incorrect click-to-seek behavior (#2208) +- Regions plugin: + - Fix mouseup not firing if click & drag a region handle & release outside the + browser window (#2213) + - Added new `showTooltip` param allowing disabling region `title` tooltip (#2213) + +4.6.0 (04.03.2021) +------------------ +- Webaudio: fix `decodeAudioData` handling in Safari (#2201) +- Markers plugin: add new plugin that allows for timeline markers (#2196) + +4.5.0 (14.02.2021) +------------------ +- Split channels: `overlay` param now properly displays a single canvas (#2161) +- Fixed memory leak with `destroy()` in `WebAudio` backend (#1940) +- Fixed `WaveSurfer.load(url)` not working when passing a HTMLMediaElement as + the url parameter, with the WebAudio backend. +- Microphone plugin: remove deprecated `MediaStream.stop` call (#2168) +- Regions plugin: stop region dragging when mouse leaves canvas (#2158) + +4.4.0 (13.01.2021) +------------------ + +- Use Webpack 5 for build (#2093) +- Fix seeking issues for `WebAudio` backend (#2149) +- Use `splitChannelsOptions` to color wave bars (#2150) + +4.3.0 (12.12.2020) +------------------ + +- Add `relativeNormalization` option to maintain proportionality between + waveforms when `splitChannels` and `normalize` are `true` (#2108) +- WebAudio backend: set playback rate modifying directly the playback + property of the source node (#2118) +- Spectrogram plugin: Use `ImageData` to draw pixel-by-pixel (#2127) + +4.2.0 (20.10.2020) +------------------ + +- Fix performance issues with `seekTo` while audio is playing (#2045) +- Trigger `waveform-ready` event when provided peaks are drawn (#2031) + +4.1.1 (24.09.2020) +------------------ + +- Revert Code cleanup for Observer class (#2069) + +4.1.0 (16.09.2020) +------------------ + +- Don't call HTMLMediaElement#load when given peaks and preload == 'none'. + Prevents browsers from pre-fetching audio (#1969, #1990) +- `seekTo` bugfix inc. basic unit tests (#2047) +- Fix unhandled `AbortError` thrown during `cancelAjax` (#2063) +- Remove `util.extend`: deprecated since v3.3.0 (#1995) +- Remove `util.ajax`: deprecated since v3.0.0 (#2033) +- Regions plugin: + - Removed `col-resize` cursor when resize is disabled (#1985) + - Improved and unified loop playback logic (#1868) + - Check `minLength` before resizing region (#2001) + - Dragging and resizing will continue outside canvas (#2006) + - `regionsMinLength` parameter to assign a min length to those regions for which the `minLength` is not specified (#2009) + - Revert PR #1926 click propagation on regions. Use event parameter passed + in `region-click` if you need `stopPropagation`. (#2024) + - Edgescroll works for both edges (#2011) +- Microphone plugin: move to separate directory (#1997) +- Minimap plugin: move plugin to separate directory (#1999) +- Cursor plugin: move plugin to separate directory (#1998) +- Elan plugin: move plugin to separate directory (#2019) +- Spectrogram plugin: move to separate directory (#1996) +- Mediasession plugin: move to separate directory (#2020) +- Timeline plugin: move to separate directory (#2018) + +4.0.1 (23.06.2020) +------------------ + +- Fixes for event handling with certain plugins (regions, microphone). + The crash would have involved '_disabledEventEmissions' (#1975) + +4.0.0 (21.06.2020) +------------------ + +- Fixed mediaelement-webaudio playback under Safari (#1964) +- Fixed the `destroy` method of the `MediaElementWebAudio` backend. Instead of + destroying only the media element, the audio nodes are disconnected and the + audio context is closed. This was done by splitting the `destroy` method of the + `WebAudio` backend, so it calls the new `destroyWebAudio` method to cancel + everything related to WebAudio (#1927) +- Removed private methods of plugins and generalized plugins' access, so they can be extended creating custom + plugins (#1928) +- Added plugin inheritance example (#1921) +- Added compatibility for Gatsby and other static site generators (#1938) +- Minimap plugin: added the ability to use a customized regions plugin using a new parameter + `regionsPluginName` (#1943) +- Fixed waveform display to not always connect to the sample=0 point (#1942) +- Elan plugin: optional params.tiers (#1910) +- Regions plugin: + - Split `regions.js` into `region.js` (containing `Region` class) and `index.js`. + Both files moved into the `src/plugin/regions` directory. This makes it easier + to extend these classes and use them in custom plugins (#1934) + - Fixed channelCount assignment (#1858) + - Fixed click propagation issue (#1926) + - Fixed switch loop region (#1929) + - Added ability to specify time format for Regions tooltip using timeformatCallback (#1948) +- Add `splitChannelsOptions` param and `setFilteredChannels` method to configure how channels are drawn (#1947) +- Added checks in `minimap` plugin for `drawer` presence (#1953) +- Add `setDisabledEventEmissions` method to optionally disable calls to event handlers for specific events (#1960) +- Drawer: removed private methods to allow overriding them (#1962) +- Add optional `setMute` method to backends to fix muting behavior with the `MediaElement` backend (#1966) + +3.3.3 (16.04.2020) +------------------ + +- Change default `desynchronized` drawing context attribute to `false` (#1908) + +3.3.2 (07.04.2020) +------------------ + +- Use `requestAnimationFrame` for clearWave (#1884) +- Fix `Unable to get property 'toLowerCase' of undefined or null reference` + in IE11 (#1771) +- Spectrogram plugin: correct the hamming windfunc formula (#1850) + +3.3.1 (13.01.2020) +------------------ + +- Regions plugin: + - Improve handles style support (#1839) + - Add support for a context menu event on a region (#1844) + - Fix for handle position when using `channelIdx` param (#1845) + +3.3.0 (29.12.2019) +------------------ + +- `wavesurfer.exportPCM` now accepts an optional `end` argument and returns + a Promise (#1728) +- Add `wavesurfer.setPlayEnd(position)` to set a point in seconds for + playback to stop at (#1795) +- Add `drawingContextAttributes` option and enable canvas `desynchronized` + hint (#1642) +- Add `barMinHeight` option (#1693) +- Expose progress to the `dblclick` event (#1790) +- Deprecate `util.extend` and replace usage with `Object.assign` (#1825) +- Regions plugin: + - Add `start` argument to `play` and `playLoop` methods (#1794) + - Add `maxRegions` option to limit max numbers of created regions (#1793) + - Don't assign to module object (#1823) + - Allow setting the `handleColor` inside `addRegion` (#1798) + - Disable drag selection before enabling it (#1698) + - Add `channelIdx` option to select specific channel to draw on (#1829) + - Refactor for improved readability (#1826) +- Cursor plugin: fix time visibility (#1802) + +3.2.0 (24.10.2019) +------------------ + +- New `MediaElementWebAudio` backend (#1767): + - Allows you to use Web Audio API with big audio files, loading audio + like with MediaElement backend (HTML5 audio tag), so you can use the + same methods of MediaElement backend for loading and playback. This way, + the audio resource is not loaded entirely from server, but in ranges, + allowing you to use WebAudio features, like filters, on audio files with + a long duration. You can also supply peaks data, so the entire audio file + does not have to be decoded. + For example: + ``` + wavesurfer.load(url | HTMLMediaElement, peaks, preload, duration); + wavesurfer.play(); + wavesurfer.setFilter(customFilter); + ``` +- Add `barRadius` option to create waveforms with rounded bars (#953) +- Throw error when the url parameter supplied to `wavesurfer.load()` + is empty (#1773, #1775) +- Specify non-minified wavesurfer.js in `main` entry of `package.json` (#1759) +- Add `dblclick` event listener to wavesurfer wrapper (#1764) +- Fix `destroy()` in `MediaElement` backend (#1778) +- Cursor plugin: flip position of time text to left of the cursor where needed + to improve readability (#1776) +- Regions plugin: change region end handler position (#1762, #1781) + +3.1.0 (26.09.2019) +------------------ + +- Add `autoCenter` and `autoCenterRate` options (#1699) +- Make sure `isReady` is true before firing the `ready` event (#1749) +- Improve fetch error messages (#1748) +- Use `MediaElement` backend for browsers that don't support WebAudio (#1739) +- Regions plugin: + - Use `isResizing` and `isDragging` to filter events in + region-updated listener (#1716) + - Fix `playLoop` and `loop` option for clips with duration <15s (#1626) +- Spectrogram plugin: fix variable name in click handler (#1742) +- Minimap plugin: fix left/width calculations for regions on retina/4k + screens (#1743) +- New example: video-annotation (#1726) + +3.0.0 (11.07.2019) +------------------ + +- Add `wavesurfer.getActivePlugins()`: return map of plugins + that are currently initialised +- Replace usage of `util.ajax` with `util.fetchFile` (#1365) +- Update progress when seeking with HTML media controls (#1535) +- Make sure mute/volume is updated when using `MediaElement` backend (#1615) +- Refactor `MultiCanvas` and add `CanvasEntry` class (#1617) +- Fix `wavesurfer.isReady`: make it a public boolean, the + broken `isReady` method is removed (#1597) +- Add support for `Blob` output type in `wavesurfer.exportImage` (#1610) +- Fix fallback to Audio Element in browsers that don't support Web Audio (#1614) +- `util.getId()` now accepts a `prefix` argument (#1619) +- Improve documentation for `xhr` option (#1656) +- Fix: the `progressWave` should not be rendered when specifying the same + value for the `progressColor` and `waveColor` options (#1620) +- Cursor plugin: + - Add `formatTimeCallback` option + - Add `followCursorY` option (#1605) + - Remove deprecated `enableCursor` method (#1646) + - Hide the cursor elements before first mouseover if `hideOnBlur` is set (#1663) +- Spectrogram plugin: + - Fix `ready` listener when loading multiple audio files (#1572) + - Allow user to specify a colorMap (#1436) +- Regions plugin: + - Fix `ready` listener when loading multiple audio files (#1602) + - Add `snapToGridInterval` and `snapToGridOffset` options (#1632) + - Allow drawing regions over existing regions, if the underlying ones are not + draggable or resizable (#1633) + - Calculate the duration at event time to allow predefined regions to be + dragged and resized (#1673) + - Remove deprecated `initRegions` method (#1646) +- Timeline plugin: fix `ready` listener when loading multiple + audio files +- Minimap plugin: remove deprecated `initMinimap` method (#1646) + +Check `UPGRADE.md` for backward incompatible changes since v2.x. + +2.2.1 (18.03.2019) +------------------ + +- Add `backgroundColor` option (#1118) +- Spectrogram plugin: fix click handler (#1585) +- Cursor plugin: fix `displayTime` (#1589) + +2.2.0 (07.03.2019) +------------------ + +- Add `rtl` option (#1296) +- Fix peaks rendering issue on zooming and scrolling multicanvas (#1570) +- Add `duration` option to specify an explicit audio length (#1441) +- Spectrogram plugin: fix event listener removal (#1571) +- Regions plugin: display regions before file load using `duration` + option (#1441) +- Build: switch to terser-webpack-plugin for minifying + +2.1.3 (21.01.2019) +------------------ + +- Fix removeOnAudioProcess for Safari (#1215, #1367, #1398) + +2.1.2 (06.01.2019) +------------------ + +- Fix computing peaks when buffer is not set (#1530) +- Cursor plugin: fix displayed time (#1543) +- Cursor plugin: document new params (#1516) +- Add syntax highlighting in examples (#1522) + +2.1.1 (18.11.2018) +------------------ + +- Fix order of arguments for PluginClass.constructor (#1472) +- Microphone plugin: Safari support (#1377) +- Minimap plugin: fix styling issues and add support for zooming (#1464) +- Timeline plugin: add duration parameter handling (#1491) +- Cursor plugin: add showTime option (#1143) +- Fix: progress bar did not reach 100% when audio file is small (#1502) + +2.1.0 (29.09.2018) +------------------ + +- Add wavesurfer.js logo, created by @entonbiba (#1409) +- Library version number is now available as `WaveSurfer.VERSION` (#1430) +- Fix `setSinkId` that used deprecated API (#1428) +- Set `isReady` attribute to false when emptying wavesufer (#1396, #1403) +- Microphone plugin: make it work in MS Edge browser (#627) +- Timeline plugin: display more tick marks as the user zooms in closely (#1455) +- Cursor plugin: fix `destroy` (#1435) + +2.0.6 (14.06.2018) +------------------ + +- Build library using webpack 4 (#1376) +- Add `audioScriptProcessor` option to use custom script processor node (#1389) +- Added `mute` and `volume` events (#1345) + +2.0.5 (26.02.2018) +------------------ + +- Fix `util.ajax` on iterating `requestHeaders` (#1329) +- Add version information to distributed files (#1330) +- Regions plugin: prevent click when creating / updating region (#1295) +- Add `wavesurfer.isReady` method (#1333) + +2.0.4 (14.02.2018) +------------------ + +- Added `xhr` option to configure util.ajax for authorization (#1310, #1038, #1100) +- Fix `setCurrentTime` method (#1292) +- Fix `getScrollX` method: Check bounds when `scrollParent: true` (#1312) +- Minimap plugin: fix initial load, canvas click did not work (#1265) +- Regions plugin: fix dragging a region utside of scrollbar (#430) + +2.0.3 (22.01.2018) +------------------ + +- Added support for selecting different audio output devices using `setSinkId` (#1293) +- Replace deprecated playbackRate.value setter (#1302) +- Play method now properly returns a Promise (#1229) + +2.0.2 (10.01.2018) +------------------ + +- Added `barGap` parameter to set the space between bars (#1058) +- Replace deprecated gain.value setter (#1277) +- MediaElement backend: Update progress on pause events (#1267) +- Restore missing MediaSession plugin (#1286) + +2.0.1 (18.12.2017) +------------------ + +- Core library and the plugins were refactored to be modular so it can be used with a module bundler +- Code updated to ES6/ES7 syntax and is transpiled with babel and webpack +- New plugin API +- `MultiCanvas` renderer is now the default +- Added getters and setters for height and color options (#1145) +- Introduce an option to prevent removing media element on destroy (#1163) +- Added duration parameter for the load function (#1239) +- New soundtouch.js filter to preserve pitch when changing tempo (#149) +- Add `getPlaybackRate` method (#1022) +- Switched to BSD license (#1060) +- Added `setCurrentTime` method +- Added `util.debounce` (#993) + 1.2.4 (11.11.2016) ------------------ + - Fix a problem of Web Audio not playing in Safari on initial load (#749) 1.2.3 (09.11.2016) @@ -14,7 +525,7 @@ wavesurfer.js changelog 1.2.2 (31.10.2016) ------------------ -- Determistic way to mute and unmute a track (#841) +- Deterministic way to mute and unmute a track (#841) - Replace jasmine with karma / jasmine test suite (#849) - Regions plugin: fix a bug when clicking on scroll-bar in Firefox (#851) diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index e34ff2ecd..000000000 --- a/Gruntfile.js +++ /dev/null @@ -1,164 +0,0 @@ -/*global module:false*/ -module.exports = function (grunt) { - var path = require('path'); - - // Project configuration. - grunt.initConfig({ - // Metadata. - pkg: grunt.file.readJSON('package.json'), - banner: '/*! <%= pkg.title || pkg.name %> <%= pkg.version %> (<%= new Date().toGMTString() %>)\n' + - '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' + - '* @license <%= pkg.license %> */', - // Task configuration. - clean : { - build: [ 'dist' ] - }, - concat: { - options: { - stripBanners: true - }, - dist: { - src: [ - 'src/wavesurfer.js', - 'src/util.js', - 'src/webaudio.js', - 'src/mediaelement.js', - 'src/drawer.js', - 'src/drawer.*.js', - 'src/html-init.js' - ], - dest: 'dist/wavesurfer.js' - } - }, - connect: { - options: { - base: '.', - port: 9000, - // Change this to '0.0.0.0' to access the server from outside. - hostname: 'localhost', - keepalive: true - } - }, - /* The build has three steps: - - concatenation (wavesurfer.js) - - UMD wrapping (wavesurfer.js) - - uglification (wavesurfer.min.js) */ - umd: { - main: { - options: { - src: '<%= concat.dist.dest %>', - dest: 'dist/wavesurfer.js', - amdModuleId: 'wavesurfer', - objectToExport: 'WaveSurfer', - globalAlias: 'WaveSurfer' - } - }, - plugins: { - src: 'plugin/*.js', - dest: 'dist', - deps: { - 'default': [ 'WaveSurfer' ], - amd: [ 'wavesurfer' ], - cjs: [ 'wavesurfer.js' ], - global: [ 'WaveSurfer' ] - } - } - }, - uglify: { - options: { - banner: '<%= banner %>' - }, - dist: { - options: { - sourceMap: 'dist/wavesurfer.min.js.map', - sourceMapRoot: '/' - }, - src: '<%= umd.main.options.dest %>', - dest: 'dist/wavesurfer.min.js' - }, - plugins: { - files: [{ - expand: true, - cwd: 'dist', - src: 'plugin/*.js', - dest: 'dist/', - rename: function (dest, src) { - if (src.indexOf('.min.js') == -1) { - return dest + src.replace('.js', '.min.js'); - } - return dest + src; - } - }] - } - }, - jshint: { - options: { - curly: true, - eqeqeq: false, - eqnull: true, - quotmark: 'single', - browser: true, - // Missing "use strict" statement. - strict: false, - globals: { - WaveSurfer: true, - Promise: true - } - }, - ignore_warning: { - options: { - '-W004': true, - // Expected an assignment or function call and instead saw an expression - '-W030': true, - // {a} used out of scope - '-W038': true, - // Use '!==' to compare with '' - '-W041': true, - '-W056': true, - // Missing '()' invoking a constructor. - '-W058': true, - '-W079': true, - // Use the function form of 'use strict' - '-W097': true - }, - src: [ '<%= concat.dist.src %>', 'plugin/*.js', 'spec/*.spec.js' ] - } - }, - jscs: { - src: [ '<%= concat.dist.src %>', 'plugin/*.js', 'spec/*.spec.js' ], - options: { - config: '.jscsrc', - fix: false, // Autofix code style violations when possible. - requireCurlyBraces: [ 'if' ] - } - }, - karma: { - e2e: { - configFile: 'karma.conf.js' - } - } - }); - - // ========================================================================== - // TASKS - // ========================================================================== - - // These plugins provide necessary tasks. - grunt.loadNpmTasks('grunt-contrib-concat'); - grunt.loadNpmTasks('grunt-contrib-uglify'); - grunt.loadNpmTasks('grunt-contrib-jshint'); - grunt.loadNpmTasks('grunt-karma'); - grunt.loadNpmTasks('grunt-contrib-connect'); - grunt.loadNpmTasks('grunt-umd'); - grunt.loadNpmTasks('grunt-jscs'); - grunt.loadNpmTasks('grunt-contrib-clean'); - - // Default task. - grunt.registerTask('default', [ - 'clean', 'jshint', 'jscs', 'concat', 'umd', 'uglify', 'test' - ]); - - // Dev - grunt.registerTask('dev', [ 'clean', 'concat', 'uglify', 'jscs', 'connect' ]); - grunt.registerTask('test', [ 'karma:e2e' ]); -}; diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..88998ae68 --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2012-2023, katspaugh and contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 4373b23d5..62a332d20 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,53 @@ # [wavesurfer.js](https://wavesurfer-js.org) +--- + +✨ We're excited to introduce version 7.0.0-beta, a TypeScript rewrite of wavesurfer.js with better performance and new plugins. Give the beta version a spin and let us know what you think! Check out the [wavesurfer.ts README](https://github.com/katspaugh/wavesurfer.ts) to get started. Happy coding! ✨ + +--- + [![npm version](https://img.shields.io/npm/v/wavesurfer.js.svg?style=flat)](https://www.npmjs.com/package/wavesurfer.js) -![npm](https://img.shields.io/npm/dm/wavesurfer.js.svg) [![Join the chat at https://gitter.im/katspaugh/wavesurfer.js](https://badges.gitter.im/katspaugh/wavesurfer.js.svg)](https://gitter.im/katspaugh/wavesurfer.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +![npm](https://img.shields.io/npm/dm/wavesurfer.js.svg) [![Join the chat at https://gitter.im/wavesurfer-js/wavesurfer.js](https://badges.gitter.im/wavesurfer-js/wavesurfer.js.svg)](https://gitter.im/wavesurfer-js/wavesurfer.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![GitPOAP Badge](https://public-api.gitpoap.io/v1/repo/wavesurfer-js/wavesurfer.js/badge)](https://www.gitpoap.io/gh/wavesurfer-js/wavesurfer.js) Interactive navigable audio visualization using Web Audio and Canvas. -[![Screenshot](https://raw.githubusercontent.com/katspaugh/wavesurfer.js/gh-pages/example/screenshot.png "Screenshot")](https://wavesurfer-js.org) +[![Screenshot](https://raw.githubusercontent.com/wavesurfer-js/wavesurfer.js/gh-pages/example/screenshot.png "Screenshot")](https://wavesurfer-js.org) + +See [docs](https://wavesurfer-js.org/docs) and [examples](https://wavesurfer-js.org/examples) on [wavesurfer-js.org](https://wavesurfer-js.org). -See a [tutorial](https://wavesurfer-js.org/docs) and [examples](https://wavesurfer-js.org/examples) on [wavesurfer-js.org](https://wavesurfer-js.org). +For a video tutorial, watch this [series by Live Blogger on YouTube](https://www.youtube.com/watch?v=yCmnDWCF8m0). 📺 -## Browser support -wavesurfer.js works only in [modern browsers supporting Web Audio](http://caniuse.com/audio-api). +## Questions +Have a question about integrating wavesurfer.js on your website? Feel free to ask in our forum: https://github.com/wavesurfer-js/wavesurfer.js/discussions/categories/q-a -It will fallback to Audio Element in other browsers (without graphics). You can also try [wavesurfer.swf](https://github.com/laurentvd/wavesurfer.swf) which is a Flash-based fallback. +## Quick start +Install the package: -## FAQ -### Can the audio start playing before the waveform is drawn? -Yes, if you use the `backend: 'MediaElement'` option. See here: https://wavesurfer-js.org/example/audio-element/. The audio will start playing as you press play. A thin line will be displayed until the whole audio file is downloaded and decoded to draw the waveform. +``` +npm install wavesurfer.js --save + +# or + +yarn add wavesurfer.js +``` -### Can drawing be done as file loads? -No. Web Audio needs the whole file to decode it in the browser. You can however load pre-decoded waveform data to draw the waveform immediately. See here: https://wavesurfer-js.org/example/audio-element/ (the "Pre-recoded Peaks" section). +And import it like so: +``` +import WaveSurfer from 'wavesurfer.js' +``` -## API in examples +If you're not using a package manager, simply insert the script from a CDN: +``` + +``` -Choose a container: +Create a container in your HTML: ```html
``` -Create an instance, passing the container selector and [options](https://wavesurfer-js.org/docs/options.html): + +Create an instance of wavesufer.js, passing the container selector and a few [options](https://wavesurfer-js.org/docs/options.html): ```javascript var wavesurfer = WaveSurfer.create({ @@ -55,64 +75,89 @@ wavesurfer.load('example/media/demo.wav'); See the documentation on all available [methods](https://wavesurfer-js.org/docs/methods.html), [options](https://wavesurfer-js.org/docs/options.html) and [events](https://wavesurfer-js.org/docs/events.html) on the [homepage](https://wavesurfer-js.org/docs/). -## Related projects +## Projects using wavesurfer.js -For a list of projects using wavesurfer.js, check out +For the list of projects using wavesurfer.js, check out [the projects page](https://wavesurfer-js.org/projects/). -## Development +## Contributing -[![npm version](https://img.shields.io/npm/v/wavesurfer.js.svg?style=flat)](https://www.npmjs.com/package/wavesurfer.js) -[![npm](https://img.shields.io/npm/dm/wavesurfer.js.svg)]() -[![Build Status](https://travis-ci.org/katspaugh/wavesurfer.js.svg?branch=master)](https://travis-ci.org/katspaugh/wavesurfer.js) +Have an idea and want to contribute to wavesurfer.js? +Please first start a discussion in the [Ideas section of our forum](https://github.com/wavesurfer-js/wavesurfer.js/discussions/categories/ideas) to coordinate with the maintainers. -Install `grunt-cli` using npm: +### Development -``` -npm install -g grunt-cli -``` +[![Build Status](https://github.com/wavesurfer-js/wavesurfer.js/workflows/wavesurfer.js/badge.svg?branch=master)](https://github.com/wavesurfer-js/wavesurfer.js/actions?workflow=wavesurfer.js) +[![Coverage Status](https://coveralls.io/repos/github/wavesurfer-js/wavesurfer.js/badge.svg)](https://coveralls.io/github/wavesurfer-js/wavesurfer.js) +![Size](https://img.shields.io/bundlephobia/minzip/wavesurfer.js.svg?style=flat) Install development dependencies: ``` npm install ``` +Development tasks automatically rebuild certain parts of the library when files are changed (`start` – wavesurfer, `start:plugins` – plugins). Start a dev task and go to `localhost:8080/example/` to test the current build. -Build a minified version of the library and plugins. This command also checks -for code-style mistakes and runs the tests: +Start development server for core library: ``` -grunt +npm run start ``` -Generated files are placed in the `dist` directory. +Start development server for plugins: -Running tests only: +``` +npm run start:plugins +``` + +Build all the files. (generated files are placed in the `dist` directory.) ``` -grunt test +npm run build ``` -Creating a coverage report: +Running tests only: + +``` +npm run test +``` +Build documentation with esdoc (generated files are placed in the `doc` directory.) ``` -grunt coverage +npm run doc ``` -The HTML report can be found in `coverage/html/index.html`. +If you want to use [the VS Code - Debugger for Chrome](https://github.com/Microsoft/vscode-chrome-debug), there is already a [launch.json](.vscode/launch.json) with a properly configured ``sourceMapPathOverrides`` for you. ## Editing documentation -The homepage and the documentation are in the [`gh-pages` branch](https://github.com/katspaugh/wavesurfer.js/tree/gh-pages). Contributions to the documentation are especially welcome. +The homepage and documentation files are maintained in the [`gh-pages` branch](https://github.com/wavesurfer-js/wavesurfer.js/tree/gh-pages). Contributions to the documentation are especially welcome. + +## Releasing a new version +To release a new version and publish it to NPM, follow the steps below. + + Switch to the master branch and make sure it's up-to-date + ``` + git checkout master + git fetch --all; git reset --hard origin/master +``` + +Run the release script: +``` +yarn release +``` +This will update the version, generate a changelog, and push everything to a new branch called `release/X.X.X`. + +A browser window will open to create a PR from this new branch to the master branch. Once the PR is approved and merged, an automated workflow will kick in and publish a release both on GitHub and NPM. ## Credits -Initial idea by [Alex Khokhulin](https://github.com/xoxulin). Many -thanks to -[the awesome contributors](https://github.com/katspaugh/wavesurfer.js/contributors)! +The main maintainer: [Thijs Triemstra](https://github.com/thijstriemstra) + +Many thanks to [all the awesome contributors](https://github.com/wavesurfer-js/wavesurfer.js/contributors)! ## License -![cc-by](https://i.creativecommons.org/l/by/3.0/88x31.png) +[![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) This work is licensed under a -[Creative Commons Attribution 3.0 Unported License](https://creativecommons.org/licenses/by/3.0/deed.en_US). +[BSD 3-Clause License](https://opensource.org/licenses/BSD-3-Clause). diff --git a/UPGRADE.md b/UPGRADE.md new file mode 100644 index 000000000..b1d395b9c --- /dev/null +++ b/UPGRADE.md @@ -0,0 +1,90 @@ +# Upgrade + +## Upgrading to version 6 from version 5 + +- `CursorPlugin.outerWidth(element)` was removed. You can use [`element.getBoundingClientRect().width`](https://developer.mozilla.org/docs/Web/API/Element/getBoundingClientRect) instead. + +## Upgrading to version 5 from version 4 + +1. **`MultiCanvas` and `Drawer` now use [`Proxy`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) objects for DOM elements**: You can access the original instances via the `domElement` property on the Proxy objects (e.g. `CanvasEntry.wave.domElement`). + +This affects the following objects: + +- `CanvasEntry.progress` (e.g. `MultiCanvas.canvases[n].progress`) +- `CanvasEntry.wave` (e.g. `MultiCanvas.canvases[n].wave`) +- `Drawer.wrapper` +- `MultiCanvas.container` + +2. **`WaveSurfer.exportPCM()` now returns a `Promise` that resolves to an `Array`**: Before, the Promise resolved to a JSON `string` representing the `Array`. You can get the same result by converting the resulting `Array` via [`JSON.stringify()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify). + +## Upgrading to version 4 from version 3 + +(No backward-incompatible changes.) + +## Upgrading to version 3 from version 2 + +- `util.ajax` was deprecated; use `util.fetchFile instead. +- The `xhr` wavesurfer option has changed to work with `util.fetchFile`. +- The `MultiCanvas` renderer was refactored and a new `CanvasEntry` class was added to represent + a canvas instance in a `MultiCanvas`. + +## Upgrading to version 2 from version 1 + +The wavesurfer.js core library and the plugins were refactored to be modular so it can be used with a module bundler. +You can still use wavesurfer without, e.g. with ` - + diff --git a/example/angular-material/main.js b/example/angular-material/main.js index ff5f488d5..1192bce5e 100644 --- a/example/angular-material/main.js +++ b/example/angular-material/main.js @@ -1,21 +1,30 @@ +/* global angular */ + /** * Created by intelWorx on 19/11/2015. */ -(function () { - 'use strict'; - angular.module('mdWavesurferApp', ['mdWavesurfer']) - .config(function ($mdIconProvider) { - //$mdIconProvider.fontSet('zmdi', 'fontawesome'); - }) - .controller('MainController', ['$scope', - function ($scope) { - $scope.urls = ["../media/demo.wav", "../panner/media.wav", "../elan/transcripts/001z.mp3"]; - $scope.longList = []; - for (var i = 0; i < 100; i++) { - $scope.longList.push({ - title: 'Long List test: ' + i, - url: $scope.urls[Math.floor(3 * Math.random())] - }); - } - }]) +(function() { + 'use strict'; + angular + .module('mdWavesurferApp', ['mdWavesurfer']) + .config(function($mdIconProvider) { + //$mdIconProvider.fontSet('zmdi', 'fontawesome'); + }) + .controller('MainController', [ + '$scope', + function($scope) { + $scope.urls = [ + '../media/demo.wav', + '../panner/media.wav', + '../elan/transcripts/001z.mp3' + ]; + $scope.longList = []; + for (let i = 0; i < 100; i++) { + $scope.longList.push({ + title: 'Long List test: ' + i, + url: $scope.urls[Math.floor(3 * Math.random())] + }); + } + } + ]); })(); diff --git a/example/angular-material/md-player-audio.partial.html b/example/angular-material/md-player-audio.partial.html index e73c3c992..611ca99b1 100644 --- a/example/angular-material/md-player-audio.partial.html +++ b/example/angular-material/md-player-audio.partial.html @@ -1,7 +1,7 @@
-
+

Audio Player

diff --git a/example/angular-material/wavesurfer.directive.js b/example/angular-material/wavesurfer.directive.js index 2e9986e29..d28f0b4f7 100644 --- a/example/angular-material/wavesurfer.directive.js +++ b/example/angular-material/wavesurfer.directive.js @@ -1,416 +1,463 @@ +/* global angular */ + /** * Created by intelWorx on 19/11/2015. */ -(function () { - 'use strict'; - - /** - * Main module, your application should depend on this - * @module {mdWavesurfer} - */ - var app = angular.module('mdWavesurfer', ['ngMaterial']); - - /** - * @ngdoc service - * @name $mdWavesurferUtils - * - * @description - * - * Utility service for this directive, exposes method: - * - getLength(url), which returns a promise for the length of the audio specified by URL - * - * ```js - * app.directive('myFancyDirective', function(mdWavesurferUtils) { - * return { - * restrict: 'e', - * link: function(scope, el, attrs) { - * mdWavesurferUtils(attrs.url) - * .then(function(l){ - * scope.length = l; - * }, function(){ - * someErrorhandler() - * }) - * ; - * } - * }; - * }); - * ``` - */ - app.factory('mdWavesurferUtils', ['$q', '$document', '$timeout', - function ($q, $document, $timeout) { - return { - getLength: function (object) { - var deferred = $q.defer(); - var estimateLength = function (url) { - var audio = $document[0].createElement('audio'); - audio.src = url; - audio.addEventListener('loadeddata', function listener() { - deferred.resolve(this.duration); - audio.removeEventListener('loadeddata', listener); - audio.src = 'data:audio/mpeg,0';//destroy loading. - }); - - audio.addEventListener('error', function (e) { - deferred.resolve(e.target.error); - }); - }; - - if (typeof object === 'string') { - //this is a URL - estimateLength(object); - } else { - $timeout(function () { - deferred.reject(new DOMError("NotSupportedError", "Specified argument is not supported")); - }); - } - - return deferred.promise; - } - }; - } - ]); - - /** - * @ngdoc filter - * @name mdWavesurferTimeFormat - * - * Simple filter to convert value in seconds to MM:SS format - * - * @param Number duration in seconds - */ - app.filter('mdWavesurferTimeFormat', function () { - return function (input) { - if (!input) { - return "00:00"; - } - - var minutes = Math.floor(input / 60); - var seconds = Math.ceil(input) % 60; - - return (minutes < 10 ? '0' : '') - + minutes - + ":" - + (seconds < 10 ? '0' : '') + seconds; - }; - }); - - - app.controller('mdWavesurferAudioController', ['$attrs', '$element', - function (attributes, $element) { - var audio = this; - - audio.tracks = []; - audio.selectedIndex = audio.selectedIndex || 0; - audio.currentTrack = null; - - //adds to an audio track - audio.addTrack = function (trackScope) { - if (audio.tracks.indexOf(trackScope) < 0) { - audio.tracks.push(trackScope); +(function() { + 'use strict'; + + /** + * Main module, your application should depend on this + * @module {mdWavesurfer} + */ + let app = angular.module('mdWavesurfer', ['ngMaterial']); + + /** + * @ngdoc service + * @name $mdWavesurferUtils + * + * @description + * + * Utility service for this directive, exposes method: + * - getLength(url), which returns a promise for the length of the audio specified by URL + * + * ```js + * app.directive('myFancyDirective', function(mdWavesurferUtils) { + * return { + * restrict: 'e', + * link: function(scope, el, attrs) { + * mdWavesurferUtils(attrs.url) + * .then(function(l){ + * scope.length = l; + * }, function(){ + * someErrorhandler() + * }) + * ; + * } + * }; + * }); + * ``` + */ + app.factory('mdWavesurferUtils', [ + '$q', + '$document', + '$timeout', + function($q, $document, $timeout) { + return { + getLength: function(object) { + let deferred = $q.defer(); + let estimateLength = function(url) { + let audio = $document[0].createElement('audio'); + audio.src = url; + audio.addEventListener( + 'loadeddata', + function listener() { + deferred.resolve(this.duration); + audio.removeEventListener( + 'loadeddata', + listener + ); + audio.src = 'data:audio/mpeg,0'; //destroy loading. + } + ); + + audio.addEventListener('error', function(e) { + deferred.resolve(e.target.error); + }); + }; + + if (typeof object === 'string') { + //this is a URL + estimateLength(object); + } else { + $timeout(function() { + deferred.reject( + new DOMError( + 'NotSupportedError', + 'Specified argument is not supported' + ) + ); + }); + } + + return deferred.promise; + } + }; } + ]); + + /** + * @ngdoc filter + * @name mdWavesurferTimeFormat + * + * Simple filter to convert value in seconds to MM:SS format + * + * @param Number duration in seconds + */ + app.filter('mdWavesurferTimeFormat', function() { + return function(input) { + if (!input) { + return '00:00'; + } - if (!audio.currentTrack) { - audio.currentTrack = audio.tracks[audio.selectedIndex]; - } - }; + const minutes = Math.floor(input / 60); + const seconds = Math.floor(input) % 60; + + return ( + (minutes < 10 ? '0' : '') + + minutes + + ':' + + (seconds < 10 ? '0' : '') + + seconds + ); + }; + }); + + app.controller('mdWavesurferAudioController', [ + '$attrs', + '$element', + function(attributes, $element) { + let audio = this; + + audio.tracks = []; + audio.selectedIndex = audio.selectedIndex || 0; + audio.currentTrack = null; + + //adds to an audio track + audio.addTrack = function(trackScope) { + if (audio.tracks.indexOf(trackScope) < 0) { + audio.tracks.push(trackScope); + } + + if (!audio.currentTrack) { + audio.currentTrack = audio.tracks[audio.selectedIndex]; + } + }; + + //remove audio track + audio.removeTrack = function(trackScope) { + const idx = audio.tracks.indexOf(trackScope); + if (idx >= 0) { + audio.tracks.splice(idx, 1); + } + }; + + audio.playerProperties = {}; + let nKey; + for (let attr in attributes) { + if (attr.match(/^player/)) { + nKey = attr.replace(/^player([A-Z])/, function(m, $1) { + return $1.toLowerCase(); + }); + audio.playerProperties[nKey] = attributes[attr]; + } + } - //remove audio track - audio.removeTrack = function (trackScope) { - var idx = audio.tracks.indexOf(trackScope); - if (idx >= 0) { - audio.tracks.splice(idx, 1); - } - }; - - audio.playerProperties = {} - var nKey; - for (var attr in attributes) { - if (attr.match(/^player/)) { - nKey = attr.replace(/^player([A-Z])/, function (m, $1) { - return $1.toLowerCase(); - }); - audio.playerProperties[nKey] = attributes[attr]; + let getPlayer = function() { + return $element + .find('md-wavesurfer-player') + .controller('mdWavesurferPlayer'); + }; + let setAutoPlay = function(forcePlay) { + let controller = getPlayer(); + if ( + controller && + (forcePlay || controller.surfer.isPlaying()) + ) { + controller.autoPlay = true; + } + }; + audio.setTrack = function(idx, forcePlay) { + if (audio.tracks.length > idx) { + if (audio.selectedIndex === idx) { + let ctrl = getPlayer(); + ctrl.surfer.playPause(); + } else { + setAutoPlay(forcePlay); + audio.currentTrack = audio.tracks[idx]; + audio.selectedIndex = idx; + } + } + }; + + audio.extraButtons = [ + { + icon: 'zmdi zmdi-skip-previous', + title: 'Previous', + action: function($event) { + if (audio.selectedIndex > 0) { + audio.setTrack(audio.selectedIndex - 1); + } + }, + class: '' + }, + { + icon: 'zmdi zmdi-skip-next', + title: 'Next', + action: function($event) { + if (audio.selectedIndex < audio.tracks.length - 1) { + audio.setTrack(audio.selectedIndex + 1); + } + }, + class: '' + } + ]; } - } - - var getPlayer = function(){ - return $element.find('md-wavesurfer-player').controller('mdWavesurferPlayer'); - }; - var setAutoPlay = function (forcePlay) { - var controller = getPlayer(); - if (controller && (forcePlay || controller.surfer.isPlaying())) { - controller.autoPlay = true; + ]); + + /** + * @ngdoc directive + * @name md-wavesurfer-audio + * + * Directive for playing a set of audio files. This directive is analogous to `
- wavesurfer.js by katspaugh is licensed under a Creative Commons Attribution 3.0 Unported License. + wavesurfer.js by katspaugh is licensed under a BSD-3-Clause License.
@@ -103,7 +103,7 @@

wavesurfer.js Angular Demo

diff --git a/example/annotation/annotations.json b/example/annotation/annotations.json index 21e444146..a2a0f05e5 100644 --- a/example/annotation/annotations.json +++ b/example/annotation/annotations.json @@ -1 +1,332 @@ -[{"start":1.1,"end":1.8,"data":{},"attributes":{"label": "abc","highlight":true}},{"start":2.7,"end":4.1,"data":{}},{"start":6,"end":8.2,"data":{}},{"start":8.6,"end":12.2,"data":{}},{"start":12.8,"end":16.2,"data":{}},{"start":"17.8","end":"18.6","data":{"note":"羅生門"}},{"start":"19.19","end":"20.5","data":{"note":"芥川龍之介"}},{"start":"22.5","end":"24.5","data":{"note":"ある日の暮方の事である。"}},{"start":"25.19","end":"26.3","data":{"note":"一人の下人げにんが、"}},{"start":"26.8","end":"28","data":{"note":"羅生門らしょうもんの下で"}},{"start":"28.3","end":"29.6","data":{"note":"雨やみを待っていた。"}},{"start":"30.8","end":"34.5","data":{"note":"広い門の下には、この男のほかに誰もいない。"}},{"start":"35.4","end":"39.8","data":{"note":"ただ、所々丹塗の剥げた、大きな円柱に、蟋蟀が一匹とまっている。"}},{"start":"40.3","end":"42.4","data":{"note":"蟋蟀が一匹とまっている。"}},{"start":43.5,"end":54.2,"data":{}},{"start":55,"end":57.7,"data":{}},{"start":59.7,"end":64.3,"data":{}},{"start":64.6,"end":67.8,"data":{}},{"start":68.7,"end":71.4,"data":{}},{"start":72.7,"end":81.6,"data":{}},{"start":81.9,"end":84.7,"data":{}},{"start":85.6,"end":89.3,"data":{}},{"start":89.6,"end":92.4,"data":{}},{"start":93.6,"end":94.3,"data":{}},{"start":94.5,"end":96.9,"data":{}},{"start":97.4,"end":98.3,"data":{}},{"start":98.6,"end":99.7,"data":{}},{"start":100.2,"end":106,"data":{}},{"start":106.3,"end":107.7,"data":{}},{"start":109,"end":113.2,"data":{}},{"start":113.6,"end":117.9,"data":{}},{"start":119.5,"end":123.7,"data":{}},{"start":124.5,"end":130.2,"data":{}},{"start":130.5,"end":131.6,"data":{}},{"start":132.8,"end":134.3,"data":{}},{"start":134.6,"end":136.4,"data":{}},{"start":137.1,"end":139.6,"data":{}},{"start":141.1,"end":146.1,"data":{}},{"start":147.9,"end":150.9,"data":{}},{"start":151.6,"end":157.7,"data":{}},{"start":158.2,"end":162.2,"data":{}},{"start":163.8,"end":168.4,"data":{}},{"start":168.7,"end":170.6,"data":{}},{"start":170.9,"end":172.2,"data":{}},{"start":172.5,"end":174.4,"data":{}},{"start":174.7,"end":175.6,"data":{}},{"start":176,"end":177.7,"data":{}},{"start":179.2,"end":183,"data":{}},{"start":183.5,"end":192.2,"data":{}},{"start":193,"end":196.9,"data":{}},{"start":197.9,"end":199.3,"data":{}},{"start":199.6,"end":203.1,"data":{}},{"start":203.7,"end":205,"data":{}},{"start":205.3,"end":211.8,"data":{}},{"start":212.7,"end":215.5,"data":{}},{"start":215.7,"end":216.6,"data":{}},{"start":217.1,"end":219.1,"data":{}},{"start":219.5,"end":221.9,"data":{}},{"start":222.5,"end":224,"data":{}},{"start":225.2,"end":232.1,"data":{}},{"start":233.5,"end":238.1,"data":{}},{"start":239.1,"end":240.7,"data":{}},{"start":241.1,"end":243.8,"data":{}},{"start":244.5,"end":246.9,"data":{}},{"start":247.1,"end":248.7,"data":{}},{"start":249,"end":251.5,"data":{}},{"start":251.7,"end":257.6,"data":{}},{"start":259.6,"end":261.7,"data":{}},{"start":262,"end":264.5,"data":{}},{"start":265.4,"end":272.4,"data":{}},{"start":272.9,"end":275.3,"data":{}},{"start":277.6,"end":282.4,"data":{}},{"start":283.1,"end":286.6,"data":{}},{"start":287,"end":288.7,"data":{}},{"start":289.4,"end":294.3,"data":{}},{"start":295.8,"end":297.3,"data":{}},{"start":298.5,"end":305,"data":{}},{"start":306,"end":306.8,"data":{}},{"start":307.2,"end":308,"data":{}},{"start":308.4,"end":312,"data":{}},{"start":312.6,"end":315.8,"data":{}},{"start":316.3,"end":318.8,"data":{}},{"start":319.3,"end":321,"data":{}},{"start":321.7,"end":324.1,"data":{}},{"start":324.8,"end":325.6,"data":{}},{"start":326,"end":329.7,"data":{}},{"start":331.8,"end":333.6,"data":{}},{"start":333.9,"end":335.9,"data":{}},{"start":336.5,"end":340.7,"data":{}},{"start":341.6,"end":344.5,"data":{}},{"start":344.8,"end":347.2,"data":{}},{"start":348.4,"end":350.8,"data":{}},{"start":351.4,"end":353.4,"data":{}},{"start":355.3,"end":357,"data":{}},{"start":357.3,"end":359,"data":{}},{"start":359.4,"end":361.3,"data":{}},{"start":361.6,"end":363.2,"data":{}},{"start":364.1,"end":370.3,"data":{}},{"start":370.6,"end":371.9,"data":{}},{"start":372.2,"end":374.3,"data":{}},{"start":375.1,"end":382.1,"data":{}},{"start":383,"end":386.1,"data":{}},{"start":387.1,"end":397.1,"data":{}},{"start":398.5,"end":406.6,"data":{}},{"start":406.9,"end":412.2,"data":{}},{"start":413.2,"end":415.2,"data":{}},{"start":415.6,"end":416.2,"data":{}},{"start":416.5,"end":418.9,"data":{}},{"start":419.9,"end":424.3,"data":{}},{"start":425.7,"end":431.2,"data":{}},{"start":432.4,"end":436,"data":{}},{"start":436.5,"end":437.1,"data":{}},{"start":437.3,"end":441.8,"data":{}},{"start":442.7,"end":452,"data":{}},{"start":453.4,"end":454.9,"data":{}},{"start":455.5,"end":458.8,"data":{}},{"start":459.3,"end":461.2,"data":{}},{"start":463,"end":470,"data":{}},{"start":470.7,"end":473.4,"data":{}},{"start":473.7,"end":478.9,"data":{}},{"start":480.4,"end":486.2,"data":{}},{"start":486.9,"end":490.6,"data":{}},{"start":490.9,"end":492.6,"data":{}},{"start":493.9,"end":495.7,"data":{}},{"start":496.2,"end":500.9,"data":{}},{"start":501.9,"end":505.5,"data":{}},{"start":506.5,"end":508.9,"data":{}},{"start":509.3,"end":509.9,"data":{}},{"start":510.2,"end":513.4,"data":{}},{"start":514.1,"end":521.5,"data":{}},{"start":522.6,"end":528.1,"data":{}},{"start":528.5,"end":534.6,"data":{}},{"start":536.7,"end":539.7,"data":{}},{"start":540.1,"end":541.1,"data":{}},{"start":542,"end":547.3,"data":{}},{"start":548.3,"end":554,"data":{}},{"start":556.2,"end":561.2,"data":{}},{"start":561.8,"end":567.8,"data":{}},{"start":568.2,"end":569.9,"data":{}},{"start":570.8,"end":575.2,"data":{}},{"start":575.6,"end":579.6,"data":{}},{"start":580.9,"end":582.9,"data":{}},{"start":583.2,"end":585.1,"data":{}},{"start":587.6,"end":594.9,"data":{}},{"start":596.3,"end":601.2,"data":{}},{"start":602.6,"end":616.4,"data":{}},{"start":617.4,"end":619.2,"data":{}},{"start":620.5,"end":623.4,"data":{}},{"start":625.1,"end":628.5,"data":{}},{"start":629,"end":630.4,"data":{}},{"start":630.9,"end":632.9,"data":{}},{"start":633.8,"end":635.2,"data":{}},{"start":635.7,"end":637.9,"data":{}},{"start":638.4,"end":639.8,"data":{}},{"start":640.9,"end":644.6,"data":{}},{"start":645.6,"end":648.7,"data":{}},{"start":649.5,"end":651.7,"data":{}},{"start":653.6,"end":664.1,"data":{}},{"start":664.7,"end":668.9,"data":{}},{"start":669.6,"end":678.7,"data":{}},{"start":681.9,"end":687,"data":{}},{"start":687.8,"end":693.3,"data":{}},{"start":694.4,"end":705.4,"data":{}},{"start":706.3,"end":711.2,"data":{}},{"start":711.6,"end":713.3,"data":{}},{"start":714.4,"end":719.6,"data":{}},{"start":720.3,"end":725.6,"data":{}},{"start":726.9,"end":729,"data":{}},{"start":730.2,"end":735.5,"data":{}},{"start":735.9,"end":736.2,"data":{}},{"start":737.4,"end":738.7,"data":{}},{"start":739.5,"end":740.1,"data":{}},{"start":740.3,"end":742.1,"data":{}},{"start":742.5,"end":745.8,"data":{}},{"start":746.4,"end":749.6,"data":{}},{"start":750,"end":752.7,"data":{}},{"start":753.9,"end":757.5,"data":{}},{"start":759.1,"end":761.2,"data":{}},{"start":761.8,"end":764.7,"data":{}},{"start":764.9,"end":766.6,"data":{}},{"start":767.5,"end":769.3,"data":{}},{"start":769.8,"end":771.9,"data":{}},{"start":773.4,"end":774.4,"data":{}},{"start":774.7,"end":775.2,"data":{}},{"start":775.6,"end":777,"data":{}},{"start":777.8,"end":780.4,"data":{}},{"start":780.7,"end":786.4,"data":{}},{"start":787.5,"end":789.1,"data":{}},{"start":790,"end":793.7,"data":{}},{"start":794.2,"end":797.3,"data":{}},{"start":797.5,"end":798.4,"data":{}},{"start":798.9,"end":801.1,"data":{}},{"start":802.1,"end":807.3,"data":{}},{"start":807.6,"end":810.9,"data":{}},{"start":812.1,"end":813.3,"data":{}},{"start":813.9,"end":816.5,"data":{}},{"start":817.1,"end":819,"data":{}},{"start":820.1,"end":821.3,"data":{}},{"start":821.9,"end":822.3,"data":{}},{"start":822.7,"end":828.4,"data":{}},{"start":828.7,"end":830.7,"data":{}},{"start":831.9,"end":837.6,"data":{}},{"start":839.3,"end":840,"data":{}},{"start":840.2,"end":842.7,"data":{}},{"start":843.7,"end":847.2,"data":{}},{"start":848.3,"end":852.7,"data":{}},{"start":853.9,"end":855.8,"data":{}},{"start":856.1,"end":857.6,"data":{}},{"start":858.2,"end":860.5,"data":{}},{"start":862.3,"end":863.2,"data":{}},{"start":863.5,"end":864.8,"data":{}},{"start":865.2,"end":866.5,"data":{}},{"start":867.1,"end":868.7,"data":{}},{"start":869.9,"end":871.4,"data":{}},{"start":871.7,"end":873,"data":{}},{"start":873.3,"end":875,"data":{}},{"start":876.1,"end":876.9,"data":{}},{"start":877.1,"end":880.6,"data":{}},{"start":881.1,"end":883.7,"data":{}},{"start":886.5,"end":887.5,"data":{}},{"start":888,"end":890.6,"data":{}},{"start":891.9,"end":892.6,"data":{}},{"start":892.9,"end":893.9,"data":{}},{"start":894.2,"end":895.8,"data":{}},{"start":896.2,"end":897.2,"data":{}},{"start":897.6,"end":899.5,"data":{}},{"start":901.9,"end":904.2,"data":{}},{"start":905.3,"end":907.3,"data":{}},{"start":908.5,"end":910.8,"data":{}},{"start":914.3,"end":917.5,"data":{}},{"start":918.1,"end":925.4,"data":{}},{"start":926.1,"end":930.9,"data":{}},{"start":931.2,"end":938.4,"data":{}},{"start":938.8,"end":939.8,"data":{}},{"start":940.2,"end":941.6,"data":{}},{"start":943.2,"end":944.4,"data":{}},{"start":945,"end":950.3,"data":{}},{"start":951.4,"end":953.7,"data":{}},{"start":954.2,"end":958,"data":{}},{"start":959.4,"end":962.8,"data":{}},{"start":963.9,"end":971.7,"data":{}},{"start":972.2,"end":976.9,"data":{}},{"start":978,"end":979,"data":{}},{"start":979.5,"end":988,"data":{}},{"start":989,"end":992.2,"data":{}},{"start":993,"end":995.1,"data":{}},{"start":995.6,"end":997.6,"data":{}},{"start":998.7,"end":1003.1,"data":{}},{"start":1004.2,"end":1006.7,"data":{}},{"start":1007,"end":1008.4,"data":{}},{"start":1008.8,"end":1011.9,"data":{}},{"start":1012.1,"end":1012.8,"data":{}},{"start":1013.4,"end":1017,"data":{}},{"start":1017.9,"end":1022.5,"data":{}},{"start":1024.2,"end":1026.4,"data":{}},{"start":1027.3,"end":1034.4,"data":{}},{"start":1035.3,"end":1038.9,"data":{}},{"start":1039.3,"end":1042.3,"data":{}},{"start":1043.5,"end":1048.4,"data":{}},{"start":1049.2,"end":1050.7,"data":{}},{"start":1051.2,"end":1053.6,"data":{}},{"start":1054.1,"end":1056.5,"data":{}},{"start":1056.9,"end":1059.1,"data":{}},{"start":1059.5,"end":1067,"data":{}},{"start":1067.3,"end":1069.1,"data":{}},{"start":1069.9,"end":1072.4,"data":{}},{"start":1072.9,"end":1076.5,"data":{}},{"start":1077,"end":1078.8,"data":{}},{"start":1080.6,"end":1081.4,"data":{}},{"start":1082.6,"end":1086.5,"data":{}},{"start":1087.6,"end":1088.4,"data":{}},{"start":1088.8,"end":1096.7,"data":{}},{"start":1097.6,"end":1104.9,"data":{}},{"start":1105.4,"end":1108.9,"data":{}},{"start":1109.6,"end":1114.6,"data":{}},{"start":1116,"end":1119.5,"data":{}},{"start":1120.4,"end":1127.8,"data":{}},{"start":1130.1,"end":1132.5,"data":{}},{"start":1133.1,"end":1134.2,"data":{}},{"start":1134.6,"end":1136.7,"data":{}},{"start":1137.3,"end":1139.1,"data":{}},{"start":1140.9,"end":1141.5,"data":{}},{"start":1141.9,"end":1143,"data":{}},{"start":1143.5,"end":1145.4,"data":{}},{"start":1146.1,"end":1148.6,"data":{}},{"start":1148.8,"end":1150.7,"data":{}},{"start":1151.9,"end":1153.1,"data":{}},{"start":1153.7,"end":1158.2,"data":{}},{"start":1159.5,"end":1160.6,"data":{}},{"start":1161.7,"end":1162.9,"data":{}},{"start":1163.3,"end":1165,"data":{}},{"start":1166.9,"end":1168.2,"data":{}},{"start":1168.5,"end":1169.5,"data":{}},{"start":1172.4,"end":1174.3,"data":{}}] \ No newline at end of file +[ + { + "start": 1.1, + "end": 1.8, + "data": {}, + "attributes": { "label": "abc", "highlight": true } + }, + { "start": 2.7, "end": 4.1, "data": {} }, + { "start": 6, "end": 8.2, "data": {} }, + { "start": 8.6, "end": 12.2, "data": {} }, + { "start": 12.8, "end": 16.2, "data": {} }, + { "start": "17.8", "end": "18.6", "data": { "note": "羅生門" } }, + { "start": "19.19", "end": "20.5", "data": { "note": "芥川龍之介" } }, + { + "start": "22.5", + "end": "24.5", + "data": { "note": "ある日の暮方の事である。" } + }, + { + "start": "25.19", + "end": "26.3", + "data": { "note": "一人の下人げにんが、" } + }, + { + "start": "26.8", + "end": "28", + "data": { "note": "羅生門らしょうもんの下で" } + }, + { + "start": "28.3", + "end": "29.6", + "data": { "note": "雨やみを待っていた。" } + }, + { + "start": "30.8", + "end": "34.5", + "data": { "note": "広い門の下には、この男のほかに誰もいない。" } + }, + { + "start": "35.4", + "end": "39.8", + "data": { + "note": + "ただ、所々丹塗の剥げた、大きな円柱に、蟋蟀が一匹とまっている。" + } + }, + { + "start": "40.3", + "end": "42.4", + "data": { "note": "蟋蟀が一匹とまっている。" } + }, + { "start": 43.5, "end": 54.2, "data": {} }, + { "start": 55, "end": 57.7, "data": {} }, + { "start": 59.7, "end": 64.3, "data": {} }, + { "start": 64.6, "end": 67.8, "data": {} }, + { "start": 68.7, "end": 71.4, "data": {} }, + { "start": 72.7, "end": 81.6, "data": {} }, + { "start": 81.9, "end": 84.7, "data": {} }, + { "start": 85.6, "end": 89.3, "data": {} }, + { "start": 89.6, "end": 92.4, "data": {} }, + { "start": 93.6, "end": 94.3, "data": {} }, + { "start": 94.5, "end": 96.9, "data": {} }, + { "start": 97.4, "end": 98.3, "data": {} }, + { "start": 98.6, "end": 99.7, "data": {} }, + { "start": 100.2, "end": 106, "data": {} }, + { "start": 106.3, "end": 107.7, "data": {} }, + { "start": 109, "end": 113.2, "data": {} }, + { "start": 113.6, "end": 117.9, "data": {} }, + { "start": 119.5, "end": 123.7, "data": {} }, + { "start": 124.5, "end": 130.2, "data": {} }, + { "start": 130.5, "end": 131.6, "data": {} }, + { "start": 132.8, "end": 134.3, "data": {} }, + { "start": 134.6, "end": 136.4, "data": {} }, + { "start": 137.1, "end": 139.6, "data": {} }, + { "start": 141.1, "end": 146.1, "data": {} }, + { "start": 147.9, "end": 150.9, "data": {} }, + { "start": 151.6, "end": 157.7, "data": {} }, + { "start": 158.2, "end": 162.2, "data": {} }, + { "start": 163.8, "end": 168.4, "data": {} }, + { "start": 168.7, "end": 170.6, "data": {} }, + { "start": 170.9, "end": 172.2, "data": {} }, + { "start": 172.5, "end": 174.4, "data": {} }, + { "start": 174.7, "end": 175.6, "data": {} }, + { "start": 176, "end": 177.7, "data": {} }, + { "start": 179.2, "end": 183, "data": {} }, + { "start": 183.5, "end": 192.2, "data": {} }, + { "start": 193, "end": 196.9, "data": {} }, + { "start": 197.9, "end": 199.3, "data": {} }, + { "start": 199.6, "end": 203.1, "data": {} }, + { "start": 203.7, "end": 205, "data": {} }, + { "start": 205.3, "end": 211.8, "data": {} }, + { "start": 212.7, "end": 215.5, "data": {} }, + { "start": 215.7, "end": 216.6, "data": {} }, + { "start": 217.1, "end": 219.1, "data": {} }, + { "start": 219.5, "end": 221.9, "data": {} }, + { "start": 222.5, "end": 224, "data": {} }, + { "start": 225.2, "end": 232.1, "data": {} }, + { "start": 233.5, "end": 238.1, "data": {} }, + { "start": 239.1, "end": 240.7, "data": {} }, + { "start": 241.1, "end": 243.8, "data": {} }, + { "start": 244.5, "end": 246.9, "data": {} }, + { "start": 247.1, "end": 248.7, "data": {} }, + { "start": 249, "end": 251.5, "data": {} }, + { "start": 251.7, "end": 257.6, "data": {} }, + { "start": 259.6, "end": 261.7, "data": {} }, + { "start": 262, "end": 264.5, "data": {} }, + { "start": 265.4, "end": 272.4, "data": {} }, + { "start": 272.9, "end": 275.3, "data": {} }, + { "start": 277.6, "end": 282.4, "data": {} }, + { "start": 283.1, "end": 286.6, "data": {} }, + { "start": 287, "end": 288.7, "data": {} }, + { "start": 289.4, "end": 294.3, "data": {} }, + { "start": 295.8, "end": 297.3, "data": {} }, + { "start": 298.5, "end": 305, "data": {} }, + { "start": 306, "end": 306.8, "data": {} }, + { "start": 307.2, "end": 308, "data": {} }, + { "start": 308.4, "end": 312, "data": {} }, + { "start": 312.6, "end": 315.8, "data": {} }, + { "start": 316.3, "end": 318.8, "data": {} }, + { "start": 319.3, "end": 321, "data": {} }, + { "start": 321.7, "end": 324.1, "data": {} }, + { "start": 324.8, "end": 325.6, "data": {} }, + { "start": 326, "end": 329.7, "data": {} }, + { "start": 331.8, "end": 333.6, "data": {} }, + { "start": 333.9, "end": 335.9, "data": {} }, + { "start": 336.5, "end": 340.7, "data": {} }, + { "start": 341.6, "end": 344.5, "data": {} }, + { "start": 344.8, "end": 347.2, "data": {} }, + { "start": 348.4, "end": 350.8, "data": {} }, + { "start": 351.4, "end": 353.4, "data": {} }, + { "start": 355.3, "end": 357, "data": {} }, + { "start": 357.3, "end": 359, "data": {} }, + { "start": 359.4, "end": 361.3, "data": {} }, + { "start": 361.6, "end": 363.2, "data": {} }, + { "start": 364.1, "end": 370.3, "data": {} }, + { "start": 370.6, "end": 371.9, "data": {} }, + { "start": 372.2, "end": 374.3, "data": {} }, + { "start": 375.1, "end": 382.1, "data": {} }, + { "start": 383, "end": 386.1, "data": {} }, + { "start": 387.1, "end": 397.1, "data": {} }, + { "start": 398.5, "end": 406.6, "data": {} }, + { "start": 406.9, "end": 412.2, "data": {} }, + { "start": 413.2, "end": 415.2, "data": {} }, + { "start": 415.6, "end": 416.2, "data": {} }, + { "start": 416.5, "end": 418.9, "data": {} }, + { "start": 419.9, "end": 424.3, "data": {} }, + { "start": 425.7, "end": 431.2, "data": {} }, + { "start": 432.4, "end": 436, "data": {} }, + { "start": 436.5, "end": 437.1, "data": {} }, + { "start": 437.3, "end": 441.8, "data": {} }, + { "start": 442.7, "end": 452, "data": {} }, + { "start": 453.4, "end": 454.9, "data": {} }, + { "start": 455.5, "end": 458.8, "data": {} }, + { "start": 459.3, "end": 461.2, "data": {} }, + { "start": 463, "end": 470, "data": {} }, + { "start": 470.7, "end": 473.4, "data": {} }, + { "start": 473.7, "end": 478.9, "data": {} }, + { "start": 480.4, "end": 486.2, "data": {} }, + { "start": 486.9, "end": 490.6, "data": {} }, + { "start": 490.9, "end": 492.6, "data": {} }, + { "start": 493.9, "end": 495.7, "data": {} }, + { "start": 496.2, "end": 500.9, "data": {} }, + { "start": 501.9, "end": 505.5, "data": {} }, + { "start": 506.5, "end": 508.9, "data": {} }, + { "start": 509.3, "end": 509.9, "data": {} }, + { "start": 510.2, "end": 513.4, "data": {} }, + { "start": 514.1, "end": 521.5, "data": {} }, + { "start": 522.6, "end": 528.1, "data": {} }, + { "start": 528.5, "end": 534.6, "data": {} }, + { "start": 536.7, "end": 539.7, "data": {} }, + { "start": 540.1, "end": 541.1, "data": {} }, + { "start": 542, "end": 547.3, "data": {} }, + { "start": 548.3, "end": 554, "data": {} }, + { "start": 556.2, "end": 561.2, "data": {} }, + { "start": 561.8, "end": 567.8, "data": {} }, + { "start": 568.2, "end": 569.9, "data": {} }, + { "start": 570.8, "end": 575.2, "data": {} }, + { "start": 575.6, "end": 579.6, "data": {} }, + { "start": 580.9, "end": 582.9, "data": {} }, + { "start": 583.2, "end": 585.1, "data": {} }, + { "start": 587.6, "end": 594.9, "data": {} }, + { "start": 596.3, "end": 601.2, "data": {} }, + { "start": 602.6, "end": 616.4, "data": {} }, + { "start": 617.4, "end": 619.2, "data": {} }, + { "start": 620.5, "end": 623.4, "data": {} }, + { "start": 625.1, "end": 628.5, "data": {} }, + { "start": 629, "end": 630.4, "data": {} }, + { "start": 630.9, "end": 632.9, "data": {} }, + { "start": 633.8, "end": 635.2, "data": {} }, + { "start": 635.7, "end": 637.9, "data": {} }, + { "start": 638.4, "end": 639.8, "data": {} }, + { "start": 640.9, "end": 644.6, "data": {} }, + { "start": 645.6, "end": 648.7, "data": {} }, + { "start": 649.5, "end": 651.7, "data": {} }, + { "start": 653.6, "end": 664.1, "data": {} }, + { "start": 664.7, "end": 668.9, "data": {} }, + { "start": 669.6, "end": 678.7, "data": {} }, + { "start": 681.9, "end": 687, "data": {} }, + { "start": 687.8, "end": 693.3, "data": {} }, + { "start": 694.4, "end": 705.4, "data": {} }, + { "start": 706.3, "end": 711.2, "data": {} }, + { "start": 711.6, "end": 713.3, "data": {} }, + { "start": 714.4, "end": 719.6, "data": {} }, + { "start": 720.3, "end": 725.6, "data": {} }, + { "start": 726.9, "end": 729, "data": {} }, + { "start": 730.2, "end": 735.5, "data": {} }, + { "start": 735.9, "end": 736.2, "data": {} }, + { "start": 737.4, "end": 738.7, "data": {} }, + { "start": 739.5, "end": 740.1, "data": {} }, + { "start": 740.3, "end": 742.1, "data": {} }, + { "start": 742.5, "end": 745.8, "data": {} }, + { "start": 746.4, "end": 749.6, "data": {} }, + { "start": 750, "end": 752.7, "data": {} }, + { "start": 753.9, "end": 757.5, "data": {} }, + { "start": 759.1, "end": 761.2, "data": {} }, + { "start": 761.8, "end": 764.7, "data": {} }, + { "start": 764.9, "end": 766.6, "data": {} }, + { "start": 767.5, "end": 769.3, "data": {} }, + { "start": 769.8, "end": 771.9, "data": {} }, + { "start": 773.4, "end": 774.4, "data": {} }, + { "start": 774.7, "end": 775.2, "data": {} }, + { "start": 775.6, "end": 777, "data": {} }, + { "start": 777.8, "end": 780.4, "data": {} }, + { "start": 780.7, "end": 786.4, "data": {} }, + { "start": 787.5, "end": 789.1, "data": {} }, + { "start": 790, "end": 793.7, "data": {} }, + { "start": 794.2, "end": 797.3, "data": {} }, + { "start": 797.5, "end": 798.4, "data": {} }, + { "start": 798.9, "end": 801.1, "data": {} }, + { "start": 802.1, "end": 807.3, "data": {} }, + { "start": 807.6, "end": 810.9, "data": {} }, + { "start": 812.1, "end": 813.3, "data": {} }, + { "start": 813.9, "end": 816.5, "data": {} }, + { "start": 817.1, "end": 819, "data": {} }, + { "start": 820.1, "end": 821.3, "data": {} }, + { "start": 821.9, "end": 822.3, "data": {} }, + { "start": 822.7, "end": 828.4, "data": {} }, + { "start": 828.7, "end": 830.7, "data": {} }, + { "start": 831.9, "end": 837.6, "data": {} }, + { "start": 839.3, "end": 840, "data": {} }, + { "start": 840.2, "end": 842.7, "data": {} }, + { "start": 843.7, "end": 847.2, "data": {} }, + { "start": 848.3, "end": 852.7, "data": {} }, + { "start": 853.9, "end": 855.8, "data": {} }, + { "start": 856.1, "end": 857.6, "data": {} }, + { "start": 858.2, "end": 860.5, "data": {} }, + { "start": 862.3, "end": 863.2, "data": {} }, + { "start": 863.5, "end": 864.8, "data": {} }, + { "start": 865.2, "end": 866.5, "data": {} }, + { "start": 867.1, "end": 868.7, "data": {} }, + { "start": 869.9, "end": 871.4, "data": {} }, + { "start": 871.7, "end": 873, "data": {} }, + { "start": 873.3, "end": 875, "data": {} }, + { "start": 876.1, "end": 876.9, "data": {} }, + { "start": 877.1, "end": 880.6, "data": {} }, + { "start": 881.1, "end": 883.7, "data": {} }, + { "start": 886.5, "end": 887.5, "data": {} }, + { "start": 888, "end": 890.6, "data": {} }, + { "start": 891.9, "end": 892.6, "data": {} }, + { "start": 892.9, "end": 893.9, "data": {} }, + { "start": 894.2, "end": 895.8, "data": {} }, + { "start": 896.2, "end": 897.2, "data": {} }, + { "start": 897.6, "end": 899.5, "data": {} }, + { "start": 901.9, "end": 904.2, "data": {} }, + { "start": 905.3, "end": 907.3, "data": {} }, + { "start": 908.5, "end": 910.8, "data": {} }, + { "start": 914.3, "end": 917.5, "data": {} }, + { "start": 918.1, "end": 925.4, "data": {} }, + { "start": 926.1, "end": 930.9, "data": {} }, + { "start": 931.2, "end": 938.4, "data": {} }, + { "start": 938.8, "end": 939.8, "data": {} }, + { "start": 940.2, "end": 941.6, "data": {} }, + { "start": 943.2, "end": 944.4, "data": {} }, + { "start": 945, "end": 950.3, "data": {} }, + { "start": 951.4, "end": 953.7, "data": {} }, + { "start": 954.2, "end": 958, "data": {} }, + { "start": 959.4, "end": 962.8, "data": {} }, + { "start": 963.9, "end": 971.7, "data": {} }, + { "start": 972.2, "end": 976.9, "data": {} }, + { "start": 978, "end": 979, "data": {} }, + { "start": 979.5, "end": 988, "data": {} }, + { "start": 989, "end": 992.2, "data": {} }, + { "start": 993, "end": 995.1, "data": {} }, + { "start": 995.6, "end": 997.6, "data": {} }, + { "start": 998.7, "end": 1003.1, "data": {} }, + { "start": 1004.2, "end": 1006.7, "data": {} }, + { "start": 1007, "end": 1008.4, "data": {} }, + { "start": 1008.8, "end": 1011.9, "data": {} }, + { "start": 1012.1, "end": 1012.8, "data": {} }, + { "start": 1013.4, "end": 1017, "data": {} }, + { "start": 1017.9, "end": 1022.5, "data": {} }, + { "start": 1024.2, "end": 1026.4, "data": {} }, + { "start": 1027.3, "end": 1034.4, "data": {} }, + { "start": 1035.3, "end": 1038.9, "data": {} }, + { "start": 1039.3, "end": 1042.3, "data": {} }, + { "start": 1043.5, "end": 1048.4, "data": {} }, + { "start": 1049.2, "end": 1050.7, "data": {} }, + { "start": 1051.2, "end": 1053.6, "data": {} }, + { "start": 1054.1, "end": 1056.5, "data": {} }, + { "start": 1056.9, "end": 1059.1, "data": {} }, + { "start": 1059.5, "end": 1067, "data": {} }, + { "start": 1067.3, "end": 1069.1, "data": {} }, + { "start": 1069.9, "end": 1072.4, "data": {} }, + { "start": 1072.9, "end": 1076.5, "data": {} }, + { "start": 1077, "end": 1078.8, "data": {} }, + { "start": 1080.6, "end": 1081.4, "data": {} }, + { "start": 1082.6, "end": 1086.5, "data": {} }, + { "start": 1087.6, "end": 1088.4, "data": {} }, + { "start": 1088.8, "end": 1096.7, "data": {} }, + { "start": 1097.6, "end": 1104.9, "data": {} }, + { "start": 1105.4, "end": 1108.9, "data": {} }, + { "start": 1109.6, "end": 1114.6, "data": {} }, + { "start": 1116, "end": 1119.5, "data": {} }, + { "start": 1120.4, "end": 1127.8, "data": {} }, + { "start": 1130.1, "end": 1132.5, "data": {} }, + { "start": 1133.1, "end": 1134.2, "data": {} }, + { "start": 1134.6, "end": 1136.7, "data": {} }, + { "start": 1137.3, "end": 1139.1, "data": {} }, + { "start": 1140.9, "end": 1141.5, "data": {} }, + { "start": 1141.9, "end": 1143, "data": {} }, + { "start": 1143.5, "end": 1145.4, "data": {} }, + { "start": 1146.1, "end": 1148.6, "data": {} }, + { "start": 1148.8, "end": 1150.7, "data": {} }, + { "start": 1151.9, "end": 1153.1, "data": {} }, + { "start": 1153.7, "end": 1158.2, "data": {} }, + { "start": 1159.5, "end": 1160.6, "data": {} }, + { "start": 1161.7, "end": 1162.9, "data": {} }, + { "start": 1163.3, "end": 1165, "data": {} }, + { "start": 1166.9, "end": 1168.2, "data": {} }, + { "start": 1168.5, "end": 1169.5, "data": {} }, + { "start": 1172.4, "end": 1174.3, "data": {} } +] diff --git a/example/annotation/app.js b/example/annotation/app.js index 01b1c3353..2fc1eea9f 100644 --- a/example/annotation/app.js +++ b/example/annotation/app.js @@ -1,39 +1,54 @@ /** * Create a WaveSurfer instance. */ -var wavesurfer = Object.create(WaveSurfer); +var wavesurfer; // eslint-disable-line no-var /** * Init & load. */ -document.addEventListener('DOMContentLoaded', function () { +document.addEventListener('DOMContentLoaded', function() { // Init wavesurfer - wavesurfer.init({ + wavesurfer = WaveSurfer.create({ container: '#waveform', height: 100, pixelRatio: 1, scrollParent: true, normalize: true, minimap: true, - backend: 'MediaElement' + backend: 'MediaElement', + plugins: [ + WaveSurfer.regions.create(), + WaveSurfer.minimap.create({ + height: 30, + waveColor: '#ddd', + progressColor: '#999', + cursorColor: '#999' + }), + WaveSurfer.timeline.create({ + container: '#wave-timeline' + }) + ] }); - wavesurfer.util.ajax({ - responseType: 'json', - url: 'rashomon.json' - }).on('success', function (data) { - wavesurfer.load( - 'http://www.archive.org/download/mshortworks_001_1202_librivox/msw001_03_rashomon_akutagawa_mt_64kb.mp3', - data - ); - }); + wavesurfer.util + .fetchFile({ + responseType: 'json', + url: 'rashomon.json' + }) + .on('success', function(data) { + wavesurfer.load( + 'http://www.archive.org/download/mshortworks_001_1202_librivox/msw001_03_rashomon_akutagawa_mt_64kb.mp3', + data + ); + }); /* Regions */ - wavesurfer.enableDragSelection({ - color: randomColor(0.1) - }); - wavesurfer.on('ready', function () { + wavesurfer.on('ready', function() { + wavesurfer.enableDragSelection({ + color: randomColor(0.1) + }); + if (localStorage.regions) { loadRegions(JSON.parse(localStorage.regions)); } else { @@ -43,16 +58,15 @@ document.addEventListener('DOMContentLoaded', function () { // wavesurfer.getDuration() // ) // ); - wavesurfer.util.ajax({ - responseType: 'json', - url: 'annotations.json' - }).on('success', function (data) { - loadRegions(data); - saveRegions(); - }); + fetch('annotations.json') + .then(r => r.json()) + .then(data => { + loadRegions(data); + saveRegions(); + }); } }); - wavesurfer.on('region-click', function (region, e) { + wavesurfer.on('region-click', function(region, e) { e.stopPropagation(); // Play on click, loop on shift click e.shiftKey ? region.playLoop() : region.play(); @@ -62,54 +76,45 @@ document.addEventListener('DOMContentLoaded', function () { wavesurfer.on('region-removed', saveRegions); wavesurfer.on('region-in', showNote); - wavesurfer.on('region-play', function (region) { - region.once('out', function () { + wavesurfer.on('region-play', function(region) { + region.once('out', function() { wavesurfer.play(region.start); wavesurfer.pause(); }); }); - - /* Minimap plugin */ - wavesurfer.initMinimap({ - height: 30, - waveColor: '#ddd', - progressColor: '#999', - cursorColor: '#999' - }); - - - /* Timeline plugin */ - wavesurfer.on('ready', function () { - var timeline = Object.create(WaveSurfer.Timeline); - timeline.init({ - wavesurfer: wavesurfer, - container: "#wave-timeline" - }); - }); - - /* Toggle play/pause buttons. */ - var playButton = document.querySelector('#play'); - var pauseButton = document.querySelector('#pause'); - wavesurfer.on('play', function () { + let playButton = document.querySelector('#play'); + let pauseButton = document.querySelector('#pause'); + wavesurfer.on('play', function() { playButton.style.display = 'none'; pauseButton.style.display = ''; }); - wavesurfer.on('pause', function () { + wavesurfer.on('pause', function() { playButton.style.display = ''; pauseButton.style.display = 'none'; }); -}); + document.querySelector( + '[data-action="delete-region"]' + ).addEventListener('click', function() { + let form = document.forms.edit; + let regionId = form.dataset.region; + if (regionId) { + wavesurfer.regions.list[regionId].remove(); + form.reset(); + } + }); +}); + /** * Save annotations to localStorage. */ function saveRegions() { localStorage.regions = JSON.stringify( - Object.keys(wavesurfer.regions.list).map(function (id) { - var region = wavesurfer.regions.list[id]; + Object.keys(wavesurfer.regions.list).map(function(id) { + let region = wavesurfer.regions.list[id]; return { start: region.start, end: region.end, @@ -120,64 +125,62 @@ function saveRegions() { ); } - /** * Load regions from localStorage. */ function loadRegions(regions) { - regions.forEach(function (region) { + regions.forEach(function(region) { region.color = randomColor(0.1); wavesurfer.addRegion(region); }); } - /** * Extract regions separated by silence. */ function extractRegions(peaks, duration) { // Silence params - var minValue = 0.0015; - var minSeconds = 0.25; + const minValue = 0.0015; + const minSeconds = 0.25; - var length = peaks.length; - var coef = duration / length; - var minLen = minSeconds / coef; + let length = peaks.length; + let coef = duration / length; + let minLen = minSeconds / coef; // Gather silence indeces - var silences = []; - Array.prototype.forEach.call(peaks, function (val, index) { + let silences = []; + Array.prototype.forEach.call(peaks, function(val, index) { if (Math.abs(val) <= minValue) { silences.push(index); } }); // Cluster silence values - var clusters = []; - silences.forEach(function (val, index) { + let clusters = []; + silences.forEach(function(val, index) { if (clusters.length && val == silences[index - 1] + 1) { clusters[clusters.length - 1].push(val); } else { - clusters.push([ val ]); + clusters.push([val]); } }); // Filter silence clusters by minimum length - var fClusters = clusters.filter(function (cluster) { + let fClusters = clusters.filter(function(cluster) { return cluster.length >= minLen; }); // Create regions on the edges of silences - var regions = fClusters.map(function (cluster, index) { - var next = fClusters[index + 1]; + let regions = fClusters.map(function(cluster, index) { + let next = fClusters[index + 1]; return { start: cluster[cluster.length - 1], - end: (next ? next[0] : length - 1) + end: next ? next[0] : length - 1 }; }); // Add an initial region if the audio doesn't start with silence - var firstCluster = fClusters[0]; + let firstCluster = fClusters[0]; if (firstCluster && firstCluster[0] != 0) { regions.unshift({ start: 0, @@ -186,12 +189,12 @@ function extractRegions(peaks, duration) { } // Filter regions by minimum length - var fRegions = regions.filter(function (reg) { + let fRegions = regions.filter(function(reg) { return reg.end - reg.start >= minLen; }); // Return time-based regions - return fRegions.map(function (reg) { + return fRegions.map(function(reg) { return { start: Math.round(reg.start * coef * 10) / 10, end: Math.round(reg.end * coef * 10) / 10 @@ -199,31 +202,32 @@ function extractRegions(peaks, duration) { }); } - /** * Random RGBA color. */ function randomColor(alpha) { - return 'rgba(' + [ - ~~(Math.random() * 255), - ~~(Math.random() * 255), - ~~(Math.random() * 255), - alpha || 1 - ] + ')'; - + return ( + 'rgba(' + + [ + ~~(Math.random() * 255), + ~~(Math.random() * 255), + ~~(Math.random() * 255), + alpha || 1 + ] + + ')' + ); } - /** * Edit annotation for a region. */ -function editAnnotation (region) { - var form = document.forms.edit; +function editAnnotation(region) { + let form = document.forms.edit; form.style.opacity = 1; - form.elements.start.value = Math.round(region.start * 10) / 10, - form.elements.end.value = Math.round(region.end * 10) / 10; + (form.elements.start.value = Math.round(region.start * 10) / 10), + (form.elements.end.value = Math.round(region.end * 10) / 10); form.elements.note.value = region.data.note || ''; - form.onsubmit = function (e) { + form.onsubmit = function(e) { e.preventDefault(); region.update({ start: form.elements.start.value, @@ -234,37 +238,20 @@ function editAnnotation (region) { }); form.style.opacity = 0; }; - form.onreset = function () { + form.onreset = function() { form.style.opacity = 0; form.dataset.region = null; }; form.dataset.region = region.id; } - /** * Display annotation. */ -function showNote (region) { +function showNote(region) { if (!showNote.el) { showNote.el = document.querySelector('#subtitle'); } showNote.el.textContent = region.data.note || '–'; } -/** - * Bind controls. - */ -GLOBAL_ACTIONS['delete-region'] = function () { - var form = document.forms.edit; - var regionId = form.dataset.region; - if (regionId) { - wavesurfer.regions.list[regionId].remove(); - form.reset(); - } -}; - -GLOBAL_ACTIONS['export'] = function () { - window.open('data:application/json;charset=utf-8,' + - encodeURIComponent(localStorage.regions)); -}; diff --git a/example/annotation/index.html b/example/annotation/index.html index 8eb95634a..92bd81e54 100644 --- a/example/annotation/index.html +++ b/example/annotation/index.html @@ -7,7 +7,7 @@ - + @@ -15,12 +15,12 @@ - + - - - + + + @@ -47,7 +47,7 @@

wavesurfer.js Annotations Tool

-
+

Click on a region to enter an annotation.
Shift-click plays a region in a loop. @@ -67,13 +67,6 @@

wavesurfer.js Annotations Tool

- -
- -
@@ -226,11 +219,11 @@
Region Events
diff --git a/example/trivia.js b/example/trivia.js index aad9430d2..cd8b7e077 100644 --- a/example/trivia.js +++ b/example/trivia.js @@ -1,42 +1,43 @@ -var GLOBAL_ACTIONS = { - 'play': function () { - wavesurfer.playPause(); +let ws = window.wavesurfer; + +var GLOBAL_ACTIONS = { // eslint-disable-line + play: function() { + window.wavesurfer.playPause(); }, - 'back': function () { - wavesurfer.skipBackward(); + back: function() { + window.wavesurfer.skipBackward(); }, - 'forth': function () { - wavesurfer.skipForward(); + forth: function() { + window.wavesurfer.skipForward(); }, - 'toggle-mute': function () { - wavesurfer.toggleMute(); + 'toggle-mute': function() { + window.wavesurfer.toggleMute(); } }; - // Bind actions to buttons and keypresses -document.addEventListener('DOMContentLoaded', function () { - document.addEventListener('keydown', function (e) { - var map = { - 32: 'play', // space - 37: 'back', // left - 39: 'forth' // right +document.addEventListener('DOMContentLoaded', function() { + document.addEventListener('keydown', function(e) { + let map = { + 32: 'play', // space + 37: 'back', // left + 39: 'forth' // right }; - var action = map[e.keyCode]; + let action = map[e.keyCode]; if (action in GLOBAL_ACTIONS) { - if (document == e.target || document.body == e.target) { + if (document == e.target || document.body == e.target || e.target.attributes["data-action"]) { e.preventDefault(); } GLOBAL_ACTIONS[action](e); } }); - [].forEach.call(document.querySelectorAll('[data-action]'), function (el) { - el.addEventListener('click', function (e) { - var action = e.currentTarget.dataset.action; + [].forEach.call(document.querySelectorAll('[data-action]'), function(el) { + el.addEventListener('click', function(e) { + let action = e.currentTarget.dataset.action; if (action in GLOBAL_ACTIONS) { e.preventDefault(); GLOBAL_ACTIONS[action](e); @@ -45,27 +46,29 @@ document.addEventListener('DOMContentLoaded', function () { }); }); - // Misc -document.addEventListener('DOMContentLoaded', function () { +document.addEventListener('DOMContentLoaded', function() { // Web Audio not supported if (!window.AudioContext && !window.webkitAudioContext) { - var demo = document.querySelector('#demo'); + let demo = document.querySelector('#demo'); if (demo) { demo.innerHTML = ''; } } - // Navbar links - var ul = document.querySelector('.nav-pills'); - var pills = ul.querySelectorAll('li'); - var active = pills[0]; + let ul = document.querySelector('.nav-pills'); + if ( !ul ) { + return; + } + + let pills = ul.querySelectorAll('li'); + let active = pills[0]; if (location.search) { - var first = location.search.split('&')[0]; - var link = ul.querySelector('a[href="' + first + '"]'); + let first = location.search.split('&')[0]; + let link = ul.querySelector('a[href="' + first + '"]'); if (link) { - active = link.parentNode; + active = link.parentNode; } } active && active.classList.add('active'); diff --git a/example/vertical/index.html b/example/vertical/index.html new file mode 100644 index 000000000..8af0572fb --- /dev/null +++ b/example/vertical/index.html @@ -0,0 +1,124 @@ + + + + + wavesurfer.js | Vertical waveform + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +

Vertical

+
+ +
+
+
+ +
+ +
+ +
+
+ +
+

How to enable vertical rendering

+ +
+ +

+ Simply set the vertical + option to true. +

+ +

+

var wavesurfer = WaveSurfer.create({
+    container: document.querySelector('#wave'),
+    vertical: true
+});
+
+

+ +

+ You probably want to set an explicit height + and use display: flex + on the container to ensure that the wave expands vertically: +

#waveform {
+    display: flex;
+    height: 400px;
+}
+
+

+ + +
+
+ + +
+ + + + + + diff --git a/example/vertical/main.js b/example/vertical/main.js new file mode 100644 index 000000000..b4835f0c6 --- /dev/null +++ b/example/vertical/main.js @@ -0,0 +1,26 @@ +'use strict'; + +// Create an instance +var wavesurfer; + +// Init & load audio file +document.addEventListener('DOMContentLoaded', function() { + // Init + wavesurfer = WaveSurfer.create({ + container: document.querySelector('#waveform'), + waveColor: '#A8DBA8', + progressColor: '#3B8686', + vertical: true + }); + + wavesurfer.on('error', function(e) { + console.warn(e); + }); + + // Load audio from URL + wavesurfer.load('../media/demo.wav'); + + document + .querySelector('[data-action="play"]') + .addEventListener('click', wavesurfer.playPause.bind(wavesurfer)); +}); diff --git a/example/video-annotation-contentEditable-regions/index.html b/example/video-annotation-contentEditable-regions/index.html new file mode 100755 index 000000000..e01223c98 --- /dev/null +++ b/example/video-annotation-contentEditable-regions/index.html @@ -0,0 +1,152 @@ + + + + + Video Annotation System + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Editable and removable regions

+
+

+ This example is based on Video Annotation System. + Instead of additional forms and buttons we do everything right in the wavefor + by using region plugin params: {contentEditable: true} and {removeButton: true}. +

+

+ Region plugin param {contentEditable: true} adds an editable div on top of each region. +

+

+ When editing is finished (onBlur) it fires event: +

{action: 'contentEdited', oldText, text}
+

+

You can handle it with: +

wavesurfer.on('region-updated', (region, event) => callback)

+

+ New text will be saved in region.data.text. +

+

+ To fill regions with text on initialization, provide them with a property data.text. +

+

+ The whole region will be looking like: +

{
+    start: 1, 
+    end: 2, 
+    data: {
+        text: 'some text'
+        }
+}
+

+

+ Region plugin param removeButton adds a little x to right bottom corner of each region. +

+

Demo

+
+ + + + + +
+ +
+ +
+ +
+
+ +
+ +
+
+

Output: text representation of regions

+
+ +
+
// Init
+var wavesurfer = WaveSurfer.create({
+    container: document.querySelector('#waveform'),
+    height: 100,
+    pixelRatio: 1,
+    minPxPerSec: 100,
+    scrollParent: true,
+    normalize: true,
+    splitChannels: false,
+    backend: 'MediaElement',
+    plugins: [
+        WaveSurfer.regions.create({
+            contentEditable: true,
+            dragSelection: true,
+            regions: [
+                {
+                    "start": 2.51,
+                    "end": 3.62,
+                    "data": {
+                        "text": "Hello down there " }
+                },
+                {
+                    "start": 3.67,
+                    "end": 4.72,
+                    "data": {
+                        "text": "on the good Earth"
+                    }
+                }
+            ]
+        }),
+        WaveSurfer.minimap.create({
+            height: 30,
+            waveColor: '#ddd',
+            progressColor: '#999'
+        }),
+        WaveSurfer.timeline.create({
+            container: '#wave-timeline'
+        }),
+        WaveSurfer.cursor.create()
+    ]
+});
+                    
+
+
+ + + + diff --git a/example/video-annotation-contentEditable-regions/main.js b/example/video-annotation-contentEditable-regions/main.js new file mode 100755 index 000000000..61a6cd187 --- /dev/null +++ b/example/video-annotation-contentEditable-regions/main.js @@ -0,0 +1,172 @@ +// Create an instance +var wavesurfer; + +const regionsInit = [ + { + "start": 2.509953716438861, + "end": 4.689913517967433, + "data": { + "text": "Hello down there on the good Earth" + } + }, + { + "start": 4.979908170464354, + "end": 7.339864652853083, + "data": { + "text": "and all the best from the International space station. " + } + }, + { + "start": 7.499907432877723, + "end": 10.659999631206684, + "data": { + "text": "I am expedition 57th commander Alexander Guest, " + } + }, + { + "start": 10.94, + "end": 12.250061772880404, + "data": { + "text": "and next to me we have " + } + }, + { + "start": 12.28977337650741, + "end": 13.67974774537196, + "data": { + "text": "Serena Auñón-Chancellor " + } + }, + { + "start": 13.709747192181986, + "end": 14.539731887259379, + "data": { + "text": "flight engineer" + } + }, + { + "start": 14.77972746173959, + "end": 15.69971049724706, + "data": { + "text": "from NASA" + } + } +]; + +async function getRegions(){ + const regionsSavedString = await localStorage.getItem('regions'); + const regionsSaved = JSON.parse(regionsSavedString); + const regions = regionsSaved || regionsInit; + return regions; +} + +async function initWavesurfer() { + const regions = await getRegions(); + + // Init + wavesurfer = WaveSurfer.create({ + container: document.querySelector('#waveform'), + height: 100, + pixelRatio: 1, + minPxPerSec: 100, + scrollParent: true, + normalize: true, + splitChannels: false, + backend: 'MediaElement', + plugins: [ + WaveSurfer.regions.create({ + contentEditable: true, + dragSelection: true, + removeButton: true, + regions + }), + WaveSurfer.minimap.create({ + height: 30, + waveColor: '#ddd', + progressColor: '#999' + }), + WaveSurfer.timeline.create({ + container: '#wave-timeline' + }), + WaveSurfer.cursor.create() + ] + }); + + // Load audio from existing media element + let mediaElt = document.querySelector('video'); + + wavesurfer.on('error', function(e) { + console.warn(e); + }); + + wavesurfer.load(mediaElt); + + wavesurfer.on('region-click', function(region, e) { + e.stopPropagation(); + // Play on click, loop on shift click + e.shiftKey ? region.playLoop() : region.play(); + }); + wavesurfer.on('region-update-end', saveRegions); + wavesurfer.on('region-updated', saveRegions); + wavesurfer.on('region-removed', saveRegions); + + /* Toggle play/pause buttons. */ + let playButton = document.querySelector('#play'); + let pauseButton = document.querySelector('#pause'); + + wavesurfer.on('play', function() { + playButton.style.display = 'none'; + pauseButton.style.display = 'block'; + }); + wavesurfer.on('pause', function() { + playButton.style.display = 'block'; + pauseButton.style.display = 'none'; + }); + + return {regions, wavesurfer}; +} + +// Init & load audio file +document.addEventListener('DOMContentLoaded', async function() { + const {regions} = await initWavesurfer(); + updateOutput(JSON.stringify(regions, null, 2)); +}); + +/** + * Save annotations to localStorage. + */ +function saveRegions() { + const regionsArray = Object.keys(wavesurfer.regions.list).map(id => { + const region = wavesurfer.regions.list[id]; + const {start, end, data} = region; + return {start, end, data}; + }).sort((a, b)=> (a.start - b.start)); + + localStorage.regions = JSON.stringify(regionsArray); + updateOutput(JSON.stringify(regionsArray, null, 2)); +} + +const outputEl = document.querySelector('#output'); + +/** + * Visible text representation of regions + */ +function updateOutput(text){ + outputEl.innerText = text; +} + +/** + * Random RGBA color. + */ +function randomColor(alpha) { + return ( + 'rgba(' + + [ + ~~(Math.random() * 255), + ~~(Math.random() * 255), + ~~(Math.random() * 255), + alpha || 1 + ] + + ')' + ); +} diff --git a/example/video-annotation/index.html b/example/video-annotation/index.html new file mode 100755 index 000000000..fb87eba94 --- /dev/null +++ b/example/video-annotation/index.html @@ -0,0 +1,108 @@ + + + + + Video Annotation System + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Video Annotation System

+
+ +
+ + + +

 

+ +
+ +
+ +
+ +
+
+

+ Click on a region to enter an annotation.
+

+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ + + + +
+ +
+ + +
+ +
+
Region edit
+ +
or
+ +
+
+
+ + diff --git a/example/video-annotation/main.js b/example/video-annotation/main.js new file mode 100755 index 000000000..731730934 --- /dev/null +++ b/example/video-annotation/main.js @@ -0,0 +1,261 @@ +// Create an instance +var wavesurfer; + +// Init & load audio file +document.addEventListener('DOMContentLoaded', function() { + // Init + wavesurfer = WaveSurfer.create({ + container: document.querySelector('#waveform'), + height: 100, + pixelRatio: 1, + minPxPerSec: 100, + scrollParent: true, + normalize: true, + splitChannels: false, + backend: 'MediaElement', + plugins: [ + WaveSurfer.regions.create(), + WaveSurfer.minimap.create({ + height: 30, + waveColor: '#ddd', + progressColor: '#999' + }), + WaveSurfer.timeline.create({ + container: '#wave-timeline' + }), + WaveSurfer.cursor.create() + ] + }); + + // Load audio from existing media element + let mediaElt = document.querySelector('video'); + + wavesurfer.on('error', function(e) { + console.warn(e); + }); + + wavesurfer.load(mediaElt); + + wavesurfer.on('ready', function() { + wavesurfer.enableDragSelection({ + color: randomColor(0.25) + }); + + wavesurfer.util + .fetchFile({ + responseType: 'json', + url: '../media/nasa.json' + }) + .on('success', function(data) { + loadRegions(data); + saveRegions(); + }); + }); + wavesurfer.on('region-click', function(region, e) { + e.stopPropagation(); + // Play on click, loop on shift click + e.shiftKey ? region.playLoop() : region.play(); + }); + wavesurfer.on('region-click', editAnnotation); + wavesurfer.on('region-update-end', saveRegions); + wavesurfer.on('region-updated', saveRegions); + wavesurfer.on('region-removed', saveRegions); + wavesurfer.on('region-in', showNote); + wavesurfer.on('region-out', hideNote); + + wavesurfer.on('region-play', function(region) { + region.once('out', function() { + wavesurfer.play(region.start); + wavesurfer.pause(); + }); + }); + + /* Toggle play/pause buttons. */ + let playButton = document.querySelector('#play'); + let pauseButton = document.querySelector('#pause'); + wavesurfer.on('play', function() { + playButton.style.display = 'none'; + pauseButton.style.display = 'block'; + }); + wavesurfer.on('pause', function() { + playButton.style.display = 'block'; + pauseButton.style.display = 'none'; + }); +}); + +/** + * Save annotations to localStorage. + */ +function saveRegions() { + localStorage.regions = JSON.stringify( + Object.keys(wavesurfer.regions.list).map(function(id) { + let region = wavesurfer.regions.list[id]; + return { + start: region.start, + end: region.end, + attributes: region.attributes, + data: region.data + }; + }) + ); +} + +/** + * Load regions from localStorage. + */ +function loadRegions(regions) { + regions.forEach(function(region) { + region.color = randomColor(0.25); + wavesurfer.addRegion(region); + }); +} + +/** + * Extract regions separated by silence. + */ +function extractRegions(peaks, duration) { + // Silence params + let minValue = 0.0015; + let minSeconds = 0.25; + + let length = peaks.length; + let coef = duration / length; + let minLen = minSeconds / coef; + + // Gather silence indeces + let silences = []; + Array.prototype.forEach.call(peaks, function(val, index) { + if (Math.abs(val) <= minValue) { + silences.push(index); + } + }); + + // Cluster silence values + let clusters = []; + silences.forEach(function(val, index) { + if (clusters.length && val == silences[index - 1] + 1) { + clusters[clusters.length - 1].push(val); + } else { + clusters.push([val]); + } + }); + + // Filter silence clusters by minimum length + let fClusters = clusters.filter(function(cluster) { + return cluster.length >= minLen; + }); + + // Create regions on the edges of silences + let regions = fClusters.map(function(cluster, index) { + let next = fClusters[index + 1]; + return { + start: cluster[cluster.length - 1], + end: next ? next[0] : length - 1 + }; + }); + + // Add an initial region if the audio doesn't start with silence + let firstCluster = fClusters[0]; + if (firstCluster && firstCluster[0] != 0) { + regions.unshift({ + start: 0, + end: firstCluster[firstCluster.length - 1] + }); + } + + // Filter regions by minimum length + let fRegions = regions.filter(function(reg) { + return reg.end - reg.start >= minLen; + }); + + // Return time-based regions + return fRegions.map(function(reg) { + return { + start: Math.round(reg.start * coef * 100) / 100, + end: Math.round(reg.end * coef * 100) / 100 + }; + }); +} + +/** + * Random RGBA color. + */ +function randomColor(alpha) { + return ( + 'rgba(' + + [ + ~~(Math.random() * 255), + ~~(Math.random() * 255), + ~~(Math.random() * 255), + alpha || 1 + ] + + ')' + ); +} + +/** + * Edit annotation for a region. + */ +function editAnnotation(region) { + let form = document.forms.edit; + form.style.opacity = 1; + (form.elements.start.value = Math.round(region.start * 100) / 100), + (form.elements.end.value = Math.round(region.end * 100) / 100); + form.elements.note.value = region.data.note || ''; + form.onsubmit = function(e) { + e.preventDefault(); + region.update({ + start: form.elements.start.value, + end: form.elements.end.value, + data: { + note: form.elements.note.value + } + }); + form.style.opacity = 0; + }; + form.onreset = function() { + form.style.opacity = 0; + form.dataset.region = null; + }; + form.dataset.region = region.id; +} + +/** + * Display annotation. + */ +function showNote(region) { + if (!showNote.el) { + showNote.el = document.querySelector('#subtitle'); + } + showNote.el.style.color = 'Red'; + showNote.el.style.fontSize = 'large'; + showNote.el.textContent = region.data.note || '–'; +} + +function hideNote(region) { + if (!hideNote.el) { + hideNote.el = document.querySelector('#subtitle'); + } + hideNote.el.style.color = 'Red'; + hideNote.el.style.fontSize = 'large'; + hideNote.el.textContent = '–'; +} + +/** + * Bind controls. + */ +window.GLOBAL_ACTIONS['delete-region'] = function() { + let form = document.forms.edit; + let regionId = form.dataset.region; + if (regionId) { + wavesurfer.regions.list[regionId].remove(); + form.reset(); + } +}; + +window.GLOBAL_ACTIONS['export'] = function() { + window.open( + 'data:application/json;charset=utf-8,' + + encodeURIComponent(localStorage.regions) + ); +}; diff --git a/example/video-element/index.html b/example/video-element/index.html index b03fa9c9c..d69cfb891 100644 --- a/example/video-element/index.html +++ b/example/video-element/index.html @@ -7,19 +7,23 @@ - + - - - + + + + + + + @@ -57,27 +61,24 @@

How to Enable the Fallback

You can choose to use an existing - html5 audio or video element manually. Simply set the backend + HTML5 audio or video element manually. Simply set the backend option to "MediaElement" and pass the media element as the first argument to wavesurfer.load(). Include an array of peaks as the second argument, the Web Audio API will not be used to render the peaks.

-

var wavesurfer = Object.create(WaveSurfer);
-
-wavesurfer.init({
-    container: document.querySelector('#waveform'),
-    waveColor: '#A8DBA8',
-    progressColor: '#3B8686',
-    backend: 'MediaElement'
+                
var wavesurfer = WaveSurfer.create({
+  container: document.querySelector('#waveform'),
+  waveColor: '#A8DBA8',
+  progressColor: '#3B8686',
+  backend: 'MediaElement'
 });
 
 // Load audio from existing media element
 var mediaElt = document.querySelector('video');
-wavesurfer.load(mediaElt);
 
-                
+wavesurfer.load(mediaElt);

Pre-rendered Peaks

@@ -87,7 +88,7 @@

Pre-rendered Peaks

you can pass them into the load function. This is optional–if you don't provide any peaks, - waserver.js will first draw a + wavesurfer.js will first draw a thin line instead of a waveform, then attempt to download the audio file via Ajax and decode it with Web Audio if available. @@ -112,11 +113,11 @@

Pre-rendered Peaks