• Audio 标签的使用和自己封装一个强大的React音乐播放器


    原文地址:https://www.dodoblog.cn/blog?id=5be84d5c70b2b617f27a4610

     

    这篇文章主要介绍一下博客里的这个音乐播放器是怎么写的

    为了更好的表达高深的东西,还是需要先强调点简单的东西 

    Audio元素的属性

    paused 是否暂停,不可以修改

    audio.paused = true // 错误
    audio.pause() // 正确

    currentTime 当前播放的时间,也可以用来改变进度,以下两行代码是等价的

    audio.currentTime = 10
    audio.play(10)

    duration 获取当前加载好的音乐的时间

    loop 是否单曲循魂

    mute 是否静音

    volume 音量大小,数值是0 到 1 

    Audio元素的事件

    play和pause

    播放和暂停的时候触发的事件

    seeking和seeked

    这里的快进是时间突然到某一个点上,不是倍速播放,快进开始和快进完成分别触发一个不同事件,因为快进了可能需要加载数据,需要一点点时间

    比如改变currentTime

    loadstart,loadeddata和canplay

    开始加,加载了一帧数据,加载到足够播放的数据

    ended

    结束的时候,结束了播放下一首用

    还有一些事件,比如progress, volumnchange 这些方法,随便搜一下就可以知道了,大家需要自行搜去,正常情况下这些就够用了。 

    React播放器

    参数

      <MusicPlayer
        getAudio={audio => this.audio = audio}
        musics={store.musicStore.currentList.songs}
      />

    为了方便启用,核心的参数只有两个

    getAudio,和ref一样,可以返回当前的audio元素 musics是一个音乐的列表

    因为在audio元素的基础上开发,所以几乎所有的事件其实也都是在audio上通过监听获取到,所以把audio元素抛出可以减少很多配置

    传入一个数组 这个数组是一个音乐列表的信息

    每个对象应该包括 id,封面图链接,歌曲链接,歌词链接,歌唱家,歌曲名称

    样式

    我写的这个播放器是这样的,很简单的样子,基本功能都有,没有循环播放和随机播放的设置,其实也很简单

     

    这个播放器的主要元素包括

    封面,歌曲列表,名称,歌词,进度,上一首下一首,还有封面上那个钱币一样的按钮是播放暂停

    然后侧面的两个按钮可以控制播放器的显示隐藏和歌曲列表的显示隐藏

    具体的UI大家想象着写就好了,反正我写的也不算好看

    留出那几个控制的按钮绑定事件就好了


    初始化

      componentDidMount() {
        const musics = this.props.musics
        const audio = this.$audio.current
        this.setState({
          currentIndex: musics && musics.findIndex(item => item.id === window.localStorage.getItem('current-music-id')) || 0,
        })
    
        audio.addEventListener('play', this.handlePlay)
        audio.addEventListener('pause', this.handlePause)
        this.props.getAudio && this.props.getAudio(audio)
      }

    初始化的时候,

    1. 添加了事件监听,这样当外部改变audio的状态也会改变播放器组件的状态

    2. 获取localstorage里的音乐,这个很友好,可以刷新仍然是那一首歌

    3. 抛出audio元素

    播放和暂停

      handlePlay = () => {
        const music = this.props.musics[this.state.currentIndex]
        const audio = this.$audio.current
    
        this.handleLoadLrc()
        this.setState({ paused: false })
    
        window.localStorage.setItem('current-music-id', music.id)
        audio.play(audio.currentTime)
    
        this.timer = setInterval(() => {
          const { currentTime, duration } = audio
          this.setState({ currentTime, duration })
        }, 100)
      }
    
      handlePause = () => {
        this.setState({ paused: true })
        this.$audio.current.pause()
        this.timer && clearInterval(this.timer)
      }

    注意

    1. currentIndex表示当前的播放的音乐的index

    2. 我们设置了pause的属性 是记录播放器的当前状态,audio的状态改变不能实时反应去渲染dom,所以需要用自己的state记录。

    3. play需要传入当前的audio时间,这样暂停播放不会重新开始,而切换后currentTime也会归0

    4. 动态记录了currentTime和duration,并且用计时器不对获取,也是因为我们并没有办法实时获取时间反映到进度条上

    上一首,下一首和切换

      handleNext = () => {
        let currentIndex = this.state.currentIndex + 1
        if (currentIndex >= this.props.musics.length) {
          currentIndex = 0
        }
    
        this.setState({currentIndex }, this.handlePlay)
      }
    
      handlePrev = () => {
        let currentIndex = this.state.currentIndex - 1
        if (currentIndex < 0) {
          currentIndex = this.props.musics.length - 1
        }
    
        this.setState({currentIndex }, this.handlePlay)
      }
    
      handleToggle = currentIndex => {
        this.setState({currentIndex }, this.handlePlay)
      }
     

    上一首就是index - 1然后播放,如果index = 0 那么index为最后一个

    下一首就是index +1然后播放,如果index已经最大 那么index重置0

    切换就是接受一个index 改变后播放

    这三个超级简单,不多解释

    快进

      handlePlayFrom = e => {
        const audio = this.$audio.current
        const { left, width } = e.target.getBoundingClientRect()
        const clickPos = (e.clientX - left) / width
        const time = audio.duration * clickPos
        if (!time) return false
        audio.currentTime = time
      }

    这个稍微考验些关于dom位置判断的功底

    通过获取进度条的宽度,点击的位置,然后计算出一个进度的百分比,最后根据这个百分比和总的音乐时长计算出点击点的时间

    加载歌词

    handleLoadLrc = () => {
        const request = new XMLHttpRequest();
        const url = this.props.musics[this.state.currentIndex].lrc
        request.open('GET', url, true);
        request.onload = () => {
          this.lyricStr = request.response
        }
        request.send();
      } 

    播放的时候回去加载歌词,就是一个ajax请求,返回的歌词一般都是这样的格式

    看起来像个数组,其实是一个字符串,然后我们可以通过正则将其拆分成一个时间 歌词一一对应的数组,这个是最麻烦的一点,但其实并没有什么技术复杂度,主要就是处理字符串通过split拆分成数组就好

    然后根据当前的currentTime获取需要显示的那句歌词

    不细节分析了,有需要的可以在我的github上看

    切换歌单

      componentDidUpdate(nextProps) {
        if (nextProps.musics !== this.props.musics) {
          this.setState({ currentIndex: 0 }, this.handlePlay)
        }
      }
    当音乐列表发生改变的时候,重新播放就好了

    播放器的显示隐藏

    动态添加一个class去隐藏整个播放器就好啦,fixed定位,很容易搞定的

    播放器的基本功能就这么多啦,当然还有一些,比如设置播放模式,依次还是随机,是否单曲循环等等,这些其实都很简单。

    具体的代码

    戳这里 获取播放器的代码,有需要的自取

    总结,在audio元素的基础上开发是比较简单的,但是这也有很多不好的地方,比如说,别人找到audio元素,然后加一个controls属性,就可以下载音乐了。

    真正强大的是Audio API,W3C提供了操作音频的一系列api 可以实现更好的音频buffer 播放效果,甚至通过analyser分析音频进行mix混音以及音乐效果的改变。

    还可以写好玩的动画效果,比如

     这些的前提当然是需要对音乐有一定的了解,有兴趣的小伙伴可以研究一下哦,这是很有趣的一个方向。

  • 相关阅读:
    禁止微信内置浏览器调整字体大小
    常用EXE文件反编译工具【转】
    C++发送HTTP请求---亲测可行(转)
    Cesium简介 [转]
    WebGL可视化地球和地图引擎:Cesium.js
    HTML5 canvas图形库 RGraph【转】
    [GitHub开源]基于HTML5实现的轻量级Google Earth三维地图引擎,带你畅游世界 【转】
    OpenGL实现多层绘制(Layered Rendering) [转]
    深度剖析OpenGL ES中的多线程和多窗口渲染技术
    OOP几大原则【转】
  • 原文地址:https://www.cnblogs.com/sowhite/p/9974238.html
Copyright © 2020-2023  润新知