音频组件开发实践

2018-09-15 admin

我的blog原文链接

最近公司迭代的项目中,新增了音频播放功能,填了不少音频的坑,总结一下:

需求:

交互需求:

  1. 上传:点击按钮上传语音,返回文件id(上传文件的范畴,本文不会阐述)
  2. 播放:点击播放按钮,异步获取语音播放src,音频载入之后播放
  3. 操作:语音支持播放、暂停、停止、进度拖动等操作

当然还有一些隐性需求:

  1. 一个界面可能存在多个播放文件
  2. 随时播放一个语音,其它语音应当暂停
  3. 播放过程中,用户重新上传新的语音,此时播放应停止

实现效果如下图所示:(当然,这只是项目用到的一部分,项目中还有其他页面也用到了这个组件,那么就更考验组件的健壮性和可拓展性了。)

音频播放效果

按需实现

一个界面可能存在多个播放文件

我们对音频的操作,通常是先获取这个音频 DOM Element,通过对它的操作,实现想要达到的效果,如果你只是设定一个audio这样单薄的ref名称,恐怕会有些问题,因此我给每个音频设定了一个唯一的ref名称。

<template>
  <audio :src="audioSrc" :ref='uniqueId' :data-key="uniqueId" hidden></audio>
</template>
<script>
export default {
  data () {
    return {
      // uniqueId() 是随机生成字符串的方法
      uniqueId: uniqueId(),
      audioSrc: '',
    }
  },
  computed: {
    audioElement () {
      return this.$refs[this.uniqueId]
    }
  },
}
</script>

暂停其他语音

注意到上面的代码,我在给 audio 添加属性的时候,多添加了一个 data-key的属性,那就是为了暂停其他语音而使用的,作为我要操作页面其他音频而设置的标识:

// 暂停其他语音的方法
pauseOthers (except) {
  var audios = document.querySelectorAll('audio')
  ;[].forEach.call(audios, audio => {
    if (audio.dataset['key'] !== except.uniqueId) {
      audio.pause()
    }
  })
}
// 调用的时候
this.pauseOthers (this)

异步获取语音src,音频载入之后播放

我想这就是项目坑点之一,因为音频src并不是上传语音就返回的,上传语音只返回了语音id,我们需要通过id再去异步请求一次,才能获取到src。

基于这样的前提,播放操作做了两点考虑(单例模式思维):

  • 为什么点击播放再获取语音src?虽然也可以进入界面就请求src,但是如果用户不点击播放,就白白浪费了不需要的请求,基于性能的考虑,决定点击播放后再进行请求。
  • 并不需要每次点击都重新请求一次,只有未获取过src的音频需要重新请求。

具体实现:

  1. 播放按钮绑定togglePlay()事件

  2. 判断audioSrc是否有值

    • 如果有值,直接进行播放,绑定相关事件,暂停其他语音
    • 如果没有值,设置loading并进行异步请求,将返回结果赋值给audioSrc,监听音频 canplay
  3. 监听音频 canplay (这边有一个坑点,后面会提到) 在canplay的回调中,loading结束,绑定相关事件,暂停其他语音

为什么相关事件的绑定放在 canplay 中? 不然你可能会出现下面的报错:

Uncaught (in promise) DOMException: The element has no supported sources.

所以,答应我,基于audio播放的 事件 或是 属性 ,都放在 canplay 的回调之后。

相关事件包括(本组件中):

  1. 监听事件 timeupdate : 控制进度条展示
  2. 监听事件 pause : 监听按钮 播放/暂停 样式
  3. 设置属性 currentTime : 控制进度拖动或者停止语音
  4. 监听事件 error : 监听播放错误

音频的操作

播放与暂停

按钮的样式通过设置一个变量作为状态值,paly()pause() 的时候分别改变状态值。

其它具体逻辑上文描述比较清楚,不再赘述。

停止、进度拖动

  • 停止:暂停音频,并将currentTime设置为0
  • 进度拖动:根据拖动位置计算currentTime值,并设置currentTime

两个操作都涉及到了currentTime的设置,我们在这里遇到了两个坑:

  1. 设置currentTime无效 查询资料后发现是后端的锅,具体解决办法链接在这里:HTML5 audio ,在chrome中设置currentTime无效

  2. 设置currentTime继续播放

    一开始仍然以为是后端的锅,因为当我静态设置一个 audioSrc 的时候,是没有问题的,但是当我动态设置,就会出现这样的问题:无论我是播放状态还是暂停状态,设置到相对应的currentTime都会继续播放。

    通过排查,发现当我设置currentTime会再次触发一次 canplay事件, canplay 的回调是绑定播放的相关操作,因此会继续播放。

    解决办法,温习了一遍addEventListener的语法,绑定canplay事件最多只调用一次。

    this.audioElement.addEventListener('canplay', () => {
          // ...相关操作
    }, {
      once: true
    })
    

音频的打断

音频的打断包括两种情况:

  1. 组件 destroyed
  2. 重新上传新的语音

第一种情况,解绑相关事件,释放内存。

第二种情况,具体描述一下:

当用户重新上传新的语音,不论此时语音暂停还是播放状态,都应该停止。

