fix(player): volume slider jumps / flickers

This commit is contained in:
2024-11-26 00:27:12 +01:00
parent 48c92f6b00
commit 9fc59cfd61
4 changed files with 61 additions and 24 deletions

View File

@@ -13,7 +13,6 @@
import { Progress } from '$lib/components/ui/progress'; import { Progress } from '$lib/components/ui/progress';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration'; import duration from 'dayjs/plugin/duration';
import { Slider } from '$lib/components/ui/slider';
import * as Popover from '$lib/components/ui/popover'; import * as Popover from '$lib/components/ui/popover';
import { Separator } from '$lib/components/ui/separator'; import { Separator } from '$lib/components/ui/separator';
import { getPlayerState } from '$lib/player.svelte'; import { getPlayerState } from '$lib/player.svelte';
@@ -77,19 +76,24 @@
}; };
}; };
const submitVolumeForm: SubmitFunction = async ({ formData }) => {
formData.set('volume', player.volume.toString());
return async ({ update, result }) => {
await update({
invalidateAll: false
});
if (result.type === 'success' && result.data && 'volume' in result.data) {
player.volume = result.data.volume;
}
};
};
function onMouseMove(e: MouseEvent) { function onMouseMove(e: MouseEvent) {
mouse.offsetX = e.offsetX; mouse.offsetX = e.offsetX;
mouse.offsetY = e.offsetY; mouse.offsetY = e.offsetY;
} }
let volume = $state({
get value() {
return [player.volume];
},
set value(v: number[]) {
player.volume = v[0];
}
});
</script> </script>
<footer class="border border-x-0 border-t border-border/40 bg-background/95 px-4"> <footer class="border border-x-0 border-t border-border/40 bg-background/95 px-4">
@@ -189,20 +193,23 @@
class="flex-1" class="flex-1"
method="POST" method="POST"
action="/player?/volume" action="/player?/volume"
use:enhance use:enhance={submitVolumeForm}
> >
<input type="hidden" name="volume" value={volume.value[0]} /> <input
<Slider class="volume-slider"
class="w-full" type="range"
min={0} min={0.0}
max={1} max={1.0}
step={0.01} step={0.01}
bind:value={volume.value} bind:value={player.volume}
onValueChange={() => { oninput={() => {
volumeForm?.requestSubmit(); volumeForm?.requestSubmit();
}} }}
onValueCommit={() => { onmousedown={() => {
volumeForm?.requestSubmit(); player.adjustingVolume = true;
}}
onmouseup={() => {
player.adjustingVolume = false;
}} }}
/> />
</form> </form>
@@ -240,3 +247,30 @@
</footer> </footer>
<svelte:window onmousemove={onMouseMove} /> <svelte:window onmousemove={onMouseMove} />
<style>
.volume-slider {
-webkit-appearance: none !important; /* Override default CSS styles */
appearance: none !important;
width: 100%; /* Full-width */
height: 8px; /* Specified height */
border-radius: 0.5rem;
background: hsl(var(--secondary));
outline: none; /* Remove outline */
opacity: 0.7; /* Set transparency (for mouse-over effects on hover) */
-webkit-transition: 0.2s; /* 0.2 seconds transition on hover */
transition: opacity 0.2s;
}
.volume-slider::-webkit-slider-thumb {
@apply size-4 bg-background ring ring-secondary ring-offset-0 ring-offset-transparent transition-colors;
appearance: none;
background-color: hsl(var(--primary));
border-radius: 100%;
}
.volume-slider::-webkit-slider-thumb:active {
@apply ring-offset-2 ring-offset-background;
}
</style>

View File

@@ -25,6 +25,7 @@
if (result.type === 'success' && result.data) { if (result.type === 'success' && result.data) {
player.currentlyPlaying = result.data.track ?? null; player.currentlyPlaying = result.data.track ?? null;
player.progress = result.data.position; player.progress = result.data.position;
player.isPaused = false;
} }
}; };
}; };

View File

@@ -5,10 +5,11 @@ import { PlayerClient } from './proto/player.client';
import { protoTransport } from '../hooks.client'; import { protoTransport } from '../hooks.client';
class PlayerState { class PlayerState {
volume = $state(0); volume = $state(0.0);
currentlyPlaying = $state<Track | null>(null); currentlyPlaying = $state<Track | null>(null);
progress = $state<bigint>(0n); progress = $state<bigint>(0n);
isPaused = $state(false); isPaused = $state(false);
adjustingVolume = $state(false);
#abortContoller: AbortController | null = null; #abortContoller: AbortController | null = null;
constructor() { constructor() {
@@ -26,7 +27,10 @@ class PlayerState {
} }
applyStatus(status: PlayerStatus) { applyStatus(status: PlayerStatus) {
if (!this.adjustingVolume) {
this.volume = status.volume; this.volume = status.volume;
}
this.progress = status.progress; this.progress = status.progress;
this.currentlyPlaying = status.currentlyPlaying ?? null; this.currentlyPlaying = status.currentlyPlaying ?? null;
this.isPaused = status.isPaused; this.isPaused = status.isPaused;

View File

@@ -1,5 +1,3 @@
import { PlayerClient } from '$lib/proto/player.client';
import { protoTransport } from '../hooks.server';
import type { LayoutServerLoad } from './$types'; import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async () => { export const load: LayoutServerLoad = async () => {