• 小程序简单版音乐播放器


    小程序简单版音乐播放器

    在这里插入图片描述

    结构

    先来看看页面结构

        
        
    
    <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>私人FMtext>
              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>Hushview>
                view>
                <view class="list-item">
                  <image src="/images/talk.png" mode="aspectFill" />
                  <view>Talkview>
                view>
                <view class="list-item">
                  <image src="/images/men.png" mode="aspectFill" />
                  <view>Memoriesview>
                view>
                <view class="list-item">
                  <image src="/images/hour.png" mode="aspectFill" />
                  <view>golden hourview>
                view>
                <view class="list-item">
                  <image src="/images/mess.png" mode="aspectFill" />
                  <view>Yes I'm A Messview>
                view>
                <view class="list-item">
                  <image src="/images/iii.png" mode="aspectFill" />
                  <view>III Do Itview>
                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) % 60
          return (minute < 10 ? '0' + minute : minute) + ':' + (second < 10 ? '0' + second : second)
        }
      },
      // 音乐播放
      setMusic: function (index) {
        var music = this.data.playlist[index]
        this.audioCtx.src = music.src
        this.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 + 1
        this.setMusic(index)
        if (this.data.state === 'running') {
          this.play()
        }
      },
    

    接下来就是一些必不可少的小功能了

    // 滚动条调节歌曲进度
      sliderChange: function (e) {
        var second = e.detail.value * this.audioCtx.duration / 100
        this.audioCtx.seek(second)
      },
    
      // 播放列表换曲功能
      change: function (e) {
        this.setMusic(e.currentTarget.dataset.index)
        this.play()
      }
    

    🆗,最后的就剩下node服务了,这边node服务只是为了挂载音乐文件这些静态资源。
    这边我也会将服务端上传到本篇博客,把自己需要的MP3或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 * 1024
      form.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) % 60
          return (minute < 10 ? '0' + minute : minute) + ':' + (second < 10 ? '0' + second : second)
        }
      },
      // 音乐播放
      setMusic: function (index) {
        var music = this.data.playlist[index]
        this.audioCtx.src = music.src
        this.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 + 1
        this.setMusic(index)
        if (this.data.state === 'running') {
          this.play()
        }
      },
    
      // 滚动条调节歌曲进度
      sliderChange: function (e) {
        var second = e.detail.value * this.audioCtx.duration / 100
        this.audioCtx.seek(second)
      },
    
      // 播放列表换曲功能
      change: function (e) {
        this.setMusic(e.currentTarget.dataset.index)
        this.play()
      }
    })
    

    在这里插入图片描述


    • 失联

    最后编辑时间 2024,06,17;10:11

  • 相关阅读:
    22年7月工作笔记整理(前端)
    IP-guard产品相关端口和服务名称
    VulnHub JANGOW
    论Orchestration和Choreography
    超详细的三星全系列机型线刷图文教程和相关注意操作常识 二
    Linux查看端口号及进程信息
    WZOI-348机动车违章识别系统
    从功能测试到自动化测试,我在阿里的这7年
    leaflet:利用Leaflet-Geoman绘制多种图形,导出为geojson文件(135)
    举个栗子~Tableau 技巧(234):实现山峰柱形图
  • 原文地址:https://blog.csdn.net/2302_76607708/article/details/139732939