Skip to content

Commit cd21726

Browse files
committed
lrc module
1 parent 3c594eb commit cd21726

3 files changed

Lines changed: 136 additions & 142 deletions

File tree

src/js/lrc.js

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import tplLrc from '../template/lrc.art';
2+
3+
class Lrc {
4+
constructor (options) {
5+
this.container = options.container;
6+
this.async = options.async;
7+
this.player = options.player;
8+
this.content = options.content;
9+
this.parsed = [];
10+
this.index = 0;
11+
this.current = [];
12+
}
13+
14+
update (currentTime = this.player.audio.currentTime) {
15+
if (this.index > this.current.length - 1 || currentTime < this.current[this.index][0] || (!this.current[this.index + 1] || currentTime >= this.current[this.index + 1][0])) {
16+
for (let i = 0; i < this.current.length; i++) {
17+
if (currentTime >= this.current[i][0] && (!this.current[i + 1] || currentTime < this.current[i + 1][0])) {
18+
this.index = i;
19+
this.container.style.transform = `translateY(${-this.index * 16}px)`;
20+
this.container.style.webkitTransform = `translateY(${-this.index * 16}px)`;
21+
this.container.getElementsByClassName('aplayer-lrc-current')[0].classList.remove('aplayer-lrc-current');
22+
this.container.getElementsByTagName('p')[i].classList.add('aplayer-lrc-current');
23+
}
24+
}
25+
}
26+
}
27+
28+
switch (index) {
29+
if (!this.parsed[index]) {
30+
if (!this.async) {
31+
if (this.content[index]) {
32+
this.parsed[index] = this.parse(this.content[index]);
33+
}
34+
else {
35+
this.parsed[index] = [['00:00', 'Not available']];
36+
}
37+
}
38+
else {
39+
this.parsed[index] = [['00:00', 'Loading']];
40+
const xhr = new XMLHttpRequest();
41+
xhr.onreadystatechange = () => {
42+
if (xhr.readyState === 4) {
43+
if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
44+
this.parsed[index] = this.parse(xhr.responseText);
45+
}
46+
else {
47+
console.log('Request was unsuccessful: ' + xhr.status);
48+
this.parsed[index] = [['00:00', 'Not available']];
49+
}
50+
51+
this.container.innerHTML = tplLrc({
52+
lyrics: this.parsed[index]
53+
});
54+
this.update(0);
55+
this.current = this.parsed[index];
56+
}
57+
};
58+
const apiurl = this.content[index];
59+
xhr.open('get', apiurl, true);
60+
xhr.send(null);
61+
}
62+
}
63+
64+
this.container.innerHTML = tplLrc({
65+
lyrics: this.parsed[index]
66+
});
67+
this.update(0);
68+
this.current = this.parsed[index];
69+
}
70+
71+
/**
72+
* Parse lrc, suppose multiple time tag
73+
*
74+
* @param {String} lrc_s - Format:
75+
* [mm:ss.xx]lyric
76+
* [mm:ss.xxx]lyric
77+
* [mm:ss.xx][mm:ss.xx][mm:ss.xx]lyric
78+
*
79+
* @return {String} [[time, text], [time, text], [time, text], ...]
80+
*/
81+
parse (lrc_s) {
82+
const lyric = lrc_s.split('\n');
83+
const lrc = [];
84+
const lyricLen = lyric.length;
85+
for (let i = 0; i < lyricLen; i++) {
86+
// match lrc time
87+
const lrcTimes = lyric[i].match(/\[(\d{2}):(\d{2})\.(\d{2,3})]/g);
88+
// match lrc text
89+
const lrcText = lyric[i].replace(/\[(\d{2}):(\d{2})\.(\d{2,3})]/g, '').replace(/^\s+|\s+$/g, '');
90+
91+
if (lrcTimes) {
92+
// handle multiple time tag
93+
const timeLen = lrcTimes.length;
94+
for (let j = 0; j < timeLen; j++) {
95+
const oneTime = /\[(\d{2}):(\d{2})\.(\d{2,3})]/.exec(lrcTimes[j]);
96+
const lrcTime = oneTime[1] * 60 + parseInt(oneTime[2]) + parseInt(oneTime[3]) / ((oneTime[3] + '').length === 2 ? 100 : 1000);
97+
lrc.push([lrcTime, lrcText]);
98+
}
99+
}
100+
}
101+
// sort by time
102+
lrc.sort((a, b) => a[0] - b[0]);
103+
return lrc;
104+
}
105+
}
106+
107+
export default Lrc;

src/js/player.js

Lines changed: 26 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import handleOption from './options';
44
import Template from './template';
55
import Bar from './bar';
66
import User from './user';
7+
import Lrc from './lrc';
78

89
const instances = [];
910

