最近应该发现,我的博客https://blog.codelabo.cn左下角多了一个音乐播放器
这个是怎么实现的?一起来看看吧
APlayer
首先我们需要一个音频播放器,这里我用到了APlayer,这是由bilibili前端大神DIYgod开源的播放器,有兴趣的可以去TA的主页看看,非常惊艳,这里我就不多说了
我们看一下APlayer
的官方文档,方法很简单
<link rel="stylesheet" href="APlayer.min.css">
<div id="aplayer"></div>
<script src="APlayer.min.js"></script>
const ap = new APlayer({
container: document.getElementById('aplayer'),
audio: [{
name: 'name',
artist: 'artist',
url: 'url.mp3',
cover: 'cover.jpg'
}]
});
这里的audio
是一个音频列表,可以是一个对象或对象数组
对象具体的参数如下
名称 | 描述 |
---|---|
name | 音频名称 |
artist | 音频艺术家 |
url | 音频链接 |
cover | 音频封面 |
lrc | 音频歌词 |
LRC一共有三种方式来给 APlayer
传递歌词,详情可参考https://aplayer.js.org/#/home?id=lrc,
这里我们选择最方便的一种,直接给LRC链接
网易云音乐API
这部分我找到了网上有人分享的API,这种官方不可能给公开API,所以还是要小心使用,说不定哪天就被修改了
我们现在其实想要两个,一个是歌单的列表,还有一个是歌词。
# 歌单
https://music.163.com/api/playlist/detail?id=37880978
id为歌单ID
# 歌词
https://music.163.com/api/song/lyric?os=pc&id=93920&lv=-1&kv=-1&tv=-1
id为歌曲ID
lv:值为-1,我猜测应该是判断是否搜索lyric格式
kv:值为-1,这个值貌似并不影响结果,意义不明
tv:值为-1,是否搜索tlyric格式
接口实现
虽然我们已经找到了网易云音乐API,但是返回的数据不是我们所需要的呀
比如这个歌单的接口
# Request
https://music.163.com/api/playlist/detail?id=2119983629
# Response
{
"result":{
"subscribers":[],
"subscribed": false,
"creator":{...},
"artists": null,
"tracks":[
{
album: {
name: "メトロノーム",
id: 36787278, type: "专辑",
size: 12, picId:18419018788768520,
}
alias: [],
artists: [{name: "MACO", id: 901025, picId: 0, img1v1Id: 0, briefDesc: "",…}],
audition: null,
bMusic: {...},
commentThreadId: "R_SO_4_515573221",
copyFrom: "",
copyright: 1,
copyrightId: 7003,
crbt: null,
...
}
]
}
}
里面字段很多,我上面只列举了一部分,tracks
就是歌单列表,但是很显然,和我们需要的格式还差很多
那么怎么来转换一下,变成我们需要的数据格式呢?
[{
name: 'name',
artist: 'artist',
url: 'url.mp3',
cover: 'cover.jpg',
lrc: 'a.lrc'
}]
这里我们就需要在服务端来完成了,思路很简单,在服务器上请求https://music.163.com/api/playlist/detail?id=2119983629
这个接口,然后拿到结果后手动处理一下,最后再返给客户端,相当于做了一次中转
我这里服务端是用koa
实现的,其他框架应该差不多
服务端发起请求
在服务端发起请求也可以用我们熟悉的fetch
,不过你需要先安装node-fetch
这个库
yarn add node-fetch
然后你就可以像前端一样发起请求了
const fetch = require('node-fetch');
//...
const getPlayList = (id) => {
return fetch(`http://music.163.com/api/playlist/detail?id=${id}`)
.then((response) => {
if (response.ok) {
return response.json();
}
})
.catch((err) => {
console.warn(err);
})
}
接口定义
现在我们需要新增一个接口用来处理歌单,返回出我们需要的格式
//获取音乐列表
router.get('/playlist/:id', async (ctx, next) => {
const responseData = {
"success": false,
"data":[],
"message": "",
}
const { id } = ctx.params;
try {
const data = await getPlayList(id);
if(data.code===200){
const playList = data.result.tracks.map(item=>({
id: item.id,
name: item.name,
artist: item.artists.map(el=>el.name).join(','),//由于歌手是一个数组,这里我们把它转换成字符串拼接
url: `https://music.163.com/song/media/outer/url?id=${item.id}.mp3`,//歌曲地址
cover: item.album.picUrl.replace(/http:/,'https:'),
lrc:null
}))
responseData.success = true;
responseData.message = '操作成功';
responseData.data = playList;
ctx.body = responseData;
}
} catch (error) {
responseData.success = false;
responseData.message = error.message;
responseData.data = [];
ctx.body = responseData;
}
});
注意这里的歌曲链接url
,本来返回信息里面是不包含的,只有歌曲ID
,不过我们发现通过https://music.163.com/song/media/outer/url?id=ID
可以直接在线播放指定ID的歌曲,所以我们这里直接写在返回结果上。
歌词处理
还有一个问题就是歌词,上面的接口中,歌词返回结果也不是我们需要的格式
# Request
https://music.163.com/api/song/lyric?os=pc&id=93920&lv=-1&kv=-1&tv=-1
# Response
{
"sgc": true,
"sfy": false,
"qfy": false,
"lrc": {
"version": 7,
"lyric": "[00:29.620]细雨带风湿透黄昏的街道\n[00:35.050]抹去雨水双眼无帮地仰望\n[00:40.240]望向孤单的晚灯是那伤感的记忆\n[00:48.630]再次泛起心里无数的思念\n[00:54.000]以往片刻欢笑仍挂在脸上\n[00:58.770]愿你此刻可会知是我衷心的说声\n[01:06.310]喜欢你\n[01:08.940]那双眼动人笑声更迷人\n[01:14.330]愿再可轻抚你那可爱面容\n[01:22.490]挽手说梦话象昨天你共我\n[01:42.970]满带理想的我曾经多冲动\n[01:48.340]埋怨与她相爱难有自由\n[01:53.040]愿你此刻可会知是我衷心的说声\n[02:00.420]喜欢你\n[02:03.230]那双眼动人笑声更迷人\n[02:08.540]愿再可轻抚你那可爱面容\n[02:16.750]挽手说梦话象昨天你共我\n[02:24.740]每晚夜里自我独行\n[02:27.670]随处荡 多冰冷\n[02:35.070]以往为了自我挣扎从不知她的痛苦\n[02:49.380]喜欢你\n[02:52.020]那双眼动人笑声更迷人\n[02:57.420]愿再可轻抚你那可爱面容\n[03:05.590]挽手说梦话象昨天你共我\n[03:13.870]挽手说梦话象昨天你共我\n"
},
"klyric": {...},
"code": 200
}
反正就是很全面,但是我们需要的仅仅是里面的内容部分,比如上面我就只需要这一段
[00:29.620]细雨带风湿透黄昏的街道
[00:35.050]抹去雨水双眼无帮地仰望
[00:40.240]望向孤单的晚灯是那伤感的记忆
[00:48.630]再次泛起心里无数的思念
[00:54.000]以往片刻欢笑仍挂在脸上
[00:58.770]愿你此刻可会知是我衷心的说声
...
所以我们需要再次做一个中介处理
const getLyric = (id) => {
return fetch(`http://music.163.com/api/song/lyric?os=pc&id=${id}&lv=-1&kv=-1&tv=-1`)
.then((response) => {
if (response.ok) {
return response.json();
}
})
.catch((err) => {
console.warn(err);
})
}
//获取音乐歌词
router.get('/lyric/:id', async (ctx, next) => {
const { id } = ctx.params;
try {
const lyric = await getLyric(id);
ctx.body = lyric.lrc.lyric;//返回指定部分
} catch (error) {
ctx.body = '';
}
});
这样在上面歌词列表中就可以直接用/api/lyric/:ID
来获取歌词了
//...
{
id: item.id,
name: item.name,
artist: item.artists.map(el=>el.name).join(','),
url: `https://music.163.com/song/media/outer/url?id=${item.id}.mp3`,
cover: item.album.picUrl.replace(/http:/,'https:'),
lrc:`/api/lyric/${item.id}`//这里歌词写上我们定义的接口地址
}
//...
测试一下吧
通过以上处理,我们接口就返回我们自定义的数据格式了
# Request
https://localhost:3000/api/playlist/2119983629
# Response
{
"success": true,
"data": [
{
"id": 515573221,
"name": "Sweet Memory",
"artist": "MACO",
"url": "https://music.163.com/song/media/outer/url?id=515573221.mp3",
"cover": "https://p1.music.126.net/-U7mfaIjENUu8G_O0Dhv8g==/18419018788768520.jpg",
"lrc": "/api/lyric/515573221"
},
{
"id": 488388942,
"name": "願い~あの頃のキミへ~",
"artist": "當山みれい",
"url": "https://music.163.com/song/media/outer/url?id=488388942.mp3",
"cover": "https://p1.music.126.net/kbLlBkGfEcA3RJyC5JhkDA==/18346451021830743.jpg",
"lrc": "/api/lyric/488388942"
},
...
],
"message": "操作成功"
}
添加到博客
其实上面对接口的数据改造才是关键,下面添加到自己的页面就很简单了。
如果你是传统HTML页面,可以直接文章开头的方式引用
<link rel="stylesheet" href="APlayer.min.css">
<div id="aplayer"></div>
<script src="APlayer.min.js"></script>
const ap = new APlayer({
container: document.getElementById('aplayer'),
audio: [{
name: 'name',
artist: 'artist',
url: 'url.mp3',
cover: 'cover.jpg'
}]
});
如果使用了使用模块管理器:
import 'APlayer/dist/APlayer.min.css';
import APlayer from 'APlayer';
const ap = new APlayer(options);
如果是react
项目,那么可以用封装好的react-aplayer
import React from 'react';
import ReactAplayer from 'react-aplayer';
export default class App extends React.Component {
// event binding example
onPlay = () => {
console.log('on play');
};
onPause = () => {
console.log('on pause');
};
// example of access aplayer instance
onInit = ap => {
this.ap = ap;
};
render() {
const props = {
theme: '#F57F17',
lrcType: 3,
audio: [
{
name: '光るなら',
artist: 'Goose house',
url: 'https://moeplayer.b0.upaiyun.com/aplayer/hikarunara.mp3',
cover: 'https://moeplayer.b0.upaiyun.com/aplayer/hikarunara.jpg',
lrc: 'https://moeplayer.b0.upaiyun.com/aplayer/hikarunara.lrc',
theme: '#ebd0c2'
}
]
};
return (
<div>
<ReactAplayer
{...props}
onInit={this.onInit}
onPlay={this.onPlay}
onPause={this.onPause}
/>
{/* example of access aplayer instance API */}
<button onClick={() => this.ap.toggle()}>toggle</button>
<div>
);
}
}
如果是vue
项目,可以使用vue-aplayer
<aplayer autoplay
:music="{
title: 'secret base~君がくれたもの~',
artist: 'Silent Siren',
src: 'https://moeplayer.b0.upaiyun.com/aplayer/secretbase.mp3',
pic: 'https://moeplayer.b0.upaiyun.com/aplayer/secretbase.jpg'
}"
/>
其他更多可以参考Aplayer生态
小节
这里的歌单,我选择了自己收藏的歌曲。每次用网易云音乐客户端播放听歌的时候,收藏的歌曲,在我的博客上也可以同步进行更新。
差不多就这些了,可能对于专业后端开发来说,这些完全就是小学生操作,但是对于一个前端来说,做这些事就感觉闯入了一片新天地,还是有很多感悟的。很多以前前端做不了的事,现在nodeJS
也能帮我们解决,进一步打通了前后端的天然屏障,离全栈也越来越近了 ^ ^
大家如果喜欢我的博客,可以多多关注一下
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。