Videocentral voor developers

Videocentral koppelen: één contract, en wat je CMS moet leveren

De speler weet niets van je CMS. Hij leest één blokje data uit de pagina en doet de rest zelf. Dit artikel beschrijft dat contract, hoe een opname binnenkomt, en waar meertaligheid in Drupal en TYPO3 stukloopt als je er niet op let.

Wat moet mijn CMS precies produceren?

Dit, en niets meer:

<div class="vc-reader-mount"
     data-vc='{"webm":"…","audio":"…","words":[…],"placement":{…}}'
     data-vc-versies='{"original":{…,"label":"Origineel","html":"…"},
                       "simple":{…,"label":"Eenvoudig","html":"…"}}'
     data-vc-versie="original">
  <div class="vc-reader__content"><!-- de tekst zelf --></div>
</div>

data-vc is de opname die nu speelt: de video met transparante achtergrond, de losse audio voor wie geen beeld wil, en de woordtijden waarmee de speler meeleest. data-vc-versies is optioneel en bevat de andere leesniveaus — per niveau de eigen opname én de eigen tekst.

De speler zelf is één gebundeld bestand met JavaScript en CSS. Bij ons draait diezelfde bundel inmiddels op vier manieren: een headless React-site, een Gutenberg-blok, een shortcode en een Elementor-widget. Geen enkele regel spelercode verschilt per variant. Wat verschilt is alleen welk stuk code dat blokje data neerzet.

Waarom staat de niveau-schakelaar client-side?

Omdat de voor de hand liggende implementatie kapotgaat achter een cache. Cookie zetten, pagina herladen, server kiest de juiste versie: dat werkt precies tot er een paginacache voor staat. Zonder Vary serveert die het leesniveau van de eerste bezoeker aan iedereen daarna. Op een gemeentesite met een reverse proxy ervoor is dat geen theoretisch risico.

Daarom zit elk niveau met tekst en al in de pagina, en wisselt de speler ze in de browser. Eén cachebare pagina, geen Vary, geen paginasprong.

Hoe komt een opname binnen?

De redacteur drukt op renderen, het CMS stuurt de tekst mee en krijgt een job-id terug. Het inspreken duurt minuten, niet seconden, dus het antwoord komt later via een webhook.

Drie dingen die je in die keten niet moet vergeten, en die ons wél zijn overkomen:

  • Idempotentie. Een webhook wordt opnieuw gestuurd als hij niet snel genoeg wordt bevestigd. Zonder claim per levering verwerk je dezelfde video drie keer.
  • Verwerken buiten het verzoek. Downloaden en opslaan duurt te lang voor de webhook zelf. Bevestig direct, verwerk daarna — en laat een periodieke taak opruimen wat halverwege is blijven hangen.
  • Opslaan per leesniveau. Bewaar je alleen "de laatste opname", dan serveer je vroeg of laat de eenvoudige opname onder de originele tekst.

Hoe weet de speler dat de opname nog klopt?

Bij het renderen wordt een vingerafdruk van de tekst opgeslagen — een goedkope hash over de genormaliseerde tekst. Wijkt de tekst op de pagina daarvan af, dan komt de speler niet in beeld en staat de pagina in het beheer als verouderd.

Dat is bewust een harde grens en geen waarschuwing. Een voorleesfunctie die iets anders zegt dan er staat is schadelijker dan geen voorleesfunctie, en het is precies het soort verschil dat niemand opmerkt zolang het alleen in een logregel staat.

Wat betekent meertaligheid voor de opslag?

Dit is waar een koppeling het vaakst misgaat, en het is per systeem net anders.

Drupal

Meertaligheid zit in core, dus dit is vooral goed configureren. Het veld met de opname moet vertaalbaar staan, anders delen alle talen er één. Hang de render aan de cache-tag van de node, zodat een nieuwe opname de paginacache invalideert. En omdat de niveau-schakelaar client-side werkt, hoef je verder niets aan caching te slopen.

TYPO3

Zet allowLanguageSynchronization uit voor de opnamevelden. Staat het aan, dan erft de Engelse vertaling netjes de Nederlandse opname en spreekt de avatar vrolijk Nederlands onder een Engelse tekst. Verder is het TCA plus een content-element in Fluid.

WordPress

De taal komt uit een filter met het post-ID erbij, zodat een vertaalplugin kan antwoorden welke taal die specifieke pagina heeft:

add_filter('wss_vc_taal', fn($taal, $post) => pll_get_post_language($post, 'slug') ?: $taal, 10, 2);

Zonder vertaalplugin blijft de sitetaal de standaard.

En de sleutel waaronder je opslaat

Sla op onder de combinatie van content-id én taal, niet onder het pad. In WordPress met een vertaalplugin krijgen vertalingen meestal een eigen slug, dus een pad-sleutel lijkt te werken. In Drupal en TYPO3 is een vertaling hetzelfde item met een taalcode — vaak op hetzelfde pad. Dan schrijft de ene taal over de andere heen, en dat merk je pas als iemand belt.

Waar we aan werken: koppelen zonder plugin

Voor systemen waar geen module voor bestaat bouwen we een tweede route: je meldt je site aan, geeft een selector op die zegt waar de tekst staat, en plaatst één scriptregel in je template. Videocentral leest dan je sitemap, haalt de pagina's zelf op en zet ze in een redactielijst. De speler in de pagina vraagt alleen nog op of er een opname is voor deze URL, deze taal en deze tekst.

Die route is in ontwikkeling, dus dit is een aankondiging en geen handleiding. De grens kennen we nu al: pagina's achter een login, gepersonaliseerde tekst en content die pas na JavaScript verschijnt vallen erbuiten. Daar blijft een echte koppeling voor nodig.

Veelgestelde vragen

Wat developers ons vragen

Welke bestandsformaten levert een render op?

Een videobestand met transparante achtergrond voor de avatar, een los audiobestand voor wie alleen wil luisteren, en de woordtijden waarmee de tekst meeloopt. De speler kiest zelf wat hij gebruikt, afhankelijk van wat de bezoeker heeft ingesteld en wat het apparaat aankan.

Werkt dit achter een paginacache of CDN?

Ja. Alle leesniveaus zitten in de pagina en het wisselen gebeurt in de browser, dus er is geen Vary-header nodig en elke bezoeker kan dezelfde gecachte pagina krijgen.

Wat gebeurt er zonder JavaScript?

Dan verschijnt de speler niet. De tekst staat gewoon in de pagina en blijft volledig leesbaar — de voorleesfunctie is een toevoeging, geen voorwaarde om de inhoud te lezen.

Kan ik de speler zelf stylen?

De plaatsing, grootte en uitsnede van de avatar zijn instelbaar vanuit het CMS. De bediening zelf houden we bewust strak: het is een toegankelijkheidsonderdeel, en dan wil je dat het overal hetzelfde werkt en aan de contrast- en focus-eisen blijft voldoen.

Een koppeling bespreken voor je eigen stack?