@speechkit/speechkit-audio-player
Version:
A web player component that can play audio from https://speechkit.io
339 lines (279 loc) • 7.77 kB
JavaScript
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