mirror of
https://github.com/SrIzan10/lofi.git
synced 2026-06-06 00:56:53 +00:00
feat: background changer and such
This commit is contained in:
@@ -1,17 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { setMode } from 'mode-watcher';
|
||||
import { state as appState } from '@/state.svelte';
|
||||
|
||||
export let videoSelector: string = "#bg-video";
|
||||
export let updateInterval: number = 2000;
|
||||
console.log('update')
|
||||
let { videoSelector }: { videoSelector: string } = $props();
|
||||
|
||||
function analyzeVideoBrightness(video: HTMLVideoElement, canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) {
|
||||
if (video.paused || video.ended) return;
|
||||
const perf = window.performance.now();
|
||||
if (video.paused || video.ended || video.readyState < 2) return;
|
||||
|
||||
canvas.width = 32;
|
||||
canvas.height = 32;
|
||||
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
||||
|
||||
const sourceY = Math.floor(video.videoHeight * 0.67); // take the top two-thirds
|
||||
const sourceHeight = Math.floor(video.videoHeight * 0.33); // take the bottom third
|
||||
|
||||
// draw the bottom third of the video to the canvas
|
||||
// prettier-ignore
|
||||
ctx.drawImage(
|
||||
video,
|
||||
0, sourceY, video.videoWidth, sourceHeight,
|
||||
0, 0, canvas.width, canvas.height
|
||||
);
|
||||
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const pixels = imageData.data;
|
||||
@@ -30,30 +39,40 @@
|
||||
}
|
||||
|
||||
const avgBrightness = totalBrightness / pixelCount;
|
||||
const isDark = avgBrightness < 0.5;
|
||||
const isDark = avgBrightness < 0.45;
|
||||
|
||||
setMode(isDark ? 'dark' : 'light');
|
||||
|
||||
const now = window.performance.now();
|
||||
console.log(`brightness: ${avgBrightness}, time taken: ${now - perf}ms`);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
$effect(() => {
|
||||
const videoId = appState.currentBackgroundId;
|
||||
if (!videoId) return;
|
||||
|
||||
const video = document.querySelector(videoSelector) as HTMLVideoElement;
|
||||
if (!video) return;
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
const analyzeWhenReady = () => {
|
||||
if (video.readyState >= 2) {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
analyzeVideoBrightness(video, canvas, ctx);
|
||||
video.removeEventListener('loadeddata', analyzeWhenReady);
|
||||
}
|
||||
};
|
||||
|
||||
function updateGlobalTextColor() {
|
||||
analyzeVideoBrightness(video, canvas, ctx!);
|
||||
if (video.readyState >= 2) {
|
||||
analyzeWhenReady();
|
||||
}
|
||||
|
||||
video.addEventListener('loadeddata', updateGlobalTextColor);
|
||||
|
||||
const interval = setInterval(updateGlobalTextColor, updateInterval);
|
||||
|
||||
|
||||
video.addEventListener('loadeddata', analyzeWhenReady);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
video.removeEventListener('loadeddata', updateGlobalTextColor);
|
||||
video.removeEventListener('loadeddata', analyzeWhenReady);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
41
src/lib/components/app/bg-dropdown.svelte
Normal file
41
src/lib/components/app/bg-dropdown.svelte
Normal file
@@ -0,0 +1,41 @@
|
||||
<script lang="ts">
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index.js';
|
||||
import { buttonVariants } from '$lib/components/ui/button/index.js';
|
||||
import { state as appState } from '@/state.svelte';
|
||||
import Image from '@lucide/svelte/icons/image';
|
||||
|
||||
let selectedBackgroundId = $state(appState.currentBackgroundId!.toString());
|
||||
$effect(() => {
|
||||
appState.currentBackgroundId = selectedBackgroundId;
|
||||
})
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger class={buttonVariants({ variant: 'default', size: 'icon' })}>
|
||||
<Image />
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content
|
||||
class="max-h-[50vh] overflow-y-auto"
|
||||
>
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.GroupHeading>Select background</DropdownMenu.GroupHeading>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.RadioGroup bind:value={selectedBackgroundId} class="grid grid-cols-2 md:grid-cols-4">
|
||||
{#each appState.backgrounds as background}
|
||||
<DropdownMenu.RadioItem value={background.id.toString()} hideRadio>
|
||||
<div class="relative flex items-center flex-col">
|
||||
<div class="absolute bottom-0 left-0 w-full bg-black/50 text-white text-sm text-center p-1">
|
||||
{background.name}
|
||||
</div>
|
||||
<img
|
||||
src={background.thumbnailUrl}
|
||||
alt={background.name}
|
||||
class="size-32 object-cover rounded-sm"
|
||||
/>
|
||||
</div>
|
||||
</DropdownMenu.RadioItem>
|
||||
{/each}
|
||||
</DropdownMenu.RadioGroup>
|
||||
</DropdownMenu.Group>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
@@ -1,5 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { state as appState } from '@/state.svelte';
|
||||
|
||||
let video: HTMLVideoElement | null = null;
|
||||
$effect(() => {
|
||||
const backgroundId = appState.currentBackgroundId;
|
||||
if (backgroundId && video) {
|
||||
const resolvedBg = appState.backgrounds.find((bg) => bg.id === backgroundId);
|
||||
// i may or may not have yoinked this cors proxy for other purposes
|
||||
video.src = `https://cors.notesnook.com/${resolvedBg?.landscapeUrl}` || '';
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<video
|
||||
src={true ? "https://ch-cdn.srizan.dev/discord-rain.mp4" : "https://ch-cdn.srizan.dev/flower-shop-beachside-moewalls-com.mp4"}
|
||||
bind:this={video}
|
||||
src={"https://example.com"}
|
||||
autoplay
|
||||
loop
|
||||
muted
|
||||
@@ -7,4 +22,4 @@
|
||||
class="absolute top-0 left-0 w-full h-full object-cover -z-10"
|
||||
id="bg-video"
|
||||
crossorigin="anonymous"
|
||||
></video>
|
||||
></video>
|
||||
@@ -1,4 +1,5 @@
|
||||
<script>
|
||||
import BgDropdown from '@/components/app/bg-dropdown.svelte';
|
||||
import Disclaimer from '@/components/app/disclaimer.svelte';
|
||||
import MusicPlayer from '@/components/app/now-playing.svelte';
|
||||
import StationDropdown from '@/components/app/station-dropdown.svelte';
|
||||
@@ -12,6 +13,7 @@
|
||||
<div class="hidden sm:block flex-1"></div>
|
||||
<div class="flex gap-4 mt-3 sm:mt-0">
|
||||
<StationDropdown />
|
||||
<BgDropdown />
|
||||
<Disclaimer />
|
||||
</div>
|
||||
</div>
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
// svelte-ignore non_reactive_update
|
||||
let audioElement: HTMLAudioElement;
|
||||
let isInteracting = false;
|
||||
|
||||
function togglePlayback(play: boolean) {
|
||||
if (!audioElement) return;
|
||||
@@ -31,17 +30,16 @@
|
||||
const data = await getGeneralData();
|
||||
state.presets = data.presets;
|
||||
state.stations = data.stations;
|
||||
state.backgrounds = data.backgrounds;
|
||||
// TODO: support parent backgrounds
|
||||
state.backgrounds = data.backgrounds.filter(bg => bg.isActive === 1 && !bg.parentId);
|
||||
state.atmospheres = data.atmospheres;
|
||||
|
||||
if (data.stations.length > 0 && state.currentStation === null) {
|
||||
state.currentStation = data.stations[0].id;
|
||||
}
|
||||
if (data.backgrounds.length > 0 && state.currentBackgroundId === null) {
|
||||
const firstActiveBg = data.backgrounds.find((bg) => bg.isActive && !bg.parentId);
|
||||
state.currentBackgroundId = firstActiveBg
|
||||
? firstActiveBg.id
|
||||
: data.backgrounds[0]?.id || null;
|
||||
if (state.backgrounds.length > 0 && state.currentBackgroundId === null) {
|
||||
state.currentBackgroundId = state.backgrounds[0].id;
|
||||
console.log('asdf', state.currentBackgroundId);
|
||||
} else {
|
||||
state.error = 'Failed to load initial data (empty response).';
|
||||
}
|
||||
|
||||
@@ -7,24 +7,36 @@
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children: childrenProp,
|
||||
hideRadio = $bindable(false),
|
||||
...restProps
|
||||
}: WithoutChild<DropdownMenuPrimitive.RadioItemProps> = $props();
|
||||
}: FixedProps = $props();
|
||||
|
||||
interface FixedProps extends WithoutChild<DropdownMenuPrimitive.RadioItemProps> {
|
||||
/**
|
||||
* Wether if the circle should be shown or not
|
||||
* @default false
|
||||
*/
|
||||
hideRadio?: boolean;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
bind:ref
|
||||
class={cn(
|
||||
'data-[highlighted]:bg-white/20 data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
'data-[highlighted]:bg-white/20 data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
hideRadio ? 'pl-2' : 'pl-8',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{#snippet children({ checked })}
|
||||
<span class="absolute left-2 flex size-3.5 items-center justify-center">
|
||||
{#if checked}
|
||||
<Circle class="size-2 fill-current" />
|
||||
{/if}
|
||||
</span>
|
||||
{#if !hideRadio}
|
||||
<span class="absolute left-2 flex size-3.5 items-center justify-center">
|
||||
{#if checked}
|
||||
<Circle class="size-2 fill-current" />
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
{@render childrenProp?.({ checked })}
|
||||
{/snippet}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
@@ -11,6 +11,6 @@
|
||||
|
||||
<DropdownMenuPrimitive.Separator
|
||||
bind:ref
|
||||
class={cn('bg-muted -mx-1 my-1 h-px', className)}
|
||||
class={cn('bg-white/20 -mx-1 my-1 h-px', className)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<BgImage />
|
||||
<Daemon />
|
||||
<BackgroundAnalyzer videoSelector="#bg-video" updateInterval={2000} />
|
||||
<BackgroundAnalyzer videoSelector="#bg-video" />
|
||||
|
||||
{#if state.isLoading && !state.hasInteracted}
|
||||
<div class="flex flex-col h-screen w-full items-center justify-center space-y-2">
|
||||
|
||||
Reference in New Issue
Block a user