5

开发一个web音乐播放器到底有多难?

 2 years ago
source link: https://jw1.dev/2019/08/22/a01.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

开发一个web音乐播放器到底有多难?

作者: 一个球 发布时间: 2019-08-22 上次修改:2021-12-14

前段时间比较闲,顺便就开发了自己的音乐主页👉https://music.jw1.dev (已下线),开发过程中遇到不少问题,一度以为解决不了,但是最后看来,其实实现都非常简单。

首先看看html吧

<audio src="path/to/your/audio/file"></audio>

<!-- 或者 -->
<audio>
<source src="path/to/your/audio/file" type="audio/mpeg">
</audio>

这时候打开页面你会发现…什么都没有。

<audio src="path/to/your/audio/file" controls></audio>

加上controls这个属性,浏览器才会显示原生的音频组件。至于浏览器兼容性我就不讲了,还是老样子扔个MDN链接在这里😁:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio#Browser_compatibility

所以,搞定了?

不!像我这种有强迫症的人,怎么可能让每个浏览器的显示都不一致!

要做的第一件事情就是,隐藏audio的原生控制组件:

<audio src="path/to/your/audio/file"></audio>
<!-- 去掉 controls -->

(👆三遍了🤣

然后用CSS将audio 移出文档流:

audio{position:fixed;top:0;left:-12138px;}

为什么要这么做?

—— 为了避免一些非主流的浏览器会让audio在没有controls属性的情况下出现在不应该出现的地方。(好累

接下来看JS的部分

let audio = document.querySelector('audio')

// 或者用jQuery
let audio = $('audio')

console.log(audio)

原生JavaScript在log下只能看到标签,而jQuery则会把所有和这个元素有关的东西都列出来,不论是有用的还是没有用的。但是知道有那么多属性/事件可以用是件好事,在这里说几样比较重要的。

接下来js区域中的audio都是以原生js获取的对象,如果你使用jQuery,audio赋值应该为

let audio = $($('audio')[0])

audio.play()

可以调用此方法来播放音频。因为市面上大多数浏览器不允许直接用js触发音乐播放,必须要由用户来触发该事件(‘click’, ‘scroll’, ‘focus’, etc.),所以我们可以加一个播放按钮来触发事件:

<audio src="path/to/your/audio/file"></audio>

<button class="play">play</button>
// 获取play button
let playBtn = document.querySelector('.play');

// 给play button添加点击事件
playBtn.addEventListener('click',function(){

// play button 点击后播放音频
audio.play()
})

audio.pause() & audio.paused

调用audio.pause()来暂停音频。

调用audio.paused来获取音频暂停状态,true为暂停状态,false则为播放状态。

比如我现在需要再点击一次play button来暂停音频,我们可以这样做:

playBtn.addEventListener('click',function(){

// 点击后判断音频是否为暂停状态
if(audio.paused){

// 如果audio.paused为true
// 则音频为暂停状态
// 这时候我们就要播放音频
// 同时修改button的文字为pause
audio.play()
playBtn.innerHTML = 'pause'
}else{

// 如果audio.paused为false
// 则音频为播放状态
// 这时候我们就要暂停音频
// 同时修改button的文字为play
audio.pause()
playBtn.innerHTML = 'play'
}
})

audio.currentTime

调用此属性获取音频当前已播放时间。

修改此属性可以达到切换音频当前播放位置的功能:

// 快进十秒
fastForward = function(){
audio.currentTime = audio.currentTime + 10
}
fastForward()

audio.duration

调用此属性获取音频的长度,单位为秒。

已知问题

  1. Firefox上会出现读不到duration的情况,可能和修改audio.src有关,我在自己项目中的做法是使用了ffmpeg读取出duration,直接当作变量使用,没有使用浏览器给的这个属性。

audio.ontimeupdate

此方法在音频播放期间会循环调用,经测试,调用间隔大约为200ms一次。

此方法可以用在更新音频进度条上。有了上面的audio.currentTimeaudio.duration,我们可以算出当前音频播放进度的百分比:

// 将百分比定为全局变量
let audioProgressPercent = 0;
audio.ontimeupdate = function(){

// 在audio时间更新的时候计算当前进度百分比
audioProgressPercent = audio.currentTime / audio.duration
console.log(audioProgressPercent)
}

这个时候如果你打开页面开始播放音频,就会看到控制台一直在更新进度百分比了,至于这个数值怎么用,不用我教大家了吧。😁

audio.buffered

调用此属性可以获取当前音频的缓冲进度和区间:

// 每200ms获取一次缓冲区间
let bufferedTimeInterval = setInterval(function(){

// 缓冲可以有多个区间
// 所以我们尽量都获取到
let bufferedLength = audio.buffered.length

// 以bufferedLength做循环
for (let i = 0; i < bufferedLength; i++) {
let bufferedStart = audio.buffered.start(i)
let bufferedEnd = audio.buffered.end(i)
console.log(bufferedStart, bufferedEnd)
}
},200)

控制台输出:

> 0 17.64
56.45 78.90

输出的含义为:

这一次读取audio.buffered得到了两个区间,所以循环了两次。

第一次获得的区间是[0,17.64],代表第0秒到第17.64秒之间的音频已经缓冲好了,可以直接播放。

第二次获得的区间是[56.45,78.90],代表这两个数字之间的音频已经缓冲好了,可以直接播放。

audio.onwaiting & audio.onplay

audio.onwaiting会在音频开始缓冲的时候被激活;

audio.onplay会在音频开始播放的时候被激活;

这时候我们可以做一个loading的动画,告诉用户音频正在加载了:

audio.onwaiting = function () {
console.log('audio is loading')
// 在这里激活loading动画
}
audio.onplay = function () {
console.log('audio is playing')
// 在这里关闭loading动画
}

audio.ended & audio.src

audio.ended顾名思义,会在音频结束后被激活;

audio.src是音频文件的位置,可修改;

当你有一个播放列表,且想在音频结束后播放下一首歌,你就可以:

// 定义播放列表
let playList = [
'path/to/song1',
'path/to/song2',
'path/to/song3'
]

// 定义当前播放歌曲的ID
let currentSongID = 0;

// 音频结束后自动播放下一首
audio.onended = function(){

// 更新当前播放歌曲的ID
currentSongID = currentSongID + 1

// 如果当前播放歌曲为播放列表中最后一首
// 就从第一首开始放
if(currentSongID >= playList.length){
currentSongID = 0
}

// 改变audio的src属性
audio.src = playList[currentSongID]
audio.play()
}

audio.volume

调用获取音频的音量,可修改;

可以做点fade in / fade out的效果,使用jQuery可以很简单的做出来,原文中也有原生js的实现方法(太长没看

$audio.animate({volume: newVolume}, 1000)

原文链接:https://stackoverflow.com/questions/7451508/html5-audio-playback-with-fade-in-and-fade-out

好了,以上就是我开发音乐主页之后想和大家分享的。

至于题中的问题,我的答案是:真的很简单。

只是js有一些性能上的限制,不能什么功能都往网页上搬…

在我的音乐主页中有一个砍掉的功能

—— 节拍器

为啥砍掉了呢…

因为js的处理效率真的不高(或者只是我太辣鸡),中低端手机做出来的节拍器根本不准,至于实现原理,很简单:

歌曲的bpm(beat per minute)是已知的:

// 假设现在我有一首歌bpm为120
let bpm = 120

// 每40毫秒更新一次,目的是让bpm更新不低于24fps
// 然而中低端手机的效果却不尽人意
// pc端倒是没啥问题
let bpmTimeInterval = setInterval(function(){
// 计算每秒有多少beats
let interval = bpm / 60

// _b会在时间线呈现出一个锯齿状的图形
// 最高那个点,也就是齿尖,就是一个beat
let _b = audio.currentTime % interval
// ...
},40)

希望有一天js能有开发Digital Audio Workstation的能力!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK