source: Klonkt/src/assets/js/mod/tape.js@ 16fd4fe

main
Last change on this file since 16fd4fe was 16fd4fe, checked in by Robin <roboburr@…>, 3 weeks ago

De cassettespeler: draaiende spoelen, vooruit en achteruit

Een album toont een genummerde lijst waar je in prikt. Een cassette is het
tegenovergestelde: je spoelt, en je hoort wat er komt in de volgorde waarin het
is opgenomen. Vandaar een eigen vorm en niet het albumjasje met een plaatje.

DE SPELER IS DE BESTAANDE SPELER. De afspeelknop draagt exact dezelfde
data-attributen als een albumhoes, dus audio-player.js pakt hem op; spoelen is
next()/prev(). Een tweede speler ernaast bouwen zou twee dingen tegelijk laten
klinken en de sessie weggooien die een paginawissel overleeft. Daarom spoelt
het per NUMMER en niet per seconde: die speler denkt in een wachtrij.

De nummers staan er als inhoud, niet als knoppen. Geen speel-url, geen knop,
geen hover -- op een cassette kun je niet naar nummer zeven springen. Wat er
speelt lees je op de statusregel, en dat is meteen de toegankelijke kant.

Verder:

  • De pauzeknop vangt zijn klik in de CAPTURE-fase, want audio-player.js luistert in de bubbelfase en zou de wachtrij anders opnieuw vanaf een zetten.
  • Spoelen doet niets als dit bandje niet loopt; anders verzet een spoelknop de wachtrij van een ander blok.
  • init() ruimt de tikker altijd eerst op, ook zonder bandje op de pagina. Zonder dat blijft hij lopen na een paginawissel -- dezelfde fout die in de leesweergave ooit de hele site heeft vastgezet.
  • prefers-reduced-motion zet het draaien uit; de statusregel blijft.

Opmaak in dezelfde familie als .post-album: zelfde rand, radius en papier, en
alleen bestaande variabelen, dus licht, donker en het accent volgen vanzelf.

MOD_V 52, style.css v106.

Co-Authored-By: Claude Opus 5 <noreply@…>

  • Property mode set to 100644
