Personal Site

Add animation when song changes.

Updated TODO list:

TODO:
- ✅ [DONE] resolve issue where focusing on artists when artist list updates closes the metadata changes (fix: move focus to equivalent index in artist list)
- ✅ [DONE] stop updating the data so frequently (only update on change)
- ✅ [DONE] hide popup when nothing playing
- 🟠 pause record player if nothing playing
- 🟠 pause record head if nothing playing
- ✅ [DONE] make record head move to stop position when changing song
- ✅ [DONE] remove randomness from record head animation
- ❌ [MARKED AS WONT-FIX] prolong the record head animation to 3 mins/length of song when js is enabled, to sync up better with real world (if it looks good)
- ❌ [MARKED AS WONT-FIX] crossfade animations for images when changing song
- 🟠 figure out what causes "The connection to http://localhost:3000/now-playing-sse was interrupted while the page was loading." error
- 🟠 preload all assets
- 🟠 figure out why firefox warns about unused box-circle-mask.png preload when it is used in bsky pfp in landing
- ❌ [MARKED AS CANT-BE-FUCKED] split up NowPlaying component if needed as is getting quite large

vielle.dev 427fda1f df8cc1fe

verified
+91 -19
+91 -19
src/components/playing/NowPlaying.astro
··· 554 554 nowPlaying: querySelector("now-playing", HTMLNowPlayingElement), 555 555 }; 556 556 557 + if (elements.spinner.length !== 2) 558 + throw new Error("Must have 2 `.spinner` elements!"); 559 + 560 + /************** 561 + * ANIMATIONS * 562 + **************/ 563 + 564 + // delete css animations since we r going to use our own 565 + elements.spinner.forEach((el) => 566 + el.getAnimations().forEach((anim) => anim.cancel()), 567 + ); 568 + 569 + const playHeadAnimation = [ 570 + { 571 + rotate: "0deg", 572 + }, 573 + { 574 + rotate: "25deg", 575 + offset: 0.05, 576 + }, 577 + { 578 + rotate: "45deg", 579 + offset: 0.7, 580 + }, 581 + { 582 + rotate: "45deg", 583 + offset: 0.75, 584 + }, 585 + { 586 + rotate: "0deg", 587 + offset: 0.8, 588 + }, 589 + ]; 590 + 591 + // start state is infered 592 + const goToStartAnimation = [{ rotate: "0deg" }]; 593 + 594 + const animations = elements.spinner.map((el) => 595 + el.animate(playHeadAnimation, { 596 + duration: 30 * 1000, 597 + fill: "forwards", 598 + iterations: Infinity, 599 + }), 600 + ); 601 + 557 602 /************ 558 603 * LISTENER * 559 604 ************/ ··· 575 620 if (!isNowPlaying(data)) 576 621 return console.warn("Unexpected package from server:", data, event.data); 577 622 578 - // data is valid nowPlayingData 579 - // first call so assume prev is in an invalid state (which it is) 580 - if (i === 0) console.log("SKIP: i = 0"); 581 - // if both are null, quit since re-rendering is pointless 582 - else if (data == prev) console.log("MATCH: null = null"); 583 - // now if both are valid play items, so check the ID for equality 584 - else if (data !== null && prev !== null && data.id === prev.id) 585 - console.log("MATCH: nowPlaying = nowPlaying"); 586 - else { 587 - console.log("DIFFERENT"); 588 - // there is now a difference between the previous setting and the new setting 589 - // so it is worth updating the UI 623 + try { 624 + // data is valid nowPlayingData 625 + // first call so assume prev is in an invalid state (which it is) 626 + if (i === 0) console.log("SKIP: i = 0"); 627 + // if both are null, quit since re-rendering is pointless 628 + else if (data == prev) console.log("MATCH: null = null"); 629 + // now if both are valid play items, so check the ID for equality 630 + else if (data !== null && prev !== null && data.id === prev.id) 631 + console.log("MATCH: nowPlaying = nowPlaying"); 632 + else { 633 + console.log("DIFFERENT"); 634 + // there is now a difference between the previous setting and the new setting 635 + // so it is worth updating the UI 636 + 637 + if (data) elements.nowPlaying.updateMetadata(data); 638 + else elements.nowPlaying.nothingPlaying(); 639 + 640 + // spinner head animation: 641 + // 1. pause current animation. 642 + animations.forEach((anim) => anim.pause()); 643 + elements.spinner.forEach((el) => 644 + // 2. send the playback head to the start 645 + el 646 + .animate(goToStartAnimation, { 647 + duration: 2.5 * 1000, 648 + easing: "ease-in-out", 649 + }) 650 + // 3. when the playback head is at the start 651 + .finished.then(async (x) => { 590 652 591 - elements.recordArt.src = data 592 - ? data?.album.images[0].url 593 - : "https://undefined"; 653 + // 4. update the record art 654 + elements.recordArt.src = data 655 + ? data?.album.images[0].url 656 + : "https://undefined"; 594 657 595 - if (data) elements.nowPlaying.updateMetadata(data); 596 - else elements.nowPlaying.nothingPlaying(); 658 + // 5. reset the position of the infinite animation 659 + animations.forEach((anim) => (anim.currentTime = 0)); 660 + 661 + // 6. after 2s 662 + setTimeout(() => { 663 + // resume the infinite animation 664 + animations.forEach((anim) => anim.play()); 665 + }, 2000); 666 + }), 667 + ); 668 + } 669 + } finally { 670 + prev = data; 597 671 } 598 - 599 - prev = data; 600 672 }); 601 673 </script>