JS Applicato

tour sul codice reale di dhtml5_player (arkenidar/dirlist-suite) — lo stesso player che hai visto su github.arkenidar.com

1. Punto d'ingresso: DOMContentLoaded

web/dhtml/dhtml5_player/player.js
document.addEventListener("DOMContentLoaded", player_initialize)

Niente bundler, niente build step: un solo <script src="player.js"> nella pagina, che aspetta che il DOM sia pronto e poi parte. È la stessa filosofia "esplicita, niente magia" già vista nel PHP — qui applicata al frontend.

2. Selezionare e filtrare elementi

player.js — player_initialize()
var links = document.querySelectorAll("a[href]")
const video_extensions = ["mp4", "webm"]
const audio_extensions = ["mp3", "3gp", "ogg", "opus", "m4a", "flac", "wav", "mid", "xm"]
const playable_extensions = audio_extensions.concat(video_extensions)
const linksFilterCriterion = link =>
    playable_extensions.some(ext => link.href.endsWith(`.${ext}`))
links = Array.from(links).filter(linksFilterCriterion)

Pattern chiave: invece di costruire la playlist a mano, il player scansiona la pagina e riconosce quali link <a> puntano a file riproducibili, in base all'estensione. La pagina HTML resta semplice (solo link), tutta l'intelligenza è nello script.

3. Annotare gli elementi con dataset

player.js
if (audio_extensions.some(ext => link.href.endsWith(`.${ext}`))) {
    link.dataset.mediaType = "audio"
}
if (video_extensions.some(ext => link.href.endsWith(`.${ext}`))) {
    link.dataset.mediaType = "video"
}

element.dataset è l'API per leggere/scrivere attributi data-* sull'HTML. Qui serve a "etichettare" ogni link col tipo di media, così il resto del codice può decidere quale player (audio/video) mostrare senza ricontrollare l'estensione ogni volta.

4. Creare elementi dinamicamente

player.js
const download_link = document.createElement("a")
download_link.download = ""
download_link.classList.add("download_link")
download_link.innerText = "⬇ copy"
download_link.href = link.href
link.after(download_link)

createElement + after() costruiscono e inseriscono nodi DOM al volo — qui un link di download accanto a ogni traccia, generato per ciascun elemento della playlist senza toccare l'HTML originale.

5. Stato condiviso tramite closure di modulo

player.js
link.next_link = links[link_index + 1]
link.onclick = player_link_click

function player_link_click(event) {
    const current_link_or_null = typeof current_link != "undefined" ? current_link : null;
    const same_link_clicked = this == current_link_or_null;
    const something_playing = !player_audio.paused || !player_video.paused
    if (same_link_clicked && something_playing) {
        player_audio.pause(); player_video.pause();
    } else link_play(event.currentTarget);
    return !player_intercept.checked;
}

Nota di stile: current_link è una variabile globale implicita (mai dichiarata con let/const in questo scope), condivisa tra tutte le funzioni del file. È una scelta pragmatica da script "single file, single purpose" — diversa da come strutturerebbe un modulo ES6 con stato incapsulato, ma coerente con la semplicità voluta qui.

6. La funzione centrale: link_play

player.js
if (typeof current_link != "undefined")
    current_link.classList.remove("current_link")
current_link = link
current_link.classList.add("current_link")
playing.innerText = link.innerText
document.title = link.innerText

classList.add/remove per evidenziare il link attivo, innerText per aggiornare sia un'area di stato in pagina (playing) sia il titolo del tab del browser — un piccolo dettaglio UX che costa tre righe.

7. Switch implicito su tipo di media

player.js — link_play()
if (media_type == "audio") {
    player_video.pause()
    player_video.style.display = "none"
    player_audio.style.display = "block"
    player_active = player_audio
} else if (media_type == "video") {
    player_audio.pause()
    player_audio.style.display = "none"
    player_video.style.display = "block"
    player_active = player_video
}

Mostra/nasconde i due elementi <audio>/<video> già presenti nell'HTML invece di crearli e distruggerli — più semplice e meno soggetto a race condition rispetto a costruire l'elemento media al volo.

8. Stato nell'URL: location.hash come canale dati

player.js
function json_link() {
    var json_data = { keyword: current_link.href, after: after.value }
    var URI = '#' + 'JSON:' + JSON.stringify(json_data)
    link_to_current_playable.href = encodeURI(URI)
}

Idea elegante: lo stato corrente del player (cosa sta suonando, cosa fare dopo) viene serializzato in JSON e infilato nell'hash dell'URL. Il link "Link to the current playable media content" diventa quindi condivisibile — chi lo apre riparte dallo stesso punto, senza nessun backend coinvolto.

9. Leggere quello stato all'avvio

player.js — player_initialize()
if (location.hash.startsWith("#")) {
    var hash = decodeURIComponent(location.hash.slice(1))
    if (hash.startsWith("JSON:")) {
        hash = hash.slice(5)
        try {
            var hash_json = JSON.parse(hash)
            if (typeof hash_json.keyword != "undefined") {
                first_link_override = links.filter(l => l.href.includes(hash_json.keyword))[0]
            }
        } catch (error) { console.error(hash, error) }
    }
}

Simmetrico alla slide precedente: al caricamento, se l'URL contiene quello stato JSON, lo riapplica trovando il link corrispondente. Il try/catch attorno al JSON.parse protegge da URL malformati senza far crashare il player.

10. Persistenza leggera con localStorage

player.js
function download_links_display(shown) {
    localStorage.download_links_display = shown
    const verb = shown ? "remove" : "add"
    document.body.classList[verb]("download_links_hide")
}

localStorage ricorda una preferenza (mostrare o no i link di download) tra una visita e l'altra — senza account, senza server, senza cookie. classList[verb] è un piccolo trucco: sceglie dinamicamente il metodo (add o remove) da chiamare in base a una stringa.

11. Logica dichiarativa con un oggetto come mappa

player.js
function on_media_end() {
    link_play({
        repeat: current_link,
        next: current_link.next_link,
        playlist: current_link.next_link || first_link_in_playlist,
    }[after.value])
}

Invece di uno switch, un oggetto letterale usato come tabella di lookup: la chiave è il valore della <select> "After this playable media do...", il valore è il link da riprodurre dopo. Più compatto di un if/else if a catena, e facile da estendere aggiungendo una chiave.

12. Il flusso completo: click su un link

utente clicca un link <a>
  → player_link_click(event)
    → stesso link e qualcosa in riproduzione? → pausa
    → altrimenti → link_play(link)
        → aggiorna classList, "playing", document.title
        → mostra audio o video player in base a dataset.mediaType
        → imposta .src se necessario, poi .play()
        → json_link() aggiorna l'hash dell'URL
  → al termine del media → on_media_end()
        → legge "after" (next/repeat/stop/playlist) → richiama link_play()

Tutto il comportamento del player nasce dalla combinazione di poche primitive DOM (querySelectorAll, dataset, classList, createElement, addEventListener implicito via onclick) più due piccoli "database" client-side: location.hash per lo stato condivisibile, localStorage per le preferenze.

13. Per chiudere

Questo è "DHTML5" applicato nel senso che intendi tu: HTML minimale (solo una lista di link), tutto il comportamento aggiunto via JS vanilla che legge e modifica il DOM esistente — nessun framework, nessuna build, un file che si legge dall'inizio alla fine.

Il repository: github.com/arkenidar/dirlist-suite (percorso: web/dhtml/dhtml5_player/)