小程序项目学习--第七章:播放页布局-歌曲进度控制-歌词的展示

news2024/11/20 15:23:50

第七章:播放页布局-歌曲进度控制-歌词的展示

01_(了解)之前页面的回顾和播放页的介绍

功能介绍

image-20230129221043201

02_(掌握)播放页-点击Item跳转到播放页和传入ID

功能概览

image-20230129221006639

1.创建页面music-player

2.监听item的点击

方式一:直接写在子组件上

绑定监听点击 需要获取点击的歌曲 和 歌曲播放的列表数据

为了获取item 我们需要自定义data-item=“” ,将数据传递过去

image-20230129221733647

方式二:在封装的组件上的更元素上绑定

本身properties上有itemData,不需要传递数据

v1
<view class="song-item" bindtap="onSongItemTap">
    
// components/song-item-v1/song-item-v1.js
Component({
  properties: {
    itemData: {
      type: Object,
      value: {}
    }
  },
  methods: {
    onSongItemTap() {
      const id = this.properties.itemData.id
      wx.navigateTo({
        url: `/pages/music-player/music-player?id=${id}`,
      })
    }
  }
})
v2
<!--components/song-item-v2/song-item-v2.wxml-->
<view class="item" bindtap="onSongItemTap">

// components/song-item-v2/song-item-v2.js
Component({
  properties: {
    itemData: {
      type: Object,
      value: {}
    },
    index: {
      type: Number,
      value: -1
    }
  },
  methods: {
    onSongItemTap() {
      const id = this.properties.itemData.id
      wx.navigateTo({
        url: `/pages/music-player/music-player?id=${id}`,
      })
    }
  }
})

3.获取id

<text>{{id}}</text>

Page({
  data:{
    id:0
  },
  onLoad(options){
    // option 就是传递过来的数据
    console.log(options);
    const id = options.id
    this.setData({id})
  }
})

03_(掌握)播放页-获取歌曲详情和歌词信息

通过id可以获取我们更加具体的数据,获取数据展示到页面中

0.封装播放相关的接口 services文件夹下player.js

import { hyRequest } from "./index"

export function getSongDetail(ids) {
  return hyRequest.get({
    url: "/song/detail",
    data: {
      ids
    }
  })
}

export function getSongLyric(id) {
  return hyRequest.get({
    url: "/lyric",
    data: {
      id
    }
  })
}

1.调用接口请求数据获取歌曲详情和歌词信息,通过data保存数据

import { getSongDetail, getSongLyric } from "../../services/player";


Page({
  data:{
    id:0,
    currentSong:{},
    lrcString:""
  },
  onLoad(options){
    // 1.获取传入的id值
    // option 就是传递过来的数据
    console.log(options);
    const id = options.id
    this.setData({id})
    // 2.根据id获取歌曲的详情
    getSongDetail(id).then(res =>{
      this.setData({currentSong :res.songs[0]})
    })

    // 3.根据id获取歌词的信息
    getSongLyric(id).then(res =>{
      this.setData({lrcString:res.lrc.lyric})
    })
  }
})

2.展示数据

<!--pages/music-player/music-player.wxml-->
<view>{{currentSong.name}}</view>
<image src="{{currentSong.al.picUrl}}"></image>

04_(掌握)播放页-背景图片和背景毛玻璃效果

播放页功能实现

1.将默认导航变成自定义导航

在对应页面的json文件中配置 自定义导航

  "navigationStyle": "custom",

2.背景图片和背景毛玻璃效果

2.1结构与样式

<image class="bg-image"  src="{{currentSong.al.picUrl}}" mode="aspectFill"></image>
<view class="bg-cover"></view>

/* pages/music-player/music-player.wxss */
.bg-image, .bg-cover {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  z-index: -1;
}
.bg-cover {
  background-color: rgba(0, 0, 0, 0.2);
  backdrop-filter:blur(10px) ;
}

05_(掌握)播放页-自定义状态栏和导航栏的流程

1.实现自定义导航栏

一旦自定义导航的时候,导航栏会消失,状态栏也会消失不见(statuBar)都会消失,不在占据位置,我们的内容也都会上移,

但是我们状态栏我们并不希望展示东西,状态栏我们展示电池量时间等,

使用我们会在自定义导航的位置,中添加一个view,给他设置一个动态高度用来动态响应不同的手机

0.动态获取高度

在app,.js中获取设备信息中的状态栏高度

// app.js
App({
  globalData: {
    screenWidth: 375,
    screenHeight: 667,
   //导航栏高度   
    statusHeight: 20,
    contentHeight: 500
  },
  onLaunch() {
    // 1.获取设备的信息
    wx.getSystemInfo({
      success: (res) => {
        this.globalData.screenWidth = res.screenWidth
        this.globalData.screenHeight = res.screenHeight
       //   获取导航栏高度      
        this.globalData.statusHeight = res.statusBarHeight
        this.globalData.contentHeight = res.screenHeight - res.statusBarHeight - 44
      },
    })
  }
})

