html-init.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. import loadScript from 'load-script';
  2. /**
  3. * @typedef {Object} InitParams
  4. * @property {WavesurferParams} [defaults={backend: 'MediaElement,
  5. * mediaControls: true}] The default wavesurfer initialisation parameters
  6. * @property {string|NodeList} containers='wavesurfer' Selector or NodeList of
  7. * elements to attach instances to
  8. * @property {string}
  9. * pluginCdnTemplate='//localhost:8080/dist/plugin/wavesurfer.[name].js' URL
  10. * template for the dynamic loading of plugins
  11. * @property {function} loadPlugin If set overwrites the default ajax function,
  12. * can be used to inject plugins differently.
  13. */
  14. /**
  15. * The HTML initialisation API is not part of the main library bundle file and
  16. * must be additionally included.
  17. *
  18. * The API attaches wavesurfer instances to all `<wavesurfer>` (can be
  19. * customised), parsing their `data-` attributes to construct an options object
  20. * for initialisation. Among other things it can dynamically load plugin code.
  21. *
  22. * The automatic initialisation can be prevented by setting the
  23. * `window.WS_StopAutoInit` flag to true. The `html-init[.min].js` file exports
  24. * the `Init` class, which can be called manually.
  25. *
  26. * Site-wide defaults can be added by setting `window.WS_InitOptions`.
  27. *
  28. * @example
  29. * <!-- with minimap and timeline plugin -->
  30. * <wavesurfer
  31. * data-url="../media/demo.wav"
  32. * data-plugins="minimap,timeline"
  33. * data-minimap-height="30"
  34. * data-minimap-wave-color="#ddd"
  35. * data-minimap-progress-color="#999"
  36. * data-timeline-font-size="13px"
  37. * data-timeline-container="#timeline"
  38. * >
  39. * </wavesurfer>
  40. * <div id="timeline"></div>
  41. *
  42. * <!-- with regions plugin -->
  43. * <wavesurfer
  44. * data-url="../media/demo.wav"
  45. * data-plugins="regions"
  46. * data-regions-regions='[{"start": 1,"end": 3,"color": "hsla(400, 100%, 30%, 0.5)"}]'
  47. * >
  48. * </wavesurfer>
  49. */
  50. class Init {
  51. /**
  52. * Instantiate Init class and initialise elements
  53. *
  54. * This is done automatically if `window` is defined and
  55. * `window.WS_StopAutoInit` is not set to true
  56. *
  57. * @param {WaveSurfer} WaveSurfer The WaveSurfer library object
  58. * @param {InitParams} params initialisation options
  59. */
  60. constructor(WaveSurfer, params = {}) {
  61. if (!WaveSurfer) {
  62. throw new Error('WaveSurfer is not available!');
  63. }
  64. /**
  65. * cache WaveSurfer
  66. * @private
  67. */
  68. this.WaveSurfer = WaveSurfer;
  69. /**
  70. * build parameters, cache them in _params so minified builds are smaller
  71. * @private
  72. */
  73. const _params = this.params = WaveSurfer.util.extend({}, {
  74. // wavesurfer parameter defaults so by default the audio player is
  75. // usable with native media element controls
  76. defaults: {
  77. backend: 'MediaElement',
  78. mediaControls: true
  79. },
  80. // containers to instantiate on, can be selector string or NodeList
  81. containers: 'wavesurfer',
  82. // @TODO insert plugin CDN URIs
  83. pluginCdnTemplate: '//localhost:8080/dist/plugin/wavesurfer.[name].js',
  84. // loadPlugin function can be overriden to inject plugin definition
  85. // objects, this default function uses load-script to load a plugin
  86. // and pass it to a callback
  87. loadPlugin(name, cb) {
  88. const src = _params.pluginCdnTemplate.replace('[name]', name);
  89. loadScript(src, { async: false }, (err, plugin) => {
  90. if (err) {
  91. return console.error(`WaveSurfer plugin ${name} not found at ${src}`);
  92. }
  93. cb(window.WaveSurfer[name]);
  94. });
  95. }
  96. }, params);
  97. /**
  98. * The nodes that should have instances attached to them
  99. * @type {NodeList}
  100. */
  101. this.containers = typeof _params.containers == 'string'
  102. ? document.querySelectorAll(_params.containers)
  103. : _params.containers;
  104. /** @private */
  105. this.pluginCache = {};
  106. /**
  107. * An array of wavesurfer instances
  108. * @type {Object[]}
  109. */
  110. this.instances = [];
  111. this.initAllEls();
  112. }
  113. /**
  114. * Initialise all container elements
  115. */
  116. initAllEls() {
  117. // iterate over all the container elements
  118. Array.prototype.forEach.call(this.containers, el => {
  119. // load the plugins as an array of plugin names
  120. const plugins = el.dataset.plugins
  121. ? el.dataset.plugins.split(',')
  122. : [];
  123. // no plugins to be loaded, just render
  124. if (!plugins.length) {
  125. return this.initEl(el);
  126. }
  127. // … or: iterate over all the plugins
  128. plugins.forEach((name, i) => {
  129. // plugin is not cached already, load it
  130. if (!this.pluginCache[name]) {
  131. this.params.loadPlugin(name, lib => {
  132. this.pluginCache[name] = lib;
  133. // plugins were all loaded, render the element
  134. if (i + 1 === plugins.length) {
  135. this.initEl(el, plugins);
  136. }
  137. });
  138. } else if (i === plugins.length) {
  139. // plugin was cached and this plugin was the last
  140. this.initEl(el, plugins);
  141. }
  142. });
  143. });
  144. }
  145. /**
  146. * Initialise a single container element and add to `this.instances`
  147. *
  148. * @param {HTMLElement} el The container to instantiate wavesurfer to
  149. * @param {PluginDefinition[]} plugins An Array of plugin names to initialise with
  150. * @return {Object} Wavesurfer instance
  151. */
  152. initEl(el, plugins = []) {
  153. const jsonRegex = /^[[|{]/;
  154. // initialise plugins with the correct options
  155. const initialisedPlugins = plugins.map(plugin => {
  156. const options = {};
  157. // the regex to find this plugin attributes
  158. const attrNameRegex = new RegExp('^' + plugin);
  159. let attrName;
  160. // iterate over all the data attributes and find ones for this
  161. // plugin
  162. for (attrName in el.dataset) {
  163. const regexResult = attrNameRegex.exec(attrName);
  164. if (regexResult) {
  165. const attr = el.dataset[attrName];
  166. // if the string begins with a [ or a { parse it as JSON
  167. const prop = jsonRegex.test(attr) ? JSON.parse(attr) : attr;
  168. // this removes the plugin prefix and changes the first letter
  169. // of the resulting string to lower case to follow the naming
  170. // convention of ws params
  171. const unprefixedOptionName = attrName.slice(plugin.length, plugin.length + 1).toLowerCase()
  172. + attrName.slice(plugin.length + 1);
  173. options[unprefixedOptionName] = prop;
  174. }
  175. }
  176. return this.pluginCache[plugin].create(options);
  177. });
  178. // build parameter object for this container
  179. const params = this.WaveSurfer.util.extend(
  180. { container: el },
  181. this.params.defaults,
  182. el.dataset,
  183. { plugins: initialisedPlugins }
  184. );
  185. // @TODO make nicer
  186. el.style.display = 'block';
  187. // initialise wavesurfer, load audio (with peaks if provided)
  188. const instance = this.WaveSurfer.create(params);
  189. const peaks = params.peaks ? JSON.parse(params.peaks) : undefined;
  190. instance.load(params.url, peaks);
  191. // push this instance into the instances cache
  192. this.instances.push(instance);
  193. return instance;
  194. }
  195. }
  196. // if window object exists and window.WS_StopAutoInit is not true
  197. if (typeof window === 'object' && !window.WS_StopAutoInit) {
  198. // call init when document is ready, apply any custom default settings
  199. // in window.WS_InitOptions
  200. if (document.readyState === 'complete') {
  201. window.WaveSurferInit = new Init(window.WaveSurfer, window.WS_InitOptions);
  202. } else {
  203. window.addEventListener('load', () => {
  204. window.WaveSurferInit = new Init(window.WaveSurfer, window.WS_InitOptions);
  205. });
  206. }
  207. }
  208. // export init for manual usage
  209. export default Init;