功能介绍

功能概览

1.创建页面music-player
2.监听item的点击
方式一:直接写在子组件上
绑定监听点击 需要获取点击的歌曲 和 歌曲播放的列表数据
为了获取item 我们需要自定义data-item=“” ,将数据传递过去

方式二:在封装的组件上的更元素上绑定
本身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})
}
})
通过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>
播放页功能实现
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) ;
}
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;
}
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>
歌曲和歌词的页面-导航切换–可以当做轮播图用来切换
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
},
})
}
})
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>
导航标题的点击和切换
注意点:轮播图里面的current属性可以显示当前展示的页面

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}}">
结构
<!-- 具体内容 -->
<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;
}
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
}
监听歌曲播放的时间 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>
各种事件可以通过查文档
点击滑块改变歌曲播放的进度
完成一次拖动后触发的事件事件:点击滑块事件 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>
拖动过程中触发的事件 动滑块改变歌曲播放进度事件 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" />
点击滑块跳动的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
})
},
播放按钮的点击 监听点击事件 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"/>
歌词的获取
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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
}
根据时间匹配歌词

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})
})
应为trailing的时间节点和刚好点击的时间节点碰到一起,trailing为true会帮我们执行一次,所以出现的bug
trailing为true会帮我们执行一次(默认为true)
// 4.监听播放的进度
const throttleUpdateProgress = throttle(this.updateProgress,800,{leading:false,trailing:false})
歌词每个字的精确匹配需要后端传过来的数据也是包含每个字的精确字符串时间,所以我们没办法实现
但是我们可以模拟实现 根据播完这个课词的所耗时的时间范围,播完这个歌词完成平均的动画渲染
结构
<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;
}
循环所有的歌词, 找到第一个比当前时间大的一句歌词
index: length - 1
currentLyricText
currentLyricIndex
我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0
Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack
我想用ruby编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序
我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此
我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r
刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr
我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R
我在Rails应用程序中使用CarrierWave/Fog将视频上传到AmazonS3。有没有办法判断上传的进度,让我可以显示上传进度如何? 最佳答案 CarrierWave和Fog本身没有这种功能;你需要一个前端uploader来显示进度。当我不得不解决这个问题时,我使用了jQueryfileupload因为我的堆栈中已经有jQuery。甚至还有apostonCarrierWaveintegration因此您只需按照那里的说明操作即可获得适用于您的应用的进度条。 关于ruby-on-r
如何检查Ruby文件是否是通过“require”或“load”导入的,而不是简单地从命令行执行的?例如:foo.rb的内容:puts"Hello"bar.rb的内容require'foo'输出:$./foo.rbHello$./bar.rbHello基本上,我想调用bar.rb以不执行puts调用。 最佳答案 将foo.rb改为:if__FILE__==$0puts"Hello"end检查__FILE__-当前ruby文件的名称-与$0-正在运行的脚本的名称。 关于ruby-检查是否
是否可以在应用程序中包含的gem代码中知道应用程序的Rails文件系统根目录?这是gem来源的示例:moduleMyGemdefself.included(base)putsRails.root#returnnilendendActionController::Base.send:include,MyGem谢谢,抱歉我的英语不好 最佳答案 我发现解决类似问题的解决方案是使用railtie初始化程序包含我的模块。所以,在你的/lib/mygem/railtie.rbmoduleMyGemclassRailtie使用此代码,您的模块将在