在js通过getApp()拿到app.js 来获取高度

const app = getApp()

data:{
    statusHeight:20
}
 // 0.获取设备信息
onLoad(){
     this.setData({ 
      statusHeight: app.globalData.statusHeight,   
    })
}   

<view class="status" style="height: {{statusHeight}}px;"></view>

1.展示结构

<!-- 2.自定义导航栏 -->
<view class="nav-bar">
  <view class="status" style="height: {{statusHeight}}px;"> </view>
  <view class="nav">
    <view class="left">返回</view>
    <view class="center">歌曲播放</view>
    <view class="right"></view>
  </view>
</view>


2.调整样式

/* 自定义导航 */
.nav {
  display: flex;
  height: 44px;
  color: #fff;
}

.left, .right, .center {
  display: flex;
  justify-content: center;
  align-items: center;
}

.nav .left, .nav .right {
  width: 120rpx;
}

.nav .center {
  flex: 1;
}

06_(掌握)播放页-自定义导航栏的封装和插槽

1.自定义导航栏初始的封装

0.封装成组件nav-bar 初始迁移–新建页面

// components/nav-bar/nav-bar.js
const app = getApp()

Component({
  data:{
    statusHeight:20
  },
  lifetimes: {
    attached() {
      this.setData({ statusHeight: app.globalData.statusHeight })
    }
  },
})

<!--components/nav-bar/nav-bar.wxml-->
<view class="nav-bar">
  <view class="status" style="height: {{statusHeight}}px;"> </view>
  <view class="nav">
    <view class="left">返回</view>
    <view class="center">歌曲播放</view>
    <view class="right"></view>
  </view>
</view>

/* 自定义导航 */
.nav {
  display: flex;
  height: 44px;
  color: #fff;
}

.left, .right, .center {
  display: flex;
  justify-content: center;
  align-items: center;
}

.nav .left, .nav .right {
  width: 120rpx;
}

.nav .center {
  flex: 1;
}

1.使用的页面进行注册

 "usingComponents": {
    "nav-bar":"/components/nav-bar/nav-bar"
  }

2.使用组件

<!-- 2.自定义导航栏 -->
<nav-bar></nav-bar>

1.通过插槽自定义导航栏内容

1.定义插槽 通过调整样式来显示和隐藏插槽

// components/nav-bar/nav-bar.js
const app = getApp()

Component({
  options: {
    multipleSlots: true
  },
  properties: {
    title: {
      type: String,
      value: "导航标题"
    }
  },
  data: {
    statusHeight: 20
  },
  lifetimes: {
    attached() {
      this.setData({ statusHeight: app.globalData.statusHeight })
    }
  },
  methods: {
    onLeftClick() {
      this.triggerEvent("leftclick")
    }
  }
})


<!--components/nav-bar/nav-bar.wxml-->
<view class="nav-bar">
  <view class="status" style="height: {{statusHeight}}px;"></view>
  <view class="nav">
    <view class="left" bindtap="onLeftClick">
      <view class="slot">
        <slot name="left"></slot>
      </view>
      <view class="default">
        <image class="icon" src="/assets/images/icons/arrow-left.png"></image>
      </view>
    </view>
    <view class="center">
      <view class="slot">
        <slot name="center"></slot>
      </view>
      <view class="default">
        {{title}}
      </view>
    </view>
    <view class="right"></view>
  </view>
</view>

/* components/nav-bar/nav-bar.wxss */
/* 自定义导航 */
.nav {
  display: flex;
  height: 44px;
  color: #fff;
}

.left, .right, .center {
  display: flex;
  justify-content: center;
  align-items: center;
}

.nav .left, .nav .right {
  width: 120rpx;
}

.nav .center {
  flex: 1;
}

.left .icon {
  width: 40rpx;
  height: 40rpx;
}
/* 控制内容显示 */
.default {
  display: none;
}

.slot:empty + .default {
  display: flex;
}

2.使用插槽

<!-- 2.自定义导航栏 -->
//如果有插槽使用插槽里面的,没有则使用父组件传递的数据,如果没有则使用默认值
<nav-bar title="111" >
<text slot="center">呵呵呵</text>
</nav-bar>

07_(掌握)播放页-歌曲和歌词的页面-导航切换

歌曲和歌词的页面-导航切换–可以当做轮播图用来切换

1.使用swiper来实现切换

注意点1.bindchange=“onSwiperChange” 当轮播图发送变化的时候,会调用本函数

注意点2.轮播图的高度是内容包裹的高度,但是我们需要的占据剩下的使用页面高度,所以我们需要响应的改变轮播图的高度

<!-- 2.自定义导航栏 -->
<nav-bar>
  <view class="tabs" slot="center">
    <view class="item {{currentPage === 0 ? 'active': ''}}">歌曲</view>
    <view class="divider">|</view>
    <view class="item {{currentPage === 1 ? 'active': ''}}">歌词</view>
  </view>
