<script lang="ts"> import { afterUpdate, onMount } from 'svelte'; import { fade } from 'svelte/transition'; import { audioBlob, notesImage, style } from './stores'; import { styles } from './config.json'; let section: HTMLElement; let currentTime: number; let duration: number; let paused = true; let player: HTMLDivElement; let visualisation: HTMLImageElement; let imageWidth: number; let imageHeight: number; const updateDimensions = (): void => { imageWidth = visualisation && visualisation.clientWidth; imageHeight = visualisation && visualisation.clientHeight; }; onMount(() => { updateDimensions(); if ('mediaSession' in navigator) { navigator.mediaSession.setActionHandler('play', () => (paused = false)); navigator.mediaSession.setActionHandler('pause', () => (paused = true)); navigator.mediaSession.setActionHandler('stop', () => { paused = true; currentTime = 0; }); } window.scrollTo({ top: section.offsetTop, behavior: 'smooth' }); }); afterUpdate((): void => { updateDimensions(); }); const mouseMove = (event: MouseEvent): void => { if (!duration) { return; } if (!event.buttons) { return; } const { left, right } = player.getBoundingClientRect(); currentTime = (duration * (event.clientX - left)) / (right - left); }; const touchMove = (event: TouchEvent): void => { if (!duration) { return; } const { left, right } = player.getBoundingClientRect(); currentTime = (duration * (event.touches[0].clientX - left)) / (right - left); }; const keyDown = (event: KeyboardEvent): void => { event.preventDefault(); if (event.code === 'Space') { paused = !paused; } if (event.code === 'ArrowLeft') { currentTime = currentTime >= 1 ? currentTime - 1 : 0; } if (event.code === 'ArrowRight') { currentTime = currentTime <= duration - 1 ? currentTime + 1 : duration; } }; </script> <section bind:this={section} transition:fade> <img class="notes" src={$notesImage} alt="" bind:this={visualisation} /> <div bind:this={player} class="player" style:width={imageWidth + 'px'} style:height={imageHeight + 'px'} on:mousemove={mouseMove} on:touchmove|preventDefault={touchMove} on:keydown={keyDown} on:click={() => (paused = !paused)} tabindex="0" > <audio bind:currentTime bind:duration bind:paused src={$audioBlob} /> <div class="handle" style:transform="translate({Math.min(imageWidth * (currentTime / (duration - 0.9)), imageWidth)}px, -2%)" /> {#if paused} <img class="play-button" src="static/play.svg" alt="Play button" draggable="false" transition:fade style:width={imageHeight > 100 ? '20%' : '7.5%'} /> {/if} </div> <a href={$audioBlob} download={`${styles[$style]} Composition - AI Guru ft. Hugging Face.wav`} class="download" >Download</a > </section> <style> section { display: flex; flex-direction: column; position: relative; border: 2px solid hsl(0 0% 80%); border-radius: 0.375rem; padding: 1rem; } .player { position: absolute; left: 50%; transform: translateX(-50%); cursor: pointer; } .notes { width: min(100%, 512px); margin: auto; box-shadow: 0 0 5px 0.1px hsl(210, 10%, 20%); } audio { width: 100%; margin: 1rem auto; } .play-button { position: absolute; left: 50%; top: 50%; width: 20%; aspect-ratio: 1 / 1; transform: translate(-50%, -50%); filter: drop-shadow(0 0 5px black); pointer-events: none; cursor: pointer; } .handle { position: absolute; left: 0; top: 0; height: 104%; width: 0.2rem; border-radius: 0.1rem; background-color: white; cursor: pointer; transform: translate(0, -2%); } a.download { display: block; font-size: 1.2rem; font-family: 'Lato', sans-serif; font-weight: 700; color: hsl(0 0% 97%); background: transparent; border: 3px solid hsl(0 0% 97%); border-radius: 0.375rem; padding: 0.5rem 1rem; cursor: pointer; margin: 1rem auto auto; } @media (min-width: 600px) { section { padding: 2rem; } } </style>