Vue Mobile Music App learning [16]: player lyrics display development

vue mobile music app learning


Player lyrics data capture

stay api Create under folder song.js Set up to get songs api

import axios from 'axios';
export function getLyric(id) {
return axios.get(`/api/lyric?id=${id}`);
}

Encapsulate this method into common->js-> Under the song class , The lyrics can be understood as song A property of . You can't get the lyrics directly, you need to call this interface , So here it is song Extend a method getLyric

import { getLyric } from '../../api/song';
import { ERR_OK } from '../../api/config';
export default class Song {
// song Of id, singer , The name of the song name, Album name album, Song length duration, Song picture img, The real path of songs url
 constructor({
id, singer, name, album, duration, image, url,
}) {
this.id = id;
this.singer = singer;
this.name = name;
this.album = album;
this.duration = duration;
this.image = image;
this.url = url;
}
getLyric() {
getLyric(this.id).then((res) => {
if (res.data.code === ERR_OK) {
this.lyric = res.data.lrc.lyric;
console.log(this.lyric);
}
});
}
}

stay player Component's watch Call to see if you can get the lyrics correctly

 

 watch: {
currentSong(newSong, oldSong) {
if (newSong.id === oldSong.id) {
return;
}
this.$nextTick(() => {
this.$refs.audio.play().catch((error) => {
this.togglePlaying();
// eslint-disable-next-line no-alert
alert(' Error playing , No resources available for this song ');
});
this.currentSong.getLyric();
});
},

 

Player lyrics data analysis

You can see that the lyrics are very long character string . The next step is Parse string . Using third party libraries lyric-parser , It supports incoming lyricStr and handler, When the lyrics keep playing , It will be executed at every time point handler function

 

npm install lyric-parser@1.0.1

 

There is a place to optimize before data analysis :currentSong Every time it changes, it calls song Of getLyric Method , There will be many requests , This is unreasonable .

So add a logic And make use of Promise To transform

getLyric() {
if (this.lyric) {
// getLyric What it returns is Promise
return Promise.resolve(this.lyric);
}
// encapsulation Promise, Just for lyrics 
return new Promise((resolve, reject) => {
getLyric(this.id).then((res) => {
if (res.data.code === ERR_OK) {
this.lyric = res.data.lrc.lyric;
resolve(this.lyric);
} else {
// No lyrics 
// eslint-disable-next-line prefer-promise-reject-errors
reject('no lyric');
}
});
});
}

stay player Add plug-ins to components lyric-parser, And modify watch Inside currentSong(), Do not call directly currentSong.getLyric(), stay methods Inner package getLyric()

 

import Lyric from 'lyric-parser';
currentSong(newSong, oldSong) {
...
// It's not called directly here currentSong.getLyric()
this.getLyric();
});
},

 

 getLyric() {
this.currentSong.getLyric().then((lyric) => {
this.currentLyric = new Lyric(lyric);
console.log(this.currentLyric);
});
},
// stay data Add in currentLyric
 data() {
return {
songReady: false,
currentTime: 0,
radius: 32,
currentLyric: null,
};
},

Print to see lyric object The data of , There's one in the data lines, Every lines Object has time and txt

Player lyrics scrolling list implementation

stay player Component add the following dom structure : Use to get currentLyric, And traverse lines object

 <div class="middle-r" ref="lyricList">
<div class="lyric-wrapper">
<div v-if="currentLyric">
<p ref="lyricLine" class="text" v-for="(line,index) in currentLyric.lines" :key="index">{{line.text}}</p>
</div>
</div>
</div>

In the browser first middle-l Of dom Structure deleted , You can see a list of lyrics

 

 

The current lyrics list is not scrollable , It can't display the corresponding lyrics in real time according to the playing of songs , I need to deal with it

When executed getLyric() when , When you get the lyrics , call this.currentLyric.play(), So the lyrics will play . You also need to pass a callback function during initialization handleLyric().

getLyric() {
this.currentSong.getLyric().then((lyric) => {
this.currentLyric = new Lyric(lyric, this.handleLyric);
if (this.playing) {
this.currentLyric.play();
}
console.log(this.currentLyric);
});
},

