Die H5P-Editor-Spezifikation umfasst Audiofelder und Videofelder, die du in deiner semantics.json-Datei verwenden kannst, um ein Eingabefeld für eine Audiodatei oder eine Videodatei anzufordern. Diese Anforderung führt dazu, dass ein Audio- oder Video-Widget im Editor-Formular gerendert wird. Dies wird von einem einzigen Modul übernommen. Ich nenne es das „A/V-Widget“. Das Audio-Widget sieht beispielsweise standardmäßig wie folgt aus:

Have you noticed that sometimes it’s different? Take a look at editor form of Dictation. The audio widget looks like this:
Ist dir schon einmal aufgefallen, dass das manchmal anders aussieht? Wirf mal einen Blick auf das Editorforumular von Dictation. Das Audio-Widget sieht dort so aus:

Es platziert nicht nur die Schaltfläche zum Hochladen und den Link in separaten Tabs, sondern verfügt auch über ein Tab „Audio-Recorder“, mit dem du Tondateien im Browser aufnehmen kannst. Der Audio-Recorder ist eine Erweiterung. Solch eine Erweiterung für eigene Zwecke zu erstellen ist keine Zauberei. Wie das geht, ist aber nirgendwo dokumentiert. Lass uns also alles einmal aufdröseln.
Mit dem A/V-Widget im H5P-Editor-Kern kannst Erweiterungen hinzufügen, mit denen du Medien anders zu H5P-Inhalten hinzufügen. Was könnte das bedeuten? Offenbar kannst du Audiodaten direkt aufnehmen. Du kannst aber auch eine Erweiterung erstellen, die vordefinierte Audio- oder Videodateien bereitstellt. Du kannst den Benutzerinnen und Benutzern auch erlauben, vorhandene Medien aus einem lokalen Medienspeicher oder irgendwo aus dem Web oder der Cloud auszuwählen. Oder…
Die API ist ziemlich einfach, aber wie bereits erwähnt, ist sie nicht dokumentiert. Du kannst deine Erweiterung so komplex gestalten, wie Sie möchten, aber sie muss mindestens diese Anforderungen erfüllen:
- eine library.json-Datei (wie immer), um H5P mitzuteilen, was für JavaScript, CSS und potentiell andere Abhängigkeiten zu laden sind,
- eine JavaScript-Datei mit einer Klasse, die deine Erweiterung darstellt und
- eine Sprachdatei
language/en.json, die den Titel deiner Erweiterung enthält.
Deine Erweiterung bauen
library.json
Deine (rudimentäre) library.json-Datei könnte wie folgt aussehen:
{
"title": "My AV widget extension",
"machineName": "H5PEditor.MyAVWidgetExtension",
"majorVersion": 1,
"minorVersion": 0,
"patchVersion": 0,
"runnable": 0,
"preloadedJs": [
{
"path": "h5p-editor-my-av-widget-extension.js"
}
],
"preloadedCss": [
{
"path": "h5p-editor-my-av-widget-extension.css"
}
]
}
Du hast deine JavaScript-/CSS-Dateien wahrscheinlich in einem Ordner namens dist oder ähnlich organisiert. Möglicherweise brauchst du auch preloadedDependencies und solltest einen Eintrag für description hinzufügen usw. – aber das würde ausreichen.
Die Sprachdatei
Wie bei jedem H5P-Editor-Widget musst du die Datei language/en.json hinzufügen. Diese Datei muss mindestens die Eigenschaft title mit dem Namen deines Widgets als Wert enthalten. Dieser wird als Tabbeschriftung im Editor angezeigt. Du kannst natürlich wie gewohnt Übersetzungsdateien für andere Sprachen hinzufügen, aber die englische Version ist als Sicherungsnetz erforderlich.
{
"libraryStrings": {
"title": "My AV widget extension"
}
}
Deine JavaScript-Datei
In deiner JavaScript-Datei musst du einigen Bedingungen genügen.
H5P.EventDispatcher beerben
H5P verfügt über ein eigenes kleines Ereignissystem, das wir verwenden müssen. Insbesondere müssen wir die trigger-Methode benutzen, um ein Ereignis an alle zu senden, die unserer Widget-Instanz zuhören. Hier wird das zumindest der H5P-Kern tun.
Deine Klasse MyAVWidgetExtension muss also von H5P.EventDispatcher erben.
Erforderliche Methoden implementieren
Der H5P-Kern geht davon aus, dass fünf exponierte Methoden in deiner Klasse implementiert werden.
appendTo(container:HTMLElement):void
Der H5P-Editor-Kern ruft diese Methode auf, damit dein Widget im Editor gerendert werden kann. Das Container-HTML-Element wird an dich übergeben, und du kannst deine DOM-Elemente daran anhängen.
Beachten, dass der Container im Gegensatz zur sonst üblichen Vorgehensweise des H5P-Kerns bei Inhaltstypen tatsächlich ein Standard-HTML-Element und kein jQuery-Element ist. Gut!
/**
* Append your widget's DOM to the container element.
* @param {HTMLElement} container The container to append to.
* @returns {void}
*/
appendTo(container) {
// Append your DOM to the container element here
}
hasMedia():boolean
Der H5P-Editor-Kern ruft diese Methode auf, wenn der Benutzer oder die Benutzerin zu deinem Tab wechselt, um festzustellen, ob dein Widget bereits eine Mediendatei bereitstellt, ohne dass der Benutzer oder die Benutzerin etwas auswählen muss. Gib einfach entsprechend true oder false zurück.
/**
* Determine if media file was selected/provided in your widget.
* @returns {boolean} True if media file is present, false otherwise.
*/
hasMedia() {
// Change as appropriate
return false;
}
getMedia():object
Der H5P-Editor-Kern ruft diese Funktion auf, wenn der Benutzer oder die Benutzerun auf den Button „Einfügen“ klickt. Die Funktion erwartet ein Objekt mit zwei Einträgen als Argument:
- Der erste Eintrag mit dem Schlüssel
datamuss ein Blob mit dem richtigen MIME-Typ für das Medium sein, z. B.audio/mpeg. - Der zweite Eintrag muss den Schlüssel
nameund einen String für den Namen der Zieldatei enthalten. Der String muss mit einer Dateierweiterung wie.mp3enden.
Beachte, dass H5P standardmößig die Dateiendungen .mp3, .m4a, .wav und .ogg für Tondateien akzeptiert und .mp4, .webm und .ogv für Videodateien. Die Liste der erlaubten Endungen kann allerdings in der H5P-Integration angepasst werden, so dass ggf. weitere möglich sein können oder nicht alle. Client-seitig lässt sich das leider nicht abfragen.
Beachte, dass der MIME-Typ des Blobs mit der Dateiendung übereinstimmen muss, da der H5P-Editor-Kern die Datei sonst ablehnt.
/**
* @typedef {object} MediaFile
* @property {Blob} data - Blob with proper MIME type (e.g., audio/mpeg).
* @property {string} Filename usually ending with .mp3, .m4a, .wav, .ogg, mp4, webm, or ogv.
*/
/*
* Get the media file provided in your widget.
* @returns {MediaFile} Media file data.
*/
getMedia() {
/*
* Just an example! Change as appropriate.
* Note that by default, H5P accepts mp3, m4a, wav, and ogg for audio, and mp4, webm or ogv for video,
* but the list of allowed extensions could be changed in the H5P integration.
* Also note that the MIME type must match the file extension, of course.
*/
return {
data: new Blob([], { type: 'audio/mpeg' }),
name: 'audio.mp3',
};
}
pause():void
Der H5P-Editor-Kern ruft diese Methode auf, wenn der Benutzer oder die Benutzerin zwischen den Tabs wechselt. Der Editor erwartet, dass deine Erweiterung ihre aktuelle Arbeit unterbricht. In der vorhandenen Audio-Recorder-Erweiterung wird beispielsweise die Aufnahme gestoppt.
Beachte, dass dies für deine Erweiterung möglicherweise nicht relevant ist, der H5P-Editor-Kern jedoch dennoch davon ausgeht, dass die pause-Funktion vorhanden ist.
/**
* Pause whatever the widget is doing. Note that it's mandatory to have this method.
* @returns {void}
*/
pause() {
// Implement as required.
}
reset():void
Der H5P-Editor-Kern ruft diese Methode auf, wenn das Audio- oder Videodialogfeld geschlossen wird. Der Editor erwartet, dass die Erweiterung in ihren Ausgangszustand zurückkehrt, damit der Benutzer oder die Benutzerin beim erneuten Öffnen deines Tabs frisch starten kann.
Beachte, dass dies für deine Erweiterung möglicherweise nicht relevant ist, der H5P-Editor-Kern jedoch dennoch davon ausgeht, dass die reset-Funktion vorhanden ist.
/**
* Reset the widget to its initial state. Note that it's mandatory to have this method.
* @returns {void}
*/
reset() {
// Implement as required.
}
Erwartetes Verhalten implementieren
Der H5P-Editor-Kern erwartet, dass du ein hasMedia-Ereignis über den H5P.EventDispatcher auslöst, wenn sich die Verfügbarkeit einer Mediendatei in deiner Erweiterung ändert. Die Ereignisdaten sollten auf true gesetzt werden, wenn ein Medienobjekt ausgewählt oder festgelegt wurde, und auf false, wenn ein Medienobjekt abgewählt oder keins festgelegt ist. Im ersten Fall aktiviert das AV-Widget den Button „Einfügen“ für die Benutzenden. Im zweiten Fall deaktiviert das AV-Widget den Button.
this.trigger('hasMedia', true); // Signal that a media file is selected/provided
this.trigger('hasMedia', false); // Signal that no media file is selected/provided
Add your extension to the AV widget
Abschließend musst du dem AV-Widget des H5P-Editor-Kerns mitteilen, dass deine Erweiterung verfügbar ist. Angenommen, deine Klasse heißt MyAVWidgetExtension und dies ist auch der Name der Erweiterung, der in semantics.json angegeben werden soll. Dann musst die diese nur zum globalen H5PEditor-Objekt und dessen AV-Eintrag hinzufügen:
H5PEditor.MyAVWidgetExtension = MyAVWidgetExtension;
H5PEditor.AV.MyAVWidgetExtension = MyAVWidgetExtension;
Dein CSS
Mach in deiner CSS-Datei was auch immer du brauchst 🙂
Deine Erweiterung nutzen …
… direkt in einem Inhaltstyp
Füge deine Erweiterung einfach als Anhängigkeit in der library.json des Inhaltstyps ein:
{
"editorDependencies": [
{
"machineName": "H5PEditor.MyAVWidgetExtension",
"majorVersion": 1,
"minorVersion": 0
}
]
}
Dann füge dem Audio- oder Videofeld einen widgetExtensions-Eintrag hinzu, falls er nicht schon besteht und füge den Namen deiner Erweiterung in die Liste ein:
{
"name": "myAudioField",
"type": "audio",
"label": "My audio field",
"widgetExtensions": [
"MyAVWidgetExtension"
]
}
Der H5P-Editor-Kern lädt dann alle vom Editor benötigten Bibliotheken, einschließlich deiner, und instanziiert sie als Erweiterungen für die Medienfelder.
…indirekt in einem Inhaltstyp
Es kann Fälle geben, in denen du den Inhaltstyp nicht ändern kannst, deine Erweiterung aber dennoch für ein Medienfeld verfügbar sein soll. Vielleicht möchtest du, dass sie für jedes Audiofeld verfügbar ist. Das könnte beispielsweise nützlich sein, wenn dein System Zugriff auf eine Audiobibliothek bietet, sodass du von dort Dateien abrufen kannst, anstatt sie hochzuladen.
Das geht auch. Die genaue Implementierung hängt ein wenig vom jeweiligen System ab, weshalb wir hier nicht auf Details eingehen. Du kannst jedenfalls H5Ps Hooks dafür benutzen:
- Benutze den alter_scripts-Hook, um die JavaScript-Dateien deiner Erweiterung zu laden.
- Benutze den alter_styles-Hook, um die CSS-Dateien deiner Erweiterung zu laden.
- Benutze den alter_semantics-Hook, um den
widgetExtensions-Eintrag hinzuzufügen oder ihn zu ergänzen und so deine Erweiterung bereitzustellen.
