小程序简单版音乐播放器
结构
先来看看页面结构
<!-- wxml --><!-- 标签页标题 -->
<view class="tab"><view class="tab-item {{tab==0?'active':''}}" bindtap="changeItem" data-item="0">音乐推荐</view><view class="tab-item {{tab==1?'active':''}}" bindtap="changeItem" data-item="1">播放器</view><view class="tab-item {{tab==2?'active':''}}" bindtap="changeItem" data-item="2">播放列表</view>
</view>
<!-- 内容区域 -->
<view class="content"><swiper current="{{item}}" bindchange="changeTab"><swiper-item><!-- 内容滚动区域 --><scroll-view class="content-info" scroll-y><!-- 轮播图 --><swiper class="content-info-slide" indicator-color="rgba(255,255,255,.5)" indicator-active-color="#fff" indicator-dots circular autoplay><swiper-item><image src="/images/02.jpg" mode="aspectFill" /></swiper-item><swiper-item><image src="/images/03.jpg" mode="aspectFill" /></swiper-item><swiper-item><image src="/images/04.jpg" mode="aspectFill" /></swiper-item></swiper><!-- 功能按钮 --><view class="content-info-portal"><view><image src="/images/04.png" mode="aspectFill" catch:tap="fm" /><text>私人FM</text></view><view><image src="/images/05.png" mode="aspectFill" /><text>每日歌曲推荐</text></view><view><image src="/images/06.png" mode="aspectFill" /><text>云音乐新歌榜</text></view></view><!-- 热门音乐 --><view class="content-info-list"><view class="list-title">推荐歌曲</view><view class="list-inner"><view class="list-item"><image src="/images/hush.png" mode="aspectFill" /><view>Hush</view></view><view class="list-item"><image src="/images/talk.png" mode="aspectFill" /><view>Talk</view></view><view class="list-item"><image src="/images/men.png" mode="aspectFill" /><view>Memories</view></view><view class="list-item"><image src="/images/hour.png" mode="aspectFill" /><view>golden hour</view></view><view class="list-item"><image src="/images/mess.png" mode="aspectFill" /><view>Yes I'm A Mess</view></view><view class="list-item"><image src="/images/iii.png" mode="aspectFill" /><view>III Do It</view></view></view></view></scroll-view></swiper-item><swiper-item><!-- 播放器页面 --><include src="play.wxml" /></swiper-item><swiper-item><include src="playlist.wxml" /></swiper-item></swiper>
</view>
<!-- 底部播放器 -->
<view class="player"><image class="player-cover" src="{{play.coverImgUrl}}" mode="aspectFill" /><view class="player-info"><view class="player-info-title">{{play.title}}</view><view class="player-info-singer">{{play.singer}}</view></view><view class="player-controls"><!-- 切换到播放列表 --><image src="/images/01.png" bindtap="changePage" data-page="2" mode="aspectFill" /><!-- 播放或暂停 --><image wx:if="{{state=='paused'}}" src="/images/02.png" bindtap="play" mode="aspectFill" /><image wx:else src="/images/02stop.png" bindtap="pause" mode="aspectFill" /><!-- 下一曲 --><image src="/images/03.png" bindtap="next" mode="aspectFill" /></view>
</view>
其中一些代码可以使用 wx:for 循环创建,这边为了简单明了就没有循环创建
页面样式
page {display: flex;flex-direction: column;background: #17181a;color: #ccc;height: 100%;
}.tab {display: flex;
}.tab-item {flex: 1;font-size: 10pt;text-align: center;line-height: 72rpx;border-bottom: 6rpx solid #eee;
}.content {flex: 1;
}.content > swiper {height: 100%;
}.player {background: #222;border-top: 1px solid #252525;height: 112rpx;
}.tab-item.active {color: #c25b5b;border-bottom-color: #c25b5b;
}.content-info {height: 100%;
}::-webkit-scrollbar {width: 0;height: 0;color: transparent;
}/* 轮播图 */.content-info-slide {height: 302rpx;margin-bottom: 20px;
}.content-info-slide image {width: 100%;height: 100%;
}/* 功能按钮 */.content-info-portal {display: flex;margin-bottom: 15px;
}.content-info-portal > view {flex: 1;font-size: 11pt;text-align: center;
}.content-info-portal image {width: 120rpx;height: 120rpx;display: block;margin: 20rpx auto;
}/* 热门音乐 */.content-info-list {font-size: 11pt;margin-bottom: 20rpx;
}.content-info-list > .list-title {margin: 20rpx 35rpx;
}.content-info-list > .list-inner {display: flex;flex-wrap: wrap;margin: 0 20rpx;
}.content-info-list > .list-inner > .list-item {flex: 1;
}.content-info-list > .list-inner > .list-item > image {display: block;width: 200rpx;height: 200rpx;margin: 0 auto;border-radius: 10rpx;border: 1rpx solid #555;
}.content-info-list > .list-inner > .list-item > view {width: 200rpx;margin: 10rpx auto;font-size: 10pt;
}/* 播放器 */.content-play {display: flex;justify-content: space-around;flex-direction: column;height: 100%;text-align: center;
}.content-play-info > view {color: #888;font-size: 11pt;
}/* 底部播放器 */.player {display: flex;align-items: center;background: #222;border-top: 1px solid #252525;height: 112rpx;
}.player-cover {width: 80rpx;height: 80rpx;margin-left: 15rpx;border-radius: 8rpx;border: 1px solid #333;
}.player-info {flex: 1;font-size: 10pt;line-height: 38rpx;margin-left: 20rpx;padding-bottom: 8rpx;
}.player-info-singer {color: #888;
}.player-controls image {width: 80rpx;height: 80rpx;margin-right: 15rpx;
}/* 显示专辑页面样式 */.content-play-cover image {animation: rotateImage 10s linear infinite;width: 400rpx;height: 400rpx;border-radius: 50%;border: 1px solid #333;
}@keyframes rotateImage {from {transform: rotate(0deg);}to {transform: rotate(360deg);}
}/* 播放进度和时间 */.content-play-progress {display: flex;align-items: center;margin: 0 35rpx;font-size: 9pt;text-align: center;
}.content-play-progress > view {flex: 1;
}/* 播放列表 */.playlist-item {display: flex;align-items: center;border-bottom: 1rpx solid #333;height: 112rpx;
}.playlist-cover {width: 80rpx;height: 80rpx;margin-left: 15rpx;border-radius: 8rpx;border: 1px solid #333;
}.playlist-info {flex: 1;font-size: 10pt;line-height: 38rpx;margin-left: 20rpx;padding-bottom: 8rpx;
}.playlist-info-singer {color: #888;
}.playlist-controls {font-size: 10pt;margin-right: 20rpx;color: #c25b5b;
}
核心代码
音乐数据
🆗,接下来创建播放列表数据
data: {item: 0,tab: 0,// 播放列表数据playlist: [{id: 1,title: 'Always Online',singer: '林俊杰',src: 'http://localhost:3000/林俊杰 - Always Online.mp3',coverImgUrl: '/images/jj.jpeg'}, {id: 2,title: '不得不爱',singer: '潘玮柏、弦子',src: 'http://localhost:3000/潘玮柏、弦子 - 不得不爱.mp3',coverImgUrl: '/images/pwb.jpg'}, {id: 3,title: '大城小爱',singer: '王力宏',src: 'http://localhost:3000/王力宏 - 大城小爱.mp3',coverImgUrl: '/images/wlh.jpeg'}, {id: 4,title: '偏爱',singer: '张芸京',src: 'http://localhost:3000/张芸京 - 偏爱.mp3',coverImgUrl: '/images/pa.jpeg'}],state: 'paused',playIndex: 0,play: {currentTime: '00:00',duration: '00:00',percent: 0,title: '',singer: '',coverImgUrl: '/images/cover.jpg',}},
切换功能
来实现一些页面之间简单的切换
// 页面切换changeItem: function (e) {this.setData({item: e.target.dataset.item,})},// tab切换changeTab: function (e) {this.setData({tab: e.detail.current})},
播放功能
// 实现播放器播放功能audioCtx: null,onReady: function () {this.audioCtx = wx.createInnerAudioContext()// 默认选择第1曲this.setMusic(0)var that = this// 播放进度检测this.audioCtx.onError(function () {console.log('播放失败:' + that.audioCtx.src)})// 播放完成自动换下一曲this.audioCtx.onEnded(function () {that.next()})// 自动更新播放进度this.audioCtx.onPlay(function () {})this.audioCtx.onTimeUpdate(function () {that.setData({'play.duration': formatTime(that.audioCtx.duration),'play.currentTime': formatTime(that.audioCtx.currentTime),'play.percent': that.audioCtx.currentTime / that.audioCtx.duration * 100})})// 格式化时间function formatTime(time) {var minute = Math.floor(time / 60) % 60;var second = Math.floor(time) % 60return (minute < 10 ? '0' + minute : minute) + ':' + (second < 10 ? '0' + second : second)}},// 音乐播放setMusic: function (index) {var music = this.data.playlist[index]this.audioCtx.src = music.srcthis.setData({playIndex: index,'play.title': music.title,'play.singer': music.singer,'play.coverImgUrl': music.coverImgUrl,'play.currentTime': '00:00','play.duration': '00:00','play.percent': 0})},// 播放按钮play: function () {this.audioCtx.play()this.setData({state: 'running'})},// 暂停按钮pause: function () {this.audioCtx.pause()this.setData({state: 'paused'})},// 下一曲按钮next: function () {var index = this.data.playIndex >= this.data.playlist.length - 1 ? 0 : this.data.playIndex + 1this.setMusic(index)if (this.data.state === 'running') {this.play()}},
接下来就是一些必不可少的小功能了
// 滚动条调节歌曲进度sliderChange: function (e) {var second = e.detail.value * this.audioCtx.duration / 100this.audioCtx.seek(second)},// 播放列表换曲功能change: function (e) {this.setMusic(e.currentTarget.dataset.index)this.play()}
🆗,最后的就剩下node服务了,这边node服务只是为了挂载音乐文件这些静态资源。
这边我也会将服务端
上传到本篇博客,把自己需要的MP
3或MP4
格式文件放入到htdocs
中即可。
这边怕引起版权纠纷和维权问题就不把歌曲文件放入其中
node 下面代码存放在 index.js 中 请记得使用 yarn 或者 npm 安装以下的引入文件
var express = require('express')
var serveIndex = require('serve-index')
var serveStatic = require('serve-static')
var multiparty = require('multiparty')
var finalhandler = require('finalhandler')
var util = require('util')var LOCAL_BIND_PORT = 3000
var app = express()app.post('/upload', function (req, res) {var form = new multiparty.Form()form.encoding = 'utf-8'form.uploadDir = './htdocs/upfile'form.maxFilesSize = 4 * 1024 * 1024form.parse(req, function (err, fields, files) {if (err) {console.log('parse error: ' + err)} else {console.log('parse files: ' + JSON.stringify(files))}res.writeHead(200, { 'content-type': 'text/plain;charset=utf-8' })res.write('received upload')res.end()})
})var serve = serveStatic('./htdocs')
app.use('/', serveIndex('./htdocs', { 'icons': true }))app.get('/*', function (req, res) {serve(req, res, finalhandler(req, res))
});// console.log(`Start static file server at ::${LOCAL_BIND_PORT}, Press ^ + C to exit`)
console.log('启动成功')app.listen(LOCAL_BIND_PORT)
// 监听3000端口
// app.listen(3000, () => {
// console.log('server running at http://127.0.0.1:3000')
// })
最后附上完整代码
// pages/index/index.js
Page({/*** 页面的初始数据*/data: {item: 0,tab: 0,// 播放列表数据playlist: [{id: 1,title: 'Always Online',singer: '林俊杰',src: 'http://localhost:3000/林俊杰 - Always Online.mp3',coverImgUrl: '/images/jj.jpeg'}, {id: 2,title: '不得不爱',singer: '潘玮柏、弦子',src: 'http://localhost:3000/潘玮柏、弦子 - 不得不爱.mp3',coverImgUrl: '/images/pwb.jpg'}, {id: 3,title: '大城小爱',singer: '王力宏',src: 'http://localhost:3000/王力宏 - 大城小爱.mp3',coverImgUrl: '/images/wlh.jpeg'}, {id: 4,title: '偏爱',singer: '张芸京',src: 'http://localhost:3000/张芸京 - 偏爱.mp3',coverImgUrl: '/images/pa.jpeg'}],state: 'paused',playIndex: 0,play: {currentTime: '00:00',duration: '00:00',percent: 0,title: '',singer: '',coverImgUrl: '/images/cover.jpg',}},// 页面切换changeItem: function (e) {this.setData({item: e.target.dataset.item,})},// tab切换changeTab: function (e) {this.setData({tab: e.detail.current})},// 实现播放器播放功能audioCtx: null,onReady: function () {this.audioCtx = wx.createInnerAudioContext()// 默认选择第1曲this.setMusic(0)var that = this// 播放进度检测this.audioCtx.onError(function () {console.log('播放失败:' + that.audioCtx.src)})// 播放完成自动换下一曲this.audioCtx.onEnded(function () {that.next()})// 自动更新播放进度this.audioCtx.onPlay(function () {})this.audioCtx.onTimeUpdate(function () {that.setData({'play.duration': formatTime(that.audioCtx.duration),'play.currentTime': formatTime(that.audioCtx.currentTime),'play.percent': that.audioCtx.currentTime / that.audioCtx.duration * 100})})// 格式化时间function formatTime(time) {var minute = Math.floor(time / 60) % 60;var second = Math.floor(time) % 60return (minute < 10 ? '0' + minute : minute) + ':' + (second < 10 ? '0' + second : second)}},// 音乐播放setMusic: function (index) {var music = this.data.playlist[index]this.audioCtx.src = music.srcthis.setData({playIndex: index,'play.title': music.title,'play.singer': music.singer,'play.coverImgUrl': music.coverImgUrl,'play.currentTime': '00:00','play.duration': '00:00','play.percent': 0})},// 播放按钮play: function () {this.audioCtx.play()this.setData({state: 'running'})},// 暂停按钮pause: function () {this.audioCtx.pause()this.setData({state: 'paused'})},// 下一曲按钮next: function () {var index = this.data.playIndex >= this.data.playlist.length - 1 ? 0 : this.data.playIndex + 1this.setMusic(index)if (this.data.state === 'running') {this.play()}},// 滚动条调节歌曲进度sliderChange: function (e) {var second = e.detail.value * this.audioCtx.duration / 100this.audioCtx.seek(second)},// 播放列表换曲功能change: function (e) {this.setMusic(e.currentTarget.dataset.index)this.play()}
})
- 失联
最后编辑时间 2024,06,17;10:11