前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于react的H5音频播放器

基于react的H5音频播放器

作者头像
一粒小麦
发布2019-07-18 17:45:49
8K1
发布2019-07-18 17:45:49
举报
文章被收录于专栏:一Li小麦一Li小麦


初步

最近刚好就做了音频播放器的需求,现将踩坑记录如右。

项目是基于React,镶嵌在页面。为此开发了组件audio.js。不过不管什么框架。逻辑都是一样的。

基础界面如下

相信布局方面已经没什么问题。组件结构如下:

代码语言:javascript
复制
            <div style={{
                paddingTop: '30px',
                paddingLeft: 20,
                paddingRight: 20,
                border: '1px solid #ccc',
                borderRadius: '4px'
            }}>
                <audio
                    preload="metadata"
                    src={src}
                    // autoPlay
                    ref={(audio) => {
                        this.lectureAudio = audio;
                    }}
                    style={{ width: '1px', height: '1px', visibility: 'hidden' }}
                    onCanPlay={() => this.handleAudioCanplay()}
                    onTimeUpdate={() => this.handleTimeUpdate()}
                >
                </audio>


                {/* 进度条 */}
                <div className="audio-control">

                    <div onClick={() => {
                        this.play()
                    }} className="audio-control-play">
                        <img src={!this.state.playState ? Img.play : Img.pause} alt="" />
                    </div>

                </div>


                <div className="audio-progress" ref={(r) => {
                    this.audioProgress = r
                }}>
                    <div className="audio-progress-bar"
                        ref={(bar) => {
                            this.progressBar = bar
                        }}>
                    </div>

                    {/* 小点 */}
                    <div className="audio-progress-point-area" ref={(point) => {
                        this.audioPoint = point
                    }} style={{ left: this.state.left + 'px' }}>
                        <div className="audio-progress-point">
                        </div>
                    </div>
                </div>

                {/* 计时器 */}
                <div className="audio-timer">
                    <p>{this.renderPlayTime(this.state.currentTime)}</p>
                    <p>{this.renderPlayTime(this.state.duration)}</p>
                </div>
            </div>

组件相关的样式如下:

代码语言:javascript
复制
/* 播放器相关代码 */

.audio-progress-bar{
    width: 100%;
    height: 4px;
    background: rgb(195,195,195)
}

.audio-timer{
    margin-top:10px;
    display: flex;
    justify-content: space-between;
}
/* 小点 */
.audio-progress-point-area{
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: rgb(80,172,81);
    margin-top:-8px;
    position: absolute;
    left:0;
}

.audio-progress{
    position: relative;
}

.audio-control{
    height: 100px;
    padding-top: 30px;
    position: relative;
    background: #fafafa;
}
.audio-control-play{
    width: 30px;
    cursor: pointer;
    margin:10px auto
    /* position: absolute;
    top:30px; */
}

原生对象

组件内部藏着一个audio。audio满足如下特殊属性

HTML 音频/视频重要属性

属性

描述

currentTime(重要)

设置或返回音频/视频中的当前播放位置(以秒计)。

duration

返回当前音频/视频的长度(以秒计)。设置或返回是否在加载完成后随即播放音频/视频。

HTML 音频/视频事件

事件

描述

canplay

当浏览器可以开始播放音频/视频时触发。

ontimeupdate

当currentTime更新时会触发timeupdate事件”

pause

当音频/视频已暂停时触发。

play

当音频/视频已开始或不再暂停时触发。

playing

当音频/视频在因缓冲而暂停或停止后已就绪时触发。

进度条的大致原理就是获取音频的当前播放时长以及音频总时长的比例,然后通过这个比例与进度条宽度相乘,可以得到当前播放时长下进度条需要被填充的宽度。

代码中的“audio-progress-bar-preload”是用来做缓冲条的,大概的做法也是一样,不过获取缓冲进度得用到audio的buffered属性,具体的用法推荐大家去MDN看看,在这里就不多赘述。

进度条以及播放按钮的布局代码大概就是这样,在css方面需要注意的就是进度条容器与进度条填充块以及进度条触点间的层级关系就好。

功能逻辑

进度动起来

播放时,currntTime是时刻变化的。那么如何监听呢?

进度控件自然是绝对定位的。

固然可以用定时器做。但是在网页性能不好的时候,定时器就是钱。前面提到ontimeupdate事件回调。那真的是太好了。

代码语言:javascript
复制
handleTimeUpdate() {
        let width = this.progressBar.offsetWidth;
        this.setState(function (preState, props) {
            let precentleft = (this.lectureAudio.currentTime / this.lectureAudio.duration);
            return {
                currentTime: this.lectureAudio.currentTime,
                left: width * precentleft
            }
        });
    }

在组件渲染结束后执行以下始化:

代码语言:javascript
复制
    initListenTouch() {
        this.audioPoint.addEventListener('touchstart', (e) => {
            this.pointStart(e)
        }, false);
        this.audioPoint.addEventListener('touchmove', (e) => {
            this.pointMove(e)
        }, false);
        this.audioPoint.addEventListener('touchend', (e) => {
            this.pointEnd(e)
        }, false);

        this.progressBar.addEventListener('click', (e) => {
            this.jump(e)
        })
    }
