mirror of
https://github.com/danbulant/osuVisualizer
synced 2026-06-10 02:00:20 +00:00
284 lines
No EOL
8.6 KiB
Svelte
284 lines
No EOL
8.6 KiB
Svelte
<script>
|
|
import Options from "./components/options.svelte";
|
|
const fs = require("fs");
|
|
const osuParser = require("./lib/osu-parser.js");
|
|
|
|
export var osuData;
|
|
export var song;
|
|
export var config;
|
|
|
|
var last = Date.now();
|
|
var lastVolumeUpdate = Date.now() - 5000;
|
|
var settingsOpen = false;
|
|
var now = Date.now();
|
|
|
|
setInterval(() => {
|
|
now = Date.now();
|
|
}, 800);
|
|
|
|
function resetPool() {
|
|
if(!osuData.songs) return false;
|
|
osuData.songPool = osuData.songs.filter(v => true);
|
|
let a = osuData.songPool;
|
|
for (let i = a.length - 1; i > 0; i--) { // shuffle
|
|
const j = Math.floor(Math.random() * (i + 1));
|
|
[a[i], a[j]] = [a[j], a[i]];
|
|
}
|
|
osuData.songPool.forEach(v => {
|
|
delete v.audio;
|
|
delete v.video;
|
|
v.playing = true;
|
|
})
|
|
song = osuData.songPool.shift();
|
|
}
|
|
|
|
function playNext() {
|
|
if(song.audio) {
|
|
song.audio.pause();
|
|
}
|
|
if(osuData.songPool.length) {
|
|
song = osuData.songPool.shift();
|
|
} else {
|
|
resetPool();
|
|
}
|
|
}
|
|
|
|
$: resetPool(osuData.songs);
|
|
|
|
$: console.log(song);
|
|
|
|
$: {
|
|
(() => {
|
|
if(song && song.folder && !song.audio) {
|
|
// var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
|
// song.context = audioCtx;
|
|
// song.analyser = audioCtx.createAnalyser();
|
|
song.audio = new Audio(process.env.USERPROFILE + "/AppData/Local/osu!/Songs/" + song.folder + "/" + song.audioFile);
|
|
// song.source = audioCtx.createMediaElementSource(song.audio);
|
|
// song.source.connect(song.analyser);
|
|
// song.analyser.connect(audioCtx.destination);
|
|
song.audio.play();
|
|
|
|
song.audio.onended = () => {
|
|
playNext();
|
|
}
|
|
song.audio.onpause = () => {
|
|
song.playing = false;
|
|
if(song.video) song.video.pause();
|
|
}
|
|
song.audio.onplay = () => {
|
|
song.playing = true;
|
|
if(song.video) song.video.play();
|
|
}
|
|
if ('mediaSession' in navigator && config.mediaSession) {
|
|
navigator.mediaSession.metadata = new MediaMetadata({
|
|
title: song.song,
|
|
artist: song.artist,
|
|
album: "Osu! visualizer",
|
|
artwork: [
|
|
// { src: process.env.USERPROFILE + "/AppData/Local/osu!/Data/bt/" + song.id + ".jpg", type: 'image/jpeg' },
|
|
]
|
|
});
|
|
|
|
navigator.mediaSession.setActionHandler('play', function() { song.playing = true; song.audio.play(); });
|
|
navigator.mediaSession.setActionHandler('pause', function() { song.playing = false; song.audio.pause(); });
|
|
navigator.mediaSession.setActionHandler('nexttrack', function() { playNext()});
|
|
}
|
|
}
|
|
})();
|
|
}
|
|
|
|
$: if(song && song.audio && config.rpc) {
|
|
if(song.playing) {
|
|
window.songActivity = {
|
|
state: "Listening to osu! beatmaps",
|
|
details: `${song.artist} - ${song.song}`,
|
|
startTimestamp: Date.now(),
|
|
endTimestamp: Date.now() + song.audio.duration * 1000,
|
|
instance: false,
|
|
largeImageKey: "logo",
|
|
largeImageText: "Osu!visualizer"
|
|
}
|
|
} else {
|
|
window.songActivity = {
|
|
state: "Paused",
|
|
details: `${song.artist} - ${song.song}`,
|
|
instance: false,
|
|
largeImageKey: "logo",
|
|
largeImageText: "Osu!visualizer"
|
|
}
|
|
}
|
|
}
|
|
|
|
function togglePlay() {
|
|
song.playing = !song.playing;
|
|
if(!song.audio) return;
|
|
if(song.playing) {
|
|
song.audio.play();
|
|
} else {
|
|
song.audio.pause();
|
|
}
|
|
}
|
|
|
|
var volume = 1;
|
|
|
|
function updateVolume(e) {
|
|
if(!song || !song.audio || !e.altKey) return;
|
|
lastVolumeUpdate = Date.now();
|
|
volume += e.deltaY * -0.0005;
|
|
volume = Math.min(1, Math.max(volume, 0));
|
|
}
|
|
|
|
$: if(song.audio) song.audio.volume = volume;
|
|
|
|
setTimeout(() => {
|
|
playNext();
|
|
}, 200);
|
|
|
|
const volumeWidth = 100;
|
|
const volumeStroke = 4;
|
|
const volumeRadius = 50;
|
|
</script>
|
|
|
|
<svelte:window on:mousemove={() => last = Date.now()} on:wheel={e => updateVolume(e)} />
|
|
|
|
<div class="menu">
|
|
{#if now - last < config.autohide.info + 1000}
|
|
<div class="info" class:hidden={now - last > config.autohide.info}>
|
|
{#if song}
|
|
<div class="song">
|
|
<h2>{song.artist} - {song.song}</h2>
|
|
<div class="controls">
|
|
<div class="play" on:click={togglePlay}>
|
|
<img src="images/music_{song.playing ? "pause" : "play"}.svg" alt="{song.playing ? "Pause" : "Play"} music" title="{song.playing ? "Pause" : "Play"} music">
|
|
</div>
|
|
<div class="forward" on:click={playNext}>
|
|
<img src="images/music_forward.svg" alt="Skip the song" title="Skip the song">
|
|
</div>
|
|
<div class="settings" on:click={() => settingsOpen = !settingsOpen}>
|
|
<img src="images/settings.svg" alt="Settings" title="Open settings">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
{#if now - lastVolumeUpdate < config.autohide.volume + 1000 && song && song.audio}
|
|
<div class="volume" class:hidden={now - lastVolumeUpdate > config.autohide.volume}>
|
|
<div class="slider">
|
|
<div class="percent">
|
|
{Math.round(song.audio.volume * 100)}%
|
|
</div>
|
|
<svg class="progress-ring" width={volumeWidth} height={volumeWidth}>
|
|
<circle
|
|
stroke-width={volumeStroke}
|
|
fill="transparent"
|
|
stroke="blue"
|
|
stroke-dasharray={volumeRadius * 2 * Math.PI + " " + volumeRadius * 2 * Math.PI}
|
|
stroke-dashoffset={volumeRadius * 2 * Math.PI - song.audio.volume * volumeRadius * 2 * Math.PI}
|
|
r={volumeRadius - 1}
|
|
cx={volumeRadius - 1}
|
|
cy={volumeRadius + 1}
|
|
/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
<Options bind:config={config} bind:visible={settingsOpen} />
|
|
</div>
|
|
|
|
<style>
|
|
.info {
|
|
opacity: 1;
|
|
position: relative;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100vw;
|
|
height: 80px;
|
|
transition: opacity 0.6s;
|
|
z-index: 2;
|
|
}
|
|
|
|
.volume {
|
|
opacity: 1;
|
|
position: fixed;
|
|
z-index: 5;
|
|
right: 0;
|
|
bottom: 0;
|
|
border-radius: 50%;
|
|
font-size: 30px;
|
|
color: white;
|
|
background-color: black;
|
|
width: 100px;
|
|
height: 100px;
|
|
}
|
|
.volume .slider {
|
|
position: relative;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
.percent {
|
|
position: absolute;
|
|
top: 25px;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
text-align: center;
|
|
}
|
|
.progress-ring {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
.progress-ring circle {
|
|
transition: stroke-dashoffset 0.32s;
|
|
transform: rotate(-90deg);
|
|
transform-origin: 50% 50%;
|
|
position: absolute;
|
|
top: 1px;
|
|
left: 1px;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.hidden {
|
|
opacity: 0;
|
|
transition: opacity 1s;
|
|
}
|
|
|
|
.info .song {
|
|
color: white;
|
|
position: absolute;
|
|
padding: 5px 5px 5px 25px;
|
|
top: 0;
|
|
right: 0;
|
|
text-align: right;
|
|
background: black;
|
|
|
|
background: linear-gradient(90deg, transparent 0%, rgba(0,0,0,0.5) 15%, rgba(0,0,0,0.5) 100%);
|
|
}
|
|
|
|
.info .song h2 {
|
|
margin: 0;
|
|
}
|
|
|
|
.info .controls {
|
|
height: 50px;
|
|
display: flex;
|
|
}
|
|
.info .controls div {
|
|
height: 100%;
|
|
}
|
|
.info .controls img {
|
|
height: 100%;
|
|
filter: invert(100%);
|
|
}
|
|
.info .controls .settings img {
|
|
height: 65%;
|
|
padding-top: 25%;
|
|
}
|
|
</style> |