uni-app中实现音乐播放器
1、主要利用的是uni-app中提供的uni.createInnerAudioContext()来进行实现;
2、代码示例
(1)主页面代码展示
<template>
<view class="music-layout">
<view class="tn-flex">
<view class="left-content">
<view class="left-pic-layout">
<img :src="bgUrl" :class="isPlay ? 'img-rotate' : ''">
<view class="small-circle"></view>
</view>
<view class="like-layout">
<text class="tn-icon-like-fill" v-if="musicDetail.liked" @click="onLikeMusic(false, musicDetail)"></text>
<text class="tn-icon-like" v-else @click="onLikeMusic(true, musicDetail)"></text>
</view>
</view>
<view class="right-content">
<view class="song-name-layout">
<view>{{ musicDetail.bsmb002 }}</view>
<view v-if="isPlay"><PlayerAnimation></PlayerAnimation></view>
</view>
<view class="progress-layout">
<text>{{ formatTime(musicCurrentTime) }}</text>
<tn-slider
:min="0"
:max="musicTotalTime"
class="progress"
v-model="musicCurrentTime"
inactiveColor="#EAEAEA"
activeColor="#FF3370"
:blockWidth="1"
:lineHeight="4">
</tn-slider>
<text>{{formatTime(musicTotalTime)}}</text>
</view>
<view class="actions-layout">
<view class="toggle-type-layout" @click="handleToggleType">
<img src="http://192.168.101.152:8848/commonStatic/ckzs/fetusMovement/loop-black-icon.png" v-if="toggleType == 1" >
<img src="http://192.168.101.152:8848/commonStatic/ckzs/fetusMovement/single-black-icon.png" v-else-if="toggleType == 2">
<img src="http://192.168.101.152:8848/commonStatic/ckzs/fetusMovement/random-black-icon.png" v-else>
</view>
<view class="action-center">
<img src="http://192.168.101.152:8848/commonStatic/ckzs/fetusMovement/previous-icon.png" class="previous-icon" @click="handlePreviousPlay">
<img src="http://192.168.101.152:8848/commonStatic/ckzs/fetusMovement/play-icon.png" v-if="isPlay" class="player-icon" @click="handlePlay(false)">
<img src="http://192.168.101.152:8848/commonStatic/ckzs/fetusMovement/stop-icon.png" v-else class="player-icon" @click="handlePlay(true)">
<img src="http://192.168.101.152:8848/commonStatic/ckzs/fetusMovement/next-icon.png" class="next-icon" @click="handleNextPlay">
</view>
<img src="http://192.168.101.152:8848/commonStatic/ckzs/fetusMovement/music-list.png" class="music-list-icon" @click="handleMusicListShow">
</view>
</view>
</view>
<view class="add-music" @click="handleAddWisk">
点这里添加您的音乐心愿单~
</view>
</view>
</template>
<script>
import { formatTime } from '@/utils/util'; // 代码在下方对应文件中
import PlayerAnimation from "../../../components/player-animation/player-animation.vue"; // 播放效果动画(代码在下方对应文件中)
import { musicList, saveCollect, cancleCollect } from "@/api/fetusMovement"; // 接口
import { mediaUrl } from '@/utils/env'; // 静态资源地址前缀
export default {
components: {
PlayerAnimation
},
data() {
return {
isPlay: false, // 是否播放
toggleType: 1, // 播放顺序 (1:循环;2:单曲循环;3:随机)
musicDetail : { }, // 音乐详情
current: null, // 当前播放的是哪首
currentSrc: '', // 当前音乐路径
musicPlayCtx: null, // 音频上下文
musicCurrentTime: 0, // 音乐当前播放进度
musicTotalTime: 100, // 音乐总的时间
musicList: [], // 音乐列表
bgUrl: 'http://192.168.101.152:8848/commonStatic/ckzs/fetusMovement/tjyy_bg.png',
timers: null,
openId: ''
}
},
watch: {
currentSrc: {
handler() {
if(this.musicPlayCtx) {
this.musicPlayCtx.destroy();
}
this.musicPlayCtx = uni.createInnerAudioContext();
this.musicPlayCtx.obeyMuteSwitch = false;
this.musicCurrentTime = 0;
this.musicPlayCtx.src = encodeURI(mediaUrl+ this.musicDetail.bsmb007);
this.musicTotalTime = this.musicDetail.bsmb009;
if(this.isPlay){
this.handleAudioPlay();
}
},
immediate: true,
deep: true,
}
},
mounted() {
this.openId = getApp().globalData.openId;
this.getMusicList();
},
methods: {
// 打开音乐列表
getMusicList() {
musicList(this.openId).then(res => {
if (res.success) {
this.musicList = res.result;
this.current = 0;
this.musicDetail = this.musicList[this.current];
this.currentSrc = encodeURI(mediaUrl+ this.musicDetail.bsmb007);
}
})
},
// 播放列表弹窗显示
handleMusicListShow() {
const { isPlay, toggleType, musicList, current} = this;
this.$emit('handleMusicListShow', {
bool: true,
isPlay,
toggleType,
musicList,
current
});
},
formatTime,
// 处理播放
handlePlay(bool) {
this.isPlay = bool;
if(bool) {
this.musicPlayCtx.seek(this.musicCurrentTime + 1);
this.handleAudioPlay();
} else {
this.musicPlayCtx.stop();
}
},
// 播放
handleAudioPlay() {
this.musicPlayCtx.play();
if(this.timers) {
clearTimeout(this.timers);
}
this.timers = setTimeout(() => {
console.log(this.musicPlayCtx.paused);
}, 200);
// 播放完成
this.musicPlayCtx.onEnded(() => {
if(this.toggleType == 1) {
this.handleNextPlay(); // 列表循环
} else if(this.toggleType == 2) {
this.musicPlayCtx.play(); // 单曲循环
} else {
this.current = Math.floor(Math.random() * this.musicList.length);
this.musicDetail = this.musicList[this.current];
this.currentSrc = encodeURI(mediaUrl+ this.musicDetail.bsmb007);
}
})
// 更新播放时间
this.musicPlayCtx.onTimeUpdate(() => {
this.onTimeUpdate();
})
// 播放出现错误
this.musicPlayCtx.onError(() => {
this.onError();
})
},
// 下一首
handleNextPlay() {
this.isPlay = true;
if(this.current < this.musicList.length - 1) {
uni.showToast({
title: '即将播放下一首',
icon: 'none'
})
this.current ++;
this.musicDetail = this.musicList[this.current];
this.currentSrc = encodeURI(mediaUrl+ this.musicDetail.bsmb007);
return
}
uni.showToast({
title: '已经为最后一首了',
icon: 'none'
})
},
// 上一首
handlePreviousPlay() {
this.isPlay = true;
if(this.current) {
this.current --;
uni.showToast({
title: '即将播放上一首',
icon: 'none'
})
this.musicDetail = this.musicList[this.current];
this.currentSrc = encodeURI(mediaUrl+ this.musicDetail.bsmb007);
return;
}
uni.showToast({
title: '已经为第一首了',
icon: 'none'
})
},
// 播放类型
handleToggleType(parmas) {
if(parmas) {
this.toggleType = parmas;
const { isPlay, toggleType, musicList, current} = this;
this.$emit('handleMusicListShow', {
bool: true,
isPlay,
toggleType,
musicList,
current
});
return;
}
this.toggleType = this.toggleType == 1 ? 2 : this.toggleType == 2 ? 3 : 1;
},
// 切换音乐
handleMusicChange(index) {
this.isPlay = true;
this.musicDetail = this.musicList[index];
this.current = index;
this.currentSrc = encodeURI(mediaUrl+ this.musicDetail.bsmb007);
this.handleMusicListShow();
},
// 加载失败
onError() {
uni.showToast({
title: '音频加载失败',
icon: 'none'
})
},
// 时间更新
onTimeUpdate() {
if (this.musicPlayCtx.currentTime > 0 && this.musicPlayCtx.currentTime <= 1) {
this.musicCurrentTime = 1;
} else if (this.musicCurrentTime !== Math.floor(this.musicPlayCtx.currentTime)) {
this.musicCurrentTime = Math.floor(this.musicPlayCtx.currentTime);
}
},
// 喜欢
onLikeMusic(bool, item) {
this.musicDetail.liked = bool;
if (bool) {
//收藏
saveCollect({
bsmId: item.id,
openId: this.openId
}).then(res => {
if (res.success) {
uni.showToast({
title: '收藏成功',
icon: 'none'
})
this.getMusicList()
}
})
} else {
//取消收藏
cancleCollect({
bsmId: item.id,
openId: this.openId,
id: item.collectId
}).then(res => {
if (res.success) {
uni.showToast({
title: '已取消收藏',
icon: 'none'
})
this.getMusicList()
}
})
}
},
// 心愿歌单
handleAddWisk() {
uni.navigateTo({
url: '/toolsPages/fetusMovement/addWish'
})
}
},
// 销毁
destroyed() {
if(this.musicPlayCtx) this.musicPlayCtx.destroy();
if(this.timers) clearTimeout(this.timers);
}
}
</script>
<style scoped lang="scss">
.music-layout {
position: relative;
width: 690rpx;
padding: 20rpx 30rpx;
background: #FFFFFF;
border-radius: 30rpx;
backdrop-filter: blur(16px);
}
.left-content {
width: 130rpx;
.like-layout {
margin-top: 20rpx;
> text {
color: #FF3370;
font-size: 40rpx;
}
}
}
.left-pic-layout {
position: relative;
width: 100rpx;
height: 100rpx;
border-radius: 50%;
overflow: hidden;
img {
width: 100%;
height: 100%;
border-radius: 50%;
}
.small-circle{
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 20rpx;
height: 20rpx;
background-color: white;
border-radius: 50%;
}
}
.right-content {
flex: 1;
margin-left: 30rpx;
}
.song-name-layout {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 32rpx;
color: #323A59;
line-height: 40rpx;
height: 40rpx;
text {
color: rgba(115, 121, 141, 1);
}
}
.progress-layout {
display: flex;
justify-content: space-between;
align-items: center;
margin: 25rpx 0 0 2rpx;
font-size: 26rpx;
color: #73798D;
line-height: 36rpx;
.progress {
display: flex;
align-items: center;
flex: 1;
margin: 0 20rpx;
}
}
.actions-layout {
display: flex;
align-items: center;
justify-content: space-between;
margin: 25rpx 0 0 4rpx;
.toggle-type-layout {
> img {
width: 30rpx;
height: 26rpx;
}
}
.action-center {
display: flex;
justify-content: space-between;
align-items: center;
flex: 1;
margin: 0 86rpx;
}
.previous-icon, .next-icon {
width: 30rpx;
height: 32rpx;
}
.player-icon {
width: 42rpx;
height: 42rpx;
}
.music-list-icon {
width: 30rpx;
height: 30rpx;
}
}
.img-rotate {
transform-origin: center center;
animation: rotate 5s infinite linear; /* 实现旋转动画效果 */
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.add-music {
margin-top: 20rpx;
text-align: center;
font-size: 20rpx;
color: #999999;
text-decoration: underline #999999;
}
</style>
(2)utils中util.js文件代码
function fixedZero(val) {
return val * 1 < 10 ? `0${val}` : val;
}
const formatTime = (time) => {
const minutes = 60;
const m = Math.floor(time / minutes);
const s = Math.floor(time % 60);
return `${fixedZero(m)}:${fixedZero(s)}`;
}
(3)components中player-animation文件夹player-animation.vue文件代码
<template>
<view class="loading">
<view class="item"></view>
<view class="item"></view>
<view class="item"></view>
<view class="item"></view>
<view class="item"></view>
</view>
</template>
<script>
export default {
name: "player-animation"
}
</script>
<style scoped>
/* 设置位置 */
.loading {
height: 24rpx;
display: flex;
align-items: center;
}
.item {
height: 24rpx;
width: 2rpx;
background: #FF3370;
margin: 0 3rpx;
border-radius: 10rpx;
animation: loading 2s infinite;
}
/* 设置动画 */
@keyframes loading {
0% {
height: 0;
}
50% {
height: 24rpx;
}
100% {
height: 0;
}
}
/* 为每一个竖条设置延时 */
.item:nth-child(2) {
animation-delay: 0.2s;
}
.item:nth-child(3) {
animation-delay: 0.4s;
}
.item:nth-child(4) {
animation-delay: 0.6s;
}
.item:nth-child(5) {
animation-delay: 0.8s;
}
</style>
(4)播放器列表弹窗代码
<template>
<tn-popup v-model="show"
safeAreaInsetBottom
mode="bottom"
height="1200rpx"
@close="handleClose">
<view class="music-container">
<view class="header-layout">
<view class="header-left">
胎教音乐列表<text>({{ musicLst.length }}首)</text>
</view>
<view class="header-right" @click="() => handleToggle()">
<img src="http://192.168.101.152:8848/commonStatic/ckzs/fetusMovement/loop-icon.png" v-if="toggleType == 1" >
<img src="http://192.168.101.152:8848/commonStatic/ckzs/fetusMovement/single-icon.png" v-else-if="toggleType == 2">
<img src="http://192.168.101.152:8848/commonStatic/ckzs/fetusMovement/random-icon.png" v-else>
顺序播放
</view>
</view>
<scroll-view scroll-y style="height: 1050rpx;">
<view class="music-list">
<view :class="['music-item', current == index && isPlay ? 'active' : '']"
v-for="(item, index) in musicLst"
:key="index"
@click="handleItemClick(index)">
<view class="name">{{item.bsmb002}}</view>
<PlayerAnimation v-if="current == index && isPlay"></PlayerAnimation>
</view>
</view>
</scroll-view>
</view>
</tn-popup>
</template>
<script>
import PlayerAnimation from "../../../components/player-animation/player-animation.vue";
export default {
props: {
musicLst : {
type: Array,
default: []
},
current: {
type: Number,
default: 0
},
toggleType:{
type:Number,
default: 1
},
isPlay:{
type:Boolean,
default: false
}
},
components: {
PlayerAnimation
},
data() {
return {
show: false
}
},
methods: {
// 切换歌曲
handleItemClick(index) {
this.$emit('handleMusicChange', index);
},
// 关闭
handleClose() {
this.show = false;
},
// 播放类型切换
handleToggle() {
const playType = this.toggleType == 1 ? 2 : this.toggleType == 2 ? 3 : 1;
this.$emit('handleToggleType', playType);
},
}
}
</script>
<style scoped lang="scss">
.music-container {
padding: 0 30rpx 0;
.header-layout {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 10rpx;
height: 90rpx;
}
.header-left {
display: flex;
align-items: center;
font-size: 32rpx;
font-weight: 500;
color: #333333;
line-height: 44rpx;
text {
font-size: 24rpx;
font-weight: 400;
color: #666666;
line-height: 34rpx;
}
}
.header-right {
display: flex;
align-items: center;
height: 56rpx;
background: linear-gradient(135deg, #FF74A2 0%, #FF3370 100%);
border-radius: 28rpx;
padding: 0 12rpx;
font-size: 26rpx;
color: white;
img {
margin-right: 10rpx;
width: 26rpx;
height: 26rpx;
}
}
}
.music-list {
font-size: 28rpx;
color: #333333;
line-height: 40rpx;
.music-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 0;
font-size: 28rpx;
color: #333333;
line-height: 40rpx;
}
}
.active {
color: #FF3370;
}
</style>
3、实现也面向效果展示
(1)播放器
(2)播放器弹窗
4、实现该功能过程所遇到的问题总结
(1)当一首歌曲播完之后,进度条不更新问题,在网上查看到的方法都是说:该问题是存在的一个bug,解决的方案是我们再代码中要主动的调用paused()方法
this.timers = setTimeout(() => {
console.log(this.musicPlayCtx.paused);
}, 200);
(2)当点击暂停播放后,音乐从头播放的问题,解决方案:利用seek()方法使其跳转到指定位置
this.musicPlayCtx.seek(this.musicCurrentTime + 1);
(3)音乐播放器的地址,含有中文名称,在模拟器上面可以正常播放,手机上面不可以,解决方案:需要将该地址进行编码,编译成编译器可以识别地址
this.currentSrc = encodeURI(mediaUrl+ this.musicDetail.bsmb007);
5、以上代码可以直接粘贴复制到项目即可使用