Home Reference Source

src/js/videojs.wavesurfer.js

  1. /**
  2. * @file videojs.wavesurfer.js
  3. *
  4. * The main file for the videojs-wavesurfer project.
  5. * MIT license: https://github.com/collab-project/videojs-wavesurfer/blob/master/LICENSE
  6. */
  7.  
  8. import Event from './event';
  9. import log from './utils/log';
  10. import formatTime from './utils/format-time';
  11. import pluginDefaultOptions from './defaults';
  12. import WavesurferMiddleware from './middleware';
  13. import window from 'global/window';
  14.  
  15. import videojs from 'video.js';
  16. import WaveSurfer from 'wavesurfer.js';
  17.  
  18. const Plugin = videojs.getPlugin('plugin');
  19.  
  20. const wavesurferPluginName = 'wavesurfer';
  21. const wavesurferClassName = 'vjs-wavedisplay';
  22. const wavesurferStyleName = 'vjs-wavesurfer';
  23.  
  24. // wavesurfer.js backends
  25. const WEBAUDIO = 'WebAudio';
  26. const MEDIAELEMENT = 'MediaElement';
  27. const MEDIAELEMENT_WEBAUDIO = 'MediaElementWebAudio';
  28.  
  29. /**
  30. * Draw a waveform for audio and video files in a video.js player.
  31. *
  32. * @class
  33. * @augments videojs.Plugin
  34. */
  35. class Wavesurfer extends Plugin {
  36. /**
  37. * The constructor function for the class.
  38. *
  39. * @param {(videojs.Player|Object)} player - video.js Player object.
  40. * @param {Object} options - Player options.
  41. */
  42. constructor(player, options) {
  43. super(player, options);
  44.  
  45. // add plugin style
  46. player.addClass(wavesurferStyleName);
  47.  
  48. // parse options
  49. if (videojs.obj !== undefined) {
  50. // video.js v8 and newer
  51. options = videojs.obj.merge(pluginDefaultOptions, options);
  52. } else {
  53. options = videojs.mergeOptions(pluginDefaultOptions, options);
  54. }
  55. this.waveReady = false;
  56. this.waveFinished = false;
  57. this.liveMode = false;
  58. this.backend = null;
  59. this.debug = (options.debug.toString() === 'true');
  60. this.textTracksEnabled = (this.player.options_.tracks.length > 0);
  61. this.displayMilliseconds = options.displayMilliseconds;
  62.  
  63. // use custom time format for video.js player
  64. if (options.formatTime && typeof options.formatTime === 'function') {
  65. // user-supplied formatTime
  66. this.setFormatTime(options.formatTime);
  67. } else {
  68. // plugin's default formatTime
  69. this.setFormatTime((seconds, guide) => {
  70. return formatTime(seconds, guide, this.displayMilliseconds);
  71. });
  72. }
  73.  
  74. // wait until player ui is ready
  75. this.player.one(Event.READY, this.initialize.bind(this));
  76. }
  77.  
  78. /**
  79. * Player UI is ready: customize controls.
  80. *
  81. * @private
  82. */
  83. initialize() {
  84. // hide big play button
  85. if (this.player.bigPlayButton !== undefined) {
  86. this.player.bigPlayButton.hide();
  87. }
  88.  
  89. // parse options
  90. let mergedOptions = this.parseOptions(this.player.options_.plugins.wavesurfer);
  91.  
  92. // controls
  93. if (this.player.options_.controls === true) {
  94. // make sure controlBar is showing.
  95. // video.js hides the controlbar by default because it expects
  96. // the user to click on the 'big play button' first.
  97. this.player.controlBar.show();
  98. this.player.controlBar.el_.style.display = 'flex';
  99.  
  100. // progress control is only supported with the MediaElement backend
  101. if (this.backend === WEBAUDIO &&
  102. this.player.controlBar.progressControl !== undefined) {
  103. this.player.controlBar.progressControl.hide();
  104. }
  105.  
  106. // disable Picture-In-Picture toggle introduced in video.js 7.6.0
  107. // until there is support for canvas in the Picture-In-Picture
  108. // browser API (see https://www.chromestatus.com/features/4844605453369344)
  109. if (this.player.controlBar.pictureInPictureToggle !== undefined) {
  110. this.player.controlBar.pictureInPictureToggle.hide();
  111. }
  112.  
  113. // make sure time displays are visible
  114. let uiElements = ['currentTimeDisplay', 'timeDivider', 'durationDisplay'];
  115. uiElements.forEach((element) => {
  116. // ignore and show when essential elements have been disabled
  117. // by user
  118. element = this.player.controlBar[element];
  119. if (element !== undefined) {
  120. element.el_.style.display = 'block';
  121. element.show();
  122. }
  123. });
  124. if (this.player.controlBar.remainingTimeDisplay !== undefined) {
  125. this.player.controlBar.remainingTimeDisplay.hide();
  126. }
  127.  
  128. if (this.backend === WEBAUDIO &&
  129. this.player.controlBar.playToggle !== undefined) {
  130. // handle play toggle interaction
  131. this.player.controlBar.playToggle.on(['tap', 'click'],
  132. this.onPlayToggle.bind(this));
  133.  
  134. // disable play button until waveform is ready
  135. this.player.controlBar.playToggle.hide();
  136. }
  137. }
  138.  
  139. // wavesurfer.js setup
  140. this.surfer = WaveSurfer.create(mergedOptions);
  141. this.surfer.on(Event.ERROR, this.onWaveError.bind(this));
  142. this.surfer.on(Event.FINISH, this.onWaveFinish.bind(this));
  143. this.backend = this.surfer.params.backend;
  144. this.log('Using wavesurfer.js ' + this.backend + ' backend.');
  145.  
  146. // check if the wavesurfer.js microphone plugin is enabled
  147. if ('microphone' in this.player.wavesurfer().surfer.getActivePlugins()) {
  148. // enable audio input from a microphone
  149. this.liveMode = true;
  150. this.waveReady = true;
  151. this.log('wavesurfer.js microphone plugin enabled.');
  152.  
  153. // in live mode, show play button at startup
  154. this.player.controlBar.playToggle.show();
  155.  
  156. // listen for wavesurfer.js microphone plugin events
  157. this.surfer.microphone.on(Event.DEVICE_ERROR,
  158. this.onWaveError.bind(this));
  159. }
  160.  
  161. // listen for wavesurfer.js events
  162. this.surferReady = this.onWaveReady.bind(this);
  163. if (this.backend === WEBAUDIO) {
  164. this.surferProgress = this.onWaveProgress.bind(this);
  165. this.surferSeek = this.onWaveSeek.bind(this);
  166.  
  167. // make sure volume is muted when requested
  168. if (this.player.muted()) {
  169. this.setVolume(0);
  170. }
  171. }
  172.  
  173. // only listen to the wavesurfer.js playback events when not
  174. // in live mode
  175. if (!this.liveMode) {
  176. this.setupPlaybackEvents(true);
  177. }
  178.  
  179. // video.js player events
  180. this.player.on(Event.VOLUMECHANGE, this.onVolumeChange.bind(this));
  181. this.player.on(Event.FULLSCREENCHANGE, this.onScreenChange.bind(this));
  182.  
  183. // video.js fluid option
  184. if (this.player.options_.fluid === true) {
  185. // give wave element a classname so it can be styled
  186. this.surfer.drawer.wrapper.className = wavesurferClassName;
  187. }
  188. }
  189.  
  190. /**
  191. * Initializes the waveform options.
  192. *
  193. * @private
  194. * @param {Object} surferOpts - Plugin options.
  195. * @returns {Object} - Updated `surferOpts` object.
  196. */
  197. parseOptions(surferOpts = {}) {
  198. let rect = this.player.el_.getBoundingClientRect();
  199. this.originalWidth = this.player.options_.width || rect.width;
  200. this.originalHeight = this.player.options_.height || rect.height;
  201.  
  202. // controlbar
  203. let controlBarHeight = this.player.controlBar.height();
  204. if (this.player.options_.controls === true && controlBarHeight === 0) {
  205. // the dimensions of the controlbar are not known yet, but we
  206. // need it now, so we can calculate the height of the waveform.
  207. // The default height is 30px, so use that instead.
  208. controlBarHeight = 30;
  209. }
  210.  
  211. // set waveform element and dimensions
  212. // Set the container to player's container if "container" option is
  213. // not provided. If a waveform needs to be appended to your custom
  214. // element, then use below option. For example:
  215. // container: document.querySelector("#vjs-waveform")
  216. if (surferOpts.container === undefined) {
  217. surferOpts.container = this.player.el_;
  218. }
  219.  
  220. // set the height of generated waveform if user has provided height
  221. // from options. If height of waveform need to be customized then use
  222. // option below. For example: waveformHeight: 30
  223. if (surferOpts.waveformHeight === undefined) {
  224. let playerHeight = rect.height;
  225. surferOpts.height = playerHeight - controlBarHeight;
  226. } else {
  227. surferOpts.height = surferOpts.waveformHeight;
  228. }
  229.  
  230. // split channels
  231. if (surferOpts.splitChannels && surferOpts.splitChannels === true) {
  232. surferOpts.height /= 2;
  233. }
  234.  
  235. // use MediaElement as default wavesurfer.js backend if one is not
  236. // specified
  237. if ('backend' in surferOpts) {
  238. this.backend = surferOpts.backend;
  239. } else {
  240. surferOpts.backend = this.backend = MEDIAELEMENT;
  241. }
  242.  
  243. return surferOpts;
  244. }
  245.  
  246. /**
  247. * Starts or stops listening to events related to audio-playback.
  248. *
  249. * @param {boolean} enable - Start or stop listening to playback
  250. * related events.
  251. * @private
  252. */
  253. setupPlaybackEvents(enable) {
  254. if (enable === false) {
  255. this.surfer.un(Event.READY, this.surferReady);
  256. if (this.backend === WEBAUDIO) {
  257. this.surfer.un(Event.AUDIOPROCESS, this.surferProgress);
  258. this.surfer.un(Event.SEEK, this.surferSeek);
  259. }
  260. } else if (enable === true) {
  261. this.surfer.on(Event.READY, this.surferReady);
  262. if (this.backend === WEBAUDIO) {
  263. this.surfer.on(Event.AUDIOPROCESS, this.surferProgress);
  264. this.surfer.on(Event.SEEK, this.surferSeek);
  265. }
  266. }
  267. }
  268.  
  269. /**
  270. * Start loading waveform data.
  271. *
  272. * @param {string|blob|file} url - Either the URL of the audio file,
  273. * a Blob or a File object.
  274. * @param {string|number[]} peaks - Either the URL of peaks
  275. * data for the audio file, or an array with peaks data.
  276. */
  277. load(url, peaks) {
  278. if (url instanceof Blob || url instanceof File) {
  279. this.log('Loading object: ' + JSON.stringify(url));
  280. this.surfer.loadBlob(url);
  281. } else {
  282. // load peak data from array or file
  283. if (peaks !== undefined) {
  284. this.loadPeaks(url, peaks);
  285. } else {
  286. // no peaks
  287. if (typeof url === 'string') {
  288. this.log('Loading URL: ' + url);
  289. } else {
  290. this.log('Loading element: ' + url);
  291. }
  292. this.surfer.load(url);
  293. }
  294. }
  295. }
  296.  
  297. /**
  298. * Start loading waveform data.
  299. *
  300. * @param {string|blob|file} url - Either the URL of the audio file,
  301. * a Blob or a File object.
  302. * @param {string|number[]} peaks - Either the URL of peaks
  303. * data for the audio file, or an array with peaks data.
  304. */
  305. loadPeaks(url, peaks) {
  306. if (Array.isArray(peaks)) {
  307. // use supplied peaks data
  308. this.log('Loading URL with array of peaks: ' + url);
  309. this.surfer.load(url, peaks);
  310. } else {
  311. // load peak data from file
  312. let requestOptions = {
  313. url: peaks,
  314. responseType: 'json'
  315. };
  316.  
  317. // supply xhr options, if any
  318. if (this.player.options_.plugins.wavesurfer.xhr !== undefined) {
  319. requestOptions.xhr = this.player.options_.plugins.wavesurfer.xhr;
  320. }
  321. let request = WaveSurfer.util.fetchFile(requestOptions);
  322.  
  323. request.once('success', data => {
  324. this.log('Loaded Peak Data URL: ' + peaks);
  325. // check for data property containing peaks
  326. if (data && data.data) {
  327. this.surfer.load(url, data.data);
  328. } else {
  329. this.player.trigger(Event.ERROR,
  330. 'Could not load peaks data from ' + peaks);
  331. this.log(err, 'error');
  332. }
  333. });
  334. request.once('error', e => {
  335. this.player.trigger(Event.ERROR,
  336. 'Unable to retrieve peak data from ' + peaks +
  337. '. Status code: ' + request.response.status);
  338. });
  339. }
  340. }
  341.  
  342. /**
  343. * Start/resume playback or microphone.
  344. */
  345. play() {
  346. // show pause button
  347. if (this.player.controlBar.playToggle !== undefined &&
  348. this.player.controlBar.playToggle.contentEl()) {
  349. this.player.controlBar.playToggle.handlePlay();
  350. }
  351.  
  352. if (this.liveMode) {
  353. // start/resume microphone visualization
  354. if (!this.surfer.microphone.active)
  355. {
  356. this.log('Start microphone');
  357. this.surfer.microphone.start();
  358. } else {
  359. // toggle paused
  360. let paused = !this.surfer.microphone.paused;
  361.  
  362. if (paused) {
  363. this.pause();
  364. } else {
  365. this.log('Resume microphone');
  366. this.surfer.microphone.play();
  367. }
  368. }
  369. } else {
  370. this.log('Start playback');
  371.  
  372. // put video.js player UI in playback mode
  373. this.player.play();
  374.  
  375. // start surfer playback
  376. this.surfer.play();
  377. }
  378. }
  379.  
  380. /**
  381. * Pauses playback or microphone visualization.
  382. */
  383. pause() {
  384. // show play button
  385. if (this.player.controlBar.playToggle !== undefined &&
  386. this.player.controlBar.playToggle.contentEl()) {
  387. this.player.controlBar.playToggle.handlePause();
  388. }
  389.  
  390. if (this.liveMode) {
  391. // pause microphone visualization
  392. this.log('Pause microphone');
  393. this.surfer.microphone.pause();
  394. } else {
  395. // pause playback
  396. this.log('Pause playback');
  397.  
  398. if (!this.waveFinished) {
  399. // pause wavesurfer playback
  400. this.surfer.pause();
  401. } else {
  402. this.waveFinished = false;
  403. }
  404.  
  405. this.setCurrentTime();
  406. }
  407. }
  408.  
  409. /**
  410. * @private
  411. */
  412. dispose() {
  413. if (this.surfer) {
  414. if (this.liveMode && this.surfer.microphone) {
  415. // destroy microphone plugin
  416. this.surfer.microphone.destroy();
  417. this.log('Destroyed microphone plugin');
  418. }
  419. // destroy wavesurfer instance
  420. this.surfer.destroy();
  421. }
  422. this.log('Destroyed plugin');
  423. }
  424.  
  425. /**
  426. * Indicates whether the plugin is destroyed or not.
  427. *
  428. * @return {boolean} Plugin destroyed or not.
  429. */
  430. isDestroyed() {
  431. return this.player && (this.player.children() === null);
  432. }
  433.  
  434. /**
  435. * Remove the player and waveform.
  436. */
  437. destroy() {
  438. this.player.dispose();
  439. }
  440.  
  441. /**
  442. * Set the volume level.
  443. *
  444. * @param {number} volume - The new volume level.
  445. */
  446. setVolume(volume) {
  447. if (volume !== undefined) {
  448. this.log('Changing volume to: ' + volume);
  449.  
  450. // update player volume
  451. this.player.volume(volume);
  452. }
  453. }
  454.  
  455. /**
  456. * Save waveform image as data URI.
  457. *
  458. * The default format is `'image/png'`. Other supported types are
  459. * `'image/jpeg'` and `'image/webp'`.
  460. *
  461. * @param {string} format='image/png' A string indicating the image format.
  462. * The default format type is `'image/png'`.
  463. * @param {number} quality=1 A number between 0 and 1 indicating the image
  464. * quality to use for image formats that use lossy compression such as
  465. * `'image/jpeg'`` and `'image/webp'`.
  466. * @param {string} type Image data type to return. Either 'blob' (default)
  467. * or 'dataURL'.
  468. * @return {string|string[]|Promise} When using `'dataURL'` `type` this returns
  469. * a single data URL or an array of data URLs, one for each canvas. The `'blob'`
  470. * `type` returns a `Promise` resolving with an array of `Blob` instances, one
  471. * for each canvas.
  472. */
  473. exportImage(format, quality, type = 'blob') {
  474. return this.surfer.exportImage(format, quality, type);
  475. }
  476.  
  477. /**
  478. * Change the audio output device.
  479. *
  480. * @param {string} deviceId - Id of audio output device.
  481. */
  482. setAudioOutput(deviceId) {
  483. if (deviceId) {
  484. this.surfer.setSinkId(deviceId).then((result) => {
  485. // notify listeners
  486. this.player.trigger(Event.AUDIO_OUTPUT_READY);
  487. }).catch((err) => {
  488. // notify listeners
  489. this.player.trigger(Event.ERROR, err);
  490.  
  491. this.log(err, 'error');
  492. });
  493. }
  494. }
  495.  
  496. /**
  497. * Get the current time (in seconds) of the stream during playback.
  498. *
  499. * Returns 0 if no stream is available (yet).
  500. *
  501. * @returns {float} Current time of the stream.
  502. */
  503. getCurrentTime() {
  504. let currentTime = this.surfer.getCurrentTime();
  505. currentTime = isNaN(currentTime) ? 0 : currentTime;
  506.  
  507. return currentTime;
  508. }
  509.  
  510. /**
  511. * Updates the player's element displaying the current time.
  512. *
  513. * @param {number} [currentTime] - Current position of the playhead
  514. * (in seconds).
  515. * @param {number} [duration] - Duration of the waveform (in seconds).
  516. * @private
  517. */
  518. setCurrentTime(currentTime, duration) {
  519. if (currentTime === undefined) {
  520. currentTime = this.surfer.getCurrentTime();
  521. }
  522.  
  523. if (duration === undefined) {
  524. duration = this.surfer.getDuration();
  525. }
  526.  
  527. currentTime = isNaN(currentTime) ? 0 : currentTime;
  528. duration = isNaN(duration) ? 0 : duration;
  529.  
  530. // update current time display component
  531. if (this.player.controlBar.currentTimeDisplay &&
  532. this.player.controlBar.currentTimeDisplay.contentEl() &&
  533. this.player.controlBar.currentTimeDisplay.contentEl().lastChild) {
  534. let time = Math.min(currentTime, duration);
  535.  
  536. this.player.controlBar.currentTimeDisplay.formattedTime_ =
  537. this.player.controlBar.currentTimeDisplay.contentEl().lastChild.textContent =
  538. this._formatTime(time, duration, this.displayMilliseconds);
  539. }
  540.  
  541. if (this.textTracksEnabled && this.player.tech_ && this.player.tech_.el_) {
  542. // only needed for text tracks
  543. this.player.tech_.setCurrentTime(currentTime);
  544. }
  545. }
  546.  
  547. /**
  548. * Get the duration of the stream in seconds.
  549. *
  550. * Returns 0 if no stream is available (yet).
  551. *
  552. * @returns {float} Duration of the stream.
  553. */
  554. getDuration() {
  555. let duration = this.surfer.getDuration();
  556. duration = isNaN(duration) ? 0 : duration;
  557.  
  558. return duration;
  559. }
  560.  
  561. /**
  562. * Updates the player's element displaying the duration time.
  563. *
  564. * @param {number} [duration] - Duration of the waveform (in seconds).
  565. * @private
  566. */
  567. setDuration(duration) {
  568. if (duration === undefined) {
  569. duration = this.surfer.getDuration();
  570. }
  571. duration = isNaN(duration) ? 0 : duration;
  572.  
  573. // update duration display component
  574. if (this.player.controlBar.durationDisplay &&
  575. this.player.controlBar.durationDisplay.contentEl() &&
  576. this.player.controlBar.durationDisplay.contentEl().lastChild) {
  577. this.player.controlBar.durationDisplay.formattedTime_ =
  578. this.player.controlBar.durationDisplay.contentEl().lastChild.textContent =
  579. this._formatTime(duration, duration, this.displayMilliseconds);
  580. }
  581. }
  582.  
  583. /**
  584. * Audio is loaded, decoded and the waveform is drawn.
  585. *
  586. * @fires waveReady
  587. * @private
  588. */
  589. onWaveReady() {
  590. this.waveReady = true;
  591. this.waveFinished = false;
  592. this.liveMode = false;
  593.  
  594. this.log('Waveform is ready');
  595. this.player.trigger(Event.WAVE_READY);
  596.  
  597. if (this.backend === WEBAUDIO) {
  598. // update time display
  599. this.setCurrentTime();
  600. this.setDuration();
  601.  
  602. // enable and show play button
  603. if (this.player.controlBar.playToggle !== undefined &&
  604. this.player.controlBar.playToggle.contentEl()) {
  605. this.player.controlBar.playToggle.show();
  606. }
  607. }
  608.  
  609. // hide loading spinner
  610. if (this.player.loadingSpinner.contentEl()) {
  611. this.player.loadingSpinner.hide();
  612. }
  613.  
  614. // auto-play when ready (if enabled)
  615. if (this.player.options_.autoplay === true) {
  616. // autoplay is only allowed when audio is muted
  617. this.setVolume(0);
  618.  
  619. // try auto-play
  620. if (this.backend === WEBAUDIO) {
  621. this.play();
  622. } else {
  623. this.player.play().catch(e => {
  624. this.onWaveError(e);
  625. });
  626. }
  627. }
  628. }
  629.  
  630. /**
  631. * Fires when audio playback completed.
  632. *
  633. * @fires playbackFinish
  634. * @private
  635. */
  636. onWaveFinish() {
  637. this.log('Finished playback');
  638.  
  639. // notify listeners
  640. this.player.trigger(Event.PLAYBACK_FINISH);
  641.  
  642. // check if loop is enabled
  643. if (this.player.options_.loop === true) {
  644. if (this.backend === WEBAUDIO) {
  645. // reset waveform
  646. this.surfer.stop();
  647. this.play();
  648. }
  649. } else {
  650. // finished
  651. this.waveFinished = true;
  652.  
  653. if (this.backend === WEBAUDIO) {
  654. // pause player
  655. this.pause();
  656.  
  657. // show the replay state of play toggle
  658. this.player.trigger(Event.ENDED);
  659.  
  660. // this gets called once after the clip has ended and the user
  661. // seeks so that we can change the replay button back to a play
  662. // button
  663. this.surfer.once(Event.SEEK, () => {
  664. if (this.player.controlBar.playToggle !== undefined) {
  665. this.player.controlBar.playToggle.removeClass('vjs-ended');
  666. }
  667. this.player.trigger(Event.PAUSE);
  668. });
  669. }
  670. }
  671. }
  672.  
  673. /**
  674. * Fires continuously during audio playback.
  675. *
  676. * @param {number} time - Current time/location of the playhead.
  677. * @private
  678. */
  679. onWaveProgress(time) {
  680. this.setCurrentTime();
  681. }
  682.  
  683. /**
  684. * Fires during seeking of the waveform.
  685. *
  686. * @private
  687. */
  688. onWaveSeek() {
  689. this.setCurrentTime();
  690. }
  691.  
  692. /**
  693. * Waveform error.
  694. *
  695. * @param {string} error - The wavesurfer error.
  696. * @private
  697. */
  698. onWaveError(error) {
  699. // notify listeners
  700. if (error.name && error.name === 'AbortError' ||
  701. error.name === 'DOMException' && error.message.startsWith('The operation was aborted'))
  702. {
  703. this.player.trigger(Event.ABORT, error);
  704. } else {
  705. this.player.trigger(Event.ERROR, error);
  706.  
  707. this.log(error, 'error');
  708. }
  709. }
  710.  
  711. /**
  712. * Fired when the play toggle is clicked.
  713. * @private
  714. */
  715. onPlayToggle() {
  716. if (this.player.controlBar.playToggle !== undefined &&
  717. this.player.controlBar.playToggle.hasClass('vjs-ended')) {
  718. this.player.controlBar.playToggle.removeClass('vjs-ended');
  719. }
  720. if (this.surfer.isPlaying()) {
  721. this.pause();
  722. } else {
  723. this.play();
  724. }
  725. }
  726.  
  727. /**
  728. * Fired when the volume in the video.js player changes.
  729. * @private
  730. */
  731. onVolumeChange() {
  732. let volume = this.player.volume();
  733. if (this.player.muted()) {
  734. // muted volume
  735. volume = 0;
  736. }
  737.  
  738. // update wavesurfer.js volume
  739. this.surfer.setVolume(volume);
  740. }
  741.  
  742. /**
  743. * Fired when the video.js player switches in or out of fullscreen mode.
  744. * @private
  745. */
  746. onScreenChange() {
  747. // execute with tiny delay so the player element completes
  748. // rendering and correct dimensions are reported
  749. let fullscreenDelay = this.player.setInterval(() => {
  750. let isFullscreen = this.player.isFullscreen();
  751. let newWidth, newHeight;
  752. if (!isFullscreen) {
  753. // restore original dimensions
  754. newWidth = this.originalWidth;
  755. newHeight = this.originalHeight;
  756. }
  757.  
  758. if (this.waveReady) {
  759. if (this.liveMode && !this.surfer.microphone.active) {
  760. // we're in live mode but the microphone hasn't been
  761. // started yet
  762. return;
  763. }
  764. // redraw
  765. this.redrawWaveform(newWidth, newHeight);
  766. }
  767.  
  768. // stop fullscreenDelay interval
  769. this.player.clearInterval(fullscreenDelay);
  770.  
  771. }, 100);
  772. }
  773.  
  774. /**
  775. * Redraw waveform.
  776. *
  777. * @param {number} [newWidth] - New width for the waveform.
  778. * @param {number} [newHeight] - New height for the waveform.
  779. * @private
  780. */
  781. redrawWaveform(newWidth, newHeight) {
  782. if (!this.isDestroyed()) {
  783. if (this.player.el_) {
  784. let rect = this.player.el_.getBoundingClientRect();
  785. if (newWidth === undefined) {
  786. // get player width
  787. newWidth = rect.width;
  788. }
  789. if (newHeight === undefined) {
  790. // get player height
  791. newHeight = rect.height;
  792. }
  793. }
  794.  
  795. // destroy old drawing
  796. this.surfer.drawer.destroy();
  797.  
  798. // set new dimensions
  799. this.surfer.params.width = newWidth;
  800. this.surfer.params.height = newHeight - this.player.controlBar.height();
  801.  
  802. // redraw waveform
  803. this.surfer.createDrawer();
  804. this.surfer.drawer.wrapper.className = wavesurferClassName;
  805. this.surfer.drawBuffer();
  806.  
  807. // make sure playhead is restored at right position
  808. this.surfer.drawer.progress(this.surfer.backend.getPlayedPercents());
  809. }
  810. }
  811.  
  812. /**
  813. * Log message to console (if the debug option is enabled).
  814. *
  815. * @private
  816. * @param {Array} args - The arguments to be passed to the matching console
  817. * method.
  818. * @param {string} logType - The name of the console method to use.
  819. */
  820. log(args, logType) {
  821. log(args, logType, this.debug);
  822. }
  823.  
  824. /**
  825. * Replaces the default `formatTime` implementation with a custom implementation.
  826. *
  827. * @param {function} customImplementation - A function which will be used in place
  828. * of the default `formatTime` implementation. Will receive the current time
  829. * in seconds and the guide (in seconds) as arguments.
  830. */
  831. setFormatTime(customImplementation) {
  832. this._formatTime = customImplementation;
  833.  
  834. if (videojs.time) {
  835. // video.js v8 and newer
  836. videojs.time.setFormatTime(this._formatTime);
  837. } else {
  838. videojs.setFormatTime(this._formatTime);
  839. }
  840. }
  841. }
  842.  
  843. // version nr is injected during build
  844. Wavesurfer.VERSION = __VERSION__;
  845.  
  846. // register plugin once
  847. videojs.Wavesurfer = Wavesurfer;
  848. if (videojs.getPlugin(wavesurferPluginName) === undefined) {
  849. videojs.registerPlugin(wavesurferPluginName, Wavesurfer);
  850. }
  851.  
  852. // register a star-middleware
  853. videojs.use('*', player => {
  854. // make player available on middleware
  855. WavesurferMiddleware.player = player;
  856.  
  857. return WavesurferMiddleware;
  858. });
  859.  
  860. export {Wavesurfer};