初步完善歌词
This commit is contained in:
@@ -85,7 +85,7 @@ div(:class="$style.player")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Lyric from 'lrc-file-parser'
|
||||
import Lyric from '@renderer/utils/lyric-font-player'
|
||||
import { rendererSend, rendererOn, NAMES } from '../../../common/ipc'
|
||||
import { formatPlayTime2, getRandom, checkPath, setTitle, clipboardWriteText, debounce, throttle, assertApiSupport } from '../../utils'
|
||||
import { mapGetters, mapActions, mapMutations } from 'vuex'
|
||||
@@ -452,6 +452,9 @@ export default {
|
||||
})
|
||||
|
||||
window.lrc = new Lyric({
|
||||
className: 'lrc-content',
|
||||
shadowContent: false,
|
||||
activeLineClassName: 'active',
|
||||
onPlay: (line, text) => {
|
||||
this.lyric.text = text
|
||||
this.lyric.line = line
|
||||
@@ -463,7 +466,7 @@ export default {
|
||||
this.lyric.lines = lines
|
||||
this.lyric.line = 0
|
||||
},
|
||||
offset: 80,
|
||||
// offset: 80,
|
||||
})
|
||||
|
||||
this.handleRegisterEvent('on')
|
||||
@@ -641,6 +644,7 @@ export default {
|
||||
this.getLrc(targetSong).then(() => {
|
||||
this.musicInfo.lrc = targetSong.lrc
|
||||
this.musicInfo.tlrc = targetSong.tlrc
|
||||
this.musicInfo.lxlrc = targetSong.lxlrc
|
||||
}).catch(() => {
|
||||
this.status = this.statusText = this.$t('core.player.lyric_error')
|
||||
}).finally(() => {
|
||||
@@ -658,6 +662,7 @@ export default {
|
||||
this.musicInfo.songmid = null
|
||||
this.musicInfo.lrc = null
|
||||
this.musicInfo.tlrc = null
|
||||
this.musicInfo.lxlrc = null
|
||||
this.musicInfo.url = null
|
||||
this.nowPlayTime = 0
|
||||
this.maxPlayTime = 0
|
||||
@@ -848,7 +853,14 @@ export default {
|
||||
})
|
||||
},
|
||||
setLyric() {
|
||||
window.lrc.setLyric((this.setting.player.isShowLyricTransition && this.musicInfo.tlrc ? (this.musicInfo.tlrc + '\n') : '') + (this.musicInfo.lrc || ''))
|
||||
window.lrc.setLyric(
|
||||
this.musicInfo.lxlrc ? this.musicInfo.lxlrc : this.musicInfo.lrc,
|
||||
// (
|
||||
// this.setting.player.isShowLyricTransition && this.musicInfo.tlrc
|
||||
// ? (this.musicInfo.tlrc + '\n')
|
||||
// : ''
|
||||
// ) + (this.musicInfo.lrc || ''),
|
||||
)
|
||||
if (this.isPlay && (this.musicInfo.url || this.listId == 'download')) {
|
||||
window.lrc.play(audio.currentTime * 1000)
|
||||
this.handleUpdateWinLyricInfo('play', audio.currentTime * 1000)
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
div(:class="$style.right")
|
||||
div(:class="[$style.lyric, lyricEvent.isMsDown ? $style.draging : null]" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric")
|
||||
div(:class="$style.lyricSpace")
|
||||
p(v-for="(info, index) in lyricLines" :key="index" :class="lyric.line == index ? $style.lrcActive : null") {{info.text}}
|
||||
div(:class="[$style.lyricText]" ref="dom_lyric_text")
|
||||
//- p(v-for="(info, index) in lyricLines" :key="index" :class="lyric.line == index ? $style.lrcActive : null") {{info.text}}
|
||||
div(:class="$style.lyricSpace")
|
||||
|
||||
material-music-comment(:class="$style.comment" :titleFormat="this.setting.download.fileName" :musicInfo="musicInfo" v-model="isShowComment")
|
||||
@@ -158,11 +159,14 @@ export default {
|
||||
handler(n, o) {
|
||||
this.isSetedLines = true
|
||||
if (o) {
|
||||
this.$refs.dom_lyric_text.textContent = ''
|
||||
this.setLyric(n)
|
||||
|
||||
this._lyricLines = n
|
||||
if (n.length) {
|
||||
this.lyricLines = n
|
||||
this.$nextTick(() => {
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
|
||||
this.handleScrollLrc()
|
||||
})
|
||||
} else {
|
||||
@@ -174,7 +178,7 @@ export default {
|
||||
if (this.lyricLines === this._lyricLines && this._lyricLines.length) return
|
||||
this.lyricLines = this._lyricLines
|
||||
this.$nextTick(() => {
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
|
||||
this.handleScrollLrc()
|
||||
})
|
||||
}, 50)
|
||||
@@ -182,7 +186,7 @@ export default {
|
||||
} else {
|
||||
this.lyricLines = n
|
||||
this.$nextTick(() => {
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
|
||||
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
|
||||
this.handleScrollLrc()
|
||||
})
|
||||
}
|
||||
@@ -235,6 +239,8 @@ export default {
|
||||
document.addEventListener('mousemove', this.handleMouseMsMove)
|
||||
document.addEventListener('mouseup', this.handleMouseMsUp)
|
||||
window.addEventListener('resize', this.handleResize)
|
||||
console.log('object', this.$refs.dom_lyric_text)
|
||||
this.setLyric(this.lyricLines)
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.clearLyricScrollTimeout()
|
||||
@@ -250,6 +256,13 @@ export default {
|
||||
...mapMutations('player', [
|
||||
'visiblePlayerDetail',
|
||||
]),
|
||||
setLyric(lines) {
|
||||
const dom_lines = document.createDocumentFragment()
|
||||
for (const line of lines) {
|
||||
dom_lines.appendChild(line.dom_line)
|
||||
}
|
||||
this.$refs.dom_lyric_text.appendChild(dom_lines)
|
||||
},
|
||||
handleResize() {
|
||||
this.setProgressWidth()
|
||||
},
|
||||
@@ -540,6 +553,7 @@ export default {
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
content: ' ';
|
||||
@@ -568,13 +582,39 @@ export default {
|
||||
&.draging {
|
||||
cursor: grabbing;
|
||||
}
|
||||
p {
|
||||
padding: 8px 0;
|
||||
line-height: 1.2;
|
||||
overflow-wrap: break-word;
|
||||
transition: @transition-theme !important;
|
||||
transition-property: color, font-size;
|
||||
:global {
|
||||
.lrc-content {
|
||||
line-height: 1.2;
|
||||
padding: 8px 0;
|
||||
overflow-wrap: break-word;
|
||||
|
||||
&.active {
|
||||
span {
|
||||
// color: @color-theme;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
transition: @transition-theme !important;
|
||||
transition-property: font-size;
|
||||
font-size: 1em;
|
||||
background-repeat: no-repeat;
|
||||
background-color: rgba(77, 77, 77, 0.9);
|
||||
background-image: -webkit-linear-gradient(top, @color-theme, @color-theme);
|
||||
-webkit-text-fill-color: transparent;
|
||||
-webkit-background-clip: text;
|
||||
background-size: 0 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
// p {
|
||||
// padding: 8px 0;
|
||||
// line-height: 1.2;
|
||||
// overflow-wrap: break-word;
|
||||
// transition: @transition-theme !important;
|
||||
// transition-property: color, font-size;
|
||||
// }
|
||||
}
|
||||
.lyric-space {
|
||||
height: 70%;
|
||||
@@ -736,6 +776,7 @@ each(@themes, {
|
||||
.container {
|
||||
border-left-color: ~'@{color-@{value}-theme}';
|
||||
background-color: ~'@{color-@{value}-theme_2-background_1}';
|
||||
// color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
.right {
|
||||
&:before {
|
||||
@@ -769,9 +810,17 @@ each(@themes, {
|
||||
box-shadow: 0 0 4px ~'@{color-@{value}-theme-hover}';
|
||||
// border-color: ~'@{color-@{value}-theme-hover}';
|
||||
}
|
||||
.lrc-active {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
:global {
|
||||
.lrc-content {
|
||||
span {
|
||||
// background-color: ~'@{color-@{value}-theme_2-font}';
|
||||
background-image: -webkit-linear-gradient(top, ~'@{color-@{value}-theme}', ~'@{color-@{value}-theme}');
|
||||
}
|
||||
}
|
||||
}
|
||||
// .lrc-active {
|
||||
// color: ~'@{color-@{value}-theme}';
|
||||
// }
|
||||
.footerLeftControlBtns {
|
||||
color: ~'@{color-@{value}-theme_2-font}';
|
||||
}
|
||||
|
||||
@@ -187,16 +187,21 @@ const actions = {
|
||||
if (musicInfo.lrc && musicInfo.tlrc != null) {
|
||||
if (musicInfo.lrc.startsWith('\ufeff[id:$00000000]')) {
|
||||
let str = musicInfo.lrc.replace('\ufeff[id:$00000000]\n', '')
|
||||
commit('setLrc', { musicInfo, lyric: str, tlyric: musicInfo.tlrc })
|
||||
commit('setLrc', { musicInfo, lyric: str, tlyric: musicInfo.tlrc, lxlyric: musicInfo.tlrc })
|
||||
} else if (musicInfo.lrc.startsWith('[id:$00000000]')) {
|
||||
let str = musicInfo.lrc.replace('[id:$00000000]\n', '')
|
||||
commit('setLrc', { musicInfo, lyric: str, tlyric: musicInfo.tlrc, lxlyric: musicInfo.tlrc })
|
||||
}
|
||||
|
||||
if ((musicInfo.lxlrc == null && musicInfo.source != 'kg') || musicInfo.lxlrc != null) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
|
||||
// lrcRequest = music[musicInfo.source].getLyric(musicInfo)
|
||||
return getLyric.call(this, musicInfo).then(({ lyric, tlyric }) => {
|
||||
return getLyric.call(this, musicInfo).then(({ lyric, tlyric, lxlyric }) => {
|
||||
// lrcRequest = null
|
||||
commit('setLrc', { musicInfo, lyric, tlyric })
|
||||
commit('setLrc', { musicInfo, lyric, tlyric, lxlyric })
|
||||
}).catch(err => {
|
||||
// lrcRequest = null
|
||||
return Promise.reject(err)
|
||||
@@ -332,6 +337,7 @@ const mutations = {
|
||||
setLrc(state, datas) {
|
||||
datas.musicInfo.lrc = datas.lyric
|
||||
datas.musicInfo.tlrc = datas.tlyric
|
||||
datas.musicInfo.lxlrc = datas.lxlyric
|
||||
},
|
||||
setList(state, { list, index }) {
|
||||
state.playMusicInfo = {
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
const { getNow, TimeoutTools } = require('./utils')
|
||||
|
||||
// const fontFormateRxp = /(?=<\d+,\d+>).*?/g
|
||||
const fontSplitRxp = /(?=<\d+,\d+>).*?/g
|
||||
const timeRxp = /<(\d+),(\d+)>/
|
||||
|
||||
|
||||
// Create animation
|
||||
const createAnimation = (dom, duration) => new window.Animation(new window.KeyframeEffect(dom, [
|
||||
{ backgroundSize: '0 100%' },
|
||||
{ backgroundSize: '100% 100%' },
|
||||
], {
|
||||
duration,
|
||||
easing: 'linear',
|
||||
},
|
||||
), document.timeline)
|
||||
|
||||
|
||||
// https://jsfiddle.net/ceqpnbky/
|
||||
// https://jsfiddle.net/ceqpnbky/1/
|
||||
|
||||
module.exports = class FontPlayer {
|
||||
constructor({ lyric = '', className = '', shadowContent = false, shadowClassName = '' }) {
|
||||
this.lyric = lyric
|
||||
|
||||
this.className = className
|
||||
this.shadowContent = shadowContent
|
||||
this.shadowClassName = shadowClassName
|
||||
|
||||
this.isPlay = false
|
||||
this.curFontNum = 0
|
||||
this.maxFontNum = 0
|
||||
this._performanceTime = 0
|
||||
this._performanceOffsetTime = 0
|
||||
|
||||
this.fontContent = null
|
||||
|
||||
this.timeoutTools = new TimeoutTools()
|
||||
this.waitPlayTimeout = new TimeoutTools()
|
||||
|
||||
this._init()
|
||||
}
|
||||
|
||||
_init() {
|
||||
if (this.lyric == null) this.lyric = ''
|
||||
|
||||
this.isLineMode = false
|
||||
|
||||
this.fontContent = document.createElement('div')
|
||||
this.fontContent.style = 'position:relative;'
|
||||
this.fontContent.className = this.className
|
||||
if (this.shadowContent) {
|
||||
this.fontShadowContent = document.createElement('div')
|
||||
this.fontShadowContent.style = 'position:absolute;top:0;left:0;width:100%;z-index:-1;'
|
||||
this.fontShadowContent.className = this.shadowClassName
|
||||
this.fontContent.appendChild(this.fontShadowContent)
|
||||
}
|
||||
this._parseLyric()
|
||||
}
|
||||
|
||||
_parseLyric() {
|
||||
const fonts = this.lyric.split(fontSplitRxp)
|
||||
// console.log(fonts)
|
||||
|
||||
this.maxFontNum = fonts.length - 1
|
||||
this.fonts = []
|
||||
let text
|
||||
for (const font of fonts) {
|
||||
text = font.replace(timeRxp, '')
|
||||
if (RegExp.$2 == '') return this._handleLineParse()
|
||||
const time = parseInt(RegExp.$2)
|
||||
|
||||
const dom = document.createElement('span')
|
||||
let shadowDom
|
||||
dom.textContent = text
|
||||
const animation = createAnimation(dom, time)
|
||||
this.fontContent.appendChild(dom)
|
||||
|
||||
if (this.shadowContent) {
|
||||
shadowDom = document.createElement('span')
|
||||
shadowDom.textContent = text
|
||||
this.fontShadowContent.appendChild(shadowDom)
|
||||
}
|
||||
// dom.style = shadowDom.style = this.fontStyle
|
||||
// dom.className = shadowDom.className = this.fontClassName
|
||||
|
||||
this.fonts.push({
|
||||
text,
|
||||
startTime: parseInt(RegExp.$1),
|
||||
time,
|
||||
dom,
|
||||
shadowDom,
|
||||
animation,
|
||||
})
|
||||
}
|
||||
// console.log(this.fonts)
|
||||
}
|
||||
|
||||
_handleLineParse() {
|
||||
this.isLineMode = true
|
||||
const dom = document.createElement('span')
|
||||
let shadowDom
|
||||
dom.textContent = this.lyric
|
||||
if (this.shadowContent) {
|
||||
shadowDom = document.createElement('span')
|
||||
shadowDom.textContent = this.lyric
|
||||
this.fontShadowContent.appendChild(shadowDom)
|
||||
}
|
||||
this.fontContent.appendChild(dom)
|
||||
this.fonts.push({
|
||||
text: this.lyric,
|
||||
dom,
|
||||
shadowDom,
|
||||
})
|
||||
}
|
||||
|
||||
_currentTime() {
|
||||
return getNow() - this._performanceTime + this._performanceOffsetTime
|
||||
}
|
||||
|
||||
_findcurFontNum(curTime) {
|
||||
const length = this.fonts.length
|
||||
for (let index = 0; index < length; index++) if (curTime <= this.fonts[index].startTime) return index === 0 ? 0 : index - 1
|
||||
return length - 1
|
||||
}
|
||||
|
||||
_handlePlayMaxFontNum() {
|
||||
let curFont = this.fonts[this.curFontNum]
|
||||
// console.log(curFont.text)
|
||||
const currentTime = this._currentTime()
|
||||
const driftTime = currentTime - curFont.startTime
|
||||
if (currentTime > curFont.startTime + curFont.time) {
|
||||
this._handlePlayFont(curFont, driftTime, true)
|
||||
this.pause()
|
||||
} else {
|
||||
this._handlePlayFont(curFont, driftTime)
|
||||
this.isPlay = false
|
||||
}
|
||||
}
|
||||
|
||||
_handlePlayFont(font, currentTime, toFinishe) {
|
||||
switch (font.animation.playState) {
|
||||
case 'finished':
|
||||
break
|
||||
case 'idle':
|
||||
font.dom.style.backgroundSize = '100% 100%'
|
||||
if (!toFinishe) font.animation.play()
|
||||
break
|
||||
default:
|
||||
if (toFinishe) {
|
||||
font.animation.cancel()
|
||||
} else {
|
||||
font.animation.currentTime = currentTime
|
||||
font.animation.play()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
_handlePlayLine(isPlayed) {
|
||||
this.isPlay = false
|
||||
this.fonts[0].dom.style.backgroundSize = isPlayed ? '100% 100%' : '100% 0'
|
||||
}
|
||||
|
||||
_handlePauseFont(font) {
|
||||
if (font.animation.playState == 'running') font.animation.pause()
|
||||
}
|
||||
|
||||
_refresh() {
|
||||
this.curFontNum++
|
||||
// console.log('curFontNum time', this.fonts[this.curFontNum].time)
|
||||
if (this.curFontNum === this.maxFontNum) return this._handlePlayMaxFontNum()
|
||||
let curFont = this.fonts[this.curFontNum]
|
||||
let nextFont = this.fonts[this.curFontNum + 1]
|
||||
// console.log(curFont, nextFont, this.curFontNum, this.maxFontNum)
|
||||
const currentTime = this._currentTime()
|
||||
// console.log(curFont.text)
|
||||
const driftTime = currentTime - curFont.startTime
|
||||
|
||||
// console.log(currentTime, driftTime)
|
||||
|
||||
if (driftTime >= 0 || this.curFontNum == 0) {
|
||||
this.delay = nextFont.startTime - curFont.startTime - driftTime
|
||||
if (this.delay > 0) {
|
||||
this._handlePlayFont(curFont, driftTime)
|
||||
this.timeoutTools.start(() => {
|
||||
this._refresh()
|
||||
}, this.delay)
|
||||
return
|
||||
}
|
||||
} else if (this.curFontNum == 0) {
|
||||
this.curFontNum--
|
||||
this.waitPlayTimeout.start(() => {
|
||||
this._refresh()
|
||||
}, -driftTime)
|
||||
return
|
||||
}
|
||||
|
||||
this.curFontNum = this._findcurFontNum(currentTime)
|
||||
for (let i = 0; i < this.curFontNum; i++) this._handlePlayFont(this.fonts[i], 0, true)
|
||||
this.curFontNum--
|
||||
this._refresh()
|
||||
}
|
||||
|
||||
play(curTime = 0) {
|
||||
// console.log('play', curTime)
|
||||
if (!this.fonts.length) return
|
||||
this.pause()
|
||||
|
||||
if (this.isLineMode) return this._handlePlayLine(true)
|
||||
this.isPlay = true
|
||||
this._performanceTime = getNow() - curTime
|
||||
this._performanceOffsetTime = 0
|
||||
if (this._performanceTime < 0) {
|
||||
this._performanceOffsetTime = -this._performanceTime
|
||||
this._performanceTime = 0
|
||||
}
|
||||
|
||||
this.curFontNum = this._findcurFontNum(curTime)
|
||||
for (let i = this.curFontNum; i > -1; i--) {
|
||||
this._handlePlayFont(this.fonts[i], 0, true)
|
||||
}
|
||||
for (let i = this.curFontNum, len = this.fonts.length; i < len; i++) {
|
||||
let font = this.fonts[i]
|
||||
font.animation.cancel()
|
||||
font.dom.style.backgroundSize = '0 100%'
|
||||
}
|
||||
|
||||
this.curFontNum--
|
||||
|
||||
this._refresh()
|
||||
}
|
||||
|
||||
pause() {
|
||||
if (!this.isPlay) return
|
||||
this.isPlay = false
|
||||
this.timeoutTools.clear()
|
||||
this.waitPlayTimeout.clear()
|
||||
this._handlePauseFont(this.fonts[this.curFontNum])
|
||||
if (this.curFontNum === this.maxLine) return
|
||||
const curFontNum = this._findcurFontNum(this._currentTime())
|
||||
if (this.curFontNum === curFontNum) return
|
||||
for (let i = 0; i < this.curFontNum; i++) this._handlePlayFont(this.fonts[i], 0, true)
|
||||
}
|
||||
|
||||
finish() {
|
||||
this.pause()
|
||||
if (this.isLineMode) return this._handlePlayLine(true)
|
||||
|
||||
for (const font of this.fonts) {
|
||||
font.animation.cancel()
|
||||
font.dom.style.backgroundSize = '100% 100%'
|
||||
}
|
||||
this.curFontNum = this.maxFontNum
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.pause()
|
||||
if (this.isLineMode) return this._handlePlayLine(false)
|
||||
for (const font of this.fonts) {
|
||||
font.animation.cancel()
|
||||
font.dom.style.backgroundSize = '0 100%'
|
||||
}
|
||||
this.curFontNum = 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
const LinePlayer = require('./line-player')
|
||||
const FontPlayer = require('./font-player')
|
||||
|
||||
const fontTimeExp = /<(\d+),(\d+)>/g
|
||||
|
||||
module.exports = class Lyric {
|
||||
constructor({
|
||||
lyric = '',
|
||||
offset = 150,
|
||||
className = '',
|
||||
activeLineClassName = 'active',
|
||||
shadowClassName = '',
|
||||
shadowContent = false,
|
||||
onPlay = function() { },
|
||||
onSetLyric = function() { },
|
||||
}) {
|
||||
this.lyric = lyric
|
||||
this.offset = offset
|
||||
this.onPlay = onPlay
|
||||
this.onSetLyric = onSetLyric
|
||||
|
||||
this.className = className
|
||||
this.activeLineClassName = activeLineClassName
|
||||
this.shadowClassName = shadowClassName
|
||||
this.shadowContent = shadowContent
|
||||
|
||||
this.playingLineNum = -1
|
||||
this.isLineMode = false
|
||||
}
|
||||
|
||||
_init() {
|
||||
this.playingLineNum = -1
|
||||
this.isLineMode = false
|
||||
|
||||
if (this.linePlayer) {
|
||||
this.linePlayer.setLyric(this.lyric)
|
||||
} else {
|
||||
this.linePlayer = new LinePlayer({
|
||||
lyric: this.lyric,
|
||||
offset: this.offset,
|
||||
onPlay: this._handleLinePlayerOnPlay,
|
||||
onSetLyric: this._handleLinePlayerOnSetLyric,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
_handleLinePlayerOnPlay = (num, text, curTime) => {
|
||||
if (this.isLineMode) {
|
||||
if (num < this.playingLineNum + 1) {
|
||||
for (let i = this.playingLineNum; i > num - 1; i--) {
|
||||
const font = this._lineFonts[i]
|
||||
font.reset()
|
||||
font.fontContent.classList.remove(this.activeLineClassName)
|
||||
}
|
||||
} else if (num > this.playingLineNum + 1) {
|
||||
for (let i = Math.max(this.playingLineNum, 0); i < num; i++) {
|
||||
const font = this._lineFonts[i]
|
||||
font.reset()
|
||||
font.fontContent.classList.remove(this.activeLineClassName)
|
||||
}
|
||||
} else if (this.playingLineNum > -1) {
|
||||
const font = this._lineFonts[this.playingLineNum]
|
||||
font.reset()
|
||||
font.fontContent.classList.remove(this.activeLineClassName)
|
||||
}
|
||||
} else {
|
||||
if (num < this.playingLineNum + 1) {
|
||||
for (let i = this.playingLineNum; i > num - 1; i--) {
|
||||
const font = this._lineFonts[i]
|
||||
font.fontContent.classList.remove(this.activeLineClassName)
|
||||
font.reset()
|
||||
}
|
||||
} else if (num > this.playingLineNum + 1) {
|
||||
for (let i = Math.max(this.playingLineNum, 0); i < num; i++) {
|
||||
const font = this._lineFonts[i]
|
||||
font.fontContent.classList.remove(this.activeLineClassName)
|
||||
font.finish()
|
||||
}
|
||||
} else if (this.playingLineNum > -1) {
|
||||
const font = this._lineFonts[this.playingLineNum]
|
||||
font.fontContent.classList.remove(this.activeLineClassName)
|
||||
}
|
||||
}
|
||||
this.playingLineNum = num
|
||||
const font = this._lineFonts[num]
|
||||
font.fontContent.classList.add(this.activeLineClassName)
|
||||
font.play(curTime - this._lines[num].time)
|
||||
this.onPlay(num, this._lines[num].text)
|
||||
}
|
||||
|
||||
_handleLinePlayerOnSetLyric = lyricLines => {
|
||||
// this._lines = lyricsLines
|
||||
this.isLineMode = lyricLines.length && !/^<\d+,\d+>/.test(lyricLines[0].text)
|
||||
|
||||
this._lineFonts = []
|
||||
if (this.isLineMode) {
|
||||
this._lines = lyricLines.map(line => {
|
||||
const fontPlayer = new FontPlayer({
|
||||
lyric: line.text,
|
||||
className: this.className,
|
||||
shadowClassName: this.shadowClassName,
|
||||
shadowContent: this.shadowContent,
|
||||
})
|
||||
|
||||
this._lineFonts.push(fontPlayer)
|
||||
return {
|
||||
text: line.text,
|
||||
time: line.time,
|
||||
dom_line: fontPlayer.fontContent,
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this._lines = lyricLines.map(line => {
|
||||
const fontPlayer = new FontPlayer({
|
||||
lyric: line.text,
|
||||
className: this.className,
|
||||
shadowClassName: this.shadowClassName,
|
||||
shadowContent: this.shadowContent,
|
||||
})
|
||||
|
||||
this._lineFonts.push(fontPlayer)
|
||||
return {
|
||||
text: line.text.replace(fontTimeExp, ''),
|
||||
time: line.time,
|
||||
dom_line: fontPlayer.fontContent,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.onSetLyric(this._lines)
|
||||
}
|
||||
|
||||
play(curTime) {
|
||||
if (!this.linePlayer) return
|
||||
this.linePlayer.play(curTime)
|
||||
}
|
||||
|
||||
pause() {
|
||||
if (!this.linePlayer) return
|
||||
this.linePlayer.pause()
|
||||
if (this.playingLineNum > -1) this._lineFonts[this.playingLineNum].pause()
|
||||
}
|
||||
|
||||
setLyric(lyric) {
|
||||
this.lyric = lyric
|
||||
this._init()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
const { getNow, TimeoutTools } = require('./utils')
|
||||
|
||||
const timeExp = /^\[([\d:.]*)\]{1}/g
|
||||
const tagRegMap = {
|
||||
title: 'ti',
|
||||
artist: 'ar',
|
||||
album: 'al',
|
||||
offset: 'offset',
|
||||
by: 'by',
|
||||
}
|
||||
|
||||
const timeoutTools = new TimeoutTools()
|
||||
|
||||
module.exports = class LinePlayer {
|
||||
constructor({ lyric = '', offset = 0, onPlay = function() { }, onSetLyric = function() { } } = {}) {
|
||||
this.lyric = lyric
|
||||
this.tags = {}
|
||||
this.lines = null
|
||||
this.onPlay = onPlay
|
||||
this.onSetLyric = onSetLyric
|
||||
this.isPlay = false
|
||||
this.curLineNum = 0
|
||||
this.maxLine = 0
|
||||
this.offset = offset
|
||||
this.isOffseted = false
|
||||
this._performanceTime = 0
|
||||
this._performanceOffsetTime = 0
|
||||
this._init()
|
||||
}
|
||||
|
||||
_init() {
|
||||
if (this.lyric == null) this.lyric = ''
|
||||
this._initTag()
|
||||
this._initLines()
|
||||
this.onSetLyric(this.lines)
|
||||
}
|
||||
|
||||
_initTag() {
|
||||
for (let tag in tagRegMap) {
|
||||
const matches = this.lyric.match(new RegExp(`\\[${tagRegMap[tag]}:([^\\]]*)]`, 'i'))
|
||||
this.tags[tag] = (matches && matches[1]) || ''
|
||||
}
|
||||
}
|
||||
|
||||
_initLines() {
|
||||
this.lines = []
|
||||
const lines = this.lyric.split('\n')
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].trim()
|
||||
let result = timeExp.exec(line)
|
||||
if (result) {
|
||||
const text = line.replace(timeExp, '').trim()
|
||||
if (text) {
|
||||
const timeArr = RegExp.$1.split(':')
|
||||
if (timeArr.length < 3) timeArr.unshift(0)
|
||||
if (timeArr[2].indexOf('.') > -1) {
|
||||
timeArr.push(...timeArr[2].split('.'))
|
||||
timeArr.splice(2, 1)
|
||||
}
|
||||
this.lines.push({
|
||||
time: parseInt(timeArr[0]) * 60 * 60 * 1000 + parseInt(timeArr[1]) * 60 * 1000 + parseInt(timeArr[2]) * 1000 + parseInt(timeArr[3] || 0),
|
||||
text,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
this.lines.sort((a, b) => {
|
||||
return a.time - b.time
|
||||
})
|
||||
this.maxLine = this.lines.length - 1
|
||||
}
|
||||
|
||||
_currentTime() {
|
||||
return getNow() - this._performanceTime + this._performanceOffsetTime
|
||||
}
|
||||
|
||||
_findCurLineNum(curTime) {
|
||||
const length = this.lines.length
|
||||
for (let index = 0; index < length; index++) if (curTime <= this.lines[index].time) return index === 0 ? 0 : index - 1
|
||||
return length - 1
|
||||
}
|
||||
|
||||
_handleMaxLine() {
|
||||
this.onPlay(this.curLineNum, this.lines[this.curLineNum].text, this._currentTime())
|
||||
this.pause()
|
||||
}
|
||||
|
||||
_refresh() {
|
||||
this.curLineNum++
|
||||
// console.log('curLineNum time', this.lines[this.curLineNum].time)
|
||||
let curLine = this.lines[this.curLineNum]
|
||||
let nextLine = this.lines[this.curLineNum + 1]
|
||||
const currentTime = this._currentTime()
|
||||
const driftTime = currentTime - curLine.time
|
||||
|
||||
if (driftTime >= 0 || this.curLineNum === 0) {
|
||||
if (this.curLineNum === this.maxLine) return this._handleMaxLine()
|
||||
this.delay = nextLine.time - curLine.time - driftTime
|
||||
if (this.delay > 0) {
|
||||
if (!this.isOffseted && this.delay >= this.offset) {
|
||||
this._performanceOffsetTime += this.offset
|
||||
this.delay -= this.offset
|
||||
this.isOffseted = true
|
||||
}
|
||||
timeoutTools.start(() => {
|
||||
this._refresh()
|
||||
}, this.delay)
|
||||
this.onPlay(this.curLineNum, curLine.text, currentTime)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.curLineNum = this._findCurLineNum(currentTime) - 1
|
||||
this._refresh()
|
||||
}
|
||||
|
||||
play(curTime = 0) {
|
||||
if (!this.lines.length) return
|
||||
this.pause()
|
||||
this.isPlay = true
|
||||
|
||||
this._performanceOffsetTime = 0
|
||||
this._performanceTime = getNow() - curTime
|
||||
if (this._performanceTime < 0) {
|
||||
this._performanceOffsetTime = -this._performanceTime
|
||||
this._performanceTime = 0
|
||||
}
|
||||
|
||||
this.curLineNum = this._findCurLineNum(curTime) - 1
|
||||
|
||||
this._refresh()
|
||||
}
|
||||
|
||||
pause() {
|
||||
if (!this.isPlay) return
|
||||
this.isPlay = false
|
||||
this.isOffseted = false
|
||||
timeoutTools.clear()
|
||||
if (this.curLineNum === this.maxLine) return
|
||||
const currentTime = this._currentTime()
|
||||
const curLineNum = this._findCurLineNum(currentTime)
|
||||
if (this.curLineNum !== curLineNum) {
|
||||
this.curLineNum = curLineNum
|
||||
this.onPlay(curLineNum, this.lines[curLineNum].text, currentTime)
|
||||
}
|
||||
}
|
||||
|
||||
setLyric(lyric) {
|
||||
if (this.isPlay) this.pause()
|
||||
this.lyric = lyric
|
||||
this._init()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
|
||||
const getNow = exports.getNow = typeof performance == 'object' && window.performance.now ? window.performance.now.bind(window.performance) : Date.now.bind(Date)
|
||||
|
||||
exports.TimeoutTools = class TimeoutTools {
|
||||
constructor() {
|
||||
this.invokeTime = 0
|
||||
this.animationFrameId = null
|
||||
this.timeoutId = null
|
||||
this.callback = null
|
||||
this.thresholdTime = 200
|
||||
}
|
||||
|
||||
run() {
|
||||
this.animationFrameId = window.requestAnimationFrame(() => {
|
||||
this.animationFrameId = null
|
||||
let diff = this.invokeTime - getNow()
|
||||
// console.log('diff', diff)
|
||||
if (diff > 0) {
|
||||
if (diff < this.thresholdTime) return this.run()
|
||||
return this.timeoutId = setTimeout(() => {
|
||||
this.timeoutId = null
|
||||
this.run()
|
||||
}, diff - this.thresholdTime)
|
||||
}
|
||||
|
||||
// console.log('diff', diff)
|
||||
this.callback(diff)
|
||||
})
|
||||
}
|
||||
|
||||
start(callback = () => {}, timeout = 0) {
|
||||
// console.log(timeout)
|
||||
this.callback = callback
|
||||
this.invokeTime = getNow() + timeout
|
||||
|
||||
this.run()
|
||||
}
|
||||
|
||||
clear() {
|
||||
if (this.animationFrameId) {
|
||||
window.cancelAnimationFrame(this.animationFrameId)
|
||||
this.animationFrameId = null
|
||||
}
|
||||
if (this.timeoutId) {
|
||||
window.clearTimeout(this.timeoutId)
|
||||
this.timeoutId = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,13 @@ import { httpFetch } from '../../request'
|
||||
import { decodeLyric } from './util'
|
||||
import { decodeName } from '../..'
|
||||
|
||||
const headExp = /^.*\[id:\$00000000\]\n/
|
||||
|
||||
const parseLyric = str => {
|
||||
str = str.replace(/(?:<\d+,\d+,\d+>|\r)/g, '')
|
||||
if (str.startsWith('\ufeff[id:$00000000]')) str = str.replace('\ufeff[id:$00000000]\n', '')
|
||||
str = str.replace(/\r/g, '')
|
||||
if (headExp.test(str)) str = str.replace(headExp, '')
|
||||
let trans = str.match(/\[language:([\w=\\/+]+)\]/)
|
||||
let lyric
|
||||
let tlyric
|
||||
if (trans) {
|
||||
str = str.replace(/\[language:[\w=\\/+]+\]\n/, '')
|
||||
@@ -18,7 +21,7 @@ const parseLyric = str => {
|
||||
}
|
||||
}
|
||||
let i = 0
|
||||
let lyric = str.replace(/\[((\d+),\d+)\].*/g, str => {
|
||||
let lxlyric = str.replace(/\[((\d+),\d+)\].*/g, str => {
|
||||
let result = str.match(/\[((\d+),\d+)\].*/)
|
||||
let time = parseInt(result[2])
|
||||
let ms = time % 1000
|
||||
@@ -31,11 +34,14 @@ const parseLyric = str => {
|
||||
return str.replace(result[1], time)
|
||||
})
|
||||
tlyric = tlyric ? tlyric.join('\n') : ''
|
||||
lyric = decodeName(lyric)
|
||||
lxlyric = lxlyric.replace(/<(\d+,\d+),\d+>/g, '<$1>')
|
||||
lxlyric = decodeName(lxlyric)
|
||||
lyric = lxlyric.replace(/<\d+,\d+>/g, '')
|
||||
tlyric = decodeName(tlyric)
|
||||
return {
|
||||
lyric,
|
||||
tlyric,
|
||||
lxlyric,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +125,7 @@ export default {
|
||||
let requestObj = this.searchLyric(songInfo.name, songInfo.hash, songInfo._interval || this.getIntv(songInfo.interval))
|
||||
|
||||
requestObj.promise = requestObj.promise.then(result => {
|
||||
if (!result) return { lyric: '', tlyric: '' }
|
||||
if (!result) return { lyric: '', tlyric: '', lxlyric: '' }
|
||||
|
||||
let requestObj2 = this.getLyricDownload(result.id, result.accessKey)
|
||||
|
||||
|
||||
@@ -20,7 +20,9 @@ const wy = {
|
||||
return getLyric(songInfo.songmid)
|
||||
},
|
||||
getPic(songInfo) {
|
||||
return getMusicInfo(songInfo.songmid).then(info => info.al.picUrl)
|
||||
const requestObj = getMusicInfo(songInfo.songmid)
|
||||
requestObj.promise = requestObj.promise.then(info => info.al.picUrl)
|
||||
return requestObj
|
||||
},
|
||||
getMusicDetailPageUrl(songInfo) {
|
||||
return `https://music.163.com/#/song?id=${songInfo.songmid}`
|
||||
|
||||
Reference in New Issue
Block a user