File size: 4.8 KB
Line 
1/**
2 * Het bandje: een cassette die draait, en alleen vooruit en achteruit speelt.
3 *
4 * WAT DEZE MODULE WEL EN NIET DOET. Hij speelt niets af. De site heeft al een
5 * speler (audio-player.js) met een wachtrij, een mini-speler onderin en een
6 * sessie die een paginawissel overleeft. Een tweede speler ernaast bouwen om
7 * een bandgevoel na te doen zou twee dingen tegelijk laten klinken en die hele
8 * sessie weggooien. Dus: de afspeelknop draagt dezelfde data-attributen als een
9 * albumhoes en wordt door die speler opgepakt, en spoelen is next()/prev().
10 *
11 * DAAROM SPOELT HET PER NUMMER en niet per seconde. Die speler denkt in een
12 * wachtrij. Dat is een eerlijke benadering van een cassette: je komt vooruit en
13 * achteruit, en je springt niet naar nummer zeven.
14 *
15 * WAAROM ER GEPOLLD WORDT. audio-player.js zendt geen gebeurtenissen uit -- het
16 * is een gesloten module met een klein oppervlak (setQueue, play, pause, next,
17 * prev, isPlaying, currentTrack). Liever hier vijf keer per seconde kijken dan
18 * daar een gebeurtenissenlaag inbouwen die de rest van de site niet vraagt. Het
19 * polsen stopt zodra er geen bandje op de pagina staat.
20 */
21
22const TIK_MS = 200;
23let tikker = null;
24let gebonden = null; // de container waarop de klikafhandelaar hangt
25
26/** De speler, of niets als hij (nog) niet geladen is. */
27const speler = () => window.pcmsAudioPlayer || null;
28
29/** De url's die op dit bandje staan, in volgorde. */
30function bandUrls(tape) {
31 try {
32 return JSON.parse(tape.dataset.pcmsAlbum || '[]').map((t) => t.url);
33 } catch { return []; }
34}
35
36/**
37 * Speelt DIT bandje op dit moment? Vergelijken op url en niet op index: de
38 * wachtrij kan ondertussen van een ander blok komen, en dan hoort deze cassette
39 * gewoon stil te staan.
40 */
41function huidigeIndex(tape) {
42 const p = speler();
43 if (!p) return -1;
44 const nu = p.currentTrack();
45 if (!nu || !nu.url) return -1;
46 return bandUrls(tape).indexOf(nu.url);
47}
48
49function tekenBand(tape) {
50 const i = huidigeIndex(tape);
51 const p = speler();
52 const draait = i >= 0 && !!p && p.isPlaying();
53
54 tape.classList.toggle('is-playing', draait);
55 tape.classList.toggle('is-loaded', i >= 0);
56
57 const knop = tape.querySelector('[data-tape-play]');
58 if (knop) {
59 knop.classList.toggle('is-pauze', draait);
60 knop.setAttribute('aria-label', draait ? 'Pauzeren' : 'Afspelen');
61 }
62
63 // Welk nummer loopt er. Bij een bandje is dat de enige plek waar je het leest,
64 // want de lijst is met opzet geen knoppenrij.
65 const items = [...tape.querySelectorAll('.tape-track')];
66 items.forEach((li, n) => li.classList.toggle('is-current', n === i));
67 const nu = tape.querySelector('[data-tape-now]');
68 if (nu) {
69 const titel = i >= 0 && items[i] ? items[i].querySelector('.tape-track-title').textContent : '';
70 nu.textContent = i >= 0 ? `${i + 1}. ${titel}` : '';
71 }
72}
73
74function tekenAlles() {
75 for (const tape of document.querySelectorAll('.post-tape')) tekenBand(tape);
76}
77
78function opKlik(e) {
79 const tape = e.target.closest && e.target.closest('.post-tape');
80 if (!tape) return;
81 const p = speler();
82
83 const spoel = e.target.closest('[data-tape-go]');
84 if (spoel) {
85 if (!p) return;
86 // Spoelen kan alleen als DIT bandje loopt. Anders zou de knop de wachtrij
87 // van een ander blok verzetten, en dat is niet wat een spoelknop op deze
88 // cassette hoort te doen.
89 if (huidigeIndex(tape) < 0) return;
90 if (spoel.dataset.tapeGo === 'fwd') p.next(); else p.prev();
91 setTimeout(tekenAlles, 60);
92 return;
93 }
94
95 const play = e.target.closest('[data-tape-play]');
96 if (play && p) {
97 // Loopt dit bandje al, dan is deze knop een pauzeknop. De globale
98 // afhandelaar van audio-player.js zou hem anders opnieuw vanaf nummer een
99 // starten, want die kent alleen "speel deze wachtrij".
100 const i = huidigeIndex(tape);
101 if (i >= 0) {
102 e.preventDefault();
103 e.stopPropagation();
104 if (p.isPlaying()) p.pause(); else p.play();
105 setTimeout(tekenAlles, 60);
106 }
107 // Staat het bandje stil, dan laten we de klik doorlopen: de speler pakt de
108 // data-attributen op en zet de wachtrij op.
109 }
110}
111
112export function init() {
113 const tapes = document.querySelectorAll('.post-tape');
114
115 // Altijd eerst opruimen, ook als er niets staat. Zonder dat blijft de tikker
116 // lopen na een paginawissel naar een pagina zonder bandje -- dezelfde fout
117 // die in de leesweergave een keer de hele site heeft vastgezet.
118 if (tikker) { clearInterval(tikker); tikker = null; }
119 if (gebonden) { gebonden.removeEventListener('click', opKlik, true); gebonden = null; }
120 if (!tapes.length) return;
121
122 // Vangen in de CAPTURE-fase, want de speler luistert zelf op document in de
123 // bubbelfase. Alleen zo kan de pauzeknop zijn klik tegenhouden voordat de
124 // wachtrij opnieuw wordt gezet.
125 gebonden = document;
126 gebonden.addEventListener('click', opKlik, true);
127
128 tikker = setInterval(tekenAlles, TIK_MS);
129 tekenAlles();
130}
Note: See TracBrowser for help on using the repository browser.