目录
本文中修改的原代码中的BUG:
修改方法:
本文案例代码仍有的BUG:(欢迎大家献计献策)
目标效果:
悦音player案例——效果展示视频:
更换的新接口/参数:
1.歌曲搜索接口:https://autumnfish.cn/cloudsearch
2.mv地址获取接口中:修改请求参数的mvid为mv
重点原理:
(1)Vue中 @play是绑定音频/视频播放事件
(2)Vue中 @pause是绑定音频/视频暂停事件
(on)play事件——音频/视频(audio/video)开始播放时触发
(on)pause事件——音频/视频(audio/video)暂停播放时触发
(3)v-bind——设置元素的属性(比如:src,title,class)
代码部分:
1.main.js(全是重点)
2.悦音player.html(部分重点,用{{}}渲染结构,传递参数,查看结构)
3.index.css(辅助作用)
4.vue.js(辅助作用)
安装Vue的方法 /获取vue.js文件的方法:
5.axios.min.js(辅助作用)
(1)可以用axios在线网址【要连网】:
(2)也可以下载axios.min.js本地文件:
本文中修改的原代码中的BUG:
原代码的BUG:如果退出MV遮罩层之前,不暂停MV的播放。则退出MV之后,依然会有MV中的声音继续播放。
修改方法:
在main.js中的methods中的隐藏MV部分:
//7.隐藏MV
hide: function () {
//先设置mvUrl为空,停止音乐播放
this.mvUrl = "";
this.isShow = false;//隐藏遮罩层
}
本文案例代码仍有的BUG:(欢迎大家献计献策)
如果在不暂停音乐播放的情况下,点击MV的播放按钮,会出现MV视频中的音乐和mp3格式的音乐一起播放。
目标效果:
1.【搜索音乐】在右上角的搜索框里面输入想搜索的音乐的信息(e.g.歌曲名称/歌手姓名...),按Enter回车键,在下面页面的左边就会出现对应的音乐
2.【播放音乐+获取歌曲封面图+获取歌曲评论+歌曲播放/暂停】点击对应的音乐前面的播放图标,即可播放该音乐。播放该音乐时,黑胶圆圈开始滚动,播放不同歌曲,圆圈里面的图片也不同。右边会出现热门评论。
3.如果该音乐右边有MV播放图标,可用点击该图标,播放MV
4.播放MV的时候如果想要退出,点击MV页面边上的黑色区域,即可退出
e.g.1初始状态:
e.g.2在初始状态基础上,搜索框输入:周杰伦
按Enter回车键:
e.g.3在e.g.2基础上,点《布拉格广场》这首歌前面的播放按钮:
点下面的播放条的||,即可暂停该音乐:
e.g.4在e.g.3基础上,点《布拉格广场》这首歌后面的MV播放按钮:
点MV边上的黑色区域:
即可退出MV:
悦音player案例——效果展示视频:
b站黑马Vue快速入门案例——悦听player
更换的新接口/参数:
1.歌曲搜索接口:https://autumnfish.cn/cloudsearch
之前的音乐搜索接口已经失效了
2.mv地址获取接口中:修改请求参数的mvid为mv
之前的MV地址接口:
mv地址获取
请求地址:https://autumnfish.cn/mv/url
请求方法:get
请求参数:id(
mv,为0表示没有mv)!!!需要修改此处mvid为mvmvid响应内容:mv的地址
重点原理:
(1)Vue中 @play是绑定音频/视频播放事件
(2)Vue中 @pause是绑定音频/视频暂停事件
(on)play事件——音频/视频(audio/video)开始播放时触发
(on)pause事件——音频/视频(audio/video)暂停播放时触发
(3)v-bind——设置元素的属性(比如:src,title,class)
语法 v-bind:属性名=表达式
简写【实际开发常用】 :属性名=表达式
e.g.用v-bind的简写,添加class类名active
【实际开发常用】【用对象的方式添加class类名】
:class=“{active:isActive}”【!!!有“”】
【active类名是否添加,取决于此时isActive是true还是false】
代码部分:
1.main.js(全是重点)
/*
1:歌曲搜索接口[搜索音乐]
之前的请求地址【已经失效了】:https://autumnfish.cn/search
最新请求地址:https://autumnfish.cn/cloudsearch
请求方法:get
请求参数:keywords(查询关键字)
响应内容:歌曲搜索结果
2:歌曲url获取接口[播放音乐]
请求地址:https://autumnfish.cn/song/url
请求方法:get
请求参数:id(歌曲id)
响应内容:歌曲url地址
3.歌曲详情获取[获取歌曲封面图]
请求地址:https://autumnfish.cn/song/detail
请求方法:get
请求参数:ids(歌曲id)
响应内容:歌曲详情(包括封面信息)
4.热门评论获取
请求地址:https://autumnfish.cn/comment/hot?type=0
请求方法:get
请求参数:id(歌曲id,地址中的type固定为0)
响应内容:歌曲的热门评论
5.mv地址获取
请求地址:https://autumnfish.cn/mv/url
请求方法:get
请求参数:id(mv,为0表示没有mv)
响应内容:mv的地址
*/
var app = new Vue({
el: "#player",
data: {
//查询关键字
query: "",
//musicList数组,用于存放音乐信息
musicList: [],
//存放歌曲的url地址
musicUrl: "",
//存放歌曲封面图片的url地址
musicCover: "",
//hotComments数组,用于存放歌曲评论相关的数组
hotComments: [],
//动画播放状态(默认不播放false)
isPlaying: false,
//遮罩层的显示状态(默认不显示false)
isShow: false,
//mv地址
mvUrl: ""
},
methods: {
//1.搜索音乐
searchMusic: function () {
// 因为this会变,所以用that存储此时的this,此时的this指当前对象#player
var that = this;
axios.get("https://autumnfish.cn/cloudsearch?keywords=" + this.query)
.then(
//(1)请求成功
function (response) {
//将返回的音乐信息数组,赋值给musicList数组
that.musicList = response.data.result.songs;
},
//(2)请求失败
function (err) { })
},
//2.播放音乐
playMusic: function (musicId) {//形参musicId接收 @click="playMusic(item.id)"传过来的item.id
// console.log(musicId);
// 因为this会变,所以用that存储此时的this,此时的this指当前对象#player
var that = this;
axios.get("https://autumnfish.cn/song/url?id=" + musicId)
.then(
//(1)请求成功
function (response) {
//将response.data.data[0].url获得的音乐的url,赋值给musicUrl
that.musicUrl = response.data.data[0].url;
},
//(2)请求失败
function (err) { })
//3.获取歌曲封面图
axios.get("https://autumnfish.cn/song/detail?ids=" + musicId)
.then(
//(1)请求成功
function (response) {
// 将response.data.songs[0].al.picUrl获得的音乐封面图片的url,赋值给musicCover
that.musicCover = response.data.songs[0].al.picUrl;
},
//(2)请求失败
function (err) { })
//4.获取歌曲评论
axios.get("https://autumnfish.cn/comment/hot?type=0&id=" + musicId)
.then(
//(1)请求成功
function (response) {
// 将response.data.hotComments获得的歌曲评论相关的数组,赋值给hotComments数组
that.hotComments = response.data.hotComments;
},
//(2)请求失败
function (err) { })
},
//5.歌曲播放时的动画
//(1)歌曲播放
play: function () {
this.isPlaying = true;
},
//(2)歌曲暂停
pause: function () {
this.isPlaying = false;
},
//6.播放mv
playMV: function (vid) {//形参vid接收 @click="playMV(item.mv)"中的每个mv对应的id item.mv
// 因为this会变,所以用that存储此时的this,此时的this指当前对象#player
var that = this;
axios.get("https://autumnfish.cn/mv/url?id=" + vid)
.then(
//(1)请求成功
function (response) {
// console.log(vid);
that.isShow = true;//显示遮罩层
//将返回的每个mv对应的url,赋值给mvUrl
that.mvUrl = response.data.data.url;
},
//(2)请求失败
function (err) { })
},
//7.隐藏MV
hide: function () {
//先设置mvUrl为空,停止音乐播放
this.mvUrl = "";
this.isShow = false;//隐藏遮罩层
}
}
})
2.悦音player.html(部分重点,用{{}}渲染结构,传递参数,查看结构)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>悦听player</title>
<!-- 样式 -->
<link rel="stylesheet" href="./css/index.css">
</head>
<body>
<div class="wrap">
<!-- 播放器主体区域 -->
<div class="play_wrap" id="player">
<div class="search_bar">
<img src="images/player_title.png" alt="" />
<!-- 搜索歌曲 -->
<!-- v-model="query"将搜索文本框和query数据双向绑定 -->
<!-- @keyup.enter="searchMusic"当键盘Enter回车键弹起,调用searchMusic事件 -->
<input type="text" autocomplete="off" v-model="query" @keyup.enter="searchMusic" />
</div>
<div class="center_con">
<!-- 搜索歌曲列表 -->
<div class='song_wrapper'>
<ul class="song_list">
<!-- v-for="item in musicList"指遍历 音乐信息列表musicList数组里的每一个数据item -->
<li v-for="item in musicList">
<!-- @click="playMusic(item.id)"指 点击播放按钮,调用playMusic方法,并传递每个音乐信息item的id -->
<a href="javascript:;" @click="playMusic(item.id)"></a>
<!-- {{item.name}}中是 音乐信息中的音乐名称 -->
<b>{{item.name}}</b>
<!-- mv播放按键 -->
<!-- v-if="item.mv!=0"指 当音乐信息列表musicList数组里的每一个数据item中,mv!=0时,mv播放按键显示 -->
<!-- @click="playMV(item.mv)"指 当点击mv播放按键时,调用playMV方法,并传入每个mv对应的id item.mv -->
<span v-if="item.mv!=0" @click="playMV(item.mv)"><i></i></span>
</li>
</ul>
<img src="images/line.png" class="switch_btn" alt="">
</div>
<!-- 歌曲黑胶部分容器 -->
<!-- :class="{playing:isPlaying}"指 用v-bind的简写,给黑胶部分容器div加类名playing -->
<!-- 且类名playing是否添加,取决于isPlaying的值是true还是false-->
<div class="player_con" :class="{playing:isPlaying}">
<img src="images/player_bar.png" class="play_bar" />
<!-- 黑胶碟片 -->
<img src="images/disc.png" class="disc autoRotate" />
<!-- 歌曲的封面图片 -->
<!-- :src="musicCover"指 给装音乐封面图的img标签添加src属性,调用musicCover中的音乐封面图的url -->
<img :src="musicCover" class="cover autoRotate" />
</div>
<!-- 评论容器 -->
<div class="comment_wrapper">
<h5 class='title'>热门留言</h5>
<div class='comment_list'>
<!-- v-for="item in hotComments"指 遍历存放存放歌曲评论相关的数组hotComments中的每一项item -->
<dl v-for="item in hotComments">
<!-- 用户头像 -->
<!-- :src="item.user.avatarUrl"指 给给装用户头像图的img标签添加src属性,调用音乐信息列表musicList数组里的每一个数据item中用户头像的url -->
<dt><img :src="item.user.avatarUrl" alt=""></dt>
<!-- 用户昵称 -->
<!-- {{item.user.nickname}}中是 用户昵称 -->
<dd class="name">{{item.user.nickname}}</dd>
<!-- 用户评论的内容 -->
<!-- {{item.content}}中是 用户评论的内容 -->
<dd class="detail">{{item.content}}</dd>
</dl>
</div>
<img src="images/line.png" class="right_line">
</div>
</div>
<div class="audio_con">
<!-- :src='MusicUrl'指 给播放音乐的audio标签添加src属性,调用musicUrl中的音乐的url -->
<!-- @play="play"指 给播放音乐的audio标签,绑定play播放事件,调用play方法 -->
<!-- @pause="pause"指 给播放音乐的audio标签,绑定pause暂停事件,调用pause方法 -->
<audio ref='audio' :src='musicUrl' @play="play" @pause="pause" controls autoplay loop class="myaudio"></audio>
</div>
<!-- MV -->
<!-- v-show="isShow"指 该遮罩层的显示/隐藏,取决于isShow的值是true/false -->
<!-- isShow的值是true,则显示遮罩层;isShow的值是false,则隐藏遮罩层 -->
<div class="video_con" v-show="isShow" style="display: none;">
<!-- :src="mvUrl"指 给video标签设置src属性,且对应的src属性值是mvUrl里的值 -->
<video :src="mvUrl" controls="controls"></video>
<!-- mv遮罩层 -->
<!-- @click="hide"指 点击mv遮罩层,调用hide方法 -->
<div class="mask" @click="hide"></div>
</div>
</div>
</div>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="../vue.js"></script>
<!-- 官网提供的 axios 在线地址 -->
<script src="../axios.min.js"></script>
<!-- 自己的js文件 -->
<script src="./js/main.js"></script>
</body>
</html>
3.index.css(辅助作用)
body,
ul,
dl,
dd {
margin: 0px;
padding: 0px;
}
.wrap {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: url("../images/bg.jpg") no-repeat;
background-size: 100% 100%;
}
.play_wrap {
width: 800px;
height: 544px;
position: fixed;
left: 50%;
top: 50%;
margin-left: -400px;
margin-top: -272px;
/* background-color: #f9f9f9; */
}
.search_bar {
height: 60px;
background-color: #1eacda;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
z-index: 11;
}
.search_bar img {
margin-left: 23px;
}
.search_bar input {
margin-right: 23px;
width: 296px;
height: 34px;
border-radius: 17px;
border: 0px;
background: url("../images/zoom.png") 265px center no-repeat
rgba(255, 255, 255, 0.45);
text-indent: 15px;
outline: none;
}
.center_con {
height: 435px;
background-color: rgba(255, 255, 255, 0.5);
display: flex;
position: relative;
}
.song_wrapper {
width: 200px;
height: 435px;
box-sizing: border-box;
padding: 10px;
list-style: none;
position: absolute;
left: 0px;
top: 0px;
z-index: 1;
}
.song_stretch {
width: 600px;
}
.song_list {
width: 100%;
overflow-y: auto;
overflow-x: hidden;
height: 100%;
}
.song_list::-webkit-scrollbar {
display: none;
}
.song_list li {
font-size: 12px;
color: #333;
height: 40px;
display: flex;
flex-wrap: wrap;
align-items: center;
width: 580px;
padding-left: 10px;
}
.song_list li:nth-child(odd) {
background-color: rgba(240, 240, 240, 0.3);
}
.song_list li a {
display: block;
width: 17px;
height: 17px;
background-image: url("../images/play.png");
background-size: 100%;
margin-right: 5px;
box-sizing: border-box;
}
.song_list li b {
font-weight: normal;
width: 122px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.song_stretch .song_list li b {
width: 200px;
}
.song_stretch .song_list li em {
width: 150px;
}
.song_list li span {
width: 23px;
height: 17px;
margin-right: 50px;
}
.song_list li span i {
display: block;
width: 100%;
height: 100%;
cursor: pointer;
background: url("../images/table.png") left -48px no-repeat;
}
.song_list li em,
.song_list li i {
font-style: normal;
width: 100px;
}
.player_con {
width: 400px;
height: 435px;
position: absolute;
left: 200px;
top: 0px;
}
.player_con2 {
width: 400px;
height: 435px;
position: absolute;
left: 200px;
top: 0px;
}
.player_con2 video {
position: absolute;
left: 20px;
top: 30px;
width: 355px;
height: 265px;
}
.disc {
position: absolute;
left: 73px;
top: 60px;
z-index: 9;
}
.cover {
position: absolute;
left: 125px;
top: 112px;
width: 150px;
height: 150px;
border-radius: 75px;
z-index: 8;
}
.comment_wrapper {
width: 180px;
height: 435px;
list-style: none;
position: absolute;
left: 600px;
top: 0px;
padding: 25px 10px;
}
.comment_wrapper .title {
position: absolute;
top: 0;
margin-top: 10px;
}
.comment_wrapper .comment_list {
overflow: auto;
height: 410px;
}
.comment_wrapper .comment_list::-webkit-scrollbar {
display: none;
}
.comment_wrapper dl {
padding-top: 10px;
padding-left: 55px;
position: relative;
margin-bottom: 20px;
}
.comment_wrapper dt {
position: absolute;
left: 4px;
top: 10px;
}
.comment_wrapper dt img {
width: 40px;
height: 40px;
border-radius: 20px;
}
.comment_wrapper dd {
font-size: 12px;
}
.comment_wrapper .name {
font-weight: bold;
color: #333;
padding-top: 5px;
}
.comment_wrapper .detail {
color: #666;
margin-top: 5px;
line-height: 18px;
}
.audio_con {
height: 50px;
background-color: #f1f3f4;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
.myaudio {
width: 800px;
height: 40px;
margin-top: 5px;
outline: none;
background-color: #f1f3f4;
}
/* 旋转的动画 */
@keyframes Rotate {
from {
transform: rotateZ(0);
}
to {
transform: rotateZ(360deg);
}
}
/* 旋转的类名 */
.autoRotate {
animation-name: Rotate;
animation-iteration-count: infinite;
animation-play-state: paused;
animation-timing-function: linear;
animation-duration: 5s;
}
/* 是否正在播放 */
.player_con.playing .disc,
.player_con.playing .cover {
animation-play-state: running;
}
.play_bar {
position: absolute;
left: 200px;
top: -10px;
z-index: 10;
transform: rotate(-25deg);
transform-origin: 12px 12px;
transition: 1s;
}
/* 播放杆 转回去 */
.player_con.playing .play_bar {
transform: rotate(0);
}
/* 搜索历史列表 */
.search_history {
position: absolute;
width: 296px;
overflow: hidden;
background-color: rgba(255, 255, 255, 0.3);
list-style: none;
right: 23px;
top: 50px;
box-sizing: border-box;
padding: 10px 20px;
border-radius: 17px;
}
.search_history li {
line-height: 24px;
font-size: 12px;
cursor: pointer;
}
.switch_btn {
position: absolute;
right: 0;
top: 0;
cursor: pointer;
}
.right_line {
position: absolute;
left: 0;
top: 0;
}
.video_con video {
position: fixed;
width: 800px;
height: 546px;
left: 50%;
top: 50%;
margin-top: -273px;
transform: translateX(-50%);
z-index: 990;
}
.video_con .mask {
position: fixed;
width: 100%;
height: 100%;
left: 0;
top: 0;
z-index: 980;
background-color: rgba(0, 0, 0, 0.8);
}
.video_con .shutoff {
position: fixed;
width: 40px;
height: 40px;
background: url("../images/shutoff.png") no-repeat;
left: 50%;
margin-left: 400px;
margin-top: -273px;
top: 50%;
z-index: 995;
}
4.vue.js(辅助作用)
因为该文件内容太多,请前往该网址(Vue官网)下载
安装 — Vue.js
安装Vue的方法 /获取vue.js文件的方法:
5.axios.min.js(辅助作用)
(1)可以用axios在线网址【要连网】:
<!-- 官网提供的 axios 在线地址 -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
(2)也可以下载axios.min.js本地文件:
去github下载本地文件 网址GitHub - axios/axios: Promise based HTTP client for the browser and node.js
解压axios-1.x文件夹:
此处使用里面的axios.min.js