前言

    之前写了一个音频播放器,但是没时间去优化,最近又有一个需求,希望能够支持背景播放,但是之前使用的是innerAudio,不能够实现背景播放,于是干脆就使用backgroundAudioManager来重新实现了一下音频播放器,并且优化了一下,使得第一次点击进度条时能够直接从点击位置开始播放音频,播放完成后进度条会归零并暂停播放;将js封装了一下便于调用

预览图

    

微信开发者工具小程序播放本地视频源码 小程序视频播放器_背景音乐播放

微信开发者工具小程序播放本地视频源码 小程序视频播放器_微信小程序_02

代码片段

    微信官方所推荐的使用方式(点击后打开开发工具):代码片段

详细代码

    以下为具体的页面代码,如果上方代码片段链接失效,请自行手动复制:

    关键代码,audioManager.js

//audioManager.js
//2019年8月15日09:52:09
// 实现的功能
//1.进入后获取音频时长; 2.播放暂停 3.进度条自动滚动,播放时间自动增加 4.在播放时拖动进度条后可跳转播放 5.播放完成后进度条归零并暂停

//bug&tip
//1.由于微信API wx.getBackgroundAudioManager();创建的背景音频对象在设置src后不能立刻pause();
//  所以获取duration时使用了 wx.createInnerAudioContext();
//  在用户点击播放按钮时才创建背景音频
//2.使用时,请按照微信官方文档,在app.json中配置支持背景播放的以下字段
//  "requiredBackgroundModes":["audio"]


//获取全局背景音乐管理器
const manager = wx.getBackgroundAudioManager();
//存储当前播放歌曲信息
let audioData = {
  src: '',
  title: '',
  singer: '',
  epname: '',
  coverImgUrl: '',
  webUrl: '',
  duration: 0,
  startTime: 0
};
let audioInterval;
//是否需要重置数据
let resetAudio = true;
const init = (e, t) => {//初始化播放器
  if (!e.src || !e.title) {
    return;
  }
  audioData.src = e.src || '';
  audioData.title = e.title || '';
  audioData.singer = e.singer || '';
  audioData.epname = e.epname || '';
  audioData.coverImgUrl = audioData.coverImgUrl || '';
  audioData.webUrl = e.webUrl || '';
  audioData.startTime = e.startTime || 0;
  resetAudio = true;
}

const createAudio = (e, t) => {//创建音频播放
  if (!e.src) {
    alert('没有音频地址')
    return;
  }
  setAudio(e, t);
  clearInterval(audioInterval);
  audioInterval = setInterval(() => {
    let backgroundAudio = t.data.backgroundAudio;
    backgroundAudio.isPlaying = !manager.paused;
    t.setData({
      backgroundAudio
    })
    if (!manager.paused) {
      let e = {
        currentTime: manager.currentTime
      }
      changeAudioProgressBar(e, t);
    }
  }, 1000);
  manager.onError((err) => {
    console.log('播放错误');
    console.log(err);
  })
  manager.onEnded(() => {//播放完成
    resetAudio = true;
    changeAudioProgressBar({ progress: 0 }, t);
    pause(t);
  })
}


const getDuration = (e, t) => {//获取音频总时长
  if (!e.src) {
    return;
  }
  const innerAudioContext = wx.createInnerAudioContext();//用于获取duration
  innerAudioContext.src = e.src;//设置src
  innerAudioContext.play();//播放一次以获取音频信息
  innerAudioContext.pause();//暂停
  innerAudioContext.onCanplay(() => {
    var durationInterval = setInterval(() => {
      if (innerAudioContext.duration) {
        audioData.duration = innerAudioContext.duration;
        let backgroundAudio = t.data.backgroundAudio;
        backgroundAudio.duration = innerAudioContext.duration;
        var min = parseInt(innerAudioContext.duration / 60), sec = parseInt(innerAudioContext.duration % 60);
        //小程序无法使用padstart,采用以下方式补全时间格式
        if (min.toString().length == 1) {
          min = `0${min}`;
        } else {
          min = `${min}`
        }
        if (sec.toString().length == 1) {
          sec = `0${sec}`;
        } else {
          sec = `${sec}`
        }
        backgroundAudio.durationTime = `${min}:${sec}`;
        t.setData({
          backgroundAudio
        })
        clearInterval(durationInterval);
      }
    }, 500)
  })
}

