Flash Video Fall­back für HTML5 Video Lösun­gen

Flash Video: mit ffmpeg konvertieren, über das video-Tag einbinden.



Einleitung

Im Internet und in Büchern kursieren mehrere angebliche und auch funktionsfähige Lösungen um HTML Dokumente, die von den neuen Funktionen des HTML5 Videos Gebrauch machen auch für ältere Browser, die aber dann zumeist mit einem Flash-Plugin ausgestattet sind, verfügbar zu machen.

Dieser Artikel soll zeigen wie man das HTML5 video-Tag auch auf nicht HTML5-fähigen Browsern selbst mittels DOM zu Leben erwecken kann. Daneben gibt es noch andere Alternativlösungen mit Java oder dem mittlerweile kostenpflichtigen Flowplayer. Das HTML5-Video Tag mittels DOM erst auszulesen und dann einfach, sofern dieses nicht unterstützt wird, ein embed-Objekt für den Flashplayer einzufügen ist ein verlockend einfacher wie effizienter Lösungsansatz. Einziger Nachteil: neben .webm und .mp4 müssen unsere Videos jetzt in einem dritten Format nämlich als .swf (Shockwave Flash) vorliegen (Mit der Konvertierung beschäftigt sich das folgende Kapitel). Die mwEmbed Bibliothek hingegen erlaubt mit Java das direkte Wiedergeben von .ogg, einem HTML5 Format.

Flash Videos mit ffmpeg konvertieren

Wollen wir mittels ffmpeg in das Shockwave Flash Video Format konvertieren, so müssen wir einfach statt einer Audio-Bitrate einen anderen Parameter nämlich die Samplerate angeben, da (zumindest unsere Version von) Shockwave Flash Video nur bestimmte Raten nämlich 44100, 22050 und 11025 unterstützt. Die sog. Sampelrate gibt die Abtastrate in Hz pro Sekunde an. Volle CD-Qualität bedient sich einer Samplingrate von 44 kHz. Werfen wir erst einen Blick auf die Eigenschaften des Eingabedatenstroms und wählen wir dann die nächst bessere Samplerate.

> ffmpeg -i Reiher.MOV -vb 536k -ar 11025 -r 25 Reiher-848x480-600kb.swf Guessed Channel Layout for Input Stream #0.1 : mono Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'Reiher.MOV': Metadata: creation_time : 2008-07-13 16:50:08 Duration: 00:00:16.50, start: 0.000000, bitrate: 12786 kb/s Stream #0:0(eng): Video: mjpeg (jpeg / 0x6765706A), yuvj420p, 848x480, 12720 kb/s, 30 fps, 30 tbr, 30 tbn, 30 tbc Metadata: creation_time : 2008-07-13 16:50:08 Stream #0:1(eng): Audio: pcm_u8 (raw / 0x20776172), 8000 Hz, mono, u8, 64 kb/s Metadata: creation_time : 2008-07-13 16:50:08 [buffer @ 0xe53140] w:848 h:480 pixfmt:yuvj420p tb:1/30 sar:0/1 sws_param:flags=2 [buffersink @ 0xe32fc0] No opaque field provided [format @ 0xe51140] auto-inserting filter 'auto-inserted scaler 0' between the filter 'src' and the filter 'format' [scale @ 0xe33e40] w:848 h:480 fmt:yuvj420p sar:0/1 -> w:848 h:480 fmt:yuv420p sar:0/1 flags:0x4 [aformat @ 0xea02c0] auto-inserting filter 'auto-inserted resampler 0' between the filter 'src' and the filter 'aformat' [aresample @ 0xea13e0] chl:mono fmt:u8 r:8000Hz -> chl:mono fmt:s16 r:11025Hz Output #0, swf, to 'Reiher-848x480-600kb.swf': Metadata: creation_time : 2008-07-13 16:50:08 encoder : Lavf54.6.100 Stream #0:0(eng): Video: flv1, yuv420p, 848x480, q=2-31, 536 kb/s, 90k tbn, 25 tbc Metadata: creation_time : 2008-07-13 16:50:08 Stream #0:1(eng): Audio: mp3, 11025 Hz, mono, s16 Metadata: creation_time : 2008-07-13 16:50:08 Stream mapping: Stream #0:0 -> #0:0 (mjpeg -> flv) Stream #0:1 -> #0:1 (pcm_u8 -> libmp3lame) Press [q] to stop, [?] for help frame= 415 fps=120 q=31.0 Lsize= 3024kB time=00:00:16.51 bitrate=1500.3kbits/s dup=0 drop=80 video:2981kB audio:32kB global headers:0kB muxing overhead 0.360153%

