UNPKG

aos-element

Version:

A Component Library for Vue.js.

423 lines (400 loc) 9.72 kB
import { Howl } from 'howler'; import clamp from 'math-clamp'; import values from 'object-values'; import assign from 'object-assign'; export default { props: { /** * A string type url of audio file */ source: { type: String, required: true, validator: (source) => { // Every source must be a non-empty string return typeof source === 'string' && source.length > 0; } }, /** * Whether to start the playback * when the component is mounted */ autoplay: { type: Boolean, default: false }, /** * Whether to start the playback again * automatically after it is done playing */ loop: { type: Boolean, default: false }, /** * Whether to start downloading the audio * file when the component is mounted */ preload: { type: Boolean, default: true }, /** * Whether to force HTML5 Audio */ html5: { type: Boolean, default: false }, /** * An array of audio file types */ formats: { type: Array, default() { return ['mp3']; } }, /** * Whether to enable the withCredentials flag on XHR * requests used to fetch audio files when using Web Audio API */ xhrWithCredentials: { type: Boolean, default: false } }, data() { return { /** * The Howl instance used for playback */ _howl: null, /** * Whether audio is currently playing */ playing: false, /** * Whether the audio playback is muted */ muted: false, /** * The volume of the playback on a scale of 0 to 1 */ volume: 1.0, /** * The rate (speed) of the playback on a scale of 0.5 to 4 */ rate: 1.0, /** * The position of playback in seconds */ seek: 0, /** * The duration of the audio in seconds */ duration: 0, /** * Functions that poll the Howl instance * to update various data */ _polls: { seek: { id: null, interval: 1000 / 4, // 4 times per second (4Hz) hook: () => { this.seek = this.$data._howl.seek(); } } }, /** * A list of howl events to listen to and * functions to call when they are triggered */ _howlEvents: [ { name: 'load', hook: () => { this.duration = this.$data._howl.duration(); } }, 'loaderror', 'playerror', { name: 'play', hook: () => { this.playing = true; } }, { name: 'end', hook: () => { this.playing = false; } }, { name: 'pause', hook: () => { this.playing = false; } }, { name: 'stop', hook: () => { this.playing = false; if (this.$data._howl != null) { this.seek = this.$data._howl.seek(); } } }, 'mute', { name: 'volume', hook: () => { this.volume = this.$data._howl.volume(); } }, { name: 'rate', hook: () => { this.rate = this.$data._howl.rate(); } }, { name: 'seek', hook: () => { if (!this.playing) this.seek = this.$data._howl.seek(); } }, 'fade' ] }; }, computed: { /** * The progress of the playback on a scale of 0 to 1 */ progress() { if (this.duration === 0) return 0; return this.seek / this.duration; } }, created() { this._initialize(); }, beforeDestroy() { this._cleanup(); }, watch: { playing(playing) { // Update the seek this.seek = this.$data._howl.seek(); if (playing) { // Start the seek poll this.$data._polls.seek.id = setInterval( this.$data._polls.seek.hook, this.$data._polls.seek.interval ); } else { // Stop the seek poll clearInterval(this.$data._polls.seek.id); } }, source(source) { if (source) { this._reinitialize(); } } }, methods: { /** * Reinitialize the Howler player */ _reinitialize() { this._cleanup(false); this._initialize(); }, /** * Initialize the Howler player */ _initialize() { if (!this.source) return; this.$data._howl = new Howl({ src: this.source, volume: this.volume, html5: this.html5, loop: this.loop, preload: this.preload, autoplay: this.autoplay, mute: this.muted, rate: this.rate, format: this.formats, xhrWithCredentials: this.xhrWithCredentials }); this.setVolume(this.curVolume); const duration = this.$data._howl.duration(); this.duration = duration; if (duration > 0) { // The audio file(s) have been cached. Howler won't // emit a load event, so we will do this manually this.$emit('load'); } // Bind to all Howl events this.$data._howlEvents = this.$data._howlEvents.map(event => { // Normalize string shorthands to objects if (typeof event === 'string') { event = { name: event }; } // Create a handler const handler = (id, details) => { if (typeof event.hook === 'function') event.hook(id, details); this.$emit(event.name, id, details); }; // Bind the handler this.$data._howl.on(event.name, handler); // Return the name and handler to unbind later return assign({}, event, { handler }); }); }, /** * Clean up the Howler player */ _cleanup(resetSettings = true) { // Stop all playback if (this.$data._howl) { this.stop(); } // Stop all polls values(this.$data._polls).forEach(poll => { if (poll.id != null) clearInterval(poll.id); }); // Clear all event listeners this.$data._howlEvents.map(event => { if (event.handler) { if (this.$data._howl) { this.$data._howl.off(event.name, event.handler); } const _event = assign({}, event); delete _event.handler; return _event; } return event; }); // Destroy the Howl instance this.$data._howl = null; this.duration = 0; if (resetSettings) { this.muted = false; this.volume = 1.0; this.rate = 1.0; } }, /** * Start the playback */ play() { if (!this.playing) this.$data._howl.play(); }, /** * Pause the playback */ pause() { if (this.playing) this.$data._howl.pause(); }, /** * Toggle playing or pausing the playback */ togglePlayback() { if (!this.playing) { this.$data._howl.play(); } else { this.$data._howl.pause(); } }, /** * Stop the playback (also resets the seek to 0) */ stop() { this.$data._howl.stop(); }, /** * Mute the playback */ mute() { this.$data._howl.mute(true); this.muted = true; }, /** * Unmute the playback */ unmute() { this.$data._howl.mute(false); this.muted = false; }, /** * Toggle muting and unmuting the playback */ toggleMute() { this.$data._howl.mute(!this.muted); this.muted = !this.muted; }, /** * Set the volume of the playback * @param {Number} volume - The new volume. * The value is clamped between 0 and 1 */ isInstanceExist() { if (this.$data && this.$data._howl) { return true; } return false; }, setVolume(volume) { if (typeof volume !== 'number') { throw new Error( `volume must be a number, got a ${typeof volume} instead` ); } if (!this.isInstanceExist()) return; this.$data._howl.volume(clamp(volume, 0, 1)); this.volume = clamp(volume, 0, 1); }, /** * Set the rate (speed) of the playback * @param {Number} rate - The new rate. * The value is clamped between 0.5 and 4 */ setRate(rate) { if (typeof rate !== 'number') { throw new Error(`rate must be a number, got a ${typeof rate} instead`); } if (!this.isInstanceExist()) return; this.$data._howl.rate(clamp(rate, 0.5, 4)); }, /** * Set the position of the playback * @param {Number} seek - The new position in seconds. * The value is clamped between 0 and the duration */ setSeek(seek) { if (typeof seek !== 'number') { throw new Error(`seek must be a number, got a ${typeof seek} instead`); } if (!this.isInstanceExist()) return; this.$data._howl.seek(clamp(seek, 0, this.duration)); }, /** * Set the progress of the playback * @param {Number} progress - The new progress. * The value is clamped between 0 and 1 */ setProgress(progress) { if (typeof progress !== 'number') { throw new Error( `progress must be a number, got a ${typeof progress} instead` ); } this.setSeek(clamp(progress, 0, 1) * this.duration); } } };