Define this method handleLyric(): When every line of the lyrics changes , It just calls back , Highlight the current lyrics .

  • First you need to go to data To define a currentLineNum, Represents the current line .

 

 data() {
return {
songReady: false,
currentTime: 0,
radius: 32,
currentLyric: null,
currentLineNum: 0,
};
},
  • In just now dom Structure is bound to a class: When currentLineNum be equal to index It shows that current style , Achieve highlight effect
<p ref="lyricLine" class="text" :class="{'current':currentLineNum === index}" v-for="(line,index) in currentLyric.lines" :key="index">{{line.txt}}</p>
  • stay handleLyric Set up current currentLine be equal to index, So you can see the current lyrics
handleLyric({ lineNum, txt }) {
this.currentLineNum = lineNum;
},

If you want to scroll the lyrics list , It needs to be used scroll Components . towards scroll Pass in data In order to currentLyric When it changes , It can automatically call its refresh Method

<scroll class="middle-r" ref="lyricList" :data="currentLyric&&currentLyric.lines">
<div class="lyric-wrapper">
<div v-if="currentLyric">
<p ref="lyricLine" class="text" :class="{'current':currentLineNum === index}" v-for="(line,index) in currentLyric.lines" :key="index">{{line.txt}}</p>
</div>
</div>
</scroll>
import Scroll from '../../base/scroll/scroll';
components: {
ProgressBar,
ProgressCircle,
Scroll,
},

When we play the lyrics in the middle ( The second part of the lyrics 5-6 OK, let's start , It's guaranteed to be in the middle of the screen ), It's going to roll up and down . At this time, if you scroll to a position, it will also roll back .

handleLyric({ lineNum, txt }) {
this.currentLineNum = lineNum;
if (lineNum > 5) {
let lineEl = this.$refs.lyricLine[lineNum - 5];
this.$refs.lyricList.scrollToElement(lineEl, 1000);
} else {
this.$refs.lyricList.scrollTo(0, 0, 1000);
}
},

Player lyrics slide left and right to achieve

On the player page there's a dot Of dom structure

<div class="bottom">
<div class="dot-wrapper">
<span class="dot"></span>
<span class="dot"></span>
</div>
...
</div>

Now which point should be active, Maintain this state with a variable .currentShow The default is cd, When switching to the right page ,currentShow Just change it to lyric.

 data() {
return {
songReady: false,
currentTime: 0,
radius: 32,
currentLyric: null,
currentLineNum: 0,
currentShow: 'cd',
};
},
<span class="dot" :class="{'active':currentShow==='cd'}"></span>
<span class="dot" :class="{'active':currentShow==='lyric'}"></span>

The next step is to slide left and right .

  • stay created() define touch Variable
  • to middle binding touch event ,touchStart,touchMove,touchEnd, And define these three methods ( It also adds some animation effects to make the sliding picture not stiff )