</nav-bar>

<!-- 具体内容 -->
<swiper bindchange="onSwiperChange" style="height: {{contentHeight}}px;" >
  <swiper-item>课词</swiper-item>
  <swiper-item>课曲</swiper-item>
</swiper>


Page({
  data:{
    currentPage:0,
    contentHeight: 500,
  },
  onLoad(options){
    // 0.获取设备信息
    this.setData({ 
      statusHeight: app.globalData.statusHeight,   
      contentHeight: app.globalData.contentHeight
    })  
  }, 

    // ==================== 事件监听 ==================== 
    onSwiperChange(event) {
      this.setData({ currentPage: event.detail.current })
    },
})


2.需要获取需要滚动的动态区域在app.json

// app.js
App({
  globalData: {
    screenWidth: 375,
    screenHeight: 667,
    statusHeight: 20,
    contentHeight: 500
  },
  onLaunch() {
    // 1.获取设备的信息
    wx.getSystemInfo({
      success: (res) => {
        this.globalData.screenWidth = res.screenWidth
        this.globalData.screenHeight = res.screenHeight
        this.globalData.statusHeight = res.statusBarHeight
        this.globalData.contentHeight = res.screenHeight - res.statusBarHeight - 44
      },
    })
  }
})



08_(掌握)播放页-歌曲分页整体的布局

1.大致结构布局

 <swiper-item class="music">
    <!-- 封面区 -->
    <view class="album">
      <image class="image" src="{{currentSong.al.picUrl}}" mode="widthFix"></image>
    </view>
    <!-- 歌曲信息 -->
    <view class="info">
      <view class="name">{{currentSong.name}}</view>
      <view class="singer">{{currentSong.ar[0].name}}</view>
    </view>
    <!-- 课词  -->
    <view class="lyric">
      {{currentLyricText}}
    </view>
    <!-- 时长 -->
    <view class="progress">
      <slider 
        class="slider" 
        block-size="12" 
        value="{{sliderValue}}"
        bindchange="onSliderChange"
        bindchanging="onSliderChanging"
      />
      <view class="time">
        <view class="current">{{fmt.formatTime(currentTime)}}</view>
        <view class="duration">{{fmt.formatTime(durationTime)}}</view>
      </view>
    </view>
    <!-- 控制台 -->
    <view class="controls">
      <image
        class="btn mode" 
        src="/assets/images/player/play_{{playModeName}}.png"
        bindtap="onModeBtnTap"
      />
      <image 
        class="btn prev" 
        src="/assets/images/player/play_prev.png"
        bindtap="onPrevBtnTap"
      />
      <image 
        class="btn play" 
        src="/assets/images/player/play_{{ isPlaying ? 'pause': 'resume' }}.png"
        bindtap="onPlayOrPauseTap"
      />
      <image 
        class="btn next" 
        src="/assets/images/player/play_next.png"
        bindtap="onNextBtnTap"
      />
      <image class="btn list" src="/assets/images/player/play_music.png"/>
    </view>
  </swiper-item>

09_(掌握)播放页-导航标题的点击和切换

导航标题的点击和切换

注意点:轮播图里面的current属性可以显示当前展示的页面

image-20230130124848345

1.需要监听点击事件,设置data数组变量,通过for循环抽出去进行遍历显示

<!-- 2.自定义导航栏 -->
<nav-bar bind:leftclick="onNavBackTap">
  <view class="tabs" slot="center">
    <block wx:for="{{pageTitles}}" wx:key="*this">
      <view 
        class="item {{currentPage === index ? 'active': ''}}" 
        bindtap="onNavTabItemTap" data-index="{{index}}"
      >
        {{item}}
      </view>
      <view class="divider" wx:if="{{index !== pageTitles.length - 1}}">|</view>
    </block>
  </view>
</nav-bar>

data:{
    pageTitles: ["歌曲", "歌词"],
}

  onNavTabItemTap(event) {
    const index = event.currentTarget.dataset.index
    this.setData({ currentPage: index })
  },

2.在轮播图中通过current属性动态显示当前展示的页面

<swiper  current="{{currentPage}}">

10_(掌握)播放页-歌曲页面的内容布局

结构

<!-- 具体内容 -->
<swiper bindchange="onSwiperChange" style="height: {{contentHeight}}px;" current="{{currentPage}}">
  <swiper-item class="music">
    <!-- 封面区 -->
    <view class="album">
      <image class="image" src="{{currentSong.al.picUrl}}" mode="widthFix"></image>
    </view>
    <!-- 歌曲信息 -->
    <view class="info">
      <view class="name">{{currentSong.name}}</view>
      <view class="singer">{{currentSong.ar[0].name}}</view>
    </view>
    <!-- 课词  -->
    <view class="lyric">
      {{currentLyricText}}
    </view>
    <!-- 时长 -->
    <view class="progress">
      <slider 
        class="slider" 
        block-size="12" 
      />
      <view class="time">
        <view class="current">01:33</view>
        <view class="duration">05:39</view>
      </view>
    </view>
    <!-- 控制台 -->
    <view class="controls">
      <image
        class="btn mode" 
        src="/assets/images/player/play_order.png"
        bindtap="onModeBtnTap"
      />
      <image 
        class="btn prev" 
        src="/assets/images/player/play_prev.png"
        bindtap="onPrevBtnTap"
      />
      <image 
        class="btn play" 
        src="/assets/images/player/play_pause.png"
        bindtap="onPlayOrPauseTap"
      />
      <image 
        class="btn next" 
        src="/assets/images/player/play_next.png"
        bindtap="onNextBtnTap"
      />
      <image class="btn list" src="/assets/images/player/play_music.png"/>
    </view>
  </swiper-item>
  <swiper-item>课曲</swiper-item>
</swiper>

样式

/* 歌曲布局 */
.music {
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  padding: 40rpx 60rpx;
  font-size: 28rpx;
  color: #fff;
}

.music .album {
  flex: 1;
}

.music .album .image {
  width: 100%;
  border-radius: 12rpx;
}

.music .info .name {
  font-size: 48rpx;
  font-weight: 700;
}

.music .info .singer {
  margin-top: 10rpx;
}

.music .lyric {
  text-align: center;
  margin: 16rpx 0;

  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.music .progress {
  margin: 12rpx 0;
}

.music .progress .slider {
  margin: 16rpx 8rpx 10rpx 18rpx;
}

.music .progress .time {
  display: flex;
  justify-content: space-between;
  font-size: 24rpx;
}

.music .controls {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin: 12rpx 0;
}

.music .controls .btn {
  width: 60rpx;
  height: 60rpx;
}

.music .controls .btn.mode {
  width: 80rpx;
  height: 80rpx;
} 

.music .controls .btn.play {
  width: 140rpx;
  height: 140rpx;
}

第七章:下午

11_(掌握)播放页-创建播放器和播放泡沫歌曲

1.创建一个播放器 ,开启播放

//创建一个播放器的上下文wx.createInnerAudioContext() 我们这个不用放到onLoad中,提前创建,只用创建一次就好,我们先放到最外层,最后我们一个独立的文件,进行维护
const audioContext = wx.createInnerAudioContext()

onLoad(){
    audioContext.src = `https://music.163.com/song/media/outer/url?id=${id}.mp3`
	//audioContext.src = `(播放地址)`
	audioContext.autoplay = true
}

12_(掌握)播放页-监听歌曲播放的时间和展示

监听歌曲播放的时间 onTimeUpdate

music-player.js 
data:{
    //当前歌曲播放的时间
	currentTime: 0,
    //当前歌曲播放的总时间    
	durationTime: 0,
    //当前歌曲滑块进度  
    sliderValue: 0,    
}

onLoad(){
// 2.1根据id获取歌曲的详情和------歌曲总时长(只用获取一次就好)
    getSongDetail(id).then(res =>{
      this.setData({
          currentSong :res.songs[0],
          durationTime:res.songs[0].dt
   
       })   
    })
   
    
 // 4.监听播放的进度 
    audioContext.onTimeUpdate(() => {
         // 1.获取当前播放的时间
        this.setData({currentTime:audioContext.currentTime * 1000})
         //2修改滑块 sliderValue
        const sliderValue = this.data.currentTime / this.data.durationTime *100
        this.setData({silderValue})
     })     
}
结构
<wxs src="/utils/format.wxs" module="fmt"></wxs>                              
  <!-- 时长 -->
    <view class="progress">
      <slider 
        class="slider" 
        block-size="12" 
        value="{{sliderValue}}"
      />
      <view class="time">
        <view class="current">{{fmt.formatTime(currentTime)}}</view>
        <view class="duration">{{fmt.formatTime(durationTime)}}</view>
      </view>
    </view>                         
                              

13_(掌握)播放页-点击滑块改变歌曲播放的进度

各种事件可以通过查文档

点击滑块改变歌曲播放的进度

完成一次拖动后触发的事件事件:点击滑块事件 bindchange

注意点:当我们点击滑块事件bindchange 歌曲会进入等待,不在监听播放进度(系统bug),所以我们需要手动跳整,让他进行继续监听

onLoad(){
  audioContext.onWaiting(() => {
  	 audioContext.pause()
})
   audioContext.onCanplay(() => {
  	 audioContext.play()
})
}

nSliderChange事件

  // onSliderChange事件
  onSliderChange(event) {
    // 1.获取点击滑块位置对应的值
    const value = event.detail.value

    // 2.计算出要播放的位置时间
    const currentTime = value / 100 * this.data.durationTime

    // 3.设置播放器,播放计算的时间
    audioContext.seek(currentTime / 1000)
    this.setData({
      currentTime
    })
  }

 <!-- 时长 -->
    <view class="progress">
      <slider class="slider" block-size="12" value="{{sliderValue}}" bindchange="onSliderChange" bindchanging="onSliderChanging" />
      <view class="time">
        <view class="current">{{fmt.formatTime(currentTime)}}</view>
        <view class="duration">{{fmt.formatTime(durationTime)}}</view>
      </view>
    </view>

14_(掌握)播放页-滑动滑块改变歌曲播放进度

拖动过程中触发的事件 动滑块改变歌曲播放进度事件 bindchanging

 // onSliderChangeing事件,滑动滑块松下的时候调用
  onSliderChangeing(event){
    //1.获取滑块到的位置的valuer
    const value = event.detail.value
    //2.根据当前的值,计算出对应的事件
    const currentTime = value / 100 * this.data.durationTime
    this.setData({currentTime}) 
    //3.变量记录滑块当前正在滑动
    this.data.isSliderChanging =true
  }

滑块改变歌曲播放进度事件可能会导致滑块反复横跳,因为手指滑动改变滑块,但是歌曲也在正常播放改变滑块,所以会导致反复横跳

但是在我们滑动的过程当中,我们不希望我们歌曲改变滑块sliderValue,也不希望改变当前时间currentTime

我们可以通过一个变量isSliderChanging用来记录是否滑动滑块,什么时候需要设置时间,什么时候不需要设置时间

如果正在滑动,就不在改变sliderValue,currentTime

通过 bindchanging 和 bindchange 用来判断变量是否播放

data:{
    isSliderChanging:false
}
当滑块当前正在滑动 设置为true
 // onSliderChangeing事件,滑动滑块松下的时候调用
  onSliderChangeing(event){
    //1.获取滑块到的位置的valuer
    const value = event.detail.value
    //2.根据当前的值,计算出对应的事件
    const currentTime = value / 100 * this.data.durationTime
    this.setData({currentTime}) 
    //3.变量记录滑块当前正在滑动
 1.   this.data.isSliderChanging =true 
  }
当滑块没有滑动滑动 设置为false监听播放的进度
  // onSliderChange事件
  onSliderChange(event) {
    // 1.获取点击滑块位置对应的值
    const value = event.detail.value
    // 2.计算出要播放的位置时间
    const currentTime = value / 100 * this.data.durationTime
    // 3.设置播放器,播放计算的时间
    audioContext.seek(currentTime / 1000)
1    this.setData({
      currentTime,isSliderChanging:false
    })
  },
      
     // 4.监听播放的进度
    audioContext.onTimeUpdate(() => {
       // if里面的变量为true的时候才执行
 1     if(!this.data.isSliderChanging){
        // 1.获取当前播放的时间
      this.setData({
        currentTime: audioContext.currentTime * 1000
      })
      //2修改滑块 sliderValue
      const sliderValue = this.data.currentTime / this.data.durationTime * 100
      this.setData({
        sliderValue
      })
      }
    })     

<slider class="slider" block-size="12" value="{{sliderValue}}" bindchange="onSliderChange" bindchanging="onSliderChangeing" />

15_(理解)播放页-点击滑块跳动的bug问题处理

点击滑块跳动的bug会出现跳跃

点击之后应该保留的是当前时间,而不是之前的时间,小程序的内部bug

方式一:

在onSliderChange中强制设置sliderValue:value

 onSliderChange(event) {
    // 1.获取点击滑块位置对应的值
    const value = event.detail.value
    // 2.计算出要播放的位置时间
    const currentTime = value / 100 * this.data.durationTime
    // 3.设置播放器,播放计算的时间
    audioContext.seek(currentTime / 1000)
    this.setData({
      currentTime,isSliderChanging:false,sliderValue:value
    })
  },

方式二:通过节流不让更新的很频繁

1.导入节流函数

import { throttle } from 'underscore'

// 4.监听播放的进度
    const throttleUpdateProgress = throttle(this.updateProgress,800,{leading:false})
    audioContext.onTimeUpdate(() => {
      // if里面的变量为true的时候才执行
      if(!this.data.isSliderChanging){
        throttleUpdateProgress()
      }
    })

  updateProgress(){
    // 1.获取当前播放的时间
    this.setData({
      currentTime: audioContext.currentTime * 1000
    })
    //2修改滑块 sliderValue
    const sliderValue = this.data.currentTime / this.data.durationTime * 100
    this.setData({
      sliderValue
    })
  },

方式三:定时器

2.通过变量 isWaiting 定时器来改变isWaiting 的值

data:{
   isWaiting:false
}
 if(!this.data.isSliderChanging && !this.data.isWaiting){
        throttleUpdateProgress()
} 
  // onSliderChange事件
  onSliderChange(event) {
    this.data.isWaiting = true
    setTimeout(()=>{
      this.data.isWaiting = false
    },1500)
    // 1.获取点击滑块位置对应的值
    const value = event.detail.value
    // 2.计算出要播放的位置时间
    const currentTime = value / 100 * this.data.durationTime
    // 3.设置播放器,播放计算的时间
    audioContext.seek(currentTime / 1000)
    this.setData({
      currentTime,isSliderChanging:false,sliderValue:value
    })
  },

16_(掌握)播放页-暂停-播放按钮的点击和控制

播放按钮的点击 监听点击事件 bindtap=“onPlayOrPauseTap”

通过变量记录isPlaying

在结构中定义图片动态切换

data:{
    isPlaying:true
}

onPlayOrPauseTap(){
    console.log(111);
    if(!audioContext.paused){
      audioContext.pause()
      this.setData({isPlaying:false})
    }else{
      audioContext.play()
      this.setData({isPlaying:true})
    }
  }


 <image class="btn play" 
        src="/assets/images/player/play_{{ isPlaying ? 'pause': 'resume' }}.png"
        bindtap="onPlayOrPauseTap"/>

17_(掌握)播放页-歌词的获取和解析过程

歌词的获取

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zcLhgPgB-1675305572113)(null)]

import { parseLyric } from "../utils/parse-lyric"

data:{
     lyricInfos: [],
}
onLoad(options) { 
    // 2.2根据id获取歌词的信息
    getSongLyric(id).then(res => {
      const lrcString =res.lrc.lyric
      const lyricInfos = parseLyric(lrcString)
       this.setData({lyricInfos})
    })
}

封装歌词解析的工具 parse-lyric.js

// [01:18.86]基于你还爱我
const timeReg = /\[(\d{2}):(\d{2})\.(\d{2,3})\]/

export function parseLyric(lrcString) {
  const lyricInfos = []

  const lyricLines = lrcString.split("\n")
  for (const lineString of lyricLines) {
    const results = timeReg.exec(lineString)
    // console.log(results,1111,lineString);
    if (!results) continue
    const minute = results[1] * 60 * 1000
    const second = results[2] * 1000
    const mSecond = results[3].length === 2 ? results[3] * 10: results[3] * 1
    const time = minute + second + mSecond
    const text = lineString.replace(timeReg, "")
    lyricInfos.push({ time, text })
  }
  return lyricInfos
}

18_(掌握)播放页-歌词的精准匹配和记录展示

根据时间匹配歌词

image-20230130224754578

1…匹配正确的歌词 2.展示歌词,放入data,进行展示

    <!-- 课词  -->
<view class="lyric">
   {{currentLyricText}}
</view>


data: {  
	currentLyricText: "",        
    currentLyricIndex: -1,
       
}  


// 4.监听播放的进度
    const throttleUpdateProgress = throttle(this.updateProgress,800,{leading:false})
    audioContext.onTimeUpdate(() => {
      // 1.更新歌曲的进度
      // if里面的变量为true的时候才执行
      if(!this.data.isSliderChanging && !this.data.isWaiting){
        throttleUpdateProgress()
      }
      // 2.匹配正确的歌词
      if(!this.data.lyricInfos.length) return
      // 初始默认值是最后一句歌词
      let index = this.data.lyricInfos.length -1
      for(let i =0; i<this.data.lyricInfos.length;i++){
        const info = this.data.lyricInfos[i]
        if(info.time > audioContext.currentTime * 1000){
          index = i-1
          break
        }
      }
      console.log(index,this.data.lyricInfos[index].text);
        if(index === this.data.currentLricIndex) return
		const currentLyricText = this.data.lyricInfos[index].text
		this.setData({currentLyricText,currentLyricIndex:index})
    })

19_(掌握)播放页-点击跳动bug处理和歌词每个字的匹配

应为trailing的时间节点和刚好点击的时间节点碰到一起,trailing为true会帮我们执行一次,所以出现的bug

trailing为true会帮我们执行一次(默认为true)

	// 4.监听播放的进度
    const throttleUpdateProgress = throttle(this.updateProgress,800,{leading:false,trailing:false})

歌词每个字的精确匹配需要后端传过来的数据也是包含每个字的精确字符串时间,所以我们没办法实现

但是我们可以模拟实现 根据播完这个课词的所耗时的时间范围,播完这个歌词完成平均的动画渲染

20_(掌握)播放页-完整的歌词在Item中展示和滚动

结构

  <swiper-item>
    <scroll-view class="lyric-list" scroll-y>
    <block wx:for="{{lyricInfos}}" wx:key="time">
      <view class="item">{{item.text}}</view>
    </block> 
    </scroll-view>
  </swiper-item>

样式

/* 歌词的样式 */
.lyric-list {
  color: #aaa;
  font-size: 28rpx;
  text-align: center;
  height: 100%;
  box-sizing: border-box;
  padding: 40rpx;
}

.lyric-list ::-webkit-scrollbar {
  display: none;
}

.lyric-list .item {
  margin: 40rpx;
}

.lyric-list .item.active {
  color: #0f0;
  font-size: 32rpx;
}

21_(理解)内容回顾和作业布置

第七章:内容回顾

一. 播放页布局

1.0. 点击Item跳转

1.1. 背景毛玻璃

  • image -> 背景
  • view -> blur毛玻璃

1.2. 自定义导航栏(掌握)

1.3. 导航栏标题和页面左右滚动

  • Swiper/Swiper-Item 轮播图
  • 标题的点击 -> 轮播图的切换

1.4. 歌曲页面的布局

  • album
  • info
  • lyric
  • progress
  • controls

二. 歌曲进度的控制

2.1. 歌曲播放

  • audioContext = wx.createInnerAudioContext()
  • src属性
  • autoplay

2.2. onTimeUpdate

  • 改变当前的时间和sliderValue

2.3. 监听slider的点击

  • bindchange
    • value
    • 计算currentTime
    • seek(currentTime)
    • setData({currentTime, value})

2.4. 监听slider的滑动

  • bindchanging
    • value
    • currentTime
    • isSliderChanging

2.5. seek(time) bug处理

  • throttle: 节流控制
    • leading: false
    • trailing: false
  • isWaiting: true

2.6. 播放和暂停功能

三. 歌词的展示

3.1. 请求和解析歌词

  • 正则表达式

3.2. time匹配歌词

  • 循环所有的歌词, 找到第一个比当前时间大的一句歌词

    • i - 1
  • index: length - 1

  • currentLyricText

  • currentLyricIndex

    • 性能优化 index === currentLyricIndex return

3.3. 歌词的文字匹配思路

3.4. 全部歌词的展示

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/194380.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关于xxl-job中的慢sql引发的磁盘I/O飙升导致拖垮整个数据库服务

背景&#xff1a; 某天突然发现服务探测接口疯狂告警、同时数据库CPU消耗也告警&#xff0c;最后系统都无法访问&#xff1b; 查看服务端日志&#xff0c;发现大量的报错如下&#xff1a; CommunicationsException: Communications link failure &#xff1a;The last packet s…

dapr入门与本地托管模式尝试

1 简介 Dapr是一个可移植的、事件驱动的运行时&#xff0c;它使任何开发人员能够轻松构建出弹性的、无状态和有状态的应用程序&#xff0c;并可运行在云平台或边缘计算中&#xff0c;它同时也支持多种编程语言和开发框架。Dapr支持的语言很多&#xff0c;包括C/Go/Java/JavaSc…

antv/x6 2.x 搭建流程图编辑页面(1)

进来闲来无事&#xff0c;看到x6 2.x版本也更新了有几个月了&#xff0c;便想着熟悉下2.x版本 一、首先搭建项目基础框架。 // yarn 方式 yarn create vitejs/app v3-ts --template vue-ts cd v3-ts yarn yarn dev// npm npm init vitejs/app v3-ts --template vue-ts cd v3…

人工神经网络BP神经网络结构及优化原理单隐层,多隐层及反向传播梯度下降释义

神经网络&#xff1a;人工神经网络&#xff08;Artificial Neural Networks&#xff0c;简写为ANNs&#xff09;也简称为神经网络&#xff08;NNs&#xff09;或称作连接模型&#xff08;Connection Model&#xff09;&#xff0c;它是一种模仿动物神经网络行为特征&#xff0c…

ASEMI整流模块MDA110-16参数,MDA110-16规格

编辑-Z ASEMI整流模块MDA110-16参数&#xff1a; 型号&#xff1a;MDA110-16 最大重复峰值反向电压&#xff08;VRRM&#xff09;&#xff1a;1600V 最大RMS电桥输入电压&#xff08;VRMS&#xff09;&#xff1a;1700V 最大平均正向整流输出电流&#xff08;IF&#xff0…

多线程synchronized对象锁与静态锁之间的8锁问题理解

目录8锁问题锁1&#xff1a;多个线程使用同一对象分别调用不同带有带synchronized关键字且非静态的方法锁2&#xff1a;在锁1基础上&#xff0c;增加A线程执行的方法的执行时间&#xff0c;使得B有机会参与执行锁3&#xff1a;多个线程使用同一对象&#xff0c;一个线程执行带有…

三菱Q系列QJ71C24N模块 MODBUS通信(含完整步骤+源代码)

MODBUS通信的其它相关基础知识请参看下面的文章链接: SMART S7-200PLC MODBUS通信_RXXW_Dor的博客-CSDN博客MODBUS 是 OSI 模型第 7 层上的应用层报文传输协议,它在连接至不同类型总线或网络的设备之间提供客户机/服务器通信。自从 1979 年出现工业串行链路的事实标准以来,…

7z压缩包有可能被破解吗,需要多久,暴力穷举和字典破解分别的速度分析

开门见山&#xff0c;我看到网上有很多此类软件&#xff0c;功能就是来破解7zip格式的压缩包&#xff0c;但都没有认真进行测试&#xff0c;这里认真进行判断和测试 首先&#xff0c;目前世界最快的计算机为Frontier&#xff0c;算力1,685.65 PFlop/s。目前最高的算力为全网比…

