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

基于H5的音乐播放器开发(1)(前端篇)

作者头像
一粒小麦
发布2019-08-13 15:28:13
2.9K0
发布2019-08-13 15:28:13
举报
文章被收录于专栏:一Li小麦一Li小麦

预览地址:http://doc.djtao.net/cms/media/audio

这是我个人练习的小项目。基于koa2-iview+less定制。用于个人对播放器的复习。现已集成于个人网站上了。

前端部分要改成更通用性的界面也是没什么问题的。对于主要用了icon而已。

需求分析

其实要求比较简单:就是仿豆瓣fm。'

https://fm.douban.com

基本功能可拆解为:

  • css原生动画
  • 播放控制:音量,播放器开关。几个原生api
  • 歌词显示
  • 播放模式·
  • 顶/踩
  • 播放列表
  • 异常处理
  • 因为页面太空,下方增加歌词播放界面。解析lrc歌词。

播放器前端部分其实就围绕一个

布局与样式

写出来的样式如下:

相信不是太难。但是我其实最烦的就是样式了,调来调去很花时间。以下记录几个开发小难点。

音量

音量需要在鼠标悬停的时候。以动画划出。

同时,类似豆瓣这些小清新系的播放器有个特点,就是显示出来的进度槽特别细。在此处给的值是2px高度。

相比之下,爱奇艺的进度条简直是播放器设计界的看泥石流,

怎样让小清新系的音量控制条也好点击呢?我采用以下方案:

代码语言:javascript
复制
  <div id="volume" style="display:flex;margin-top:6px;">
    <!-- 音量小喇叭图标 -->
    <Icon style="margin-top:-4px;cursor:pointer;" size="14" type="md-volume-up" />&nbsp;
    <!-- 音量槽 -->
    <div @click="setvolume" id="volumeControlArea">
      <div id="volumeControl" class="volume">
        <div class="valunm-range" :style="{width:volumeWidth}"></div>
      </div>
    </div>
  </div>

div#volumeControlArea包着音量槽(div.#olumeControl),而音量槽正常时是隐藏的。悬停在小喇叭上才显示(把宽度加到100px):

代码语言:javascript
复制
// 音量
.volume {
  width: 0px;
  height: 2px;
  background: #ddd;
  margin-top: 2px;
  transition: width 0.2s;
  transition-timing-function: ease-out;
}

#volumeControlArea {
  padding-top: 10px;
  height: 20px;
  margin-top: -10px;
  cursor: pointer;
}

.valunm-range {
  height: 2px;
  background: #888;
}

.show {
  width: 100px;
  /* 属性过渡(动画) */
  transition: width 0.2s; /* 2s表示动画持续时间,多个属性之间用","号隔开 */
  transition-timing-function: ease-out;
}

在js这么写。当悬停/移出div.volume时,触发动画。如果你鼠标继续移到弹出来的音量槽,事件依然被div.colume捕获。因此不会出现抖动。

代码语言:javascript
复制
// mounted
      const volumeControl = document.querySelector("#volumeControl");

        volume.addEventListener("mouseenter", e => {
      let _this = e.target;
      volumeControl.classList.add("show");
    });

    volume.addEventListener("mouseleave", e => {
      let _this = e.target;
      volumeControl.classList.remove("show");
    });

而从样式得知:div#volumeControl本身就带transition属性。因此也能做到平滑滚动。

而核心在于div#volumeControlArea,它是有意做高了区域。

给了20像素的高度。那就不至于点不准了。

封面图

封面图是一个正圆,宽度是百分比,你如果用img来做是比较掉价的,所以用背景图。

代码语言:javascript
复制
<Col span="8">
    <div class="cover" id="cover"></div>
</Col>
代码语言:javascript
复制
.cover {
  width: 80%;
  margin: auto;
  height: 0;
  padding-top: 80%;
  border-radius: 50%;
    /* ... */
  background-size: 110% 110%;
  overflow: hidden;
  animation: play 8s linear infinite;
  animation-play-state:paused;
}
.cover-play{
    animation-play-state:running;
}

设置高度为0,给一个和宽度相等的百分比,便是正放形。

动画是8秒匀速转一圈,当播放开始时,给它加上.cover-play的类就可以了。反之去掉。

播放控制

相比之下,播放进度其实并没有那么难。

涉及关键api:

  • duration:总时长(秒)
  • currentTime:已播放时长(秒)
  • play/pause
播放进度条

先在mounted初始化:

代码语言:javascript
复制
    const audio = document.querySelector("#audio");
    this.audio = audio;
    this.currentTime = audio.currentTime;

    // 获取播放时长
    this.audio.addEventListener("canplay", e => {
      this.duration = e.target.duration;
    });

    // 监听播放进度变化
    audio.addEventListener("timeupdate", e => {
      this.currentTime = e.target.currentTime;
    });

