新增播放速率调整功能

This commit is contained in:
lyswhut
2023-02-08 18:27:35 +08:00
parent 9522e975a0
commit f809782559
27 changed files with 450 additions and 154 deletions
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="144" height="144">
<path fill="currentColor" d="M11.5 6C8.467 6 6 8.467 6 11.5L6 36.5C6 39.533 8.467 42 11.5 42L36.5 42C39.533 42 42 39.533 42 36.5L42 11.5C42 8.467 39.533 6 36.5 6L11.5 6 z M 11.5 9L36.5 9C37.878 9 39 10.122 39 11.5L39 36.5C39 37.878 37.878 39 36.5 39L11.5 39C10.122 39 9 37.878 9 36.5L9 11.5C9 10.122 10.122 9 11.5 9 z M 18.167969 13.074219L25.197266 24L18.167969 34.925781L24.644531 34.925781L31.753906 24L24.644531 13.074219L18.167969 13.074219 z" />
</svg>
+140
View File
@@ -0,0 +1,140 @@
<template>
<div :class="[$style.sliderContent, className]">
<div :class="[$style.slider ]">
<div ref="dom_sliderBar" :class="$style.sliderBar" :style="{ transform: `scaleX(${(value - min) / (max - min) || 0})` }" />
</div>
<div :class="$style.sliderMask" @mousedown="handleSliderMsDown" />
</div>
</template>
<script>
import { ref, onBeforeUnmount } from '@common/utils/vueTools'
// import { player as eventPlayerNames } from '@renderer/event/names'
export default {
props: {
className: {
type: String,
default: '',
},
value: {
type: Number,
required: true,
},
min: {
type: Number,
required: true,
},
max: {
type: Number,
required: true,
},
},
emits: ['change'],
setup(props, { emit }) {
const sliderEvent = {
isMsDown: false,
msDownX: 0,
msDownValue: 0,
}
const dom_sliderBar = ref(null)
const handleSliderMsDown = event => {
sliderEvent.isMsDown = true
sliderEvent.msDownX = event.clientX
sliderEvent.msDownValue = event.offsetX / dom_sliderBar.value.clientWidth
let val = sliderEvent.msDownValue * (props.max - props.min) + props.min
if (val < props.min) val = props.min
if (val > props.max) val = props.max
emit('change', val)
// if (isMute.value) window.app_event.setSliderIsMute(false)
}
const handleSliderMsUp = () => {
sliderEvent.isMsDown = false
}
const handleSliderMsMove = event => {
if (!sliderEvent.isMsDown) return
let value = (sliderEvent.msDownValue + (event.clientX - sliderEvent.msDownX) / dom_sliderBar.value.clientWidth) * (props.max - props.min) + props.min
if (value > props.max) value = props.max
else if (value < props.min) value = props.min
emit('change', value)
}
document.addEventListener('mousemove', handleSliderMsMove)
document.addEventListener('mouseup', handleSliderMsUp)
onBeforeUnmount(() => {
document.removeEventListener('mousemove', handleSliderMsMove)
document.removeEventListener('mouseup', handleSliderMsUp)
})
return {
handleSliderMsDown,
dom_sliderBar,
}
},
}
</script>
<style lang="less" module>
@import '@renderer/assets/styles/layout.less';
.slider-content {
flex: none;
position: relative;
width: 100px;
padding: 5px 0;
// margin-right: 10px;
display: flex;
align-items: center;
opacity: .5;
transition: opacity @transition-normal;
&:hover {
opacity: 1;
}
}
.slider {
// cursor: pointer;
width: 100%;
height: 5px;
border-radius: 20px;
overflow: hidden;
transition: @transition-normal;
transition-property: background-color, opacity;
background-color: var(--color-primary-alpha-700);
// background-color: #f5f5f5;
position: relative;
// border-radius: @radius-progress-border;
}
// .muted {
// opacity: .5;
// }
.slider-bar {
position: absolute;
left: 0;
top: 0;
transform: scaleX(0);
transform-origin: 0;
transition-property: transform;
transition-timing-function: ease;
width: 100%;
height: 100%;
// border-radius: @radius-progress-border;
transition-duration: 0.2s;
background-color: var(--color-button-font);
box-shadow: 0 0 2px rgba(0, 0, 0, 0.2);
}
.slider-mask {
position: absolute;
top: 0;
width: 100%;
height: 100%;
cursor: pointer;
}
</style>
@@ -0,0 +1,104 @@
<template>
<material-popup-btn :class="$style.btnContent">
<button :class="[$style.btn, { [$style.active]: playbackRate != 1 }]" :aria-label="`${$t('player__playback_rate')}x${playbackRate}`">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" width="100%" viewBox="0 0 24 24" space="preserve">
<use xlink:href="#icon-plex" />
</svg>
</button>
<template #content>
<div :class="$style.setting">
<div :class="$style.info">
<span>x{{ playbackRate }}</span>
<base-btn min @click="handleUpdatePlaybackRate(1)">{{ $t('player__playback_rate_reset_btn') }}</base-btn>
</div>
<base-slider-bar :value="playbackRate" :min="0.5" :max="2" @change="handleUpdatePlaybackRate" />
</div>
</template>
</material-popup-btn>
</template>
<script setup>
// import { computed } from '@common/utils/vueTools'
import { playbackRate } from '@renderer/store/player/playbackRate'
const handleUpdatePlaybackRate = (val) => {
window.app_event.setPlaybackRate(Math.round(val * 10) / 10)
}
// const icon = computed(() => {
// return playbackRate.value == 0
// ? '#icon-volume-off-outline'
// : playbackRate.value < 0.3
// ? '#icon-volume-low-outline'
// : playbackRate.value < 0.7
// ? '#icon-volume-medium-outline'
// : '#icon-volume-high-outline'
// })
</script>
<style lang="less" module>
@import '@renderer/assets/styles/layout.less';
.btnContent {
flex: none;
height: 100%;
}
.btn {
position: relative;
// color: var(--color-button-font);
justify-content: center;
align-items: center;
transition: color @transition-normal;
cursor: pointer;
background-color: transparent;
border: none;
width: 24px;
display: flex;
flex-flow: column nowrap;
padding: 0;
svg {
transition: opacity @transition-fast;
opacity: .5;
// filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));
}
&:hover {
svg {
opacity: .9;
}
}
&:active {
svg {
opacity: 1;
}
}
&.active {
svg {
color: var(--color-primary);
opacity: .8;
}
}
}
.setting {
display: flex;
flex-flow: column nowrap;
padding: 2px 3px;
gap: 8px;
}
.info {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
align-items: center;
font-size: 13px;
span {
line-height: 1;
}
}
</style>
@@ -1,133 +0,0 @@
<template>
<div :class="[$style.volumeContent, className]">
<div :class="[$style.volume ]">
<div ref="dom_volumeBar" :class="$style.volumeBar" :style="{ transform: `scaleX(${volume || 0})` }" />
</div>
<div :class="$style.volumeMask" @mousedown="handleVolumeMsDown" />
</div>
</template>
<script>
import { ref, onBeforeUnmount } from '@common/utils/vueTools'
// import { player as eventPlayerNames } from '@renderer/event/names'
import { volume, isMute } from '@renderer/store/player/volume'
import { appSetting } from '@renderer/store/setting'
export default {
props: {
className: {
type: String,
default: '',
},
},
setup(props) {
const volumeEvent = {
isMsDown: false,
msDownX: 0,
msDownVolume: 0,
}
const dom_volumeBar = ref(null)
const handleVolumeMsDown = event => {
volumeEvent.isMsDown = true
volumeEvent.msDownX = event.clientX
let val = event.offsetX / dom_volumeBar.value.clientWidth
if (val < 0) val = 0
if (val > 1) val = 1
window.app_event.setVolume(volumeEvent.msDownVolume = val)
// if (isMute.value) window.app_event.setVolumeIsMute(false)
}
const handleVolumeMsUp = () => {
volumeEvent.isMsDown = false
}
const handleVolumeMsMove = event => {
if (!volumeEvent.isMsDown) return
let volume = volumeEvent.msDownVolume + (event.clientX - volumeEvent.msDownX) / dom_volumeBar.value.clientWidth
if (volume > 1) volume = 1
else if (volume < 0) volume = 0
window.app_event.setVolume(volume)
}
document.addEventListener('mousemove', handleVolumeMsMove)
document.addEventListener('mouseup', handleVolumeMsUp)
onBeforeUnmount(() => {
document.removeEventListener('mousemove', handleVolumeMsMove)
document.removeEventListener('mouseup', handleVolumeMsUp)
})
return {
handleVolumeMsDown,
dom_volumeBar,
volume,
isMute,
appSetting,
}
},
}
</script>
<style lang="less" module>
@import '@renderer/assets/styles/layout.less';
.volume-content {
flex: none;
position: relative;
width: 100px;
padding: 5px 0;
// margin-right: 10px;
display: flex;
align-items: center;
opacity: .5;
transition: opacity @transition-normal;
&:hover {
opacity: 1;
}
}
.volume {
// cursor: pointer;
width: 100%;
height: 5px;
border-radius: 20px;
overflow: hidden;
transition: @transition-normal;
transition-property: background-color, opacity;
background-color: var(--color-primary-alpha-700);
// background-color: #f5f5f5;
position: relative;
// border-radius: @radius-progress-border;
}
// .muted {
// opacity: .5;
// }
.volume-bar {
position: absolute;
left: 0;
top: 0;
transform: scaleX(0);
transform-origin: 0;
transition-property: transform;
transition-timing-function: ease;
width: 100%;
height: 100%;
// border-radius: @radius-progress-border;
transition-duration: 0.2s;
background-color: var(--color-button-font);
box-shadow: 0 0 2px rgba(0, 0, 0, 0.2);
}
.volume-mask {
position: absolute;
top: 0;
width: 100%;
height: 100%;
cursor: pointer;
}
</style>
+5 -1
View File
@@ -16,7 +16,7 @@
@update:model-value="saveVolumeIsMute($event)"
/>
</div>
<common-volume-bar />
<base-slider-bar :value="volume" :min="0" :max="1" @change="handleUpdateVolume" />
</div>
</template>
</material-popup-btn>
@@ -30,6 +30,10 @@ import { computed } from '@common/utils/vueTools'
import { saveVolumeIsMute } from '@renderer/store/setting'
import { volume, isMute } from '@renderer/store/player/volume'
const handleUpdateVolume = (val) => {
window.app_event.setVolume(val)
}
const icon = computed(() => {
return isMute.value
? '#icon-volume-mute-outline'
@@ -14,6 +14,7 @@ div(:class="$style.footerLeftControlBtns")
button(:class="[$style.footerLeftControlBtn, {[$style.active]: isShowPlayComment}]" @click="toggleVisibleComment" :aria-label="$t('comment__show')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='95%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-comment')
common-playback-rate-btn
common-volume-btn
common-toggle-play-mode-btn
button(:class="$style.footerLeftControlBtn" @click="isShowAddMusicTo = true" :aria-label="$t('player__add_music_to')")
+21
View File
@@ -69,6 +69,7 @@ const handleDesktopLyricMessage = (action: LX.DesktopLyric.WinMainActions) => {
action: 'set_status',
data: {
isPlay: isPlay.value,
rate: appSetting['player.playbackRate'],
line: lyric.line,
played_time: getCurrentTime(),
},
@@ -100,6 +101,7 @@ export const init = () => {
setLines(markRawList([...lines]))
setText(lines[0] ?? '', 0)
},
rate: appSetting['player.playbackRate'],
// offset: 80,
})
@@ -143,6 +145,25 @@ export const setLyricOffset = (offset: number) => {
}
}
export const setPlaybackRate = (rate: number) => {
lrc.setPlaybackRate(rate)
sendDesktopLyricInfo({
action: 'set_playbackRate',
data: rate,
})
if (isPlay.value) {
setTimeout(() => {
const time = getCurrentTime()
sendDesktopLyricInfo({
action: 'set_play',
data: time,
})
lrc.play(time)
})
}
}
export const setLyric = () => {
if (!musicInfo.id) return
if (musicInfo.lrc) {
@@ -1,4 +1,5 @@
import { onBeforeUnmount, watch } from '@common/utils/vueTools'
import { debounce } from '@common/utils/common'
// import { setDesktopLyricInfo, onGetDesktopLyricInfo } from '@renderer/utils/ipc'
// import { musicInfo } from '@renderer/store/player/state'
import {
@@ -8,9 +9,11 @@ import {
stop,
init,
sendInfo,
setPlaybackRate,
} from '@renderer/core/lyric'
import { appSetting } from '@renderer/store/setting'
const handleApplyPlaybackRate = debounce(setPlaybackRate, 300)
export default () => {
init()
@@ -30,6 +33,7 @@ export default () => {
window.app_event.on('error', pause)
window.app_event.on('musicToggled', setPlayInfo)
window.app_event.on('lyricUpdated', setLyric)
window.app_event.on('setPlaybackRate', handleApplyPlaybackRate)
onBeforeUnmount(() => {
window.app_event.off('play', play)
@@ -38,5 +42,6 @@ export default () => {
window.app_event.off('error', pause)
window.app_event.off('musicToggled', setPlayInfo)
window.app_event.off('lyricUpdated', setLyric)
window.app_event.off('setPlaybackRate', handleApplyPlaybackRate)
})
}
@@ -0,0 +1,53 @@
import { onBeforeUnmount, watch } from '@common/utils/vueTools'
import { setPlaybackRate as setPlayerPlaybackRate } from '@renderer/plugins/player'
import { debounce } from '@common/utils'
// import { HOTKEY_PLAYER } from '@common/hotKey'
import { playbackRate, setplaybackRate } from '@renderer/store/player/playbackRate'
import { appSetting, savePlaybackRate } from '@renderer/store/setting'
export default () => {
const handleSavePlaybackRate = debounce(savePlaybackRate, 300)
setplaybackRate(appSetting['player.playbackRate'])
setPlayerPlaybackRate(appSetting['player.playbackRate'])
const handleSetPlaybackRate = (num: number) => {
const rate = num < 0.5 ? 0.5 : num > 2 ? 2 : num
setplaybackRate(rate)
}
// const handleSetPlaybackRateUp = (step = 0.02) => {
// handleSetPlaybackRate(volume.value + step)
// }
// const handleSetPlaybackRateDown = (step = 0.02) => {
// handleSetPlaybackRate(volume.value - step)
// }
// const hotkeyVolumeUp = () => {
// handleSetPlaybackRateUp()
// }
// const hotkeyVolumeDown = () => {
// handleSetPlaybackRateDown()
// }
watch(playbackRate, rate => {
handleSavePlaybackRate(rate)
setPlayerPlaybackRate(rate)
})
watch(() => appSetting['player.playbackRate'], rate => {
setplaybackRate(rate)
})
// window.key_event.on(HOTKEY_PLAYER.volume_up.action, hotkeyVolumeUp)
// window.key_event.on(HOTKEY_PLAYER.volume_down.action, hotkeyVolumeDown)
window.app_event.on('setPlaybackRate', handleSetPlaybackRate)
onBeforeUnmount(() => {
// window.key_event.off(HOTKEY_PLAYER.volume_up.action, hotkeyVolumeUp)
// window.key_event.off(HOTKEY_PLAYER.volume_down.action, hotkeyVolumeDown)
window.app_event.off('setPlaybackRate', handleSetPlaybackRate)
})
}
@@ -30,6 +30,7 @@ import useVolume from './useVolume'
import useWatchList from './useWatchList'
import { HOTKEY_PLAYER } from '@common/hotKey'
import { playNext, pause, playPrev, togglePlay } from '@renderer/core/player'
import usePlaybackRate from './usePlaybackRate'
export default () => {
@@ -40,6 +41,7 @@ export default () => {
usePlayEvent()
useLyric()
useVolume()
usePlaybackRate()
useWatchList()
const handlePlayNext = () => {
+8
View File
@@ -50,6 +50,14 @@ export class AppEvent extends Event {
this.emit('setVolume', volume)
}
/**
* 设置播放速率大小
* @param rate 播放速率
*/
setPlaybackRate(rate: number) {
this.emit('setPlaybackRate', rate)
}
/**
* 设置是否静音
* @param isMute 是否静音
+13 -3
View File
@@ -52,6 +52,16 @@ export const setLoopPlay = (isLoop: boolean) => {
if (audio) audio.loop = isLoop
}
export const getPlaybackRate = (): number => {
return audio?.defaultPlaybackRate ?? 1
}
export const setPlaybackRate = (rate: number) => {
if (!audio) return
audio.defaultPlaybackRate = rate
audio.playbackRate = rate
}
export const getMute = (): boolean => {
return audio?.muted ?? false
}
@@ -81,9 +91,9 @@ export const getDuration = () => {
return audio?.duration ?? 0
}
export const getPlaybackRate = () => {
return audio?.playbackRate ?? 1
}
// export const getPlaybackRate = () => {
// return audio?.playbackRate ?? 1
// }
type Noop = () => void
@@ -0,0 +1,8 @@
import { ref } from '@common/utils/vueTools'
export const playbackRate = ref(1)
export const setplaybackRate = (num: number) => {
playbackRate.value = num
}
+8
View File
@@ -57,6 +57,14 @@ export const saveVolumeIsMute = (isMute: boolean) => {
updateSetting({ 'player.isMute': isMute })
}
/**
* 设置播放速率
* @param rate 播放速率
*/
export const savePlaybackRate = (rate: number) => {
updateSetting({ 'player.playbackRate': rate })
}
/**
* 设置是否开启桌面歌词
+9 -5
View File
@@ -12,6 +12,7 @@ const parseTools = {
rxps: {
info: /^{"/,
lineTime: /^\[(\d+),\d+\]/,
lineTime2: /^\[([\d:.]+)\]/,
wordTime: /\(\d+,\d+\)/,
wordTimeAll: /(\(\d+,\d+\))/g,
timeLabelFixRxp: /(?:\.0+|0+)$/,
@@ -42,6 +43,10 @@ const parseTools = {
lxlrcLines.push(line)
lrcLines.push(line)
}
if (this.rxps.lineTime2.test(line)) {
// lxlrcLines.push(line)
lrcLines.push(line)
}
continue
}
@@ -105,22 +110,21 @@ const parseTools = {
const rlrcLines = rlrc.split('\n')
let lrcLines = lrc.split('\n')
// let temp = []
const timeTagRxp = /^\[([\d:.]+)\]/
let newLrc = []
rlrcLines.forEach((line) => {
const result = timeTagRxp.exec(line)
const result = this.rxps.lineTime2.exec(line)
if (!result) return
const words = line.replace(timeTagRxp, '')
const words = line.replace(this.rxps.lineTime2, '')
if (!words.trim()) return
const t1 = this.getIntv(result[1])
while (lrcLines.length) {
const lrcLine = lrcLines.shift()
const lrcLineResult = timeTagRxp.exec(lrcLine)
const lrcLineResult = this.rxps.lineTime2.exec(lrcLine)
if (!lrcLineResult) continue
const t2 = this.getIntv(lrcLineResult[1])
if (Math.abs(t1 - t2) < 10) {
newLrc.push(line.replace(timeTagRxp, lrcLineResult[0]))
newLrc.push(line.replace(this.rxps.lineTime2, lrcLineResult[0]))
break
}
// temp.push(line)