Add MilkDrop
This commit is contained in:
parent
bd1da668f5
commit
5eff89920a
|
@ -28,6 +28,8 @@
|
||||||
"@vueuse/router": "9.3.1",
|
"@vueuse/router": "9.3.1",
|
||||||
"axios": "0.27.2",
|
"axios": "0.27.2",
|
||||||
"axios-auth-refresh": "3.3.4",
|
"axios-auth-refresh": "3.3.4",
|
||||||
|
"butterchurn": "^3.0.0-beta.4",
|
||||||
|
"butterchurn-presets": "^3.0.0-beta.4",
|
||||||
"diff": "5.1.0",
|
"diff": "5.1.0",
|
||||||
"dompurify": "2.4.0",
|
"dompurify": "2.4.0",
|
||||||
"focus-trap": "7.0.0",
|
"focus-trap": "7.0.0",
|
||||||
|
|
|
@ -17,6 +17,7 @@ import time from '~/utils/time'
|
||||||
import TrackFavoriteIcon from '~/components/favorites/TrackFavoriteIcon.vue'
|
import TrackFavoriteIcon from '~/components/favorites/TrackFavoriteIcon.vue'
|
||||||
import TrackPlaylistIcon from '~/components/playlists/TrackPlaylistIcon.vue'
|
import TrackPlaylistIcon from '~/components/playlists/TrackPlaylistIcon.vue'
|
||||||
import PlayerControls from '~/components/audio/PlayerControls.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 VirtualList from '~/components/vui/list/VirtualList.vue'
|
||||||
import QueueItem from '~/components/QueueItem.vue'
|
import QueueItem from '~/components/QueueItem.vue'
|
||||||
|
|
||||||
|
@ -164,6 +165,7 @@ const hideArtist = () => {
|
||||||
<template v-if="currentTrack">
|
<template v-if="currentTrack">
|
||||||
<div class="cover-container">
|
<div class="cover-container">
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
|
<milk-drop />
|
||||||
<img
|
<img
|
||||||
ref="cover"
|
ref="cover"
|
||||||
alt=""
|
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%;
|
padding-bottom: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
canvas,
|
||||||
img {
|
img {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -302,6 +303,10 @@
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,11 @@
|
||||||
jsonpointer "^5.0.0"
|
jsonpointer "^5.0.0"
|
||||||
leven "^3.1.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":
|
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.18.6":
|
||||||
version "7.18.6"
|
version "7.18.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a"
|
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a"
|
||||||
|
@ -905,6 +910,13 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.13.10"
|
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":
|
"@babel/template@^7.0.0", "@babel/template@^7.18.10":
|
||||||
version "7.18.10"
|
version "7.18.10"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71"
|
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"
|
resolved "https://registry.yarnpkg.com/buntis/-/buntis-0.2.1.tgz#a043aabc7d64f2243bfaa53e34e999c2dd790e82"
|
||||||
integrity sha512-5wszfQlsqJmZrfxpPkO5yQcEoBAmfUYlXxXU/IM6PhPZ8DMnMMJQ9rvAHfe5WZmnB6E1IoJYylFfTaf1e2FJbQ==
|
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:
|
c8@^7.12.0:
|
||||||
version "7.12.0"
|
version "7.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/c8/-/c8-7.12.0.tgz#402db1c1af4af5249153535d1c84ad70c5c96b14"
|
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"
|
"@vue/compiler-sfc" "^3.0.0"
|
||||||
pug "^3.0.2"
|
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:
|
ejs@^3.1.6:
|
||||||
version "3.1.8"
|
version "3.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.8.tgz#758d32910c78047585c7ef1f92f9ee041c1c190b"
|
resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.8.tgz#758d32910c78047585c7ef1f92f9ee041c1c190b"
|
||||||
|
|
Loading…
Reference in New Issue