UNPKG

wavesurfer.js

Version:

Interactive navigable audio visualization using Web Audio and Canvas

223 lines (200 loc) 7.16 kB
/** * @since 3.0.0 */ import Observer from './observer'; class ProgressHandler { /** * Instantiate ProgressHandler * * @param {Observer} instance The `fetchFile` observer instance. * @param {Number} contentLength Content length. * @param {Response} response Response object. */ constructor(instance, contentLength, response) { this.instance = instance; this.instance._reader = response.body.getReader(); this.total = parseInt(contentLength, 10); this.loaded = 0; } /** * A method that is called once, immediately after the `ReadableStream`` * is constructed. * * @param {ReadableStreamDefaultController} controller Controller instance * used to control the stream. */ start(controller) { const read = () => { // instance._reader.read() returns a promise that resolves // when a value has been received this.instance._reader .read() .then(({ done, value }) => { // result objects contain two properties: // done - true if the stream has already given you all its data. // value - some data. Always undefined when done is true. if (done) { // ensure onProgress called when content-length=0 if (this.total === 0) { this.instance.onProgress.call(this.instance, { loaded: this.loaded, total: this.total, lengthComputable: false }); } // no more data needs to be consumed, close the stream controller.close(); return; } this.loaded += value.byteLength; this.instance.onProgress.call(this.instance, { loaded: this.loaded, total: this.total, lengthComputable: !(this.total === 0) }); // enqueue the next data chunk into our target stream controller.enqueue(value); read(); }) .catch(error => { controller.error(error); }); }; read(); } } /** * Load a file using `fetch`. * * @param {object} options Request options to use. See example below. * @returns {Observer} Observer instance * @example * // default options * let options = { * url: undefined, * method: 'GET', * mode: 'cors', * credentials: 'same-origin', * cache: 'default', * responseType: 'json', * requestHeaders: [], * redirect: 'follow', * referrer: 'client' * }; * * // override some options * options.url = '../media/demo.wav'; * // available types: 'arraybuffer', 'blob', 'json' or 'text' * options.responseType = 'arraybuffer'; * * // make fetch call * let request = util.fetchFile(options); * * // listen for events * request.on('progress', e => { * console.log('progress', e); * }); * * request.on('success', data => { * console.log('success!', data); * }); * * request.on('error', e => { * console.warn('fetchFile error: ', e); * }); */ export default function fetchFile(options) { if (!options) { throw new Error('fetch options missing'); } else if (!options.url) { throw new Error('fetch url missing'); } const instance = new Observer(); const fetchHeaders = new Headers(); const fetchRequest = new Request(options.url); // add ability to abort instance.controller = new AbortController(); // check if headers have to be added if (options && options.requestHeaders) { // add custom request headers options.requestHeaders.forEach(header => { fetchHeaders.append(header.key, header.value); }); } // parse fetch options const responseType = options.responseType || 'json'; const fetchOptions = { method: options.method || 'GET', headers: fetchHeaders, mode: options.mode || 'cors', credentials: options.credentials || 'same-origin', cache: options.cache || 'default', redirect: options.redirect || 'follow', referrer: options.referrer || 'client', signal: instance.controller.signal }; fetch(fetchRequest, fetchOptions) .then(response => { // store response reference instance.response = response; let progressAvailable = true; if (!response.body) { // ReadableStream is not yet supported in this browser // see https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream progressAvailable = false; } // Server must send CORS header "Access-Control-Expose-Headers: content-length" const contentLength = response.headers.get('content-length'); if (contentLength === null) { // Content-Length server response header missing. // Don't evaluate download progress if we can't compare against a total size // see https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Access-Control-Expose-Headers progressAvailable = false; } if (!progressAvailable) { // not able to check download progress so skip it return response; } // fire progress event when during load instance.onProgress = e => { instance.fireEvent('progress', e); }; return new Response( new ReadableStream( new ProgressHandler(instance, contentLength, response) ), fetchOptions ); }) .then(response => { let errMsg; if (response.ok) { switch (responseType) { case 'arraybuffer': return response.arrayBuffer(); case 'json': return response.json(); case 'blob': return response.blob(); case 'text': return response.text(); default: errMsg = 'Unknown responseType: ' + responseType; break; } } if (!errMsg) { errMsg = 'HTTP error status: ' + response.status; } throw new Error(errMsg); }) .then(response => { instance.fireEvent('success', response); }) .catch(error => { instance.fireEvent('error', error); }); // return the fetch request instance.fetchRequest = fetchRequest; return instance; }