const seek = (e, t) => {//跳转到该进度播放
  if (!(e.currentTime || e.progress || e.currentTime == 0 || e.progress == 0)) {
    console.log('没有参数,无法跳转');
    return;
  }
  if (resetAudio) {
    if (e.currentTime || e.currentTime == 0) {
      audioData.startTime = e.currentTime;
    } else {
      audioData.startTime = parseInt(e.progress * audioData.duration / 100)
    }
  } else {
    if (e.currentTime || e.currentTime == 0) {
      manager.seek(parseInt(e.currentTime));
    } else {
      manager.seek(parseInt(e.progress * manager.duration / 100));
    }
    if (!manager.paused) {
      play(t);
    } else {
      pause(t);
    }
  }
}

const changeAudioProgressBar = (e, t) => {//修改进度条等显示用的信息
  if (!(e.currentTime || e.progress || e.currentTime == 0 || e.progress == 0)) {
    console.log('没有参数,无法设置进度条');
    return;
  }
  let duration, durationTime, currentDuration, currentDurationTime, progress;
  duration = manager.duration;
  if (e.currentTime || e.currentTime == 0) {
    currentDuration = e.currentTime;
    progress = parseInt(100 * currentDuration / duration);
  } else {
    progress = e.progress;
    currentDuration = parseInt(progress * duration / 100);
  }
  let timeArr = [parseInt(duration / 60), parseInt(duration % 60), parseInt(currentDuration / 60), parseInt(currentDuration % 60)];
  for (let i in timeArr) {
    //小程序无法使用padstart,采用以下方式补全时间格式
    if (timeArr[i].toString().length == 1) {
      timeArr[i] = `0${timeArr[i]}`
    } else {
      timeArr[i] = `${timeArr[i]}`
    }
  }
  currentDurationTime = `${timeArr[2]}:${timeArr[3]}`;
  let backgroundAudio = t.data.backgroundAudio;
  backgroundAudio.currentDuration = currentDuration;
  backgroundAudio.currentDurationTime = currentDurationTime;
  backgroundAudio.progress = progress;
  t.setData({ backgroundAudio })
  return backgroundAudio;
}


const play = (t) => {//播放
  if (resetAudio) {
    let e = {
      src: audioData.src,
      title: audioData.title,
      startTime: audioData.startTime
    }
    createAudio(e, t);
  }
  manager.play();
  if (t) {
    let backgroundAudio = t.data.backgroundAudio;
    backgroundAudio.isPlaying = true;
    t.setData({
      backgroundAudio
    })
  }
}
const pause = (t) => {//暂停
  manager.pause();
  if (t) {
    let backgroundAudio = t.data.backgroundAudio;
    backgroundAudio.isPlaying = false;
    t.setData({
      backgroundAudio
    })
  }
}
const stop = (t) => {//结束
  manager.stop();
  if (t) {
    let backgroundAudio = t.data.backgroundAudio;
    backgroundAudio.isPlaying = false;
    t.setData({
      backgroundAudio
    })
  }
}
const uninstall = (t) => {
  stop();//停止播放
  clearInterval(audioInterval);//清除计时器
  audioData = {//重置数据
    src: '',
    title: '',
    singer: '',
    epname: '',
    coverImgUrl: '',
    webUrl: ''
  };
  resetAudio = true;
}

const setAudio = (e, t) => {//设置播放器数据
  if (!e.src) {
    alert('没有音频地址')
    return;
  }
  console.log('setAudio的信息');
  console.log(e);
  manager.src = e.src;
  manager.title = e.title;
  manager.startTime = e.startTime;
  manager.epname = e.epname || '';
  manager.singer = e.singer || '';
  manager.coverImgUrl = e.coverImgUrl || '';
  manager.webUrl = e.webUrl || '';
  //将音频数据暂存
  audioData.src = e.src;
  audioData.title = e.title;
  audioData.epname = e.epname || '';
  audioData.singer = e.singer || '';
  audioData.coverImgUrl = e.coverImgUrl || '';
  audioData.webUrl = e.webUrl || '';
  audioData.startTime = 0;
  resetAudio = false;
  pause(t);
}

const hideAudio = (t) => {//切换到背景播放,暂时没用到

}
const showAudio = (t) => {//切换到前台播放,暂时没用到
  if (!manager.currentTime || !manager.duration) {
    return;
  }
}



const alert = (e) => {//提示
  wx.showToast({
    title: e,
    icon: 'none',
    mask: true
  })
}
module.exports = {
  init,
  getDuration,
  play,
  pause,
  stop,
  seek,
  hideAudio,
  showAudio,
  uninstall
}

       index.js

//index.js
const app = getApp(), myAudio = require("../utils/audioManager.js");