我们通过 watch 监听 id (上传返回来的音频id),当id变化的时候,将 audioSrc 清空,以免播放旧的音频内容。

然而,仅仅这样是不够的,如果监听 error 事件,就会发现报错,解决的办法还是解绑相关事件,即,我们在 canplay 回调中的绑定的相关事件,让audio恢复初始状态,等到下一次播放的时候,需要重新请求新的src,回到上面播放的部分。

拓展

在解决问题的过程中,也查询到了一些实用的知识点,虽然在应用中没有运用到,但是这些知识点看起来似乎挺有用的,为了下次遇到其他坑能快速找到解决办法,先把这些知识点记录下来。

canplaycanplaythrough 辨析

  • 当浏览器能够开始播放指定的音频/视频时,会发生 canplay 事件。
  • 当浏览器预计能够在不停下来进行缓冲的情况下持续播放指定的音频/视频时,会发生 canplaythrough 事件。
  • 了解其他媒体相关事件

HTMLMediaElement.play() 返回 Promise

<video><audio>play()返回一个 Promise,如果播放成功,Promise状态变成fulfilled,如果播放失败,状态变为rejected并提供错误信息。

var playPromise = document.querySelector('video').play();

// In browsers that don’t yet support this functionality,
// playPromise won’t be defined.
if (playPromise !== undefined) {
  playPromise.then(function() {
    // Automatic playback started!
  }).catch(function(error) {
    // Automatic playback failed.
    // Show a UI element to let the user manually start playback.
  });
}

video 412错误

412 一般是因为服务器的 If-Unmodified-Since 或 If-None-Match 未实现

// 解决办法
media.addEventListener('error', function (e) {
   var date = new Date();
   var milliSecs = date.getMilliseconds();
   var curr_src = $(media[0]).attr('src');
   var curr_src_arr = curr_src.split("?");
   var new_src = curr_src_arr[0]+"?t="+milliSecs;

   $(media[0]).attr('src',new_src);
   $(media[0]).find('source').attr('src',new_src);
   media[0].load();
}, false);

暂时完。

后续如果测试妹妹发现了什么bug,我会继续填坑记录滴。

原文链接:https://segmentfault.com/a/1190000016409976

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-40368.html

文章标题:音频组件开发实践

相关文章
详解angular2封装material2对话框组件
1. 说明 angular-material2自身文档不详,控件不齐,使用上造成了很大的障碍。这里提供一个方案用于封装我们最常用的alert和confirm组件。 2. 官方使用方法之alert ①编写alert内容组件 @Componen...
2017-03-13
Node.js v0.11.16 开发版发布
Node.js v0.11.16 开发版发布了,改进记录包括: openssl: Upgrade to 1.0.1l npm: Upgrade to 2.3.0 url: revert support of path for url.fo...
2015-11-12
JavaScript开发工具
常用的有sublime,webstorm,notepad++等 ...
2015-11-12
梳理前端开发使用eslint-prettier检查和格式化代码
问题痛点 在团队的项目开发过程中,代码维护所占的时间比重往往大于新功能的开发。因此编写符合团队编码规范的代码是至关重要的,这样做不仅可以很大程度地避免基本语法错误,也保证了代码的可读性。 对于代码版本管理系统(svn 和 git或者其他)...
2018-05-07
Webpack(含 4)配置详解——从 0 配置一套开发模板
前言 源代码 熟悉 webpack 与 webpack4 配置。 webpack4 相对于 3 的最主要的区别是所谓的零配置,但是为了满足我们的项目需求还是要自己进行配置,不过我们可以使用一些 webpack 的预设值。同时 webpack...
2018-05-02
JavaScript库开发者们的规则
保持无侵入性 我的HTML标记不想知道你的JavaScript代码。 严禁修改和扩展Object.prototype! 这条很重要,因此需要一条完全针对它的规则。对象是JavaScript功能的基本构建模块,不要搞乱它们。 不要...
2015-11-11
Vue 短信验证码组件开发详解
Vue.js(读音 /vjuː/, 类似于 view)是一个构建数据驱动的 web 界面的库。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。 Vue.js 自身不是一个全能框架——它只聚焦于视图层。因此...
2017-03-17
五个值得尝试的前端开发工具
在过去的几年时间里,出现了许多全新的网页应用程序,不过,由于应用程序的功能越来越丰富,也导致了前端开发的复杂度大幅增加。 现在也有不少前端开发工具,比如Backbone和EmberJS框架都能提供稳定的App开发解决方案。同时,Javasc...
2015-12-23
vuejs2.0实现分页组件使用$emit进行事件监听数据传递的方法
如果我有几个页面都需要有分页效果,不可能每个页面都去复制一下这段代码吧,意思是封装一下,变成通用的组件。 首先使用基础 Vue 构造器,创建一个“子类”,Vue.extend( options ) var barHtml = &#x27;&...
2017-03-15
使用AngularJS开发我们下一款Web应用的七个理由
在当下这个电子商务时代,每一家企业都热衷于通过网络拓展自身业务。而这也使Web开发人员市场呈现出前所未有的红火态势。根据最近发布的一份调查报告,全球网站总数已经超过8.76亿个,而且这一数字还在不断上升当中。市场上用于Web开发的平台亦多种...
2015-12-25
回到顶部