【IoT】智能小产品:简易电子琴设计

说明 随着电子技术的发展&#xff0c;电子技术正在逐渐改善着人们的学习、生活、工作&#xff0c;因此开发本系统希望能够给人们多带来一点生活上的乐趣&#xff0c;电子技术与音乐的结合不断加深。 由此而产生的电子琴在这种形势下&#xff0c;因其体积小&#xff0c;易于携…

芯科EFRBG22C112 empty工程创建

目录 1.使用工具 2.创建工程 2.1进入welcome,选择芯片&#xff0c;点击start ​编辑 2.2选择sdk4.2.0版本 2.3点击Create New Project 2.4同步步骤创建引导程序bootloader-apploader 3.遇到问题 1.使用工具 ARM仿真器 EFR32BG22C112芯片开发板 pc 使用ARM仿真器连接BG22…

PMP考试多少分算通过?

PMP 是没有具体分数的&#xff0c;只有等级&#xff0c; PASS 就是通过了这张成绩单就是 PASS&#xff0c;3A。主要看头部这里&#xff0c;PASS 就是通过了&#xff0c;FAIL 就是没通过。至于说的 3A&#xff0c;2A1T 啥的&#xff0c;是对应的每个模块的成绩&#xff0c;PMP 的…

【leetcode - 基础】编程能力( 4 / 20天 )

目录 Day - 1 896. 单调数列 - 简单ac 28. 找出字符串中第一个匹配项的下标 - KMP 1、kmp模板 2、java api Day - 2 110. 平衡二叉树 - 递归 1、自顶向下 - 暴力 459. 重复的子字符串 - 暴力 Day - 3 150. 逆波兰表达式求值 - 栈 后缀表达式 66.加一 - 模拟 Day…

Tensorflow 基础与实战

目录 一、线性回归实现 1.1 数据加载与查看绘图 1.2 模型建立、训练与预测 二、神经网络实现 2.1 数据加载与查看绘图 2.2 模型建立、训练与预测 三、逻辑回归实现 3.1 数据加载与查看绘图 3.2 模型建立、训练与预测 四、softmax分类 4.1 数据加载 4.2 数据归一化 …

ReadyAPI x64bit 3.43.0 Crack

ReadyAPI 允许团队在一个集中的界面中创建、管理和执行自动化的功能、安全和性能测试——为敏捷和 DevOps 软件团队提高 API 质量。 ReadyAPI 在单一平台上提供三个模块&#xff1a; 在ReadyAPI 测试模块中&#xff0c;您创建功能测试以验证您的服务是否按预期工作。您可以使用…

高频数组算法

1.二分查找 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否则返回 -1。 示例 1: 输入: nums [-1,0,3,5,9,12], target 9 输…

程序员的职业生涯个人规划(附上学习资料)

目录 前言 1.程序员的规划 2.程序员的进化路径 3.IT行业的难度 4.你的选择 5.再谈谈资本、工具人和内卷 总结&#xff1a; 前言 今天不讲技术也不讲干货&#xff0c;来聊一聊程序员职业生涯的个人规划。希望对不管是想入门的还是已经在这条路上的朋友能有一个帮助。 1.程…

opencv之直方图绘制及均衡化

直方图均衡化优势&#xff1a;增强图像对比度&#xff0c;使对比度较低的局部区域获得高对比度。当图像非常暗或者非常亮&#xff0c;并且背景和前景之间的差异非常小时&#xff0c;此方法非常有效&#xff0c;能够解决暴露过度或暴露不足的问题。直方图均衡化缺陷&#xff1a;…

多彩的书写工具,画图写字更好看,米家液晶小黑板多彩版上手

平时临时记些信息&#xff0c;或者是教小朋友认字的时候&#xff0c;液晶小黑板都是特别实用的工具&#xff0c;我之前就用过米家液晶小黑板&#xff0c;最近我发现米家新出了一款彩色笔迹的小黑板&#xff0c;书写效果更加好看&#xff0c;比以前单色款的更好用。 这款米家液晶…

【技术分享】MA21日均线交易策略

文章目录1.前言1.1.相关术语1.2.MA均线系统设置分类1.3.斐波那契数列2.MA21日均线2.1.MA21日均线2.2.MA21日均线交易策略2.3.MA21日均线案例分析1.前言 1.1.相关术语 空头市场&#xff08;Bear Market&#xff09;&#xff1a;亦称熊市&#xff0c;指价格长期呈 下跌 趋势的证…

操作系统权限提升(十)之系统错误配置-计划任务提权

系列文章 操作系统权限提升(一)之操作系统权限介绍 操作系统权限提升(二)之常见提权的环境介绍 操作系统权限提升(三)之Windows系统内核溢出漏洞提权 操作系统权限提升(四)之系统错误配置-Tusted Service Paths提权 操作系统权限提升(五)之系统错误配置-PATH环境变量提权 操作…