middleTouchStart(e) {
this.touch.initiated = true;
const touch = e.touches[0];
// Record X Coordinates and Y coordinate 
this.touch.startX = touch.pageX;
this.touch.startY = touch.pageY;
},
middleTouchMove(e) {
if (!this.touch.initiated) {
return;
}
const touch = e.touches[0];
const deltaX = touch.pageX - this.touch.startX;
// Why maintain the ordinate , Because lyrics roll with scroll It's a process of rolling up and down , When the vertical axis offset is greater than the horizontal axis offset , You shouldn't move left and right 
const deltaY = touch.pageY - this.touch.startY;
if (Math.abs(deltaY) > Math.abs(deltaX)) {
return;
}
// While scrolling , Need to know how wide the lyrics list scrolls . First of all, record it in the process of scrolling ,middle-r The width to the right 
const left = this.currentShow === 'cd' ? 0 : -window.innerWidth;
// No more than 0
const offsetWidth = Math.min(0, Math.max(-window.innerWidth, left + deltaX));
// lyricList It's actually a scroll Components , namely vue Components , It can't be operated directly dom, Need access to it element Ability to visit dom
// The sliding ratio = The width of the left width of the list / The width of the entire screen 
this.touch.percent = Math.abs(offsetWidth / window.innerWidth);
this.$refs.lyricList.$el.style[transform] = `translate3d(${offsetWidth}px,0,0)`;
this.$refs.lyricList.$el.style[transitionDuration] = 0;
// percent The bigger it is , The less transparent it is 
this.$refs.middleL.style.opacity = 1 - this.touch.percent;
this.$refs.middleL.style[transitionDuration] = 0;
},
middleTouchEnd() {
let offsetWidth;
let opacity;
// The lyrics page slides from right to left 
if (this.currentShow === 'cd') {
if (this.touch.percent > 0.1) {
offsetWidth = -window.innerWidth;
opacity = 0;
this.currentShow = 'lyric';
} else {
offsetWidth = 0;
opacity = 1;
}
} else { // Slide from left to right 
// If you slide over 10% It's going to move back 
// eslint-disable-next-line no-lonely-if
if (this.touch.percent < 0.9) {
offsetWidth = 0;
opacity = 1;
this.currentShow = 'cd';
} else {
offsetWidth = -window.innerWidth;
opacity = 0;
}
}
const time = 300;
this.$refs.lyricList.$el.style[transform] = `translate3d(${offsetWidth}px,0,0)`;
// Add some animation , So that the sliding effect does not regenerate hard 
this.$refs.lyricList.$el.style[transitionDuration] = `${time}ms`;
this.$refs.middleL.style.opacity = opacity;
this.$refs.middleL.style[transitionDuration] = `${time}ms`;
},
View Code

The remaining functions of the player are realized

The following problems need to be improved

  • When switching songs many times , The lyrics will keep beating back and forth

reason : The lyrics are in currentLyric Some of the functions inside the object realize jumping , Every currentLyric The interior uses a timer Realize the play of the song and jump to the corresponding position . Every time currentSong When things change , It'll be all over again new A new lyric-parser come out , But the previous objects didn't clean up , That's what happened before currentLyric There's still a timer in there , So it makes the lyrics flash back and forth bug.

resolvent : Cutting currentSong I.e. re getLyric Before , hold Current getLyric to stop fall

 currentSong(newSong, oldSong) {
if (newSong.id === oldSong.id) {
return;
}
if (this.currentLyric) {
this.currentLyric.stop();
}
...
},
  • When you click pause , The lyrics didn't stop rolling

reason : When the playback state changes , The playing state of the lyrics has not changed

resolvent : stay togglePlaying() Add judgment logic to : When the playback state changes , The playing state of the lyrics also changes

 togglePlaying() {
// without ready Well, go straight back 
if (!this.songReady) {
return;
}
this.setPlayingState(!this.playing);
if (this.currentLyric) {
this.currentLyric.togglePlay();
}
},
  • In loop mode , When you cut the progress bar to the end and return it to its original position , The lyrics didn't go back to their original position

resolvent : stay loop() To implement logic , Using lyrics seek Method to shift it to the initial position .

loop() {
this.$refs.audio.currentTime = 0;
this.$refs.audio.play();
if (this.currentLyric) {
this.currentLyric.seek(0);
}
},
  • While dragging the progress bar , The lyrics don't change with the progress bar

resolvent : stay onProgressBarChange() To implement logic , It's also called lyrics seek Method

onProgressBarChange(percent) {
const currentTime = this.currentSong.duration * percent;
this.$refs.audio.currentTime = currentTime;
if (!this.playing) {
this.togglePlaying();
}
if (this.currentLyric) {
this.currentLyric.seek(currentTime * 1000);
}
},
  • stay cd Lyrics below , So users don't have to switch to the lyrics list to see the lyrics every time

resolvent : stay cd-wrapper Add a div, Incoming data playingLyric.playingLyric stay handleLyric Change when executing .

