added audioplayer contrib module

This commit is contained in:
Bachir Soussi Chiadmi
2017-11-25 16:05:32 +01:00
parent ff23c45e43
commit 0942e9a212
174 changed files with 21682 additions and 0 deletions
+14
View File
@@ -0,0 +1,14 @@
{
"presets": [
["es2015", "stage-0"]
],
"plugins": [
"transform-class-properties",
"add-module-exports"
],
"env": {
"test": {
"plugins": [ "istanbul" ]
}
}
}
+11
View File
@@ -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
+20
View File
@@ -0,0 +1,20 @@
{
"source": "./src",
"destination": "./doc",
"title": "wavesurfer.js documentation",
"styles": ["example/css/doc.css"],
"lint": true,
"builtinExternal": true,
"experimentalProposal": {
"classProperties": true,
"objectRestSpread": true,
"exportExtensions": true
},
"test": {
"type": "mocha",
"source": "spec"
},
"manual": {
"changelog": ["CHANGES.md"]
}
}
+279
View File
@@ -0,0 +1,279 @@
module.exports = {
"env": {
"browser": true,
"commonjs": true,
"es6": true
},
"extends": "eslint:recommended",
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 7,
"sourceType": "module",
"ecmaFeatures": {
"classes": true
},
},
"rules": {
"accessor-pairs": "error",
"array-bracket-spacing": "error",
"array-callback-return": "error",
"arrow-body-style": "error",
"arrow-parens": "off",
"arrow-spacing": [
"error",
{
"after": true,
"before": true
}
],
"block-scoped-var": "off",
"block-spacing": [
"error",
"always"
],
"brace-style": [
"error",
"1tbs",
{
"allowSingleLine": true
}
],
"callback-return": "off",
"camelcase": "error",
"class-methods-use-this": "off",
"comma-dangle": "error",
"comma-spacing": [
"error",
{
"after": true,
"before": false
}
],
"comma-style": [
"error",
"last"
],
"complexity": "error",
"computed-property-spacing": [
"error",
"never"
],
"consistent-return": "off",
"consistent-this": "off",
"curly": "error",
"default-case": "off",
"dot-location": "off",
"dot-notation": "error",
"eol-last": "error",
"eqeqeq": "off",
"func-call-spacing": "error",
"func-name-matching": "error",
"func-names": [
"error",
"never"
],
"func-style": "off",
"generator-star-spacing": "error",
"global-require": "error",
"guard-for-in": "off",
"handle-callback-err": "error",
"id-blacklist": "error",
"id-length": "off",
"id-match": "error",
"indent": [2, 4, { "SwitchCase": 1 }],
"init-declarations": "off",
"jsx-quotes": "error",
"key-spacing": "off",
"keyword-spacing": [
"error",
{
"after": true,
"before": true
}
],
"line-comment-position": "off",
"linebreak-style": [
"error",
"unix"
],
"lines-around-comment": "off",
"lines-around-directive": "error",
"max-depth": "error",
"max-len": "off",
"max-lines": "off",
"max-nested-callbacks": "error",
"max-params": "off",
"max-statements": "off",
"max-statements-per-line": "off",
"multiline-ternary": "off",
"new-cap": "error",
"new-parens": "off",
"newline-after-var": "off",
"newline-before-return": "off",
"newline-per-chained-call": "off",
"no-redeclare": "error",
"no-unused-vars": "off",
"no-console": "off",
"no-alert": "error",
"no-array-constructor": "error",
"no-bitwise": "off",
"no-caller": "error",
"no-catch-shadow": "error",
"no-confusing-arrow": [
"error",
{
"allowParens": true
}
],
"no-continue": "error",
"no-div-regex": "error",
"no-duplicate-imports": "error",
"no-else-return": "error",
"no-empty-function": "off",
"no-eq-null": "off",
"no-eval": "error",
"no-extend-native": "error",
"no-extra-bind": "error",
"no-extra-label": "error",
"no-extra-parens": "off",
"no-floating-decimal": "error",
"no-implicit-coercion": "off",
"no-implicit-globals": "error",
"no-implied-eval": "error",
"no-inline-comments": "off",
"no-inner-declarations": [
"error",
"functions"
],
"no-invalid-this": "off",
"no-iterator": "error",
"no-label-var": "error",
"no-labels": "error",
"no-lone-blocks": "error",
"no-lonely-if": "error",
"no-loop-func": "error",
"no-magic-numbers": "off",
"no-mixed-operators": "off",
"no-mixed-requires": "error",
"no-multi-spaces": "error",
"no-multi-str": "error",
"no-multiple-empty-lines": "off",
"no-native-reassign": "error",
"no-negated-condition": "off",
"no-negated-in-lhs": "error",
"no-nested-ternary": "error",
"no-new": "error",
"no-new-func": "error",
"no-new-object": "error",
"no-new-require": "error",
"no-new-wrappers": "error",
"no-octal-escape": "error",
"no-param-reassign": "off",
"no-path-concat": "error",
"no-plusplus": "off",
"no-process-env": "error",
"no-process-exit": "error",
"no-proto": "error",
"no-prototype-builtins": "error",
"no-restricted-globals": "error",
"no-restricted-imports": "error",
"no-restricted-modules": "error",
"no-restricted-properties": "error",
"no-restricted-syntax": "error",
"no-return-assign": "error",
"no-script-url": "error",
"no-self-compare": "error",
"no-sequences": "error",
"no-shadow": "off",
"no-shadow-restricted-names": "error",
"no-spaced-func": "error",
"no-sync": "error",
"no-tabs": "off",
"no-template-curly-in-string": "error",
"no-ternary": "off",
"no-throw-literal": "off",
"no-trailing-spaces": "error",
"no-undef-init": "error",
"no-undefined": "off",
"no-underscore-dangle": "off",
"no-unmodified-loop-condition": "error",
"no-unneeded-ternary": "error",
"no-use-before-define": "error",
"no-useless-call": "error",
"no-useless-computed-key": "error",
"no-useless-concat": "error",
"no-useless-constructor": "off",
"no-useless-escape": "error",
"no-useless-rename": "error",
"no-useless-return": "error",
"no-var": "error",
"no-void": "error",
"no-warning-comments": "off",
"no-whitespace-before-property": "error",
"no-with": "error",
"object-curly-newline": "off",
"object-curly-spacing": "off",
"object-property-newline": "off",
"object-shorthand": [
"error",
"methods",
{
avoidQuotes: true
}
],
"one-var": [
"error",
{
var: "never",
let: "never",
const: "never"
}
],
"one-var-declaration-per-line": "error",
"operator-assignment": "off",
"operator-linebreak": "off",
"padded-blocks": "off",
"prefer-arrow-callback": "error",
"prefer-const": "error",
"prefer-numeric-literals": "error",
"prefer-reflect": "off",
"prefer-rest-params": "error",
"prefer-spread": "error",
"prefer-template": "off",
"quote-props": "off",
"quotes": [
"error",
"single"
],
"radix": "off",
"require-jsdoc": "error",
"rest-spread-spacing": "error",
"semi": "error",
"semi-spacing": "error",
"sort-imports": "off",
"sort-keys": "off",
"sort-vars": "off",
"space-before-blocks": "off",
"space-before-function-paren": "off",
"space-in-parens": [
"error",
"never"
],
"space-infix-ops": "off",
"space-unary-ops": "off",
"spaced-comment": "off",
"strict": "error",
"symbol-description": "error",
"template-curly-spacing": "error",
"unicode-bom": [
"error",
"never"
],
"valid-jsdoc": "off",
"vars-on-top": "off",
"wrap-iife": "error",
"wrap-regex": "error",
"yield-star-spacing": "error",
"yoda": "off"
}
};
+26
View File
@@ -0,0 +1,26 @@
# Hey, thank you for testing and contributing to wavesurfer.js!
## 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/katspaugh/wavesurfer.js/issues) which desribe my problem.
- [ ] The issue I'm having is related to and caused by wavesurfer.js, not by other software (which maybe packages and uses wavesurfer incorrectly) In that case you should open the issue on the respective project pages.
## Please make sure you provide the following information (if applicable):
### Wavesurfer.js version(s):
### Browser 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)
### Use behaviour needed to reproduce the issue:
+21
View File
@@ -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:
+13
View File
@@ -0,0 +1,13 @@
dist/wavesurfer.dev.js
dist/plugin/*.js
!/dist/plugin/*.min.js
dist/**/*.map
/node_modules
/bower_components
/npm-debug.log
/.grunt
/coverage
/_SpecRunner.html
.DS_Store
.idea
.project
+7
View File
@@ -0,0 +1,7 @@
{
"disallowTrailingWhitespace": true,
"disallowMixedSpacesAndTabs": true,
"requirePaddingNewLinesAfterUseStrict": true,
"requireSpaceAfterComma": true,
"requireSpaceBetweenArguments": true
}
+4
View File
@@ -0,0 +1,4 @@
# exclude everything except dist and src
**/*
!dist/**
!src/**
+17
View File
@@ -0,0 +1,17 @@
sudo: required
dist: trusty
language: node_js
node_js:
- "6.9.4"
before_install:
- export CHROME_BIN=chromium-browser
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
install:
- npm prune
- npm install
- npm update
script:
- npm run test
- npm run build
- npm run doc
+26
View File
@@ -0,0 +1,26 @@
wavesurfer.js changelog
=======================
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)
------------------
- Add a 'waveform-ready' event, triggered when waveform is drawn with MediaElement backend (#736)
- Add a 'preload' parameter to load function to choose the preload HTML5 audio attribute value if MediaElement backend is choosen (#854)
1.2.2 (31.10.2016)
------------------
- Determistic 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)
1.2.1 (01.10.2016)
------------------
- Added changelog (#824)
- Correct AMD module name for plugins (#831)
- Fix to remove small gaps between regions (#834)
+1
View File
@@ -0,0 +1 @@
wavesurfer-js.org
+29
View File
@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2012-2017, 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.
+193
View File
@@ -0,0 +1,193 @@
# [wavesurfer.js](https://wavesurfer-js.org)
# Read below how to update to version 2!
[![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)
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)
See a [tutorial](https://wavesurfer-js.org/docs) and [examples](https://wavesurfer-js.org/examples) on [wavesurfer-js.org](https://wavesurfer-js.org).
## Browser support
wavesurfer.js works only in [modern browsers supporting Web Audio](http://caniuse.com/audio-api).
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.
## 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.
### 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).
## API in examples
Choose a container:
```html
<div id="waveform"></div>
```
Create an instance, passing the container selector and [options](https://wavesurfer-js.org/docs/options.html):
```javascript
var wavesurfer = WaveSurfer.create({
container: '#waveform',
waveColor: 'violet',
progressColor: 'purple'
});
```
Subscribe to some [events](https://wavesurfer-js.org/docs/events.html):
```javascript
wavesurfer.on('ready', function () {
wavesurfer.play();
});
```
Load an audio file from a URL:
```javascript
wavesurfer.load('example/media/demo.wav');
```
## Documentation
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/).
**Note on version 2**: 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 `<script>` tags) The code was also updated to ES6/ES7 syntax and is transpiled with babel and webpack. Read below how to update your code.
## Upgrading to version 2
The API has mostly stayed the same but there are some changes to consider:
1. **MultiCanvas renderer is now the default:** It provides all functionality of the Canvas renderer. Most likely you can simply remove the renderer option The Canvas renderer has been removed. (The `renderer` option still exists but wavesurfer expects it to be a renderer object, not merely a string.)
2. **Constructor functions instead of object constructors**
```javascript
// Old:
var wavesurfer = Object.create(WaveSurfer);
Wavesurfer.init(options);
// New:
var wavesurfer = WaveSurfer.create(options);
// ... or
var wavesurfer = new WaveSurfer(options);
wavesurfer.init();
```
3. **New plugin API:** Previously all plugins had their own initialisation API. The new API replaces all these different ways to do the same thing with one plugin API built into the core library. Plugins are now added as a property of the wavesurfer configuration object during creation. You don't need to initialise the plugins yourself anymore. Below is an example of initialising wavesurfer with plugins (Note the different ways to import the library at the top):
```javascript
// EITHER - accessing modules with <script> tags
var WaveSurfer = window.WaveSurfer;
var TimelinePlugin = window.WaveSurfer.timeline;
var MinimapPlugin = window.WaveSurfer.minimap;
// OR - importing as es6 module
import WaveSurfer from 'wavesurfer.js';
import TimelinePlugin from 'wavesurfer.js/dist/plugin/wavesurfer.timeline.min.js';
import MinimapPlugin from 'wavesurfer.js/dist/plugin/wavesurfer.minimap.min.js';
// OR - importing as require.js/commonjs modules
var WaveSurfer = require('wavesurfer.js');
var TimelinePlugin = require('wavesurfer.js/dist/plugin/wavesurfer.timeline.min.js');
var MinimapPlugin = require('wavesurfer.js/dist/plugin/wavesurfer.minimap.min.js');
// ... initialising waveform with plugins
var wavesurfer = WaveSurfer.create({
container: '#waveform',
waveColor: 'violet',
plugins: [
TimelinePlugin.create({
container: '#wave-timeline'
}),
MinimapPlugin.create()
]
});
```
**Note:** Read more about the plugin API in the documentation.
## Using with a module bundler
Wavesurfer can be used with a module system like this:
```javascript
// import
import WaveSurfer from 'wavesurfer.js';
// commonjs/requirejs
var WaveSurfer = require('wavesurfer.js');
// amd
define(['WaveSurfer'], function(WaveSurfer) {
// ... code
});
```
## Related projects
For a list of projects using wavesurfer.js, check out
[the projects page](https://wavesurfer-js.org/projects/).
## Development
[![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)
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.
Start development server for core library:
```
npm run start
```
Start development server for plugins:
```
npm run start:plugins
```
Build all the files. (generated files are placed in the `dist` directory.)
```
npm run build
```
Running tests only:
```
npm run test
```
Build documentation with esdoc (generated files are placed in the `doc` directory.)
```
npm run doc
```
## Editing documentation
The homepage and documentation files are maintained in the [`gh-pages` branch](https://github.com/katspaugh/wavesurfer.js/tree/gh-pages). Contributions to the documentation are especially welcome.
## Credits
Initial idea by [Alex Khokhulin](https://github.com/xoxulin). Many
thanks to
[the awesome contributors](https://github.com/katspaugh/wavesurfer.js/contributors)!
## License
[![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
[BSD 3-Clause License](https://opensource.org/licenses/BSD-3-Clause).
+14
View File
@@ -0,0 +1,14 @@
{
"name": "wavesurfer.js",
"version": "2.0.0-beta01",
"homepage": "http://wavesurfer-js.org",
"authors": [
"katspaugh <katspaugh@gmail.com>"
],
"description": "Navigable waveform built on Web Audio and Canvas",
"main": "dist/wavesurfer.js",
"keywords": [
"waveform", "audio", "music", "player"
],
"license": "BSD-3-Clause"
}
@@ -0,0 +1,45 @@
<html ng-app="mdWavesurferApp">
<head>
<title>wavesurfer.js | Angular Material</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href='http://fonts.googleapis.com/css?family=Roboto:400,500' rel='stylesheet' type='text/css'>
<link href='https://cdnjs.cloudflare.com/ajax/libs/material-design-iconic-font/2.2.0/css/material-design-iconic-font.min.css'
rel='stylesheet' type='text/css'>
<link href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.9.1/styles/github.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/angular-material/1.0.0-rc3/angular-material.min.css"
rel="stylesheet" type="text/css"/>
<link rel="stylesheet" href="main.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.7/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.7/angular-animate.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.7/angular-aria.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-material/1.0.0-rc3/angular-material.min.js"></script>
<script type="text/javascript" src="../../dist/wavesurfer.js"></script>
<script type="text/javascript" src="wavesurfer.directive.js"></script>
<script type="text/javascript" src="main.js"></script>
</head>
<body ng-controller="MainController">
<md-wavesurfer-audio player-wave-color="gray" player-progress-color="black"
player-backend="MediaElement">
<!-- This is analogous to HTML <source> element -->
<md-wavesurfer-source src="../media/demo.wav" title="czskamaarù Trou"></md-wavesurfer-source>
<md-wavesurfer-source src="../panner/media.wav" title="日本人の話し"></md-wavesurfer-source>
<md-wavesurfer-source src="../elan/transcripts/001z.mp3"
title="Рассказы о сновидениях"></md-wavesurfer-source>
<!-- <md-wavesurfer-source -->
<!-- src="http://download.wavetlan.com/SVV/Media/HTTP/MP3/Nero_SmartTrax/NeroSmartTrax_test1_MPEG2_Stereo_CBR_48kbps_22050Hz.mp3" -->
<!-- title="Remote: Nero SmartTrax- Test 1"></md-wavesurfer-source> -->
<!-- <md-wavesurfer-source -->
<!-- src="http://download.wavetlan.com/SVV/Media/HTTP/MP3/Nero_SmartTrax/NeroSmartTrax_test2_MPEG1_Mono_CBR_64kbps_44100Hz.mp3" -->
<!-- title="Remote: Nero SmartTrax- Test 2"></md-wavesurfer-source> -->
<!-- Long list, using previously loaded files, need to test container.-->
<md-wavesurfer-source ng-repeat="item in longList" src="{{item.url}}"
title="{{item.title}}"></md-wavesurfer-source>
</md-wavesurfer-audio>
</body>
</html>
@@ -0,0 +1,35 @@
.text-center {
text-align: center !important;
}
.text-left {
text-align: left !important;;
}
.text-right {
text-align: right !important;;
}
.text-justify {
text-align: justify !important;;
}
md-icon[md-font-icon] {
font-size: 24px;
}
md-toolbar.md-toolbar-sm .md-toolbar-tools {
max-height: 32px;
padding: 6px;
font-size: 16px;
}
md-toolbar.md-toolbar-sm {
min-height: 24px;
}
.md-player-controls .md-button {
min-width: 44px;
}
+21
View File
@@ -0,0 +1,21 @@
/**
* 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())]
});
}
}])
})();
@@ -0,0 +1,30 @@
<md-card layout="column">
<div ng-transclude=""></div>
<md-toolbar class="md-toolbar-sm">
<div class="text-center" class="md-toolbar-tools">
<h2 style="font-size: 24px;">Audio Player</h2>
</div>
</md-toolbar>
<md-list layout="column">
<md-virtual-repeat-container style="height: 250px">
<md-list-item md-virtual-repeat="track in audio.tracks" md-start-index="audio.selectedIndex"
ng-click="audio.setTrack($index, true)">
<p>
{{$index+1}}. {{track.title}}
</p>
<div class="text-right md-secondary">
{{track.duration | mdWavesurferTimeFormat}}
</div>
<md-divider ng-if="$index < audio.tracks.length-1"></md-divider>
</md-list-item>
</md-virtual-repeat-container>
</md-list>
<md-wavesurfer-player
url="{{audio.currentTrack.src}}#{{audio.selectedIndex}}"
title="{{audio.selectedIndex+1}}. {{audio.currentTrack.title}}"
extra-buttons="audio.extraButtons"
properties="audio.playerProperties"
>
</md-wavesurfer-player>
</md-card>
@@ -0,0 +1,70 @@
<div layout="column" class="audioPlayerWrapper">
<md-toolbar class="md-toolbar-sm {{control.themeClass}} {{control.toolbarClass}} " style="color: #333333; background:
none;">
<div class="md-toolbar-tools" layout="row">
<div flex="initial" layout-align="center start">
{{control.currentTime | mdWavesurferTimeFormat}}
</div>
<div flex="grow" class="text-center">
{{control.title}}
</div>
<div flex="initial" layout-align="center end" class="text-right">
{{control.surfer.getDuration() | mdWavesurferTimeFormat}}
</div>
</div>
</md-toolbar>
<md-divider></md-divider>
<md-content>
<div class="waveSurferWave"></div>
</md-content>
<md-divider md-inset></md-divider>
<md-toolbar class="{{control.themeClass}} {{control.toolbarClass}} md-player-controls">
<div layout="row" class="md-toolbar-tools">
<div flex="initial" layout-align="center start" ng-show="control.extraButtons.length">
<md-button ng-click="btn.action()"
ng-repeat="btn in control.extraButtons" class="{{control.themeClass}} {{btn.class}}">
<md-tooltip>
{{btn.title}}
</md-tooltip>
<md-icon md-font-icon="{{btn.icon}}"></md-icon>
</md-button>
</div>
<div flex layout="row" layout-align="center center">
<md-button ng-click="control.surfer.skipBackward()" type="button"
ng-disabled="!control.surfer.isPlaying()">
<md-tooltip>
Rewind
</md-tooltip>
<md-icon md-font-icon="zmdi zmdi-fast-rewind"></md-icon>
</md-button>
<md-button ng-disabled="!control.isReady" type="button"
ng-click="control.surfer.playPause()">
<md-tooltip>
{{control.surfer.isPlaying() ? 'Pause' : 'Play'}}
</md-tooltip>
<md-icon ng-show="control.surfer.isPlaying()" md-font-icon="zmdi zmdi-pause"></md-icon>
<md-icon ng-show="!control.surfer.isPlaying()" md-font-icon="zmdi zmdi-play"></md-icon>
</md-button>
<md-button type="button" ng-click="control.surfer.skipForward()"
ng-disabled="!control.surfer.isPlaying()">
<md-tooltip>
Skip forward
</md-tooltip>
<md-icon md-font-icon="zmdi zmdi-fast-forward"></md-icon>
</md-button>
</div>
<div flex="initial" layout-align="center end" >
<md-button type="button" ng-click="control.toggleMute()" ng-disabled="!control.surfer.isPlaying()">
<md-tooltip>
Toggle mute
</md-tooltip>
<md-icon md-font-icon="zmdi zmdi-volume-off" ng-show="control.isMute"></md-icon>
<md-icon md-font-icon="zmdi zmdi-volume-up" ng-show="!control.isMute"></md-icon>
</md-button>
</div>
</div>
</md-toolbar>
</div>
@@ -0,0 +1,415 @@
/**
* 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);
}
if (!audio.currentTrack) {
audio.currentTrack = audio.tracks[audio.selectedIndex];
}
};
//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];
}
}
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;
}
};
audio.setTrack = function (idx, forcePlay) {
if (audio.tracks.length > idx) {
if (audio.selectedIndex === idx) {
var 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: ''
}];
}
]);
/**
* @ngdoc directive
* @name md-wavesurfer-audio
*
* Directive for playing a set of audio files. This directive is analogous to `<audio>` HTML tag.
* The audio files, should be specified using the `md-wavesurfer-source`
*
* WaveSurfer properties can be passed in using the prefix : player-* for attributes, e.g. `player-wave-color` is
* equivalent to WaveSurfer's waveColor option.
*
* Must be used as an element.
*
* @usage
* ```html
* <md-wavesurfer-audio player-wave-color="gray" player-progress-color="black" player-backend="MediaElement">
* <md-wavesurfer-source src="source1" title="Title-1"></md-wavesurfer-source>
* <md-wavesurfer-source src="source2" title="Title-2"></md-wavesurfer-source>
* <md-wavesurfer-source src="source3" title="Title-3"></md-wavesurfer-source>
* ...
* <md-wavesurfer-source src="sourceN" title="Рассказы о сновидениях"></md-wavesurfer-source>
* </md-wavesurfer-audio>
* ```
*
* @param string player-* specifies WaveSurfer properties.
*
*/
app.directive('mdWavesurferAudio', [
function () {
return {
restrict: 'E',
templateUrl: 'md-player-audio.partial.html',
transclude: true,
controller: 'mdWavesurferAudioController',
controllerAs: 'audio'
};
}
]);
/**
* @ngdoc directive
*
* @name md-wavesurfer-source
*
* This directive is used within the `md-wavesurfer-audio` directive to specify an audio file source, it is
* synonymous to `<source>` tag in HTML
*
* The directive cannot be used as standalone.
*
* @usage
*
* ```html
* <md-wavesurfer-source src="source3" title="Title-3" album-art="Album-Art-Url" duration=""></md-wavesurfer-source>
* ```
* @param String src the URL to the audio file, this is required.
* @param String title track title
* @param String album-art the album art URL
* @param Number duration the length of the audio file in seconds, will be auto-detected if not specified.
*
*/
app.directive('mdWavesurferSource', ['mdWavesurferUtils',
function (mdWavesurferUtils) {
return {
restrict: 'E',
require: '^mdWavesurferAudio',
scope: {
src: '@',
albumArt: '@',
title: '@',
duration: '='
},
link: function (scope, element, attrs, audio) {
audio.addTrack(scope);
if (!scope.duration) {
mdWavesurferUtils.getLength(scope.src).then(function (dur) {
scope.duration = dur;
}, function (e) {
scope.duration = 0;
console.log('Failed to get audio length, reason: ', e.message);
});
}
element.on('$destroy', function () {
audio.removeTrack(audio);
});
}
};
}
]);
app.controller('mdWavesurferPlayerController', ['$element', '$scope', '$attrs', '$interval', '$mdTheming',
function ($element, $scope, attributes, $interval, $mdTheme) {
var control = this, timeInterval;
control.themeClass = "md-" + $mdTheme.defaultTheme() + "-theme";
control.isReady = false;
control.surfer = null;
control.toggleMute = function () {
if (control.surfer) {
control.surfer.toggleMute();
control.isMute = !control.isMute;
}
};
var initWaveSurfer = function () {
control.isReady = false;
control.currentTime = 0;
if (!control.surfer) {
var options = {
container: $element[0].querySelector('.waveSurferWave')
}, defaults = {
scrollParent: true,
waveColor: 'violet',
progressColor: 'purple'
};
options = angular.extend(defaults, attributes, (control.properties || {}), options);
control.surfer = WaveSurfer.create(options);
control.surfer.on('ready', function () {
control.isReady = true;
if (control.autoPlay) {
control.surfer.play();
}
$scope.$apply();
});
control.surfer.on('pause', function () {
stopInterval();
});
control.surfer.on('finish', function () {
stopInterval();
});
control.surfer.on('play', function () {
startInterval();
});
}
control.title = control.title || control.src.split('/').pop();
control.surfer.load(control.src);
};
var startInterval = function () {
timeInterval = $interval(function () {
control.currentTime = control.isReady ? control.surfer.getCurrentTime() : 0;
}, 1000);
}, stopInterval = function () {
$interval.cancel(timeInterval);
};
initWaveSurfer();
$scope.$watch('control.src', function (src1, src2) {
if (src1 != src2) {
initWaveSurfer();
}
});
$element.on('$destroy', function () {
if (control.surfer) {
control.surfer.destroy();
}
stopInterval();
});
$scope.$watch(function () {
var div = $element[0].querySelector('.audioPlayerWrapper');
return div ? div.offsetWidth : 0;
}, function (width) {
if (width < 1) {
//hidden
control.surfer.pause();
}
});
}
]);
/**
* @ngdoc directive
*
* @name md-wavesurfer-player
*
* @usage
* This directive can be used as a stand-alone directive to display Audio WaveSurfer with a few controls, by default
* this will only display play/pause, fast-forward, rewind and mute toggle buttons, however, you can add extra
* buttons using the `extra-buttons` parameters.
*
* ```html
* <md-wavesurfer-player url="trackUrl" title="Track Title"
* extra-buttons="extraButtons" properties="properties">
* </md-wavesurfer-player>
* ```
*
* @param {string} url the URL of the audio file
* @param {string} title title of the audio track
* @param {object} properties an object specifying init options for WaveSurfer
* @param {boolean} auto-play specifies if the player should start as soon as it's loaded.
* @param {object[]} extra-buttons a list of extra buttons to add to the control panel
* each button should be an object with the following properties:
* {
* title: "button title"
* action: "call back to call when button is clicked, executed in parent scope",
* icon: "md-font-icon parameter for the button"
* class: "extra classes to add to the button."
* }
*
* Every other attribute passed to this directive is assumed to a WaveSurver init parameter.
*/
app.directive('mdWavesurferPlayer', function () {
return {
restrict: 'E',
templateUrl: 'md-player.partial.html',
scope: {
src: '@url',
title: '@',
extraButtons: '=',
toolbarClass: '@',
autoPlay: '=',
properties: '='
},
controller: 'mdWavesurferPlayerController',
controllerAs: 'control',
bindToController: true
};
});
;
})();
+63
View File
@@ -0,0 +1,63 @@
var app = angular.module('ngWavesurfer', []);
app.directive('ngWavesurfer', function () {
return {
restrict: 'E',
link: function ($scope, $element, $attrs) {
$element.css('display', 'block');
var options = angular.extend({ container: $element[0] }, $attrs);
var wavesurfer = WaveSurfer.create(options);
if ($attrs.url) {
wavesurfer.load($attrs.url, $attrs.data || null);
}
$scope.$emit('wavesurferInit', wavesurfer);
}
};
});
app.controller('PlaylistController', function ($scope) {
var activeUrl = null;
$scope.paused = true;
$scope.$on('wavesurferInit', function (e, wavesurfer) {
$scope.wavesurfer = wavesurfer;
$scope.wavesurfer.on('play', function () {
$scope.paused = false;
});
$scope.wavesurfer.on('pause', function () {
$scope.paused = true;
});
$scope.wavesurfer.on('finish', function () {
$scope.paused = true;
$scope.wavesurfer.seekTo(0);
$scope.$apply();
});
});
$scope.play = function (url) {
if (!$scope.wavesurfer) {
return;
}
activeUrl = url;
$scope.wavesurfer.once('ready', function () {
$scope.wavesurfer.play();
$scope.$apply();
});
$scope.wavesurfer.load(activeUrl);
};
$scope.isPlaying = function (url) {
return url == activeUrl;
};
});
@@ -0,0 +1,120 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>wavesurfer.js | Angular</title>
<link href="data:image/gif;" rel="icon" type="image/x-icon" />
<!-- Bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/ribbon.css" />
<link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
<!-- AngularJS -->
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<!-- wavesurfer.js -->
<script src="../../dist/wavesurfer.js"></script>
<!-- App -->
<script src="app.js"></script>
</head>
<body ng-app="ngWavesurfer" ng-controller="PlaylistController">
<div class="container">
<div class="header">
<ul class="nav nav-pills pull-right">
<li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
</ul>
<h1 itemprop="name">wavesurfer.js Angular Demo</h1>
</div>
<div id="demo">
<div class="row" style="margin: 30px 0">
<div class="col-sm-10">
<ng-wavesurfer url="../media/demo.wav" wave-color="#337ab7" progress-color="#23527c" height="64">
</ng-wavesurfer>
</div>
<div class="col-sm-2">
<button class="btn btn-success btn-block" ng-click="wavesurfer.playPause()">
<span id="play" ng-show="paused">
<i class="glyphicon glyphicon-play"></i>
Play
</span>
<span id="pause" ng-show="!paused">
<i class="glyphicon glyphicon-pause"></i>
Pause
</span>
</button>
</div>
</div>
<div class="list-group" id="playlist">
<a href=""
ng-class="{ 'list-group-item': true, active: isPlaying('../media/demo.wav') }"
ng-click="play('../media/demo.wav')">
<i class="glyphicon glyphicon-play"></i>
czskamaarù Trou
<span class="badge">0:21</span>
</a>
<a href=""
ng-class="{ 'list-group-item': true, active: isPlaying('../panner/media.wav') }"
ng-click="play('../panner/media.wav')">
<i class="glyphicon glyphicon-play"></i>
日本人の話し
<span class="badge">1:04</span>
</a>
<a href=""
ng-class="{ 'list-group-item': true, active: isPlaying('../elan/transcripts/001z.mp3') }"
ng-click="play('../elan/transcripts/001z.mp3')">
<i class="glyphicon glyphicon-play"></i>
Рассказы о сновидениях
<span class="badge badge-info">1:26</span>
</a>
</div>
</div>
<div class="footer row">
<div class="col-sm-12">
</div>
<div class="col-sm-7">
<span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
</div>
<div class="col-sm-5">
<p>
Audio sources:<br />
<a rel="nofollow" href="http://www.jamendo.com/en/track/661578/trou"><b>Trou</b> <span class="muted">by</span>&nbsp;<b>czskamaarù</b></a>,
<a rel="nofollow" href="http://spokencorpora.ru/">spokencorpora.ru</a>
</p>
</div>
</div>
</div>
<div class="github-fork-ribbon-wrapper right">
<div class="github-fork-ribbon">
<a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
</div>
</div>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-50026819-1', 'wavesurfer.fm');
ga('send', 'pageview');
</script>
</body>
</html>
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
region.wavesurfer-region:before {
content: attr(data-region-label);
}
region.wavesurfer-region[data-region-highlight] {
border: 1px solid rgb(86, 180, 239);
box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.05) inset, 0px 0px 8px rgba(82, 168, 236, 0.6);
}
@@ -0,0 +1,264 @@
/**
* Create a WaveSurfer instance.
*/
var wavesurfer;
/**
* Init & load.
*/
document.addEventListener('DOMContentLoaded', function () {
// Init wavesurfer
wavesurfer = WaveSurfer.create({
container: '#waveform',
height: 100,
pixelRatio: 1,
scrollParent: true,
normalize: true,
minimap: true,
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
);
});
/* Regions */
wavesurfer.on('ready', function () {
wavesurfer.enableDragSelection({
color: randomColor(0.1)
});
if (localStorage.regions) {
loadRegions(JSON.parse(localStorage.regions));
} else {
// loadRegions(
// extractRegions(
// wavesurfer.backend.getPeaks(512),
// wavesurfer.getDuration()
// )
// );
wavesurfer.util.ajax({
responseType: 'json',
url: 'annotations.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-updated', saveRegions);
wavesurfer.on('region-removed', saveRegions);
wavesurfer.on('region-in', showNote);
wavesurfer.on('region-play', function (region) {
region.once('out', function () {
wavesurfer.play(region.start);
wavesurfer.pause();
});
});
/* Toggle play/pause buttons. */
var playButton = document.querySelector('#play');
var pauseButton = document.querySelector('#pause');
wavesurfer.on('play', function () {
playButton.style.display = 'none';
pauseButton.style.display = '';
});
wavesurfer.on('pause', function () {
playButton.style.display = '';
pauseButton.style.display = 'none';
});
});
/**
* Save annotations to localStorage.
*/
function saveRegions() {
localStorage.regions = JSON.stringify(
Object.keys(wavesurfer.regions.list).map(function (id) {
var 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.1);
wavesurfer.addRegion(region);
});
}
/**
* Extract regions separated by silence.
*/
function extractRegions(peaks, duration) {
// Silence params
var minValue = 0.0015;
var minSeconds = 0.25;
var length = peaks.length;
var coef = duration / length;
var minLen = minSeconds / coef;
// Gather silence indeces
var 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) {
if (clusters.length && val == silences[index - 1] + 1) {
clusters[clusters.length - 1].push(val);
} else {
clusters.push([ val ]);
}
});
// Filter silence clusters by minimum length
var 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];
return {
start: cluster[cluster.length - 1],
end: (next ? next[0] : length - 1)
};
});
// Add an initial region if the audio doesn't start with silence
var firstCluster = fClusters[0];
if (firstCluster && firstCluster[0] != 0) {
regions.unshift({
start: 0,
end: firstCluster[firstCluster.length - 1]
});
}
// Filter regions by minimum length
var 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 * 10) / 10,
end: Math.round(reg.end * coef * 10) / 10
};
});
}
/**
* 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) {
var 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.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.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));
};
@@ -0,0 +1,260 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>wavesurfer.js | Annotation tool</title>
<link href="data:image/gif;" rel="icon" type="image/x-icon" />
<!-- Bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/ribbon.css" />
<link rel="stylesheet" href="app.css" />
<link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
<!-- wavesurfer.js -->
<script src="../../dist/wavesurfer.js"></script>
<!-- plugins -->
<script src="../../dist/plugin/wavesurfer.timeline.js"></script>
<script src="../../dist/plugin/wavesurfer.regions.js"></script>
<script src="../../dist/plugin/wavesurfer.minimap.js"></script>
<!-- App -->
<script src="../trivia.js"></script>
<script src="app.js"></script>
</head>
<body itemscope itemtype="http://schema.org/WebApplication">
<div class="container">
<div class="header">
<ul class="nav nav-pills pull-right">
<li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
</ul>
<h1 itemprop="name">wavesurfer.js Annotations Tool</h1>
</div>
<div id="demo">
<p id="subtitle" class="text-center text-info">&nbsp;</p>
<div id="wave-timeline"></div>
<div id="waveform">
<!-- Here be waveform -->
</div>
<div class="row" style="margin: 30px 0">
<div class="col-sm-8">
<p>
Click on a region to enter an annotation.<br />
Shift-click plays a region in a loop.
</p>
</div>
<div class="col-sm-2">
<button class="btn btn-primary btn-block" data-action="play">
<span id="play">
<i class="glyphicon glyphicon-play"></i>
Play
</span>
<span id="pause" style="display: none">
<i class="glyphicon glyphicon-pause"></i>
Pause
</span>
</button>
</div>
<div class="col-sm-2">
<button class="btn btn-info btn-block" data-action="export" title="Export annotations to JSON">
<i class="glyphicon glyphicon-file"></i>
Export
</button>
</div>
</div>
</div>
<form role="form" name="edit" style="opacity: 0; transition: opacity 300ms linear; margin: 30px 0;">
<div class="form-group">
<label for="start">Start</label>
<input class="form-control" id="start" name="start" />
</div>
<div class="form-group">
<label for="end">End</label>
<input class="form-control" id="end" name="end" />
</div>
<div class="form-group">
<label for="note">Note</label>
<textarea id="note" class="form-control" rows="3" name="note"></textarea>
</div>
<button type="submit" class="btn btn-success btn-block">Save</button>
<center><i>or</i></center>
<button type="button" class="btn btn-danger btn-block" data-action="delete-region">Delete</button>
</form>
<div class="row">
<h4>Region events:</h4>
<ul>
<li><code>region-in</code> When playback enters a region. Callback will receive the <code>Region</code> object.</li>
<li><code>region-out</code> When playback leaves a region. Callback will receive the <code>Region</code> object.</li>
<li><code>region-mouseenter</code> - When the mouse moves over a region. Callback will receive the <code>Region</code> object, and a <code>MouseEvent</code> object.</li>
<li><code>region-mouseleave</code> - When the mouse leaves a region. Callback will receive the <code>Region</code> object, and a <code>MouseEvent</code> object.</li>
<li><code>region-click</code> - When the mouse clicks on a region. Callback will receive the <code>Region</code> object, and a <code>MouseEvent</code> object.</li>
<li><code>region-dblclick</code> - When the mouse double-clicks on a region. Callback will receive the <code>Region</code> object, and a <code>MouseEvent</code> object.</li>
<li><code>region-created</code> When a region is created. Callback will receive the <code>Region</code> object.</li>
<li><code>region-updated</code> When a region is updated. Callback will receive the <code>Region</code> object.</li>
<li><code>region-update-end</code> When dragging or resizing is finished. Callback will receive the <code>Region</code> object.</li>
<li><code>region-removed</code> When a region is removed. Callback will receive the <code>Region</code> object.</li>
</ul>
<h4>Regions Plugin</h4>
<p>Regions are visual overlays on waveform that can be used to play and
loop portions of audio. Regions can be dragged and resized.</p>
<p>Visual customization is possible via CSS (using the selectors
<code>.wavesurfer-region</code> and <code>.wavesurfer-handle</code>).</p>
<p>To enable the plugin, add the script <code>plugin/wavesurfer.regions.js</code> to
your page.</p>
<p>After doing that, use <code>wavesurfer.addRegion()</code> to create Region objects.</p>
<h5>Exposed Methods</h5>
<ul>
<li><code>addRegion(options)</code> Creates a region on the waveform. Returns a <code>Region</code> object. See <a href="#region-options">Region Options</a>, <a href="#region-methods">Region Methods</a> and <a href="#region-events">Region Events</a> below.
<ul>
<li><strong>Note:</strong> You cannot add regions until the audio has finished loading, otherwise the <code>start:</code> and <code>end:</code> properties of the new region will be set to <code>0</code>, or an unexpected value.</li>
</ul></li>
<li><code>clearRegions()</code> Removes all regions.</li>
<li><code>enableDragSelection(options)</code> Lets you create regions by selecting.
areas of the waveform with mouse. <code>options</code> are Region objects' params (see <a href="#region-options">below</a>).</li>
<li><code>disableDragSelection()</code> - Disables ability to create regions.</li>
</ul>
<h5>Region Options</h5>
<table><thead>
<tr>
<th>option</th>
<th>type</th>
<th>default</th>
<th>description</th>
</tr>
</thead><tbody>
<tr>
<td><code>id</code></td>
<td>string</td>
<td>random</td>
<td>The id of the region.</td>
</tr>
<tr>
<td><code>start</code></td>
<td>float</td>
<td><code>0</code></td>
<td>The start position of the region (in seconds).</td>
</tr>
<tr>
<td><code>end</code></td>
<td>float</td>
<td><code>0</code></td>
<td>The end position of the region (in seconds).</td>
</tr>
<tr>
<td><code>loop</code></td>
<td>boolean</td>
<td><code>false</code></td>
<td>Whether to loop the region when played back.</td>
</tr>
<tr>
<td><code>drag</code></td>
<td>boolean</td>
<td><code>true</code></td>
<td>Allow/dissallow dragging the region.</td>
</tr>
<tr>
<td><code>resize</code></td>
<td>boolean</td>
<td><code>true</code></td>
<td>Allow/dissallow resizing the region.</td>
</tr>
<tr>
<td><code>color</code></td>
<td>string</td>
<td><code>"rgba(0, 0, 0, 0.1)"</code></td>
<td>HTML color code.</td>
</tr>
</tbody></table>
<h5>Region Methods</h5>
<ul>
<li><code>remove()</code> - Remove the region object.</li>
<li><code>update(options)</code> - Modify the settings of the region.</li>
<li><code>play()</code> - Play the audio region from the start to end position.</li>
</ul>
<h5>Region Events</h5>
<p>General events:</p>
<ul>
<li><code>in</code> - When playback enters the region.</li>
<li><code>out</code> - When playback leaves the region.</li>
<li><code>remove</code> - Happens just before the region is removed.</li>
<li><code>update</code> - When the region's options are updated.</li>
</ul>
<p>Mouse events:</p>
<ul>
<li><code>click</code> - When the mouse clicks on the region. Callback will receive a <code>MouseEvent</code>.</li>
<li><code>dblclick</code> - When the mouse double-clicks on the region. Callback will receive a <code>MouseEvent</code>.</li>
<li><code>over</code> - When mouse moves over the region. Callback will receive a <code>MouseEvent</code>.</li>
<li><code>leave</code> - When mouse leaves the region. Callback will receive a <code>MouseEvent</code>.</li>
</ul>
</div>
<div class="footer row">
<div class="col-sm-12">
<a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
</div>
<div class="col-sm-8">
<span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
</div>
<div class="col-sm-4">
<p>
The sound file is from <a href="https://librivox.org/librivox-multilingual-short-works-collection-001-by-various/">librivox.org</a>.
</p>
</div>
</div>
</div>
<div class="github-fork-ribbon-wrapper right">
<div class="github-fork-ribbon">
<a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
</div>
</div>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-50026819-1', 'wavesurfer.fm');
ga('send', 'pageview');
</script>
</body>
</html>
File diff suppressed because one or more lines are too long
@@ -0,0 +1,133 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>wavesurfer.js | Media Element Example</title>
<link href="data:image/gif;" rel="icon" type="image/x-icon" />
<!-- Bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/ribbon.css" />
<link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
<!-- wavesurfer.js -->
<script src="../../dist/wavesurfer.js"></script>
<script src="../../dist/plugin/wavesurfer.regions.js"></script>
<!-- Demo -->
<script src="main.js"></script>
</head>
<body itemscope itemtype="http://schema.org/WebApplication">
<div class="container">
<div class="header">
<ul class="nav nav-pills pull-right">
<li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
</ul>
<h1 itemprop="name">Media Element Fallback Example</h1>
</div>
<div id="demo">
<div id="waveform">
<!-- Here be the waveform -->
</div>
<div class="controls">
<button class="btn btn-primary" data-action="play">
<i class="glyphicon glyphicon-play"></i>
Play
/
<i class="glyphicon glyphicon-pause"></i>
Pause
</button>
</div>
</div>
<div class="row marketing">
<h3>How to Enable the Fallback</h3>
<hr />
<p>
<strong>wavesurfer.js</strong> will automatically
fallback to HTML5 Media if Web Audio is not
supported. However, you can choose to use audio
element manually. Simply set the <code>backend</code>
option to <code>"MediaElement"</code>.
</p>
<p>
<pre><code>var wavesurfer = WaveSurfer.create({
container: document.querySelector('#wave'),
backend: 'MediaElement'
});
</code></pre>
</p>
<h3>Pre-rendered Peaks</h3>
<p>
If you have pre-rendered peaks (on your server),
you can pass them into the <code>load</code>
function. This is optionalif you don't provide
any peaks,
<strong>waserver.js</strong> 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.
</p>
<p>
<pre><code>wavesurfer.load('example/media/demo.mp3');</code></pre>
</p>
<p>
Press this button to see the same demo with pre-decoded peaks:
<button class="btn btn-warning" data-action="peaks">
Load with pre-rendered peaks
</button>
</p>
</div>
<div class="footer row">
<div class="col-sm-12">
<a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
</div>
<div class="col-sm-7">
<span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a&nbsp;<a style="white-space: nowrap" rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
</div>
<div class="col-sm-5">
<div class="pull-right">
<noindex>
The audio file is from <a rel="nofollow" href="http://spokencorpora.ru/">spokencorpora.ru</a>, used with permission.
</noindex>
</div>
</div>
</div>
</div>
<div class="github-fork-ribbon-wrapper right">
<div class="github-fork-ribbon">
<a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
</div>
</div>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-50026819-1', 'wavesurfer.fm');
ga('send', 'pageview');
</script>
</body>
</html>
@@ -0,0 +1,31 @@
'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',
backend: 'MediaElement'
});
// Load audio from URL
wavesurfer.load('../media/demo.wav');
document.querySelector(
'[data-action="play"]'
).addEventListener('click', wavesurfer.playPause.bind(wavesurfer));
document.querySelector(
'[data-action="peaks"]'
).addEventListener('click', function () {
wavesurfer.load('../media/demo.wav', [
0.0218, 0.0183, 0.0165, 0.0198, 0.2137, 0.2888, 0.2313, 0.15, 0.2542, 0.2538, 0.2358, 0.1195, 0.1591, 0.2599, 0.2742, 0.1447, 0.2328, 0.1878, 0.1988, 0.1645, 0.1218, 0.2005, 0.2828, 0.2051, 0.1664, 0.1181, 0.1621, 0.2966, 0.189, 0.246, 0.2445, 0.1621, 0.1618, 0.189, 0.2354, 0.1561, 0.1638, 0.2799, 0.0923, 0.1659, 0.1675, 0.1268, 0.0984, 0.0997, 0.1248, 0.1495, 0.1431, 0.1236, 0.1755, 0.1183, 0.1349, 0.1018, 0.1109, 0.1833, 0.1813, 0.1422, 0.0961, 0.1191, 0.0791, 0.0631, 0.0315, 0.0157, 0.0166, 0.0108
]);
document.body.scrollTop = 0;
});
});
+189
View File
@@ -0,0 +1,189 @@
body {
line-height: 1.6;
}
.content {
max-width: 65em;
padding: 15px 5%;
}
.content > div {
margin-bottom: 4em;
}
.self-detail {
margin-bottom: 2em;
}
/* detail */
.content .detail > div {
margin-left: 0;
}
.content .detail h4 + :not(pre) {
margin-left: 0;
}
.content .detail {
margin-top: 2em;
}
.content .detail + .detail {
border-top: 1px solid #f5f5f5;
}
.content .detail + .detail h3 {
padding-top: 3em;
}
.content .detail > h3 {
color: #4d4e53;
margin-top: 0;
}
/* table styling */
table.params tr:hover td,
table.summary tr:hover td {
background: #F5F5F5;
}
table.params td,
table.summary td {
border: 2px solid #fff;
background: #FAFAFA;
padding: 0.25em;
vertical-align: baseline;
}
table.summary thead {
background: transparent;
}
/* Property tables should follow some sort of grid */
div[data-ice="properties"] td:nth-child(1) {
width: 10% !important;
}
div[data-ice="properties"] td:nth-child(2),
div[data-ice="properties"] td:nth-child(3) {
width: 20% !important;
}
div[data-ice="properties"] td:nth-child(4) {
width: 50% !important;
}
/* headings */
h1,
.github-markdown h1 a {
font-weight: normal;
border-bottom: 0;
margin-bottom: 1.5em;
font-size: 2.5rem;
}
h2,
.github-markdown h2 {
font-size: 1.5rem;
border-bottom: 0;
border-top: 4px solid #f5f5f5;
padding-top: 1em;
margin-top: 1em;
margin-bottom: 1em;
}
h3 {
font-size: 1.25em;
background: transparent;
padding: 0;
}
h3 [data-ice="name"] {
font-weight: 600;
}
h4,
table.summary thead {
font-weight: 600;
text-transform: uppercase;
font-size: 0.75rem;
letter-spacing: 0.075em;
color: #555;
}
/* misc & type */
.access {
font-style: italic;
opacity: 0.5;
}
.abstract {
font-style: italic;
color: #009800;
}
*[data-ice="appendix"] li {
margin: 0;
}
p {
line-height: inherit;
margin-bottom: 1em;
}
/* links */
a,
.inherited-summary thead a {
color: #528DD9;
border-bottom: 1px solid #DDEBFE;
}
a:hover,
a:focus,
a:active,
.inherited-summary thead a:hover,
.inherited-summary thead a:focus,
.inherited-summary thead a:active {
color: #4271ae;
border-bottom-color: #4271ae;
}
/* sidebar */
.navigation {
border: 0;
}
.navigation li a {
border: 0;
}
.navigation li a:hover,
.navigation li a:focus,
.navigation li a:active {
color: #333;
}
.navigation *[data-ice="kind"] {
line-height: 1.25em;
}
/* active state for sections */
.inner-link-active {
background: transparent;
}
.inner-link-active::before {
content: '';
display: block;
height: 0.5em;
width: 0.5em;
background: #009800;
position: absolute;
left: -1.5em;
top: 0.5em;
}
/* position for other than the first detail container */
.content .detail + .detail .inner-link-active::before {
top: 3.5em;
}
/* search */
.search-box.active .search-input-edge {
display: none;
}
.search-box {
font-size: 0.85rem;
}
.search-result {
height: auto;
max-height: 90vh;
white-space: normal;
box-shadow: -2px 5px 10px rgba(0,0,0,.05);
border: 1px solid #ddd;
border-top: 0;
}
.search-result li a {
border: 0;
overflow: hidden;
text-overflow: ellipsis;
}
.search-result li.search-separator {
background: #4d4e53;
}
+140
View File
@@ -0,0 +1,140 @@
/*!
* "Fork me on GitHub" CSS ribbon v0.1.1 | MIT License
* https://github.com/simonwhitaker/github-fork-ribbon-css
*/
/* Left will inherit from right (so we don't need to duplicate code) */
.github-fork-ribbon {
/* The right and left classes determine the side we attach our banner to */
position: absolute;
/* Add a bit of padding to give some substance outside the "stitching" */
padding: 2px 0;
/* Set the base colour */
background-color: #a00;
/* Set a gradient: transparent black at the top to almost-transparent black at the bottom */
background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0)), to(rgba(0, 0, 0, 0.15)));
background-image: -webkit-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
background-image: -moz-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
background-image: -ms-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
background-image: -o-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
/* Add a drop shadow */
-webkit-box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.5);
-moz-box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.5);
box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.5);
/* Set the font */
font: 700 13px "Helvetica Neue", Helvetica, Arial, sans-serif;
z-index: 9999;
pointer-events: auto;
}
.github-fork-ribbon a,
.github-fork-ribbon a:hover {
/* Set the text properties */
color: #fff;
text-decoration: none;
text-shadow: 0 -1px rgba(0, 0, 0, 0.5);
text-align: center;
/* Set the geometry. If you fiddle with these you'll also need
to tweak the top and right values in .github-fork-ribbon. */
width: 200px;
line-height: 20px;
/* Set the layout properties */
display: inline-block;
padding: 2px 0;
/* Add "stitching" effect */
border-width: 1px 0;
border-style: dotted;
border-color: #fff;
border-color: rgba(255, 255, 255, 0.7);
}
.github-fork-ribbon-wrapper {
width: 150px;
height: 150px;
position: absolute;
overflow: hidden;
top: 0;
z-index: 9999;
pointer-events: none;
}
.github-fork-ribbon-wrapper.fixed {
position: fixed;
}
.github-fork-ribbon-wrapper.left {
left: 0;
}
.github-fork-ribbon-wrapper.right {
right: 0;
}
.github-fork-ribbon-wrapper.left-bottom {
position: fixed;
top: inherit;
bottom: 0;
left: 0;
}
.github-fork-ribbon-wrapper.right-bottom {
position: fixed;
top: inherit;
bottom: 0;
right: 0;
}
.github-fork-ribbon-wrapper.right .github-fork-ribbon {
top: 42px;
right: -43px;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
}
.github-fork-ribbon-wrapper.left .github-fork-ribbon {
top: 42px;
left: -43px;
-webkit-transform: rotate(-45deg);
-moz-transform: rotate(-45deg);
-ms-transform: rotate(-45deg);
-o-transform: rotate(-45deg);
transform: rotate(-45deg);
}
.github-fork-ribbon-wrapper.left-bottom .github-fork-ribbon {
top: 80px;
left: -43px;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
}
.github-fork-ribbon-wrapper.right-bottom .github-fork-ribbon {
top: 80px;
right: -43px;
-webkit-transform: rotate(-45deg);
-moz-transform: rotate(-45deg);
-ms-transform: rotate(-45deg);
-o-transform: rotate(-45deg);
transform: rotate(-45deg);
}
+110
View File
@@ -0,0 +1,110 @@
/* Space out content a bit */
body {
padding-top: 20px;
padding-bottom: 20px;
}
/* Everything but the jumbotron gets side spacing for mobile first views */
.header,
.marketing,
.footer {
padding-left: 15px;
padding-right: 15px;
}
/* Custom page header */
.header {
border-bottom: 1px solid #e5e5e5;
}
/* Make the masthead heading the same height as the navigation */
.header h3 {
margin-top: 0;
margin-bottom: 0;
line-height: 40px;
padding-bottom: 19px;
}
/* Custom page footer */
.footer {
padding-top: 19px;
color: #777;
border-top: 1px solid #e5e5e5;
}
/* Customize container */
@media (min-width: 1024px) {
.container {
max-width: 900px;
}
}
.container-narrow > hr {
margin: 30px 0;
}
/* Supporting marketing content */
.marketing {
margin: 40px 0;
}
.marketing p + h4 {
margin-top: 28px;
}
/* Responsive: Portrait tablets and up */
@media screen and (min-width: 768px) {
/* Remove the padding we set earlier */
.header,
.marketing,
.footer {
padding-left: 0;
padding-right: 0;
}
/* Space out the masthead */
.header {
margin-bottom: 30px;
}
}
.controls {
padding: 30px 0 15px;
text-align: center;
}
.controls .btn {
margin-bottom: 15px;
}
@media screen and (min-width: 990px) {
.controls .mark-controls {
display: inline;
}
}
.lead {
text-align: center;
padding: 20px;
}
#waveform {
position: relative;
}
#progress-bar {
position: absolute;
z-index: 10;
top: 50%;
margin-top: -10px;
left: 5%;
width: 90%;
}
#drop {
border: 3px dashed #ddd;
padding: 30px;
}
#drop.wavesurfer-dragover {
border-color: #333;
}
@@ -0,0 +1,106 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>wavesurfer.js | Cursor Example</title>
<link href="data:image/gif;" rel="icon" type="image/x-icon" />
<!-- Bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/ribbon.css" />
<link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
<!-- wavesurfer.js -->
<script src="../../dist/wavesurfer.js"></script>
<script src="../../dist/plugin/wavesurfer.cursor.js"></script>
<!-- Demo -->
<script src="main.js"></script>
</head>
<body itemscope itemtype="http://schema.org/WebApplication">
<div class="container">
<div class="header">
<ul class="nav nav-pills pull-right">
<li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
</ul>
<h1 itemprop="name">Cursor Plugin Example</h1>
</div>
<div id="demo">
<div id="waveform">
<!-- Here be the waveform -->
</div>
<div class="controls">
<div class="row">
<div class="col-sm-7">
<button class="btn btn-primary" data-action="play">
<i class="glyphicon glyphicon-play"></i>
Play
/
<i class="glyphicon glyphicon-pause"></i>
Pause
</button>
</div>
</div>
</div>
</div>
<div class="row marketing">
<h3>Cursor plugin</h3>
<hr />
<p>Shows a cursor on the waveform.</p>
<p>
<pre><code>var wavesurfer = WaveSurfer.create({
container: document.querySelector('#waveform'),
plugins: [
WaveSurfer.cursor.create()
]
});
</code></pre>
</p>
</div>
<div class="footer row">
<div class="col-sm-12">
<a rel="license" href="https://creativecommons.org/licenses/by/3.0/deed.en_US"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by/3.0/80x15.png" /></a>
</div>
<div class="col-sm-7">
<span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a xmlns:cc="http://creativecommons.org/ns#" href="https://github.com/katspaugh/wavesurfer.js" property="cc:attributionName" rel="cc:attributionURL">katspaugh</a> is licensed under a&nbsp;<a style="white-space: nowrap" rel="license" href="https://creativecommons.org/licenses/by/3.0/deed.en_US">Creative Commons Attribution 3.0 Unported License</a>.
</div>
<div class="col-sm-5">
<div class="pull-right">
<noindex>
The audio file is from <a rel="nofollow" href="http://spokencorpora.ru/">spokencorpora.ru</a>, used with permission.
</noindex>
</div>
</div>
</div>
</div>
<div class="github-fork-ribbon-wrapper right">
<div class="github-fork-ribbon">
<a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
</div>
</div>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-50026819-1', 'wavesurfer.fm');
ga('send', 'pageview');
</script>
</body>
</html>
@@ -0,0 +1,25 @@
'use strict';
// Create an instance
var wavesurfer = {};
// Init & load audio file
document.addEventListener('DOMContentLoaded', function () {
wavesurfer = WaveSurfer.create({
container: document.querySelector('#waveform'),
plugins: [
WaveSurfer.cursor.create()
]
});
// Load audio from URL
wavesurfer.load('../media/demo.wav');
// Play button
var button = document.querySelector('[data-action="play"]');
button.addEventListener('click', wavesurfer.playPause.bind(wavesurfer));
});
@@ -0,0 +1,194 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>wavesurfer.js | ELAN Wave Segment player</title>
<link href="data:image/gif;" rel="icon" type="image/x-icon" />
<!-- Bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/ribbon.css" />
<link rel="stylesheet" href="css/elan.css" />
<link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
<!-- wavesurfer.js -->
<script src="../../dist/wavesurfer.min.js"></script>
<!-- regions plugin -->
<script src="../../dist/plugin/wavesurfer.regions.min.js"></script>
<!-- ELAN format renderer -->
<script src="../../dist/plugin/wavesurfer.elan.min.js"></script>
<!-- ELAN wave segment renderer -->
<script src="../../plugin/wavesurfer.elan-wave-segment.js"></script>
<!-- App -->
<script src="app.js"></script>
<script src="../trivia.js"></script>
</head>
<body itemscope itemtype="http://schema.org/WebApplication">
<div class="container">
<div class="header">
<noindex>
<ul class="nav nav-pills pull-right">
<li><a href="?fill">Fill</a></li>
<li><a href="?scroll">Scroll</a></li>
</ul>
</noindex>
<h1 itemprop="name"><a href="http://wavesurfer-js.org">wavesurfer.js</a><noindex> + <a rel="nofollow" href="http://spokencorpora.ru/showelan.py">ELAN</a> + Wave Segment</noindex></h1>
</div>
<div><p>The Elan Wave Segment Plugin uses the table and the time values created by the
<a href="../elan/index.html">ELAN plugin</a> to insert a wave form column for each row.
</p></div>
<div id="demo">
<div id="waveform">
<div class="progress progress-striped active" id="progress-bar">
<div class="progress-bar progress-bar-info"></div>
</div>
<!-- Here be waveform -->
</div>
<div class="controls">
<button class="btn btn-primary" data-action="play">
<i class="glyphicon glyphicon-play"></i>
Play
/
<i class="glyphicon glyphicon-pause"></i>
Pause
</button>
</div>
</div>
<div id="annotations" class="table-responsive">
<!-- Here be transcript -->
</div>
<h2>How to Enable Elan Wave Segment</h2>
<h3>Javascript Dependencies</h3>
<ul>
<li>Wavesufer <code></code></li>
<li>Region Plugin <code></code></li></li>
<li>ElAN Plugin <code></code></li></li>
<li>ELAN Wave Segment</li>
<pre><code>&lt;script src="[path_to]/wavesurfer.min.js"&gt;&lt;/script&gt;
&lt;script src="[path_to]/plugin/wavesurfer.region.min.js"&gt;&lt;/script&gt;
&lt;script src="[path_to]/plugin/wavesurfer.elan.min.js"&gt;&lt;/script&gt;
&lt;script src="[path_to]/plugin/wavesurfer.elan-wave-segment.min.js"&gt;&lt;/script&gt; </code></pre>
</ul>
<h2>Javascript Initialization</h2>
<pre><code>// Create the wave surfer instance
var wavesurfer = Object.create(WaveSurfer);
// Create elan instance
var elan = Object.create(WaveSurfer.ELAN);
// Create Elan Wave Segment instance
var elanWaveSegment = Object.create(WaveSurfer.ELANWaveSegment);
document.addEventListener('DOMContentLoaded', function () {
var options = {
container : '#waveform',
};
//################## set up some listeners ####################
//set up listener for when elan is done
elan.on('ready', function (data) {
wavesurfer.load('../elan/transcripts/001z.mp3');
});
//set up listener for playing when clicked on
elan.on('select', function (start, end) {
wavesurfer.backend.play(start, end);
});
//############################## initialize wavesurfer and related plugins###############
// Init wavesurfer
wavesurfer.init(options);
//init elan
elan.init({
url: '../elan/transcripts/001z.xml',
container: '#annotations',
tiers: {
Text: true,
Comments: true
}
});
//int elanWaveSegment when wavesurfer is done loading the sound file
wavesurfer.on('ready', function() {
options.plotTimeEnd = wavesurfer.backend.getDuration();
options.wavesurfer = wavesurfer;
options.ELAN = elan;
elanWaveSegment.init(options);
});
//update waveSegments when time advances
var onProgress = function (time) {
elanWaveSegment.onProgress(time);
//code for scrolling Elan goes here
};
wavesurfer.on('audioprocess', onProgress);
}); </code></pre>
<h2>Options</h2>
<ul>
<li><code>ELAN:</code> required - The ELAN instance used to parse the elan data</li>
<li><code>wafesurver:</code> required - The wavesurfer instance used to draw the original waveform</li>
<li><code>waveSegmentWidth:</code> optional - The width of each wave segment (defaults to 200)</li>
<li><code>waveSegmentPeaksPerSegment:</code> optional - The number of peaks that should be drawn (defaults to 400)</li>
<li><code>waveSegmentHeight:</code> optional - The height of each wave segment (defaults to 30)</li>
<li><code>waveSegmentRenderer:</code> optional - The renderer (drawer) to be used for the wave segments</li>
<li><code>waveSegmentNormalizeTo:</code> optional - What to normalize each wave segment to [whole, segment,none]</li>
<li><code>waveSegmentBorderWidth:</code> optional - The width of the border of the container element</li>
<li><code>waveSegmentBarHeight:</code> optional - the height of the peaks/bars (defaults to 1)</li>
</ul>
<div class="footer row">
<div class="col-sm-12">
<a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
</div>
<div class="col-sm-8">
<span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
</div>
<div class="col-sm-4">
<p>
The ELAN program and format were developed by <a href="http://tla.mpi.nl/tools/tla-tools/elan/">Max Planck Institute</a>.
</p>
<p>
The sample ELAN file and audio are from <a rel="nofollow" href="http://spokencorpora.ru/">spokencorpora.ru</a>, used with permission.
</p>
</div>
</div>
</div>
<div class="github-fork-ribbon-wrapper right">
<div class="github-fork-ribbon">
<a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
</div>
</div>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-50026819-1', 'wavesurfer.fm');
ga('send', 'pageview');
</script>
</body>
</html>
+109
View File
@@ -0,0 +1,109 @@
'use strict';
// Create an instance
var wavesurfer;
// Init & load
document.addEventListener('DOMContentLoaded', function () {
var options = {
container : '#waveform',
waveColor : 'violet',
progressColor : 'purple',
loaderColor : 'purple',
cursorColor : 'navy',
selectionColor: '#d0e9c6',
loopSelection : false,
plugins: [
WaveSurfer.elan.create({
url: 'transcripts/001z.xml',
container: '#annotations',
tiers: {
Text: true,
Comments: true
}
}),
WaveSurfer.regions.create()
]
};
if (location.search.match('scroll')) {
options.minPxPerSec = 100;
options.scrollParent = true;
}
if (location.search.match('normalize')) {
options.normalize = true;
}
// Init wavesurfer
wavesurfer = WaveSurfer.create(options);
/* Progress bar */
(function () {
var progressDiv = document.querySelector('#progress-bar');
var progressBar = progressDiv.querySelector('.progress-bar');
var showProgress = function (percent) {
progressDiv.style.display = 'block';
progressBar.style.width = percent + '%';
};
var hideProgress = function () {
progressDiv.style.display = 'none';
};
wavesurfer.on('loading', showProgress);
wavesurfer.on('ready', hideProgress);
wavesurfer.on('destroy', hideProgress);
wavesurfer.on('error', hideProgress);
}());
wavesurfer.elan.on('ready', function (data) {
wavesurfer.load('transcripts/' + data.media.url);
});
wavesurfer.elan.on('select', function (start, end) {
wavesurfer.backend.play(start, end);
});
wavesurfer.elan.on('ready', function () {
var classList = wavesurfer.elan.container.querySelector('table').classList;
[ 'table', 'table-striped', 'table-hover' ].forEach(function (cl) {
classList.add(cl);
});
});
var prevAnnotation, prevRow, region;
var onProgress = function (time) {
var annotation = wavesurfer.elan.getRenderedAnnotation(time);
if (prevAnnotation != annotation) {
prevAnnotation = annotation;
region && region.remove();
region = null;
if (annotation) {
// Highlight annotation table row
var row = wavesurfer.elan.getAnnotationNode(annotation);
prevRow && prevRow.classList.remove('success');
prevRow = row;
row.classList.add('success');
var before = row.previousSibling;
if (before) {
wavesurfer.elan.container.scrollTop = before.offsetTop;
}
// Region
region = wavesurfer.addRegion({
start: annotation.start,
end: annotation.end,
resize: false,
color: 'rgba(223, 240, 216, 0.7)'
});
}
}
};
wavesurfer.on('audioprocess', onProgress);
});
@@ -0,0 +1,25 @@
#annotations {
max-height: 300px;
overflow: auto;
}
.wavesurfer-annotations tr.wavesurfer-active td {
background-color: yellow;
}
.wavesurfer-time {
width: 100px;
color: #555;
}
.wavesurfer-tier-Text {
width: 500px;
}
td.wavesurfer-tier-Comments {
color: #999;
}
.wavesurfer-handle {
background-color: #c9e2b3;
}
@@ -0,0 +1,105 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>wavesurfer.js | ELAN player</title>
<link href="data:image/gif;" rel="icon" type="image/x-icon" />
<!-- Bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/ribbon.css" />
<link rel="stylesheet" href="css/elan.css" />
<link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
<!-- wavesurfer.js -->
<script src="../../dist/wavesurfer.js"></script>
<!-- regions plugin -->
<script src="../../dist/plugin/wavesurfer.regions.js"></script>
<!-- ELAN format renderer -->
<script src="../../dist/plugin/wavesurfer.elan.js"></script>
<!-- App -->
<script src="app.js"></script>
<script src="../trivia.js"></script>
</head>
<body itemscope itemtype="http://schema.org/WebApplication">
<div class="container">
<div class="header">
<noindex>
<ul class="nav nav-pills pull-right">
<li><a href="?fill">Fill</a></li>
<li><a href="?scroll">Scroll</a></li>
</ul>
</noindex>
<h1 itemprop="name"><a href="http://wavesurfer-js.org">wavesurfer.js</a><noindex> + <a rel="nofollow" href="http://spokencorpora.ru/showelan.py">ELAN</a></noindex></h1>
</div>
<div id="demo">
<div id="waveform">
<div class="progress progress-striped active" id="progress-bar">
<div class="progress-bar progress-bar-info"></div>
</div>
<!-- Here be waveform -->
</div>
<div class="controls">
<button class="btn btn-primary" data-action="play">
<i class="glyphicon glyphicon-play"></i>
Play
/
<i class="glyphicon glyphicon-pause"></i>
Pause
</button>
</div>
</div>
<div id="annotations" class="table-responsive">
<!-- Here be transcript -->
</div>
<div class="footer row">
<div class="col-sm-12">
<a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
</div>
<div class="col-sm-8">
<span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
</div>
<div class="col-sm-4">
<p>
The ELAN program and format were developed by <a href="http://tla.mpi.nl/tools/tla-tools/elan/">Max Planck Institute</a>.
</p>
<p>
The sample ELAN file and audio are from <a rel="nofollow" href="http://spokencorpora.ru/">spokencorpora.ru</a>, used with permission.
</p>
</div>
</div>
</div>
<div class="github-fork-ribbon-wrapper right">
<div class="github-fork-ribbon">
<a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
</div>
</div>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-50026819-1', 'wavesurfer.fm');
ga('send', 'pageview');
</script>
</body>
</html>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,92 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>wavesurfer.js | Equalizer Example</title>
<link href="data:image/gif;" rel="icon" type="image/x-icon" />
<!-- Bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/ribbon.css" />
<link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
<!-- wavesurfer.js -->
<script src="../../dist/wavesurfer.js"></script>
<!-- Demo -->
<script src="main.js"></script>
</head>
<body itemscope itemtype="http://schema.org/WebApplication">
<div class="container">
<div class="header">
<ul class="nav nav-pills pull-right">
<li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
</ul>
<h1 itemprop="name">Equalizer Example</h1>
</div>
<div id="demo">
<div id="waveform">
<div class="progress progress-striped active" id="progress-bar">
<div class="progress-bar progress-bar-info"></div>
</div>
<!-- Here be the waveform -->
</div>
<div class="controls">
<button class="btn btn-primary" data-action="play">
<i class="glyphicon glyphicon-play"></i>
Play
/
<i class="glyphicon glyphicon-pause"></i>
Pause
</button>
<div id="equalizer">
<!-- Here be equalizer sliders -->
</div>
</div>
</div>
<div class="footer row">
<div class="col-sm-12">
<a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
</div>
<div class="col-sm-7">
<span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a&nbsp;<a style="white-space: nowrap" rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
</div>
<div class="col-sm-5">
<div class="pull-right">
<noindex>
Demo music track is <a href="http://www.jamendo.com/en/track/661578/trou" rel="nofollow"><b>Trou</b> <span class="muted">by</span>&nbsp;<b>czskamaarù</b></a>. Thanks!
</noindex>
</div>
</div>
</div>
</div>
<div class="github-fork-ribbon-wrapper right">
<div class="github-fork-ribbon">
<a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
</div>
</div>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-50026819-1', 'wavesurfer.fm');
ga('send', 'pageview');
</script>
</body>
</html>
@@ -0,0 +1,128 @@
'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'
});
// Load audio from URL
wavesurfer.load('../media/demo.wav');
// Equalizer
wavesurfer.on('ready', function () {
var EQ = [
{
f: 32,
type: 'lowshelf'
}, {
f: 64,
type: 'peaking'
}, {
f: 125,
type: 'peaking'
}, {
f: 250,
type: 'peaking'
}, {
f: 500,
type: 'peaking'
}, {
f: 1000,
type: 'peaking'
}, {
f: 2000,
type: 'peaking'
}, {
f: 4000,
type: 'peaking'
}, {
f: 8000,
type: 'peaking'
}, {
f: 16000,
type: 'highshelf'
}
];
// Create filters
var filters = EQ.map(function (band) {
var filter = wavesurfer.backend.ac.createBiquadFilter();
filter.type = band.type;
filter.gain.value = 0;
filter.Q.value = 1;
filter.frequency.value = band.f;
return filter;
});
// Connect filters to wavesurfer
wavesurfer.backend.setFilters(filters);
// Bind filters to vertical range sliders
var container = document.querySelector('#equalizer');
filters.forEach(function (filter) {
var input = document.createElement('input');
wavesurfer.util.extend(input, {
type: 'range',
min: -40,
max: 40,
value: 0,
title: filter.frequency.value
});
input.style.display = 'inline-block';
input.setAttribute('orient', 'vertical');
wavesurfer.util.style(input, {
'webkitAppearance': 'slider-vertical',
width: '50px',
height: '150px'
});
container.appendChild(input);
var onChange = function (e) {
filter.gain.value = ~~e.target.value;
};
input.addEventListener('input', onChange);
input.addEventListener('change', onChange);
});
// For debugging
wavesurfer.filters = filters;
});
// Log errors
wavesurfer.on('error', function (msg) {
console.log(msg);
});
// Bind play/pause button
document.querySelector(
'[data-action="play"]'
).addEventListener('click', wavesurfer.playPause.bind(wavesurfer));
// Progress bar
(function () {
var progressDiv = document.querySelector('#progress-bar');
var progressBar = progressDiv.querySelector('.progress-bar');
var showProgress = function (percent) {
progressDiv.style.display = 'block';
progressBar.style.width = percent + '%';
};
var hideProgress = function () {
progressDiv.style.display = 'none';
};
wavesurfer.on('loading', showProgress);
wavesurfer.on('ready', hideProgress);
wavesurfer.on('destroy', hideProgress);
wavesurfer.on('error', hideProgress);
}());
});
@@ -0,0 +1,114 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>wavesurfer.js | HTML initialisation API example</title>
<link href="data:image/gif;" rel="icon" type="image/x-icon" />
<!-- Bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/ribbon.css" />
<link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
<!-- wavesurfer.js -->
<script src="../../dist/wavesurfer.js"></script>
<script src="../../dist/wavesurfer-html-init.js"></script>
</head>
<body itemscope itemtype="http://schema.org/WebApplication">
<div class="container">
<div class="header">
<ul class="nav nav-pills pull-right">
<li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
</ul>
<h1 itemprop="name">HTML initialisation API</h1>
</div>
<p class="marketing">
Using the HTML initialisation script and the HTML attribute API is a quick way of creating wavesurfer instances.
</p>
<div id="demo">
<section class="marketing">
<wavesurfer
data-url="../media/demo.wav"
data-plugins="regions"
data-regions-regions='[{"start": 1,"end": 3,"color": "hsla(400, 100%, 30%, 0.5)"}, {"start": 5,"end": 7,"color": "hsla(200, 50%, 70%, 0.4)"}]'
>
</wavesurfer>
<pre><code>&lt;wavesurfer
data-url="../media/demo.wav"
data-plugins="regions"
data-regions-regions='[{"start": 1,"end": 3,"color": "hsla(400, 100%, 30%, 0.5)"}, {"start": 5,"end": 7,"color": "hsla(200, 50%, 70%, 0.4)"}]'
>
&lt;/wavesurfer>
</code></pre>
</section>
<hr />
<section class="marketing">
<wavesurfer
data-url="../media/demo.wav"
data-plugins="minimap,timeline"
data-minimap-height="30"
data-minimap-wave-color="#ddd"
data-minimap-progress-color="#999"
data-timeline-font-size="13px"
data-timeline-container="#timeline"
>
</wavesurfer>
<div id="timeline"></div>
<pre><code>&lt;wavesurfer
data-url="../media/demo.wav"
data-plugins="minimap,timeline"
data-minimap-height="30"
data-minimap-wave-color="#ddd"
data-minimap-progress-color="#999"
data-timeline-font-size="13px"
data-timeline-container="#timeline"
>
&lt;/wavesurfer>
&lt;div id="timeline">&lt;/div>
</code></pre>
</section>
</div>
<div class="footer row">
<div class="col-sm-12">
<a rel="license" href="https://creativecommons.org/licenses/by/3.0/deed.en_US"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by/3.0/80x15.png" /></a>
</div>
<div class="col-sm-7">
<span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a xmlns:cc="http://creativecommons.org/ns#" href="https://github.com/katspaugh/wavesurfer.js" property="cc:attributionName" rel="cc:attributionURL">katspaugh</a> is licensed under a&nbsp;<a style="white-space: nowrap" rel="license" href="https://creativecommons.org/licenses/by/3.0/deed.en_US">Creative Commons Attribution 3.0 Unported License</a>.
</div>
<div class="col-sm-5">
<div class="pull-right">
<noindex>
The audio file is from <a rel="nofollow" href="http://spokencorpora.ru/">spokencorpora.ru</a>, used with permission.
</noindex>
</div>
</div>
</div>
</div>
<div class="github-fork-ribbon-wrapper right">
<div class="github-fork-ribbon">
<a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
</div>
</div>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-50026819-1', 'wavesurfer.fm');
ga('send', 'pageview');
</script>
</body>
</html>
+108
View File
@@ -0,0 +1,108 @@
'use strict';
// Create an instance
var wavesurfer;
// Init & load audio file
document.addEventListener('DOMContentLoaded', function () {
var options = {
container : document.querySelector('#waveform'),
waveColor : 'violet',
progressColor : 'purple',
cursorColor : 'navy'
};
if (location.search.match('scroll')) {
options.minPxPerSec = 100;
options.scrollParent = true;
}
// Init
wavesurfer = WaveSurfer.create(options);
// Load audio from URL
wavesurfer.load('example/media/demo.wav');
// Regions
if (wavesurfer.enableDragSelection) {
wavesurfer.enableDragSelection({
color: 'rgba(0, 255, 0, 0.1)'
});
}
});
// Play at once when ready
// Won't work on iOS until you touch the page
wavesurfer.on('ready', function () {
//wavesurfer.play();
});
// Report errors
wavesurfer.on('error', function (err) {
console.error(err);
});
// Do something when the clip is over
wavesurfer.on('finish', function () {
console.log('Finished playing');
});
/* Progress bar */
document.addEventListener('DOMContentLoaded', function () {
var progressDiv = document.querySelector('#progress-bar');
var progressBar = progressDiv.querySelector('.progress-bar');
var showProgress = function (percent) {
progressDiv.style.display = 'block';
progressBar.style.width = percent + '%';
};
var hideProgress = function () {
progressDiv.style.display = 'none';
};
wavesurfer.on('loading', showProgress);
wavesurfer.on('ready', hideProgress);
wavesurfer.on('destroy', hideProgress);
wavesurfer.on('error', hideProgress);
});
// Drag'n'drop
document.addEventListener('DOMContentLoaded', function () {
var toggleActive = function (e, toggle) {
e.stopPropagation();
e.preventDefault();
toggle ? e.target.classList.add('wavesurfer-dragover') :
e.target.classList.remove('wavesurfer-dragover');
};
var handlers = {
// Drop event
drop: function (e) {
toggleActive(e, false);
// Load the file into wavesurfer
if (e.dataTransfer.files.length) {
wavesurfer.loadBlob(e.dataTransfer.files[0]);
} else {
wavesurfer.fireEvent('error', 'Not a file');
}
},
// Drag-over event
dragover: function (e) {
toggleActive(e, true);
},
// Drag-leave event
dragleave: function (e) {
toggleActive(e, false);
}
};
var dropTarget = document.querySelector('#drop');
Object.keys(handlers).forEach(function (event) {
dropTarget.addEventListener(event, handlers[event]);
});
});
@@ -0,0 +1,168 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>wavesurfer.js | Media Sesssion plugin</title>
<link href="data:image/gif;" rel="icon" type="image/x-icon" />
<!-- Bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/ribbon.css" />
<!-- wavesurfer.js -->
<script src="../../dist/wavesurfer.min.js"></script>
<!-- media session plugin -->
<script src="../../dist/plugin/wavesurfer.mediasession.min.js"></script>
<!-- App -->
<script src="app.js"></script>
</head>
<body itemscope itemtype="http://schema.org/WebApplication">
<div class="container">
<div class="header">
<h1 itemprop="name"><a href="http://wavesurfer-js.org">wavesurfer.js</a><noindex> + Media Session API</noindex></h1>
</div>
<div id="demo">
<div id="waveform"></div>
<div class="controls">
<button class="btn btn-primary" data-action="play">
<i class="glyphicon glyphicon-play"></i>
Play
/
<i class="glyphicon glyphicon-pause"></i>
Pause
</button>
</div>
<audio src='../media/demo.wav' controls />
</div>
<div class="row marketing">
<div class="col-lg-4">
<h4>wavesurfer.js Media Session Plugin</h4>
<p itemprop="about">The <a href='https://wicg.github.io/mediasession/' target='blank'>Media Session API</a> plugin for <strong>wavesurfer.js</strong> allows you to customize media notifications
by providing metadata for the media your web app is playing. It also allows you to handle media related events such as
seeking or playback which may come from notifications or media keys.</p>
<p><strong>Note</strong>: this plugin only works in Chrome 57 and newer (Firefox is <a href='https://bugzilla.mozilla.org/show_bug.cgi?id=1112032' target='blank'>working on it</a>).</p>
<h4>Installation</h4>
<p>
<ol>
<li>add the MediaSession plugin script tag</li>
<li>create a <code>WaveSurfer</code> instance and supply an object for the <code>metadata</code> property</li>
<li>create a <code>MediaSession</code> instance</li>
<li>control playback from the notification screen on a mobile device</li>
</ol>
</p>
<p>
<a class="btn btn-large btn-success" href="../../plugin/wavesurfer.mediasession.js" itemprop="downloadUrl">Download <strong>the plugin</strong> (1.2 KB)</a>
</p>
</div>
<div class="col-lg-8">
<h4>Quick Start</h4>
<noindex><p>
<pre><code>var wavesurfer = Object.create(WaveSurfer);
wavesurfer.init({
container : '#waveform',
waveColor : 'black',
backend : 'MediaElement'
});
var msPlugin = Object.create(WaveSurfer.MediaSession);
msPlugin.init({
wavesurfer: wavesurfer,
metadata: {
title: 'Wavesurfer.js Example',
artist: 'The Wavesurfer.js Project',
album: 'Media Session Example',
artwork: [
{src: 'https://dummyimage.com/96x96', sizes: '96x96', type: 'image/png'},
{src: 'https://dummyimage.com/128x128', sizes: '128x128', type: 'image/png'},
{src: 'https://dummyimage.com/192x192', sizes: '192x192', type: 'image/png'},
{src: 'https://dummyimage.com/256x256', sizes: '256x256', type: 'image/png'},
{src: 'https://dummyimage.com/384x384', sizes: '384x384', type: 'image/png'},
{src: 'https://dummyimage.com/512x512', sizes: '512x512', type: 'image/png'},
]
}
});
// load audio from existing media element
var mediaElt = document.querySelector('audio');
wavesurfer.load(mediaElt);
</code></pre>
</p></noindex>
<br />
<h4>Options</h4>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Name</th>
<th>Required</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>wavesurfer</code></td>
<td>yes</td>
<td></td>
<td>A WaveSurfer instance.</td>
</tr>
<tr>
<td><code>metadata</code></td>
<td>yes</td>
<td></td>
<td>A <a href='https://wicg.github.io/mediasession/#mediametadata' target='blank'>MediaMetadata</a> object: a representation of the metadata associated with a MediaSession that can be used by
user agents to provide customized user interface.</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="footer row">
<div class="col-sm-12">
<a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
</div>
<div class="col-sm-12">
<span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
</div>
</div>
</div>
<div class="github-fork-ribbon-wrapper right">
<div class="github-fork-ribbon">
<a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
</div>
</div>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-50026819-1', 'wavesurfer.fm');
ga('send', 'pageview');
</script>
</body>
</html>
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,36 @@
'use strict';
// Create an instance
var wavesurfer;
// Init & load
document.addEventListener('DOMContentLoaded', function () {
var micBtn = document.querySelector('#micBtn');
// Init wavesurfer
wavesurfer = WaveSurfer.create({
container : '#waveform',
waveColor : 'black',
interact : false,
cursorWidth : 0,
plugins: [
WaveSurfer.microphone.create()
]
});
wavesurfer.microphone.on('deviceReady', function() {
console.info('Device ready!');
});
wavesurfer.microphone.on('deviceError', function(code) {
console.warn('Device error: ' + code);
});
// start/stop mic on button click
micBtn.onclick = function() {
if (wavesurfer.microphone.active) {
wavesurfer.microphone.stop();
} else {
wavesurfer.microphone.start();
}
};
});
@@ -0,0 +1,198 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>wavesurfer.js | Microphone plugin</title>
<link href="data:image/gif;" rel="icon" type="image/x-icon" />
<!-- Bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/ribbon.css" />
<!-- wavesurfer.js -->
<script src="../../dist/wavesurfer.js"></script>
<!-- microphone renderer -->
<script src="../../dist/plugin/wavesurfer.microphone.js"></script>
<!-- App -->
<script src="app.js"></script>
</head>
<body itemscope itemtype="http://schema.org/WebApplication">
<div class="container">
<div class="header">
<h1 itemprop="name"><a href="http://wavesurfer-js.org">wavesurfer.js</a><noindex> + Microphone</noindex></h1>
</div>
<div id="demo">
<div id="waveform"></div>
<div class="controls">
<button id="micBtn" class="btn btn-primary" data-action="start">
Microphone:
<i class="glyphicon glyphicon-play"></i>
Start
/
<i class="glyphicon glyphicon-stop"></i>
Stop
</button>
</div>
</div>
<div class="row marketing">
<div class="col-lg-4">
<h4>wavesurfer.js Microphone Plugin</h4>
<p itemprop="about">Visualizes audio input from a microphone in <strong>wavesurfer.js</strong> instances.</p>
<h4>Installation</h4>
<p>
<ol>
<li>add the Microphone plugin to the plugins property of the wavesurfer options</li>
<li>create a new instance of wavesurfer by using the create function</li>
<li>control the Microphone using the <code>start</code>, <code>stopDevice</code>, <code>play</code>, <code>pause</code>, <code>stop</code> and <code>togglePlay</code> methods</li>
</ol>
</p>
<p>
<a class="btn btn-large btn-success" href="../../dist/plugin/wavesurfer.microphone.min.js" itemprop="downloadUrl" download>Download the plugin</a>
</p>
</div>
<div class="col-lg-8">
<h4>Quick Start</h4>
<noindex><p>
<pre><code>var wavesurfer = WaveSurfer.create({
container : '#waveform',
waveColor : 'black',
interact : false,
cursorWidth : 0,
plugins: [
WaveSurfer.microphone.create()
]
});
wavesurfer.microphone.on('deviceReady', function(stream) {
console.log('Device ready!', stream);
});
wavesurfer.microphone.on('deviceError', function(code) {
console.warn('Device error: ' + code);
});
// start the microphone
wavesurfer.microphone.start();
// pause rendering
//wavesurfer.microphone.pause();
// resume rendering
//wavesurfer.microphone.play();
// stop visualization and disconnect microphone
//wavesurfer.microphone.stopDevice();
// same as stopDevice() but also clears the wavesurfer canvas
//wavesurfer.microphone.stop();
// destroy the plugin
//wavesurfer.microphone.destroy();
</code></pre>
</p></noindex>
<br />
<h4>Options</h4>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Name</th>
<th>Required</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>wavesurfer</code></td>
<td>yes</td>
<td></td>
<td>A WaveSurfer instance.</td>
</tr>
<tr>
<td><code>bufferSize</code></td>
<td>no</td>
<td>4096</td>
<td>The buffer size in units of sample-frames. If specified, the <code>bufferSize</code> must be one of the following values: 256, 512, 1024, 2048, 4096, 8192, 16384.</td>
</tr>
<tr>
<td><code>numberOfInputChannels</code></td>
<td>no</td>
<td>1</td>
<td>Integer specifying the number of channels for this node's input. Values of up to 32 are supported.</td>
</tr>
<tr>
<td><code>numberOfOutputChannels</code></td>
<td>no</td>
<td>1</td>
<td>Integer specifying the number of channels for this node's output. Values of up to 32 are supported.</td>
</tr>
</tbody>
</table>
<h4>Events</h4>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>deviceReady</code></td>
<td>Invoked when the device is ready to use. Callback will receive a <code>LocalMediaStream</code> object that contains the microphone stream.</td>
</tr>
<tr>
<td><code>deviceError</code></td>
<td>Invoked when the user doesn't allow the browser to access the microphone. Callback will receive a (string) <a href='https://developer.mozilla.org/en-US/docs/NavigatorUserMedia.getUserMedia#errorCallback'>error code</a>.</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="footer row">
<div class="col-sm-12">
<a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
</div>
<div class="col-sm-12">
<span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
</div>
</div>
</div>
<div class="github-fork-ribbon-wrapper right">
<div class="github-fork-ribbon">
<a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
</div>
</div>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-50026819-1', 'wavesurfer.fm');
ga('send', 'pageview');
</script>
</body>
</html>
+40
View File
@@ -0,0 +1,40 @@
'use strict';
// Create an instance
var wavesurfer;
// Init & load
document.addEventListener('DOMContentLoaded', function () {
var playButton = document.querySelector('#playBtn'),
toggleMuteButton = document.querySelector('#toggleMuteBtn'),
setMuteOnButton = document.querySelector('#setMuteOnBtn'),
setMuteOffButton = document.querySelector('#setMuteOffBtn');
// Init wavesurfer
wavesurfer = WaveSurfer.create({
container : '#waveform',
waveColor : 'black',
interact : false,
cursorWidth : 0
});
wavesurfer.load('../media/demo.wav');
wavesurfer.on('ready', function() {
playButton.onclick = function() {
wavesurfer.playPause();
};
toggleMuteButton.onclick = function() {
wavesurfer.toggleMute();
}
setMuteOnButton.onclick = function() {
wavesurfer.setMute(true);
}
setMuteOffButton.onclick = function() {
wavesurfer.setMute(false);
}
});
});
@@ -0,0 +1,75 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>wavesurfer.js | Mute Example</title>
<link href="data:image/gif;" rel="icon" type="image/x-icon" />
<!-- Bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/ribbon.css" />
<!-- wavesurfer.js -->
<script src="../../dist/wavesurfer.js"></script>
<!-- App -->
<script src="app.js"></script>
</head>
<body>
<div class="container">
<div class="header">
<h1>
<a href="http://wavesurfer-js.org">wavesurfer.j</a> (Muting)
</h1>
</div>
<div id="demo">
<div id="waveform"></div>
<div class="controls">
<button id="playBtn" class="btn btn-primary">Play / Pause</button>
<button id="toggleMuteBtn" class="btn btn-primary">Toggle Mute</button>
<button id="setMuteOnBtn" class="btn btn-primary">
Mute <i class="glyphicon glyphicon-volume-off"></i>
</button>
<button id="setMuteOffBtn" class="btn btn-primary">
Unmute <i class="glyphicon glyphicon-volume-up"></i>
</button>
</div>
</div>
<div class="footer row">
<div class="col-sm-12">
<a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
</div>
<div class="col-sm-12">
<span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
</div>
</div>
</div>
<div class="github-fork-ribbon-wrapper right">
<div class="github-fork-ribbon">
<a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
</div>
</div>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-50026819-1', 'wavesurfer.fm');
ga('send', 'pageview');
</script>
</body>
</html>
@@ -0,0 +1,166 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>wavesurfer.js | Panner Example</title>
<link href="data:image/gif;" rel="icon" type="image/x-icon" />
<!-- Bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/ribbon.css" />
<link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
<!-- wavesurfer.js -->
<script src="../../dist/wavesurfer.js"></script>
<!-- Demo -->
<script src="main.js"></script>
</head>
<body itemscope itemtype="http://schema.org/WebApplication">
<div class="container">
<div class="header">
<ul class="nav nav-pills pull-right">
<li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
</ul>
<h1 itemprop="name">Panner Filter Example</h1>
</div>
<div id="demo">
<div id="waveform">
<div class="progress progress-striped active" id="progress-bar">
<div class="progress-bar progress-bar-info"></div>
</div>
<!-- Here be the waveform -->
</div>
<div class="controls">
<button class="btn btn-primary" data-action="play">
<i class="glyphicon glyphicon-play"></i>
Play
/
<i class="glyphicon glyphicon-pause"></i>
Pause
</button>
<hr />
<div class="row">
<div class="col-sm-4">
&larr; left
</div>
<div class="col-sm-4">
<!-- Panner -->
<input data-action="pan" type="range" min="-45" max="45" value="0" style="width: 100%" />
</div>
<div class="col-sm-4">
right &rarr;
</div>
</div>
</div>
</div>
<div class="row marketing">
<h3>How to Create a Panner Interface</h3>
<p>
This is an example of how to add an arbitrary Web
Audio node into a wavesurfer.js graph. Panner node
is one such node.
</p>
<hr />
<div class="col-lg-6">
<h4>1. Initialize wavesurfer.js</h4>
<p>Create a <code>WaveSurfer</code> instance and load an audio clip.</p>
<noindex><p>
<pre><code>var wavesurfer = WaveSurfer.create({
container: '#demo' // this is the only required param
});
wavesurfer.load('media.wav');</code></pre>
</p></noindex>
<h4>2. Create a Panner Node</h4>
<p>
Create a panner node and add it to the Web
Audio graph using the <code>setFilter</code> method.
</p>
<noindex><p>
<pre><code>var panner = wavesurfer.backend.ac.createPanner();
wavesurfer.backend.setFilter(panner);</code></pre>
</p></noindex>
</div>
<div class="col-lg-6">
<h4>3. Create a Range Slider</h4>
<p>
In your HTML, add a range input.
</p>
<noindex><p>
<pre><code>&lt;input id="panner-input" type="range" min="-45" max="45" value="0" /&gt;</code></pre>
</p></noindex>
<h4>4. Bind the Range Slider</h4>
<p>
Listen to the range input's <code>input</code> event and set the panner's position
according to the input's value.
<small>Adapted from <a href="http://stackoverflow.com/a/14412601/352796">this SO answer</a>.</small>
</p>
<noindex><p>
<pre><code>var slider = document.querySelector('#panner-input');
slider.addEventListener('input', function (e) {
var xDeg = parseInt(e.target.value);
var x = Math.sin(xDeg * (Math.PI / 180));
wavesurfer.panner.setPosition(x, 0, 0);
});</code></pre>
</p></noindex>
</div>
</div>
<div class="footer row">
<div class="col-sm-12">
<a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
</div>
<div class="col-sm-7">
<span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a&nbsp;<a style="white-space: nowrap" rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
</div>
<div class="col-sm-5">
<div class="pull-right">
<noindex>
The audio file is from <a rel="nofollow" href="http://spokencorpora.ru/">spokencorpora.ru</a>, used with permission.
</noindex>
</div>
</div>
</div>
</div>
<div class="github-fork-ribbon-wrapper right">
<div class="github-fork-ribbon">
<a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
</div>
</div>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-50026819-1', 'wavesurfer.fm');
ga('send', 'pageview');
</script>
</body>
</html>
@@ -0,0 +1,68 @@
'use strict';
// Create an instance
var wavesurfer;
// Init & load audio file
document.addEventListener('DOMContentLoaded', function () {
// Init
wavesurfer = WaveSurfer.create({
container: document.querySelector('#waveform'),
minPxPerSec: 30,
scrollParent: true,
waveColor: '#A8DBA8',
progressColor: '#3B8686'
});
// Load audio from URL
wavesurfer.load('media.wav');
// Panner
(function () {
// Add panner
wavesurfer.panner = wavesurfer.backend.ac.createPanner();
wavesurfer.backend.setFilter(wavesurfer.panner);
// Bind panner slider
// @see http://stackoverflow.com/a/14412601/352796
var onChange = function () {
var xDeg = parseInt(slider.value);
var x = Math.sin(xDeg * (Math.PI / 180));
wavesurfer.panner.setPosition(x, 0, 0);
};
var slider = document.querySelector('[data-action="pan"]');
slider.addEventListener('input', onChange);
slider.addEventListener('change', onChange);
onChange();
}());
// Log errors
wavesurfer.on('error', function (msg) {
console.log(msg);
});
// Bind play/pause button
document.querySelector(
'[data-action="play"]'
).addEventListener('click', wavesurfer.playPause.bind(wavesurfer));
// Progress bar
(function () {
var progressDiv = document.querySelector('#progress-bar');
var progressBar = progressDiv.querySelector('.progress-bar');
var showProgress = function (percent) {
progressDiv.style.display = 'block';
progressBar.style.width = percent + '%';
};
var hideProgress = function () {
progressDiv.style.display = 'none';
};
wavesurfer.on('loading', showProgress);
wavesurfer.on('ready', hideProgress);
wavesurfer.on('destroy', hideProgress);
wavesurfer.on('error', hideProgress);
}());
});
Binary file not shown.
@@ -0,0 +1,67 @@
// Create a WaveSurfer instance
var wavesurfer;
// Init on DOM ready
document.addEventListener('DOMContentLoaded', function () {
wavesurfer = WaveSurfer.create({
container: '#waveform',
waveColor: '#428bca',
progressColor: '#31708f',
height: 120,
barWidth: 3
});
});
// Bind controls
document.addEventListener('DOMContentLoaded', function () {
var playPause = document.querySelector('#playPause');
playPause.addEventListener('click', function () {
wavesurfer.playPause();
});
// Toggle play/pause text
wavesurfer.on('play', function () {
document.querySelector('#play').style.display = 'none';
document.querySelector('#pause').style.display = '';
});
wavesurfer.on('pause', function () {
document.querySelector('#play').style.display = '';
document.querySelector('#pause').style.display = 'none';
});
// The playlist links
var links = document.querySelectorAll('#playlist a');
var currentTrack = 0;
// Load a track by index and highlight the corresponding link
var setCurrentSong = function (index) {
links[currentTrack].classList.remove('active');
currentTrack = index;
links[currentTrack].classList.add('active');
wavesurfer.load(links[currentTrack].href);
};
// Load the track on click
Array.prototype.forEach.call(links, function (link, index) {
link.addEventListener('click', function (e) {
e.preventDefault();
setCurrentSong(index);
});
});
// Play on audio load
wavesurfer.on('ready', function () {
wavesurfer.play();
});
// Go to the next track on finish
wavesurfer.on('finish', function () {
setCurrentSong((currentTrack + 1) % links.length);
});
// Load the first track
setCurrentSong(currentTrack);
});
@@ -0,0 +1,112 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>wavesurfer.js | Playlist</title>
<link href="data:image/gif;" rel="icon" type="image/x-icon" />
<!-- Bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/ribbon.css" />
<link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
<!-- wavesurfer.js -->
<script src="../../dist/wavesurfer.min.js"></script>
<!-- App -->
<script src="app.js"></script>
</head>
<body>
<div class="container">
<div class="header">
<ul class="nav nav-pills pull-right">
<li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
</ul>
<h1 itemprop="name">wavesurfer.js Playlist Demo</h1>
</div>
<div id="demo">
<div class="row" style="margin: 30px 0">
<div class="col-sm-10">
<div id="waveform">
<!-- Here be waveform -->
</div>
</div>
<div class="col-sm-2">
<button class="btn btn-success btn-block" id="playPause">
<span id="play">
<i class="glyphicon glyphicon-play"></i>
Play
</span>
<span id="pause" style="display: none">
<i class="glyphicon glyphicon-pause"></i>
Pause
</span>
</button>
</div>
</div>
<div class="list-group" id="playlist">
<a href="../media/demo.wav" class="list-group-item">
<i class="glyphicon glyphicon-play"></i>
czskamaarù Trou
<span class="badge">0:21</span>
</a>
<a href="../panner/media.wav" class="list-group-item">
<i class="glyphicon glyphicon-play"></i>
日本人の話し
<span class="badge">1:04</span>
</a>
<a href="../elan/transcripts/001z.mp3" class="list-group-item">
<i class="glyphicon glyphicon-play"></i>
Рассказы о сновидениях
<span class="badge badge-info">1:26</span>
</a>
</div>
</div>
<div class="footer row">
<div class="col-sm-12">
</div>
<div class="col-sm-7">
<span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
</div>
<div class="col-sm-5">
<p>
Audio sources:<br />
<a rel="nofollow" href="http://www.jamendo.com/en/track/661578/trou"><b>Trou</b> <span class="muted">by</span>&nbsp;<b>czskamaarù</b></a>,
<a rel="nofollow" href="http://spokencorpora.ru/">spokencorpora.ru</a>
</p>
</div>
</div>
</div>
<div class="github-fork-ribbon-wrapper right">
<div class="github-fork-ribbon">
<a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
</div>
</div>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-50026819-1', 'wavesurfer.fm');
ga('send', 'pageview');
</script>
</body>
</html>
@@ -0,0 +1,113 @@
'use strict';
var wavesurfer;
// Init & load
document.addEventListener('DOMContentLoaded', function () {
var pluginOptions = {
minimap: {
waveColor : '#777',
progressColor : '#222',
height: 30
},
timeline: {
container: '#wave-timeline'
},
spectrogram: {
container: '#wave-spectrogram'
},
regions: {
regions: [
{
start: 1,
end: 3,
color: 'hsla(400, 100%, 30%, 0.5)'
},
{
start: 4,
end: 5.4
},
{
start: 6.22,
end: 7.1
}
]
},
elan: {
url: '../elan/transcripts/001z.xml',
container: '#annotations',
tiers: {
Text: true,
Comments: true
}
}
};
var options = {
container : '#waveform',
waveColor : 'violet',
progressColor : 'purple',
loaderColor : 'purple',
cursorColor : 'navy',
plugins: [
WaveSurfer.minimap.create(pluginOptions.minimap)
]
};
if (location.search.match('scroll')) {
options.minPxPerSec = 100;
options.scrollParent = true;
}
if (location.search.match('normalize')) {
options.normalize = true;
}
// Init wavesurfer
wavesurfer = WaveSurfer.create(options);
[].forEach.call(document.querySelectorAll('[data-activate-plugin]'), function (el) {
var activePlugins = wavesurfer.initialisedPluginList;
Object.keys(activePlugins).forEach(function(name) {
if (el.dataset.activatePlugin === name) {
el.checked = true;
}
});
});
[].forEach.call(document.querySelectorAll('[data-activate-plugin]'), function (el) {
el.addEventListener('change', function (e) {
var pluginName = e.currentTarget.dataset.activatePlugin;
var activate = e.target.checked;
var options = pluginOptions[pluginName] || {};
var plugin = WaveSurfer[pluginName].create(options);
if (activate) {
wavesurfer.addPlugin(plugin).initPlugin(pluginName);
} else {
wavesurfer.destroyPlugin(pluginName);
}
});
});
/* Progress bar */
(function () {
var progressDiv = document.querySelector('#progress-bar');
var progressBar = progressDiv.querySelector('.progress-bar');
var showProgress = function (percent) {
progressDiv.style.display = 'block';
progressBar.style.width = percent + '%';
};
var hideProgress = function () {
progressDiv.style.display = 'none';
};
wavesurfer.on('loading', showProgress);
wavesurfer.on('ready', hideProgress);
wavesurfer.on('destroy', hideProgress);
wavesurfer.on('error', hideProgress);
}());
wavesurfer.load('../media/demo.wav');
});
@@ -0,0 +1,188 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>wavesurfer.js | Timeline plugin</title>
<link href="data:image/gif;" rel="icon" type="image/x-icon" />
<!-- Bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/ribbon.css" />
<link rel="screenshot" itemprop="screenshot" href="//katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
<!-- wavesurfer.js -->
<script src="../../dist/wavesurfer.js"></script>
<!-- timeline format renderer -->
<script src="../../dist/plugin/wavesurfer.timeline.js"></script>
<script src="../../dist/plugin/wavesurfer.minimap.js"></script>
<script src="../../dist/plugin/wavesurfer.cursor.js"></script>
<script src="../../dist/plugin/wavesurfer.elan.js"></script>
<script src="../../dist/plugin/wavesurfer.microphone.js"></script>
<script src="../../dist/plugin/wavesurfer.regions.js"></script>
<script src="../../dist/plugin/wavesurfer.spectrogram.js"></script>
<!-- App -->
<script src="app.js"></script>
<script src="../trivia.js"></script>
<!-- Styling information for the elan plugin -->
<style type="text/css">
#annotations {
max-height: 300px;
overflow: auto;
}
.wavesurfer-annotations tr.wavesurfer-active td {
background-color: yellow;
}
.wavesurfer-time {
width: 100px;
color: #555;
}
.wavesurfer-tier-Text {
width: 500px;
}
td.wavesurfer-tier-Comments {
color: #999;
}
.wavesurfer-handle {
background-color: #c9e2b3;
}
</style>
</head>
<body itemscope itemtype="http://schema.org/WebApplication">
<div class="container">
<div class="header">
<noindex>
<ul class="nav nav-pills pull-right">
<li><a href="?fill">Fill</a></li>
<li><a href="?scroll">Scroll</a></li>
</ul>
</noindex>
<h1 itemprop="name"><a href="http://wavesurfer-js.org">wavesurfer.js</a><noindex> Plugin system</noindex></h1>
</div>
<div id="demo">
<div id="waveform">
<div class="progress progress-striped active" id="progress-bar">
<div class="progress-bar progress-bar-info"></div>
</div>
<!-- Here be waveform -->
</div>
<div id="wave-timeline"></div>
<div id="wave-spectrogram"></div>
<div id="annotations"></div>
<div class="controls">
</div>
</div>
<div class="row">
<div class="col-lg-4">
<div class="form-group marketing">
<h4>Disable and enable plugins on the fly:</h4>
<hr />
<div class="checkbox">
<label><input type="checkbox" value="" data-activate-plugin="minimap">Minimap</label>
</div>
<div class="checkbox">
<label><input type="checkbox" value="" data-activate-plugin="timeline">Timeline</label>
</div>
<div class="checkbox">
<label><input type="checkbox" value="" data-activate-plugin="cursor">Cursor</label>
</div>
<div class="checkbox">
<label><input type="checkbox" value="" data-activate-plugin="spectrogram">Spectrogram</label>
</div>
<div class="checkbox">
<label><input type="checkbox" value="" data-activate-plugin="regions">Regions</label>
</div>
<div class="checkbox">
<label><input type="checkbox" value="" data-activate-plugin="elan">Elan</label>
</div>
</div>
</div>
<div class="col-lg-8">
<div class="marketing">
<h4>Initialising wavesurfer with plugins</h4>
<hr />
<p>The <code>plugins</code> option is an array of plugin definitions. Calling a plugin with the parameter <code>deferInit: true</code> will stop it from automatically initialising you can do that at a later time with <code>wavesurfer.initPlugin('mypluginname')</code>.</p>
<noindex><p>
<pre><code>var wavesurfer = WaveSurfer.create({
container: '#waveform',
waveColor: 'violet',
// ... other wavesurfer options
plugins: [
WaveSurfer.timeline.create{
container: '#wave-timeline',
// ... other timeline options
})
]
});
wavesurfer.load('example/media/demo.mp3');</code></pre>
</p></noindex>
</div>
<div class="marketing">
<h4>Dynamically adding and initialising a plugin</h4>
<hr />
<noindex><p>
<pre><code>var wavesurfer = WaveSurfer.create({
container: '#waveform',
waveColor: 'violet',
// ... other wavesurfer options
});
// adding and initialising a plugin after initialisation
wavesurfer.addPlugin(WaveSurfer.timeline.create{
container: '#wave-timeline',
// ... other timeline options
})).initPlugin('timeline')
wavesurfer.load('example/media/demo.mp3');</code></pre>
</p></noindex>
</div>
</div>
</div>
<div class="footer row">
<div class="col-sm-12">
<a rel="license" href="https://creativecommons.org/licenses/by/3.0/deed.en_US"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by/3.0/80x15.png" /></a>
</div>
<div class="col-sm-12">
<span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a xmlns:cc="http://creativecommons.org/ns#" href="https://github.com/katspaugh/wavesurfer.js" property="cc:attributionName" rel="cc:attributionURL">katspaugh</a> is licensed under a <a rel="license" href="https://creativecommons.org/licenses/by/3.0/deed.en_US">Creative Commons Attribution 3.0 Unported License</a>.
</div>
</div>
</div>
<div class="github-fork-ribbon-wrapper right">
<div class="github-fork-ribbon">
<a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
</div>
</div>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-50026819-1', 'wavesurfer.fm');
ga('send', 'pageview');
</script>
</body>
</html>
@@ -0,0 +1,42 @@
'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',
backend: 'MediaElement',
plugins: [
WaveSurfer.regions.create({
regions: [
{
start: 1,
end: 3,
color: 'hsla(400, 100%, 30%, 0.5)'
}, {
start: 5,
end: 7,
color: 'hsla(200, 50%, 70%, 0.4)'
}
],
dragSelection: {
slop: 5
}
})
]
});
// Load audio from URL
wavesurfer.load('../media/demo.wav');
// this is already being done in /examples/trivia.js
// document.querySelector(
// '[data-action="play"]'
// ).addEventListener('click', wavesurfer.playPause.bind(wavesurfer));
});
@@ -0,0 +1,120 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>wavesurfer.js | Regions</title>
<link href="data:image/gif;" rel="icon" type="image/x-icon" />
<!-- Bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/ribbon.css" />
<link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
<!-- wavesurfer.js -->
<script src="../../dist/wavesurfer.js"></script>
<!-- plugins -->
<script src="../../dist/plugin/wavesurfer.timeline.js"></script>
<script src="../../dist/plugin/wavesurfer.regions.js"></script>
<script src="../../dist/plugin/wavesurfer.minimap.js"></script>
<!-- App -->
<script src="../trivia.js"></script>
<script src="app.js"></script>
</head>
<body itemscope itemtype="http://schema.org/WebApplication">
<div class="container">
<div class="header">
<ul class="nav nav-pills pull-right">
<li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
</ul>
<h1 itemprop="name">wavesurfer.js Regions</h1>
</div>
<div id="demo">
<div id="waveform">
<!-- Here be waveform -->
</div>
<div class="controls">
<button class="btn btn-primary" data-action="play">
<i class="glyphicon glyphicon-play"></i>
Play
/
<i class="glyphicon glyphicon-pause"></i>
Pause
</button>
</div>
</div>
<div class="row marketing">
<p>
<pre><code>var wavesurfer = WaveSurfer.create({
container: document.querySelector('#waveform'),
waveColor: '#A8DBA8',
progressColor: '#3B8686',
backend: 'MediaElement',
plugins: [
WaveSurfer.regions.create{
regions: [
{
start: 1,
end: 3,
color: 'hsla(400, 100%, 30%, 0.5)'
}, {
start: 5,
end: 7,
color: 'hsla(200, 50%, 70%, 0.4)'
}
],
dragSelection: {
slop: 5
}
})
]
});
</code></pre>
</p>
</div>
<div class="footer row">
<div class="col-sm-12">
<a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
</div>
<div class="col-sm-8">
<span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
</div>
<div class="col-sm-4">
<p>
The sound file is from <a href="https://librivox.org/librivox-multilingual-short-works-collection-001-by-various/">librivox.org</a>.
</p>
</div>
</div>
</div>
<div class="github-fork-ribbon-wrapper right">
<div class="github-fork-ribbon">
<a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
</div>
</div>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-50026819-1', 'wavesurfer.fm');
ga('send', 'pageview');
</script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