移动端处理

移动端处理有三个事件回调:

  • touchstart--负责获取触摸进度触点时触点的方位
  • touchmove--负责动态计算触点的拖动距离,并转换成this.state.currentTime从而触发组件的重渲染.
  • touchend--负责恢复音频的播放
代码语言:javascript
复制
pointStart(e) {
        e.preventDefault();
        let touch = e.touches[0];
        this.lectureAudio.pause();
        //为了更好的体验,在移动触点的时候我选择将音频暂停
        this.setState({
            isPlaying: false,//播放按钮变更
            startX: touch.pageX//进度触点在页面中的x坐标
        });
    }

    pointMove(e) {
        e.preventDefault();
        let touch = e.touches[0];
        let x = touch.pageX - this.state.startX; //滑动的距离
        let maxMove = this.progressBar.clientWidth;//最大的移动距离不可超过进度条宽度
        let offsetWindowLeft = this.progressBar.getBoundingClientRect().left
        let moveX = this.lectureAudio.duration / this.progressBar.clientWidth;
        //moveX是一个固定的常数,它代表着进度条宽度与音频总时长的关系,我们可以通过获取触点移动的距离从而计算出此时对应的currentTime
        //下面是触点移动时会碰到的情况,分为正移动、负移动以及两端的极限移动。
        if (x >= 0) {
            // 一拖到底
            if (x + this.state.startX - offsetWindowLeft >= maxMove) {
                this.setState({
                    currentTime: this.state.duration,
                    left: offsetWindowLeft + this.progressBar.clientWidth
                }, () => {
                    this.lectureAudio.currentTime = this.state.currentTime;
                    //改变audio真正的播放时间
                })
                // 正常前进
            } else {
                this.setState({
                    currentTime: (x + this.state.startX - offsetWindowLeft) * moveX
                }, () => {
                    this.lectureAudio.currentTime = this.state.currentTime;
                })
            }
        } else {
            // 反向拖动
            if (-x <= this.state.startX - offsetWindowLeft) {
                // 反向托到底
                this.setState({
                    currentTime: (this.state.startX + x - offsetWindowLeft) * moveX,
                }, () => {
                    this.lectureAudio.currentTime = this.state.currentTime;
                })

            } else {

                this.setState({
                    currentTime: 0
                }, () => {
                    this.lectureAudio.currentTime = this.state.currentTime;
                })
            }
        }
    }

    pointEnd(e) {
        e.preventDefault();
        if (this.state.currentTime < this.state.duration) {
            this.touchendPlay = setTimeout(() => {
                // this.handleAudioPlay();
                this.setState({
                    playState: true
                })
                this.lectureAudio.play();
            }, 300)
        }
        //关于300ms的setTimeout,一是为了体验的良好,可以试试不要300ms的延迟,会发现收听体验不好,音频的播放十分仓促。
        //另外还有一点是,audio的pause与play间隔过短会出现报错,导致audio无法准确的执行相应的动作。
    }
PC端点击控件跟随:
代码语言:javascript
复制
    jump(e) {
        let width = e.target.offsetWidth;
        let x = e.offsetX
        let currentTime = x / width * this.state.duration;

        this.setState({
            currentTime
        }, () => {
            this.lectureAudio.currentTime = this.state.currentTime;
        })
    }

### 其它逻辑

渲染时间
代码语言:javascript
复制
// 渲染秒为分钟
    renderPlayTime(time) {

        var minute = time / 60;
        var minutes = parseInt(minute);
        if (minutes < 10) {
            minutes = "0" + minutes;
        }
        //秒
        var second = time % 60;
        var seconds = Math.round(second);
        if (seconds < 10) {
            seconds = "0" + seconds;
        }

        return `${minutes}:${seconds}`
    }
其它辅助功能
代码语言:javascript
复制
play() {
        if (this.state.playState == false) {
            this.lectureAudio.play()
        } else {
            this.lectureAudio.pause()
        }

        this.setState((preState, props) => {

            return {
                playState: !preState.playState
            }
        })

    }

小结

整个组件用到的状态极少:

代码语言:javascript
复制
constructor(props) {
        super(props);
        this.state = {
            currentTime: 0,
            duration: 0,
            left: 0,
            playState: false
        }
    }

播放器的核心就是currentTime,这也是开发时的刻意为之,最后会发现这个组件中的唯一变量就是currentTime,我们可以通过currentTime的变化完成所有的需求,并且不需要考虑其他因素的影响,因为所有的子组件都是围绕着currentTime运转。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-03-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一Li小麦 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 初步
  • 原生对象
    • HTML 音频/视频重要属性
      • HTML 音频/视频事件
      • 功能逻辑
        • 进度动起来
          • 移动端处理
          • PC端点击控件跟随:
          • 渲染时间
          • 其它辅助功能
      • 小结
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档