- Created complete documentation in docs/ directory - Added PROJECT_OVERVIEW.md with feature highlights and getting started guide - Added ARCHITECTURE.md with system design and technical details - Added SECURITY.md with comprehensive security implementation guide - Added DEVELOPMENT.md with development workflows and best practices - Added DEPLOYMENT.md with production deployment instructions - Added API.md with complete REST API documentation - Added CONTRIBUTING.md with contribution guidelines - Added CHANGELOG.md with version history and migration notes - Reorganized all documentation files into docs/ directory for better organization - Updated README.md with proper documentation links and quick navigation - Enhanced project structure with professional documentation standards
828 lines
25 KiB
JavaScript
828 lines
25 KiB
JavaScript
(function (root, factory)
|
|
{
|
|
if (typeof define === 'function' && define.amd)
|
|
{
|
|
// AMD. Register as an anonymous module.
|
|
define(['videojs', 'wavesurfer'], factory);
|
|
}
|
|
else if (typeof module === 'object' && module.exports)
|
|
{
|
|
// Node. Does not work with strict CommonJS, but
|
|
// only CommonJS-like environments that support module.exports,
|
|
// like Node.
|
|
module.exports = factory(require('video.js'), require('wavesurfer.js'));
|
|
}
|
|
else
|
|
{
|
|
// Browser globals (root is window)
|
|
root.returnExports = factory(root.videojs, root.WaveSurfer);
|
|
}
|
|
}(this, function (videojs, WaveSurfer)
|
|
{
|
|
var ERROR = 'error';
|
|
var WARN = 'warn';
|
|
var VjsComponent = videojs.getComponent('Component');
|
|
|
|
/**
|
|
* Draw a waveform for audio and video files in a video.js player.
|
|
* @class
|
|
* @augments videojs.Component
|
|
*/
|
|
videojs.Waveform = videojs.extend(VjsComponent,
|
|
{
|
|
/**
|
|
* The constructor function for the class.
|
|
*
|
|
* @param {(videojs.Player|Object)} player - Video.js player instance.
|
|
* @param {Object} options - Player options.
|
|
*/
|
|
constructor: function(player, options)
|
|
{
|
|
// run base component initializing with new options
|
|
VjsComponent.call(this, player, options);
|
|
|
|
// parse options
|
|
this.waveReady = false;
|
|
this.waveFinished = false;
|
|
this.liveMode = false;
|
|
this.debug = (this.options_.options.debug.toString() === 'true');
|
|
this.msDisplayMax = parseFloat(this.options_.options.msDisplayMax);
|
|
|
|
if (this.options_.options.src === 'live')
|
|
{
|
|
// check if the wavesurfer.js microphone plugin can be enabled
|
|
try
|
|
{
|
|
this.microphone = Object.create(WaveSurfer.Microphone);
|
|
|
|
// listen for events
|
|
this.microphone.on('deviceError', this.onWaveError.bind(this));
|
|
|
|
// enable audio input from a microphone
|
|
this.liveMode = true;
|
|
this.waveReady = true;
|
|
|
|
this.log('wavesurfer.js microphone plugin enabled.');
|
|
}
|
|
catch (TypeError)
|
|
{
|
|
this.onWaveError('Could not find wavesurfer.js ' +
|
|
'microphone plugin!');
|
|
}
|
|
}
|
|
|
|
// wait until player ui is ready
|
|
this.player().one('ready', this.setupUI.bind(this));
|
|
},
|
|
|
|
/**
|
|
* Player UI is ready.
|
|
* @private
|
|
*/
|
|
setupUI: function()
|
|
{
|
|
// customize controls
|
|
this.player().bigPlayButton.hide();
|
|
|
|
// the native controls don't work for this UI so disable
|
|
// them no matter what
|
|
if (this.player().usingNativeControls_ === true)
|
|
{
|
|
if (this.player().tech_.el_ !== undefined)
|
|
{
|
|
this.player().tech_.el_.controls = false;
|
|
}
|
|
}
|
|
|
|
// since video.js 5.11.0 the player won't start if no src is
|
|
// set (see PR 3378), so fake it
|
|
this.player().currentSrc = function()
|
|
{
|
|
return 'videojs-wavesurfer';
|
|
};
|
|
|
|
if (this.player().options_.controls)
|
|
{
|
|
// make sure controlBar is showing
|
|
this.player().controlBar.show();
|
|
this.player().controlBar.el().style.display = 'flex';
|
|
|
|
// progress control isn't used by this plugin
|
|
this.player().controlBar.progressControl.hide();
|
|
|
|
// prevent controlbar fadeout
|
|
this.player().on('userinactive', function(event)
|
|
{
|
|
this.player().userActive(true);
|
|
});
|
|
|
|
// make sure time display is visible
|
|
var uiElements = [this.player().controlBar.currentTimeDisplay,
|
|
this.player().controlBar.timeDivider,
|
|
this.player().controlBar.durationDisplay];
|
|
for (var element in uiElements)
|
|
{
|
|
// ignore when elements have been disabled by user
|
|
if (uiElements[element] !== undefined)
|
|
{
|
|
uiElements[element].el().style.display = 'block';
|
|
uiElements[element].show();
|
|
}
|
|
}
|
|
if (this.player().controlBar.remainingTimeDisplay !== undefined)
|
|
{
|
|
this.player().controlBar.remainingTimeDisplay.hide();
|
|
}
|
|
if (this.player().controlBar.timeDivider !== undefined)
|
|
{
|
|
this.player().controlBar.timeDivider.el().style.textAlign = 'center';
|
|
this.player().controlBar.timeDivider.el().style.width = '2em';
|
|
}
|
|
|
|
// disable play button until waveform is ready
|
|
// (except when in live mode)
|
|
if (!this.liveMode)
|
|
{
|
|
this.player().controlBar.playToggle.hide();
|
|
}
|
|
}
|
|
|
|
// waveform events
|
|
this.surfer = Object.create(WaveSurfer);
|
|
this.surfer.on('error', this.onWaveError.bind(this));
|
|
this.surfer.on('finish', this.onWaveFinish.bind(this));
|
|
|
|
this.surferReady = this.onWaveReady.bind(this);
|
|
this.surferProgress = this.onWaveProgress.bind(this);
|
|
this.surferSeek = this.onWaveSeek.bind(this);
|
|
|
|
// only listen to these events when we're not in live mode
|
|
if (!this.liveMode)
|
|
{
|
|
this.setupPlaybackEvents(true);
|
|
}
|
|
|
|
// player events
|
|
this.player().on('play', this.onPlay.bind(this));
|
|
this.player().on('pause', this.onPause.bind(this));
|
|
this.player().on('volumechange', this.onVolumeChange.bind(this));
|
|
this.player().on('fullscreenchange', this.onScreenChange.bind(this));
|
|
|
|
// kick things off
|
|
this.startPlayers();
|
|
},
|
|
|
|
/**
|
|
* Starts or stops listening to events related to audio-playback.
|
|
*
|
|
* @param {boolean} enable - Start or stop listening to playback
|
|
* related events.
|
|
* @private
|
|
*/
|
|
setupPlaybackEvents: function(enable)
|
|
{
|
|
if (enable === false)
|
|
{
|
|
this.surfer.un('ready', this.surferReady);
|
|
this.surfer.un('audioprocess', this.surferProgress);
|
|
this.surfer.un('seek', this.surferSeek);
|
|
}
|
|
else if (enable === true)
|
|
{
|
|
this.surfer.on('ready', this.surferReady);
|
|
this.surfer.on('audioprocess', this.surferProgress);
|
|
this.surfer.on('seek', this.surferSeek);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Start the players.
|
|
* @private
|
|
*/
|
|
startPlayers: function()
|
|
{
|
|
var options = this.options_.options;
|
|
|
|
// init waveform
|
|
this.initialize(options);
|
|
|
|
if (options.src !== undefined)
|
|
{
|
|
if (this.microphone === undefined)
|
|
{
|
|
// show loading spinner
|
|
this.player().loadingSpinner.show();
|
|
|
|
// start loading file
|
|
this.load(options.src);
|
|
}
|
|
else
|
|
{
|
|
// hide loading spinner
|
|
this.player().loadingSpinner.hide();
|
|
|
|
// connect microphone input to our waveform
|
|
options.wavesurfer = this.surfer;
|
|
this.microphone.init(options);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// no valid src found, hide loading spinner
|
|
this.player().loadingSpinner.hide();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Initializes the waveform.
|
|
*
|
|
* @param {Object} opts - Plugin options.
|
|
* @private
|
|
*/
|
|
initialize: function(opts)
|
|
{
|
|
this.originalHeight = this.player().options_.height;
|
|
var controlBarHeight = this.player().controlBar.height();
|
|
if (this.player().options_.controls === true && controlBarHeight === 0)
|
|
{
|
|
// the dimensions of the controlbar are not known yet, but we
|
|
// need it now, so we can calculate the height of the waveform.
|
|
// The default height is 30px, so use that instead.
|
|
controlBarHeight = 30;
|
|
}
|
|
|
|
// set waveform element and dimensions
|
|
// Set the container to player's container if "container" option is
|
|
// not provided. If a waveform needs to be appended to your custom
|
|
// element, then use below option. For example:
|
|
// container: document.querySelector("#vjs-waveform")
|
|
if (opts.container === undefined)
|
|
{
|
|
opts.container = this.el();
|
|
}
|
|
|
|
// set the height of generated waveform if user has provided height
|
|
// from options. If height of waveform need to be customized then use
|
|
// option below. For example: waveformHeight: 30
|
|
if (opts.waveformHeight === undefined)
|
|
{
|
|
opts.height = this.player().height() - controlBarHeight;
|
|
}
|
|
else
|
|
{
|
|
opts.height = opts.waveformHeight;
|
|
}
|
|
|
|
// split channels
|
|
if (opts.splitChannels && opts.splitChannels === true)
|
|
{
|
|
opts.height /= 2;
|
|
}
|
|
|
|
// customize waveform appearance
|
|
this.surfer.init(opts);
|
|
},
|
|
|
|
/**
|
|
* Start loading waveform data.
|
|
*
|
|
* @param {string|blob|file} url - Either the URL of the audio file,
|
|
* a Blob or a File object.
|
|
*/
|
|
load: function(url)
|
|
{
|
|
if (url instanceof Blob || url instanceof File)
|
|
{
|
|
this.log('Loading object: ' + url);
|
|
this.surfer.loadBlob(url);
|
|
}
|
|
else
|
|
{
|
|
this.log('Loading URL: ' + url);
|
|
this.surfer.load(url);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Start/resume playback or microphone.
|
|
*/
|
|
play: function()
|
|
{
|
|
if (this.liveMode)
|
|
{
|
|
// start/resume microphone visualization
|
|
if (!this.microphone.active)
|
|
{
|
|
this.log('Start microphone');
|
|
this.microphone.start();
|
|
}
|
|
else
|
|
{
|
|
this.log('Resume microphone');
|
|
this.microphone.play();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this.log('Start playback');
|
|
|
|
// put video.js player UI in playback mode
|
|
this.player().play();
|
|
|
|
// start surfer playback
|
|
this.surfer.play();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Pauses playback or microphone visualization.
|
|
*/
|
|
pause: function()
|
|
{
|
|
if (this.liveMode)
|
|
{
|
|
// pause microphone visualization
|
|
this.log('Pause microphone');
|
|
this.microphone.pause();
|
|
}
|
|
else
|
|
{
|
|
// pause playback
|
|
this.log('Pause playback');
|
|
if (!this.waveFinished)
|
|
{
|
|
this.surfer.pause();
|
|
}
|
|
else
|
|
{
|
|
this.waveFinished = false;
|
|
}
|
|
|
|
this.setCurrentTime();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Remove the player and waveform.
|
|
*/
|
|
destroy: function()
|
|
{
|
|
this.log('Destroying plugin');
|
|
|
|
if (this.liveMode && this.microphone)
|
|
{
|
|
// destroy microphone plugin
|
|
this.log('Destroying microphone');
|
|
this.microphone.destroy();
|
|
}
|
|
|
|
this.surfer.destroy();
|
|
this.player().dispose();
|
|
},
|
|
|
|
/**
|
|
* Set the current volume.
|
|
*
|
|
* @param {number} volume - The new volume level.
|
|
*/
|
|
setVolume: function(volume)
|
|
{
|
|
if (volume !== undefined)
|
|
{
|
|
this.log('Changing volume to: ' + volume);
|
|
this.surfer.setVolume(volume);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Save waveform image as data URI.
|
|
*
|
|
* The default format is 'image/png'. Other supported types are
|
|
* 'image/jpeg' and 'image/webp'.
|
|
*
|
|
* @param {string} [format=image/png] - String indicating the image format.
|
|
* @param {number} [quality=1] - Number between 0 and 1 indicating image
|
|
* quality if the requested type is 'image/jpeg' or 'image/webp'.
|
|
* @returns {string} The data URI of the image data.
|
|
*/
|
|
exportImage: function(format, quality)
|
|
{
|
|
return this.surfer.exportImage(format, quality);
|
|
},
|
|
|
|
/**
|
|
* Log message (if the debug option is enabled).
|
|
* @private
|
|
*/
|
|
log: function(args, logType)
|
|
{
|
|
if (this.debug === true)
|
|
{
|
|
if (logType === ERROR)
|
|
{
|
|
videojs.log.error(args);
|
|
}
|
|
else if (logType === WARN)
|
|
{
|
|
videojs.log.warn(args);
|
|
}
|
|
else
|
|
{
|
|
videojs.log(args);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Get the current time (in seconds) of the stream during playback.
|
|
*
|
|
* Returns 0 if no stream is available (yet).
|
|
*/
|
|
getCurrentTime: function()
|
|
{
|
|
var currentTime = this.surfer.getCurrentTime();
|
|
|
|
currentTime = isNaN(currentTime) ? 0 : currentTime;
|
|
|
|
return currentTime;
|
|
},
|
|
|
|
/**
|
|
* Updates the player's element displaying the current time.
|
|
*
|
|
* @param {number} [currentTime] - Current position of the playhead
|
|
* (in seconds).
|
|
* @param {number} [duration] - Duration of the waveform (in seconds).
|
|
* @private
|
|
*/
|
|
setCurrentTime: function(currentTime, duration)
|
|
{
|
|
if (currentTime === undefined)
|
|
{
|
|
currentTime = this.surfer.getCurrentTime();
|
|
}
|
|
|
|
if (duration === undefined)
|
|
{
|
|
duration = this.surfer.getDuration();
|
|
}
|
|
|
|
currentTime = isNaN(currentTime) ? 0 : currentTime;
|
|
duration = isNaN(duration) ? 0 : duration;
|
|
var time = Math.min(currentTime, duration);
|
|
|
|
// update control
|
|
this.player().controlBar.currentTimeDisplay.contentEl(
|
|
).innerHTML = this.formatTime(time, duration);
|
|
},
|
|
|
|
/**
|
|
* Get the duration of the stream in seconds.
|
|
*
|
|
* Returns 0 if no stream is available (yet).
|
|
*/
|
|
getDuration: function()
|
|
{
|
|
var duration = this.surfer.getDuration();
|
|
|
|
duration = isNaN(duration) ? 0 : duration;
|
|
|
|
return duration;
|
|
},
|
|
|
|
/**
|
|
* Updates the player's element displaying the duration time.
|
|
*
|
|
* @param {number} [duration] - Duration of the waveform (in seconds).
|
|
* @private
|
|
*/
|
|
setDuration: function(duration)
|
|
{
|
|
if (duration === undefined)
|
|
{
|
|
duration = this.surfer.getDuration();
|
|
}
|
|
|
|
duration = isNaN(duration) ? 0 : duration;
|
|
|
|
// update control
|
|
this.player().controlBar.durationDisplay.contentEl(
|
|
).innerHTML = this.formatTime(duration, duration);
|
|
},
|
|
|
|
/**
|
|
* Wave ready event.
|
|
*
|
|
* Fired when audio is loaded, decoded and the waveform is drawn.
|
|
*
|
|
* @event waveReady
|
|
*/
|
|
|
|
/**
|
|
* Audio is loaded, decoded and the waveform is drawn.
|
|
*
|
|
* @fires waveReady
|
|
* @private
|
|
*/
|
|
onWaveReady: function()
|
|
{
|
|
this.waveReady = true;
|
|
this.waveFinished = false;
|
|
this.liveMode = false;
|
|
|
|
this.log('Waveform is ready');
|
|
this.player().trigger('waveReady');
|
|
|
|
// update time display
|
|
this.setCurrentTime();
|
|
this.setDuration();
|
|
|
|
// enable and show play button
|
|
this.player().controlBar.playToggle.show();
|
|
|
|
// hide loading spinner
|
|
this.player().loadingSpinner.hide();
|
|
|
|
// auto-play when ready (if enabled)
|
|
if (this.player().options_.autoplay)
|
|
{
|
|
this.play();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Playback finish event.
|
|
*
|
|
* Fired when audio playback finished.
|
|
*
|
|
* @event playbackFinish
|
|
*/
|
|
|
|
/**
|
|
* Fires when audio playback completed.
|
|
* @private
|
|
*/
|
|
onWaveFinish: function()
|
|
{
|
|
this.log('Finished playback');
|
|
|
|
this.player().trigger('playbackFinish');
|
|
|
|
// check if player isn't paused already
|
|
if (!this.player().paused())
|
|
{
|
|
// check if loop is enabled
|
|
if (this.player().options_.loop)
|
|
{
|
|
// reset waveform
|
|
this.surfer.stop();
|
|
this.play();
|
|
}
|
|
else
|
|
{
|
|
// finished
|
|
this.waveFinished = true;
|
|
|
|
// pause player
|
|
this.player().pause();
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Fires continuously during audio playback.
|
|
*
|
|
* @param {number} time - Current time/location of the playhead.
|
|
* @private
|
|
*/
|
|
onWaveProgress: function(time)
|
|
{
|
|
this.setCurrentTime();
|
|
},
|
|
|
|
/**
|
|
* Fires during seeking of the waveform.
|
|
* @private
|
|
*/
|
|
onWaveSeek: function()
|
|
{
|
|
this.setCurrentTime();
|
|
},
|
|
|
|
/**
|
|
* Fired whenever the media in the player begins or resumes playback.
|
|
* @private
|
|
*/
|
|
onPlay: function()
|
|
{
|
|
// don't start playing until waveform's ready
|
|
if (this.waveReady)
|
|
{
|
|
this.play();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Fired whenever the media in the player has been paused.
|
|
* @private
|
|
*/
|
|
onPause: function()
|
|
{
|
|
this.pause();
|
|
},
|
|
|
|
/**
|
|
* Fired when the volume in the player changes.
|
|
* @private
|
|
*/
|
|
onVolumeChange: function()
|
|
{
|
|
var volume = this.player().volume();
|
|
if (this.player().muted())
|
|
{
|
|
// muted volume
|
|
volume = 0;
|
|
}
|
|
|
|
this.setVolume(volume);
|
|
},
|
|
|
|
/**
|
|
* Fired when the player switches in or out of fullscreen mode.
|
|
* @private
|
|
*/
|
|
onScreenChange: function()
|
|
{
|
|
var isFullscreen = this.player().isFullscreen();
|
|
var newHeight;
|
|
|
|
if (!isFullscreen)
|
|
{
|
|
// restore original height
|
|
newHeight = this.originalHeight;
|
|
}
|
|
else
|
|
{
|
|
// fullscreen height
|
|
newHeight = window.outerHeight;
|
|
}
|
|
|
|
if (this.waveReady)
|
|
{
|
|
if (this.liveMode && !this.microphone.active)
|
|
{
|
|
// we're in live mode but the microphone hasn't been
|
|
// started yet
|
|
return;
|
|
}
|
|
|
|
// destroy old drawing
|
|
this.surfer.drawer.destroy();
|
|
|
|
// set new height
|
|
this.surfer.params.height = newHeight - this.player().controlBar.height();
|
|
this.surfer.createDrawer();
|
|
|
|
// redraw
|
|
this.surfer.drawBuffer();
|
|
|
|
// make sure playhead is restored at right position
|
|
this.surfer.drawer.progress(this.surfer.backend.getPlayedPercents());
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Waveform error.
|
|
*
|
|
* @param {string} error - The wavesurfer error.
|
|
* @private
|
|
*/
|
|
onWaveError: function(error)
|
|
{
|
|
this.log(error, ERROR);
|
|
|
|
this.player().trigger('error', error);
|
|
},
|
|
|
|
/**
|
|
* Format seconds as a time string, H:MM:SS, M:SS or M:SS:MMM.
|
|
*
|
|
* Supplying a guide (in seconds) will force a number of leading zeros
|
|
* to cover the length of the guide.
|
|
*
|
|
* @param {number} seconds - Number of seconds to be turned into a
|
|
* string.
|
|
* @param {number} guide - Number (in seconds) to model the string
|
|
* after.
|
|
* @return {string} Time formatted as H:MM:SS, M:SS or M:SS:MMM, e.g.
|
|
* 0:00:12.
|
|
* @private
|
|
*/
|
|
formatTime: function(seconds, guide)
|
|
{
|
|
// Default to using seconds as guide
|
|
seconds = seconds < 0 ? 0 : seconds;
|
|
guide = guide || seconds;
|
|
var s = Math.floor(seconds % 60),
|
|
m = Math.floor(seconds / 60 % 60),
|
|
h = Math.floor(seconds / 3600),
|
|
gm = Math.floor(guide / 60 % 60),
|
|
gh = Math.floor(guide / 3600),
|
|
ms = Math.floor((seconds - s) * 1000);
|
|
|
|
// handle invalid times
|
|
if (isNaN(seconds) || seconds === Infinity)
|
|
{
|
|
// '-' is false for all relational operators (e.g. <, >=) so this
|
|
// setting will add the minimum number of fields specified by the
|
|
// guide
|
|
h = m = s = ms = '-';
|
|
}
|
|
|
|
// Check if we need to show milliseconds
|
|
if (guide > 0 && guide < this.msDisplayMax)
|
|
{
|
|
if (ms < 100)
|
|
{
|
|
if (ms < 10)
|
|
{
|
|
ms = '00' + ms;
|
|
}
|
|
else
|
|
{
|
|
ms = '0' + ms;
|
|
}
|
|
}
|
|
ms = ':' + ms;
|
|
}
|
|
else
|
|
{
|
|
ms = '';
|
|
}
|
|
|
|
// Check if we need to show hours
|
|
h = (h > 0 || gh > 0) ? h + ':' : '';
|
|
|
|
// If hours are showing, we may need to add a leading zero.
|
|
// Always show at least one digit of minutes.
|
|
m = (((h || gm >= 10) && m < 10) ? '0' + m : m) + ':';
|
|
|
|
// Check if leading zero is need for seconds
|
|
s = ((s < 10) ? '0' + s : s);
|
|
|
|
return h + m + s + ms;
|
|
}
|
|
|
|
});
|
|
|
|
var createWaveform = function()
|
|
{
|
|
var props = {
|
|
className: 'vjs-waveform',
|
|
tabIndex: 0
|
|
};
|
|
return VjsComponent.prototype.createEl('div', props);
|
|
};
|
|
|
|
// plugin defaults
|
|
var defaults = {
|
|
// Display console log messages.
|
|
debug: false,
|
|
// msDisplayMax indicates the number of seconds that is
|
|
// considered the boundary value for displaying milliseconds
|
|
// in the time controls. An audio clip with a total length of
|
|
// 2 seconds and a msDisplayMax of 3 will use the format
|
|
// M:SS:MMM. Clips longer than msDisplayMax will be displayed
|
|
// as M:SS or HH:MM:SS.
|
|
msDisplayMax: 3
|
|
};
|
|
|
|
/**
|
|
* Initialize the plugin.
|
|
*
|
|
* @param {Object} [options] - Configuration for the plugin.
|
|
* @private
|
|
*/
|
|
var wavesurferPlugin = function(options)
|
|
{
|
|
var settings = videojs.mergeOptions(defaults, options);
|
|
var player = this;
|
|
|
|
// create new waveform
|
|
player.waveform = new videojs.Waveform(player,
|
|
{
|
|
'el': createWaveform(),
|
|
'options': settings
|
|
});
|
|
|
|
// add waveform to dom
|
|
player.el().appendChild(player.waveform.el());
|
|
};
|
|
|
|
// register the plugin
|
|
videojs.plugin('wavesurfer', wavesurferPlugin);
|
|
|
|
// return a function to define the module export
|
|
return wavesurferPlugin;
|
|
}));
|