微信小程序学习之旅--零基础制作自己的小程序-文章详情页音乐播放功能的实现-背景音频/全局App/页面的bug修改和优化

本文详细介绍了微信小程序中实现音乐播放功能的步骤,包括样式制作、背景音频播放、事件绑定、功能测试和错误处理。作者通过调整代码,解决了音乐播放图标不同步、控制台报错等问题,并实现了音乐播放状态的初始化和切换。此外,还讨论了全局变量的使用以保持音乐播放状态,以及在不同文章间切换时音乐状态的同步问题。
摘要由CSDN通过智能技术生成


前文
微信小程序学习之旅–第一个页面的制作
微信小程序学习之旅–零基础制作自己的小程序–第二个页面的制作
微信小程序学习之旅–完善pages页面–字体图标,数据绑定,条件/列表渲染,事件/catch/bind以及路由的学习
微信小程序学习之旅–零基础制作自己的小程序-完成文章详情页–自定义属性/页面通信/缓存机制/异步API/async/await

微信小程序入门(五)

音乐播放功能

制作样式

静态样式没啥好说的,基本上有手就行了。

<view class="head-image-audio">
    <image class="head-image" src="{{imgSrc}}"></image>
    <!-- 实现音乐播放  -->
    <image class="audio" src="/images/music/music-start.png" />
  </view>
/* 大图+音乐播放 */
.head-image-audio {
  position: relative;
  height: 460rpx;
}

/* 图片 */
.head-image {
  width: 100%;
  height: 100%;
}

/* 音乐播放 */
.audio {
  width: 102rpx;
  height: 110rpx;
  position: absolute;
  /* 居中 */
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  opacity: .6;
}

image-20210829110147532

我们想要实现的功能就是点击以后实现音乐的播放。

背景音频播放

这里也需要了解一下官方提供的关于背景音频播放的API组件。

绑定事件

老规矩,肯定还是需要在图片上绑定点击事件。

<!-- 实现音乐播放  -->
    <image bind:tap="onMusic" class="audio" src="/images/music/music-start.png" />
功能测试

这里先简单的使用一下背景音频。

/**
   * 音乐播放功能
   * @param {*} event 
   */
  onMusic(event) {
    // 获取全局唯一的背景音频管理器。 小程序切入后台,如果音频处于播放状态,可以继续播放。但是后台状态不能通过调用API操纵音频的播放状态
    const mgr = wx.getBackgroundAudioManager();
    // 赋值音乐播放的链接
    mgr.src = this.data.music.url;
    // 音乐播放的标题
    mgr.title = this.data.music.title;
   }

image-20210829112622826

咳咳,很明显,没什么大毛病。用起来也不难。

控制台报错

image-20210829112702351

虽然使用没什么问题,但是控制台给我们了报错提示。这时候,我们去这个地址看看。

为了能够转后台以后可以继续播放音频。我们就在app.json文件中配置一下。

image-20210829112917612

当我们在app.json中配置了这个属性,并且数组里面只有一个值audio时

"requiredBackgroundModes": ["audio"]

音乐播放的同时,切后台音乐也会一直播放。

而只使用location这个值,只有在小程序没有退出的时候才会一直播放,一旦切后台,音乐就会停止。

"requiredBackgroundModes": ["location"]

我们这里就把两个值都加上吧。

"requiredBackgroundModes": ["audio","location"]
切换音乐播放图标

因为需要进行音乐播放与停止的切换,所以图标也需要更改。

<!-- 实现音乐播放  -->
    <!-- 播放和暂停图标的切换 条件渲染 -->
    <!-- 当然这里用三元表达式就不需要使用两个标签了 -->
    <image wx:if="{{!isPlaying}}" bind:tap="onMusicStart" class="audio" src="/images/music/music-start.png" />
    <image wx:else bind:tap="onMusicStop" class="audio" src="/images/music/music-stop.png" />