@@ -0,0 +1,52 @@
'use strict';
var wavesurfer;
// Init & load
document.addEventListener('DOMContentLoaded', function () {
// Create an instance
wavesurfer = WaveSurfer.create({
container : '#waveform',
waveColor : 'violet',
progressColor : 'purple',
loaderColor : 'purple',
cursorColor : 'navy',
plugins: [
WaveSurfer.spectrogram.create({
container: '#wave-spectrogram'
})
]
});
if (location.search.match('scroll')) {
options.minPxPerSec = 100;
options.scrollParent = true;
}
if (location.search.match('normalize')) {
options.normalize = true;
}
/* Progress bar */
(function () {
var progressDiv = document.querySelector('#progress-bar');
var progressBar = progressDiv.querySelector('.progress-bar');
var showProgress = function (percent) {
progressDiv.style.display = 'block';
progressBar.style.width = percent + '%';
};
var hideProgress = function () {
progressDiv.style.display = 'none';
};
wavesurfer.on('loading', showProgress);
wavesurfer.on('ready', hideProgress);
wavesurfer.on('destroy', hideProgress);
wavesurfer.on('error', hideProgress);
}());
wavesurfer.load('../media/demo.wav');
});
@@ -0,0 +1,138 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>wavesurfer.js | Spectrogram plugin</title>
<link href="data:image/gif;" rel="icon" type="image/x-icon" />
<!-- Bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/ribbon.css" />
<!-- wavesurfer.js -->
<script src="../../dist/wavesurfer.js"></script>
<!-- spectrogram format renderer -->
<script src="../../dist/plugin/wavesurfer.spectrogram.js"></script>
<!-- App -->
<script src="app.js"></script>
<script src="../trivia.js"></script>
</head>
<body itemscope itemtype="http://schema.org/WebApplication">
<div class="container">
<div class="header">
<noindex>
<ul class="nav nav-pills pull-right">
<li><a href="?fill">Fill</a></li>
<li><a href="?scroll">Scroll</a></li>
</ul>
</noindex>
<h1 itemprop="name"><a href="http://wavesurfer-js.org">wavesurfer.js</a><noindex> + Spectrogram</noindex></h1>
</div>
<div id="demo">
<div id="waveform">
<div class="progress progress-striped active" id="progress-bar">
<div class="progress-bar progress-bar-info"></div>
</div>
<!-- Here be waveform -->
</div>
<div id="wave-spectrogram"></div>
<div class="controls">
<button class="btn btn-primary" data-action="play">
<i class="glyphicon glyphicon-play"></i>
Play
/
<i class="glyphicon glyphicon-pause"></i>
Pause
</button>
</div>
</div>
<div class="row marketing">
<div class="col-lg-4">
<h4>wavesurfer.js Spectrogram Plugin</h4>
<p itemprop="about">Adds a simple spectrogram to your <strong>wavesurfer.js</strong> instances.</p>
<h4>Installation</h4>
<p>
<ol>
<li>add the Spectrogram plugin script tag</li>
<li>create a <code>WaveSurfer</code> instance</li>
<li>add a container HTML element for the spectrogram</li>
<li>create a Spectrogram instance in the <code>WaveSurfer</code>'s <code>"ready"</code> event callback</li>
</ol>
</p>
<p>
<a class="btn btn-large btn-success" href="../../plugin/wavesurfer.spectrogram.js" itemprop="downloadUrl" download>Download <strong>the plugin</strong> (5 KB)</a>
</p>
</div>
<div class="col-lg-8">
<h4>Quick Start</h4>
<noindex><p>
<pre><code>var wavesurfer = WaveSurfer.create({
// your options here
plugins: [
WaveSurfer.spectrogram.create{
wavesurfer: wavesurfer,
container: "#wave-spectrogram"
})
]
});
wavesurfer.load('example/media/demo.wav');</code></pre>
</p></noindex>
<br />
<h4>Options</h4>
<ul class="markdown-body">
<li><code>wavesurfer</code> - <em>required</em> - a WaveSurfer instance.</li>
<li><code>container</code> - <em>required</em> - the element in which to place the spectrogram, or a CSS selector to find it.</li>
<li><code>fftSamples</code> - number of FFT samples (<code>512</code> by default). Number of spectral lines and height of the spectrogram will be a half of this parameter.</li>
<li><code>frequenciesDataUrl</code> - URL to load spectral data from.</li>
</ul>
</div>
</div>
<div class="footer row">
<div class="col-sm-12">
<a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
</div>
<div class="col-sm-12">
<span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
</div>
</div>
</div>
<div class="github-fork-ribbon-wrapper right">
<div class="github-fork-ribbon">
<a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
</div>
</div>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-50026819-1', 'wavesurfer.fm');
ga('send', 'pageview');
</script>
</body>
</html>
@@ -0,0 +1,55 @@
// Create an instance
var wavesurfer;
window.onload = function () {
wavesurfer = WaveSurfer.create({
container: document.querySelector('#waveform'),
splitChannels: true
});
// Load audio from URL
wavesurfer.load('stereo.mp3');
// Play/pause on button press
document.querySelector('[data-action="play"]').addEventListener(
'click', wavesurfer.playPause.bind(wavesurfer)
);
// Drag'n'drop
var toggleActive = function (e, toggle) {
e.stopPropagation();
e.preventDefault();
toggle ? e.target.classList.add('wavesurfer-dragover') :
e.target.classList.remove('wavesurfer-dragover');
};
var handlers = {
// Drop event
drop: function (e) {
toggleActive(e, false);
// Load the file into wavesurfer
if (e.dataTransfer.files.length) {
wavesurfer.loadBlob(e.dataTransfer.files[0]);
} else {
wavesurfer.fireEvent('error', 'Not a file');
}
},
// Drag-over event
dragover: function (e) {
toggleActive(e, true);
},
// Drag-leave event
dragleave: function (e) {
toggleActive(e, false);
}
};
var dropTarget = document.querySelector('#drop');
Object.keys(handlers).forEach(function (event) {
dropTarget.addEventListener(event, handlers[event]);
});
};
@@ -0,0 +1,108 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>wavesurfer.js | Split Channel Example</title>
<link href="data:image/gif;" rel="icon" type="image/x-icon" />
<!-- Bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/ribbon.css" />
<link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
<!-- wavesurfer.js -->
<script src="../../dist/wavesurfer.js"></script>
<script src="../../dist/plugin/wavesurfer.regions.js"></script>
<!-- Demo -->
<script src="app.js"></script>
</head>
<body itemscope itemtype="http://schema.org/WebApplication">
<div class="container">
<div class="header">
<ul class="nav nav-pills pull-right">
<li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
</ul>
<h1 itemprop="name">Split Channel Waveforms</h1>
</div>
<div id="demo">
<div id="waveform">
<!-- Here be the waveform -->
</div>
<div class="controls">
<button class="btn btn-primary" data-action="play">
<i class="glyphicon glyphicon-play"></i>
Play
/
<i class="glyphicon glyphicon-pause"></i>
Pause
</button>
</div>
</div>
<p class="lead pull-center" id="drop">
Drag'n'drop your
<i class="glyphicon glyphicon-music"></i>-file
here!
</p>
<div class="row marketing">
<h3>How to Enable Split Channels</h3>
<p>
Set the <code>splitChannels</code> option to <code>true</code>.
</p>
<p>
<pre><code>var wavesurfer = WaveSurfer.create({
container: document.querySelector('#wave'),
splitChannels: true
});
</code></pre>
</p>
</div>
<div class="footer row">
<div class="col-sm-12">
<a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
</div>
<div class="col-sm-7">
<span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a&nbsp;<a style="white-space: nowrap" rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
</div>
<div class="col-sm-5">
<noindex>
Demo music track is <a href="https://www.jamendo.com/en/track/205154/transistor" rel="nofollow"><b>Transistor</b> <span class="muted">by</span>&nbsp;<b>Transistor</b></a> (CC BY-NC-ND 3.0). Thanks!
</noindex>
</div>
</div>
</div>
<div class="github-fork-ribbon-wrapper right">
<div class="github-fork-ribbon">
<a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
</div>
</div>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-50026819-1', 'wavesurfer.fm');
ga('send', 'pageview');
</script>
</body>
</html>
@@ -0,0 +1,157 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>wavesurfer.js | Split Wave Point Plot Example</title>
<link href="data:image/gif;" rel="icon" type="image/x-icon" />
<!-- Bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/ribbon.css" />
<link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
<!-- wavesurfer.js -->
<script src="../../dist/wavesurfer.min.js"></script>
<script src="../../plugin/wavesurfer.regions.js"></script>
<!-- Demo -->
<script src="app.js"></script>
</head>
<body itemscope itemtype="http://schema.org/WebApplication">
<div class="container">
<div class="header">
<ul class="nav nav-pills pull-right">
<li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
</ul>
<h1 itemprop="name">Split Wave / Point Plot </h1>
</div>
<div>
<p>
The Split Wave Point Plot drawer splits the graphic in two, with the upper half being a plot of
points defined by time and a range of values. The following example shows the calculated pitch
at each point in time.
</p>
</div>
<div id="demo">
<div id="waveform">
<!-- Here be the waveform -->
</div>
<div class="controls">
<button class="btn btn-primary" data-action="play">
<i class="glyphicon glyphicon-play"></i>
Play
/
<i class="glyphicon glyphicon-pause"></i>
Pause
</button>
</div>
</div>
<div class="row marketing">
<h3>How to Enable Split Wave/Point Plot</h3>
<p>
Set the <code>renderer</code> option to <code>SplitWavePointPlot</code> and the <code>plotFileUrl</code> to the file containing the time aligned data.
</p>
<p>
<pre><code>var wavesurfer = WaveSurfer.create({
container: document.querySelector('#wave'),
renderer: 'SplitWavePointPlot',
plotFileUrl: 'data.txt'
});
</code></pre>
</p>
<h3>Providing Point Data</h3>
<p>The data to be graphed can be provided by either providing a file defined in the <code>plotFileUrl</code> or by passing in an array of data in the <code>plotArray</code> option. The time data does not need to be continous and can have gaps.</p>
<strong>Note: if the timing of your data does not span the duration of the sound file you should set <code>plotTimeEnd</code> to the total duration of the sound file or the points may not be aligned correctly</strong></li>
<h4>Data File Format</h4>
<p>If providing data by loading a file, each line of the file must contain two elements: the time and the value of the point separated by a delimiter (defaults to tab). E.g.</p>
<pre><code>0.01 123
0.02 121
0.03 127
0.22 120
0.23 119</code></pre>
<h4>Data Array Format</h4>
<p>If providing data by a javascript array via the <code>plotArray</code> option the array should have the following form:</p>
<pre><code>[
{time: 0.02, value: 121},
{time: 0.03, value: 127},
{time: 0.22, value: 120},
{time: 0.03, value: 119}
]</code></pre>
<h3>Options</h3>
<p>The following additional options can be set when initializing wavesurfer to control the waveform</p>
<ul>
<li><code>plotArray:</code> array of objects with time and plot (required unless plotFileUrl is set)</li>
<li><code>plotFileUrl:</code> url of the file that contains the plot information (required unless plotArray is set)</li>
<li><code>plotNormalizeTo:</code> [whole/segment/none/values] - what value to normalize the plot to (defaults to "whole")</li>
<li><code>plotMin:</code> the minimum value to normalize points to. Any value below this will be ignored (defaults to 0)</li>
<li><code>plotMax:</code> the maximum value to normalize points to. Any value above this will be ignored (defaults to 1)</li>
<li><code>plotTimeStart:</code> the time included in the plot file which corresponds with the start of the displayed wave (defaults to 0)</li>
<li><code>plotTimeEnd:</code> the time included in the plot file which corresponds with the end of the displayed wave (defaults maximum plot time)</li>
<li><code>plotColor:</code> the color of the plot (defaults to #f63)</li>
<li><code>plotProgressColor:</code> the color of the progress plot (defaults to '#F00')</li>
<li><code>plotFileDelimiter:</code> the delimiter which separates the time from the value in the plot file (defaults to tab characater = "\t")</li>
<li><code>plotPointHeight:</code> the canvas height of each plot point (defaults to 2)</li>
<li><code>plotPointWidth:</code> the canvas width of each plot point (defaults to 2)</li>
<li><code>plotSeparator:</code> boolean indicating a separator should be included between the wave and point plot</li>
<li><code>plotRangeDisplay:</code> boolean indicating if the min and max range should be displayed (defaults to false)</li>
<li><code>plotRangePrecision:</code> integer determining the precision of the displayed plot range</li>
<li><code>plotRangeUnits:</code> units appended to the range</li>
<li><code>plotRangeFontSize:</code> the font for displaying the range - defaults to 20</li>
<li><code>plotRangeFontType:</code> the font type for displaying range - defaults to Ariel</li>
<li><code>plotRangeIgnoreOutliers:</code> boolean indicating if values outside of range should be ignored or plotted at min/max</li>
</ul>
</div>
<div class="footer row">
<div class="col-sm-12">
<a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
</div>
<div class="col-sm-7">
<span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a&nbsp;<a style="white-space: nowrap" rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
</div>
<div class="col-sm-5">
<noindex>
Demo is selection of from The Hobbit by J.R.R. Tolkien
</noindex>
</div>
</div>
</div>
<div class="github-fork-ribbon-wrapper right">
<div class="github-fork-ribbon">
<a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
</div>
</div>
<script>
/*
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-50026819-1', 'wavesurfer.fm');
ga('send', 'pageview');
*/
</script>
</body>
</html>
@@ -0,0 +1,55 @@
'use strict';
var wavesurfer;
// Init & load
document.addEventListener('DOMContentLoaded', function () {
var options = {
container : '#waveform',
waveColor : 'violet',
progressColor : 'purple',
loaderColor : 'purple',
cursorColor : 'navy',
plugins: [
WaveSurfer.timeline.create({
container: '#wave-timeline'
})
]
};
if (location.search.match('scroll')) {
options.minPxPerSec = 100;
options.scrollParent = true;
}
if (location.search.match('normalize')) {
options.normalize = true;
}
// Init wavesurfer
wavesurfer = WaveSurfer.create(options);
/* Progress bar */
(function () {
var progressDiv = document.querySelector('#progress-bar');
var progressBar = progressDiv.querySelector('.progress-bar');
var showProgress = function (percent) {
progressDiv.style.display = 'block';
progressBar.style.width = percent + '%';
};
var hideProgress = function () {
progressDiv.style.display = 'none';
};
wavesurfer.on('loading', showProgress);
wavesurfer.on('ready', hideProgress);
wavesurfer.on('destroy', hideProgress);
wavesurfer.on('error', hideProgress);
}());
wavesurfer.load('../media/demo.wav');
});
@@ -0,0 +1,136 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>wavesurfer.js | Timeline plugin</title>
<link href="data:image/gif;" rel="icon" type="image/x-icon" />
<!-- Bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/ribbon.css" />
<link rel="screenshot" itemprop="screenshot" href="//katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
<!-- wavesurfer.js -->
<script src="../../dist/wavesurfer.js"></script>
<!-- timeline format renderer -->
<script src="../../dist/plugin/wavesurfer.timeline.js"></script>
<!-- App -->
<script src="app.js"></script>
<script src="../trivia.js"></script>
</head>
<body itemscope itemtype="http://schema.org/WebApplication">
<div class="container">
<div class="header">
<noindex>
<ul class="nav nav-pills pull-right">
<li><a href="?fill">Fill</a></li>
<li><a href="?scroll">Scroll</a></li>
</ul>
</noindex>
<h1 itemprop="name"><a href="http://wavesurfer-js.org">wavesurfer.js</a><noindex> + Timeline</noindex></h1>
</div>
<div id="demo">
<div id="waveform">
<div class="progress progress-striped active" id="progress-bar">
<div class="progress-bar progress-bar-info"></div>
</div>
<!-- Here be waveform -->
</div>
<div id="wave-timeline"></div>
<div class="controls">
<button class="btn btn-primary" data-action="play">
<i class="glyphicon glyphicon-play"></i>
Play
/
<i class="glyphicon glyphicon-pause"></i>
Pause
</button>
</div>
</div>
<div class="row marketing">
<div class="col-lg-4">
<h4>wavesurfer.js Timeline Plugin</h4>
<p itemprop="about">Adds a simple timeline to your <strong>wavesurfer.js</strong> instances.</p>
<p>
<a class="btn btn-large btn-success" href="../../plugin/wavesurfer.timeline.js" itemprop="downloadUrl" download>Download the plugin</a>
</p>
</div>
<div class="col-lg-8">
<h4>Quick Start</h4>
<noindex><p>
<pre><code>var wavesurfer = WaveSurfer.create({
// your options here
plugins: [
WaveSurfer.timeline.create{
container: "#wave-timeline"
})
]
});
wavesurfer.load('example/media/demo.mp3');</code></pre>
</p></noindex>
<br />
<h4>Options</h4>
<ul class="markdown-body">
<li><code>container</code> - <em>required</em> - the element in which to place the timeline, or a CSS selector to find it</li>
<li><code>height</code> - the height (in pixels) of the timeline. The default is 20.</li>
<li><code>notchPercentHeight</code> - the height (in percent) of the minor notch lines in the timeline. The default is 90.</li>
<li><code>primaryColor</code> - the color of the modulo-ten notch lines (e.g. 10sec, 20sec). The default is '#000'.</li>
<li><code>secondaryColor</code> - the color of the non-modulo-ten notch lines. The default is '#c0c0c0'.</li>
<li><code>primaryFontColor</code> - the color of the non-modulo-ten time labels (e.g. 10sec, 20sec). The default is '#000'.</li>
<li><code>primaryFontColor</code> - the color of the non-modulo-ten time labels (e.g. 5sec, 15sec). The default is '#c0c0c0'.</li>
<li><code>timeInterval</code> - number of intervals that records consists of. Usually it is equal to the duration in minutes. (Integer or function which receives pxPerSec value and reurns value)</li>
<li><code>primaryLabelInterval</code> - number of primary time labels. (Integer or function which receives pxPerSec value and reurns value)</li>
<li><code>secondaryLabelInterval</code> - number of secondary time labels (Time labels between primary labels, integer or function which receives pxPerSec value and reurns value).</li>
<li><code>formatTimeCallback</code> - custom time format callback. (Function which receives number of seconds and returns formatted string)</li>
</ul>
</div>
</div>
<div class="footer row">
<div class="col-sm-12">
<a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
</div>
<div class="col-sm-12">
<span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
</div>
</div>
</div>
<div class="github-fork-ribbon-wrapper right">
<div class="github-fork-ribbon">
<a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
</div>
</div>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-50026819-1', 'wavesurfer.fm');
ga('send', 'pageview');
</script>
</body>
</html>
+72
View File
@@ -0,0 +1,72 @@
var GLOBAL_ACTIONS = {
'play': function () {
wavesurfer.playPause();
},
'back': function () {
wavesurfer.skipBackward();
},
'forth': function () {
wavesurfer.skipForward();
},
'toggle-mute': function () {
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
};
var action = map[e.keyCode];
if (action in GLOBAL_ACTIONS) {
if (document == e.target || document.body == e.target) {
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;
if (action in GLOBAL_ACTIONS) {
e.preventDefault();
GLOBAL_ACTIONS[action](e);
}
});
});
});
// Misc
document.addEventListener('DOMContentLoaded', function () {
// Web Audio not supported
if (!window.AudioContext && !window.webkitAudioContext) {
var demo = document.querySelector('#demo');
if (demo) {
demo.innerHTML = '<img src="/example/screenshot.png" />';
}
}
// Navbar links
var ul = document.querySelector('.nav-pills');
var pills = ul.querySelectorAll('li');
var active = pills[0];
if (location.search) {
var first = location.search.split('&')[0];
var link = ul.querySelector('a[href="' + first + '"]');
if (link) {
active = link.parentNode;
}
}
active && active.classList.add('active');
});
@@ -0,0 +1,145 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>wavesurfer.js | Media Element Example</title>
<link href="data:image/gif;" rel="icon" type="image/x-icon" />
<!-- Bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/ribbon.css" />
<link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
<!-- wavesurfer.js -->
<script src="../../dist/wavesurfer.js"></script>
<script src="../../dist/plugin/wavesurfer.regions.js"></script>
<!-- Demo -->
<script src="main.js"></script>
</head>
<body itemscope itemtype="http://schema.org/WebApplication">
<div class="container">
<div class="header">
<ul class="nav nav-pills pull-right">
<li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
</ul>
<h1 itemprop="name">Existing Media Element Example</h1>
</div>
<div id="demo">
<div id="waveform">
<!-- Here be the waveform -->
</div>
<div class="controls">
<button class="btn btn-primary" data-action="play">
<i class="glyphicon glyphicon-play"></i>
Play
/
<i class="glyphicon glyphicon-pause"></i>
Pause
</button>
</div>
<video src="../media/demo_video.mp4" type="video/mpeg"></video>
</div>
<div class="row marketing">
<h3>How to Enable the Fallback</h3>
<hr />
<p>
You can choose to use an existing
html5 audio or video element manually. Simply set the <code>backend</code>
option to <code>"MediaElement"</code> 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.
</p>
<p>
<pre><code>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);</code></pre>
</p>
<h3>Pre-rendered Peaks</h3>
<p>
If you have pre-rendered peaks (on your server),
you can pass them into the <code>load</code>
function. This is optionalif you don't provide
any peaks,
<strong>waserver.js</strong> 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.
</p>
<p>
<pre><code>
// init with an array of peak data.
wavesurfer.load(mediaElt, [0.0218, 0.0183, 0.0165, 0.0198, 0.2137]);
</code></pre>
</p>
<p>
Press this button to see the same demo with pre-decoded peaks:
<button class="btn btn-warning" data-action="peaks">
Load with pre-rendered peaks
</button>
</p>
</div>
<div class="footer row">
<div class="col-sm-12">
<a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
</div>
<div class="col-sm-7">
<span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a&nbsp;<a style="white-space: nowrap" rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
</div>
<div class="col-sm-5">
<div class="pull-right">
<noindex>
The audio file is from <a rel="nofollow" href="http://spokencorpora.ru/">spokencorpora.ru</a>, used with permission.
</noindex>
</div>
</div>
</div>
</div>
<div class="github-fork-ribbon-wrapper right">
<div class="github-fork-ribbon">
<a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
</div>
</div>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-50026819-1', 'wavesurfer.fm');
ga('send', 'pageview');
</script>
</body>
</html>
@@ -0,0 +1,34 @@
'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',
backend: 'MediaElement'
});
// Load audio from existing media element
var mediaElt = document.querySelector('video');
wavesurfer.load(mediaElt);
document.querySelector(
'[data-action="play"]'
).addEventListener('click', wavesurfer.playPause.bind(wavesurfer));
document.querySelector(
'[data-action="peaks"]'
).addEventListener('click', function () {
wavesurfer.load(mediaElt, [
0.0218, 0.0183, 0.0165, 0.0198, 0.2137, 0.2888, 0.2313, 0.15, 0.2542, 0.2538, 0.2358, 0.1195, 0.1591, 0.2599, 0.2742, 0.1447, 0.2328, 0.1878, 0.1988, 0.1645, 0.1218, 0.2005, 0.2828, 0.2051, 0.1664, 0.1181, 0.1621, 0.2966, 0.189, 0.246, 0.2445, 0.1621, 0.1618, 0.189, 0.2354, 0.1561, 0.1638, 0.2799, 0.0923, 0.1659, 0.1675, 0.1268, 0.0984, 0.0997, 0.1248, 0.1495, 0.1431, 0.1236, 0.1755, 0.1183, 0.1349, 0.1018, 0.1109, 0.1833, 0.1813, 0.1422, 0.0961, 0.1191, 0.0791, 0.0631, 0.0315, 0.0157, 0.0166, 0.0108
]);
document.body.scrollTop = 0;
});
});
@@ -0,0 +1,122 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>wavesurfer.js | Zoom Example</title>
<link href="data:image/gif;" rel="icon" type="image/x-icon" />
<!-- Bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/ribbon.css" />
<link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
<!-- wavesurfer.js -->
<script src="../../dist/wavesurfer.js"></script>
<script src="../../dist/plugin/wavesurfer.timeline.js"></script>
<script src="../../dist/plugin/wavesurfer.regions.js"></script>
<!-- Demo -->
<script src="main.js"></script>
</head>
<body itemscope itemtype="http://schema.org/WebApplication">
<div class="container">
<div class="header">
<ul class="nav nav-pills pull-right">
<li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
</ul>
<h1 itemprop="name">Zooming Feature Example</h1>
</div>
<div id="demo">
<div id="waveform">
<!-- Here be the waveform -->
</div>
<div id="timeline"></div>
<div class="controls">
<div class="row">
<div class="col-sm-7">
<button class="btn btn-primary" data-action="play">
<i class="glyphicon glyphicon-play"></i>
Play
/
<i class="glyphicon glyphicon-pause"></i>
Pause
</button>
</div>
<div class="col-sm-1">
<i class="glyphicon glyphicon-zoom-in"></i>
</div>
<div class="col-sm-3">
<input data-action="zoom" type="range" min="1" max="200" value="0" style="width: 100%" />
</div>
<div class="col-sm-1">
<i class="glyphicon glyphicon-zoom-out"></i>
</div>
</div>
</div>
</div>
<div class="row marketing">
<h3>How to Zoom</h3>
<hr />
<p>
<pre><code>var wavesurfer = WaveSurfer.create({
container: document.querySelector('#wave'),
backend: 'MediaElement'
});
document.querySelector('#slider').oninput = function () {
wavesurfer.zoom(Number(this.value));
};
</code></pre>
</p>
</div>
<div class="footer row">
<div class="col-sm-12">
<a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
</div>
<div class="col-sm-7">
<span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a&nbsp;<a style="white-space: nowrap" rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
</div>
<div class="col-sm-5">
<div class="pull-right">
<noindex>
The audio file is from <a rel="nofollow" href="http://spokencorpora.ru/">spokencorpora.ru</a>, used with permission.
</noindex>
</div>
</div>
</div>
</div>
<div class="github-fork-ribbon-wrapper right">
<div class="github-fork-ribbon">
<a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
</div>
</div>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-50026819-1', 'wavesurfer.fm');
ga('send', 'pageview');
</script>
</body>
</html>
+54
View File
@@ -0,0 +1,54 @@
'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',
backend: 'MediaElement',
plugins: [
WaveSurfer.regions.create({
regions: [
{
start: 0,
end: 5,
color: 'hsla(400, 100%, 30%, 0.1)'
}, {
start: 10,
end: 100,
color: 'hsla(200, 50%, 70%, 0.1)'
}
]
}),
WaveSurfer.timeline.create({
container: '#timeline'
})
]
});
// Load audio from URL
wavesurfer.load('../media/demo.wav');
// Zoom slider
var slider = document.querySelector('[data-action="zoom"]');
slider.value = wavesurfer.params.minPxPerSec;
slider.min = wavesurfer.params.minPxPerSec;
slider.addEventListener('input', function () {
wavesurfer.zoom(Number(this.value));
});
// Play button
var button = document.querySelector('[data-action="play"]');
button.addEventListener('click', wavesurfer.playPause.bind(wavesurfer));
});
+65
View File
@@ -0,0 +1,65 @@
require('babel-register');
var webpackConfig = require('./webpack.config.babel.js').default({ test: true });
module.exports = function (config) {
var configuration = {
basePath: '',
frameworks: [
'jasmine',
'jasmine-matchers'
],
hostname: 'localhost',
port: 9876,
singleRun: true,
autoWatch: false,
files: [
{
pattern: 'spec/support/demo.wav',
included: false,
watched: false,
served: true
},
// specs
'spec/plugin-api.spec.js',
'spec/util.spec.js',
'spec/wavesurfer.spec.js',
'spec/peakcache.spec.js'
],
preprocessors: {
'spec/plugin-api.spec.js': ['webpack'],
'spec/util.spec.js': ['webpack'],
'spec/wavesurfer.spec.js': ['webpack'],
'spec/peakcache.spec.js': ['webpack']
},
webpackMiddleware: {
stats: 'errors-only'
},
plugins: [
'karma-webpack',
'karma-jasmine',
'karma-jasmine-matchers',
'karma-chrome-launcher'
],
browsers: [
'Chrome'
],
captureConsole: true,
colors: true,
reporters: ['progress'],
webpack: webpackConfig,
customLaunchers: {
Chrome_travis_ci: {
base: 'Chrome',
flags: ['--no-sandbox']
}
}
};
if (process.env.TRAVIS) {
configuration.browsers = ['Chrome_travis_ci'];
}
config.set(configuration);
};
+55
View File
@@ -0,0 +1,55 @@
{
"name": "wavesurfer.js",
"version": "2.0.0-beta02",
"description": "Interactive navigable audio visualization using Web Audio and Canvas",
"main": "dist/wavesurfer.min.js",
"directories": {
"example": "example"
},
"scripts": {
"start": "webpack --env.plugins && webpack --env.htmlinit && webpack-dev-server",
"start:htmlinit": "webpack && webpack --env.plugins && webpack-dev-server --env.htmlinit",
"start:plugins": "webpack && webpack --env.htmlinit && webpack-dev-server --env.plugins",
"build": "npm run build:normal & npm run build:minified",
"build:normal": "webpack & webpack --env.plugins & webpack --env.htmlinit",
"build:minified": "webpack --env.minify & webpack --env.plugins --env.minify & webpack --env.htmlinit --env.minify",
"doc": "esdoc",
"test": "BABEL_ENV=test karma start karma.conf.js"
},
"repository": {
"type": "git",
"url": "git://github.com/katspaugh/wavesurfer.js.git"
},
"author": "",
"license": "BSD-3-Clause",
"bugs": {
"url": "https://github.com/katspaugh/wavesurfer.js/issues"
},
"devDependencies": {
"babel-core": "^6.18.2",
"babel-eslint": "^7.1.1",
"babel-loader": "^6.2.7",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-istanbul": "^2.0.3",
"babel-plugin-transform-class-properties": "^6.22.0",
"babel-preset-es2015": "^6.18.0",
"babel-preset-es2015-native-modules": "^6.9.4",
"babel-preset-stage-0": "^6.22.0",
"babel-register": "^6.18.0",
"debounce": "^1.0.0",
"esdoc": "^0.5.2",
"eslint": "^3.9.1",
"eslint-loader": "^1.6.1",
"jasmine-core": "^2.5.2",
"karma": "^1.3.0",
"karma-chrome-launcher": "2.0.0",
"karma-coverage": "^1.1.1",
"karma-jasmine": "1.0.2",
"karma-jasmine-matchers": "3.0.1",
"load-script": "^1.0.0",
"karma-webpack": "^2.0.2",
"webpack": "2",
"webpack-dev-server": "2"
},
"homepage": "https://github.com/katspaugh/wavesurfer.js"
}
+108
View File
@@ -0,0 +1,108 @@
import PeakCache from '../src/peakcache';
describe('PeakCache:', function() {
var peakcache;
var test_length = 200;
var test_length2 = 300;
var test_start = 50;
var test_end = 100;
var test_start2 = 100;
var test_end2 = 120;
var test_start3 = 120;
var test_end3 = 150;
var window_size = 20;
function __createPeakCache() {
peakcache = new PeakCache();
}
beforeEach(function (done) {
__createPeakCache();
done();
});
/** @test {PeakCache#addRangeToPeakCache} */
it('empty cache returns full range', function() {
var newranges = peakcache.addRangeToPeakCache(test_length, test_start, test_end);
expect(newranges.length).toEqual(1);
expect(newranges[0][0]).toEqual(test_start);
expect(newranges[0][1]).toEqual(test_end);
});
/** @test {PeakCache#addRangeToPeakCache} */
it('different length clears cache', function() {
peakcache.addRangeToPeakCache(test_length, test_start, test_end);
var newranges = peakcache.addRangeToPeakCache(test_length2, test_start, test_end);
expect(newranges.length).toEqual(1);
expect(newranges[0][0]).toEqual(test_start);
expect(newranges[0][1]).toEqual(test_end);
});
/** @test {PeakCache#addRangeToPeakCache} */
it('consecutive calls return no ranges', function() {
peakcache.addRangeToPeakCache(test_length, test_start, test_end);
var newranges = peakcache.addRangeToPeakCache(test_length, test_start, test_end);
expect(newranges.length).toEqual(0);
});
/** @test {PeakCache#addRangeToPeakCache} */
it('sliding window returns window sized range', function() {
var newranges = peakcache.addRangeToPeakCache(test_length, test_start, test_end);
expect(newranges.length).toEqual(1);
expect(newranges[0][0]).toEqual(test_start);
expect(newranges[0][1]).toEqual(test_end);
var newranges = peakcache.addRangeToPeakCache(test_length, test_start + window_size, test_end + window_size);
expect(newranges.length).toEqual(1);
expect(newranges[0][0]).toEqual(test_end);
expect(newranges[0][1]).toEqual(test_end + window_size);
var newranges = peakcache.addRangeToPeakCache(test_length, test_start + window_size * 2, test_end + window_size * 2);
expect(newranges.length).toEqual(1);
expect(newranges[0][0]).toEqual(test_end + window_size);
expect(newranges[0][1]).toEqual(test_end + window_size * 2);
});
/** @test {PeakCache#addRangeToPeakCache} */
/** @test {PeakCache#getCacheRanges} */
it('disjoint set creates two ranges', function() {
peakcache.addRangeToPeakCache(test_length, test_start, test_end);
peakcache.addRangeToPeakCache(test_length, test_start3, test_end3);
var ranges = peakcache.getCacheRanges();
expect(ranges.length).toEqual(2);
expect(ranges[0][0]).toEqual(test_start);
expect(ranges[0][1]).toEqual(test_end);
expect(ranges[1][0]).toEqual(test_start3);
expect(ranges[1][1]).toEqual(test_end3);
});
/** @test {PeakCache#addRangeToPeakCache} */
/** @test {PeakCache#getCacheRanges} */
it('filling in disjoint sets coalesces', function() {
peakcache.addRangeToPeakCache(test_length, test_start, test_end);
peakcache.addRangeToPeakCache(test_length, test_start3, test_end3);
var newranges = peakcache.addRangeToPeakCache(test_length, test_start, test_end3);
expect(newranges.length).toEqual(1);
expect(newranges[0][0]).toEqual(test_end);
expect(newranges[0][1]).toEqual(test_start3);
var ranges = peakcache.getCacheRanges();
expect(ranges.length).toEqual(1);
expect(ranges[0][0]).toEqual(test_start);
expect(ranges[0][1]).toEqual(test_end3);
});
/** @test {PeakCache#addRangeToPeakCache} */
/** @test {PeakCache#getCacheRanges} */
it('filling in disjoint sets coalesces / edge cases', function() {
peakcache.addRangeToPeakCache(test_length, test_start, test_end);
peakcache.addRangeToPeakCache(test_length, test_start3, test_end3);
var newranges = peakcache.addRangeToPeakCache(test_length, test_start2, test_end2);
expect(newranges.length).toEqual(1);
expect(newranges[0][0]).toEqual(test_end);
expect(newranges[0][1]).toEqual(test_start3);
var ranges = peakcache.getCacheRanges();
expect(ranges.length).toEqual(1);
expect(ranges[0][0]).toEqual(test_start);
expect(ranges[0][1]).toEqual(test_end3);
});
});
@@ -0,0 +1,118 @@
import WaveSurfer from '../src/wavesurfer.js';
/** @test {WaveSurfer} */
describe('WaveSurfer/plugin API:', () => {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
let waveformDiv;
let dummyPlugin;
let wavesurfer;
// clean up after each test
afterEach(done => {
wavesurfer.destroy();
waveformDiv.parentNode.removeChild(waveformDiv);
done();
});
// utility function to generate a mock plugin object
function mockPlugin(name, deferInit = false) {
class MockPlugin {
constructor(params, ws) {
this.ws = ws;
// using the instance factory unfortunately makes it
// difficult to use the spyOn function, so we use this
// instead
this.isInitialised = false;
}
init() {
this.isInitialised = true;
}
destroy() { }
}
return {
name,
deferInit,
staticProps: {
[`${name}Static`]: 'static property value'
},
instance: MockPlugin
};
}
// utility function to generate wavesurfer instances for testing
function __createWaveform(options = {}) {
waveformDiv = document.createElement('div');
document.getElementsByTagName('body')[0].appendChild(waveformDiv);
wavesurfer = WaveSurfer.create(Object.assign({
container: waveformDiv
}, options));
wavesurfer.load('/base/spec/support/demo.wav');
}
// plugin methods
/** @test {WaveSurfer#addPlugin} */
it('addPlugin adds staticProps and correctly builds and instantiates plugin class', () => {
dummyPlugin = mockPlugin('dummy');
__createWaveform();
wavesurfer.addPlugin(dummyPlugin);
expect(wavesurfer.dummyStatic).toEqual(dummyPlugin.staticProps.dummyStatic);
expect(wavesurfer.dummy.ws).toEqual(wavesurfer);
expect(typeof Object.getPrototypeOf(wavesurfer.dummy).on).toEqual('function');
});
/** @test {WaveSurfer#initPlugin} */
it('initPlugin calls init function of the plugin and adds its name to the initialisedPluginList', () => {
dummyPlugin = mockPlugin('dummy');
__createWaveform();
wavesurfer.addPlugin(dummyPlugin);
spyOn(wavesurfer.dummy, 'init');
wavesurfer.initPlugin('dummy');
expect(wavesurfer.dummy.init).toHaveBeenCalled();
expect(wavesurfer.initialisedPluginList.dummy).toBeTrue();
});
/** @test {WaveSurfer#destroyPlugin} */
it('destroyPlugin calls plugin destroy function and removes the plugin name from the initialisedPluginList', () => {
dummyPlugin = mockPlugin('dummy');
__createWaveform();
wavesurfer.addPlugin(dummyPlugin);
wavesurfer.initPlugin('dummy');
spyOn(wavesurfer.dummy, 'destroy');
wavesurfer.destroyPlugin('dummy');
expect(wavesurfer.dummy.destroy).toHaveBeenCalled();
expect(wavesurfer.initialisedPluginList.dummy).toBeUndefined();
});
// auto-adding and initialising of plugins (registerPlugins)
/** @test {WaveSurfer#registerPlugins} */
it('registerPlugin adds a plugin but does not call plugin init function if the plugin property deferInit is truethy', () => {
dummyPlugin = mockPlugin('dummy', true);
__createWaveform({
plugins: [
dummyPlugin
]
});
expect(wavesurfer.dummyStatic).toEqual(dummyPlugin.staticProps.dummyStatic);
expect(wavesurfer.dummy.ws).toEqual(wavesurfer);
expect(wavesurfer.dummy.isInitialised).toBeFalse();
});
/** @test {WaveSurfer#registerPlugins} */
it('registerPlugin adds a plugin ands calls plugin init function if the plugin property deferInit is falsey', () => {
dummyPlugin = mockPlugin('dummy');
__createWaveform({
plugins: [
dummyPlugin
]
});
expect(wavesurfer.dummyStatic).toEqual(dummyPlugin.staticProps.dummyStatic);
expect(wavesurfer.dummy.ws).toEqual(wavesurfer);
expect(wavesurfer.dummy.isInitialised).toBeTrue();
});
});
Binary file not shown.
+29
View File
@@ -0,0 +1,29 @@
import WaveSurfer from '../src/wavesurfer.js';
/** @test {util} */
describe('util:', function() {
/** @test {getId} */
it('getId returns a random string', function() {
expect(WaveSurfer.util.getId()).toStartWith('wavesurfer_');
});
/** @test {min} */
it('min returns the smallest number in the provided array', function() {
expect(WaveSurfer.util.min([0, 1, 1.1, 100, -1])).toEqual(-1);
});
/** @test {min} */
it('min returns +Infinity for an empty array', function() {
expect(WaveSurfer.util.min([])).toEqual(+Infinity);
});
/** @test {max} */
it('max returns the largest number in the provided array', function() {
expect(WaveSurfer.util.max([0, 1, 1.1, 100, -1])).toEqual(100);
});
/** @test {max} */
it('max returns -Infinity for an empty array', function() {
expect(WaveSurfer.util.max([])).toEqual(-Infinity);
});
});
+113
View File
@@ -0,0 +1,113 @@
import WaveSurfer from '../src/wavesurfer.js';
/** @test {WaveSurfer} */
describe('WaveSurfer/playback:', function () {
var wavesurfer;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
/*
* Handle creating wavesurfer ui requirements
*/
function __createWaveform() {
var waveformDiv = document.createElement('div');
waveformDiv.id = 'waveform';
document.getElementsByTagName('body')[0].appendChild(waveformDiv);
return WaveSurfer.create({
container: '#waveform',
waveColor: 'violet',
progressColor: 'purple'
});
}
beforeAll(function (done) {
wavesurfer = __createWaveform();
wavesurfer.load('/base/spec/support/demo.wav');
wavesurfer.on('ready', function () {
done();
});
});
beforeEach(function () {
wavesurfer.seekTo(0);
});
afterAll(function () {
wavesurfer.destroy();
});
/**
* @test {WaveSurfer#play}
* @test {WaveSurfer#isPlaying}
*/
it('should play', function () {
wavesurfer.play();
expect(wavesurfer.isPlaying()).toBeTrue();
});
/**
* @test {WaveSurfer#play}
* @test {WaveSurfer#isPlaying}
* @test {WaveSurfer#pause}
*/
it('should pause', function () {
wavesurfer.play();
expect(wavesurfer.isPlaying()).toBeTrue();
wavesurfer.pause();
expect(wavesurfer.isPlaying()).toBeFalse();
});
/**
* @test {WaveSurfer#playPause}
* @test {WaveSurfer#isPlaying}
*/
it('should play or pause', function () {
wavesurfer.playPause();
expect(wavesurfer.isPlaying()).toBeTrue();
wavesurfer.playPause();
expect(wavesurfer.isPlaying()).toBeFalse();
});
/** @test {WaveSurfer#getDuration} */
it('should get duration', function () {
var duration = parseInt(wavesurfer.getDuration(), 10);
expect(duration).toBeNumber();
});
/** @test {WaveSurfer#toggleMute} */
it('should toggle mute', function () {
wavesurfer.toggleMute();
expect(wavesurfer.isMuted).toBeTrue();
wavesurfer.toggleMute();
expect(wavesurfer.isMuted).toBeFalse();
});
/** @test {WaveSurfer#setMute} */
it('should set mute', function () {
wavesurfer.setMute(true);
expect(wavesurfer.isMuted).toBeTrue();
wavesurfer.setMute(false);
expect(wavesurfer.isMuted).toBeFalse();
});
/** @test {WaveSurfer#zoom} */
it('should set zoom parameters', function () {
wavesurfer.zoom(20);
expect(wavesurfer.params.minPxPerSec).toEqual(20);
expect(wavesurfer.params.scrollParent).toBe(true);
});
/** @test {WaveSurfer#zoom} */
it('should set unzoom parameters', function () {
wavesurfer.zoom(false);
expect(wavesurfer.params.minPxPerSec).toEqual(wavesurfer.defaultParams.minPxPerSec);
expect(wavesurfer.params.scrollParent).toBe(false);
});
});
+357
View File
@@ -0,0 +1,357 @@
import * as util from './util';
/**
* Parent class for renderers
*
* @extends {Observer}
*/
export default class Drawer extends util.Observer {
/**
* @param {HTMLElement} container The container node of the wavesurfer instance
* @param {WavesurferParams} params The wavesurfer initialisation options
*/
constructor(container, params) {
super();
/** @private */
this.container = container;
/**
* @type {WavesurferParams}
* @private
*/
this.params = params;
/**
* The width of the renderer
* @type {number}
*/
this.width = 0;
/**
* The height of the renderer
* @type {number}
*/
this.height = params.height * this.params.pixelRatio;
/** @private */
this.lastPos = 0;
/**
* The `<wave>` element which is added to the container
* @type {HTMLElement}
*/
this.wrapper = null;
}
/**
* Alias of `util.style`
*
* @param {HTMLElement} el The element that the styles will be applied to
* @param {Object} styles The map of propName: attribute, both are used as-is
* @return {HTMLElement} el
*/
style(el, styles) {
return util.style(el, styles);
}
/**
* Create the wrapper `<wave>` element, style it and set up the events for
* interaction
*/
createWrapper() {
this.wrapper = this.container.appendChild(
document.createElement('wave')
);
this.style(this.wrapper, {
display: 'block',
position: 'relative',
userSelect: 'none',
webkitUserSelect: 'none',
height: this.params.height + 'px'
});
if (this.params.fillParent || this.params.scrollParent) {
this.style(this.wrapper, {
width: '100%',
overflowX: this.params.hideScrollbar ? 'hidden' : 'auto',
overflowY: 'hidden'
});
}
this.setupWrapperEvents();
}
/**
* Handle click event
*
* @param {Event} e Click event
* @param {?boolean} noPrevent Set to true to not call `e.preventDefault()`
* @return {number} Playback position from 0 to 1
*/
handleEvent(e, noPrevent) {
!noPrevent && e.preventDefault();
const clientX = e.targetTouches ? e.targetTouches[0].clientX : e.clientX;
const bbox = this.wrapper.getBoundingClientRect();
const nominalWidth = this.width;
const parentWidth = this.getWidth();
let progress;
if (!this.params.fillParent && nominalWidth < parentWidth) {
progress = ((clientX - bbox.left) * this.params.pixelRatio / nominalWidth) || 0;
if (progress > 1) {
progress = 1;
}
} else {
progress = ((clientX - bbox.left + this.wrapper.scrollLeft) / this.wrapper.scrollWidth) || 0;
}
return progress;
}
/**
* @private
*/
setupWrapperEvents() {
this.wrapper.addEventListener('click', e => {
const scrollbarHeight = this.wrapper.offsetHeight - this.wrapper.clientHeight;
if (scrollbarHeight != 0) {
// scrollbar is visible. Check if click was on it
const bbox = this.wrapper.getBoundingClientRect();
if (e.clientY >= bbox.bottom - scrollbarHeight) {
// ignore mousedown as it was on the scrollbar
return;
}
}
if (this.params.interact) {
this.fireEvent('click', e, this.handleEvent(e));
}
});
this.wrapper.addEventListener('scroll', e => this.fireEvent('scroll', e));
}
/**
* Draw peaks on the canvas
*
* @param {number[]|number[][]} peaks Can also be an array of arrays for split channel
* rendering
* @param {number} length The width of the area that should be drawn
* @param {number} start The x-offset of the beginning of the area that
* should be rendered
* @param {number} end The x-offset of the end of the area that should be
* rendered
*/
drawPeaks(peaks, length, start, end) {
if (!this.setWidth(length)) {
this.clearWave();
}
this.params.barWidth ?
this.drawBars(peaks, 0, start, end) :
this.drawWave(peaks, 0, start, end);
}
/**
* Scroll to the beginning
*/
resetScroll() {
if (this.wrapper !== null) {
this.wrapper.scrollLeft = 0;
}
}
/**
* Recenter the viewport at a certain percent of the waveform
*
* @param {number} percent Value from 0 to 1 on the waveform
*/
recenter(percent) {
const position = this.wrapper.scrollWidth * percent;
this.recenterOnPosition(position, true);
}
/**
* Recenter the viewport on a position, either scroll there immediately or
* in steps of 5 pixels
*
* @param {number} position X-offset in pixels
* @param {boolean} immediate Set to true to immediately scroll somewhere
*/
recenterOnPosition(position, immediate) {
const scrollLeft = this.wrapper.scrollLeft;
const half = ~~(this.wrapper.clientWidth / 2);
const maxScroll = this.wrapper.scrollWidth - this.wrapper.clientWidth;
let target = position - half;
let offset = target - scrollLeft;
if (maxScroll == 0) {
// no need to continue if scrollbar is not there
return;
}
// if the cursor is currently visible...
if (!immediate && -half <= offset && offset < half) {
// we'll limit the "re-center" rate.
const rate = 5;
offset = Math.max(-rate, Math.min(rate, offset));
target = scrollLeft + offset;
}
// limit target to valid range (0 to maxScroll)
target = Math.max(0, Math.min(maxScroll, target));
// no use attempting to scroll if we're not moving
if (target != scrollLeft) {
this.wrapper.scrollLeft = target;
}
}
/**
* Get the current scroll position in pixels
*
* @return {number}
*/
getScrollX() {
return Math.round(this.wrapper.scrollLeft * this.params.pixelRatio);
}
/**
* Get the width of the container
*
* @return {number}
*/
getWidth() {
return Math.round(this.container.clientWidth * this.params.pixelRatio);
}
/**
* Set the width of the container
*
* @param {number} width
*/
setWidth(width) {
if (this.width == width) {
return false;
}
this.width = width;
if (this.params.fillParent || this.params.scrollParent) {
this.style(this.wrapper, {
width: ''
});
} else {
this.style(this.wrapper, {
width: ~~(this.width / this.params.pixelRatio) + 'px'
});
}
this.updateSize();
return true;
}
/**
* Set the height of the container
*
* @param {number} height
*/
setHeight(height) {
if (height == this.height) {
return false;
}
this.height = height;
this.style(this.wrapper, {
height: ~~(this.height / this.params.pixelRatio) + 'px'
});
this.updateSize();
return true;
}
/**
* Called by wavesurfer when progress should be renderered
*
* @param {number} progress From 0 to 1
*/
progress(progress) {
const minPxDelta = 1 / this.params.pixelRatio;
const pos = Math.round(progress * this.width) * minPxDelta;
if (pos < this.lastPos || pos - this.lastPos >= minPxDelta) {
this.lastPos = pos;
if (this.params.scrollParent && this.params.autoCenter) {
const newPos = ~~(this.wrapper.scrollWidth * progress);
this.recenterOnPosition(newPos);
}
this.updateProgress(pos);
}
}
/**
* This is called when wavesurfer is destroyed
*/
destroy() {
this.unAll();
if (this.wrapper) {
if (this.wrapper.parentNode == this.container) {
this.container.removeChild(this.wrapper);
}
this.wrapper = null;
}
}
/* Renderer-specific methods */
/**
* Called when the size of the container changes so the renderer can adjust
*
* @abstract
*/
updateSize() {}
/**
* Draw a waveform with bars
*
* @abstract
* @param {number[]|number[][]} peaks Can also be an array of arrays for split channel
* rendering
* @param {number} channelIndex The index of the current channel. Normally
* should be 0
* @param {number} start The x-offset of the beginning of the area that
* should be rendered
* @param {number} end The x-offset of the end of the area that should be
* rendered
*/
drawBars(peaks, channelIndex, start, end) {}
/**
* Draw a waveform
*
* @abstract
* @param {number[]|number[][]} peaks Can also be an array of arrays for split channel
* rendering
* @param {number} channelIndex The index of the current channel. Normally
* should be 0
* @param {number} start The x-offset of the beginning of the area that
* should be rendered
* @param {number} end The x-offset of the end of the area that should be
* rendered
*/
drawWave(peaks, channelIndex, start, end) {}
/**
* Clear the waveform
*
* @abstract
*/
clearWave() {}
/**
* Render the new progress
*
* @abstract
* @param {number} position X-Offset of progress position in pixels
*/
updateProgress(position) {}
}
@@ -0,0 +1,529 @@
import Drawer from './drawer';
import * as util from './util';
/**
* @typedef {Object} CanvasEntry
* @private
* @property {HTMLElement} wave The wave node
* @property {CanvasRenderingContext2D} waveCtx The canvas rendering context
* @property {?HTMLElement} progress The progress wave node
* @property {?CanvasRenderingContext2D} progressCtx The progress wave canvas
* rendering context
* @property {?number} start Start of the area the canvas should render, between 0 and 1
* @property {?number} end End of the area the canvas should render, between 0 and 1
*/
/**
* MultiCanvas renderer for wavesurfer. Is currently the default and sole built
* in renderer.
*/
export default class MultiCanvas extends Drawer {
/**
* @param {HTMLElement} container The container node of the wavesurfer instance
* @param {WavesurferParams} params The wavesurfer initialisation options
*/
constructor(container, params) {
super(container, params);
/**
* @type {number}
* @private
*/
this.maxCanvasWidth = params.maxCanvasWidth;
/**
* @private
* @type {number}
*/
this.maxCanvasElementWidth = Math.round(params.maxCanvasWidth / params.pixelRatio);
/**
* Whether or not the progress wave is renderered. If the `waveColor`
* and `progressColor` are the same colour it is not.
* @type {boolean}
*/
this.hasProgressCanvas = params.waveColor != params.progressColor;
/**
* @private
* @type {number}
*/
this.halfPixel = 0.5 / params.pixelRatio;
/**
* @private
* @type {Array}
*/
this.canvases = [];
/** @private */
this.progressWave = null;
}
/**
* Initialise the drawer
*/
init() {
this.createWrapper();
this.createElements();
}
/**
* Create the canvas elements and style them
*
* @private
*/
createElements() {
this.progressWave = this.wrapper.appendChild(
this.style(document.createElement('wave'), {
position: 'absolute',
zIndex: 3,
left: 0,
top: 0,
bottom: 0,
overflow: 'hidden',
width: '0',
display: 'none',
boxSizing: 'border-box',
borderRightStyle: 'solid',
borderRightWidth: this.params.cursorWidth + 'px',
borderRightColor: this.params.cursorColor,
pointerEvents: 'none'
})
);
this.addCanvas();
}
/**
* Adjust to the updated size by adding or removing canvases
*/
updateSize() {
const totalWidth = Math.round(this.width / this.params.pixelRatio);
const requiredCanvases = Math.ceil(totalWidth / this.maxCanvasElementWidth);
while (this.canvases.length < requiredCanvases) {
this.addCanvas();
}
while (this.canvases.length > requiredCanvases) {
this.removeCanvas();
}
this.canvases.forEach((entry, i) => {
// Add some overlap to prevent vertical white stripes, keep the width even for simplicity.
let canvasWidth = this.maxCanvasWidth + 2 * Math.ceil(this.params.pixelRatio / 2);
if (i == this.canvases.length - 1) {
canvasWidth = this.width - (this.maxCanvasWidth * (this.canvases.length - 1));
}
this.updateDimensions(entry, canvasWidth, this.height);
this.clearWaveForEntry(entry);
});
}
/**
* Add a canvas to the canvas list
*
* @private
*/
addCanvas() {
const entry = {};
const leftOffset = this.maxCanvasElementWidth * this.canvases.length;
entry.wave = this.wrapper.appendChild(
this.style(document.createElement('canvas'), {
position: 'absolute',
zIndex: 2,
left: leftOffset + 'px',
top: 0,
bottom: 0,
height: '100%',
pointerEvents: 'none'
})
);
entry.waveCtx = entry.wave.getContext('2d');
if (this.hasProgressCanvas) {
entry.progress = this.progressWave.appendChild(
this.style(document.createElement('canvas'), {
position: 'absolute',
left: leftOffset + 'px',
top: 0,
bottom: 0,
height: '100%'
})
);
entry.progressCtx = entry.progress.getContext('2d');
}
this.canvases.push(entry);
}
/**
* Pop one canvas from the list
*
* @private
*/
removeCanvas() {
const lastEntry = this.canvases.pop();
lastEntry.wave.parentElement.removeChild(lastEntry.wave);
if (this.hasProgressCanvas) {
lastEntry.progress.parentElement.removeChild(lastEntry.progress);
}
}
/**
* Update the dimensions of a canvas element
*
* @private
* @param {CanvasEntry} entry
* @param {number} width The new width of the element
* @param {number} height The new height of the element
*/
updateDimensions(entry, width, height) {
const elementWidth = Math.round(width / this.params.pixelRatio);
const totalWidth = Math.round(this.width / this.params.pixelRatio);
// Where the canvas starts and ends in the waveform, represented as a decimal between 0 and 1.
entry.start = (entry.waveCtx.canvas.offsetLeft / totalWidth) || 0;
entry.end = entry.start + elementWidth / totalWidth;
entry.waveCtx.canvas.width = width;
entry.waveCtx.canvas.height = height;
this.style(entry.waveCtx.canvas, { width: elementWidth + 'px'});
this.style(this.progressWave, { display: 'block'});
if (this.hasProgressCanvas) {
entry.progressCtx.canvas.width = width;
entry.progressCtx.canvas.height = height;
this.style(entry.progressCtx.canvas, { width: elementWidth + 'px'});
}
}
/**
* Clear the whole waveform
*/
clearWave() {
this.canvases.forEach(entry => this.clearWaveForEntry(entry));
}
/**
* Clear one canvas
*
* @private
* @param {CanvasEntry} entry
*/
clearWaveForEntry(entry) {
entry.waveCtx.clearRect(0, 0, entry.waveCtx.canvas.width, entry.waveCtx.canvas.height);
if (this.hasProgressCanvas) {
entry.progressCtx.clearRect(0, 0, entry.progressCtx.canvas.width, entry.progressCtx.canvas.height);
}
}
/**
* Draw a waveform with bars
*
* @param {number[]|number[][]} peaks Can also be an array of arrays for split channel
* rendering
* @param {number} channelIndex The index of the current channel. Normally
* should be 0. Must be an integer.
* @param {number} start The x-offset of the beginning of the area that
* should be rendered
* @param {number} end The x-offset of the end of the area that should be
* rendered
*/
drawBars(peaks, channelIndex, start, end) {
return this.prepareDraw(peaks, channelIndex, start, end, ({
absmax,
hasMinVals,
height,
offsetY,
halfH
}) => {
// if drawBars was called within ws.empty we don't pass a start and
// don't want anything to happen
if (start === undefined) {
return;
}
// Skip every other value if there are negatives.
const peakIndexScale = hasMinVals ? 2 : 1;
const length = peaks.length / peakIndexScale;
const bar = this.params.barWidth * this.params.pixelRatio;
const gap = Math.max(this.params.pixelRatio, ~~(bar / 2));
const step = bar + gap;
const scale = length / this.width;
const first = start;
const last = end;
let i;
for (i = first; i < last; i += step) {
const peak = peaks[Math.floor(i * scale * peakIndexScale)] || 0;
const h = Math.round(peak / absmax * halfH);
this.fillRect(i + this.halfPixel, halfH - h + offsetY, bar + this.halfPixel, h * 2);
}
});
}
/**
* Draw a waveform
*
* @param {number[]|number[][]} peaks Can also be an array of arrays for split channel
* rendering
* @param {number} channelIndex The index of the current channel. Normally
* should be 0
* @param {number?} start The x-offset of the beginning of the area that
* should be rendered (If this isn't set only a flat line is rendered)
* @param {number?} end The x-offset of the end of the area that should be
* rendered
*/
drawWave(peaks, channelIndex, start, end) {
return this.prepareDraw(peaks, channelIndex, start, end, ({
absmax,
hasMinVals,
height,
offsetY,
halfH
}) => {
if (!hasMinVals) {
const reflectedPeaks = [];
const len = peaks.length;
let i;
for (i = 0; i < len; i++) {
reflectedPeaks[2 * i] = peaks[i];
reflectedPeaks[2 * i + 1] = -peaks[i];
}
peaks = reflectedPeaks;
}
// if drawWave was called within ws.empty we don't pass a start and
// end and simply want a flat line
if (start !== undefined) {
this.drawLine(peaks, absmax, halfH, offsetY, start, end);
}
// Always draw a median line
this.fillRect(0, halfH + offsetY - this.halfPixel, this.width, this.halfPixel);
});
}
/**
* Tell the canvas entries to render their portion of the waveform
*
* @private
* @param {number[]} peaks Peak data
* @param {number} absmax Maximum peak value (absolute)
* @param {number} halfH Half the height of the waveform
* @param {number} offsetY Offset to the top
* @param {number} start The x-offset of the beginning of the area that
* should be rendered
* @param {number} end The x-offset of the end of the area that
* should be rendered
*/
drawLine(peaks, absmax, halfH, offsetY, start, end) {
this.canvases.forEach(entry => {
this.setFillStyles(entry);
this.drawLineToContext(entry, entry.waveCtx, peaks, absmax, halfH, offsetY, start, end);
this.drawLineToContext(entry, entry.progressCtx, peaks, absmax, halfH, offsetY, start, end);
});
}
/**
* Render the actual waveform line on a canvas
*
* @private
* @param {CanvasEntry} entry
* @param {Canvas2DContextAttributes} ctx Essentially `entry.[wave|progress]Ctx`
* @param {number[]} peaks
* @param {number} absmax Maximum peak value (absolute)
* @param {number} halfH Half the height of the waveform
* @param {number} offsetY Offset to the top
* @param {number} start The x-offset of the beginning of the area that
* should be rendered
* @param {number} end The x-offset of the end of the area that
* should be rendered
*/
drawLineToContext(entry, ctx, peaks, absmax, halfH, offsetY, start, end) {
if (!ctx) { return; }
const length = peaks.length / 2;
const scale = (this.params.fillParent && this.width != length)
? this.width / length
: 1;
const first = Math.round(length * entry.start);
// Use one more peak value to make sure we join peaks at ends -- unless,
// of course, this is the last canvas.
const last = Math.round(length * entry.end) + 1;
if (first > end || last < start) { return; }
const canvasStart = Math.min(first, start);
const canvasEnd = Math.max(last, end);
let i;
let j;
ctx.beginPath();
ctx.moveTo((canvasStart - first) * scale + this.halfPixel, halfH + offsetY);
for (i = canvasStart; i < canvasEnd; i++) {
const peak = peaks[2 * i] || 0;
const h = Math.round(peak / absmax * halfH);
ctx.lineTo((i - first) * scale + this.halfPixel, halfH - h + offsetY);
}
// Draw the bottom edge going backwards, to make a single
// closed hull to fill.
for (j = canvasEnd - 1; j >= canvasStart; j--) {
const peak = peaks[2 * j + 1] || 0;
const h = Math.round(peak / absmax * halfH);
ctx.lineTo((j - first) * scale + this.halfPixel, halfH - h + offsetY);
}
ctx.closePath();
ctx.fill();
}
/**
* Draw a rectangle on the waveform
*
* @param {number} x
* @param {number} y
* @param {number} width
* @param {number} height
*/
fillRect(x, y, width, height) {
const startCanvas = Math.floor(x / this.maxCanvasWidth);
const endCanvas = Math.min(
Math.ceil((x + width) / this.maxCanvasWidth) + 1,
this.canvases.length
);
let i;
for (i = startCanvas; i < endCanvas; i++) {
const entry = this.canvases[i];
const leftOffset = i * this.maxCanvasWidth;
const intersection = {
x1: Math.max(x, i * this.maxCanvasWidth),
y1: y,
x2: Math.min(x + width, i * this.maxCanvasWidth + entry.waveCtx.canvas.width),
y2: y + height
};
if (intersection.x1 < intersection.x2) {
this.setFillStyles(entry);
this.fillRectToContext(entry.waveCtx,
intersection.x1 - leftOffset,
intersection.y1,
intersection.x2 - intersection.x1,
intersection.y2 - intersection.y1);
this.fillRectToContext(entry.progressCtx,
intersection.x1 - leftOffset,
intersection.y1,
intersection.x2 - intersection.x1,
intersection.y2 - intersection.y1);
}
}
}
/**
* Performs preparation tasks and calculations which are shared by drawBars and drawWave
*
* @private
* @param {number[]|number[][]} peaks Can also be an array of arrays for split channel
* rendering
* @param {number} channelIndex The index of the current channel. Normally
* should be 0
* @param {number?} start The x-offset of the beginning of the area that
* should be rendered (If this isn't set only a flat line is rendered)
* @param {number?} end The x-offset of the end of the area that should be
* rendered
* @param {function} fn The render function to call
*/
prepareDraw(peaks, channelIndex, start, end, fn) {
return util.frame(() => {
// Split channels and call this function with the channelIndex set
if (peaks[0] instanceof Array) {
const channels = peaks;
if (this.params.splitChannels) {
this.setHeight(channels.length * this.params.height * this.params.pixelRatio);
channels.forEach((channelPeaks, i) => this.prepareDraw(channelPeaks, i, start, end, fn));
return;
}
peaks = channels[0];
}
// calculate maximum modulation value, either from the barHeight
// parameter or if normalize=true from the largest value in the peak
// set
let absmax = 1 / this.params.barHeight;
if (this.params.normalize) {
const max = util.max(peaks);
const min = util.min(peaks);
absmax = -min > max ? -min : max;
}
// Bar wave draws the bottom only as a reflection of the top,
// so we don't need negative values
const hasMinVals = [].some.call(peaks, val => val < 0);
const height = this.params.height * this.params.pixelRatio;
const offsetY = height * channelIndex || 0;
const halfH = height / 2;
return fn({
absmax: absmax,
hasMinVals: hasMinVals,
height: height,
offsetY: offsetY,
halfH: halfH
});
})();
}
/**
* Draw the actual rectangle on a canvas
*
* @private
* @param {Canvas2DContextAttributes} ctx
* @param {number} x
* @param {number} y
* @param {number} width
* @param {number} height
*/
fillRectToContext(ctx, x, y, width, height) {
if (!ctx) { return; }
ctx.fillRect(x, y, width, height);
}
/**
* Set the fill styles for a certain entry (wave and progress)
*
* @private
* @param {CanvasEntry} entry
*/
setFillStyles(entry) {
entry.waveCtx.fillStyle = this.params.waveColor;
if (this.hasProgressCanvas) {
entry.progressCtx.fillStyle = this.params.progressColor;
}
}
/**
* Return image data of the waveform
*
* @param {string} type='image/png' An optional value of a format type.
* @param {number} quality=0.92 An optional value between 0 and 1.
* @return {string|string[]} images A data URL or an array of data URLs
*/
getImage(type, quality) {
const images = this.canvases.map(entry => entry.wave.toDataURL(type, quality));
return images.length > 1 ? images : images[0];
}
/**
* Render the new progress
*
* @param {number} position X-Offset of progress position in pixels
*/
updateProgress(position) {
this.style(this.progressWave, { width: position + 'px' });
}
}
+221
View File
@@ -0,0 +1,221 @@
import loadScript from 'load-script';
/**
* @typedef {Object} InitParams
* @property {WavesurferParams} [defaults={backend: 'MediaElement,
* mediaControls: true}] The default wavesurfer initialisation parameters
* @property {string|NodeList} containers='wavesurfer' Selector or NodeList of
* elements to attach instances to
* @property {string}
* pluginCdnTemplate='//localhost:8080/dist/plugin/wavesurfer.[name].js' URL
* template for the dynamic loading of plugins
* @property {function} loadPlugin If set overwrites the default ajax function,
* can be used to inject plugins differently.
*/
/**
* The HTML initialisation API is not part of the main library bundle file and
* must be additionally included.
*
* The API attaches wavesurfer instances to all `<wavesurfer>` (can be
* customised), parsing their `data-` attributes to construct an options object
* for initialisation. Among other things it can dynamically load plugin code.
*
* The automatic initialisation can be prevented by setting the
* `window.WS_StopAutoInit` flag to true. The `html-init[.min].js` file exports
* the `Init` class, which can be called manually.
*
* Site-wide defaults can be added by setting `window.WS_InitOptions`.
*
* @example
* <!-- with minimap and timeline plugin -->
* <wavesurfer
* data-url="../media/demo.wav"
* data-plugins="minimap,timeline"
* data-minimap-height="30"
* data-minimap-wave-color="#ddd"
* data-minimap-progress-color="#999"
* data-timeline-font-size="13px"
* data-timeline-container="#timeline"
* >
* </wavesurfer>
* <div id="timeline"></div>
*
* <!-- with regions plugin -->
* <wavesurfer
* data-url="../media/demo.wav"
* data-plugins="regions"
* data-regions-regions='[{"start": 1,"end": 3,"color": "hsla(400, 100%, 30%, 0.5)"}]'
* >
* </wavesurfer>
*/
class Init {
/**
* Instantiate Init class and initialise elements
*
* This is done automatically if `window` is defined and
* `window.WS_StopAutoInit` is not set to true
*
* @param {WaveSurfer} WaveSurfer The WaveSurfer library object
* @param {InitParams} params initialisation options
*/
constructor(WaveSurfer, params = {}) {
if (!WaveSurfer) {
throw new Error('WaveSurfer is not available!');
}
/**
* cache WaveSurfer
* @private
*/
this.WaveSurfer = WaveSurfer;
/**
* build parameters, cache them in _params so minified builds are smaller
* @private
*/
const _params = this.params = WaveSurfer.util.extend({}, {
// wavesurfer parameter defaults so by default the audio player is
// usable with native media element controls
defaults: {
backend: 'MediaElement',
mediaControls: true
},
// containers to instantiate on, can be selector string or NodeList
containers: 'wavesurfer',
// @TODO insert plugin CDN URIs
pluginCdnTemplate: '//localhost:8080/dist/plugin/wavesurfer.[name].js',
// loadPlugin function can be overriden to inject plugin definition
// objects, this default function uses load-script to load a plugin
// and pass it to a callback
loadPlugin(name, cb) {
const src = _params.pluginCdnTemplate.replace('[name]', name);
loadScript(src, { async: false }, (err, plugin) => {
if (err) {
return console.error(`WaveSurfer plugin ${name} not found at ${src}`);
}
cb(window.WaveSurfer[name]);
});
}
}, params);
/**
* The nodes that should have instances attached to them
* @type {NodeList}
*/
this.containers = typeof _params.containers == 'string'
? document.querySelectorAll(_params.containers)
: _params.containers;
/** @private */
this.pluginCache = {};
/**
* An array of wavesurfer instances
* @type {Object[]}
*/
this.instances = [];
this.initAllEls();
}
/**
* Initialise all container elements
*/
initAllEls() {
// iterate over all the container elements
Array.prototype.forEach.call(this.containers, el => {
// load the plugins as an array of plugin names
const plugins = el.dataset.plugins
? el.dataset.plugins.split(',')
: [];
// no plugins to be loaded, just render
if (!plugins.length) {
return this.initEl(el);
}
// … or: iterate over all the plugins
plugins.forEach((name, i) => {
// plugin is not cached already, load it
if (!this.pluginCache[name]) {
this.params.loadPlugin(name, lib => {
this.pluginCache[name] = lib;
// plugins were all loaded, render the element
if (i + 1 === plugins.length) {
this.initEl(el, plugins);
}
});
} else if (i === plugins.length) {
// plugin was cached and this plugin was the last
this.initEl(el, plugins);
}
});
});
}
/**
* Initialise a single container element and add to `this.instances`
*
* @param {HTMLElement} el The container to instantiate wavesurfer to
* @param {PluginDefinition[]} plugins An Array of plugin names to initialise with
* @return {Object} Wavesurfer instance
*/
initEl(el, plugins = []) {
const jsonRegex = /^[[|{]/;
// initialise plugins with the correct options
const initialisedPlugins = plugins.map(plugin => {
const options = {};
// the regex to find this plugin attributes
const attrNameRegex = new RegExp('^' + plugin);
let attrName;
// iterate over all the data attributes and find ones for this
// plugin
for (attrName in el.dataset) {
const regexResult = attrNameRegex.exec(attrName);
if (regexResult) {
const attr = el.dataset[attrName];
// if the string begins with a [ or a { parse it as JSON
const prop = jsonRegex.test(attr) ? JSON.parse(attr) : attr;
// this removes the plugin prefix and changes the first letter
// of the resulting string to lower case to follow the naming
// convention of ws params
const unprefixedOptionName = attrName.slice(plugin.length, plugin.length + 1).toLowerCase()
+ attrName.slice(plugin.length + 1);
options[unprefixedOptionName] = prop;
}
}
return this.pluginCache[plugin].create(options);
});
// build parameter object for this container
const params = this.WaveSurfer.util.extend(
{ container: el },
this.params.defaults,
el.dataset,
{ plugins: initialisedPlugins }
);
// @TODO make nicer
el.style.display = 'block';
// initialise wavesurfer, load audio (with peaks if provided)
const instance = this.WaveSurfer.create(params);
const peaks = params.peaks ? JSON.parse(params.peaks) : undefined;
instance.load(params.url, peaks);
// push this instance into the instances cache
this.instances.push(instance);
return instance;
}
}
// if window object exists and window.WS_StopAutoInit is not true
if (typeof window === 'object' && !window.WS_StopAutoInit) {
// call init when document is ready, apply any custom default settings
// in window.WS_InitOptions
if (document.readyState === 'complete') {
window.WaveSurferInit = new Init(window.WaveSurfer, window.WS_InitOptions);
} else {
window.addEventListener('load', () => {
window.WaveSurferInit = new Init(window.WaveSurfer, window.WS_InitOptions);
});
}
}
// export init for manual usage
export default Init;
+312
View File
@@ -0,0 +1,312 @@
import WebAudio from './webaudio';
import * as util from './util';
/**
* MediaElement backend
*/
export default class MediaElement extends WebAudio {
/**
* Construct the backend
*
* @param {WavesurferParams} params
*/
constructor(params) {
super(params);
/** @private */
this.params = params;
// Dummy media to catch errors
/** @private */
this.media = {
currentTime: 0,
duration: 0,
paused: true,
playbackRate: 1,
play() {},
pause() {}
};
/** @private */
this.mediaType = params.mediaType.toLowerCase();
/** @private */
this.elementPosition = params.elementPosition;
/** @private */
this.peaks = null;
/** @private */
this.playbackRate = 1;
/** @private */
this.buffer = null;
/** @private */
this.onPlayEnd = null;
}
/**
* Initialise the backend, called in `wavesurfer.createBackend()`
*/
init() {
this.setPlaybackRate(this.params.audioRate);
this.createTimer();
}
/**
* Create a timer to provide a more precise `audioprocess` event.
*
* @private
*/
createTimer() {
const onAudioProcess = () => {
if (this.isPaused()) { return; }
this.fireEvent('audioprocess', this.getCurrentTime());
// Call again in the next frame
const requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame;
requestAnimationFrame(onAudioProcess);
};
this.on('play', onAudioProcess);
}
/**
* Create media element with url as its source,
* and append to container element.
*
* @param {string} url Path to media file
* @param {HTMLElement} container HTML element
* @param {Array} peaks Array of peak data
* @param {string} preload HTML 5 preload attribute value
*/
load(url, container, peaks, preload) {
const media = document.createElement(this.mediaType);
media.controls = this.params.mediaControls;
media.autoplay = this.params.autoplay || false;
media.preload = preload == null ? 'auto' : preload;
media.src = url;
media.style.width = '100%';
const prevMedia = container.querySelector(this.mediaType);
if (prevMedia) {
container.removeChild(prevMedia);
}
container.appendChild(media);
this._load(media, peaks);
}
/**
* Load existing media element.
*
* @param {MediaElement} elt HTML5 Audio or Video element
* @param {Array} peaks Array of peak data
*/
loadElt(elt, peaks) {
elt.controls = this.params.mediaControls;
elt.autoplay = this.params.autoplay || false;
this._load(elt, peaks);
}
/**
* Private method called by both load (from url)
* and loadElt (existing media element).
*
* @param {MediaElement} media HTML5 Audio or Video element
* @param {Array} peaks array of peak data
* @private
*/
_load(media, peaks) {
// load must be called manually on iOS, otherwise peaks won't draw
// until a user interaction triggers load --> 'ready' event
if (typeof media.load == 'function') {
media.load();
}
media.addEventListener('error', () => {
this.fireEvent('error', 'Error loading media element');
});
media.addEventListener('canplay', () => {
this.fireEvent('canplay');
});
media.addEventListener('ended', () => {
this.fireEvent('finish');
});
// Listen to and relay play and pause events to enable
// playback control from the external media element
media.addEventListener('play', () => {
this.fireEvent('play');
});
media.addEventListener('pause', () => {
this.fireEvent('pause');
});
this.media = media;
this.peaks = peaks;
this.onPlayEnd = null;
this.buffer = null;
this.setPlaybackRate(this.playbackRate);
}
/**
* Used by `wavesurfer.isPlaying()` and `wavesurfer.playPause()`
*
* @return {boolean}
*/
isPaused() {
return !this.media || this.media.paused;
}
/**
* Used by `wavesurfer.getDuration()`
*
* @return {number}
*/
getDuration() {
let duration = (this.buffer || this.media).duration;
if (duration >= Infinity) { // streaming audio
duration = this.media.seekable.end(0);
}
return duration;
}
/**
* Returns the current time in seconds relative to the audioclip's
* duration.
*
* @return {number}
*/
getCurrentTime() {
return this.media && this.media.currentTime;
}
/**
* Get the position from 0 to 1
*
* @return {number}
*/
getPlayedPercents() {
return (this.getCurrentTime() / this.getDuration()) || 0;
}
/**
* Get the audio source playback rate.
*
* @return {number}
*/
getPlaybackRate() {
return this.playbackRate || this.media.playbackRate;
}
/**
* Set the audio source playback rate.
*
* @param {number} value
*/
setPlaybackRate(value) {
this.playbackRate = value || 1;
this.media.playbackRate = this.playbackRate;
}
/**
* Used by `wavesurfer.seekTo()`
*
* @param {number} start Position to start at in seconds
*/
seekTo(start) {
if (start != null) {
this.media.currentTime = start;
}
this.clearPlayEnd();
}
/**
* Plays the loaded audio region.
*
* @param {Number} start Start offset in seconds, relative to the beginning
* of a clip.
* @param {Number} end When to stop relative to the beginning of a clip.
* @emits MediaElement#play
*/
play(start, end) {
this.seekTo(start);
this.media.play();
end && this.setPlayEnd(end);
}
/**
* Pauses the loaded audio.
*
* @emits MediaElement#pause
*/
pause() {
this.media && this.media.pause();
this.clearPlayEnd();
}
/** @private */
setPlayEnd(end) {
this._onPlayEnd = time => {
if (time >= end) {
this.pause();
this.seekTo(end);
}
};
this.on('audioprocess', this._onPlayEnd);
}
/** @private */
clearPlayEnd() {
if (this._onPlayEnd) {
this.un('audioprocess', this._onPlayEnd);
this._onPlayEnd = null;
}
}
/**
* Compute the max and min value of the waveform when broken into
* <length> subranges.
*
* @param {number} length How many subranges to break the waveform into.
* @param {number} first First sample in the required range.
* @param {number} last Last sample in the required range.
* @return {number[]|number[][]} Array of 2*<length> peaks or array of
* arrays of peaks consisting of (max, min) values for each subrange.
*/
getPeaks(length, first, last) {
if (this.buffer) {
return super.getPeaks(length, first, last);
}
return this.peaks || [];
}
/**
* Get the current volume
*
* @return {number} value A floating point value between 0 and 1.
*/
getVolume() {
return this.media.volume;
}
/**
* Set the audio volume
*
* @param {number} value A floating point value between 0 and 1.
*/
setVolume(value) {
this.media.volume = value;
}
/**
* This is called when wavesurfer is destroyed
*
*/
destroy() {
this.pause();
this.unAll();
this.media && this.media.parentNode && this.media.parentNode.removeChild(this.media);
this.media = null;
}
}
+116
View File
@@ -0,0 +1,116 @@
/**
* Caches the decoded peaks data to improve rendering speed for lage audio
*
* Is used if the option parameter `partialRender` is set to `true`
*/
export default class PeakCache {
/**
* Instantiate cache
*/
constructor() {
this.clearPeakCache();
}
/**
* Empty the cache
*/
clearPeakCache() {
/**
* Flat array with entries that are always in pairs to mark the
* beginning and end of each subrange. This is a convenience so we can
* iterate over the pairs for easy set difference operations.
* @private
*/
this.peakCacheRanges = [];
/**
* Length of the entire cachable region, used for resetting the cache
* when this changes (zoom events, for instance).
* @private
*/
this.peakCacheLength = -1;
}
/**
* Add a range of peaks to the cache
*
* @param {number} length The length of the range
* @param {number} start The x offset of the start of the range
* @param {number} end The x offset of the end of the range
* @return {number[][]}
*/
addRangeToPeakCache(length, start, end) {
if (length != this.peakCacheLength) {
this.clearPeakCache();
this.peakCacheLength = length;
}
// Return ranges that weren't in the cache before the call.
let uncachedRanges = [];
let i = 0;
// Skip ranges before the current start.
while (i < this.peakCacheRanges.length && this.peakCacheRanges[i] < start) {
i++;
}
// If |i| is even, |start| falls after an existing range. Otherwise,
// |start| falls between an existing range, and the uncached region
// starts when we encounter the next node in |peakCacheRanges| or
// |end|, whichever comes first.
if (i % 2 == 0) {
uncachedRanges.push(start);
}
while (i < this.peakCacheRanges.length && this.peakCacheRanges[i] <= end) {
uncachedRanges.push(this.peakCacheRanges[i]);
i++;
}
// If |i| is even, |end| is after all existing ranges.
if (i % 2 == 0) {
uncachedRanges.push(end);
}
// Filter out the 0-length ranges.
uncachedRanges = uncachedRanges.filter((item, pos, arr) => {
if (pos == 0) {
return item != arr[pos + 1];
} else if (pos == arr.length - 1) {
return item != arr[pos - 1];
}
return item != arr[pos - 1] && item != arr[pos + 1];
});
// Merge the two ranges together, uncachedRanges will either contain
// wholly new points, or duplicates of points in peakCacheRanges. If
// duplicates are detected, remove both and extend the range.
this.peakCacheRanges = this.peakCacheRanges.concat(uncachedRanges);
this.peakCacheRanges = this.peakCacheRanges.sort((a, b) => a - b).filter((item, pos, arr) => {
if (pos == 0) {
return item != arr[pos + 1];
} else if (pos == arr.length - 1) {
return item != arr[pos - 1];
}
return item != arr[pos - 1] && item != arr[pos + 1];
});
// Push the uncached ranges into an array of arrays for ease of
// iteration in the functions that call this.
const uncachedRangePairs = [];
for (i = 0; i < uncachedRanges.length; i += 2) {
uncachedRangePairs.push([uncachedRanges[i], uncachedRanges[i+1]]);
}
return uncachedRangePairs;
}
/**
* For testing
*
* @return {number[][]}
*/
getCacheRanges() {
const peakCacheRangePairs = [];
let i;
for (i = 0; i < this.peakCacheRanges.length; i += 2) {
peakCacheRangePairs.push([this.peakCacheRanges[i], this.peakCacheRanges[i+1]]);
}
return peakCacheRangePairs;
}
}
+137
View File
@@ -0,0 +1,137 @@
/**
* @typedef {Object} CursorPluginParams
* @property {?boolean} deferInit Set to true to stop auto init in `addPlugin()`
*/
/**
* Displays a thin line at the position of the cursor on the waveform.
*
* @implements {PluginClass}
* @extends {Observer}
* @example
* // es6
* import CursorPlugin from 'wavesurfer.cursor.js';
*
* // commonjs
* var CursorPlugin = require('wavesurfer.cursor.js');
*
* // if you are using <script> tags
* var CursorPlugin = window.WaveSurfer.cursor;
*
* // ... initialising wavesurfer with the plugin
* var wavesurfer = WaveSurfer.create({
* // wavesurfer options ...
* plugins: [
* CursorPlugin.create({
* // plugin options ...
* })
* ]
* });
*/
export default class CursorPlugin {
/**
* Cursor plugin definition factory
*
* This function must be used to create a plugin definition which can be
* used by wavesurfer to correctly instantiate the plugin.
*
* @param {CursorPluginParams} params parameters use to initialise the
* plugin
* @return {PluginDefinition} an object representing the plugin
*/
static create(params) {
return {
name: 'cursor',
deferInit: params && params.deferInit ? params.deferInit : false,
params: params,
staticProps: {
enableCursor() {
console.warn('Deprecated enableCursor!');
this.initPlugins('cursor');
}
},
instance: CursorPlugin
};
}
constructor(params, ws) {
this.wavesurfer = ws;
this.style = ws.util.style;
this._onDrawerCreated = () => {
this.drawer = this.wavesurfer.drawer;
this.wrapper = this.wavesurfer.drawer.wrapper;
this._onMousemove = e => this.updateCursorPosition(this.drawer.handleEvent(e));
this.wrapper.addEventListener('mousemove', this._onMousemove);
this._onMouseenter = () => this.showCursor();
this.wrapper.addEventListener('mouseenter', this._onMouseenter);
this._onMouseleave = () => this.hideCursor();
this.wrapper.addEventListener('mouseleave', this._onMouseleave);
this.cursor = this.wrapper.appendChild(
this.style(document.createElement('wave'), {
position: 'absolute',
zIndex: 3,
left: 0,
top: 0,
bottom: 0,
width: '0',
display: 'block',
borderRightStyle: 'solid',
borderRightWidth: 1 + 'px',
borderRightColor: 'black',
opacity: '.25',
pointerEvents: 'none'
})
);
};
}
init() {
// drawer already existed, just call initialisation code
if (this.wavesurfer.drawer) {
this._onDrawerCreated();
}
// the drawer was initialised, call the initialisation code
this.wavesurfer.on('drawer-created', this._onDrawerCreated);
}
destroy() {
this.wavesurfer.un('drawer-created', this._onDrawerCreated);
// if cursor was appended, remove it
if (this.cursor) {
this.cursor.parentNode.removeChild(this.cursor);
}
// if the drawer existed (the cached version referenced in the init code),
// remove the event listeners attached to it
if (this.drawer) {
this.wrapper.removeEventListener('mousemove', this._onMousemove);
this.wrapper.removeEventListener('mouseenter', this._onMouseenter);
this.wrapper.removeEventListener('mouseleave', this._onMouseleave);
}
}
updateCursorPosition(progress) {
const pos = Math.round(this.drawer.width * progress) / this.drawer.params.pixelRatio - 1;
this.style(this.cursor, {
left: `${pos}px`
});
}
showCursor() {
this.style(this.cursor, {
display: 'block'
});
}
hideCursor() {
this.style(this.cursor, {
display: 'none'
});
}
}
+297
View File
@@ -0,0 +1,297 @@
/**
* @typedef {Object} ElanPluginParams
* @property {string|HTMLElement} container CSS selector or HTML element where
* the ELAN information should be renderer.
* @property {string} url The location of ELAN XML data
* @property {?boolean} deferInit Set to true to manually call
* @property {?Object} tiers If set only shows the data tiers with the `TIER_ID`
* in this map.
*/
/**
* Downloads and renders ELAN audio transcription documents alongside the
* waveform.
*
* @implements {PluginClass}
* @extends {Observer}
* @example
* // es6
* import ElanPlugin from 'wavesurfer.elan.js';
*
* // commonjs
* var ElanPlugin = require('wavesurfer.elan.js');
*
* // if you are using <script> tags
* var ElanPlugin = window.WaveSurfer.elan;
*
* // ... initialising wavesurfer with the plugin
* var wavesurfer = WaveSurfer.create({
* // wavesurfer options ...
* plugins: [
* ElanPlugin.create({
* // plugin options ...
* })
* ]
* });
*/
export default class ElanPlugin {
/**
* Elan plugin definition factory
*
* This function must be used to create a plugin definition which can be
* used by wavesurfer to correctly instantiate the plugin.
*
* @param {ElanPluginParams} params parameters use to initialise the plugin
* @return {PluginDefinition} an object representing the plugin
*/
static create(params) {
return {
name: 'elan',
deferInit: params && params.deferInit ? params.deferInit : false,
params: params,
instance: ElanPlugin
};
}
Types = {
ALIGNABLE_ANNOTATION: 'ALIGNABLE_ANNOTATION',
REF_ANNOTATION: 'REF_ANNOTATION'
}
constructor(params, ws) {
this.data = null;
this.params = params;
this.container = 'string' == typeof params.container ?
document.querySelector(params.container) : params.container;
if (!this.container) {
throw Error('No container for ELAN');
}
}
init() {
this.bindClick();
if (this.params.url) {
this.load(this.params.url);
}
}
destroy() {
this.container.removeEventListener('click', this._onClick);
this.container.removeChild(this.table);
}
load(url) {
this.loadXML(url, xml => {
this.data = this.parseElan(xml);
this.render();
this.fireEvent('ready', this.data);
});
}
loadXML(url, callback) {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'document';
xhr.send();
xhr.addEventListener('load', e => {
callback && callback(e.target.responseXML);
});
}
parseElan(xml) {
const _forEach = Array.prototype.forEach;
const _map = Array.prototype.map;
const data = {
media: {},
timeOrder: {},
tiers: [],
annotations: {},
alignableAnnotations: []
};
const header = xml.querySelector('HEADER');
const inMilliseconds = header.getAttribute('TIME_UNITS') == 'milliseconds';
const media = header.querySelector('MEDIA_DESCRIPTOR');
data.media.url = media.getAttribute('MEDIA_URL');
data.media.type = media.getAttribute('MIME_TYPE');
const timeSlots = xml.querySelectorAll('TIME_ORDER TIME_SLOT');
const timeOrder = {};
_forEach.call(timeSlots, slot => {
let value = parseFloat(slot.getAttribute('TIME_VALUE'));
// If in milliseconds, convert to seconds with rounding
if (inMilliseconds) {
value = Math.round(value * 1e2) / 1e5;
}
timeOrder[slot.getAttribute('TIME_SLOT_ID')] = value;
});
data.tiers = _map.call(xml.querySelectorAll('TIER'), tier => ({
id: tier.getAttribute('TIER_ID'),
linguisticTypeRef: tier.getAttribute('LINGUISTIC_TYPE_REF'),
defaultLocale: tier.getAttribute('DEFAULT_LOCALE'),
annotations: _map.call(
tier.querySelectorAll('REF_ANNOTATION, ALIGNABLE_ANNOTATION'), node => {
const annot = {
type: node.nodeName,
id: node.getAttribute('ANNOTATION_ID'),
ref: node.getAttribute('ANNOTATION_REF'),
value: node.querySelector('ANNOTATION_VALUE')
.textContent.trim()
};
if (this.Types.ALIGNABLE_ANNOTATION == annot.type) {
// Add start & end to alignable annotation
annot.start = timeOrder[node.getAttribute('TIME_SLOT_REF1')];
annot.end = timeOrder[node.getAttribute('TIME_SLOT_REF2')];
// Add to the list of alignable annotations
data.alignableAnnotations.push(annot);
}
// Additionally, put into the flat map of all annotations
data.annotations[annot.id] = annot;
return annot;
}
)
}));
// Create JavaScript references between annotations
data.tiers.forEach(tier => {
tier.annotations.forEach(annot => {
if (null != annot.ref) {
annot.reference = data.annotations[annot.ref];
}
});
});
// Sort alignable annotations by start & end
data.alignableAnnotations.sort((a, b) => {
let d = a.start - b.start;
if (d == 0) {
d = b.end - a.end;
}
return d;
});
data.length = data.alignableAnnotations.length;
return data;
}
render() {
// apply tiers filter
let tiers = this.data.tiers;
if (this.params.tiers) {
tiers = tiers.filter(tier => tier.id in this.params.tiers);
}
// denormalize references to alignable annotations
const backRefs = {};
let indeces = {};
tiers.forEach((tier, index) => {
tier.annotations.forEach(annot => {
if (annot.reference && annot.reference.type == this.Types.ALIGNABLE_ANNOTATION) {
if (!(annot.reference.id in backRefs)) {
backRefs[annot.ref] = {};
}
backRefs[annot.ref][index] = annot;
indeces[index] = true;
}
});
});
indeces = Object.keys(indeces).sort();
this.renderedAlignable = this.data.alignableAnnotations.filter(alignable => backRefs[alignable.id]);
// table
const table = this.table = document.createElement('table');
table.className = 'wavesurfer-annotations';
// head
const thead = document.createElement('thead');
const headRow = document.createElement('tr');
thead.appendChild(headRow);
table.appendChild(thead);
const th = document.createElement('th');
th.textContent = 'Time';
th.className = 'wavesurfer-time';
headRow.appendChild(th);
indeces.forEach(index => {
const tier = tiers[index];
const th = document.createElement('th');
th.className = 'wavesurfer-tier-' + tier.id;
th.textContent = tier.id;
th.style.width = this.params.tiers[tier.id];
headRow.appendChild(th);
});
// body
const tbody = document.createElement('tbody');
table.appendChild(tbody);
this.renderedAlignable.forEach(alignable => {
const row = document.createElement('tr');
row.id = 'wavesurfer-alignable-' + alignable.id;
tbody.appendChild(row);
const td = document.createElement('td');
td.className = 'wavesurfer-time';
td.textContent = alignable.start.toFixed(1) + '' +
alignable.end.toFixed(1);
row.appendChild(td);
const backRef = backRefs[alignable.id];
indeces.forEach(index => {
const tier = tiers[index];
const td = document.createElement('td');
const annotation = backRef[index];
if (annotation) {
td.id = 'wavesurfer-annotation-' + annotation.id;
td.dataset.ref = alignable.id;
td.dataset.start = alignable.start;
td.dataset.end = alignable.end;
td.textContent = annotation.value;
}
td.className = 'wavesurfer-tier-' + tier.id;
row.appendChild(td);
});
});
this.container.innerHTML = '';
this.container.appendChild(table);
}
bindClick() {
this._onClick = e => {
const ref = e.target.dataset.ref;
if (null != ref) {
const annot = this.data.annotations[ref];
if (annot) {
this.fireEvent('select', annot.start, annot.end);
}
}
};
this.container.addEventListener('click', this._onClick);
}
getRenderedAnnotation(time) {
let result;
this.renderedAlignable.some(annotation => {
if (annotation.start <= time && annotation.end >= time) {
result = annotation;
return true;
}
return false;
});
return result;
}
getAnnotationNode(annotation) {
return document.getElementById(
'wavesurfer-alignable-' + annotation.id
);
}
}
@@ -0,0 +1,350 @@
/**
* @typedef {Object} MicrophonePluginParams
* @property {MediaStreamConstraints} constraints The constraints parameter is a
* MediaStreamConstaints object with two members: video and audio, describing
* the media types requested. Either or both must be specified.
* @property {number} bufferSize=4096 The buffer size in units of sample-frames.
* If specified, the bufferSize must be one of the following values: `256`,
* `512`, `1024`, `2048`, `4096`, `8192`, `16384`
* @property {number} numberOfInputChannels=1 Integer specifying the number of
* channels for this node's input. Values of up to 32 are supported.
* @property {?boolean} deferInit Set to true to manually call
* `initPlugin('microphone')`
*/
/**
* Visualise microphone input in a wavesurfer instance.
*
* @implements {PluginClass}
* @extends {Observer}
* @example
* // es6
* import MicrophonePlugin from 'wavesurfer.microphone.js';
*
* // commonjs
* var MicrophonePlugin = require('wavesurfer.microphone.js');
*
* // if you are using <script> tags
* var MicrophonePlugin = window.WaveSurfer.microphone;
*
* // ... initialising wavesurfer with the plugin
* var wavesurfer = WaveSurfer.create({
* // wavesurfer options ...
* plugins: [
* MicrophonePlugin.create({
* // plugin options ...
* })
* ]
* });
*/
export default class MicrophonePlugin {
/**
* Microphone plugin definition factory
*
* This function must be used to create a plugin definition which can be
* used by wavesurfer to correctly instantiate the plugin.
*
* @param {MicrophonePluginParams} params parameters use to initialise the plugin
* @return {PluginDefinition} an object representing the plugin
*/
static create(params) {
return {
name: 'microphone',
deferInit: params && params.deferInit ? params.deferInit : false,
params: params,
instance: MicrophonePlugin
};
}
constructor(params, ws) {
this.params = params;
this.wavesurfer = ws;
this.active = false;
this.paused = false;
this.reloadBufferFunction = e => this.reloadBuffer(e);
// cross-browser getUserMedia
const promisifiedOldGUM = (constraints, successCallback, errorCallback) => {
// get ahold of getUserMedia, if present
const getUserMedia = (navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia
);
// Some browsers just don't implement it - return a rejected
// promise with an error to keep a consistent interface
if (!getUserMedia) {
return Promise.reject(
new Error('getUserMedia is not implemented in this browser')
);
}
// otherwise, wrap the call to the old navigator.getUserMedia with
// a Promise
return new Promise((successCallback, errorCallback) => {
getUserMedia.call(navigator, constraints, successCallback, errorCallback);
});
};
// Older browsers might not implement mediaDevices at all, so we set an
// empty object first
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {};
}
// Some browsers partially implement mediaDevices. We can't just assign
// an object with getUserMedia as it would overwrite existing
// properties. Here, we will just add the getUserMedia property if it's
// missing.
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = promisifiedOldGUM;
}
this.constraints = this.params.constraints || {
video: false,
audio: true
};
this.bufferSize = this.params.bufferSize || 4096;
this.numberOfInputChannels = this.params.numberOfInputChannels || 1;
this.numberOfOutputChannels = this.params.numberOfOutputChannels || 1;
this._onBackendCreated = () => {
// wavesurfer's AudioContext where we'll route the mic signal to
this.micContext = this.wavesurfer.backend.getAudioContext();
};
}
init() {
this.wavesurfer.on('backend-created', this._onBackendCreated);
if (this.wavesurfer.backend) {
this._onBackendCreated();
}
}
/**
* Destroy the microphone plugin.
*/
destroy() {
// make sure the buffer is not redrawn during
// cleanup and demolition of this plugin.
this.paused = true;
this.wavesurfer.un('backend-created', this._onBackendCreated);
this.stop();
}
/**
* Allow user to select audio input device, eg. microphone, and
* start the visualization.
*/
start() {
navigator.mediaDevices.getUserMedia(this.constraints)
.then((data) => this.gotStream(data))
.catch((data) => this.deviceError(data));
}
/**
* Pause/resume visualization.
*/
togglePlay() {
if (!this.active) {
// start it first
this.start();
} else {
// toggle paused
this.paused = !this.paused;
if (this.paused) {
this.pause();
} else {
this.play();
}
}
}
/**
* Play visualization.
*/
play() {
this.paused = false;
this.connect();
}
/**
* Pause visualization.
*/
pause() {
this.paused = true;
// disconnect sources so they can be used elsewhere
// (eg. during audio playback)
this.disconnect();
}
/**
* Stop the device stream and remove any remaining waveform drawing from
* the wavesurfer canvas.
*/
stop() {
if (this.active) {
// stop visualization and device
this.stopDevice();
// empty last frame
this.wavesurfer.empty();
}
}
/**
* Stop the device and the visualization.
*/
stopDevice() {
this.active = false;
// stop visualization
this.disconnect();
// stop stream from device
if (this.stream) {
const result = this.detectBrowser();
// MediaStream.stop is deprecated since:
// - Firefox 44 (https://www.fxsitecompat.com/en-US/docs/2015/mediastream-stop-has-been-deprecated/)
// - Chrome 45 (https://developers.google.com/web/updates/2015/07/mediastream-deprecations)
if ((result.browser === 'chrome' && result.version >= 45) ||
(result.browser === 'firefox' && result.version >= 44) ||
(result.browser === 'edge')) {
if (this.stream.getTracks) { // note that this should not be a call
this.stream.getTracks().forEach(stream => stream.stop());
return;
}
}
this.stream.stop();
}
}
/**
* Connect the media sources that feed the visualization.
*/
connect() {
if (this.stream !== undefined) {
// Create an AudioNode from the stream.
this.mediaStreamSource = this.micContext.createMediaStreamSource(this.stream);
this.levelChecker = this.micContext.createScriptProcessor(
this.bufferSize,
this.numberOfInputChannels,
this.numberOfOutputChannels
);
this.mediaStreamSource.connect(this.levelChecker);
this.levelChecker.connect(this.micContext.destination);
this.levelChecker.onaudioprocess = this.reloadBufferFunction;
}
}
/**
* Disconnect the media sources that feed the visualization.
*/
disconnect() {
if (this.mediaStreamSource !== undefined) {
this.mediaStreamSource.disconnect();
}
if (this.levelChecker !== undefined) {
this.levelChecker.disconnect();
this.levelChecker.onaudioprocess = undefined;
}
}
/**
* Redraw the waveform.
*/
reloadBuffer(event) {
if (!this.paused) {
this.wavesurfer.empty();
this.wavesurfer.loadDecodedBuffer(event.inputBuffer);
}
}
/**
* Audio input device is ready.
*
* @param {LocalMediaStream} stream The microphone's media stream.
*/
gotStream(stream) {
this.stream = stream;
this.active = true;
// start visualization
this.play();
// notify listeners
this.fireEvent('deviceReady', stream);
}
/**
* Device error callback.
*/
deviceError(code) {
// notify listeners
this.fireEvent('deviceError', code);
}
/**
* Extract browser version out of the provided user agent string.
* @param {!string} uastring userAgent string.
* @param {!string} expr Regular expression used as match criteria.
* @param {!number} pos position in the version string to be returned.
* @return {!number} browser version.
*/
extractVersion(uastring, expr, pos) {
const match = uastring.match(expr);
return match && match.length >= pos && parseInt(match[pos], 10);
}
/**
* Browser detector.
* @return {object} result containing browser, version and minVersion
* properties.
*/
detectBrowser() {
// Returned result object.
const result = {};
result.browser = null;
result.version = null;
result.minVersion = null;
// Non supported browser.
if (typeof window === 'undefined' || !window.navigator) {
result.browser = 'Not a supported browser.';
return result;
}
// Firefox.
if (navigator.mozGetUserMedia) {
result.browser = 'firefox';
result.version = this.extractVersion(navigator.userAgent, /Firefox\/([0-9]+)\./, 1);
result.minVersion = 31;
return result;
}
// Chrome/Chromium/Webview.
if (navigator.webkitGetUserMedia && window.webkitRTCPeerConnection) {
result.browser = 'chrome';
result.version = this.extractVersion(navigator.userAgent, /Chrom(e|ium)\/([0-9]+)\./, 2);
result.minVersion = 38;
return result;
}
// Edge.
if (navigator.mediaDevices && navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) {
result.browser = 'edge';
result.version = this.extractVersion(navigator.userAgent, /Edge\/(\d+).(\d+)$/, 2);
result.minVersion = 10547;
return result;
}
// Non supported browser default.
result.browser = 'Not a supported browser.';
return result;
}
}
+326
View File
@@ -0,0 +1,326 @@
/**
* @typedef {Object} MinimapPluginParams
* @desc Extends the `WavesurferParams` wavesurfer was initialised with
* @property {?string|HTMLElement} container CSS selector or HTML element where
* the ELAN information should be renderer. By default it is simply appended
* after the waveform.
* @property {?boolean} deferInit Set to true to manually call
* `initPlugin('minimap')`
*/
/**
* Renders a smaller version waveform as a minimap of the main waveform.
*
* @implements {PluginClass}
* @extends {Observer}
* @example
* // es6
* import MinimapPlugin from 'wavesurfer.minimap.js';
*
* // commonjs
* var MinimapPlugin = require('wavesurfer.minimap.js');
*
* // if you are using <script> tags
* var MinimapPlugin = window.WaveSurfer.minimap;
*
* // ... initialising wavesurfer with the plugin
* var wavesurfer = WaveSurfer.create({
* // wavesurfer options ...
* plugins: [
* MinimapPlugin.create({
* // plugin options ...
* })
* ]
* });
*/
export default class MinimapPlugin {
/**
* Minimap plugin definition factory
*
* This function must be used to create a plugin definition which can be
* used by wavesurfer to correctly instantiate the plugin.
*
* @param {MinimapPluginParams} params parameters use to initialise the plugin
* @return {PluginDefinition} an object representing the plugin
*/
static create(params) {
return {
name: 'minimap',
deferInit: params && params.deferInit ? params.deferInit : false,
params: params,
staticProps: {
initMinimap(customConfig) {
console.warn('Deprecated initMinimap!');
params = customConfig;
this.initPlugins('minimap');
}
},
instance: MinimapPlugin
};
}
constructor(params, ws) {
this.params = ws.util.extend(
{}, ws.params, {
showRegions: false,
showOverview: false,
overviewBorderColor: 'green',
overviewBorderSize: 2,
// the container should be different
container: false,
height: Math.max(Math.round(ws.params.height / 4), 20)
}, params, {
scrollParent: false,
fillParent: true
}
);
// if container is a selector, get the element
if (typeof params.container === 'string') {
const el = document.querySelector(params.container);
if (!el) {
console.warn(`Wavesurfer minimap container ${params.container} was not found! The minimap will be automatically appended below the waveform.`);
}
this.params.container = el;
}
// if no container is specified add a new element and insert it
if (!params.container) {
this.params.container = ws.util.style(document.createElement('minimap'), {
display: 'block'
});
}
this.drawer = new (ws.Drawer)(this.params.container, this.params);
this.wavesurfer = ws;
this.util = ws.util;
/**
* Minimap needs to register to ready and waveform-ready events to
* work with MediaElement, the time when ready is called is different
* (peaks can not be got)
*
* @type {string}
* @see https://github.com/katspaugh/wavesurfer.js/issues/736
*/
this.renderEvent = ws.params.backend === 'MediaElement' ? 'waveform-ready' : 'ready';
this.overviewRegion = null;
this.drawer.createWrapper();
this.createElements();
let isInitialised = false;
// ws ready event listener
this._onShouldRender = () => {
// only bind the events in the first run
if (!isInitialised) {
this.bindWavesurferEvents();
this.bindMinimapEvents();
isInitialised = true;
}
// if there is no such element, append it to the container (below
// the waveform)
if (!document.body.contains(this.params.container)) {
ws.container.insertBefore(this.params.container, null);
}
if (this.wavesurfer.regions && this.params.showRegions) {
this.regions();
}
this.render();
};
this._onAudioprocess = currentTime => {
this.drawer.progress(this.wavesurfer.backend.getPlayedPercents());
};
// ws seek event listener
this._onSeek = () => this.drawer.progress(ws.backend.getPlayedPercents());
// event listeners for the overview region
this._onScroll = e => {
if (!this.draggingOverview) {
this.moveOverviewRegion(e.target.scrollLeft / this.ratio);
}
};
this._onMouseover = e => {
if (this.draggingOverview) {
this.draggingOverview = false;
}
};
let prevWidth = 0;
this._onResize = ws.util.debounce(() => {
if (prevWidth != this.drawer.wrapper.clientWidth) {
prevWidth = this.drawer.wrapper.clientWidth;
this.render();
this.drawer.progress(this.wavesurfer.backend.getPlayedPercents());
}
});
}
init() {
if (this.wavesurfer.isReady) {
this._onShouldRender();
}
this.wavesurfer.on(this.renderEvent, this._onShouldRender);
}
destroy() {
window.removeEventListener('resize', this._onResize, true);
window.removeEventListener('orientationchange', this._onResize, true);
this.wavesurfer.drawer.wrapper.removeEventListener('mouseover', this._onMouseover);
this.wavesurfer.un(this.renderEvent, this._onShouldRender);
this.wavesurfer.un('seek', this._onSeek);
this.wavesurfer.un('scroll', this._onScroll);
this.wavesurfer.un('audioprocess', this._onAudioprocess);
this.drawer.destroy();
this.overviewRegion = null;
this.unAll();
}
regions() {
this.regions = {};
this.wavesurfer.on('region-created', region => {
this.regions[region.id] = region;
this.renderRegions();
});
this.wavesurfer.on('region-updated', region => {
this.regions[region.id] = region;
this.renderRegions();
});
this.wavesurfer.on('region-removed', region => {
delete this.regions[region.id];
this.renderRegions();
});
}
renderRegions() {
const regionElements = this.drawer.wrapper.querySelectorAll('region');
let i;
for (i = 0; i < regionElements.length; ++i) {
this.drawer.wrapper.removeChild(regionElements[i]);
}
Object.keys(this.regions).forEach(id => {
const region = this.regions[id];
const width = (this.drawer.width * ((region.end - region.start) / this.wavesurfer.getDuration()));
const left = (this.drawer.width * (region.start / this.wavesurfer.getDuration()));
const regionElement = this.util.style(document.createElement('region'), {
height: 'inherit',
backgroundColor: region.color,
width: width + 'px',
left: left + 'px',
display: 'block',
position: 'absolute'
});
regionElement.classList.add(id);
this.drawer.wrapper.appendChild(regionElement);
});
}
createElements() {
this.drawer.createElements();
if (this.params.showOverview) {
this.overviewRegion = this.util.style(document.createElement('overview'), {
height: (this.drawer.wrapper.offsetHeight - (this.params.overviewBorderSize * 2)) + 'px',
width: '0px',
display: 'block',
position: 'absolute',
cursor: 'move',
border: this.params.overviewBorderSize + 'px solid ' + this.params.overviewBorderColor,
zIndex: 2,
opacity: this.params.overviewOpacity
});
this.drawer.wrapper.appendChild(this.overviewRegion);
}
}
bindWavesurferEvents() {
window.addEventListener('resize', this._onResize, true);
window.addEventListener('orientationchange', this._onResize, true);
this.wavesurfer.on('audioprocess', this._onAudioprocess);
this.wavesurfer.on('seek', this._onSeek);
if (this.params.showOverview) {
this.wavesurfer.on('scroll', this._onScroll);
this.wavesurfer.drawer.wrapper.addEventListener('mouseover', this._onMouseover);
}
}
bindMinimapEvents() {
const positionMouseDown = {
clientX: 0,
clientY: 0
};
let relativePositionX = 0;
let seek = true;
// the following event listeners will be destroyed by using
// this.unAll() and nullifying the DOM node references after
// removing them
this.on('click', (e, position) => {
if (seek) {
this.progress(position);
this.wavesurfer.seekAndCenter(position);
} else {
seek = true;
}
});
if (this.params.showOverview) {
this.overviewRegion.addEventListener('mousedown', event => {
this.draggingOverview = true;
relativePositionX = event.layerX;
positionMouseDown.clientX = event.clientX;
positionMouseDown.clientY = event.clientY;
});
this.drawer.wrapper.addEventListener('mousemove', event => {
if (this.draggingOverview) {
this.moveOverviewRegion(event.clientX - this.drawer.container.getBoundingClientRect().left - relativePositionX);
}
});
this.drawer.wrapper.addEventListener('mouseup', event => {
if (positionMouseDown.clientX - event.clientX === 0 && positionMouseDown.clientX - event.clientX === 0) {
seek = true;
this.draggingOverview = false;
} else if (this.draggingOverview) {
seek = false;
this.draggingOverview = false;
}
});
}
}
render() {
const len = this.drawer.getWidth();
const peaks = this.wavesurfer.backend.getPeaks(len, 0, len);
this.drawer.drawPeaks(peaks, len, 0, len);
this.drawer.progress(this.wavesurfer.backend.getPlayedPercents());
if (this.params.showOverview) {
//get proportional width of overview region considering the respective
//width of the drawers
this.ratio = this.wavesurfer.drawer.width / this.drawer.width;
this.waveShowedWidth = this.wavesurfer.drawer.width / this.ratio;
this.waveWidth = this.wavesurfer.drawer.width;
this.overviewWidth = (this.drawer.width / this.ratio);
this.overviewPosition = 0;
this.moveOverviewRegion(this.wavesurfer.drawer.wrapper.scrollLeft / this.ratio);
this.overviewRegion.style.width = (this.overviewWidth - (this.params.overviewBorderSize * 2)) + 'px';
}
}
moveOverviewRegion(pixels) {
if (pixels < 0) {
this.overviewPosition = 0;
} else if (pixels + this.overviewWidth < this.drawer.width) {
this.overviewPosition = pixels;
} else {
this.overviewPosition = (this.drawer.width - this.overviewWidth);
}
this.overviewRegion.style.left = this.overviewPosition + 'px';
if (this.draggingOverview) {
this.wavesurfer.drawer.wrapper.scrollLeft = this.overviewPosition * this.ratio;
}
}
}
+622
View File
@@ -0,0 +1,622 @@
/**
* (Single) Region plugin class
*
* Must be turned into an observer before instantiating. This is done in
* RegionsPlugin (main plugin class)
*
* @extends {Observer}
*/
class Region {
constructor(params, ws) {
this.wavesurfer = ws;
this.wrapper = ws.drawer.wrapper;
this.style = ws.util.style;
this.id = params.id == null ? ws.util.getId() : params.id;
this.start = Number(params.start) || 0;
this.end = params.end == null ?
// small marker-like region
this.start + (4 / this.wrapper.scrollWidth) * this.wavesurfer.getDuration() :
Number(params.end);
this.resize = params.resize === undefined ? true : Boolean(params.resize);
this.drag = params.drag === undefined ? true : Boolean(params.drag);
this.loop = Boolean(params.loop);
this.color = params.color || 'rgba(0, 0, 0, 0.1)';
this.data = params.data || {};
this.attributes = params.attributes || {};
this.maxLength = params.maxLength;
this.minLength = params.minLength;
this.bindInOut();
this.render();
this.onZoom = this.updateRender.bind(this);
this.wavesurfer.on('zoom', this.onZoom);
this.wavesurfer.fireEvent('region-created', this);
}
/* Update region params. */
update(params) {
if (null != params.start) {
this.start = Number(params.start);
}
if (null != params.end) {
this.end = Number(params.end);
}
if (null != params.loop) {
this.loop = Boolean(params.loop);
}
if (null != params.color) {
this.color = params.color;
}
if (null != params.data) {
this.data = params.data;
}
if (null != params.resize) {
this.resize = Boolean(params.resize);
}
if (null != params.drag) {
this.drag = Boolean(params.drag);
}
if (null != params.maxLength) {
this.maxLength = Number(params.maxLength);
}
if (null != params.minLength) {
this.minLength = Number(params.minLength);
}
if (null != params.attributes) {
this.attributes = params.attributes;
}
this.updateRender();
this.fireEvent('update');
this.wavesurfer.fireEvent('region-updated', this);
}
/* Remove a single region. */
remove() {
if (this.element) {
this.wrapper.removeChild(this.element);
this.element = null;
this.fireEvent('remove');
this.wavesurfer.un('zoom', this.onZoom);
this.wavesurfer.fireEvent('region-removed', this);
}
}
/* Play the audio region. */
play() {
this.wavesurfer.play(this.start, this.end);
this.fireEvent('play');
this.wavesurfer.fireEvent('region-play', this);
}
/* Play the region in loop. */
playLoop() {
this.play();
this.once('out', () => this.playLoop());
}
/* Render a region as a DOM element. */
render() {
const regionEl = document.createElement('region');
regionEl.className = 'wavesurfer-region';
regionEl.title = this.formatTime(this.start, this.end);
regionEl.setAttribute('data-id', this.id);
for (const attrname in this.attributes) {
regionEl.setAttribute('data-region-' + attrname, this.attributes[attrname]);
}
const width = this.wrapper.scrollWidth;
this.style(regionEl, {
position: 'absolute',
zIndex: 2,
height: '100%',
top: '0px'
});
/* Resize handles */
if (this.resize) {
const handleLeft = regionEl.appendChild(document.createElement('handle'));
const handleRight = regionEl.appendChild(document.createElement('handle'));
handleLeft.className = 'wavesurfer-handle wavesurfer-handle-start';
handleRight.className = 'wavesurfer-handle wavesurfer-handle-end';
const css = {
cursor: 'col-resize',
position: 'absolute',
left: '0px',
top: '0px',
width: '1%',
maxWidth: '4px',
height: '100%'
};
this.style(handleLeft, css);
this.style(handleRight, css);
this.style(handleRight, {
left: '100%'
});
}
this.element = this.wrapper.appendChild(regionEl);
this.updateRender();
this.bindEvents(regionEl);
}
formatTime(start, end) {
return (start == end ? [start] : [start, end]).map(time => [
Math.floor((time % 3600) / 60), // minutes
('00' + Math.floor(time % 60)).slice(-2) // seconds
].join(':')).join('-');
}
getWidth() {
return this.wavesurfer.drawer.width / this.wavesurfer.params.pixelRatio;
}
/* Update element's position, width, color. */
updateRender() {
const dur = this.wavesurfer.getDuration();
const width = this.getWidth();
if (this.start < 0) {
this.start = 0;
this.end = this.end - this.start;
}
if (this.end > dur) {
this.end = dur;
this.start = dur - (this.end - this.start);
}
if (this.minLength != null) {
this.end = Math.max(this.start + this.minLength, this.end);
}
if (this.maxLength != null) {
this.end = Math.min(this.start + this.maxLength, this.end);
}
if (this.element != null) {
// Calculate the left and width values of the region such that
// no gaps appear between regions.
const left = Math.round(this.start / dur * width);
const regionWidth =
Math.round(this.end / dur * width) - left;
this.style(this.element, {
left: left + 'px',
width: regionWidth + 'px',
backgroundColor: this.color,
cursor: this.drag ? 'move' : 'default'
});
for (const attrname in this.attributes) {
this.element.setAttribute('data-region-' + attrname, this.attributes[attrname]);
}
this.element.title = this.formatTime(this.start, this.end);
}
}
/* Bind audio events. */
bindInOut() {
this.firedIn = false;
this.firedOut = false;
const onProcess = time => {
if (!this.firedOut && this.firedIn && (this.start >= Math.round(time * 100) / 100 || this.end <= Math.round(time * 100) / 100)) {
this.firedOut = true;
this.firedIn = false;
this.fireEvent('out');
this.wavesurfer.fireEvent('region-out', this);
}
if (!this.firedIn && this.start <= time && this.end > time) {
this.firedIn = true;
this.firedOut = false;
this.fireEvent('in');
this.wavesurfer.fireEvent('region-in', this);
}
};
this.wavesurfer.backend.on('audioprocess', onProcess);
this.on('remove', () => {
this.wavesurfer.backend.un('audioprocess', onProcess);
});
/* Loop playback. */
this.on('out', () => {
if (this.loop) {
this.wavesurfer.play(this.start);
}
});
}
/* Bind DOM events. */
bindEvents() {
this.element.addEventListener('mouseenter', e => {
this.fireEvent('mouseenter', e);
this.wavesurfer.fireEvent('region-mouseenter', this, e);
});
this.element.addEventListener('mouseleave', e => {
this.fireEvent('mouseleave', e);
this.wavesurfer.fireEvent('region-mouseleave', this, e);
});
this.element.addEventListener('click', e => {
e.preventDefault();
this.fireEvent('click', e);
this.wavesurfer.fireEvent('region-click', this, e);
});
this.element.addEventListener('dblclick', e => {
e.stopPropagation();
e.preventDefault();
this.fireEvent('dblclick', e);
this.wavesurfer.fireEvent('region-dblclick', this, e);
});
/* Drag or resize on mousemove. */
(this.drag || this.resize) && (() => {
const duration = this.wavesurfer.getDuration();
let startTime;
let touchId;
let drag;
let resize;
const onDown = e => {
if (e.touches && e.touches.length > 1) { return; }
touchId = e.targetTouches ? e.targetTouches[0].identifier : null;
e.stopPropagation();
startTime = this.wavesurfer.drawer.handleEvent(e, true) * duration;
if (e.target.tagName.toLowerCase() == 'handle') {
if (e.target.classList.contains('wavesurfer-handle-start')) {
resize = 'start';
} else {
resize = 'end';
}
} else {
drag = true;
resize = false;
}
};
const onUp = e => {
if (e.touches && e.touches.length > 1) { return; }
if (drag || resize) {
drag = false;
resize = false;
this.fireEvent('update-end', e);
this.wavesurfer.fireEvent('region-update-end', this, e);
}
};
const onMove = e => {
if (e.touches && e.touches.length > 1) { return; }
if (e.targetTouches && e.targetTouches[0].identifier != touchId) { return; }
if (drag || resize) {
const time = this.wavesurfer.drawer.handleEvent(e) * duration;
const delta = time - startTime;
startTime = time;
// Drag
if (this.drag && drag) {
this.onDrag(delta);
}
// Resize
if (this.resize && resize) {
this.onResize(delta, resize);
}
}
};
this.element.addEventListener('mousedown', onDown);
this.element.addEventListener('touchstart', onDown);
this.wrapper.addEventListener('mousemove', onMove);
this.wrapper.addEventListener('touchmove', onMove);
document.body.addEventListener('mouseup', onUp);
document.body.addEventListener('touchend', onUp);
this.on('remove', () => {
document.body.removeEventListener('mouseup', onUp);
document.body.removeEventListener('touchend', onUp);
this.wrapper.removeEventListener('mousemove', onMove);
this.wrapper.removeEventListener('touchmove', onMove);
});
this.wavesurfer.on('destroy', () => {
document.body.removeEventListener('mouseup', onUp);
document.body.removeEventListener('touchend', onUp);
});
})();
}
onDrag(delta) {
const maxEnd = this.wavesurfer.getDuration();
if ((this.end + delta) > maxEnd || (this.start + delta) < 0) {
return;
}
this.update({
start: this.start + delta,
end: this.end + delta
});
}
onResize(delta, direction) {
if (direction == 'start') {
this.update({
start: Math.min(this.start + delta, this.end),
end: Math.max(this.start + delta, this.end)
});
} else {
this.update({
start: Math.min(this.end + delta, this.start),
end: Math.max(this.end + delta, this.start)
});
}
}
}
/**
* @typedef {Object} RegionsPluginParams
* @property {?boolean} dragSelection Enable creating regions by dragging wih
* the mouse
* @property {?RegionParams[]} regions Regions that should be added upon
* initialisation
* @property {number} slop=2 The sensitivity of the mouse dragging
* @property {?boolean} deferInit Set to true to manually call
* `initPlugin('regions')`
*/
/**
* @typedef {Object} RegionParams
* @desc The parameters used to describe a region.
* @example wavesurfer.addRegion(regionParams);
* @property {string} id=→random The id of the region
* @property {number} start=0 The start position of the region (in seconds).
* @property {number} end=0 The end position of the region (in seconds).
* @property {?boolean} loop Whether to loop the region when played back.
* @property {boolean} drag=true Allow/dissallow dragging the region.
* @property {boolean} resize=true Allow/dissallow resizing the region.
* @property {string} [color='rgba(0, 0, 0, 0.1)'] HTML color code.
*/
/**
* Regions are visual overlays on waveform that can be used to play and loop
* portions of audio. Regions can be dragged and resized.
*
* Visual customization is possible via CSS (using the selectors
* `.wavesurfer-region` and `.wavesurfer-handle`).
*
* @implements {PluginClass}
* @extends {Observer}
*
* @example
* // es6
* import RegionsPlugin from 'wavesurfer.regions.js';
*
* // commonjs
* var RegionsPlugin = require('wavesurfer.regions.js');
*
* // if you are using <script> tags
* var RegionsPlugin = window.WaveSurfer.regions;
*
* // ... initialising wavesurfer with the plugin
* var wavesurfer = WaveSurfer.create({
* // wavesurfer options ...
* plugins: [
* RegionsPlugin.create({
* // plugin options ...
* })
* ]
* });
*/
export default class RegionsPlugin {
/**
* Regions plugin definition factory
*
* This function must be used to create a plugin definition which can be
* used by wavesurfer to correctly instantiate the plugin.
*
* @param {RegionsPluginParams} params parameters use to initialise the plugin
* @return {PluginDefinition} an object representing the plugin
*/
static create(params) {
return {
name: 'regions',
deferInit: params && params.deferInit ? params.deferInit : false,
params: params,
staticProps: {
initRegions() {
console.warn('Deprecated initRegions! Use wavesurfer.initPlugins("regions") instead!');
this.initPlugin('regions');
},
addRegion(options) {
if (!this.initialisedPluginList.regions) {
this.initPlugin('regions');
}
return this.regions.add(options);
},
clearRegions() {
this.regions && this.regions.clear();
},
enableDragSelection(options) {
if (!this.initialisedPluginList.regions) {
this.initPlugin('regions');
}
this.regions.enableDragSelection(options);
},
disableDragSelection() {
this.regions.disableDragSelection();
}
},
instance: RegionsPlugin
};
}
constructor(params, ws) {
this.params = params;
this.wavesurfer = ws;
this.util = ws.util;
// turn the plugin instance into an observer
const observerPrototypeKeys = Object.getOwnPropertyNames(this.util.Observer.prototype);
observerPrototypeKeys.forEach(key => {
Region.prototype[key] = this.util.Observer.prototype[key];
});
this.wavesurfer.Region = Region;
// Id-based hash of regions.
this.list = {};
this._onReady = () => {
this.wrapper = this.wavesurfer.drawer.wrapper;
if (this.params.regions) {
this.params.regions.forEach(region => {
this.add(region);
});
}
if (this.params.dragSelection) {
this.enableDragSelection(this.params);
}
};
}
init() {
// Check if ws is ready
if (this.wavesurfer.isReady) {
this._onReady();
}
this.wavesurfer.on('ready', this._onReady);
}
destroy() {
this.wavesurfer.un('ready', this._onReady);
this.disableDragSelection();
this.clear();
}
/* Add a region. */
add(params) {
const region = new this.wavesurfer.Region(params, this.wavesurfer);
this.list[region.id] = region;
region.on('remove', () => {
delete this.list[region.id];
});
return region;
}
/* Remove all regions. */
clear() {
Object.keys(this.list).forEach(id => {
this.list[id].remove();
});
}
enableDragSelection(params) {
const slop = params.slop || 2;
let drag;
let start;
let region;
let touchId;
let pxMove = 0;
const eventDown = e => {
if (e.touches && e.touches.length > 1) { return; }
touchId = e.targetTouches ? e.targetTouches[0].identifier : null;
drag = true;
start = this.wavesurfer.drawer.handleEvent(e, true);
region = null;
};
this.wrapper.addEventListener('mousedown', eventDown);
this.wrapper.addEventListener('touchstart', eventDown);
this.on('disable-drag-selection', () => {
this.wrapper.removeEventListener('touchstart', eventDown);
this.wrapper.removeEventListener('mousedown', eventDown);
});
const eventUp = e => {
if (e.touches && e.touches.length > 1) { return; }
drag = false;
pxMove = 0;
if (region) {
region.fireEvent('update-end', e);
this.wavesurfer.fireEvent('region-update-end', region, e);
}
region = null;
};
this.wrapper.addEventListener('mouseup', eventUp);
this.wrapper.addEventListener('touchend', eventUp);
this.on('disable-drag-selection', () => {
this.wrapper.removeEventListener('touchend', eventUp);
this.wrapper.removeEventListener('mouseup', eventUp);
});
const eventMove = e => {
if (!drag) { return; }
if (++pxMove <= slop) { return; }
if (e.touches && e.touches.length > 1) { return; }
if (e.targetTouches && e.targetTouches[0].identifier != touchId) { return; }
if (!region) {
region = this.add(params || {});
}
const duration = this.wavesurfer.getDuration();
const end = this.wavesurfer.drawer.handleEvent(e);
region.update({
start: Math.min(end * duration, start * duration),
end: Math.max(end * duration, start * duration)
});
};
this.wrapper.addEventListener('mousemove', eventMove);
this.wrapper.addEventListener('touchmove', eventMove);
this.on('disable-drag-selection', () => {
this.wrapper.removeEventListener('touchmove', eventMove);
this.wrapper.removeEventListener('mousemove', eventMove);
});
}
disableDragSelection() {
this.fireEvent('disable-drag-selection');
}
/* Get current region
* The smallest region that contains the current time.
* If several such regions exist, we take the first.
* Return null if none exist. */
getCurrentRegion() {
const time = this.wavesurfer.getCurrentTime();
let min = null;
Object.keys(this.list).forEach(id => {
const cur = this.list[id];
if (cur.start <= time && cur.end >= time) {
if (!min || ((cur.end - cur.start) < (min.end - min.start))) {
min = cur;
}
}
});
return min;
}
}
@@ -0,0 +1,562 @@
/**
* Calculate FFT - Based on https://github.com/corbanbrook/dsp.js
*/
/* eslint-disable complexity, no-redeclare, no-var, one-var */
const FFT = function(bufferSize, sampleRate, windowFunc, alpha) {
this.bufferSize = bufferSize;
this.sampleRate = sampleRate;
this.bandwidth = 2 / bufferSize * sampleRate / 2;
this.sinTable = new Float32Array(bufferSize);
this.cosTable = new Float32Array(bufferSize);
this.windowValues = new Float32Array(bufferSize);
this.reverseTable = new Uint32Array(bufferSize);
this.peakBand = 0;
this.peak = 0;
switch (windowFunc) {
case 'bartlett' :
for (var i = 0; i<bufferSize; i++) {
this.windowValues[i] = 2 / (bufferSize - 1) * ((bufferSize - 1) / 2 - Math.abs(i - (bufferSize - 1) / 2));
}
break;
case 'bartlettHann' :
for (var i = 0; i<bufferSize; i++) {
this.windowValues[i] = 0.62 - 0.48 * Math.abs(i / (bufferSize - 1) - 0.5) - 0.38 * Math.cos(Math.PI * 2 * i / (bufferSize - 1));
}
break;
case 'blackman' :
alpha = alpha || 0.16;
for (var i = 0; i<bufferSize; i++) {
this.windowValues[i] = (1 - alpha)/2 - 0.5 * Math.cos(Math.PI * 2 * i / (bufferSize - 1)) + alpha/2 * Math.cos(4 * Math.PI * i / (bufferSize - 1));
}
break;
case 'cosine' :
for (var i = 0; i<bufferSize; i++) {
this.windowValues[i] = Math.cos(Math.PI * i / (bufferSize - 1) - Math.PI / 2);
}
break;
case 'gauss' :
alpha = alpha || 0.25;
for (var i = 0; i<bufferSize; i++) {
this.windowValues[i] = Math.pow(Math.E, -0.5 * Math.pow((i - (bufferSize - 1) / 2) / (alpha * (bufferSize - 1) / 2), 2));
}
break;
case 'hamming' :
for (var i = 0; i<bufferSize; i++) {
this.windowValues[i] = 0.54 - 0.46 * Math.cos(Math.PI * 2 * i / (bufferSize - 1));
}
break;
case 'hann' :
case undefined :
for (var i = 0; i<bufferSize; i++) {
this.windowValues[i] = 0.5 * (1 - Math.cos(Math.PI * 2 * i / (bufferSize - 1)));
}
break;
case 'lanczoz' :
for (var i = 0; i<bufferSize; i++) {
this.windowValues[i] = Math.sin(Math.PI * (2 * i / (bufferSize - 1) - 1)) / (Math.PI * (2 * i / (bufferSize - 1) - 1));
}
break;
case 'rectangular' :
for (var i = 0; i<bufferSize; i++) {
this.windowValues[i] = 1;
}
break;
case 'triangular' :
for (var i = 0; i<bufferSize; i++) {
this.windowValues[i] = 2 / bufferSize * (bufferSize / 2 - Math.abs(i - (bufferSize - 1) / 2));
}
break;
default:
throw Error('No such window function \'' + windowFunc + '\'');
}
var limit = 1;
var bit = bufferSize >> 1;
var i;
while (limit < bufferSize) {
for (i = 0; i < limit; i++) {
this.reverseTable[i + limit] = this.reverseTable[i] + bit;
}
limit = limit << 1;
bit = bit >> 1;
}
for (i = 0; i < bufferSize; i++) {
this.sinTable[i] = Math.sin(-Math.PI/i);
this.cosTable[i] = Math.cos(-Math.PI/i);
}
this.calculateSpectrum = function(buffer) {
// Locally scope variables for speed up
var bufferSize = this.bufferSize,
cosTable = this.cosTable,
sinTable = this.sinTable,
reverseTable = this.reverseTable,
real = new Float32Array(bufferSize),
imag = new Float32Array(bufferSize),
bSi = 2 / this.bufferSize,
sqrt = Math.sqrt,
rval,
ival,
mag,
spectrum = new Float32Array(bufferSize / 2);
var k = Math.floor(Math.log(bufferSize) / Math.LN2);
if (Math.pow(2, k) !== bufferSize) {
throw 'Invalid buffer size, must be a power of 2.';
}
if (bufferSize !== buffer.length) {
throw 'Supplied buffer is not the same size as defined FFT. FFT Size: ' + bufferSize + ' Buffer Size: ' + buffer.length;
}
var halfSize = 1,
phaseShiftStepReal,
phaseShiftStepImag,
currentPhaseShiftReal,
currentPhaseShiftImag,
off,
tr,
ti,
tmpReal;
for (var i = 0; i < bufferSize; i++) {
real[i] = buffer[reverseTable[i]] * this.windowValues[reverseTable[i]];
imag[i] = 0;
}
while (halfSize < bufferSize) {
phaseShiftStepReal = cosTable[halfSize];
phaseShiftStepImag = sinTable[halfSize];
currentPhaseShiftReal = 1;
currentPhaseShiftImag = 0;
for (var fftStep = 0; fftStep < halfSize; fftStep++) {
var i = fftStep;
while (i < bufferSize) {
off = i + halfSize;
tr = (currentPhaseShiftReal * real[off]) - (currentPhaseShiftImag * imag[off]);
ti = (currentPhaseShiftReal * imag[off]) + (currentPhaseShiftImag * real[off]);
real[off] = real[i] - tr;
imag[off] = imag[i] - ti;
real[i] += tr;
imag[i] += ti;
i += halfSize << 1;
}
tmpReal = currentPhaseShiftReal;
currentPhaseShiftReal = (tmpReal * phaseShiftStepReal) - (currentPhaseShiftImag * phaseShiftStepImag);
currentPhaseShiftImag = (tmpReal * phaseShiftStepImag) + (currentPhaseShiftImag * phaseShiftStepReal);
}
halfSize = halfSize << 1;
}
for (var i = 0, N = bufferSize / 2; i < N; i++) {
rval = real[i];
ival = imag[i];
mag = bSi * sqrt(rval * rval + ival * ival);
if (mag > this.peak) {
this.peakBand = i;
this.peak = mag;
}
spectrum[i] = mag;
}
return spectrum;
};
};
/* eslint-enable complexity, no-redeclare, no-var, one-var */
/**
* @typedef {Object} SpectrogramPluginParams
* @property {string|HTMLElement} container Selector of element or element in
* which to render
* @property {number} fftSamples=512 number of samples to fetch to FFT. Must be
* a pwer of 2.
* @property {number} noverlap Size of the overlapping window. Must be <
* fftSamples. Auto deduced from canvas size by default.
* @property {string} windowFunc='hann' The window function to be used. One of
* these: `'bartlett'`, `'bartlettHann'`, `'blackman'`, `'cosine'`, `'gauss'`,
* `'hamming'`, `'hann'`, `'lanczoz'`, `'rectangular'`, `'triangular'`
* @property {?number} alpha Some window functions have this extra value.
* (Between 0 and 1)
* @property {number} pixelRatio=wavesurfer.params.pixelRatio to control the
* size of the spectrogram in relation with its canvas. 1 = Draw on the whole
* canvas. 2 = Draw on a quarter (1/2 the length and 1/2 the width)
* @property {?boolean} deferInit Set to true to manually call
* `initPlugin('spectrogram')`
*/
/**
* Render a spectrogram visualisation of the audio.
*
* @implements {PluginClass}
* @extends {Observer}
* @example
* // es6
* import SpectrogramPlugin from 'wavesurfer.spectrogram.js';
*
* // commonjs
* var SpectrogramPlugin = require('wavesurfer.spectrogram.js');
*
* // if you are using <script> tags
* var SpectrogramPlugin = window.WaveSurfer.spectrogram;
*
* // ... initialising wavesurfer with the plugin
* var wavesurfer = WaveSurfer.create({
* // wavesurfer options ...
* plugins: [
* SpectrogramPlugin.create({
* // plugin options ...
* })
* ]
* });
*/
export default class SpectrogramPlugin {
/**
* Spectrogram plugin definition factory
*
* This function must be used to create a plugin definition which can be
* used by wavesurfer to correctly instantiate the plugin.
*
* @param {SpectrogramPluginParams} params parameters use to initialise the plugin
* @return {PluginDefinition} an object representing the plugin
*/
static create(params) {
return {
name: 'spectrogram',
deferInit: params && params.deferInit ? params.deferInit : false,
params: params,
staticProps: {
FFT: FFT
},
instance: SpectrogramPlugin
};
}
constructor(params, ws) {
this.params = params;
this.wavesurfer = ws;
this.util = ws.util;
this.frequenciesDataUrl = params.frequenciesDataUrl;
this._onScroll = e => {
this.updateScroll(e);
};
this._onReady = () => {
const drawer = this.drawer = ws.drawer;
this.container = 'string' == typeof params.container ?
document.querySelector(params.container) : params.container;
if (!this.container) {
throw Error('No container for WaveSurfer spectrogram');
}
this.width = drawer.width;
this.pixelRatio = this.params.pixelRatio || ws.params.pixelRatio;
this.fftSamples = this.params.fftSamples || ws.params.fftSamples || 512;
this.height = this.fftSamples / 2;
this.noverlap = params.noverlap;
this.windowFunc = params.windowFunc;
this.alpha = params.alpha;
this.createWrapper();
this.createCanvas();
this.render();
drawer.wrapper.addEventListener('scroll', this._onScroll);
ws.on('redraw', () => this.render());
};
}
init() {
// Check if ws is ready
if (this.wavesurfer.isReady) {
this._onReady();
}
this.wavesurfer.on('ready', this._onReady);
}
destroy() {
this.unAll();
this.wavesurfer.un('ready', this._onReady);
this.drawer.wrapper.removeEventListener('scroll', this._onScroll);
this.wavesurfer = null;
this.util = null;
this.params = null;
if (this.wrapper) {
this.wrapper.parentNode.removeChild(this.wrapper);
this.wrapper = null;
}
}
createWrapper() {
const prevSpectrogram = this.container.querySelector('spectrogram');
if (prevSpectrogram) {
this.container.removeChild(prevSpectrogram);
}
const wsParams = this.wavesurfer.params;
this.wrapper = document.createElement('spectrogram');
// if labels are active
if (this.params.labels) {
const labelsEl = this.labelsEl = document.createElement('canvas');
labelsEl.classList.add('spec-labels');
this.drawer.style(labelsEl, {
left: 0,
position: 'absolute',
zIndex: 9,
height: `${this.height / this.pixelRatio}px`,
width: `${55 / this.pixelRatio}px`
});
this.wrapper.appendChild(labelsEl);
// can be customized in next version
this.loadLabels('rgba(68,68,68,0.5)', '12px', '10px', '', '#fff', '#f7f7f7', 'center', '#specLabels');
}
this.drawer.style(this.wrapper, {
display: 'block',
position: 'relative',
userSelect: 'none',
webkitUserSelect: 'none',
height: this.height + 'px'
});
if (wsParams.fillParent || wsParams.scrollParent) {
this.drawer.style(this.wrapper, {
width: '100%',
overflowX: 'hidden',
overflowY: 'hidden'
});
}
this.container.appendChild(this.wrapper);
this.wrapper.addEventListener('click', e => {
e.preventDefault();
const relX = 'offsetX' in e ? e.offsetX : e.layerX;
this.fireEvent('click', (relX / this.scrollWidth) || 0);
});
}
createCanvas() {
const canvas = this.canvas = this.wrapper.appendChild(
document.createElement('canvas')
);
this.spectrCc = canvas.getContext('2d');
this.util.style(canvas, {
position: 'absolute',
zIndex: 4
});
}
render() {
this.updateCanvasStyle();
if (this.frequenciesDataUrl) {
this.loadFrequenciesData(this.frequenciesDataUrl);
} else {
this.getFrequencies(this.drawSpectrogram);
}
}
updateCanvasStyle() {
const width = Math.round(this.width / this.pixelRatio) + 'px';
this.canvas.width = this.width;
this.canvas.height = this.height;
this.canvas.style.width = width;
}
drawSpectrogram(frequenciesData, my) {
const spectrCc = my.spectrCc;
const length = my.wavesurfer.backend.getDuration();
const height = my.height;
const pixels = my.resample(frequenciesData);
const heightFactor = my.buffer ? 2 / my.buffer.numberOfChannels : 1;
let i;
let j;
for (i = 0; i < pixels.length; i++) {
for (j = 0; j < pixels[i].length; j++) {
const colorValue = 255 - pixels[i][j];
my.spectrCc.fillStyle = 'rgb(' + colorValue + ', ' + colorValue + ', ' + colorValue + ')';
my.spectrCc.fillRect(i, height - j * heightFactor, 1, heightFactor);
}
}
}
getFrequencies(callback) {
const fftSamples = this.fftSamples;
const buffer = this.buffer = this.wavesurfer.backend.buffer;
const channelOne = buffer.getChannelData(0);
const bufferLength = buffer.length;
const sampleRate = buffer.sampleRate;
const frequencies = [];
if (!buffer) {
this.fireEvent('error', 'Web Audio buffer is not available');
return;
}
let noverlap = this.noverlap;
if (!noverlap) {
const uniqueSamplesPerPx = buffer.length / this.canvas.width;
noverlap = Math.max(0, Math.round(fftSamples - uniqueSamplesPerPx));
}
const fft = new FFT(fftSamples, sampleRate, this.windowFunc, this.alpha);
const maxSlicesCount = Math.floor(bufferLength / (fftSamples - noverlap));
let currentOffset = 0;
while (currentOffset + fftSamples < channelOne.length) {
const segment = channelOne.slice(currentOffset, currentOffset + fftSamples);
const spectrum = fft.calculateSpectrum(segment);
const array = new Uint8Array(fftSamples / 2);
let j;
for (j = 0; j < fftSamples / 2; j++) {
array[j] = Math.max(-255, Math.log10(spectrum[j]) * 45);
}
frequencies.push(array);
currentOffset += (fftSamples - noverlap);
}
callback(frequencies, this);
}
loadFrequenciesData(url) {
const ajax = this.util.ajax({ url: url });
ajax.on('success', data => this.drawSpectrogram(JSON.parse(data), this));
ajax.on('error', e => this.fireEvent('error', 'XHR error: ' + e.target.statusText));
return ajax;
}
freqType(freq) {
return (freq >= 1000 ? (freq / 1000).toFixed(1) : Math.round(freq));
}
unitType(freq) {
return (freq >= 1000 ? 'KHz' : 'Hz');
}
loadLabels(bgFill, fontSizeFreq, fontSizeUnit, fontType, textColorFreq, textColorUnit, textAlign, container) {
const frequenciesHeight = this.height;
bgFill = bgFill || 'rgba(68,68,68,0)';
fontSizeFreq = fontSizeFreq || '12px';
fontSizeUnit = fontSizeUnit || '10px';
fontType = fontType || 'Helvetica';
textColorFreq = textColorFreq || '#fff';
textColorUnit = textColorUnit || '#fff';
textAlign = textAlign || 'center';
container = container || '#specLabels';
const getMaxY = frequenciesHeight || 512;
const labelIndex = 5 * (getMaxY / 256);
const freqStart = 0;
const step = ((this.wavesurfer.backend.ac.sampleRate / 2) - freqStart) / labelIndex;
const ctx = this.labelsEl.getContext('2d');
this.labelsEl.height = this.height;
this.labelsEl.width = 55;
ctx.fillStyle = bgFill;
ctx.fillRect(0, 0, 55, getMaxY);
ctx.fill();
let i;
for (i = 0; i <= labelIndex; i++) {
ctx.textAlign = textAlign;
ctx.textBaseline = 'middle';
const freq = freqStart + (step * i);
const index = Math.round(freq / (this.sampleRate / 2) * this.fftSamples);
const label = this.freqType(freq);
const units = this.unitType(freq);
const x = 16;
const yLabelOffset = 2;
if (i == 0) {
ctx.fillStyle = textColorUnit;
ctx.font = fontSizeUnit + ' ' + fontType;
ctx.fillText(units, x + 24, getMaxY + i - 10);
ctx.fillStyle = textColorFreq;
ctx.font = fontSizeFreq + ' ' + fontType;
ctx.fillText(label, x, getMaxY + i - 10);
} else {
ctx.fillStyle = textColorUnit;
ctx.font = fontSizeUnit + ' ' + fontType;
ctx.fillText(units, x + 24, getMaxY - i * 50 + yLabelOffset);
ctx.fillStyle = textColorFreq;
ctx.font = fontSizeFreq + ' ' + fontType;
ctx.fillText(label, x, getMaxY - i * 50 + yLabelOffset);
}
}
}
updateScroll(e) {
if (this.wrapper) {
this.wrapper.scrollLeft = e.target.scrollLeft;
}
}
resample(oldMatrix) {
const columnsNumber = this.width;
const newMatrix = [];
const oldPiece = 1 / oldMatrix.length;
const newPiece = 1 / columnsNumber;
let i;
for (i = 0; i < columnsNumber; i++) {
const column = new Array(oldMatrix[0].length);
let j;
for (j = 0; j < oldMatrix.length; j++) {
const oldStart = j * oldPiece;
const oldEnd = oldStart + oldPiece;
const newStart = i * newPiece;
const newEnd = newStart + newPiece;
const overlap = (oldEnd <= newStart || newEnd <= oldStart) ?
0 :
Math.min(Math.max(oldEnd, newStart), Math.max(newEnd, oldStart)) -
Math.max(Math.min(oldEnd, newStart), Math.min(newEnd, oldStart));
let k;
/* eslint-disable max-depth */
if (overlap > 0) {
for (k = 0; k < oldMatrix[0].length; k++) {
if (column[k] == null) {
column[k] = 0;
}
column[k] += (overlap / newPiece) * oldMatrix[j][k];
}
}
/* eslint-enable max-depth */
}
const intColumn = new Uint8Array(oldMatrix[0].length);
let m;
for (m = 0; m < oldMatrix[0].length; m++) {
intColumn[m] = column[m];
}
newMatrix.push(intColumn);
}
return newMatrix;
}
}
+358
View File
@@ -0,0 +1,358 @@
/**
* @typedef {Object} TimelinePluginParams
* @desc Extends the `WavesurferParams` wavesurfer was initialised with
* @property {!string|HTMLElement} container CSS selector or HTML element where
* the timeline should be drawn. This is the only required parameter.
* @property {number} notchPercentHeight=90 Height of notches in percent
* @property {string} primaryColor='#000' The colour of the main notches
* @property {string} secondaryColor='#c0c0c0' The colour of the secondary
* notches
* @property {string} primaryFontColor='#000' The colour of the labels next to
* the main notches
* @property {string} secondaryFontColor='#000' The colour of the labels next to
* the secondary notches
* @property {string} fontFamily='Arial'
* @property {number} fontSize=10 Font size of labels in pixels
* @property {function} formatTimeCallback=→00:00
* @property {?boolean} deferInit Set to true to manually call
* `initPlugin('timeline')`
*/
/**
* Adds a timeline to the waveform.
*
* @implements {PluginClass}
* @extends {Observer}
* @example
* // es6
* import TimelinePlugin from 'wavesurfer.timeline.js';
*
* // commonjs
* var TimelinePlugin = require('wavesurfer.timeline.js');
*
* // if you are using <script> tags
* var TimelinePlugin = window.WaveSurfer.timeline;
*
* // ... initialising wavesurfer with the plugin
* var wavesurfer = WaveSurfer.create({
* // wavesurfer options ...
* plugins: [
* TimelinePlugin.create({
* // plugin options ...
* })
* ]
* });
*/
export default class TimelinePlugin {
/**
* Timeline plugin definition factory
*
* This function must be used to create a plugin definition which can be
* used by wavesurfer to correctly instantiate the plugin.
*
* @param {TimelinePluginParams} params parameters use to initialise the plugin
* @return {PluginDefinition} an object representing the plugin
*/
static create(params) {
return {
name: 'timeline',
deferInit: params && params.deferInit ? params.deferInit : false,
params: params,
instance: TimelinePlugin
};
}
constructor(params, ws) {
this.container = 'string' == typeof params.container
? document.querySelector(params.container)
: params.container;
if (!this.container) {
throw new Error('No container for wavesurfer timeline');
}
this.wavesurfer = ws;
this.util = ws.util;
this.params = this.util.extend({}, {
height: 20,
notchPercentHeight: 90,
primaryColor: '#000',
secondaryColor: '#c0c0c0',
primaryFontColor: '#000',
secondaryFontColor: '#000',
fontFamily: 'Arial',
fontSize: 10,
formatTimeCallback(seconds) {
if (seconds / 60 > 1) {
// calculate minutes and seconds from seconds count
const minutes = parseInt(seconds / 60, 10);
seconds = parseInt(seconds % 60, 10);
// fill up seconds with zeroes
seconds = (seconds < 10) ? '0' + seconds : seconds;
return `${minutes}:${seconds}`;
}
return Math.round(seconds * 1000) / 1000;
},
timeInterval(pxPerSec) {
if (pxPerSec >= 25) {
return 1;
} else if (pxPerSec * 5 >= 25) {
return 5;
} else if (pxPerSec * 15 >= 25) {
return 15;
}
return Math.ceil(0.5 / pxPerSec) * 60;
},
primaryLabelInterval(pxPerSec) {
if (pxPerSec >= 25) {
return 10;
} else if (pxPerSec * 5 >= 25) {
return 6;
} else if (pxPerSec * 15 >= 25) {
return 4;
}
return 4;
},
secondaryLabelInterval(pxPerSec) {
if (pxPerSec >= 25) {
return 5;
} else if (pxPerSec * 5 >= 25) {
return 2;
} else if (pxPerSec * 15 >= 25) {
return 2;
}
return 2;
}
}, params);
this.canvases = [];
this._onZoom = () => this.render();
this._onScroll = () => {
if (this.wrapper && this.drawer.wrapper) {
this.wrapper.scrollLeft = this.drawer.wrapper.scrollLeft;
}
};
this._onRedraw = () => this.render();
this._onReady = () => {
this.drawer = ws.drawer;
this.pixelRatio = ws.drawer.params.pixelRatio;
this.maxCanvasWidth = ws.drawer.maxCanvasWidth || ws.drawer.width;
this.maxCanvasElementWidth = ws.drawer.maxCanvasElementWidth || Math.round(this.maxCanvasWidth / this.pixelRatio);
this.createWrapper();
this.render();
ws.drawer.wrapper.addEventListener('scroll', this._onScroll);
ws.on('redraw', this._onRedraw);
ws.on('zoom', this._onZoom);
};
}
init() {
this.wavesurfer.on('ready', this._onReady);
// Check if ws is ready
if (this.wavesurfer.isReady) {
this._onReady();
}
}
destroy() {
this.unAll();
this.wavesurfer.un('redraw', this._onRedraw);
this.wavesurfer.un('zoom', this._onZoom);
this.wavesurfer.un('ready', this._onReady);
this.wavesurfer.drawer.wrapper.removeEventListener('scroll', this._onScroll);
if (this.wrapper && this.wrapper.parentNode) {
this.wrapper.parentNode.removeChild(this.wrapper);
this.wrapper = null;
}
}
createWrapper() {
const wsParams = this.wavesurfer.params;
this.wrapper = this.container.appendChild(
document.createElement('timeline')
);
this.util.style(this.wrapper, {
display: 'block',
position: 'relative',
userSelect: 'none',
webkitUserSelect: 'none',
height: `${this.params.height}px`
});
if (wsParams.fillParent || wsParams.scrollParent) {
this.util.style(this.wrapper, {
width: '100%',
overflowX: 'hidden',
overflowY: 'hidden'
});
}
this._onClick = e => {
e.preventDefault();
const relX = 'offsetX' in e ? e.offsetX : e.layerX;
this.fireEvent('click', (relX / this.wrapper.scrollWidth) || 0);
};
this.wrapper.addEventListener('click', this._onClick);
}
removeOldCanvases() {
while (this.canvases.length > 0) {
const canvas = this.canvases.pop();
canvas.parentElement.removeChild(canvas);
}
}
createCanvases() {
this.removeOldCanvases();
const totalWidth = Math.round(this.drawer.wrapper.scrollWidth);
const requiredCanvases = Math.ceil(totalWidth / this.maxCanvasElementWidth);
let i;
for (i = 0; i < requiredCanvases; i++) {
const canvas = this.wrapper.appendChild(document.createElement('canvas'));
this.canvases.push(canvas);
this.util.style(canvas, {
position: 'absolute',
zIndex: 4
});
}
}
render() {
this.createCanvases();
this.updateCanvasStyle();
this.drawTimeCanvases();
}
updateCanvasStyle() {
const requiredCanvases = this.canvases.length;
let i;
for (i = 0; i < requiredCanvases; i++) {
const canvas = this.canvases[i];
let canvasWidth = this.maxCanvasElementWidth;
if (i === requiredCanvases - 1) {
canvasWidth = this.drawer.wrapper.scrollWidth - (this.maxCanvasElementWidth * (requiredCanvases - 1));
}
canvas.width = canvasWidth * this.pixelRatio;
canvas.height = this.params.height * this.pixelRatio;
this.util.style(canvas, {
width: `${canvasWidth}px`,
height: `${this.params.height}px`,
left: `${i * this.maxCanvasElementWidth}px`
});
}
}
drawTimeCanvases() {
const backend = this.wavesurfer.backend;
const wsParams = this.wavesurfer.params;
const duration = this.wavesurfer.backend.getDuration();
const totalSeconds = parseInt(duration, 10) + 1;
const width = wsParams.fillParent && !wsParams.scrollParent
? this.drawer.getWidth()
: this.drawer.wrapper.scrollWidth * wsParams.pixelRatio;
const pixelsPerSecond = width / duration;
const formatTime = this.params.formatTimeCallback;
// if parameter is function, call the function with
// pixelsPerSecond, otherwise simply take the value as-is
const intervalFnOrVal = option => (typeof option === 'function' ? option(pixelsPerSecond) : option);
const timeInterval = intervalFnOrVal(this.params.timeInterval);
const primaryLabelInterval = intervalFnOrVal(this.params.primaryLabelInterval);
const secondaryLabelInterval = intervalFnOrVal(this.params.secondaryLabelInterval);
let curPixel = 0;
let curSeconds = 0;
if (duration <= 0) {
return;
}
const height1 = this.params.height - 4;
const height2 = (this.params.height * (this.params.notchPercentHeight / 100)) - 4;
const fontSize = this.params.fontSize * wsParams.pixelRatio;
let i;
for (i = 0; i < totalSeconds / timeInterval; i++) {
if (i % primaryLabelInterval == 0) {
this.setFillStyles(this.params.primaryColor);
this.fillRect(curPixel, 0, 1, height1);
this.setFonts(`${fontSize}px ${this.params.fontFamily}`);
this.setFillStyles(this.params.primaryFontColor);
this.fillText(formatTime(curSeconds), curPixel + 5, height1);
} else if (i % secondaryLabelInterval == 0) {
this.setFillStyles(this.params.secondaryColor);
this.fillRect(curPixel, 0, 1, height1);
this.setFonts(`${fontSize}px ${this.params.fontFamily}`);
this.setFillStyles(this.params.secondaryFontColor);
this.fillText(formatTime(curSeconds), curPixel + 5, height1);
} else {
this.setFillStyles(this.params.secondaryColor);
this.fillRect(curPixel, 0, 1, height2);
}
curSeconds += timeInterval;
curPixel += pixelsPerSecond * timeInterval;
}
}
setFillStyles(fillStyle) {
this.canvases.forEach(canvas => {
canvas.getContext('2d').fillStyle = fillStyle;
});
}
setFonts(font) {
this.canvases.forEach(canvas => {
canvas.getContext('2d').font = font;
});
}
fillRect(x, y, width, height) {
this.canvases.forEach((canvas, i) => {
const leftOffset = i * this.maxCanvasWidth;
const intersection = {
x1: Math.max(x, i * this.maxCanvasWidth),
y1: y,
x2: Math.min(x + width, i * this.maxCanvasWidth + canvas.width),
y2: y + height
};
if (intersection.x1 < intersection.x2) {
canvas.getContext('2d').fillRect(
intersection.x1 - leftOffset,
intersection.y1,
intersection.x2 - intersection.x1,
intersection.y2 - intersection.y1
);
}
});
}
fillText(text, x, y) {
let textWidth;
let xOffset = 0;
let i;
for (i in this.canvases) {
const context = this.canvases[i].getContext('2d');
const canvasWidth = context.canvas.width;
if (xOffset > x + textWidth) {
break;
}
if (xOffset + canvasWidth > x) {
textWidth = context.measureText(text).width;
context.fillText(text, x - xOffset, y);
}
xOffset += canvasWidth;
}
}
}
+37
View File
@@ -0,0 +1,37 @@
import Observer from './observer';
/**
* Perform an ajax request
*
* @param {Options} options Description
*
* @returns {Object} Observer instance
*/
export default function ajax (options) {
const instance = new Observer();
const xhr = new XMLHttpRequest();
let fired100 = false;
xhr.open(options.method || 'GET', options.url, true);
xhr.responseType = options.responseType || 'json';
xhr.addEventListener('progress', e => {
instance.fireEvent('progress', e);
if (e.lengthComputable && e.loaded == e.total) {
fired100 = true;
}
});
xhr.addEventListener('load', e => {
if (!fired100) {
instance.fireEvent('progress', e);
}
instance.fireEvent('load', e);
if (200 == xhr.status || 206 == xhr.status) {
instance.fireEvent('success', xhr.response, e);
} else {
instance.fireEvent('error', e);
}
});
xhr.addEventListener('error', e => instance.fireEvent('error', e));
xhr.send();
instance.xhr = xhr;
return instance;
}
+16
View File
@@ -0,0 +1,16 @@
/**
* Extend an object shallowly with others
*
* @param {Object} dest The target object
* @param {Object[]} sources The objects to use for extending
*
* @return {Object} Merged object
*/
export default function extend (dest, ...sources) {
sources.forEach(source => {
Object.keys(source).forEach(key => {
dest[key] = source[key];
});
});
return dest;
}
+13
View File
@@ -0,0 +1,13 @@
import reqAnimationFrame from './request-animation-frame';
/**
* Create a function which will be called at the next requestAnimationFrame
* cycle
*
* @param {function} func The function to call
*
* @return {func} The function wrapped within a requestAnimationFrame
*/
export default function frame (func) {
return (...args) => reqAnimationFrame(() => func(...args));
}
+8
View File
@@ -0,0 +1,8 @@
/**
* Get a random prefixed ID
*
* @returns {String} Random ID
*/
export default function getId () {
return 'wavesurfer_' + Math.random().toString(32).substring(2);
}
+10
View File
@@ -0,0 +1,10 @@
export { default as ajax } from './ajax';
export { default as getId } from './get-id';
export { default as max } from './max';
export { default as min } from './min';
export { default as Observer } from './observer';
export { default as extend } from './extend';
export { default as style } from './style';
export { default as requestAnimationFrame } from './request-animation-frame';
export { default as frame } from './frame';
export { default as debounce } from 'debounce';
+15
View File
@@ -0,0 +1,15 @@
/**
* Get the largest value
*
* @param {Array} values Array of numbers
* @returns {Number} Largest number found
*/
export default function max (values) {
let largest = -Infinity;
Object.keys(values).forEach(i => {
if (values[i] > largest) {
largest = values[i];
}
});
return largest;
}

Some files were not shown because too many files have changed in this diff Show More