@@ -17,40 +18,11 @@ class APlayer {
1718
*/
1819
constructor (options) {
1920
this.options = handleOption(options);
21+
this.container = this.options.container;
2022

2123
this.audios = [];
2224
this.mode = this.options.mode;
2325

24-
// save lrc
25-
this.container = this.options.container;
26-
if (this.options.showlrc === 2 || this.options.showlrc === true) {
27-
this.savelrc = [];
28-
const lrcEle = this.container.getElementsByClassName('aplayer-lrc-content');
29-
for (let i = 0; i < lrcEle.length; i++) {
30-
this.savelrc.push(lrcEle[i].innerHTML);
31-
}
32-
}
33-
this.lrcs = [];
34-
35-
/**
36-
* Update lrc
37-
*
38-
* @param {Number} currentTime
39-
*/
40-
this.updateLrc = (currentTime = this.audio.currentTime) => {
41-
if (this.lrcIndex > this.lrc.length - 1 || currentTime < this.lrc[this.lrcIndex][0] || (!this.lrc[this.lrcIndex + 1] || currentTime >= this.lrc[this.lrcIndex + 1][0])) {
42-
for (let i = 0; i < this.lrc.length; i++) {
43-
if (currentTime >= this.lrc[i][0] && (!this.lrc[i + 1] || currentTime < this.lrc[i + 1][0])) {
44-
this.lrcIndex = i;
45-
this.template.lrc.style.transform = `translateY(${-this.lrcIndex * 16}px)`;
46-
this.template.lrc.style.webkitTransform = `translateY(${-this.lrcIndex * 16}px)`;
47-
this.template.lrc.getElementsByClassName('aplayer-lrc-current')[0].classList.remove('aplayer-lrc-current');
48-
this.template.lrc.getElementsByTagName('p')[i].classList.add('aplayer-lrc-current');
49-
}
50-
}
51-
}
52-
};
53-
5426
// define APlayer events
5527
const eventTypes = ['play', 'pause', 'canplay', 'playing', 'ended', 'error'];
5628
this.event = {};
@@ -96,6 +68,17 @@ class APlayer {
9668
this.container.classList.add('aplayer-narrow');
9769
}
9870

71+
// save lrc
72+
this.container = this.options.container;
73+
if (this.options.showlrc === 2 || this.options.showlrc === true) {
74+
const lrcEle = this.container.getElementsByClassName('aplayer-lrc-content');
75+
for (let i = 0; i < lrcEle.length; i++) {
76+
if (this.options.music[i]) {
77+
this.options.music[i].lrc = lrcEle[i].innerHTML;
78+
}
79+
}
80+
}
81+
9982
this.template = new Template({
10083
container: this.container,
10184
options: this.options,
@@ -106,6 +89,15 @@ class APlayer {
10689
this.template.time.classList.add('aplayer-time-narrow');
10790
}
10891

92+
if (this.options.showlrc) {
93+
this.lrc = new Lrc({
94+
container: this.template.lrc,
95+
async: this.options.showlrc === 3,
96+
content: this.options.music.map((item) => item.lrc),
97+
player: this,
98+
});
99+
}
100+
109101
this.bar = new Bar(this.template);
110102

111103
// play and pause button
@@ -160,9 +152,7 @@ class APlayer {
160152
percentage = percentage > 0 ? percentage : 0;
161153
percentage = percentage < 1 ? percentage : 1;
162154
this.bar.set('played', percentage, 'width');
163-
if (this.options.showlrc) {
164-
this.updateLrc(this.bar.get('played', 'width') * this.audio.duration);
165-
}
155+
this.lrc && this.lrc.update(this.bar.get('played', 'width') * this.audio.duration);
166156
this.template.ptime.innerHTML = utils.secondToTime(percentage * this.audio.duration);
167157
};
168158

@@ -176,9 +166,7 @@ class APlayer {
176166
this.audio.currentTime = this.bar.get('played', 'width') * this.audio.duration;
177167
this.playedTime = setInterval(() => {
178168
this.bar.set('played', this.audio.currentTime / this.audio.duration, 'width');
179-
if (this.options.showlrc) {
180-
this.updateLrc();
181-
}
169+
this.lrc && this.lrc.update();
182170
this.template.ptime.innerHTML = utils.secondToTime(this.audio.currentTime);
183171
this.trigger('playing');
184172
}, 100);
@@ -344,9 +332,7 @@ class APlayer {
344332
}
345333
this.playedTime = setInterval(() => {
346334
this.bar.set('played', this.audio.currentTime / this.audio.duration, 'width');
347-
if (this.options.showlrc) {
348-
this.updateLrc();
349-
}
335+
this.lrc && this.lrc.update();
350336
this.template.ptime.innerHTML = utils.secondToTime(this.audio.currentTime);
351337
this.trigger('playing');
352338
}, 100);
@@ -448,109 +434,7 @@ class APlayer {
448434
this.audios[indexMusic] = this.audio;
449435
}
450436

451-
/**
452-
* Parse lrc, suppose multiple time tag
453-
*
454-
* @param {String} lrc_s - Format:
455-
* [mm:ss.xx]lyric
456-
* [mm:ss.xxx]lyric
457-
* [mm:ss.xx][mm:ss.xx][mm:ss.xx]lyric
458-
*
459-
* @return {String} [[time, text], [time, text], [time, text], ...]
460-
*/
461-
const parseLrc = (lrc_s) => {
462-
const lyric = lrc_s.split('\n');
463-
const lrc = [];
464-
const lyricLen = lyric.length;
465-
for (let i = 0; i < lyricLen; i++) {
466-
// match lrc time
467-
const lrcTimes = lyric[i].match(/\[(\d{2}):(\d{2})\.(\d{2,3})]/g);
468-
// match lrc text
469-
const lrcText = lyric[i].replace(/\[(\d{2}):(\d{2})\.(\d{2,3})]/g, '').replace(/^\s+|\s+$/g, '');
470-
471-
if (lrcTimes) {
472-
// handle multiple time tag
473-
const timeLen = lrcTimes.length;
474-
for (let j = 0; j < timeLen; j++) {
475-
const oneTime = /\[(\d{2}):(\d{2})\.(\d{2,3})]/.exec(lrcTimes[j]);
476-
const lrcTime = oneTime[1] * 60 + parseInt(oneTime[2]) + parseInt(oneTime[3]) / ((oneTime[3] + '').length === 2 ? 100 : 1000);
477-
lrc.push([lrcTime, lrcText]);
478-
}
479-
}
480-
}
481-
// sort by time
482-
lrc.sort((a, b) => a[0] - b[0]);
483-
return lrc;
484-
};
485-
486-
// fill in lrc
487-
if (this.options.showlrc) {
488-
const index = indexMusic;
489-
490-
if (!this.lrcs[index]) {
491-
let lrcs = '';
492-
if (this.options.showlrc === 1) {
493-
lrcs = this.options.music[index].lrc;
494-
}
495-
else if (this.options.showlrc === 2 || this.options.showlrc === true) {
496-
lrcs = this.savelrc[index];
497-
}
498-
else if (this.options.showlrc === 3) {
499-
const xhr = new XMLHttpRequest();
500-
xhr.onreadystatechange = () => {
501-
if (xhr.readyState === 4) {
502-
if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
503-
lrcs = xhr.responseText;
504-
this.lrcs[index] = parseLrc(lrcs);
505-
}
506-
else {
507-
console.log('Request was unsuccessful: ' + xhr.status);
508-
this.lrcs[index] = [['00:00', 'Not available']];
509-
}
510-
this.lrc = this.lrcs[index];
511-
let lrcHTML = '';
512-
for (let i = 0; i < this.lrc.length; i++) {
513-
lrcHTML += `<p>${this.lrc[i][1]}</p>`;
514-
}
515-
this.template.lrc.innerHTML = lrcHTML;
516-
if (!this.lrcIndex) {
517-
this.lrcIndex = 0;
518-
}
519-
this.template.lrc.getElementsByTagName('p')[0].classList.add('aplayer-lrc-current');
520-
this.template.lrc.style.transform = 'translateY(0px)';
521-
this.template.lrc.style.webkitTransform = 'translateY(0px)';
522-
}
523-
};
524-
const apiurl = this.options.music[index].lrc;
525-
xhr.open('get', apiurl, true);
526-
xhr.send(null);
527-
}
528-
if (lrcs) {
529-
this.lrcs[index] = parseLrc(lrcs);
530-
}
531-
else {
532-
if (this.options.showlrc === 3) {
533-
this.lrcs[index] = [['00:00', 'Loading']];
534-
}
535-
else {
536-
this.lrcs[index] = [['00:00', 'Not available']];
537-
}
538-
}
539-
}
540-
541-
this.lrc = this.lrcs[index];
542-
let lrcHTML = '';
543-
for (let i = 0; i < this.lrc.length; i++) {
544-
lrcHTML += `<p>${this.lrc[i][1]}</p>`;
545-
}
546-
this.template.lrc.innerHTML = lrcHTML;
547-
if (!this.lrcIndex) {
548-
this.lrcIndex = 0;
549-
}
550-
this.template.lrc.getElementsByTagName('p')[0].classList.add('aplayer-lrc-current');
551-
this.template.lrc.style.transform = 'translateY(0px)';
552-
this.template.lrc.style.webkitTransform = 'translateY(0px)';
553-
}
437+
this.lrc && this.lrc.switch(indexMusic);
554438

555439
// set duration time
556440
if (this.audio.duration !== 1) { // compatibility: Android browsers will output 1 at first

src/template/lrc.art

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{{each lyrics}}
2+
<p{{ if $index === 0 }} class="aplayer-lrc-current"{{ /if }}>{{$value[1]}}</p>
3+
{{/each}}

0 commit comments

Comments
 (0)