主要由於微信小程序官方的audio
不維護了,而且在不少iOS
真機上確實也存在點擊沒法播放,總時長不顯示等問題.
javascript
icon_loading.gif
icon_playing.png
icon_paused.pngcss
屬性:html
stringsrc
: 音頻資源的地址,用於直接播放。
bumberstartTime
: 開始播放的位置(單位:s),默認爲 0
booleanautoplay
: 是否自動開始播放,默認爲false
booleanloop
: 是否循環播放,默認爲false
numbervolume
: 音量。範圍 0~1。默認爲 1
numberplaybackRate
: 播放速度。範圍 0.5-2.0,默認爲 1。(Android 須要 6 及以上版本)
numberduration
: 當前音頻的長度(單位 s)。只有在當前有合法的 src 時返回(只讀)
numbercurrentTime
: 當前音頻的播放位置(單位 s)。只有在當前有合法的 src 時返回,時間保留小數點後 6 位(只讀)
booleanpaused
: 當前是是否暫停或中止狀態(只讀)
numberbuffered
: 音頻緩衝的時間點,僅保證當前播放時間點到此時間點內容已緩衝(只讀)
方法:java
play()
: 播放
pause()
: 暫停。暫停後的音頻再播放會從暫停處開始播放
stop()
: 中止。中止後的音頻再播放會從頭開始播放。
seek(postions: number)
:跳轉到指定位置
destory()
: 銷燬當前實例
onCanplay(callback)
: 監聽音頻進入能夠播放狀態的事件。但不保證後面能夠流暢播放
offCanplay(callback)
: 取消監聽音頻進入能夠播放狀態的事件
onPlay(callback)
: 監聽音頻播放事件
offPlay(callback)
: 取消監聽音頻播放事件
onPause(callback)
: 監聽音頻暫停事件
offPause(callback)
: 取消監聽音頻暫停事件
onStop(callback)
: 監聽音頻中止事件
offStop(callback)
: 取消監聽音頻中止事件
onEnded(callback)
: 監聽音頻天然播放至結束的事件
offEnded(callback)
: 取消監聽音頻天然播放至結束的事件
onTimeUpdate(callback)
: 監聽音頻播放進度更新事件
offTimeUpdate(callback)
: 取消監聽音頻播放進度更新事件
onError(callback)
: 監聽音頻播放錯誤事件
offError(callbcak)
: 取消監聽音頻播放錯誤事件
onWaiting(callback)
: 監聽音頻加載中事件。當音頻由於數據不足,須要停下來加載時會觸發
offWaiting(callback)
: 取消監聽音頻加載中事件
onSeeking(callback)
: 監聽音頻進行跳轉操做的事件
offSeeking(callback)
: 取消監聽音頻進行跳轉操做的事件
onSeeked(callback)
: 監聽音頻完成跳轉操做的事件
offSeeked(callback)
: 取消監聽音頻完成跳轉操做的事件
讓咱們開始吧🛠小程序
<!-- playOrPauseAudio()是一個播放或者暫停播放音頻的方法 --> <!-- fmtSecond(time)是一個將秒格式化爲 分:秒 的方法 --> <View className='custom-audio'> <Image onClick={() => this.playOrStopAudio()} src={audioImg} className='audio-btn' /> <Text>{this.fmtSecond(Math.floor(currentTime))}/{this.fmtSecond(Math.floor(duration))}</Text> </View>
type PageOwnProps = { audioSrc: string // 傳入的音頻的src }
CustomAudio
組件的初始化相關的操做,並給innerAudioContext
的回調添加一寫行爲// src/components/widget/CustomAudio.tsx import Taro, { Component, ComponentClass } from '@tarojs/taro' import { View, Image, Text } from "@tarojs/components"; import iconPaused from '../../../assets/images/icon_paused.png' import iconPlaying from '../../../assets/images/icon_playing.png' import iconLoading from '../../../assets/images/icon_loading.gif' interface StateInterface { audioCtx: Taro.InnerAudioContext // innerAudioContext實例 audioImg: string // 當前音頻icon標識 currentTime: number // 當前播放的時間 duration: number // 當前音頻總時長 } class CustomAudio extends Component<{}, StateInterface> { constructor(props) { super(props) this.fmtSecond = this.fmtSecond.bind(this) this.state = { audioCtx: Taro.createInnerAudioContext(), audioImg: iconLoading, // 默認是在加載音頻中的狀態 currentTime: 0, duration: 0 } } componentWillMount() { const { audioCtx, audioImg } = this.state audioCtx.src = this.props.audioSrc // 當播放的時候經過TimeUpdate的回調去更改當前播放時長和總時長(總時長更新放到onCanplay回調中會出錯) audioCtx.onTimeUpdate(() => { if (audioCtx.currentTime > 0 && audioCtx.currentTime <= 1) { this.setState({ currentTime: 1 }) } else if (audioCtx.currentTime !== Math.floor(audioCtx.currentTime)) { this.setState({ currentTime: Math.floor(audioCtx.currentTime) }) } const tempDuration = Math.ceil(audioCtx.duration) if (this.state.duration !== tempDuration) { this.setState({ duration: tempDuration }) } console.log('onTimeUpdate') }) // 當音頻能夠播放就將狀態從loading變爲可播放 audioCtx.onCanplay(() => { if (audioImg === iconLoading) { this.setAudioImg(iconPaused) console.log('onCanplay') } }) // 當音頻在緩衝時改變狀態爲加載中 audioCtx.onWaiting(() => { if (audioImg !== iconLoading) { this.setAudioImg(iconLoading) } }) // 開始播放後更改圖標狀態爲播放中 audioCtx.onPlay(() => { console.log('onPlay') this.setAudioImg(iconPlaying) }) // 暫停後更改圖標狀態爲暫停 audioCtx.onPause(() => { console.log('onPause') this.setAudioImg(iconPaused) }) // 播放結束後更改圖標狀態 audioCtx.onEnded(() => { console.log('onEnded') if (audioImg !== iconPaused) { this.setAudioImg(iconPaused) } }) // 音頻加載失敗時 拋出異常 audioCtx.onError((e) => { Taro.showToast({ title: '音頻加載失敗', icon: 'none' }) throw new Error(e.errMsg) }) } setAudioImg(newImg: string) { this.setState({ audioImg: newImg }) } // 播放或者暫停 playOrStopAudio() { const audioCtx = this.state.audioCtx if (audioCtx.paused) { audioCtx.play() } else { audioCtx.pause() } } fmtSecond (time: number){ let hour = 0 let min = 0 let second = 0 if (typeof time !== 'number') { throw new TypeError('必須是數字類型') } else { hour = Math.floor(time / 3600) >= 0 ? Math.floor(time / 3600) : 0, min = Math.floor(time % 3600 / 60) >= 0 ? Math.floor(time % 3600 / 60) : 0, second = Math.floor(time % 3600 % 60) >=0 ? Math.floor(time % 3600 % 60) : 0 } } return `${hour}:${min}:${second}` } render () { const { audioImg, currentTime, duration } = this.state return( <View className='custom-audio'> <Image onClick={() => this.playOrStopAudio()} src={audioImg} className='audio-btn' /> <Text>{this.fmtSecond(Math.floor(currentTime))}/{this.fmtSecond(Math.floor(duration))}</Text> </View> ) } } export default CustomAudio as ComponentClass<PageOwnProps, PageState>
乍一看咱們的組件已經知足了微信小程序
可是這個組件還有一些問題:微信
innerAudioContext
對象中止播放和回收ComponentWillMount
中初始化了innerAudioContext
的屬性因此當props
中的audioSrc
變化的時候組件自己不會更新音源、組件的播放狀態和播放時長在componentWillReceiveProps
中增長一些行爲達到props
中的audioSrc
更新時組件的音源也作一個更新,播放時長和狀態也作一個更新app
componentWillReceiveProps(nextProps) { const newSrc = nextProps.audioSrc || '' console.log('componentWillReceiveProps', nextProps) if (this.props.audioSrc !== newSrc && newSrc !== '') { const audioCtx = this.state.audioCtx if (!audioCtx.paused) { // 若是還在播放中,先進行中止播放操做 audioCtx.stop() } audioCtx.src = nextProps.audioSrc // 重置當前播放時間和總時長 this.setState({ currentTime: 0, duration: 0, }) } }
這時候咱們在切換音源的時候就不會存在還在播放舊音源的問題函數
componentWillUnmount
中中止播放和銷燬innerAudioContext
達到一個提高性能的目的componentWillUnmount() { console.log('componentWillUnmount') this.state.audioCtx.stop() this.state.audioCtx.destory() }
audioPlaying
來保證全局有且僅有一個音頻組件能夠處於播放狀態// 在Taro中定義全局變量按照一下的規範來,獲取和更改數據也要使用定義的get和set方法,直接經過Taro.getApp()是不行的 // src/lib/Global.ts const globalData = { audioPlaying: false, // 默認沒有音頻組件處於播放狀態 } export function setGlobalData (key: string, val: any) { globalData[key] = val } export function getGlobalData (key: string) { return globalData[key] }
beforeAudioPlay
和afterAudioPlay
// src/lib/Util.ts import Taro from '@tarojs/taro' import { setGlobalData, getGlobalData } from "./Global"; // 每次在一個音源暫停或者中止播放的時候將全局標識audioPlaying重置爲false,用以讓後續的音頻能夠播放 export function afterAudioPlay() { setGlobalData('audioPlaying', false) } // 在每次播放音頻以前檢查全局變量audioPlaying是否爲true,若是是true,當前音頻不能播放,須要以前的音頻結束或者手動去暫停或者中止以前的音頻播放,若是是false,返回true,並將audioPlaying置爲true export function beforeAudioPlay() { const audioPlaying = getGlobalData('audioPlaying') if (audioPlaying) { Taro.showToast({ title: '請先暫停其餘音頻播放', icon: 'none' }) return false } else { setGlobalData('audioPlaying', true) return true } }
CustomAudio
組件import { beforeAudioPlay, afterAudioPlay } from '../../lib/Utils'; /* ... */ // 由於組件卸載致使的中止播放別忘了也要改變全局audioPlaying的狀態 componentWillUnmount() { console.log('componentWillUnmount') this.state.audioCtx.stop() this.state.audioCtx.destory() ++ afterAudioPlay() } /* ... */ // 每次暫停或者播放完畢的時候須要執行一次afterAudioPlay()讓出播放音頻的機會給其餘的音頻組件 audioCtx.onPause(() => { console.log('onPause') this.setAudioImg(iconPaused) ++ afterAudioPlay() }) audioCtx.onEnded(() => { console.log('onEnded') if (audioImg !== iconPaused) { this.setAudioImg(iconPaused) } ++ afterAudioPlay() }) /* ... */ // 播放前先檢查有沒有其餘正在播放的音頻,沒有的狀況下才能播放當前音頻 playOrStopAudio() { const audioCtx = this.state.audioCtx if (audioCtx.paused) { ++ if (beforeAudioPlay()) { audioCtx.play() ++ } } else { audioCtx.pause() } }
// src/components/widget/CustomAudio.tsx import Taro, { Component, ComponentClass } from '@tarojs/taro' import { View, Image, Text } from "@tarojs/components"; import { beforeAudioPlay, afterAudioPlay } from '../../lib/Utils'; import './CustomAudio.scss' import iconPaused from '../../../assets/images/icon_paused.png' import iconPlaying from '../../../assets/images/icon_playing.png' import iconLoading from '../../../assets/images/icon_loading.gif' type PageStateProps = { } type PageDispatchProps = { } type PageOwnProps = { audioSrc: string } type PageState = {} type IProps = PageStateProps & PageDispatchProps & PageOwnProps interface CustomAudio { props: IProps } interface StateInterface { audioCtx: Taro.InnerAudioContext audioImg: string currentTime: number duration: number } class CustomAudio extends Component<{}, StateInterface> { constructor(props) { super(props) this.fmtSecond = this.fmtSecond.bind(this) this.state = { audioCtx: Taro.createInnerAudioContext(), audioImg: iconLoading, currentTime: 0, duration: 0 } } componentWillMount() { const { audioCtx, audioImg } = this.state audioCtx.src = this.props.audioSrc // 當播放的時候經過TimeUpdate的回調去更改當前播放時長和總時長(總時長更新放到onCanplay回調中會出錯) audioCtx.onTimeUpdate(() => { if (audioCtx.currentTime > 0 && audioCtx.currentTime <= 1) { this.setState({ currentTime: 1 }) } else if (audioCtx.currentTime !== Math.floor(audioCtx.currentTime)) { this.setState({ currentTime: Math.floor(audioCtx.currentTime) }) } const tempDuration = Math.ceil(audioCtx.duration) if (this.state.duration !== tempDuration) { this.setState({ duration: tempDuration }) } console.log('onTimeUpdate') }) // 當音頻能夠播放就將狀態從loading變爲可播放 audioCtx.onCanplay(() => { if (audioImg === iconLoading) { this.setAudioImg(iconPaused) console.log('onCanplay') } }) // 當音頻在緩衝時改變狀態爲加載中 audioCtx.onWaiting(() => { if (audioImg !== iconLoading) { this.setAudioImg(iconLoading) } }) // 開始播放後更改圖標狀態爲播放中 audioCtx.onPlay(() => { console.log('onPlay') this.setAudioImg(iconPlaying) }) // 暫停後更改圖標狀態爲暫停 audioCtx.onPause(() => { console.log('onPause') this.setAudioImg(iconPaused) afterAudioPlay() }) // 播放結束後更改圖標狀態 audioCtx.onEnded(() => { console.log('onEnded') if (audioImg !== iconPaused) { this.setAudioImg(iconPaused) } afterAudioPlay() }) // 音頻加載失敗時 拋出異常 audioCtx.onError((e) => { Taro.showToast({ title: '音頻加載失敗', icon: 'none' }) throw new Error(e.errMsg) }) } componentWillReceiveProps(nextProps) { const newSrc = nextProps.audioSrc || '' console.log('componentWillReceiveProps', nextProps) if (this.props.audioSrc !== newSrc && newSrc !== '') { const audioCtx = this.state.audioCtx if (!audioCtx.paused) { // 若是還在播放中,先進行中止播放操做 audioCtx.stop() } audioCtx.src = nextProps.audioSrc // 重置當前播放時間和總時長 this.setState({ currentTime: 0, duration: 0, }) } } componentWillUnmount() { console.log('componentWillUnmount') this.state.audioCtx.stop() this.state.audioCtx.destory() afterAudioPlay() } setAudioImg(newImg: string) { this.setState({ audioImg: newImg }) } playOrStopAudio() { const audioCtx = this.state.audioCtx if (audioCtx.paused) { if (beforeAudioPlay()) { audioCtx.play() } } else { audioCtx.pause() } } fmtSecond (time: number){ let hour = 0 let min = 0 let second = 0 if (typeof time !== 'number') { throw new TypeError('必須是數字類型') } else { hour = Math.floor(time / 3600) >= 0 ? Math.floor(time / 3600) : 0, min = Math.floor(time % 3600 / 60) >= 0 ? Math.floor(time % 3600 / 60) : 0, second = Math.floor(time % 3600 % 60) >=0 ? Math.floor(time % 3600 % 60) : 0 } } return `${hour}:${min}:${second}` } render () { const { audioImg, currentTime, duration } = this.state return( <View className='custom-audio'> <Image onClick={() => this.playOrStopAudio()} src={audioImg} className='audio-btn' /> <Text>{this.fmtSecond(Math.floor(currentTime))}/{this.fmtSecond(Math.floor(duration))}</Text> </View> ) } } export default CustomAudio as ComponentClass<PageOwnProps, PageState>
// src/components/widget/CustomAudio.scss .custom-audio { border-radius: 8vw; border: #CCC 1px solid; background: #F3F6FC; color: #333; display: flex; flex-flow: row nowrap; align-items: center; justify-content: space-between; padding: 2vw; font-size: 4vw; .audio-btn { width: 10vw; height: 10vw; white-space: nowrap; display: flex; align-items: center; justify-content: center; } }
★,°:.☆( ̄▽ ̄)/$:*.°★* 。完美*★,°*:.☆( ̄▽ ̄)/$:.°★ 。🎉🎉🎉oop
有什麼好的建議你們能夠在評論區跟我討論下哈,別忘了點贊收藏分享哦,下期就更uni-app
版本的~