mediaelement.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. import WebAudio from './webaudio';
  2. import * as util from './util';
  3. /**
  4. * MediaElement backend
  5. */
  6. export default class MediaElement extends WebAudio {
  7. /**
  8. * Construct the backend
  9. *
  10. * @param {WavesurferParams} params
  11. */
  12. constructor(params) {
  13. super(params);
  14. /** @private */
  15. this.params = params;
  16. // Dummy media to catch errors
  17. /** @private */
  18. this.media = {
  19. currentTime: 0,
  20. duration: 0,
  21. paused: true,
  22. playbackRate: 1,
  23. play() {},
  24. pause() {}
  25. };
  26. /** @private */
  27. this.mediaType = params.mediaType.toLowerCase();
  28. /** @private */
  29. this.elementPosition = params.elementPosition;
  30. /** @private */
  31. this.peaks = null;
  32. /** @private */
  33. this.playbackRate = 1;
  34. /** @private */
  35. this.buffer = null;
  36. /** @private */
  37. this.onPlayEnd = null;
  38. }
  39. /**
  40. * Initialise the backend, called in `wavesurfer.createBackend()`
  41. */
  42. init() {
  43. this.setPlaybackRate(this.params.audioRate);
  44. this.createTimer();
  45. }
  46. /**
  47. * Create a timer to provide a more precise `audioprocess` event.
  48. *
  49. * @private
  50. */
  51. createTimer() {
  52. const onAudioProcess = () => {
  53. if (this.isPaused()) { return; }
  54. this.fireEvent('audioprocess', this.getCurrentTime());
  55. // Call again in the next frame
  56. const requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame;
  57. requestAnimationFrame(onAudioProcess);
  58. };
  59. this.on('play', onAudioProcess);
  60. }
  61. /**
  62. * Create media element with url as its source,
  63. * and append to container element.
  64. *
  65. * @param {string} url Path to media file
  66. * @param {HTMLElement} container HTML element
  67. * @param {Array} peaks Array of peak data
  68. * @param {string} preload HTML 5 preload attribute value
  69. */
  70. load(url, container, peaks, preload) {
  71. const media = document.createElement(this.mediaType);
  72. media.controls = this.params.mediaControls;
  73. media.autoplay = this.params.autoplay || false;
  74. media.preload = preload == null ? 'auto' : preload;
  75. media.src = url;
  76. media.style.width = '100%';
  77. const prevMedia = container.querySelector(this.mediaType);
  78. if (prevMedia) {
  79. container.removeChild(prevMedia);
  80. }
  81. container.appendChild(media);
  82. this._load(media, peaks);
  83. }
  84. /**
  85. * Load existing media element.
  86. *
  87. * @param {MediaElement} elt HTML5 Audio or Video element
  88. * @param {Array} peaks Array of peak data
  89. */
  90. loadElt(elt, peaks) {
  91. elt.controls = this.params.mediaControls;
  92. elt.autoplay = this.params.autoplay || false;
  93. this._load(elt, peaks);
  94. }
  95. /**
  96. * Private method called by both load (from url)
  97. * and loadElt (existing media element).
  98. *
  99. * @param {MediaElement} media HTML5 Audio or Video element
  100. * @param {Array} peaks array of peak data
  101. * @private
  102. */
  103. _load(media, peaks) {
  104. // load must be called manually on iOS, otherwise peaks won't draw
  105. // until a user interaction triggers load --> 'ready' event
  106. if (typeof media.load == 'function') {
  107. media.load();
  108. }
  109. media.addEventListener('error', () => {
  110. this.fireEvent('error', 'Error loading media element');
  111. });
  112. media.addEventListener('canplay', () => {
  113. this.fireEvent('canplay');
  114. });
  115. media.addEventListener('ended', () => {
  116. this.fireEvent('finish');
  117. });
  118. // Listen to and relay play and pause events to enable
  119. // playback control from the external media element
  120. media.addEventListener('play', () => {
  121. this.fireEvent('play');
  122. });
  123. media.addEventListener('pause', () => {
  124. this.fireEvent('pause');
  125. });
  126. this.media = media;
  127. this.peaks = peaks;
  128. this.onPlayEnd = null;
  129. this.buffer = null;
  130. this.setPlaybackRate(this.playbackRate);
  131. }
  132. /**
  133. * Used by `wavesurfer.isPlaying()` and `wavesurfer.playPause()`
  134. *
  135. * @return {boolean}
  136. */
  137. isPaused() {
  138. return !this.media || this.media.paused;
  139. }
  140. /**
  141. * Used by `wavesurfer.getDuration()`
  142. *
  143. * @return {number}
  144. */
  145. getDuration() {
  146. let duration = (this.buffer || this.media).duration;
  147. if (duration >= Infinity) { // streaming audio
  148. duration = this.media.seekable.end(0);
  149. }
  150. return duration;
  151. }
  152. /**
  153. * Returns the current time in seconds relative to the audioclip's
  154. * duration.
  155. *
  156. * @return {number}
  157. */
  158. getCurrentTime() {
  159. return this.media && this.media.currentTime;
  160. }
  161. /**
  162. * Get the position from 0 to 1
  163. *
  164. * @return {number}
  165. */
  166. getPlayedPercents() {
  167. return (this.getCurrentTime() / this.getDuration()) || 0;
  168. }
  169. /**
  170. * Get the audio source playback rate.
  171. *
  172. * @return {number}
  173. */
  174. getPlaybackRate() {
  175. return this.playbackRate || this.media.playbackRate;
  176. }
  177. /**
  178. * Set the audio source playback rate.
  179. *
  180. * @param {number} value
  181. */
  182. setPlaybackRate(value) {
  183. this.playbackRate = value || 1;
  184. this.media.playbackRate = this.playbackRate;
  185. }
  186. /**
  187. * Used by `wavesurfer.seekTo()`
  188. *
  189. * @param {number} start Position to start at in seconds
  190. */
  191. seekTo(start) {
  192. if (start != null) {
  193. this.media.currentTime = start;
  194. }
  195. this.clearPlayEnd();
  196. }
  197. /**
  198. * Plays the loaded audio region.
  199. *
  200. * @param {Number} start Start offset in seconds, relative to the beginning
  201. * of a clip.
  202. * @param {Number} end When to stop relative to the beginning of a clip.
  203. * @emits MediaElement#play
  204. */
  205. play(start, end) {
  206. this.seekTo(start);
  207. this.media.play();
  208. end && this.setPlayEnd(end);
  209. }
  210. /**
  211. * Pauses the loaded audio.
  212. *
  213. * @emits MediaElement#pause
  214. */
  215. pause() {
  216. this.media && this.media.pause();
  217. this.clearPlayEnd();
  218. }
  219. /** @private */
  220. setPlayEnd(end) {
  221. this._onPlayEnd = time => {
  222. if (time >= end) {
  223. this.pause();
  224. this.seekTo(end);
  225. }
  226. };
  227. this.on('audioprocess', this._onPlayEnd);
  228. }
  229. /** @private */
  230. clearPlayEnd() {
  231. if (this._onPlayEnd) {
  232. this.un('audioprocess', this._onPlayEnd);
  233. this._onPlayEnd = null;
  234. }
  235. }
  236. /**
  237. * Compute the max and min value of the waveform when broken into
  238. * <length> subranges.
  239. *
  240. * @param {number} length How many subranges to break the waveform into.
  241. * @param {number} first First sample in the required range.
  242. * @param {number} last Last sample in the required range.
  243. * @return {number[]|number[][]} Array of 2*<length> peaks or array of
  244. * arrays of peaks consisting of (max, min) values for each subrange.
  245. */
  246. getPeaks(length, first, last) {
  247. if (this.buffer) {
  248. return super.getPeaks(length, first, last);
  249. }
  250. return this.peaks || [];
  251. }
  252. /**
  253. * Get the current volume
  254. *
  255. * @return {number} value A floating point value between 0 and 1.
  256. */
  257. getVolume() {
  258. return this.media.volume;
  259. }
  260. /**
  261. * Set the audio volume
  262. *
  263. * @param {number} value A floating point value between 0 and 1.
  264. */
  265. setVolume(value) {
  266. this.media.volume = value;
  267. }
  268. /**
  269. * This is called when wavesurfer is destroyed
  270. *
  271. */
  272. destroy() {
  273. this.pause();
  274. this.unAll();
  275. this.media && this.media.parentNode && this.media.parentNode.removeChild(this.media);
  276. this.media = null;
  277. }
  278. }