Videos einbinden

Das einzige was es braucht ist etwas Javascript Code einzubinden. An unserem HTML5-Dokument brauchen wir fast nichts zu ändern, außer daß wir eine zusätzliche Quelle für ein Shockwave Flash Video einfügen:

<div><video controls preload=metadata width=848 height=480 poster='data/frame049.jpg'> <source src='data/Reiher-848x480-600kb.mp4' type='video/mp4'> <source src='data/Reiher-848x480-600kb.webm' type='video/webm'> <source src='data/Reiher-848x480-600kb.swf' type='application/x-shockwave-flash'> <source src='data/Reiher-848x480-600kb.ogg' type='video/ogg'> <img src='...'> - Kann das HTML5-Video leider nicht abspielen; bitte Javascript einschalten einen anderen Browser verwenden. </video></div>

elegant, oder?

Bitte beachten Sie daß das video-Tag jetzt in einem Umschließenden div-Tag angesiedelt ist. Das ist deshalb notwendig, weil die Dokumentstruktur für das video-Tag in nicht HTML5-fähigen Browsern nicht definiert ist. Nur so erhalten wir eine allgemein funktionsfähige Lösung.

Beachten Sie auch die Angabe von width und height, da diese zumindest für die Größenbestimmung des einzubettenden Flash-Videos unbedingt erforderlich ist.

Auch wenn die im folgenden vorgestellte Lösung eher beispielhaften Charakter hat, weil wir keine fortgeschrittenen Bedienelemente wie zum Vor- oder Zurückspulen für das Flashvideo anzeigen werden, sondern die Lösung bewußt einfach halten, so kann man seine Videos trotzdem einfach durch Kopieren und Einfügen des im übernächsten Kapitel angezeigten Javascript Codes Videos für Flash auf seiner Homepage verfügbar machen. Die Steuerung des Flash-Videos erfolgt dabei noch über das Kontextmenü der rechten Maustaste.

Die vorgestelle Lösung funktioniert soweit getestet sowohl am IE8, am IE6 als auch auf uralten Firefox Versionen wie 3.04.

Der Lösungsansatz

Bevor es im nächsten Kapitel den Quellcode für einfaches Copy-and-Paste gibt, soll hier der allgemeine Lösungsansatz vorgestellt und diskutiert werden.

Beginnen tut alles mit dem Aufspüren der video-Tags, wofür selbst in nicht HTML5-fähigen Browsern ein einfaches document.getElementsByTagName("VIDEO") reicht. Danach wird es schon trickreicher. Da wir für unsere source-Tags keine schließenden Tags angegeben haben, ist deren TagStruktur auch nicht definiert. Während Firefox 3.04 sogar jedes folgende source-Element als Kindelement des vorhergehenden einschachtelt, listet der Internet Explorer einfach alle Tags ohne Strukturierung hintereinander auf. Nicht einmal das schließende video-Tag wird in irgendeiner Form im DOM abgebildet. Deshalb wissen wir auch nicht wo das video-Tag aufhört und wo nachfolgender HTML-Code beginnt. Hier bedienen wir uns eines Tricks: Wir legen das video-Tag einfach in ein umschließendes div-Tag, das in allen Browsern unterstützt wird. Hat der Websitenschreiber einmal das umschließende div-Tag vergessen, so brechen wir einfach beim nächsten video-Tag bzw. beim nächsten script-Tag ab, da keines dieser Tags innerhalb eines video-Tags vorkommen darf. Andersherum sollten source-Tags ebenfalls nur in video-Tags zum Einsatz kommen, weshalb ein Weiterlesen im Prinzip auch keinen Schaden anrichten können sollte. Firefox 3.0.4 ignoriert nämlich das schließende </video> Tag und würde ohne umschließendes div-Tag sogar irgendwelche nachfolgenden Elemente zu Kindelementen von <video> machen während beim MSIE zum Schluß nocheinmal ein öffnendes video-Tag kommt.