<div class="middle-l" ref="middleL">
<div class="cd-wrapper" ref="cdWrapper">
<div class="cd" :class="cdCls">
<img class="image" :src="currentSong.image">
</div>
</div>
<div class="playing-lyric-wrapper">
<div class="playing-lyric">{{playingLyric}}</div>
</div>
</div>
data() {
return {
...
playingLyric: '',
};
},
handleLyric({ lineNum, txt }) {
...
this.playingLyric = txt;
},
  • consider getLyric Abnormal situation : When you can't get the lyrics , To clean up
getLyric() {
this.currentSong.getLyric().then((lyric) => {
this.currentLyric = new Lyric(lyric, this.handleLyric);
if (this.playing) {
this.currentLyric.play();
}
}).catch(() => {
this.currentLyric = null;
this.playingLyric = '';
this.currentLineNum = 0;
});
},
  • Consider the boundary conditions : When there is only one song in the song list , What's wrong with clicking on the next or previous song ? In the source code index = currentIndex + 1,currentIndex = 0; here index be equal to playlist The length of , Reset to 0, Then execute the following logic to currentIndex Set as 0, be playlist It won't change ,currentSong Of id It won't change , The logic after that doesn't work .

resolvent : stay next() and prev() Add a judgment , If playlist The length of is 1 when , Just let it use loop() Do a single loop .

prev() {
// without ready Well, go straight back The following logic cannot be used to implement the function 
if (!this.songReady) {
return;
}
if (this.playlist.length === 1) {
this.loop();
} else {
let index = this.currentIndex - 1;
if (index === -1) {
index = this.playlist.length - 1;
}
this.setCurrentIndex(index);
if (!this.playing) {
this.togglePlaying();
}
}
this.songReady = false;
},
  • When we play on wechat , actually js It won't be implemented, but audio You can play the current song . Once the song is played, it will trigger end event , however end Event is js Not execute . If end Don't execute , So when you play it again ,songReady It will never be set to true, We can't switch songs .

resolvent : Give Way audio Of play The delay time of the method is a little longer , Make sure to switch from the background to the foreground in the mobile browser js When it comes to execution , The player can play normally .

currentSong(newSong, oldSong) {
if (newSong.id === oldSong.id) {
return;
}
if (this.currentLyric) {
this.currentLyric.stop();
}
setTimeout(() => {
this.$refs.audio.play().catch((error) => {
this.togglePlaying();
// eslint-disable-next-line no-alert
alert(' Error playing , No resources available for this song ');
}, 1000);
// It's not called directly here currentSong.getLyric()
this.getLyric();
});
},

Player bottom, player adaptation

Previously, the scrolling height of the page was calculated to the end , But now with the mini player, it takes up a certain height at the bottom ,scroll The height of the roll is wrong .

monitor playerlist, If there is playerlist When ,scoll Component's bottom The value is reset to mini-player Height , Let it recalculate scorll The height of the roll .

Because these components all need to deal with this problem , The logic of dealing with this problem is very similar , have access to mixin

establish mixin.js

export const playlistMixin = {
computed: {
// adopt getters Get playlist
 ...mapGetters([
'playlist',
]),
},
mounted() {
this.handlePlaylist(this.playlist);
},
activated() {
this.handlePlaylist(this.playlist);
},
watch: {
playlist(newVal) {
this.handlePlaylist(newVal);
},
},
methods: {
handlePlaylist() {
// The specific method should be implemented by specific components 
// Throw an exception , The component must implement this function , Once the component defines this function , It will cover mixin This function in . If not, call mixin This function in 
throw new Error('component must implement handlePlaylist method');
},
},
};

One component can insert multiple mixin, So there's a mixins Attributes use . stay music-list Component application mixin, Once the component is used mixin, You have to define handlePlaylist Otherwise, it will report an error .

Definition handlePlaylist Method , Judgment if any playlist, Change... Change list Of bottom And force scroll Recalculate

 

import { playlistMixin } from '../../common/js/mixin';
mixins: [playlistMixin],
handlePlaylist(playlist) {
const bottom = playlist.length > 0 ? '60px' : '';
this.$refs.list.$el.style.bottom = bottom;
// call refresh() Give Way scroll Recalculation of height 
this.$refs.list.refresh();
},

 

