Add MilkDrop
This commit is contained in:
parent
bd1da668f5
commit
5eff89920a
|
@ -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",
|
||||
|
|
|
@ -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=""
|
||||
|
|
|
@ -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 }} — {{ 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>
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue