originally

Again, because applets officially don’t maintain the audio component

Requirements and limitations of audio components

  1. Click Play or pause
  2. Display the playback progress and total duration
  3. Display current audio status by icon change (pause/play/loading)
  4. Refresh component state when page audio is updated
  5. There is one and only one audio in the global playback state
  6. Automatically stop playing and destroy the audio instance when you leave the page

Material/attribute/method

Let’s get started 🛠

uni-app Vue

  • Same preconstructionDOMstructure
<view class="custom-audio">
  <image v-if="audioSrc ! == undefined && audioSrc ! == null && audioSrc ! = = "" @click="playOrStopAudio" :src="audioImg" class="audio-btn" />
  <text v-else @click="tips" class="audio-btn">No audio</text>
  <text>{{ fmtSecond(currentTime) }}/{{ fmtSecond(duration) }}</text>
</view>
Copy the code
  • Define the accepted components
props: {
  audioSrc: {
    type: String.default: ' '}},Copy the code
  • defineCustomAudioComponent initialization related operations, and toinnerAudioContextThe callback adds some behavior (beforeTaro thatThe potholes we stepped on are directly in the code here)
import { formatSecondToHHmmss, afterAudioPlay, beforeAudioRecordOrPlay } from '.. /.. /lib/Utils'
const iconPaused = '.. /.. /static/images/icon_paused.png'
const iconPlaying = '.. /.. /static/images/icon_playing.png'
const iconStop = '.. /.. /static/images/icon_stop.png'
const iconLoading = '.. /.. /static/images/icon_loading.gif'
// ...
data() {
  return {
    audioCtx: null.// Audio context
    duration: 0.// Total audio duration
    currentTime: 0.// How long the audio is currently playing
    audioImg: iconLoading, // The default state is loading}},watch: {
  audioSrc: {
    handler(newSrc, oldSrc) {
      console.log('watch', newSrc, oldSrc)
      this.audioImg = iconLoading
      this.currentTime = 0
      this.duration = 0
      if (this.audioCtx === undefined) {
        this.audioCtx = uni.createInnerAudioContext()
        this.onTimeUpdate = this.audioCtx.onTimeUpdate
        this.bindAuidoCallback(this.audioCtx)
      } else {
        this.audioCtx.src = newSrc
      }
      if (this.audioCtx.play) {
        this.audioCtx.stop()
        getApp().globalData.audioPlaying = false}}}},mounted() {
  this.audioCtx = uni.createInnerAudioContext()
  this.audioCtx.src = this.audioSrc
  this.audioCtx.startTime = 0
  this.bindAuidoCallback(this.audioCtx)
},
methods: {
  bindAuidoCallback(ctx) {
    ctx.onTimeUpdate((e) = > {
      this.onTimeUpdate(e)
    })
    ctx.onCanplay((e) = > {
      this.onCanplay(e)
    })
    ctx.onWaiting((e) = > {
      this.onWaiting(e)
    })
    ctx.onPlay((e) = > {
      this.onPlay(e)
    })
    ctx.onPause((e) = > {
      this.onPause(e)
    })
    ctx.onEnded((e) = > {
      this.onEnded(e)
    })
    ctx.onError((e) = > {
      this.onError(e)
    })
  },
  tips(){
    uni.showToast({
      title: 'Invalid audio source, please record first'.icon: 'none'})},playOrStopAudio() {
    if (this.audioCtx === null) {
      this.audioCtx = uni.createInnerAudioContext()
      this.audioCtx.src = this.audioSrc
      this.bindAuidoCallback(this.audioCtx)
    }
    if (this.audioCtx.paused) {
      if (beforeAudioRecordOrPlay('play')) {
        this.audioCtx.play()
        this.audioImg = iconPlaying
      }
    } else {
      this.audioCtx.pause()
      afterAudioPlay()
      this.audioImg = iconPaused
    }
  },
  onTimeUpdate(e) {
    console.log('onTimeUpdate'.this.audioCtx.duration, this.audioCtx.currentTime)
    if (this.audioCtx.currentTime > 0 && this.audioCtx.currentTime <= 1) {
      this.currentTime = 1
    } else if (this.currentTime ! = =Math.floor(this.audioCtx.currentTime)) {
      this.currentTime = Math.floor(this.audioCtx.currentTime)
    }
    const duration = Math.floor(this.audioCtx.duration)
    if (this.duration ! == duration) {this.duration = duration
    }
  },
  onCanplay(e) {
    if (this.audioImg === iconLoading) {
      this.audioImg = iconPaused
    }
    console.log('onCanplay', e)
  },
  onWaiting(e) {
    if (this.audioImg ! == iconLoading) {this.audioImg = iconLoading
    }
  },
  onPlay(e) {
    console.log('onPlay', e, this.audioCtx.duration)
    this.audioImg = iconPlaying
    if (this.audioCtx.duration > 0 && this.audioCtx.duration <= 1) {
      this.duration = 1
    } else {
      this.duration = Math.floor(this.audioCtx.duration)
    }
  },
  onPause(e) {
    console.log('onPause', e)
    this.audioImg = iconPaused
  },
  onEnded(e) {
    console.log('onEnded', e)
    if (this.audioImg ! == iconPaused) {this.audioImg = iconPaused
    }
    afterAudioPlay()
  },
  onError(e) {
    uni.showToast({
      title: 'Audio load failed'.icon: 'none'
    })
    throw new Error(e.errMsg, e.errCode)
  },
  fmtSecond(sec) {
    const { min, second } = formatSecondToHHmmss(sec)
    return `${min}:${second}`}},Copy the code

The samescssfile

<style lang="scss" scoped>
.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: 14px;
  .audio-btn {
    width: 10vw;
    height: 10vw;
    white-space: nowrap;
    display: flex;
    align-items: center;
    justify-content: center;
  }
}
</style>
Copy the code

The last

You have what question to encounter or what suggestion can discuss with me yo ~