音乐播放器app及小程序

项目概述

这是一个基于Vue.js和UniApp开发的音乐播放器应用,支持多平台运行(H5、小程序、App)。项目实现了完整的音乐播放功能,包括歌曲列表、播放控制、进度条管理等。

功能特性

  • 🎵 歌曲管理:支持多首歌曲的列表展示
  • ⏯️ 播放控制:播放/暂停、上一首/下一首
  • 📊 进度控制:可拖拽的播放进度条
  • 🎨 视觉反馈:播放动画、专辑封面旋转
  • 📱 响应式设计:适配不同屏幕尺寸

技术栈

  • 前端框架:Vue.js 2.x
  • 跨端方案:UniApp
  • 样式预处理器:CSS3
  • 构建工具:HBuilderX

核心代码展示

Template 部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
<template>
<view class="container">
<!-- 歌曲列表区域 -->
<view class="song-list">
<view
class="song"
v-for="(song, index) in songList"
:key="index"
:class="{playing: currentSongIndex === index}"
@click="selectSong(index)"
>
<!-- 序号或播放动画 -->
<view class="number">
<text v-if="currentSongIndex !== index || !isPlaying">{{index + 1}}</text>
<image
v-if="currentSongIndex === index && isPlaying"
src="/static/playing.gif"
class="playing-animation"
></image>
</view>

<!-- 歌曲信息 -->
<view class="info">
<text class="title">{{song.name}}</text>
<text class="artist">{{song.singer}}</text>
</view>

<!-- 歌曲时长 -->
<view class="time">
{{formatSongTime(song.duration || 0)}}
</view>
</view>
</view>

<!-- 底部播放控制栏 -->
<view class="player" v-if="songList.length > 0">
<!-- 专辑封面(播放时旋转) -->
<view class="cover" :class="{ spin: isPlaying }">
<image
:src="nowPlaying.cover || '/static/default-cover.png'"
class="cover-img"
mode="aspectFill"
></image>
</view>

<!-- 当前播放信息 -->
<view class="now-playing">
<text class="song-title">{{nowPlaying.name}}</text>
<text class="artist-name">{{nowPlaying.singer}}</text>

<!-- 播放进度条 -->
<view class="progress">
<slider
:value="currentPosition"
:max="totalDuration"
@changing="dragProgress"
@change="setProgress"
block-size="16"
block-color="#4a90e2"
activeColor="#4a90e2"
backgroundColor="#e5e5e5"
class="slider"
/>
<view class="time-info">
<text>{{formatSongTime(currentPosition)}}</text>
<text>{{formatSongTime(totalDuration)}}</text>
</view>
</view>
</view>

<!-- 控制按钮 -->
<view class="controls">
<button class="btn" @click="previousSong">
<image src="/static/prev.png" class="btn-icon"></image>
</button>
<button class="btn play-pause" @click="togglePlayPause">
<image
:src="isPlaying ? '/static/pause.png' : '/static/play.png'"
class="play-pause-icon"
></image>
</button>
<button class="btn" @click="nextSong">
<image src="/static/next.png" class="btn-icon"></image>
</button>
</view>

<!-- 音频播放器 -->
<audio
id="audioPlayer"
:src="nowPlaying.url"
:poster="nowPlaying.cover"
:name="nowPlaying.name"
:author="nowPlaying.singer"
:action="playbackControl"
@play="handlePlay"
@pause="handlePause"
@timeupdate="updateProgress"
@ended="songFinished"
@error="handleError"
@loadedmetadata="getSongInfo"
></audio>
</view>
</view>
</template>

Script 部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
<script>
export default {
data() {
return {
// 歌曲列表
songList: [
{
cover: 'https://imgessl.kugou.com/stdmusic/20250125/20250125121745628430.jpg',
name: '借口',
singer: '周杰伦',
url: 'https://webfs.kugou.com/202511161520/681a6135cb3541079baf69a9617f3b8c/v3/47c5990aebdbdc27239d989bc3ff638e/yp/full/ap1014_us1173921957_mii0w1iw8z2ai2iphcu80ooo2ki81120_pi406_mx32144419_s120623741.mp3',
duration: 268 // 添加预设时长
},
{
cover: 'https://imgessl.kugou.com/stdmusic/20170728/20170728122746411503.jpg',
name: '退后',
singer: '周杰伦',
url: 'https://webfs.kugou.com/202511161508/1ed48bb609f30a75e2614540a7dca012/v3/5971de0b235d5b2708d6a6feb3d31b6f/yp/full/ap1014_us1173921957_mii0w1iw8z2ai2iphcu80ooo2ki81120_pi406_mx64473829_s3949997481.mp3',
duration: 268 // 添加预设时长
},
{
cover: 'https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/music-a.png',
name: '致爱丽丝',
singer: '贝多芬',
url: 'https://web-ext-storage.dcloud.net.cn/uni-app/ForElise.mp3',
duration: 196 // 添加预设时长
}
],

// 播放状态
currentSongIndex: 0, // 当前播放的歌曲索引
isPlaying: false, // 是否正在播放
currentPosition: 0, // 当前播放位置(秒)
totalDuration: 268, // 歌曲总时长(秒)
playbackControl: { // 播放控制
method: 'pause'
},
isDragging: false, // 是否正在拖动进度条
audioPlayer: null // 音频播放器实例

}
},

// 计算当前播放的歌曲信息
computed: {
nowPlaying() {
return this.songList[this.currentSongIndex] || {}
}
},

// 页面加载时初始化
onLoad() {
this.setupAudioPlayer();
},

// 页面卸载时清理
onUnload() {
if (this.audioPlayer) {
this.audioPlayer.destroy();
}
},

methods: {
// 初始化音频播放器
setupAudioPlayer() {
this.audioPlayer = uni.createInnerAudioContext();
this.audioPlayer.autoplay = false;
this.audioPlayer.src = this.nowPlaying.url;

// 设置各种事件监听
this.audioPlayer.onPlay(() => {
this.handlePlay();
});

this.audioPlayer.onPause(() => {
this.handlePause();
});

this.audioPlayer.onTimeUpdate(() => {
this.updateProgress({
detail: {
currentTime: this.audioPlayer.currentTime,
duration: this.audioPlayer.duration
}
});
});

this.audioPlayer.onEnded(() => {
this.songFinished();
});

this.audioPlayer.onError((error) => {
this.handleError({
detail: {
errMsg: error.errMsg
}
});
});
},

// 选择歌曲播放
selectSong(index) {
if (this.currentSongIndex === index) {
// 点击当前歌曲:切换播放/暂停
this.togglePlayPause();
} else {
// 点击其他歌曲:切换到该歌曲并播放
this.currentSongIndex = index;
this.audioPlayer.src = this.nowPlaying.url;
this.isPlaying = true;
this.audioPlayer.play();
}
},

// 播放/暂停切换
togglePlayPause() {
if (this.isPlaying) {
this.audioPlayer.pause();
} else {
this.audioPlayer.play();
}
},

// 上一首
previousSong() {
if (this.currentSongIndex > 0) {
this.currentSongIndex--;
} else {
// 如果是第一首,跳到最后一首
this.currentSongIndex = this.songList.length - 1;
}
this.switchToSong();
},

// 下一首
nextSong() {
if (this.currentSongIndex < this.songList.length - 1) {
this.currentSongIndex++;
} else {
// 如果是最后一首,跳到第一首
this.currentSongIndex = 0;
}
this.switchToSong();
},

// 切换到指定歌曲并播放
switchToSong() {
this.audioPlayer.src = this.nowPlaying.url;
this.isPlaying = true;
this.audioPlayer.play();
},

// 开始播放
handlePlay() {
this.isPlaying = true;
},

// 暂停播放
handlePause() {
this.isPlaying = false;
},

// 更新播放进度
updateProgress(event) {
if (!this.isDragging) {
this.currentPosition = event.detail.currentTime;
this.totalDuration = event.detail.duration || this.nowPlaying.duration;
}
},

// 歌曲播放结束
songFinished() {
this.nextSong();
},

// 拖动进度条过程中
dragProgress(event) {
this.isDragging = true;
this.currentPosition = event.detail.value;
},

// 进度条拖动结束
setProgress(event) {
this.audioPlayer.seek(event.detail.value);
this.isDragging = false;
},

// 格式化时间显示(秒 -> 分:秒)
formatSongTime(seconds) {
seconds = Math.floor(seconds || 0);
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return `${minutes < 10 ? '0' + minutes : minutes}:${remainingSeconds < 10 ? '0' + remainingSeconds : remainingSeconds}`;
}
}
}
</script>