Danach geht es an die Erzeugung eines embed-Tags für unseren Flash Content, das direkt vor unserem video Tag im Dokument eingefügt wird.

<embed src="data/Reiher-848x480-600kb.swf" width="848" height="480" play="true" loop="false" quality="high" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash"> </embed>

Obige Abbildung zeigt wie man mittels des embed-Tags Flash Videos einbinden kann. Im Internet kursieren teils auch Lösungen mit dem neueren object-Tag; sehr obskure zum Teil wo das embed-Tag in ein neueres object-Tag miteingeschachtelt wird, um eine Doppeldarstellung zu verhindern. Wir wollen hier auf das object-Tag zum Einbinden von Flash vollständig verzichten. Neuere Browser unterstützen ohenehin HTML5. Alle anderen müssen zwecks Abwärtskompatibilität das embed-Tag immer noch unterstützen.

Das Einfügen des embed-Tags soll natürlich nur bei nicht HTML5-fähigen Browsern durchgeführt werden, da unser Video sonst gleich doppelt vorhanden wäre: einmal als Flash Video und einmal als HTML5-Video. Ob HTML5 Video unterstützt wird, fragen wir ab, indem wir nachschauen ob das video-Objekt die Eigenschaft "videoWidth" besitzt. Wenn ja muß es sich um ein HTML5-Video Objekt handeln; ansonsten handelt es sich um das, was der DOM-Parser in Unkenntnis des video-Tags hinterlassen hat. Das ansonsten ohnehin unsichtbare video-Tag soll nach Einfügen des Flash-Contents nicht gelöscht werden, da es ja sichtbare Kindelemente, die textuell nach dem schließenden </video> stehen, enthalten kann.

Nun zum Alternativcontent, der angezeigt werden soll wenn keine Video-Engine zur verfügung steht. Hier ist das Fehlen eines abschließenden </video>-Tags sehr störend, da wir für den Fall das Javascript ausgeschalten ist eben doch zumindest eine Fehlermeldung anzeigen wollen. Wenn wir alles sauber in einem dedizierten div-Tag verstaut hätten, könnten wir im Prinzip gleich das ganze div-Tag herauslöschen. Wir gehen hier aber einen viel vorsichtigeren Weg und löschen nur den ersten TextKnoten nach dem öffnenden <video> in dem das Wort "HTML5" vorkommt heraus. Statt des ersten Textknotens nach dem video-Tag könnten wir natürlich auch das erste img-Tag für die Anzeige von Bildern als Alternativcontent herauslöschen. Soviel zur Unterstützung von Alternativcontent, der natürlich nicht angezeigt werden soll wenn Flash und Javascript eingeschaltet sind. Abschließend bleibt uns noch der Fall, daß Javascript zwar eingeschalten ist aber HTML5 und Flash nicht unterstützt werden. Da braucht es eigentlich nur ein ebd.appendChild(ourAlternative­Content), der wieder in einem img-Tag oder einem Textknoten bestehen kann.


Die Sourcen

Jetzt nicht mehr viel zu den Sourcen, da diese selbsterklärend sind, wenn wir den letzten Abschnitt über die Lösungsstrategie gelesen haben. traverseNodesIE liest für den Internet Explorer alle Knoten sequentiell bis zum schließenden video-Tag ein, während traverseNodes für Firefox in die Tiefen hinabsteigt, sollte jedes neue Tag als ein Kindelement des vorhergehenden im DOM auftauchen. Dabei geht es nur darum die source-Elemente hrauszukitzeln (getFlashSource) und das erste mit swf-Typ abzuspeichern (und den Alternativcontent zum Löschen zu markieren).

Wird HTML5 unterstützt, so entkommen wir der Flashisierungsschleife mit einem einfachen break, die sonst für jedes video-Tag ein zugehöriges embed-Tag für die Wiedergabe in Flash bereitstellt. Zum Schluß wird noch der als Alternativcontent erkannte Text, der für den Fall da ist, daß Javascript ausgeschalten ist, gelöscht.

