Introduction to the

@diyGod /APlayer is a simple and beautiful HTML5 music player (” blue ω Blue “). When I first saw the appearance level of this player, I was very impressed by those designers who can design a beautiful interface (* >ω<).

But after using, I found that there are still insufficient places. This is the Issues I have mentioned

I have been using APlayer for a while and like the simplicity of its UI. Here are some other suggestions for improvement:

1. I think it’s necessary to provide an API for dynamically managing playlists (if not, you can only re-initialize when you need to dynamically add songs to the list) 2. 3. Lyrics are allowed to be added asynchronously, usually the interface to get lyrics is separate (now you have to wait for the lyrics interface to return and then initialize the player, if the lyrics fail to get or the time is too long, the music playing function will be affected simultaneously).

Regarding the third item, APlayer actually supports asynchronous lyrics, but only supports the address of the incoming.lrc file. If it is returned in JSON format like netease Cloud/QQ Music, it does not meet the requirement

Why not mention that PR has to be rewritten? I have thought about this for a while, but I still think component-based development is better (the original APlayer uses native JS and does not rely on other libraries). Besides, I am quite experienced in writing music player (imitated weibo player, at that time I will not use Git source has been lost) when I was working on the back end. Rewrite a difficulty is not easy, and more arbitrary, but also feel free to add something you want

screenshots

Note: This player is based on the layout and style of @DiyGod /APlayer using TS + Vue componentized reconstruction

Demo: aplayer.quq.cat Documentation: aplayer.quq.cat/docs source: github.com/MoeFE/vue-…. NPM:www.npmjs.com/package… Playlist from netease Cloud Playlist: music.163.com/#/playli…

If you like don’t forget to point a star yo (*ゝω ·) welcome to mention Issues and PR (´ _ · ‘)

Selection of the framework

For the convenience of use, I choose Vue, which can control the attributes of the player in a responsive manner and release them in the form of plug-ins (see demo for details). In order to facilitate better debugging, SourceMap and DevTools enabled in production If you have vue-devTools installed you can open the debugger to view component partitioning and information about individual components

I don’t want to go into too much detail about why we use TypeScript and you can look up the difference between TypeScript and JavaScript on the web. Okay

I can only tell you: it’s not too cool for a developer who used to use C#.

Finally, we recommend a TS + Vue scaffold template: github.com/Toilal/vue…. Later or will be added to the official template: github.com/vuejs-temp….

TS + React scaffolding can be used with this: github.com/wmonk/crea….

Break up the component

The first thing you do when you get the layout is split up the components copy the layout and style of @diyGod /APlayer to make sure that the style is ok and then copy the layout and style of each component individually if you don’t know the design you have to copy it please allow me to make a sad face (ಗ ‸ ಗ)

I split the player into the following components:

Component name Component description
APlayer.ts Player container Components
Button.ts Button component
Picture.ts Song Picture component
Container.ts Right side container Component
Info.ts Song information component
Lyric.ts Lyrics Panel Component
Progress.ts Progress bar component
Time.ts Play time component
Volume.ts Volume control unit
List.ts Playlist component
Item.ts Playlist item component

Here’s a clearer picture:

Click to see the original hd image

The function development

It’s really not that difficult to develop functionality, HTML5 has already wrapped the HTMLAudioElement element and we’re just going to use its API and view for data binding and interaction and just look at the documentation

But there was a slight problem, and that was that Vue couldn’t listen for changes in the properties of the Audio object because the Audio object was an HTMLAudioElement, and Vue couldn’t listen for changes in the properties of the element, so I came up with a trick

We define a Media interface that defines the same properties as the Audio object. Synchronize the Media properties in the Audio event so that we can use the Media object to retrieve the value of the Audio property. APlayer.ts#L326-L334

Let me briefly introduce some of the more common properties and methods

The name of the instructions
autoplay Whether auto play (inSafariCall the play method manually after initializing the audio.
bufferd Get buffered progress (must be inreadeyState >= 3Fetch later, otherwise an exception will be thrown)
loop Whether to play the audio loop (it is recommended to implement this function according to the current playing mode)
preload Preloading option, recommendedmetadataGet only the length of the audio when it is not playing instead of loading the entire audio
src Gets or sets the playback address of the audio
volume Gets or sets the audio volume (0 ~ 1)
paused Gets whether the current audio has been paused
currentTime Gets or sets the current audio playback progress (in seconds)
duration Gets the length of the current audio in seconds
playbackRate Gets or sets the playback speed of the current audio
play () Playback of audio
pause () Pause the audio

Click to see all Media events

In fact Audio and Video objects are pretty much Media objects so if you can develop music players you can also develop Video players

I’m going to focus on the Playing event, which is the event that is constantly triggered when the audio is playing, and this is probably the most useful event because you have to constantly redraw the progress of the player and the time that it has been playing, and then synchronize the lyrics to the current time of the song

If you don’t have or don’t know about this event, you might use setInterval instead of setInterval, and there are two problems: 1. How much redraw time is appropriate? 2. If the user pauses the playback, the timer needs to be cleared, and the timer needs to be initialized when the playback starts, which is too troublesome (or lazy, you can judge the return when paused, so you need to continuously run an empty timer).

LRC lyrics analysis and synchronization

Probably the most fun to do this function QWQ, because long ago I used to do LRC lyrics when I was bored, so I am sensitive to this function try to do the best (´ _ ‘)

The main function here is lyrics analysis. For lyrics synchronization, you only need to calculate the item element that best matches the current playing time and then set the scroll bar position of lyrics panel to the current element position

Common time labels are as follows



[mm:ss:ms] have minute, second, millisecond time tag [mm:ss:ms] have minute, second, millisecond time tag another format [mm:ss:ms] [mm:ss.msMultiple time tags share the same lyricCopy the code

Here’s my idea: First, divide the lyric text into an array according to the line, and then parse it by line. Use regular expression to match the minutes, seconds, and milliseconds of the line and the displayed lyric text. Convert the minutes, seconds, and milliseconds into millisecond units, and then add them up and associate them with the lyric text and save them in the array. Finally, we need to arrange the lyrics in chronological order so that the current lyrics to display = the last item in the filter array after the current playing time



private async parseLRC (): Promise<void> {
  if (!this.lrc || this.lrc === 'loading') return
  if (this.isURL(this.lrc)) { // If the lyric is a URL, that address is requested to get the text of the lyric
    const { data } = await Axios.get(this.lrc.toString())
    this.currentLRC = data
  } else this.currentLRC = this.lrc

  const reg = /\[(\d+):(\d+)[.|:](\d+)\](.+)/
  const regTime = /\[(\d+):(\d+)[.|:](\d+)\]/g
  const regCompatible = /\[(\d+):(\d+)]()(.+)/
  const regTimeCompatible = /\[(\d+):(\d+)]/g
  const regOffset = / \ [offset: \ s * (- {0, 1} \ d +) \] /
  const offsetMatch = this.lrc.match(regOffset)
  const offset = offsetMatch ? Number.parseInt(offsetMatch[1) :0
  this.LRC = []

  const matchAll = (line: string) = > {
    let match = line.match(reg) || line.match(regCompatible)
    if(! match)return
    if(match.length ! = =5) return
    const minutes = Number.parseInt(match[1) | |0
    const seconds = Number.parseInt(match[2) | |0
    const milliseconds = Number.parseInt(match[3) | |0
    const time = (minutes * 60 * 1000 + seconds * 1000 + milliseconds) + offset
    const text = (match[4] as string).replace(regTime, ' ').replace(regTimeCompatible, ' ')
    if(! text)return // optimization: do not display blank lines
    this.LRC.push({ time, text })
    matchAll(match[4]) // Recursively matches multiple time tags
  }

  this.currentLRC.replace(/\\n/g.'\n').split('\n').forEach(line= > matchAll(line))

  // Lyrics format not supported
  if (this.LRC.length <= 0) this.LRC = [{ time: - 1, text: '(· ∀ · *) Sorry, the lyric format is not supported ' }]
  else this.LRC.sort((a, b) = > a.time - b.time)
}Copy the code

Click here to see the full code

conclusion

Improved the shortcomings of the original APlayer: 1. Responsive control of player attributes 2. Support multiple time tag format (FIX #39) 3. Sync lyrics compatible with [offset:0] tag 4. Asynchronous lyrics support 5. Allow to control the playback speed (the same song with different speed listening will feel different) 6. Volume allow drag control 7. Support registration of all Media events

And experienced the pleasure of writing Vue with TS

(๑•̀ㅂ•́)و✧.. There is no requirement for a cute profile picture!!

I wish I had a big boy who could take me with me