播放详情页新增音频可视化功能

pull/733/head
lyswhut 2021-12-24 17:39:50 +08:00
parent dcfe055461
commit c2d6737796
11 changed files with 171 additions and 3 deletions

View File

@ -6,6 +6,7 @@
- 新增根据歌曲名、歌手名等字段对列表自动排序的功能,可以在我的列表右击列表名弹出的菜单中使用 - 新增根据歌曲名、歌手名等字段对列表自动排序的功能,可以在我的列表右击列表名弹出的菜单中使用
- 新增将播放与下载的歌词转换为繁体中文选项,默认关闭,可在设置-播放设置中开启 - 新增将播放与下载的歌词转换为繁体中文选项,默认关闭,可在设置-播放设置中开启
- 现在已允许进入临时播放列表,即:使用歌单详情页、排行榜名称右键菜单的“播放”按钮播放歌曲时,可右击播放封面进入此临时列表 - 现在已允许进入临时播放列表,即:使用歌单详情页、排行榜名称右键菜单的“播放”按钮播放歌曲时,可右击播放封面进入此临时列表
- 播放详情页新增音频可视化功能(实验性)
### 优化 ### 优化

View File

@ -2,7 +2,7 @@ const path = require('path')
const os = require('os') const os = require('os')
const defaultSetting = { const defaultSetting = {
version: '1.0.46', version: '1.0.47',
player: { player: {
togglePlayMethod: 'listLoop', togglePlayMethod: 'listLoop',
highQuality: false, highQuality: false,
@ -15,6 +15,7 @@ const defaultSetting = {
isS2t: false, // 是否将歌词从简体转换为繁体 isS2t: false, // 是否将歌词从简体转换为繁体
isPlayLxlrc: true, isPlayLxlrc: true,
isSavePlayTime: false, isSavePlayTime: false,
audioVisualization: false,
}, },
desktopLyric: { desktopLyric: {
enable: false, enable: false,

View File

@ -1,6 +1,7 @@
{ {
"action": "Manage", "action": "Manage",
"agree": "Accept", "agree": "Accept",
"audio_visualization": "Audio visualization (experimental)",
"back": "Back", "back": "Back",
"cancel_button_text": "Cancel", "cancel_button_text": "Cancel",
"close": "Close", "close": "Close",

View File

@ -1,6 +1,7 @@
{ {
"action": "操作", "action": "操作",
"agree": "接受", "agree": "接受",
"audio_visualization": "音频可视化(实验性)",
"back": "返回", "back": "返回",
"cancel_button_text": "我不", "cancel_button_text": "我不",
"close": "关闭", "close": "关闭",

View File

@ -1,6 +1,7 @@
{ {
"action": "操作", "action": "操作",
"agree": "接受", "agree": "接受",
"audio_visualization": "音頻可視化(實驗性)",
"back": "返回", "back": "返回",
"cancel_button_text": "取消", "cancel_button_text": "取消",
"close": "關閉", "close": "關閉",

View File

@ -0,0 +1,132 @@
<template>
<div :class="$style.content">
<canvas :class="$style.canvas" ref="dom_canvas"></canvas>
</div>
</template>
<script>
import { ref, onBeforeUnmount, onMounted, useRefGetter, watch } from '@renderer/utils/vueTools'
import { getAnalyser } from '@renderer/plugins/player'
import { isPlay } from '@renderer/core/share/player'
import { player as eventPlayerNames } from '@renderer/event/names'
const themes = {
green: 'rgba(77,175,124,.1)',
blue: 'rgba(52,152,219,.1)',
yellow: 'rgba(233,212,96,.16)',
orange: 'rgba(245,171,53,.1)',
red: 'rgba(214,69,65,.08)',
pink: 'rgba(241,130,141,.1)',
purple: 'rgba(155,89,182,.1)',
grey: 'rgba(108,122,137,.1)',
ming: 'rgba(51,110,123,.1)',
blue2: 'rgba(79,98,208,.1)',
black: 'rgba(39,39,39,.26)',
mid_autumn: 'rgba(74,55,82,.05)',
naruto: 'rgba(87,144,167,.1)',
happy_new_year: 'rgba(252,57,57,.1)',
}
export default {
setup() {
const dom_canvas = ref(null)
const analyser = getAnalyser()
let ctx
let bufferLength
let dataArray
let WIDTH
let HEIGHT
let barWidth
let barHeight
let x = 0
let isPlaying = false
const theme = useRefGetter('theme')
// const setting = useRefGetter('setting')
let themeColor = themes[theme.value || 'green']
watch(theme, theme => {
themeColor = themes[theme || 'green']
})
// https://codepen.io/nfj525/pen/rVBaab
const renderFrame = () => {
if (isPlaying) window.requestAnimationFrame(renderFrame)
x = 0
analyser.getByteFrequencyData(dataArray)
ctx.clearRect(0, 0, WIDTH, HEIGHT)
// ctx.fillRect(0, 0, WIDTH, HEIGHT)
for (let i = 0; i < bufferLength; i++) {
barHeight = dataArray[i]
// let r = barHeight + (25 * (i / bufferLength))
// let g = 250 * (i / bufferLength)
// let b = 50
// ctx.fillStyle = 'rgb(' + r + ',' + g + ',' + b + ')'
ctx.fillStyle = themeColor
ctx.fillRect(x, HEIGHT - barHeight, barWidth, barHeight)
x += barWidth
}
}
const handlePlay = () => {
isPlaying = true
analyser.fftSize = 256
bufferLength = analyser.frequencyBinCount
// console.log(bufferLength)
barWidth = (WIDTH / bufferLength) * 2.5
dataArray = new Uint8Array(bufferLength)
renderFrame()
}
const handlePause = () => {
isPlaying = false
}
window.eventHub.on(eventPlayerNames.play, handlePlay)
window.eventHub.on(eventPlayerNames.pause, handlePause)
window.eventHub.on(eventPlayerNames.error, handlePause)
onBeforeUnmount(() => {
handlePause()
window.eventHub.off(eventPlayerNames.play, handlePlay)
window.eventHub.off(eventPlayerNames.pause, handlePause)
window.eventHub.off(eventPlayerNames.error, handlePause)
})
onMounted(() => {
const canvas = dom_canvas.value
ctx = canvas.getContext('2d')
canvas.width = canvas.clientWidth
canvas.height = canvas.clientHeight
WIDTH = canvas.width
HEIGHT = canvas.height
if (isPlay.value) handlePlay()
})
return {
dom_canvas,
}
},
}
</script>
<style lang="less" module>
.content {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 100;
}
.canvas {
width: 100%;
height: 100%;
// opacity: 0.1;
}
</style>

View File

@ -222,5 +222,9 @@ svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/19
// 0 0 24 24 // 0 0 24 24
path(fill='currentColor', d='M21,6V8H3V6H21M3,18H12V16H3V18M3,13H21V11H3V13Z') path(fill='currentColor', d='M21,6V8H3V6H21M3,18H12V16H3V18M3,13H21V11H3V13Z')
g#icon-audio-wave(fill='currentColor')
// 0 0 24 24
path(fill='currentColor', d='M22 12L20 13L19 14L18 13L17 16L16 13L15 21L14 13L13 15L12 13L11 17L10 13L9 22L8 13L7 19L6 13L5 14L4 13L2 12L4 11L5 10L6 11L7 5L8 11L9 2L10 11L11 7L12 11L13 9L14 11L15 3L16 11L17 8L18 11L19 10L20 11L22 12Z')
</template> </template>

View File

@ -6,6 +6,9 @@ div(:class="$style.footerLeftControlBtns")
use(xlink:href='#icon-desktop-lyric-on') use(xlink:href='#icon-desktop-lyric-on')
svg(v-show="!setting.desktopLyric.enable" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='125%' viewBox='0 0 512 512' space='preserve') svg(v-show="!setting.desktopLyric.enable" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='125%' viewBox='0 0 512 512' space='preserve')
use(xlink:href='#icon-desktop-lyric-off') use(xlink:href='#icon-desktop-lyric-off')
div(:class="[$style.footerLeftControlBtn, { [$style.active]: setting.player.audioVisualization }]" @click="toggleAudioVisualization" :tips="$t('audio_visualization')")
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-audio-wave')
div(:class="[$style.footerLeftControlBtn, { [$style.active]: isShowLrcSelectContent }]" @click="toggleVisibleLrc" :tips="$t('lyric__select')") div(:class="[$style.footerLeftControlBtn, { [$style.active]: isShowLrcSelectContent }]" @click="toggleVisibleLrc" :tips="$t('lyric__select')")
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') 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-text') use(xlink:href='#icon-text')
@ -31,7 +34,7 @@ div(:class="$style.footerLeftControlBtns")
</template> </template>
<script> <script>
import { useI18n, useRefGetter, ref } from '@renderer/utils/vueTools' import { useI18n, useRefGetter, ref, useCommit } from '@renderer/utils/vueTools'
import { import {
isShowLrcSelectContent, isShowLrcSelectContent,
@ -48,6 +51,7 @@ export default {
setup() { setup() {
const { t } = useI18n() const { t } = useI18n()
const setting = useRefGetter('setting') const setting = useRefGetter('setting')
const setAudioVisualization = useCommit('setAudioVisualization')
const toggleVisibleLrc = () => { const toggleVisibleLrc = () => {
setShowPlayLrcSelectContentLrc(!isShowLrcSelectContent.value) setShowPlayLrcSelectContentLrc(!isShowLrcSelectContent.value)
@ -68,6 +72,10 @@ export default {
const isShowAddMusicTo = ref(false) const isShowAddMusicTo = ref(false)
const toggleAudioVisualization = () => {
setAudioVisualization(!setting.value.player.audioVisualization)
}
return { return {
setting, setting,
isShowLrcSelectContent, isShowLrcSelectContent,
@ -79,6 +87,7 @@ export default {
toggleDesktopLyricBtnTitle, toggleDesktopLyricBtnTitle,
toggleDesktopLyric, toggleDesktopLyric,
toggleLockDesktopLyric, toggleLockDesktopLyric,
toggleAudioVisualization,
isShowAddMusicTo, isShowAddMusicTo,
musicInfoItem, musicInfoItem,
} }

View File

@ -32,6 +32,8 @@ transition(enter-active-class="animated lightSpeedIn" leave-active-class="animat
music-comment(:class="$style.comment" :musicInfo="musicInfoItem" :show="isShowPlayComment" @close="hideComment" v-if="visibled") music-comment(:class="$style.comment" :musicInfo="musicInfoItem" :show="isShowPlayComment" @close="hideComment" v-if="visibled")
transition(enter-active-class="animated fadeIn" leave-active-class="animated fadeOut") transition(enter-active-class="animated fadeIn" leave-active-class="animated fadeOut")
play-bar(v-if="visibled") play-bar(v-if="visibled")
transition(enter-active-class="animated-slow fadeIn" leave-active-class="animated-slow fadeOut")
common-audio-visualizer(v-if="setting.player.audioVisualization && visibled")
</template> </template>

View File

@ -1,5 +1,7 @@
let audio let audio
let audioContext
let mediaSource
let analyser
export const createAudio = () => { export const createAudio = () => {
if (audio) return if (audio) return
window.audio = audio = new window.Audio() window.audio = audio = new window.Audio()
@ -8,6 +10,17 @@ export const createAudio = () => {
audio.preload = 'auto' audio.preload = 'auto'
} }
export const getAnalyser = () => {
if (audioContext == null) {
audioContext = new window.AudioContext()
mediaSource = audioContext.createMediaElementSource(audio)
analyser = audioContext.createAnalyser()
mediaSource.connect(analyser)
analyser.connect(audioContext.destination)
}
return analyser
}
export const setResource = src => { export const setResource = src => {
if (audio) audio.src = src if (audio) audio.src = src
} }

View File

@ -55,4 +55,7 @@ export default {
setProxyEnable(state, val) { setProxyEnable(state, val) {
state.setting.network.proxy.enable = val state.setting.network.proxy.enable = val
}, },
setAudioVisualization(state, val) {
state.setting.player.audioVisualization = val
},
} }