Page({
  data: {
    audio:{//用来存储服务器传输过来的内容
      src:'',
      title:'',
      coverImgUrl:''
    },
    backgroundAudio: {//实际正在播放的内容,必须配置在data中
      image: "",
      url: "",
      name: "",
      duration: "",
      durationTime: "",
      currentDuration: "",
      currentDurationTime: "",
      progress: "",
      isPlaying: false
    }
  },
  onLoad: function () {
    //模拟从服务器获取数据
    setTimeout(()=>{
      let audio={
        src:'http://rv01.sycdn.kuwo.cn/f79cf09a4426480dd5717af406275ffb/5d71f7df/resource/n3/2/9/591852463.mp3',
        title:'比翼的羽根',
        coverImgUrl:'https://goss.veer.com/creative/vcg/veer/800water/veer-146156021.jpg'
      };
      this.setData({
        audio
      })
      this.initBackGroundAudio();
    },2000);
  },
  //初始化音频
  initBackGroundAudio() {
    if (!this.data.audio.src) {
      return
    }
    let audio = {//设置播放器属性,src与title为必填
      src: this.data.audio.src,
      title: this.data.audio.title,
      coverImgUrl: this.data.audio.coverImgUrl
    };
    myAudio.init(audio, this);//初始化audio
    myAudio.getDuration(audio, this);//获取视频长度
    myAudio.pause();//暂停播放
  },
  dragAudioSlider(e) {//拖动进度条
    myAudio.seek({
      progress: e.detail.value
    }, this)//跳转到该进度
    myAudio.play(this);//播放
  },
  //自定义音频播放器
  sliderChange(e) {
    myAudio.seek({
      progress: e.detail.value
    }, this)//跳转到该进度
    myAudio.play(this);//播放
  },
  //播放按钮
  playAudio() {
    if (this.data.backgroundAudio.isPlaying) {
      myAudio.pause(this);
    } else {
      myAudio.play(this);
    }
  },
  onUnload(){
    myAudio.uninstall(this);//卸载播放器,重置播放器数据
  }
})

    index.wxml

<!-- index.wxml -->
<view class='audioPlayer'>
  <view class='player'>
    <image src='{{audio.coverImgUrl}}' class='audioBack' mode='aspectFill'></image>
    <view class='audioControls'>
      <view class='flex'>
        <view class='bottom' catchtap='playAudio'>
          <!-- 按钮 -->
          <view wx:if="{{backgroundAudio.isPlaying}}">
            <image src='../images/pause.png' />
          </view>
          <view wx:else>
            <image src='../images/play.png' />
          </view>
        </view>
        <view class='slider'>
          <slider bindchange='dragAudioSlider' activeColor='red' block-size="12" value='{{backgroundAudio.progress}}' />
        </view>
        <view class='time'>
          {{backgroundAudio.currentDurationTime||'00:00'}}/{{backgroundAudio.durationTime||'00:00'}}
        </view>
      </view>
    </view>
  </view>
</view>

    index.wxss

/* index.wxss */
.flex{
  display: flex;
}
.audioPlayer{
  width: 100%;
  height: 400rpx;
  margin-bottom: 30rpx;
  box-sizing: border-box;
  padding: 20rpx 30rpx;
}
.player{
  width: 100%;
  height: 100%;
  position: relative;
}
.audioBack{
  width: 100%;
  height: 100%;
}
.audioControls{
  width: 100%;
  height: 80rpx;
  background: black;
  opacity: .8;
  position: absolute;
  bottom: 0;
  color: white;
  font-size: 6pt;
  line-height: 80rpx;
  text-align: center;
}
.audioControls .bottom{
  width: 60rpx;
  height: 100%;
}
.audioControls .bottom image{
  margin-top: 30%;
  margin-left: 30%;
  width: 40rpx;
  height: 40rpx;
}
.audioControls .slider{
  width: 520rpx;
  height: 100%;
}
.slider slider{
  width: 95%;
  margin-left: 4%;
  margin-right: 0;
}
.audioControls .time{
  width: 120rpx;
  height: 100%;
}

注意事项

    1.使用时必须在使用的页面中引入audioManager.js,并且在data中设置好backgroundAudio的全部参数

    2.必须要在页面onunload事件中卸载播放器,否则会导致interval一直存在占用内容,导致crash

    3.需要在手机端预览,请填写appid

    4.以下为播放暂停图片

     

微信开发者工具小程序播放本地视频源码 小程序视频播放器_微信小程序_03

    

微信开发者工具小程序播放本地视频源码 小程序视频播放器_背景音乐播放_04