singer The same goes for components , But you need to call listview Let it recalculate , stay listview.vue One of them is exposed refresh After the method , And then singer.vue Call in

 

refresh() {
this.$refs.listview.refresh();
},
<div class="singer" ref="singer">
<list-view @select="selectSinger" :data="singers" ref="list"></list-view>
<router-view></router-view>
</div>
handlePlaylist(playlist) {
const bottom = playlist.length > 0 ? '60px' : '';
this.$refs.list.$el.style.bottom = bottom;
// call refresh() Give Way scroll Recalculation of height
this.$refs.list.refresh();
},

Finally, modify the recommendation page

handlePlaylist(playlist) {
const bottom = playlist.length > 0 ? '60px' : '';
this.$refs.recommend.style.bottom = bottom;
// call refresh() Give Way scroll Recalculation of height 
this.$refs.scroll.refresh();
},

 

版权声明
本文为[The little windmill creaks and turns]所创,转载请带上原文链接,感谢
https://qdmana.com/2021/05/20210503224558606A.html

  1. 21. Object oriented foundation "problems and solutions of object traversal"
  2. Discussion on hot micro front end: Google AdWords is a real micro front end
  3. Usecallback and usememo for real performance optimization
  4. 【前端圭臬】十一:从规范看 JavaScript 执行上下文(下)
  5. [front end standard] 11: Javascript execution context from the perspective of specification (2)
  6. Hexagonal六角形架构ReactJS的实现方式 - Janos Pasztor
  7. Transaction of spring's reactive / imperative relational database
  8. The implementation of hexagonal hexagonal reactjs Janos pasztor
  9. HTTP状态码:402 Payment Required需要付款 - mozilla
  10. HTTP status code: 402 payment required - Mozilla
  11. Factory mode, constructor mode and prototype mode
  12. Build the scaffold of react project from scratch (Series 1: encapsulating a request method with cache function based on Axios)
  13. Cocos Quick Start Guide
  14. Comparison of three default configurations of webpack5 modes
  15. A case study of the combination of flutter WebView and Vue
  16. CSS: BFC and IFC
  17. A common error report and solution in Vue combat
  18. JS: this point
  19. JS: prototype chain
  20. JavaScript series -- promise, generator, async and await
  21. JS: event flow
  22. Front end performance optimization: rearrangement and redrawing
  23. JS - deep and shallow copy
  24. JavaScript异步编程3——Promise的链式使用
  25. JavaScript asynchronous programming 3 -- chain use of promise
  26. Vue.js组件的使用
  27. The use of vue.js component
  28. How to judge whether a linked list has links
  29. Element UI custom theme configuration
  30. Text image parallax effect HTML + CSS + JS
  31. Spring的nohttp宣言:消灭http://
  32. Vue3 intermediate guide - composition API
  33. Analysis of URL
  34. These 10 widgets that every developer must know
  35. Spring's nohttp Manifesto: eliminate http://
  36. Learn more about JS prototypes
  37. Refer to await to JS to write an await error handling
  38. A short article will directly let you understand what the event loop mechanism is
  39. Vue3 uses mitt for component communication
  40. Characteristics and thinking of ES6 symbol
  41. Two way linked list: I'm no longer one-way driving
  42. Vue event and form processing
  43. Reactive TraderCloud实时外汇开源交易平台
  44. Reactive tradercloud real time foreign exchange open source trading platform
  45. Node.js REST API的10个最佳实践
  46. Ten best practices of node.js rest API
  47. Fiddler advanced usage
  48. Process from Vue template to render
  49. Promise up (asynchronous or synchronous)
  50. Principle and implementation of promise
  51. Vs code plug in sharing - run code
  52. Vue practical notes (1) introduction of Ant Design
  53. Vue actual combat notes (2) introduction of element plus
  54. Introduction to webpack
  55. Webpack construction process
  56. Vue notes
  57. The experience and lessons of moving from ruby megalith architecture to go microservice
  58. Using leancloud to add artitalk module to hexo blog
  59. Implementation of chrome request filtering extension
  60. Detailed introduction of beer import declaration elements and label quarantine [import knowledge]