博客篇 -- Vuepress 音乐播放器 & 沉浸式
基于 vuepress v1.x (opens new window) ,接入第三方网易云音乐API库NeteaseCloudMusicApi (opens new window),纯手写一个导航栏音乐播放器,目前支持获取指定ID的 网易云歌单 (opens new window),随机播放,未来实现歌单列表。
先看下效果。

# Step 1 功能设计
# Step 2 集成环境
- NeteaseCloudMusicApi (opens new window)
- Vue2 (opens new window)
- iconfont (opens new window)
- Axios (opens new window)
- VuePress (opens new window)
- Html Audio组件 (opens new window)
# Step 3 部署网易云音乐API服务
方法1:不需要部署,直接请求网易云官方API 不推荐
'https://autumnfish.cn'
方法2:使用开源托管平台 Vercel 部署 推荐
Vercel开源免费,不需要自己搭建服务器。NeteaseCloudMusicApi v4.0.8增加了Vercel配置文件,部署方式简单。- 操作步骤
- 将
NeteaseCloudMusicApi源码克隆到自己的 GitHub 仓库。 - 注册
Vercel账号。 - 创建一个项目。
- 点击
Import Git Repository,将指定项目克隆到托管平台。 - 点击
Deploy就可以完成部署。
- 将
方法3:使用私有服务器部署不推荐
- 需要有一台私有服务器。
- 需要配置
NodeJs、Docker或npx环境。 - 需要配置
Nginx代理。
如需自行部署,点这里 (opens new window)
# Step 4 NavPlayer 开发
自行开发请忽略
# 页面及样式
<template>
<div class="nav-music" v-if="isPC"
:style="linksWrapOffsetWidth ? {'right': linksWrapOffsetWidth + 'px'} : {}">
<div class="img-box">
<img class="avatar" :class="{playing: isPlaying}"
:src="currentMusic? currentMusic.cover : ''"
/>
</div>
<!-- 播放时长 歌手-歌名 -->
<div class="actions">
<div class="title">
<span style="margin-right: 0.1rem">
{{currentMusic.currentTime == 0 ? '' : currentMusic.currentTime}}
</span>
<div class="title-name">
<span>
{{currentMusic.artist || ''}} {{currentMusic.name? ' - ':''}} {{ currentMusic.name || ''}}
</span>
</div>
</div>
<!-- 上一首、播放/暂停、下一首、音量-、音量+ 按钮-->
<div class="action-bar">
<!-- <i class="iconfont rays-switch" @click="next"></i> -->
<i class="iconfont rays-prev-face" @click="prev"></i>
<i v-if="!isPlaying" class="iconfont rays-play" @click="onPlay"></i>
<i v-if="isPlaying" class="iconfont rays-pause" @click="onPlay"></i>
<i class="iconfont rays-next-face" @click="next"></i>
<i v-if="currentMusic.volume <= 0" class="iconfont rays-mute" style="margin-left: 1rem"></i>
<i v-if="currentMusic.volume > 0" class="iconfont rays-volume-reduce" style="margin-left: 1rem" @click="onVolume('jian')"></i>
<span class="volume">{{parseInt(currentMusic.volume * 10)}}</span>
<i class="iconfont rays-volume-add" @click="onVolume('jia')"></i>
</div>
</div>
<!-- 引入Html Audio组件 -->
<audio ref="audio"
:autoplay="false"
:src="currentMusic.url"
:volume="currentMusic.volume"
@play="play"
@pause="pause"
@loadedmetadata="onLoadedmetadata"
@timeupdate="onTimeupdate"
@ended="onEnded"
></audio>
</div>
</template>
- <audio> 标签常用属性。
| 属性 | 定义 | 用途 |
|---|---|---|
autoplay | 自动播放 | 设置初次加载是否自动播放音频 |
src | 音频文件访问地址 | 加载音频文件 |
volume | 播放音量 | 设置播放音量 |
@play | 播放事件回调 | 切换播放状态图标 |
@pause | 暂停事件回调 | 切换播放状态图标 |
@loadedmetadata | 音频文件加载完毕回调 | 查询音频文件播放总时长(maxTime) |
@timeupdate | 播放时当前时间变更回调 | 监控剩余播放时长 注意:1s触发3~4次,需要节流 |
@ended | 播放结束回调 | 自动播放下一首 |
# 获取歌单列表
- 获取指定ID的歌单
- 通过歌单ID获取歌曲详情
/**
* @description: Add by RayShine 获取歌单列表
* @param {*} playlistId
* @return {*}
*/
getMusicList(playlistId = '144719593'){
let that = this
let sort = 0
// 获取音乐文件 /playlist/detail?id=123412
axios({
baseURL: that.$themeConfig.back.musicUrl,
url: "/playlist/detail?id=" + playlistId,
withCredentials: true // 跨域解决方案
}).then(function(response) {
if (response.status === 200 && response.data.code === 200) {
// 提取歌单中所有歌曲ID
let ids = response.data.playlist.trackIds.filter((track) => {
return track.id
}).map((track) => {
return track.id
}).join(',')
// 获取所有音乐详情 /song/detail?ids=111,111,111
// 记录一下获取歌词 /lyric?id=
axios({
baseURL: that.$themeConfig.back.musicUrl,
url: "/song/detail?ids=" + ids,
withCredentials: true
}).then(function(response) {
if (response.status === 200 && response.data.code === 200) {
// 提取需要展示的歌曲详情
that.musicList = response.data.songs.map((song) => {
// 提取音质列表
let brList = response.data.privileges.filter((privilege) => {
return privilege.id == song.id
}).map((br) => {
return br.chargeInfoList.map((chargeInfo) => {
return chargeInfo.rate
})
})[0]
return {
musicId: song.id, // 歌曲ID
name: song.name || '', // 歌曲名称
artist: song.ar[0].name || '',// 歌手名称
cover: song.al.picUrl || '', // 歌曲图片
brList, // 音质列表
sort: sort++
}
})
// 加载一首歌
that.getCurrentMusic('first')
}
}, function(err) {
console.log(err);
});
}
}, function(err) {
that.currentMusic.artist = '歌单获取失败'
console.log(err);
});
}
# 获取音频文件地址
歌曲详情 接口中没有音频文件地址,需要单独获取
- 检查歌曲是否可用
- 获取音频文件地址
- 如地址是
http转换为https - 自动播放设置
- 错误处理
/**
* @description: Add by RayShine 获取当前歌曲,支持自动播放
* @param {*} musicId
* @return {*}
*/
getMusic(musicId = '1868943615', br = 128000, type) {
let that = this
// 检查音乐是否可用 /check/music?id=123&br=128000
axios({
baseURL: that.$themeConfig.back.musicUrl,
url:"/check/music?id=" + musicId + '&br=' + br,
withCredentials: true
}).then(function(response) {
// 返回 { success: true, message: 'ok' } 或者 { success: false, message: '亲爱的,暂无版权' }
if (response.status === 200 && response.data.success) {
// 获取音乐文件 /song/url?id=123&br=128000
axios({
baseURL: that.$themeConfig.back.musicUrl,
url:"/song/url?id=" + musicId + '&br=' + 320000,
withCredentials: true
}).then(function(response) {
if (response.status === 200) {
// 替换 http 为 https
that.currentMusic.url = response.data.data[0].url.match('^http://') ? response.data.data[0].url.replace("http://","https://") : response.data.data[0].url;
that.$refs.audio.volume = that.defaultVolume
// 显示默认音量
that.currentMusic.volume = that.$refs.audio.volume
// 首次加载歌单,是否自动播放取决于用户设置
if ((type != 'first' || (type == 'first' && that.autoPlay)) && that.playHistory) setTimeout(() => { that.$refs.audio.play() }, 2000)
}
}, function(err) {
console.log(err);
});
}
}, function(err) {
that.currentMusic.artist = err.response.data.message
that.currentMusic.url = ''
that.currentMusic.name = ''
that.currentMusic.currentTime = ''
setTimeout(() => { that.next() }, 2000)
console.log(err);
});
}
# 播放/暂停
/**
* @description: Add by RayShine 播放与暂停切换 需要防抖
* @return {*}
*/
onPlay () {
return this.isPlaying ? this.$refs.audio.pause() : this.$refs.audio.play()
}
# 上一首
/**
* @description: Add by RayShine 切换下一首 需要防抖
* @param {*} e
* @return {*}
*/
next (e) {
// 暂停音乐
this.$refs.audio.pause()
// 获取歌曲
this.getCurrentMusic('next')
}
# 下一首
/**
* @description: Add by RayShine 切换上一首 需要防抖
* @param {*} e
* @return {*}
*/
prev (e) {
// 暂停音乐
this.$refs.audio.pause()
// 获取歌曲
this.getCurrentMusic('prev')
}
# 更新当前播放时间
/**
* @description: Add by RayShine 更新当前播放时间 需要节流
* @param {*} res
* @return {*}
*/
onTimeupdate(e) {
this.currentMusic.currentTime = this.transTime(this.currentMusic.duration - e.target.currentTime)
}
# 获取音频总时长
/**
* @description: Add by RayShine 获取音频总时长
* @return {*}
*/
onLoadedmetadata(e) {
this.currentMusic.duration = e.target.duration
this.currentMusic.maxTime = this.transTime(e.target.duration)
this.currentMusic.currentTime = this.currentMusic.maxTime
}
# 播放时间转换
/**
* @description: Add by RayShine 音频播放时间换算 返回 00:00
* @param {number} time - 音频当前播放时间,单位秒
* @return {*}
*/
transTime(time) {
var duration = parseInt(time)
var minute = parseInt(duration / 60)
var sec = (duration % 60) + ''
var isM0 = ':'
if (minute === 0) {
minute = '00'
} else if (minute < 10) {
minute = '0' + minute
}
if (sec.length === 1) {
sec = '0' + sec
}
return minute + isM0 + sec
}
# 音量控制
/**
* @description: Add by RayShine 声音控制器 需要防抖
* @param {*} e
* @return {*}
*/
onVolume(e) {
let currentVolume = parseInt(this.$refs.audio.volume * 10)
let step = this.volumeStep * 10
if (e === 'jian') {
if (currentVolume - step <= 0) {
this.$refs.audio.volume = 0
} else {
this.$refs.audio.volume = (parseInt(this.$refs.audio.volume * 10) - step) / 10
}
} else {
if (currentVolume + step >= 10 ) {
this.$refs.audio.volume = 1
}else {
this.$refs.audio.volume = (parseInt(this.$refs.audio.volume * 10) + step) / 10
}
}
this.currentMusic.volume = this.$refs.audio.volume
}
# 鸣谢
NeteaseCloudMusicApi (opens new window)
Vercel (opens new window)
虚心的小白菜 - audio标签的使用方式 (opens new window)