UNPKG

@speechkit/speechkit-audio-player

Version:

A web player component that can play audio from https://speechkit.io

339 lines (279 loc) 7.77 kB
import Hls from 'hls.js' import EventEmitter from 'eventemitter3' import { find } from 'lodash' import { noop } from '../utils' import { PLAYER_DOM_EVENTS_MAP } from '../constants/playerEvents' const EVENT_HANDLE_MAP = { [PLAYER_DOM_EVENTS_MAP.playing]: 'didPlay', [PLAYER_DOM_EVENTS_MAP.ended]: 'didEnd', [PLAYER_DOM_EVENTS_MAP.pause]: 'didPause', [PLAYER_DOM_EVENTS_MAP.loadedmetadata]: 'didLoadMetadata', [PLAYER_DOM_EVENTS_MAP.timeupdate]: 'didTimeUpdate', [PLAYER_DOM_EVENTS_MAP.waiting]: 'handleWaiting', } const contentTypes = { MP3: 'audio/mpeg', M3U8: 'application/x-mpegURL', } // IE dont have remove if (!('remove' in Element.prototype)) { Element.prototype.remove = function() { if (this.parentNode) { this.parentNode.removeChild(this) } } } class AudioPlayer extends EventEmitter { static contentTypes = contentTypes constructor() { super() this.state = { loading: false, loaded: true, playing: false, paused: false, } // Instance variables that will be set this.player = null this.timeOutWaitPlay = null this.isKicker = false } getMediaForPodcast(podcast) { if (this.options.isDemo) { return { content_type: "audio/mpeg", duration: this.demoAudio.duration, url: this.options.preview } } let preferredContentType = this.getPreferredContentType() const preferredMedia = find(podcast.media, (media) => media['content_type'] === preferredContentType) return preferredMedia || podcast.media[0] } canPlayHls() { return Hls.isSupported() } getPreferredContentType() { return this.canPlayHls() ? AudioPlayer.contentTypes.M3U8 : AudioPlayer.contentTypes.MP3 } setupAudioEvents() { // Set up tracking events if (this.player) { Object.keys(EVENT_HANDLE_MAP).forEach(event => { this.player.addEventListener(event, this[EVENT_HANDLE_MAP[event]], false) }) } } detachAudioEvents() { if (this.player) { Object.keys(EVENT_HANDLE_MAP).forEach(event => { this.player.removeEventListener(event, this[EVENT_HANDLE_MAP[event]]) }) } } destroyAudio() { this.pause() if (this.hls) { this.hlsIsLoaded = false this.hls.destroy() if (this.player) { this.detachAudioEvents() this.player.remove() this.player = null } } } loadMedia() { const media = this.mediaAds || this.media if (!this.hlsIsLoaded && this.getPreferredContentType() === AudioPlayer.contentTypes.M3U8) { this.hls.loadSource(media.url) this.hlsIsLoaded = true } else if (!this.state.loaded && this.getPreferredContentType() === AudioPlayer.contentTypes.MP3) { this.player.load() this.setState({ loading: false, loaded: true }) } } play() { this.setState({ loading: true }) this.loadMedia() this.afterLoadPlay() this.updateUIState() } clearTimeoutWaitPlay = () => { if (this.timeOutWaitPlay) { clearTimeout(this.timeOutWaitPlay) this.timeOutWaitPlay = null } } afterLoadPlay() { const adsIsPlaying = this.adsService.ads && !this.adsService.isPlayed() && !this.adsService.isPlaying() const playPromise = this.player.play() if (playPromise !== undefined) { this.timeOutWaitPlay = setTimeout(() => { this.clearTimeoutWaitPlay() this.timeOutWaitPlay = null this.afterLoadPlay() }, 500) playPromise.catch(error => { this.clearTimeoutWaitPlay() console.error('playPromise with error -> ', error) }).then(() => { this.clearTimeoutWaitPlay() }) } else { this.clearTimeoutWaitPlay() } if (adsIsPlaying) { this.setCurrentTime(0) this.setPlaybackRate(1) } if (!adsIsPlaying && this.speed) { this.setPlaybackRate(this.speed) } } pause() { if (this.player) { this.player.pause() } } mute() { this.player.muted = true } unmute() { this.player.muted = false } /** * Build the HTML5 audio player */ buildAudio(container, selectedMedia) { // Set up audio files if (!this.player) { this.player = document.createElement('audio') this.player.preload = 'none' this.player.id = `${this.renderNode}-audio` // set title if present (so ios displays on lockscreen) this.player.title = (this.podcast && this.podcast.title && this.podcast.title.length) ? this.podcast.title : 'SpeechKit Audio' this.setupAudioEvents() } this.player.innerHTML = `<source id=${this.renderNode}-audio__source src=${selectedMedia.url} type=${selectedMedia.content_type} />` this.setState({ metadataloaded: false }) // Setup HLS this.hls = new Hls() this.hlsIsLoaded = false if (this.canPlayHls() && selectedMedia.content_type === AudioPlayer.contentTypes.M3U8) { const { Events: { MEDIA_ATTACHED, MANIFEST_PARSED, ERROR, FRAG_LOADED, }, } = Hls this.hls.attachMedia(this.player) this.hls.on(MEDIA_ATTACHED, () => { this.player = this.hls.media this.hls.on(MANIFEST_PARSED, () => { this.setState({ loading: false, loaded: true }) this.hlsIsLoaded = true }) this.hls.on(ERROR, (err) => { this.hlsIsLoaded = false console.error('hls on ERROR -> ', err) if (this.adsService.ads && !this.adsService.isPlayed()) { this.resetAdsAndPlayPodcast(false) } }) }) this.hls.on(FRAG_LOADED, (name, { frag }) => { this.loadedFragment = frag }) } } skipCurrentTime(seconds) { this.player.currentTime = this.player.currentTime + seconds } setCurrentTimePercent(percent) { const duration = this.getDuration() if (duration) { if (this.adsService.ads && !this.adsService.isPlayed()) { this.setPodcastCurrentTime(this.getDuration() * percent) } this.setCurrentTime(this.getDuration() * percent) } } playSoundSnippet(src, volume = 1) { return new Promise(resolve => { const resolveFn = () => resolve() const audio = new Audio(src) audio.volume = volume audio.onended = resolveFn audio.onerror = resolveFn const playPromise = audio.play() if (playPromise !== undefined) { playPromise.catch(error => { console.error('playSoundSnippet with error -> ', error) resolveFn() }).then(noop) } }) } /** * Getters */ getCurrentTime() { return this.currentTime || this.player.currentTime } getDuration() { return this.player.duration } getCurrentPercentage() { return (100 * this.getCurrentTime() / this.getDuration()) } getSpeed() { return this.player.playbackRate } isPlaying() { return !this.player.paused } isEnded() { return !this.player.ended } getVolume() { return this.player.volume } isMuted() { return this.player.muted } isLoop() { return this.player.loop } /** * Setters */ setPodcastCurrentTime(time) { this.shouldPlayInTime = time } setCurrentTime(time) { if (this.state.metadataloaded) { this.currentTime = null return this.player.currentTime = time } return this.currentTime = time } setVolume(volume) { this.player.volume = volume } setLoop(loop) { this.player.loop = loop } setPlaybackRate(playbackRate) { this.player.playbackRate = playbackRate } setState(state) { this.state = { ...this.state, ...state, } } } export default AudioPlayer