/**
   * 音乐播放开始功能
   * @param {*} event 
   */
  onMusicStart(event) {
    // 获取全局唯一的背景音频管理器。 小程序切入后台,如果音频处于播放状态,可以继续播放。但是后台状态不能通过调用API操纵音频的播放状态
    const mgr = wx.getBackgroundAudioManager();
    // 赋值音乐播放的链接
    mgr.src = this.data.music.url;
    // 音乐播放的标题
    mgr.title = this.data.music.title;
    // 播放时使用的图片
    mgr.coverImgUrl = this.data.music.coverImg;
    // 切换当前音乐的播放状态为 播放中
    this.setData({
      isPlaying: true
    });

  },
  /**
   * 音乐暂停的事件处理
   * @param {*} event 
   */
  onMusicStop(event) {
    const mgr = wx.getBackgroundAudioManager();
    // 音乐停止
    mgr.stop();
    // 音乐状态的切换
    this.setData({
      isPlaying:false
    })
   },

说实话,这样的确达到了音乐的停止和播放之间状态的切换。但是,你会发现每次切换播放和暂停音乐以后,音乐需要重新播放、并且,如果在音乐播放的时候,我们点击下面的控制面板,可以发现,音乐暂停了,但是图标不会自动切换。问题还是很多。

image-20210829141418289

同步音乐控制开关与图标的状态
背景音频对象的播放/暂停事件回调

很明显,想要达到我们需要的某些状态,肯定是需要使用音频对象提供的某些回调函数。

image-20210829142040225

所以我们通过这两个事件,来根据音乐的播放和暂停调用不同的函数。

进一步优化

因为我们会在很多地方都使用到这个背景音频的对象,所以我们将其放到data数据源中,并且在页面加载的时候进行获取,同时在onLoad中监听音乐的播放和暂停两个事件。

/**
   * 页面的初始数据
   */
  data: {
    post: {},
    // 记录当前文章的id
    _pid: null,
    // 当前文章是否收藏
    collected: false,
    // 所有文章是否收藏的缓存对象
    _postsCollected: {},
    // 音乐是否播放中
    isPlaying: false,
    // 背景音频对象
    _mgr: null
  },

  /**
   * 生命周期函数--监听页面加载
   */
  async onLoad(options) {
    // 通过监听页面加载的的函数的参数options,机也可以拿到我们传递过来本页面的参数
    // 注意:查询字符串(参数)的类型都是字符串类型
    // console.log(options);
    const [post] = [...postList.filter(post => post.postId === parseInt(options.pid))];

    // 记录文章id
    this.data._pid = post.postId;

    // console.log(post);
    // 拿到我们指定的文章
    this.setData(post);

    // 读取缓存,拿到文章是否收藏的状态
    const { data: postsCollected } = await wx.getStorage({
      key: 'post_collected',
    })
    // 记录所有文章的缓存状态的对象
    this.data._postsCollected = postsCollected;
    // 拿到当前文章的收藏状态 取不到则表示这篇文章未收藏
    const collected = postsCollected[this.data._pid] || false;
    // console.log(postsCollected);
    // console.log(collected);
    this.setData({ collected });

    

    const mgr = wx.getBackgroundAudioManager();
    this.data._mgr = mgr;
    // 监听背景音乐的播放和停止
    // mgr.onPlay(()=>{
    //   // 音乐播放
    //   this.onMusicStart();
    // });
    mgr.onPlay(this.onMusicStart);
    // 音乐暂停的回调处理
    mgr.onPause(this.onMusicStop);
  },
  /**
   * 文章收藏点击事件的回调
   * @param {*} event  事件对象
   */
  async onCollect(event) {

    const postCollected = this.data._postsCollected;


    // 当前文章的收藏状态 取不到则肯定没有收藏过
    const collected = postCollected[this.data._pid] || false;
    // 用文章id的值作为实际存储对象的属性,属性值是是否收藏
    postCollected[this.data._pid] = !collected;
    wx.setStorageSync('post_collected', postCollected)
    this.setData({
      // 这里直接取反就可以了 上面的执行完毕这里可以直接取反了
      collected: !collected
    });

    // 使用小程序默认的API 弹框组件来提示用户是收藏文章 还是取消收藏
    wx.showToast({
      // 提示文字
      title: this.data.collected ? "收藏文章成功!" : "取消收藏成功!",
      // 提示框停留时间
      duration: 2000
    })


  },
  /**
   * 完成文章分享的回调函数
   * @param {*} event 
   */
  onShare(event) {
    // 调用小程序原生的组件 实现分享
    wx.showActionSheet({
      // 分享的方式(具体分享以后做什么,我们并没有做)
      itemList: ["分享到QQ", "分享到微信", "分享到微博", "分享到朋友圈"],
      success(res) {
        // 通过 res.tapIndex 可以拿到我们点击了哪一个数组元素的索引
        // 想做其他事情可以做
        // console.log(res.tapIndex);
      }
    })
  },
  /**
   * 音乐播放开始功能
   * @param {*} event 
   */
  onMusicStart(event) {
    const mgr = this.data._mgr;
    // 赋值音乐播放的链接
    mgr.src = this.data.music.url;
    // 音乐播放的标题
    mgr.title = this.data.music.title;
    // 播放时使用的图片
    mgr.coverImgUrl = this.data.music.coverImg;
    // 切换当前音乐的播放状态为 播放中
    this.setData({
      isPlaying: true
    });
  },
  /**
   * 音乐暂停的事件处理
   * @param {*} event 
   */
  onMusicStop(event) {
    const mgr = this.data._mgr;
    // 音乐停止
    mgr.stop();
    // 暂停播放
    // mgr.pause();
    // 音乐状态的切换
    this.setData({
      isPlaying: false
    })
  },