Style 部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
<style>
/* 页面基础样式 */
page {
background-color: #f8f8f8;
}

.container {
padding-bottom: 300rpx;
min-height: 100vh;
}

/* 歌曲列表样式 */
.song-list {
padding: 20rpx 30rpx;
}

.song {
display: flex;
align-items: center;
padding: 25rpx 0;
border-bottom: 1rpx solid #f0f0f0;
background-color: #fff;
border-radius: 12rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.02);
}

.song.playing {
color: #4a90e2;
}

.number {
width: 60rpx;
text-align: center;
font-size: 28rpx;
color: #999;
}

.playing-animation {
width: 30rpx;
height: 30rpx;
}

.info {
flex: 1;
display: flex;
flex-direction: column;
padding: 0 20rpx;
overflow: hidden;
}

.title {
font-size: 32rpx;
margin-bottom: 8rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.artist {
font-size: 24rpx;
color: #888;
}

.playing .artist {
color: #4a90e2;
}

.time {
font-size: 24rpx;
color: #888;
padding-right: 10rpx;
}

/* 播放器样式 */
.player {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
box-shadow: 0 -4rpx 30rpx rgba(0, 0, 0, 0.1);
padding: 30rpx 40rpx;
border-top-left-radius: 30rpx;
border-top-right-radius: 30rpx;
z-index: 100;
}

/* 专辑封面旋转动画 */
.cover {
width: 200rpx;
height: 200rpx;
border-radius: 50%;
margin: 0 auto 30rpx;
overflow: hidden;
border: 8rpx solid rgba(74, 144, 226, 0.2);
animation: rotate 20s linear infinite;
animation-play-state: paused;
}

.cover.spin {
animation-play-state: running;
}

@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}

.cover-img {
width: 100%;
height: 100%;
}

/* 当前播放信息 */
.now-playing {
text-align: center;
margin-bottom: 30rpx;
}

.song-title {
font-size: 36rpx;
font-weight: bold;
display: block;
margin-bottom: 10rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.artist-name {
font-size: 26rpx;
color: #888;
margin-bottom: 30rpx;
}

/* 进度条 */
.progress {
margin: 30rpx 0;
}

.slider {
margin: 0;
}

.time-info {
display: flex;
justify-content: space-between;
font-size: 24rpx;
color: #888;
margin-top: 10rpx;
}

/* 控制按钮 */
.controls {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20rpx;
}

.btn {
background: none;
border: none;
padding: 0 50rpx;
margin: 0;
line-height: 1;
}

.play-pause {
padding: 0 60rpx;
}

.btn-icon {
width: 50rpx;
height: 50rpx;
}

.play-pause-icon {
width: 80rpx;
height: 80rpx;
}
</style>

项目亮点

  1. 播放状态管理
1
2
3
4
5
6
7
8
9
// 使用Vue的响应式系统管理播放状态
data() {
return {
currentSongIndex: 0,
isPlaying: false,
currentPosition: 0,
totalDuration: 0
}
}