以isPlay为状态,暂时先不考虑播放结束或报错的问题。只是以点击播放按钮为目标:

代码语言:javascript
复制
<!-- 进度显示 -->
<div class="audio-progress">
    <div class="played" :style="{right:played}"></div>
</div>

<!-- 播放/暂停 -->
<Icon @click="setplay" class="play-btn" :type="isPlay?'md-pause':'md-play'" />

<script>
  // method...
    // 播放暂停设置
    setplay() {
      this.isPlay = !this.isPlay;
      const cover=document.querySelector('#cover');

      if(this.isPlay){
          this.audio.play();
          cover.classList.add('cover-play');
      }else{
          this.audio.pause();
          cover.classList.remove('cover-play');
      }
    },
</sript>

div.play是绝对定位,通过监听进度百分比来变动数值。

剩余时间

把duration-currentTime的值转化为播放器的时间格式(HH-MM-SS)

代码语言:javascript
复制
    // 渲染秒为分钟
    formatSeconds(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}`;
    }

直接渲染就可以了。

音量控制

音量控制在样式那里已经做的足够好了。接下来就处理设置音量的问题。首先明确一下原生事件的属性:

clientX、clientY:点击位置距离当前body可视区域的x,y坐标

pageX、pageY:对于整个页面来说,包括了被卷去的body部分的长度

screenX、screenY:点击位置距离当前电脑屏幕的x,y坐标

offsetX、offsetY:相对于带有定位的父盒子的x,y坐标

用offset就可以了。

代码语言:javascript
复制
    // 设置音量
    setvolume(e) {
      // console.log(e.offsetX);
      let volumeSet = document.querySelector(".show");
      let width = parseInt(getComputedStyle(volumeSet).width);
      let volume = (e.offsetX / width) * 100;
      // 设置播放器音量
      this.audio.volume = volume / 100;
      // 设置音量槽宽度
      this.volumeWidth = volume + "%";
    },

异常处理

是的,核心功能差不多就结束了。还留下2个坑。

最常见的异常就是网络链接挂了。还有一个是播完了怎么办。

播完了怎么办

ended-----判断是否已经播放完毕,返回true/false

因为目前只是前端在搞,所以,播完就让他结束吧。我们把它放到返回进度条百分比计算属性里判断。

代码语言:javascript
复制
    played() {
      let percents = Math.round(100 * (1 - this.currentTime / this.duration));
      if (this.audio && this.audio.ended) {
        const cover = document.querySelector("#cover");
        this.isPlay = false;
        cover.classList.remove("cover-play");
        this.audio.pause();
      }

      return percents + "%";
    },

可以提炼出两个方法以优化代码:

代码语言:javascript
复制
    // 播放
    play() {
      const cover = document.querySelector("#cover");
      this.isPlay = false;
      cover.classList.remove("cover-play");
      this.audio.play()
    },

    // 暂停
    pause() {
      const cover = document.querySelector("#cover");
      this.isPlay = false;
      cover.classList.remove("cover-play");
      this.audio.pause()
    },
断网了

error----在发生了错误后,返回错误代码

MediaError 对象由一个code和message组成,其中 code 属性返回一个数字值,它表示音频的错误状态:

  • 1 = MEDIA_ERR_ABORTED - 取回过程被用户中止
  • 2 = MEDIA_ERR_NETWORK - 当下载时发生错误
  • 3 = MEDIA_ERR_DECODE - 当解码时发生错误
  • 4 = MEDIA_ERR_SRC_NOT_SUPPORTED - 不支持音频/视频

出错了怎么办?让播放停止。同时报错。

需要在两个地方监听error事件:播放过程中,设置播放时。

代码语言:javascript
复制
    // 播放暂停设置
    setplay() {
      if (!this.audio.error) {
        this.isPlay = !this.isPlay;
        if (this.isPlay) {
          this.play();
        } else {
          this.pause();
          cover.classList.remove("cover-play");
        }
      }else{
          this.pause();
          this.$Message.error(this.audio.error.message);
      }
    },
代码语言:javascript
复制
   // 监听
        played() {
      let percents = Math.round(100 * (1 - this.currentTime / this.duration));
      if ((!!this.audio) && this.audio.ended) {
        this.pause();
      }

      if((!!this.audio) && this.audio.error){
          this.pause();
          this.$Message.error(this.audio.error.message)
      }

      return percents + "%";
    },
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-08-11,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 需求分析
  • 布局与样式
    • 音量
      • 封面图
      • 播放控制
        • 播放进度条
          • 剩余时间
          • 音量控制
          • 异常处理
            • 播完了怎么办
              • 断网了
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档