image-20210829144141275

这样就可以完成音乐的停止和播放之间的切换了。

但是,这样做我们音乐就直接停止了,用户点击的是暂停按钮,按理说我们要做的应该是让音乐暂停,而不是直接终止。

很显然,用户点击控制面板的暂停,应该就是让音乐暂停,而不是直接停止。

所以说,我们要做的,应该是点击我们自己的按钮,实现的是音乐的播放和停止,而用户点击下面的控制面板,应该是在播放和暂停之间切换。

再一次优化音乐的暂停和停止

话都说的这么明白了,接下来不用我说,有手就行了。

这里将音乐停止的函数进行改进。

/**
   * 音乐暂停的事件处理
   * @param {*} isStop 停止音乐 
   */
  onMusicStop(isStop = true) {
    // const mgr = wx.getBackgroundAudioManager();
    const mgr = this.data._mgr;
    if (isStop) {
      // 音乐停止
      mgr.stop();
    } else {
      // 暂停播放
      mgr.pause();
    }
    // 音乐状态的切换
    this.setData({
      isPlaying: false
    })
  },
  async onLoad(options) {
      // 省略 。。。
      const mgr = wx.getBackgroundAudioManager();
      this.data._mgr = mgr;
      mgr.onPlay(this.onMusicStart);
      // 音乐暂停的回调处理
      mgr.onPause(this.onMusicStop.bind(this,false));
}

这就达到了我们想要的效果。点击下面的控制面板,音乐暂停;点击上面图片按钮,音乐由播放转为停止。

image-20210829145522243

音乐播放状态的初始化问题

当我们解决了上面的问题之后,接下来其实很有其他问题。

image-20210829150048411

当我们点击返回按钮,此时音乐还是处于播放状态,因为我们没有停止播放。再次进行本页面的时候,这个音乐还是播放中,但是我们的图标和下面音乐的状态对不上了。

image-20210829151024017

原因当然很明显,是我们的状态问题,每次我们页面的加载时,状态都被重置为false。

这里我们可以采用全局变量的方式,来记录音乐的播放与否。没必要使用缓存,因为缓存是持久化的。实际上程序退出了就没必要管音乐的事情了。

在优化

这思路就很简单了。搞一个全局变量,每次在音乐状态切换的同时,我们全局的状态跟着改变。

// app.js