function traverseNodes(parentnode,applyProc,level) { result = true; //document.writeln(level+":"+parentnode.nodeType+""+parentnode.tagName+parentnode.childNodes.length); if(!parentnode.hasChildNodes()) return true; for(cidx=0;cidx<parentnode.childNodes.length;cidx++) { node=parentnode.childNodes[cidx]; if(node.tagName=='VIDEO'||node.tagName=='SCRIPT') return false; applyProc(parentnode,node,level); // mozilla 3.0.4 bug: infinite recursion through script-Tag result = result && traverseNodes(node,applyProc,level+1); if(!result) break; } return result; } function traverseNodesIE(startnode,applyProc,level) { if(startnode.hasChildNodes()) return traverseNodes( startnode, applyProc, level ); else { node = startnode; parentnode = startnode.parentNode; while( node.nextSibling ) { node = node.nextSibling; if(node.tagName=='VIDEO'||node.tagName=='SCRIPT') break; applyProc( parentnode, node, level ); traverseNodes( node, applyProc, level ); } }} var toremove = null; var removeparent = null; var flashsource = '', swftype = "application/x-shockwave-flash"; function getFlashSource(parentnode,node,level) { //document.writeln(level+":"+node.nodeType+""+node.tagName); if( node.nodeType==3 && node.data.indexOf("HTML5")>-1 && !toremove ) { toremove=node; removeparent=parentnode; } if(node.tagName=='SOURCE') { //document.writeln("&nbsp;&nbsp;"+node.getAttribute("src")+"<br>"); if( node.getAttribute("TYPE") == swftype ) flashsource = node.getAttribute("SRC"); //else document.writeln(node.value+node.type); }} videos = document.getElementsByTagName("VIDEO"); for(vidx=0;vidx<videos.length;vidx++) { video = videos[vidx]; if( "videoWidth" in video ) break; // HTML 5 supported //document.writeln("video no "+vidx+"<br>"); flashsource='' traverseNodesIE(video,getFlashSource,0); //document.writeln('source:'+flashsource+'<br>'); ebd = document.createElement("EMBED"); ebd.setAttribute("src",flashsource); ebd.setAttribute("type",swftype); ebd.setAttribute("width",video.getAttribute("WIDTH")); ebd.setAttribute("height",video.getAttribute("HEIGHT")); ebd.setAttribute("loop",video.getAttributeNode("LOOP")?"true":"false"); ebd.setAttribute("play",video.getAttributeNode("AUTOPLAY")?"true":"false"); ebd.setAttribute("quality","high"); ebd.appendChild(document.createTextNode("Weder HTML5 noch Flash werden unterstützt.")); video.parentNode.insertBefore(ebd,video); if(toremove) removeparent.removeChild(toremove); }

Andere Alternativlösungen

Zuletzt noch ein paar Worte über mögliche andere Alternativlösungen.

Eine interessante Alternativlösung besteht in der Verwendung von .ogg-Videos, die mit mwEmbed automatisch auf das Java Applet Cortado umgeleitet werden. Einziger Nachteil: die Kompressionsraten von .ogg mit VP3.2 müssen hinter jenen mit VP8/H.264 zurücklbleiben, was für kurze Videos, die vollständig vorgeladen werden können wenig ausmacht, bei längeren jedoch signifikante Qualitätseinbußen erfordert um das Streaming bei konstanter Datenrate aufrechterhalten zu könenn.

<script type="text/javascript" src="libs/html5media.min.js"></script>

Ein einfaches <script>-Tag im Header des HTML Dokuments genügt um im Bedarfsfall statt dem Video-Tag den Open-Source-Flash-Video-Player Flowplayer zu laden. Einziger Nachteil der html5media Bibliothek zum Stand von 2011 war, daß das Video nicht mittels <source>-Elementen wie oben in mehreren Formaten als Subtag angegeben werden kann sondern nur als mp4 als src-Attribut des Video Tags. Ein Fehler, der sich aber leicht patchen lassen können sollte. Leider ist der Flowplayer inzwischen kostenpflichtig.

<head> <title> … </title> <script type="text/javascript" src="libs/html5media.min.js"></script> … <video controls preload=metadata src='xy.mp4'> </video>

Genuß des Ergebnisses

Testen Sie es selbst; die vorgestellte Lösung funktioniert auf fast allen Browsern.

Reiher 848x480, 600 kbit/s