Changeset e679bce in Klonkt for src/assets/js/mod/tape.js


Ignore:
Timestamp:
08/22/2026 09:54:51 AM (3 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
8bb3b09
Parents:
16fd4fe
Message:

Het bandje speelt als bandje: een cassette, een object, spoelen in seconden

Robins correctie (21-8): vooruit en achteruit deden hier next()/prev(), en dat
is een playlistgebaar. Dan is het een lijst met een cassetteplaatje erboven.
Een cassette kent geen "volgend nummer" -- je houdt de knop ingedrukt, de band
loopt door, en je laat los waar je bent.

Dat kan omdat audio-player.js een wachtrij al in EEN doorlopende
MediaSource-tijdlijn giet: "een trackwissel is een positie, geen omschakeling".
Een bandje IS die tijdlijn. Dus:

  • seekBy(seconden) op de speler, over de nummergrenzen heen. Op de blob-motor (iOS, geen MSE) bestaat die tijdlijn niet; daar loopt spoelen tot de rand van het nummer en stapt dan naar de buur. Grover, maar eerlijker dan doen alsof.
  • Bandmodus: de speler toont het BANDJE als titel met het lopende nummer eronder, en de teller loopt over de hele band in plaats van per nummer. Dat is wat "een object, geen losse tracks" betekent op het scherm.
  • Vasthouden versnelt (4x tot 16x), loslaten stopt -- ook als je buiten de knop loslaat. Een korte tik spoelt vijf seconden, want vasthouden is met een spatiebalk geen gebaar.

Een echte cassette, als SVG. Verhouding 100,4 x 63,8 mm, de maat van een
compact cassette, dus viewBox 314x200: behuizing, labelvlak met de titel erop,
venster, twee spoelen met tandjes die meedraaien, de bandpakketten, vier
schroefjes en de openingen voor de kop en de capstans. De vorige versie waren
twee CSS-schijfjes.

Twee dingen die alleen door het echt te draaien boven kwamen:

  • audio-player.js kiest zijn knoppen met een lijst KLASSENAMEN (PLAY_SELECTOR), niet met een regel over data-attributen. De cassetteknop droeg alle juiste data-pcms-* en deed niets. Er staat nu een test op.
  • De sessie sloeg displayTimes().cur op als trackpositie. In bandmodus is dat de bandteller, dus een hersteld bandje sprong naar een plek die in dat nummer niet bestaat. Daarvoor is trackTijd() afgesplitst.

MOD_V 53, style.css v107, audio-player v35.

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/assets/js/mod/tape.js

    r16fd4fe re679bce  
    77 * een bandgevoel na te doen zou twee dingen tegelijk laten klinken en die hele
    88 * sessie weggooien. Dus: de afspeelknop draagt dezelfde data-attributen als een
    9  * albumhoes en wordt door die speler opgepakt, en spoelen is next()/prev().
     9 * albumhoes en wordt door die speler opgepakt.
    1010 *
    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.
     11 * SPOELEN GAAT IN SECONDEN, over de nummergrenzen heen. Dat kan omdat die
     12 * speler een wachtrij in EEN doorlopende MediaSource-tijdlijn giet: "een
     13 * trackwissel is een positie, geen omschakeling". Een bandje is dus letterlijk
     14 * die tijdlijn. Vandaar seekBy() en niet next()/prev() -- springen per nummer
     15 * is een playlistgebaar, en dan is dit een lijst met een cassetteplaatje.
    1416 *
    1517 * WAAROM ER GEPOLLD WORDT. audio-player.js zendt geen gebeurtenissen uit -- het
     
    7678}
    7779
     80/**
     81 * SPOELEN IS VASTHOUDEN, en dat is het verschil met een afspeellijst.
     82 *
     83 * Eerst deed vooruit en achteruit hier next()/prev(): een nummer verder. Dat is
     84 * een playlistgebaar. Op een cassette bestaat "volgend nummer" niet -- je houdt
     85 * de knop ingedrukt, de band loopt door, en je laat los waar je bent. Robin
     86 * wees daarop (21-8) en hij heeft gelijk: met knoppen die per nummer springen
     87 * is het een lijst met een cassetteplaatje erboven.
     88 *
     89 * Het versnelt terwijl je hem vasthoudt, zoals een echt bandje op gang komt.
     90 * Een korte TIK spoelt een klein stukje -- dat is er voor toetsenbord en
     91 * schermlezer, want vasthouden is met een spatiebalk geen gebaar.
     92 */
     93const START_SNELHEID = 4;    // maal de normale snelheid bij het indrukken
     94const MAX_SNELHEID = 16;
     95const VERSNELLING = 1.35;    // per stap
     96const STAP_MS = 120;
     97const TIK_STAP_S = 5;        // een losse klik
     98
     99let winder = null;
     100
     101function stopWinden(tape) {
     102  if (winder) { clearInterval(winder.timer); winder = null; }
     103  if (tape) tape.classList.remove('is-winding', 'is-winding-back');
     104}
     105
     106function startWinden(tape, richting) {
     107  const p = speler();
     108  if (!p || huidigeIndex(tape) < 0) return;
     109  stopWinden(tape);
     110  let snelheid = START_SNELHEID;
     111  tape.classList.add('is-winding');
     112  if (richting < 0) tape.classList.add('is-winding-back');
     113  const timer = setInterval(() => {
     114    snelheid = Math.min(MAX_SNELHEID, snelheid * VERSNELLING);
     115    p.seekBy(richting * snelheid * (STAP_MS / 1000));
     116    tekenBand(tape);
     117  }, STAP_MS);
     118  winder = { timer, tape };
     119}
     120
     121function opPointerDown(e) {
     122  const knop = e.target.closest && e.target.closest('[data-tape-go]');
     123  if (!knop) return;
     124  const tape = knop.closest('.post-tape');
     125  if (!tape) return;
     126  startWinden(tape, knop.dataset.tapeGo === 'fwd' ? 1 : -1);
     127}
     128
     129function opPointerUp() {
     130  if (winder) stopWinden(winder.tape);
     131}
     132
    78133function opKlik(e) {
    79134  const tape = e.target.closest && e.target.closest('.post-tape');
     
    83138  const spoel = e.target.closest('[data-tape-go]');
    84139  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);
     140    // Een tik spoelt een stukje. Vasthouden gaat via de pointer-afhandelaars
     141    // hierboven; die hebben dan al gespoeld en deze klik doet er nog een klein
     142    // beetje bovenop, wat niet stoort.
     143    if (!p || huidigeIndex(tape) < 0) return;
     144    p.seekBy(spoel.dataset.tapeGo === 'fwd' ? TIK_STAP_S : -TIK_STAP_S);
     145    setTimeout(() => tekenBand(tape), 60);
    92146    return;
    93147  }
     
    96150  if (play && p) {
    97151    // 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
     152    // afhandelaar van audio-player.js zou hem anders opnieuw vanaf het begin
    99153    // starten, want die kent alleen "speel deze wachtrij".
    100154    const i = huidigeIndex(tape);
     
    106160    }
    107161    // Staat het bandje stil, dan laten we de klik doorlopen: de speler pakt de
    108     // data-attributen op en zet de wachtrij op.
     162    // data-attributen op en zet de band op.
    109163  }
    110164}
     
    117171  // die in de leesweergave een keer de hele site heeft vastgezet.
    118172  if (tikker) { clearInterval(tikker); tikker = null; }
    119   if (gebonden) { gebonden.removeEventListener('click', opKlik, true); gebonden = null; }
     173  stopWinden(null);
     174  if (gebonden) {
     175    gebonden.removeEventListener('click', opKlik, true);
     176    gebonden.removeEventListener('pointerdown', opPointerDown, true);
     177    gebonden.removeEventListener('pointerup', opPointerUp, true);
     178    gebonden.removeEventListener('pointercancel', opPointerUp, true);
     179    gebonden = null;
     180  }
    120181  if (!tapes.length) return;
    121182
     
    125186  gebonden = document;
    126187  gebonden.addEventListener('click', opKlik, true);
     188  gebonden.addEventListener('pointerdown', opPointerDown, true);
     189  // Loslaten telt ook als je BUITEN de knop loslaat, anders blijft de band
     190  // doorspoelen als je met je vinger wegglijdt.
     191  gebonden.addEventListener('pointerup', opPointerUp, true);
     192  gebonden.addEventListener('pointercancel', opPointerUp, true);
    127193
    128194  tikker = setInterval(tekenAlles, TIK_MS);
Note: See TracChangeset for help on using the changeset viewer.