App({
  // 记录音乐的播放状态
  gIsPlayingMusic:false
})
// // pages/post-detail/post-detail.js

// 全局的小程序对象,用来获取我们的全局变量
const app = getApp()

Page({
    onLoad(){
        // 获取全局记录的播放状态
    	this.setData({
      	isPlaying: app.gIsPlayingMusic
    	});
    },
    /**
   * 音乐播放开始功能
   * @param {*} event 
   */
  onMusicStart(event) {

    // 改变全局的音乐播放状态
    app.gIsPlayingMusic = true;
  },
  /**
   * 音乐暂停的事件处理
   * @param {*} isStop 停止音乐 
   */
  onMusicStop(isStop = true) {
    // 改变全局的音乐播放状态
    app.gIsPlayingMusic = false;
  }
})

其它代码都不在赘述。

image-20210829152951445

分析其它问题
不是问题也是问题的问题

老实说,改了这么多,还是有一些小的bug。刚解决一个又来一个。

这个问题说是问题,也可以说不是问题。

就是我们发现,我们当前文章的歌曲在播放,然后我们退出去进入其他页面,按理说其他页面的歌曲和我们这个是不一样的歌曲,应该是不应该让音乐按钮显示播放状态的,但是我们每次获取的状态都是全局的,所以导致只要有音乐在播放,进去其他文章也都是会显示播放状态。

这个问题你说是问题,其实也没错,本来就是bug。你要是说不是问题,也没毛病,本来就是有音乐就是在播放中。

这里我理解呢是bug。就简单的解决一下。

思路还是那么明显,搞个全局变量就完事。弄一个全局变量记录当前音乐播放是属于那篇文章的。这样在赋值播放状态的时候,我们就可以根据文章的id号是否相同,来解决这个问题。

全局变量
// app.js

  // 记录音乐的播放状态
  gIsPlayingMusic: false,
  // 记录播放的音乐是属于那篇文章的 默认是-1
  gIsPlayingPostId: -1
文章音乐播放状态
/**
   * 当前文章的音乐播放状态 
   * 
   * @returns 只有音乐所属的文章号是当前所在的文章页面,且音乐一直处于播放状态, 返回true
   */
  currentPostMusicIsPlaying() {
    if (app.gIsPlayingMusic && this.data._pid === app.gIsPlayingPostId)
      return true;
    return false;
  }
文章音乐暂停和停止

音乐暂停的逻辑不需要改变,音乐停止的逻辑进行修改

/**
   * 音乐暂停的事件处理
   * @param {*} isStop 停止音乐 
   */
  onMusicStop(isStop = true) {
    const mgr = this.data._mgr;
    if (isStop) {
      // 音乐停止
      mgr.stop();
      // 音乐停止,重置全局音乐的文章id
      app.gIsPlayingPostId = -1;
    } else {
      // 暂停播放
      mgr.pause();
    }
    // 音乐状态的切换
    this.setData({
      isPlaying: false
    })
    // 改变全局的音乐播放状态
    app.gIsPlayingMusic = false;
  }
onLoad初始化的时候赋值
onLoad(){
       // 音乐播放状态 只有音乐所属的文章号是当前所在的文章页面,且音乐一直处于播放状态,我们才重置音乐的播放状态为true
    this.setData({
      isPlaying: this.currentPostMusicIsPlaying()
    });
}

image-20210829162601019

还未解决的bug

解决了很多问题。但是还是存在一些问题。

音乐播放完毕,图标不同步

当我们音乐播放完毕后,上面的图标并没有同步的进行状态的切换,音乐都播放完了,按理说状态应该是未播放。

音乐播放时进入其他文章

表面上,我们解决了这些问题,但是进去其他页面的时候,如果我们点击了音乐的暂停,这很ok。如果暂停后再次点击播放,播放的音乐就变成当前文章的背景音乐了。我觉得这也不是很合理的。

bug很多。头疼。

下一篇

微信小程序入门之旅-第六天-自定义组件及request的使用-制作电影页面

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

尤雨东

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值