播放详情页新增音频可视化功能
parent
dcfe055461
commit
c2d6737796
|
@ -6,6 +6,7 @@
|
|||
- 新增根据歌曲名、歌手名等字段对列表自动排序的功能,可以在我的列表右击列表名弹出的菜单中使用
|
||||
- 新增将播放与下载的歌词转换为繁体中文选项,默认关闭,可在设置-播放设置中开启
|
||||
- 现在已允许进入临时播放列表,即:使用歌单详情页、排行榜名称右键菜单的“播放”按钮播放歌曲时,可右击播放封面进入此临时列表
|
||||
- 播放详情页新增音频可视化功能(实验性)
|
||||
|
||||
### 优化
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ const path = require('path')
|
|||
const os = require('os')
|
||||
|
||||
const defaultSetting = {
|
||||
version: '1.0.46',
|
||||
version: '1.0.47',
|
||||
player: {
|
||||
togglePlayMethod: 'listLoop',
|
||||
highQuality: false,
|
||||
|
@ -15,6 +15,7 @@ const defaultSetting = {
|
|||
isS2t: false, // 是否将歌词从简体转换为繁体
|
||||
isPlayLxlrc: true,
|
||||
isSavePlayTime: false,
|
||||
audioVisualization: false,
|
||||
},
|
||||
desktopLyric: {
|
||||
enable: false,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"action": "Manage",
|
||||
"agree": "Accept",
|
||||
"audio_visualization": "Audio visualization (experimental)",
|
||||
"back": "Back",
|
||||
"cancel_button_text": "Cancel",
|
||||
"close": "Close",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"action": "操作",
|
||||
"agree": "接受",
|
||||
"audio_visualization": "音频可视化(实验性)",
|
||||
"back": "返回",
|
||||
"cancel_button_text": "我不",
|
||||
"close": "关闭",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"action": "操作",
|
||||
"agree": "接受",
|
||||
"audio_visualization": "音頻可視化(實驗性)",
|
||||
"back": "返回",
|
||||
"cancel_button_text": "取消",
|
||||
"close": "關閉",
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
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>
|
||||
|
||||
|
|
|
@ -6,6 +6,9 @@ div(:class="$style.footerLeftControlBtns")
|
|||
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')
|
||||
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')")
|
||||
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')
|
||||
|
@ -31,7 +34,7 @@ div(:class="$style.footerLeftControlBtns")
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { useI18n, useRefGetter, ref } from '@renderer/utils/vueTools'
|
||||
import { useI18n, useRefGetter, ref, useCommit } from '@renderer/utils/vueTools'
|
||||
|
||||
import {
|
||||
isShowLrcSelectContent,
|
||||
|
@ -48,6 +51,7 @@ export default {
|
|||
setup() {
|
||||
const { t } = useI18n()
|
||||
const setting = useRefGetter('setting')
|
||||
const setAudioVisualization = useCommit('setAudioVisualization')
|
||||
|
||||
const toggleVisibleLrc = () => {
|
||||
setShowPlayLrcSelectContentLrc(!isShowLrcSelectContent.value)
|
||||
|
@ -68,6 +72,10 @@ export default {
|
|||
|
||||
const isShowAddMusicTo = ref(false)
|
||||
|
||||
const toggleAudioVisualization = () => {
|
||||
setAudioVisualization(!setting.value.player.audioVisualization)
|
||||
}
|
||||
|
||||
return {
|
||||
setting,
|
||||
isShowLrcSelectContent,
|
||||
|
@ -79,6 +87,7 @@ export default {
|
|||
toggleDesktopLyricBtnTitle,
|
||||
toggleDesktopLyric,
|
||||
toggleLockDesktopLyric,
|
||||
toggleAudioVisualization,
|
||||
isShowAddMusicTo,
|
||||
musicInfoItem,
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
transition(enter-active-class="animated fadeIn" leave-active-class="animated fadeOut")
|
||||
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>
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
let audio
|
||||
|
||||
let audioContext
|
||||
let mediaSource
|
||||
let analyser
|
||||
export const createAudio = () => {
|
||||
if (audio) return
|
||||
window.audio = audio = new window.Audio()
|
||||
|
@ -8,6 +10,17 @@ export const createAudio = () => {
|
|||
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 => {
|
||||
if (audio) audio.src = src
|
||||
}
|
||||
|
|
|
@ -55,4 +55,7 @@ export default {
|
|||
setProxyEnable(state, val) {
|
||||
state.setting.network.proxy.enable = val
|
||||
},
|
||||
setAudioVisualization(state, val) {
|
||||
state.setting.player.audioVisualization = val
|
||||
},
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue