Add MilkDrop

This commit is contained in:
wvffle 2022-11-03 09:37:56 +00:00 committed by Kasper Seweryn
parent bd1da668f5
commit 5eff89920a
6 changed files with 230 additions and 0 deletions

View File

@ -28,6 +28,8 @@
"@vueuse/router": "9.3.1",
"axios": "0.27.2",
"axios-auth-refresh": "3.3.4",
"butterchurn": "^3.0.0-beta.4",
"butterchurn-presets": "^3.0.0-beta.4",
"diff": "5.1.0",
"dompurify": "2.4.0",
"focus-trap": "7.0.0",

View File

@ -17,6 +17,7 @@ import time from '~/utils/time'
import TrackFavoriteIcon from '~/components/favorites/TrackFavoriteIcon.vue'
import TrackPlaylistIcon from '~/components/playlists/TrackPlaylistIcon.vue'
import PlayerControls from '~/components/audio/PlayerControls.vue'
import MilkDrop from '~/components/audio/visualizer/MilkDrop.vue'
import VirtualList from '~/components/vui/list/VirtualList.vue'
import QueueItem from '~/components/QueueItem.vue'
@ -164,6 +165,7 @@ const hideArtist = () => {
<template v-if="currentTrack">
<div class="cover-container">
<div class="cover">
<milk-drop />
<img
ref="cover"
alt=""

View File

@ -0,0 +1,119 @@
<script setup lang="ts">
import { useMilkDrop } from '~/composables/audio/visualizer'
import { onScopeDispose, ref } from 'vue'
import { refAutoReset, useFullscreen, useIdle, useRafFn, whenever } from '@vueuse/core'
import { useQueue } from '~/composables/audio/queue'
const milkdrop = ref()
const canvas = ref()
const { isFullscreen: fullscreen, enter, exit } = useFullscreen(milkdrop)
const { visualizer, loadRandomPreset, render } = useMilkDrop(canvas)
const { currentTrack } = useQueue()
const { idle } = useIdle(2000)
const showTrackInfo = refAutoReset(false, 5000)
whenever(currentTrack, () => (showTrackInfo.value = true))
const { pause } = useRafFn(render)
onScopeDispose(() => {
exit()
pause()
if (visualizer.value) {
visualizer.value.loseGLContext()
}
})
</script>
<template>
<div
ref="milkdrop"
:class="['visualizer', { idle, fullscreen }]"
>
<canvas
ref="canvas"
@click="loadRandomPreset()"
/>
<button
v-if="!fullscreen"
class="ui secondary button"
@click="enter"
>
<i class="icon expand arrows alternate" />
</button>
<Transition name="slide-down">
<div
v-if="fullscreen && (!idle || showTrackInfo)"
class="track-info"
@click="loadRandomPreset()"
>
<h1>{{ currentTrack.title }}</h1>
<h2>{{ currentTrack.artistName }} &mdash; {{ currentTrack.albumTitle }}</h2>
</div>
</Transition>
</div>
</template>
<style scoped lang="scss">
.visualizer {
&.idle {
cursor: none;
}
&.fullscreen {
.slide-down-enter-active,
.slide-down-leave-active {
transition: all 0.2s ease;
will-change: transform, opacity;
}
.slide-down-enter-from,
.slide-down-leave-to {
opacity: 0;
transform: translateY(5vh);
}
}
button {
z-index: 2;
position: absolute;
right: 1em;
bottom: 1em;
padding: 0.78571429em;
margin-right: 0 !important;
i.icon {
display: inline-block;
margin-right: 0 !important;
display: contents;
&::before {
display: flex;
justify-content: center;
width: 1em;
height: 1em;
}
}
}
}
.track-info {
position: absolute;
z-index: 2;
bottom: 0;
left: 0;
width: 100%;
background: linear-gradient(to bottom, #0000, #000);
color: #fff;
text-align: left;
padding: 3em 1em 1em;
h1, h2 {
margin: 0;
}
}
</style>

View File

@ -0,0 +1,63 @@
import type { Ref } from 'vue'
import { AUDIO_CONTEXT, GAIN_NODE } from './audio-api'
import { useResizeObserver } from '@vueuse/core'
import { ref, markRaw } from 'vue'
// @ts-expect-error butterchurn has no typings
import butterchurnPresets from 'butterchurn-presets'
// @ts-expect-error butterchurn has no typings
import butterchurn from 'butterchurn'
export const useMilkDrop = (canvas: Ref<HTMLCanvasElement>) => {
const presets = Object.entries(butterchurnPresets)
const visualizer = ref()
const loadRandomPreset = (blendTime = 1) => {
const index = (presets.length * Math.random()) | 0
const [name, preset] = presets[index]
console.log(`Switching to preset: '${name}'`)
visualizer.value.loadPreset(preset, blendTime)
}
const initialize = (canvas: HTMLCanvasElement, width: number, height: number) => {
visualizer.value = markRaw(butterchurn.createVisualizer(AUDIO_CONTEXT, canvas, {
width,
height
}))
loadRandomPreset(0)
visualizer.value.connectAudio(GAIN_NODE)
}
useResizeObserver(canvas, ([entry]) => {
const { width, height } = entry.contentRect
console.log(width, height)
canvas.value.width = width
canvas.value.height = height
if (visualizer.value === undefined) {
initialize(entry.target as HTMLCanvasElement, width, height)
return
}
visualizer.value.setRendererSize(width, height)
})
const render = () => {
try {
visualizer.value?.render()
} catch (error) {
console.error(error)
loadRandomPreset(0)
}
}
return {
visualizer,
loadRandomPreset,
render
}
}

View File

@ -295,6 +295,7 @@
padding-bottom: 100%;
position: relative;
canvas,
img {
height: 100%;
width: 100%;
@ -302,6 +303,10 @@
top: 0;
left: 0;
}
canvas {
z-index: 1;
}
}
}

View File

@ -19,6 +19,11 @@
jsonpointer "^5.0.0"
leven "^3.1.0"
"@assemblyscript/loader@^0.17.11":
version "0.17.14"
resolved "https://registry.yarnpkg.com/@assemblyscript/loader/-/loader-0.17.14.tgz#43bfe793c787180c5eb0a57ada8318fb62171b4e"
integrity sha512-+PVTOfla/0XMLRTQLJFPg4u40XcdTfon6GGea70hBGi8Pd7ZymIXyVUR+vK8wt5Jb4MVKTKPIz43Myyebw5mZA==
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a"
@ -905,6 +910,13 @@
dependencies:
regenerator-runtime "^0.13.10"
"@babel/runtime@^7.12.5":
version "7.20.1"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.1.tgz#1148bb33ab252b165a06698fde7576092a78b4a9"
integrity sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg==
dependencies:
regenerator-runtime "^0.13.10"
"@babel/template@^7.0.0", "@babel/template@^7.18.10":
version "7.18.10"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71"
@ -2179,6 +2191,23 @@ buntis@0.2.1:
resolved "https://registry.yarnpkg.com/buntis/-/buntis-0.2.1.tgz#a043aabc7d64f2243bfaa53e34e999c2dd790e82"
integrity sha512-5wszfQlsqJmZrfxpPkO5yQcEoBAmfUYlXxXU/IM6PhPZ8DMnMMJQ9rvAHfe5WZmnB6E1IoJYylFfTaf1e2FJbQ==
butterchurn-presets@^3.0.0-beta.4:
version "3.0.0-beta.4"
resolved "https://registry.yarnpkg.com/butterchurn-presets/-/butterchurn-presets-3.0.0-beta.4.tgz#393d3f7863d546bdef2c52aec90da76bdbcacd3b"
integrity sha512-TbQLUPvGOYMZAtWKoCmBtludh9aQZ6NaMGQU4lvPeadBPy3Du3yNmwBjlTMLP5c5mRWElxQPjTL1PtR7FZK3OQ==
dependencies:
"@babel/runtime" "^7.12.5"
butterchurn@^3.0.0-beta.4:
version "3.0.0-beta.4"
resolved "https://registry.yarnpkg.com/butterchurn/-/butterchurn-3.0.0-beta.4.tgz#f459d8b81decd4f28f4c90b5c0fc40611ad3d86f"
integrity sha512-hiY1ktHYHQ8MT65nnZi7GjrgZZ6sl/ipT5rBqEfaYJd90L4SvOtB6lVxtKadtzAyJo2TQJc4gJfEca4cpZo0DA==
dependencies:
"@assemblyscript/loader" "^0.17.11"
"@babel/runtime" "^7.11.2"
ecma-proposal-math-extensions "0.0.2"
eel-wasm "^0.0.15"
c8@^7.12.0:
version "7.12.0"
resolved "https://registry.yarnpkg.com/c8/-/c8-7.12.0.tgz#402db1c1af4af5249153535d1c84ad70c5c96b14"
@ -2641,6 +2670,16 @@ easygettext@2.17.0:
"@vue/compiler-sfc" "^3.0.0"
pug "^3.0.2"
ecma-proposal-math-extensions@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/ecma-proposal-math-extensions/-/ecma-proposal-math-extensions-0.0.2.tgz#a6a3d64819db70cee0d2e1976b6315d00e4714a0"
integrity sha512-80BnDp2Fn7RxXlEr5HHZblniY4aQ97MOAicdWWpSo0vkQiISSE9wLR4SqxKsu4gCtXFBIPPzy8JMhay4NWRg/Q==
eel-wasm@^0.0.15:
version "0.0.15"
resolved "https://registry.yarnpkg.com/eel-wasm/-/eel-wasm-0.0.15.tgz#d7767081e16591ca02a223c2a62cc84d304021c5"
integrity sha512-FSTWf6lwGn7Zc3QiV+KxWTznIqq4j9eST/aXmyN/cC39+1Arqs13YOMosHQ7tqUt+OjQmG79Vd41f9gu+w1lvA==
ejs@^3.1.6:
version "3.1.8"
resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.8.tgz#758d32910c78047585c7ef1f92f9ee041c1c190b"