新增通过播放详情页歌词调整播放进度

pull/930/merge
lyswhut 2022-03-07 15:33:04 +08:00
parent 34febf4949
commit cfee4a0b24
5 changed files with 162 additions and 11 deletions

View File

@ -1,6 +1,7 @@
### 新增 ### 新增
- 新增对播放详情页歌词大小、是否缩放、对齐方式的设置,可以去设置-播放详情页设置查看 - 新增对播放详情页歌词大小、是否缩放、对齐方式的设置,可以去设置-播放详情页设置查看
- 新增通过播放详情页歌词调整播放进度
### 优化 ### 优化

View File

@ -1,10 +1,21 @@
<template> <template>
<div :class="['right', $style.right]"> <div :class="['right', $style.right]" :style="lrcFontSize">
<div :class="['lyric', $style.lyric, { [$style.draging]: isMsDown }, { [$style.lrcActiveZoom]: isZoomActiveLrc }]" :style="lrcStyles" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric"> <div :class="['lyric', $style.lyric, { [$style.draging]: isMsDown }, { [$style.lrcActiveZoom]: isZoomActiveLrc }]" :style="lrcStyles" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric">
<div :class="$style.lyricSpace"></div> <div :class="['pre', $style.lyricSpace]"></div>
<div ref="dom_lyric_text"></div> <div ref="dom_lyric_text"></div>
<div :class="$style.lyricSpace"></div> <div :class="$style.lyricSpace"></div>
</div> </div>
<transition enter-active-class="animated fadeIn" leave-active-class="animated fadeOut">
<div :class="$style.skip" v-show="isStopScroll">
<div :class="$style.line" ref="dom_skip_line"></div>
<span :class="$style.label">{{timeStr}}</span>
<base-btn :class="$style.skipBtn" @mouseenter="handleSkipMouseEnter" @mouseleave="handleSkipMouseLeave" @click="handleSkipPlay">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="50%" viewBox="0 0 170 170" space="preserve">
<use xlink:href="#icon-play"></use>
</svg>
</base-btn>
</div>
</transition>
<transition enter-active-class="animated fadeIn" leave-active-class="animated fadeOut"> <transition enter-active-class="animated fadeIn" leave-active-class="animated fadeOut">
<div :class="[$style.lyricSelectContent, 'select', 'scroll', 'lyricSelectContent']" v-if="isShowLrcSelectContent" @contextmenu="handleCopySelectText"> <div :class="[$style.lyricSelectContent, 'select', 'scroll', 'lyricSelectContent']" v-if="isShowLrcSelectContent" @contextmenu="handleCopySelectText">
<div v-for="(info, index) in lyric.lines" :key="index" :class="[$style.lyricSelectline, { [$style.lrcActive]: lyric.line == index }]"> <div v-for="(info, index) in lyric.lines" :key="index" :class="[$style.lyricSelectline, { [$style.lrcActive]: lyric.line == index }]">
@ -31,9 +42,15 @@ export default {
const { const {
dom_lyric, dom_lyric,
dom_lyric_text, dom_lyric_text,
dom_skip_line,
isMsDown, isMsDown,
isStopScroll,
timeStr,
handleLyricMouseDown, handleLyricMouseDown,
handleWheel, handleWheel,
handleSkipPlay,
handleSkipMouseEnter,
handleSkipMouseLeave,
} = useLyric({ isPlay, lyric }) } = useLyric({ isPlay, lyric })
const fontSizeUp = () => { const fontSizeUp = () => {
@ -48,6 +65,10 @@ export default {
const lrcStyles = computed(() => { const lrcStyles = computed(() => {
return { return {
textAlign: setting.value.playDetail.style.align, textAlign: setting.value.playDetail.style.align,
}
})
const lrcFontSize = computed(() => {
return {
'--playDetail-lrc-font-size': setting.value.playDetail.style.fontSize / 100 + 'rem', '--playDetail-lrc-font-size': setting.value.playDetail.style.fontSize / 100 + 'rem',
} }
}) })
@ -69,13 +90,20 @@ export default {
return { return {
dom_lyric, dom_lyric,
dom_lyric_text, dom_lyric_text,
dom_skip_line,
isMsDown, isMsDown,
timeStr,
handleLyricMouseDown, handleLyricMouseDown,
handleWheel, handleWheel,
handleSkipPlay,
handleSkipMouseEnter,
handleSkipMouseLeave,
lyric, lyric,
isShowLrcSelectContent, isShowLrcSelectContent,
lrcStyles, lrcStyles,
lrcFontSize,
isZoomActiveLrc, isZoomActiveLrc,
isStopScroll,
} }
}, },
methods: { methods: {
@ -133,11 +161,11 @@ export default {
:global { :global {
.lrc-content { .lrc-content {
line-height: 1.2; line-height: 1.2;
margin: var(--playDetail-lrc-font-size, 16px) 0; padding: calc(var(--playDetail-lrc-font-size, 16px) / 2) 0;
overflow-wrap: break-word; overflow-wrap: break-word;
color: @color-player-detail-lyric; color: @color-player-detail-lyric;
transition: @transition-theme; transition: @transition-theme;
transition-property: margin; transition-property: padding;
.translation { .translation {
transition: @transition-theme !important; transition: @transition-theme !important;
@ -203,6 +231,50 @@ export default {
} }
} }
} }
.skip {
position: absolute;
top: calc(38% + var(--playDetail-lrc-font-size, 16px) + 4px);
left: 0;
// height: 6px;
width: 100%;
pointer-events: none;
// opacity: .5;
.line {
border-top: 1px dashed @color-player-detail-lyric-active;
opacity: .15;
margin-right: 30px;
}
.label {
position: absolute;
right: 30px;
top: -14px;
line-height: 1;
font-size: 12px;
color: @color-player-detail-lyric-active;
opacity: .7;
}
.skipBtn {
position: absolute;
right: 0;
top: 0;
transform: translateY(-50%);
width: 30px;
height: 30px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
background: none !important;
pointer-events: initial;
transition: @transition-theme;
transition-property: opacity;
opacity: .8;
&:hover {
opacity: .6;
}
}
}
.lyricSelectContent { .lyricSelectContent {
position: absolute; position: absolute;
left: 0; left: 0;
@ -267,6 +339,14 @@ each(@themes, {
// .lrc-active { // .lrc-active {
// color: ~'@{color-@{value}-theme}'; // color: ~'@{color-@{value}-theme}';
// } // }
.skip {
.line {
border-top-color: ~'@{color-@{value}-player-detail-lyric-active}';
}
.label {
color:~'@{color-@{value}-player-detail-lyric-active}';
}
}
.lyricSelectContent { .lyricSelectContent {
background-color: ~'@{color-@{value}-theme_2-background_1}'; background-color: ~'@{color-@{value}-theme_2-background_1}';
color: ~'@{color-@{value}-player-detail-lyric}'; color: ~'@{color-@{value}-player-detail-lyric}';

View File

@ -1,18 +1,74 @@
import { ref, onMounted, onBeforeUnmount, watch, nextTick } from '@renderer/utils/vueTools' import { ref, onMounted, onBeforeUnmount, watch, nextTick } from '@renderer/utils/vueTools'
import { scrollTo } from '@renderer/utils' import { scrollTo, throttle, formatPlayTime2 } from '@renderer/utils'
import { player as eventPlayerNames } from '@renderer/event/names'
export default ({ isPlay, lyric }) => { export default ({ isPlay, lyric }) => {
const dom_lyric = ref(null) const dom_lyric = ref(null)
const dom_lyric_text = ref(null) const dom_lyric_text = ref(null)
const dom_skip_line = ref(null)
const isMsDown = ref(false) const isMsDown = ref(false)
const isStopScroll = ref(false)
const timeStr = ref('--/--')
let msDownY = 0 let msDownY = 0
let msDownScrollY = 0 let msDownScrollY = 0
let isStopScroll = false
let timeout = null let timeout = null
let cancelScrollFn let cancelScrollFn
let dom_lines let dom_lines
let isSetedLines = false let isSetedLines = false
let point = {
x: null,
y: null,
}
let time = -1
let dom_pre_line = null
let isSkipMouseEnter = false
const handleSkipPlay = () => {
if (time == -1) return
handleSkipMouseLeave()
isStopScroll.value = false
window.eventHub.emit(eventPlayerNames.setProgress, time)
if (!isPlay.value) window.eventHub.emit(eventPlayerNames.setPlay)
}
const handleSkipMouseEnter = () => {
isSkipMouseEnter = true
clearLyricScrollTimeout()
}
const handleSkipMouseLeave = () => {
isSkipMouseEnter = false
startLyricScrollTimeout()
}
const setTime = throttle(() => {
if (point.x == null) {
const rect = dom_skip_line.value.getBoundingClientRect()
point.x = rect.x
point.y = rect.y
}
let dom = document.elementFromPoint(point.x, point.y)
if (dom_pre_line === dom) return
if (dom.tagName == 'SPAN') {
dom = dom.parentNode.parentNode
} else if (dom.classList.contains('font')) {
dom = dom.parentNode
}
if (dom.time == null) {
if (lyric.lines.length) {
time = dom.classList.contains('pre') ? 0 : lyric.lines[lyric.lines.length - 1].time ?? 0
if (time) time = time / 1000
timeStr.value = formatPlayTime2(time)
} else {
time = -1
timeStr.value = '--:--'
}
} else {
time = dom.time
if (time) time = time / 1000
timeStr.value = formatPlayTime2(time)
}
dom_pre_line = dom
})
const handleScrollLrc = (duration = 300) => { const handleScrollLrc = (duration = 300) => {
if (!dom_lines?.length || !dom_lyric.value) return if (!dom_lines?.length || !dom_lyric.value) return
@ -20,7 +76,8 @@ export default ({ isPlay, lyric }) => {
cancelScrollFn() cancelScrollFn()
cancelScrollFn = null cancelScrollFn = null
} }
if (isStopScroll) return if (isSkipMouseEnter) return
if (isStopScroll.value) return
let dom_p = dom_lines[lyric.line] let dom_p = dom_lines[lyric.line]
cancelScrollFn = scrollTo(dom_lyric.value, dom_p ? (dom_p.offsetTop - dom_lyric.value.clientHeight * 0.38) : 0, duration) cancelScrollFn = scrollTo(dom_lyric.value, dom_p ? (dom_p.offsetTop - dom_lyric.value.clientHeight * 0.38) : 0, duration)
} }
@ -31,9 +88,10 @@ export default ({ isPlay, lyric }) => {
} }
const startLyricScrollTimeout = () => { const startLyricScrollTimeout = () => {
clearLyricScrollTimeout() clearLyricScrollTimeout()
if (isSkipMouseEnter) return
timeout = setTimeout(() => { timeout = setTimeout(() => {
timeout = null timeout = null
isStopScroll = false isStopScroll.value = false
if (!isPlay.value) return if (!isPlay.value) return
handleScrollLrc() handleScrollLrc()
}, 3000) }, 3000)
@ -53,25 +111,27 @@ export default ({ isPlay, lyric }) => {
} }
const handleMouseMsMove = event => { const handleMouseMsMove = event => {
if (isMsDown.value) { if (isMsDown.value) {
if (!isStopScroll) isStopScroll = true if (!isStopScroll.value) isStopScroll.value = true
if (cancelScrollFn) { if (cancelScrollFn) {
cancelScrollFn() cancelScrollFn()
cancelScrollFn = null cancelScrollFn = null
} }
dom_lyric.value.scrollTop = msDownScrollY + msDownY - event.clientY dom_lyric.value.scrollTop = msDownScrollY + msDownY - event.clientY
startLyricScrollTimeout() startLyricScrollTimeout()
setTime()
} }
} }
const handleWheel = (event) => { const handleWheel = (event) => {
console.log(event.deltaY) console.log(event.deltaY)
if (!isStopScroll) isStopScroll = true if (!isStopScroll.value) isStopScroll.value = true
if (cancelScrollFn) { if (cancelScrollFn) {
cancelScrollFn() cancelScrollFn()
cancelScrollFn = null cancelScrollFn = null
} }
dom_lyric.value.scrollTop = dom_lyric.value.scrollTop + event.deltaY dom_lyric.value.scrollTop = dom_lyric.value.scrollTop + event.deltaY
startLyricScrollTimeout() startLyricScrollTimeout()
setTime()
} }
const setLyric = (lines) => { const setLyric = (lines) => {
@ -139,8 +199,14 @@ export default ({ isPlay, lyric }) => {
return { return {
dom_lyric, dom_lyric,
dom_lyric_text, dom_lyric_text,
dom_skip_line,
isStopScroll,
isMsDown, isMsDown,
timeStr,
handleLyricMouseDown, handleLyricMouseDown,
handleWheel, handleWheel,
handleSkipPlay,
handleSkipMouseEnter,
handleSkipMouseLeave,
} }
} }

View File

@ -20,7 +20,8 @@ const createAnimation = (dom, duration) => new window.Animation(new window.Keyfr
// https://jsfiddle.net/ceqpnbky/1/ // https://jsfiddle.net/ceqpnbky/1/
module.exports = class FontPlayer { module.exports = class FontPlayer {
constructor({ lyric = '', translationLyric = '', lineClassName = '', fontClassName = '', translationClassName = '', lineModeClassName = '', shadowContent = false, shadowClassName = '' }) { constructor({ time = 0, lyric = '', translationLyric = '', lineClassName = '', fontClassName = '', translationClassName = '', lineModeClassName = '', shadowContent = false, shadowClassName = '' }) {
this.time = time
this.lyric = lyric this.lyric = lyric
this.translationLyric = translationLyric this.translationLyric = translationLyric
@ -51,6 +52,7 @@ module.exports = class FontPlayer {
this.isLineMode = false this.isLineMode = false
this.lineContent = document.createElement('div') this.lineContent = document.createElement('div')
this.lineContent.time = this.time
if (this.lineClassName) this.lineContent.classList.add(this.lineClassName) if (this.lineClassName) this.lineContent.classList.add(this.lineClassName)
this.fontContent = document.createElement('div') this.fontContent = document.createElement('div')
this.fontContent.style = 'position:relative;display:inline-block;' this.fontContent.style = 'position:relative;display:inline-block;'

View File

@ -106,6 +106,7 @@ module.exports = class Lyric {
if (this.isLineMode) { if (this.isLineMode) {
this._lines = lyricLines.map(line => { this._lines = lyricLines.map(line => {
const fontPlayer = new FontPlayer({ const fontPlayer = new FontPlayer({
time: line.time,
lyric: line.text, lyric: line.text,
translationLyric: line.translation, translationLyric: line.translation,
lineClassName: this.lineClassName, lineClassName: this.lineClassName,
@ -127,6 +128,7 @@ module.exports = class Lyric {
} else { } else {
this._lines = lyricLines.map(line => { this._lines = lyricLines.map(line => {
const fontPlayer = new FontPlayer({ const fontPlayer = new FontPlayer({
time: line.time,
lyric: line.text, lyric: line.text,
translationLyric: line.translation, translationLyric: line.translation,
lineClassName: this.lineClassName, lineClassName: this.lineClassName,