Verwendung von MediaRecorder als MediaSource

8

Als Übung zum Erlernen von WebRTC versuche ich, die lokale Webcam neben einer verzögerten Wiedergabe der Webcam anzuzeigen. Um dies zu erreichen, versuche ich, aufgezeichnete Blobs an eine BufferSource zu übergeben und die entsprechende MediaSource als Quelle für ein Videoelement zu verwenden.

// the ondataavailable callback for the MediaRecorder
async function handleDataAvailable(event) {
  // console.log("handleDataAvailable", event);
  if (event.data && event.data.size > 0) {
    recordedBlobs.push(event.data);
  }

  if (recordedBlobs.length > 5) {
    if (recordedBlobs.length === 5)
      console.log("buffered enough for delayed playback");
    if (!updatingBuffer) {
      updatingBuffer = true;
      const bufferedBlob = recordedBlobs.shift();
      const bufferedAsArrayBuffer = await bufferedBlob.arrayBuffer();
      if (!sourceBuffer.updating) {
        console.log("appending to buffer");
        sourceBuffer.appendBuffer(bufferedAsArrayBuffer);
      } else {
        console.warn("Buffer still updating... ");
        recordedBlobs.unshift(bufferedBlob);
      }
    }
  }
}
// connecting the media source to the video element
recordedVideo.src = null;
recordedVideo.srcObject = null;
recordedVideo.src = window.URL.createObjectURL(mediaSource);
recordedVideo.controls = true;
try {
  await recordedVideo.play();
} catch (e) {
  console.error(`Play failed: ${e}`);
}

Alle Codes: https://jsfiddle.net/43rm7258/1/

Wenn ich dies in Chromium 78 ausführe, erhalte ich eine NotSupportedError: Failed to load because no supported source was found.vom playElement des Videoelements.

Ich habe keine Ahnung, was ich falsch mache oder wie ich an dieser Stelle vorgehen soll.

Hier geht es um etwas Ähnliches, aber es hilft mir nicht: MediaSource stoppt das Video zufällig

Dieses Beispiel war mein Ausgangspunkt: https://webrtc.github.io/samples/src/content/getusermedia/record/

André
quelle

Antworten:

9

Zusammenfassend

Es ist einfach, es in Firefox und Chrome zum Laufen zu bringen: Sie müssen lediglich einen Audio-Codec zu Ihrer Codec-Liste hinzufügen! video/webm;codecs=opus,vp8

Es ist wesentlich komplizierter, es in Safari zum Laufen zu bringen. MediaRecorder ist eine "experimentelle" Funktion, die unter den Entwickleroptionen manuell aktiviert werden muss. Nach der Aktivierung fehlt Safari eine isTypeSupportedMethode, daher müssen Sie damit umgehen. Unabhängig davon, was Sie vom MediaRecorder anfordern, erhalten Sie von Safari immer eine MP4-Datei, die nicht wie WEBM gestreamt werden kann. Dies bedeutet, dass Sie Transmuxing in JavaScript durchführen müssen, um das Videocontainerformat im laufenden Betrieb zu konvertieren

Android sollte funktionieren, wenn Chrome funktioniert

iOS unterstützt keine Media Source Extensions, SourceBufferist also unter iOS nicht definiert und die gesamte Lösung funktioniert nicht

Ursprünglicher Beitrag

Wenn Sie sich die von Ihnen veröffentlichte JSFiddle ansehen, eine kurze Lösung, bevor wir beginnen:

  • Sie verweisen auf eine Variable, errorMsgElementdie niemals definiert ist. Sie sollten <div>der Seite eine mit einer entsprechenden ID hinzufügen und dann eine const errorMsgElement = document.querySelector(...)Linie erstellen , um sie zu erfassen

Wenn Sie mit Media Source Extensions und MediaRecorder arbeiten, sollten Sie beachten, dass die Unterstützung je nach Browser sehr unterschiedlich sein wird. Obwohl dies ein "standardisierter" Teil der HTML5-Spezifikation ist, ist er plattformübergreifend nicht sehr konsistent. Nach meiner Erfahrung ist es nicht allzu schwierig, MediaRecorder in Firefox zum Laufen zu bringen. Es ist etwas schwieriger, es in Chrome zum Laufen zu bringen. Es ist fast unmöglich, es in Safari zum Laufen zu bringen, und es funktioniert buchstäblich nicht unter iOS etwas, was du tun kannst.

Ich habe dies pro Browser durchgesehen und debuggt und meine Schritte aufgezeichnet, damit Sie einige der Tools verstehen, die Ihnen beim Debuggen von Medienproblemen zur Verfügung stehen

Feuerfuchs

Beim Auschecken Ihrer JSFiddle in Firefox wurde der folgende Fehler in der Konsole angezeigt:

NotSupportedError: Eine Audiospur kann nicht aufgezeichnet werden: video / webm; codecs = vp8 zeigt einen nicht unterstützten Codec an

Ich erinnere mich, dass VP8 / VP9 große Pushs von Google waren und als solche in Firefox möglicherweise nicht funktionieren. Deshalb habe ich versucht, eine kleine Änderung an Ihrem Code vorzunehmen. Ich habe den , options)Parameter aus Ihrem Aufruf von entfernt new MediaRecorder(). Dies weist den Browser an, den gewünschten Codec zu verwenden, sodass Sie wahrscheinlich in jedem Browser eine andere Ausgabe erhalten (dies sollte jedoch zumindest in jedem Browser funktionieren ).

Dies funktionierte in Firefox, also habe ich Chrome ausgecheckt.

Chrom

Diesmal habe ich einen neuen Fehler bekommen:

(Index): 409 Nicht erfasst (im Versprechen) DOMException: Fehler beim Ausführen von 'appendBuffer' für 'SourceBuffer': Dieser SourceBuffer wurde aus der übergeordneten Medienquelle entfernt. unter MediaRecorder.handleDataAvailable ( https://fiddle.jshell.net/43rm7258/1/show/:409:22 )

Also ging ich in meinem Browser zu chrome: // media-internals / und sah Folgendes:

Das Audio-Stream-Codec-Opus stimmt nicht mit den SourceBuffer-Codecs überein.

In Ihrem Code geben Sie einen Video-Codec (VP9 oder VP8) an, jedoch keinen Audio-Codec. Der MediaRecorder lässt den Browser also einen beliebigen Audio-Codec auswählen. Es sieht so aus, als würde in Chrome MediaRecorder standardmäßig "opus" als Audio-Codec ausgewählt, in Chrome SourceBuffer wird jedoch standardmäßig etwas anderes ausgewählt. Dies wurde trivial behoben. Ich habe Ihre beiden Zeilen aktualisiert, die Folgendes festlegen options.mimeType:

  • options = { mimeType: "video/webm;codecs=opus, vp9" };
  • options = { mimeType: "video/webm;codecs=opus, vp8" };

Da Sie dasselbe optionsObjekt zum Deklarieren des MediaRecorders und des SourceBuffers verwenden, bedeutet das Hinzufügen des Audio-Codecs zur Liste, dass der SourceBuffer jetzt mit einem gültigen Audio-Codec deklariert wird und das Video abgespielt wird

Zum guten Teil habe ich den neuen Code (mit einem Audio-Codec) auf Firefox getestet. Das hat funktioniert! Wir sind also 2 für 2, indem wir einfach den Audio-Codec zur optionsListe hinzufügen (und ihn in den Parametern zum Deklarieren des MediaRecorders belassen).

Es sieht so aus, als ob VP8 und Opus in Firefox funktionieren, sind aber nicht die Standardeinstellungen (obwohl im Gegensatz zu Chrome die Standardeinstellungen für MediaRecorder und SourceBuffer dieselben sind, weshalb das Entfernen des optionsParameters vollständig funktioniert hat).

Safari

Diesmal haben wir einen Fehler erhalten, den wir möglicherweise nicht beheben können:

Nicht behandelte Ablehnung von Versprechungen: ReferenceError: Variable: MediaRecorder kann nicht gefunden werden

Das erste, was ich tat, war Google "Safari MediaRecorder", das diesen Artikel auftauchte . Ich dachte, ich würde es versuchen, also warf ich einen Blick darauf. Sicher genug:

Safari-Eigenschaften

Ich habe darauf geklickt, um MediaRecorder zu aktivieren, und in der Konsole wurde Folgendes festgestellt:

Nicht behandelte Ablehnung von Versprechungen: TypeError: MediaRecorder.isTypeSupported ist keine Funktion. (In 'MediaRecorder.isTypeSupported (options.mimeType)' ist 'MediaRecorder.isTypeSupported' undefiniert.)

Safari hat also nicht die isTypeSupportedMethode. Keine Sorge, wir sagen nur: "Wenn diese Methode nicht existiert, nehmen Sie an, dass es sich um Safari handelt, und stellen Sie den Typ entsprechend ein."

  if (MediaRecorder.isTypeSupported) {
    options = { mimeType: "video/webm;codecs=vp9" };
    if (!MediaRecorder.isTypeSupported(options.mimeType)) {
      console.error(`${options.mimeType} is not Supported`);
      errorMsgElement.innerHTML = `${options.mimeType} is not Supported`;
      options = { mimeType: "video/webm;codecs=vp8" };
      if (!MediaRecorder.isTypeSupported(options.mimeType)) {
        console.error(`${options.mimeType} is not Supported`);
        errorMsgElement.innerHTML = `${options.mimeType} is not Supported`;
        options = { mimeType: "video/webm" };
        if (!MediaRecorder.isTypeSupported(options.mimeType)) {
          console.error(`${options.mimeType} is not Supported`);
          errorMsgElement.innerHTML = `${options.mimeType} is not Supported`;
          options = { mimeType: "" };
        }
      }
    }
  } else {
    options = { mimeType: "" };
  }

Jetzt musste ich nur noch einen mimeType finden, den Safari unterstützte. Etwas leichtes Googeln deutet darauf hin, dass H.264 unterstützt wird, also habe ich versucht:

options = { mimeType: "video/webm;codecs=h264" };

Dies gab mir erfolgreich MediaRecorder started, scheiterte aber an der Zeile addSourceBuffermit dem neuen Fehler:

NotSupportedError: Der Vorgang wird nicht unterstützt.

Ich werde weiterhin versuchen, zu diagnostizieren, wie dies in Safari funktioniert, aber im Moment habe ich zumindest Firefox und Chrome angesprochen

Update 1

Ich habe weiter an Safari gearbeitet. Leider fehlt Safari das Tool von Chrome und Firefox, um tief in die Medieninternale einzudringen, sodass viele Vermutungen erforderlich sind.

Ich hatte zuvor herausgefunden, dass beim Versuch, einen Anruf zu tätigen, die Fehlermeldung "Der Vorgang wird nicht unterstützt" angezeigt wird addSourceBuffer. Deshalb habe ich eine einmalige Seite erstellt, um zu versuchen, genau diese Methode unter verschiedenen Umständen aufzurufen:

  • Fügen Sie möglicherweise einen Quellpuffer hinzu, bevor playdas Video aufgerufen wird
  • Fügen Sie möglicherweise einen Quellpuffer hinzu, bevor die Medienquelle an ein Videoelement angehängt wurde
  • Fügen Sie möglicherweise einen Quellpuffer mit verschiedenen Codecs hinzu
  • usw

Ich stellte fest, dass das Problem immer noch der Codec war und dass die Fehlermeldung, dass die "Operation" nicht zulässig war, leicht irreführend war. Es waren die Parameter , die nicht erlaubt waren. Die einfache Angabe von "h264" funktionierte für den MediaRecorder, aber für den SourceBuffer musste ich die Codec-Parameter weitergeben .

Eines der ersten Dinge, die ich versuchte, war, zur MDN-Beispielseite zu gehen und die dort verwendeten Codecs zu kopieren : 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"'. Dies ergab den gleichen Fehler "Operation nicht zulässig". Graben in die Bedeutung dieser Codec - Parameter (wie das, was zum Teufel ist 42E01Eauch bedeuten ?). Während ich wünschte, ich hätte eine bessere Antwort, stieß ich beim Googeln auf diesen StackOverflow-Beitrag, in dem die Verwendung 'video/mp4; codecs="avc1.64000d,mp4a.40.2"'auf Safari erwähnt wurde. Ich habe es ausprobiert und die Konsolenfehler waren weg!

Obwohl die Konsolenfehler jetzt verschwunden sind, sehe ich immer noch kein Video. Es gibt also noch viel zu tun.

Update 2

Weitere Untersuchungen im Debugger in Safari (Platzieren mehrerer Haltepunkte und Überprüfen von Variablen in jedem Schritt des Prozesses) ergaben, dass handleDataAvailabledies in Safari nie aufgerufen wurde. Es sieht so aus, als würde Firefox und Chrome mediaRecorder.start(100)der Spezifikation ordnungsgemäß folgen und ondatavailablealle 100 Millisekunden aufrufen , aber Safari ignoriert den Parameter und puffert alles in einem massiven Blob. Manuelles mediaRecorder.stop()Anrufen führte ondataavailabledazu, dass mit allem, was bis zu diesem Zeitpunkt aufgezeichnet wurde, angerufen wurde

Ich habe versucht setInterval, mediaRecorder.requestData()alle 100 Millisekunden aufzurufen , requestDatawurde aber in Safari nicht definiert (ähnlich wie isTypeSupportednicht definiert). Das hat mich ein bisschen in Schwierigkeiten gebracht.

Als nächstes habe ich versucht, das gesamte MediaRecorder-Objekt zu bereinigen und alle 100 Millisekunden ein neues zu erstellen, aber dies warf einen Fehler in die Zeile await bufferedBlob.arrayBuffer(). Ich untersuche immer noch, warum dieser fehlgeschlagen ist

Update 3

Eine Sache, an die ich mich beim MP4-Format erinnere, ist, dass das "moov" -Atom benötigt wird, um Inhalte wiederzugeben . Aus diesem Grund können Sie die mittlere Hälfte einer MP4-Datei nicht herunterladen und abspielen. Sie müssen die GANZE Datei herunterladen. Ich fragte mich, ob die Tatsache, dass ich MP4 ausgewählt hatte, der Grund war, warum ich keine regelmäßigen Updates erhielt.

Ich habe versucht, video/mp4auf einige andere Werte umzusteigen, und dabei unterschiedliche Ergebnisse erzielt:

  • video/webm - Der Betrieb wird nicht unterstützt
  • video/x-m4v- Ich habe mich wie MP4 verhalten und nur dann Daten erhalten, wenn sie .stop()aufgerufen wurden
  • video/3gpp - Benahm sich wie MP4
  • video/flv - Der Betrieb wird nicht unterstützt
  • video/mpeg - Benahm sich wie MP4

Alles, was sich wie MP4 verhält, hat mich dazu gebracht, die Daten zu überprüfen, an die tatsächlich weitergegeben wurde handleDataAvailable. Da bemerkte ich Folgendes:

Geben Sie hier die Bildbeschreibung ein

Egal was ich für das Videoformat ausgewählt habe, Safari hat mir immer einen MP4 gegeben!

Plötzlich erinnerte ich mich daran, warum Safari so ein Albtraum war und warum ich es mental als "fast unmöglich" eingestuft hatte. Um mehrere MP4s zusammenzufügen, wäre ein JavaScript-Transmuxer erforderlich

Da erinnerte ich mich, genau das hatte ich zuvor getan . Ich habe vor etwas mehr als einem Jahr mit MediaRecorder und SourceBuffer zusammengearbeitet, um einen JavaScript-RTMP-Player zu erstellen. Sobald der Player fertig war, wollte ich Unterstützung für DVR hinzufügen (Suche nach Teilen des bereits gestreamten Videos), indem ich MediaRecorder verwendete und einen Ringpuffer im Speicher von 1-Sekunden-Video-Blobs aufbewahrte. Auf Safari habe ich diese Video-Blobs über den Transmuxer ausgeführt, den ich codiert hatte, um sie von MP4 in ISO-BMFF zu konvertieren, damit ich sie miteinander verketten konnte.

Ich wünschte, ich könnte den Code mit Ihnen teilen, aber alles gehört meinem alten Arbeitgeber - an diesem Punkt ist mir die Lösung verloren gegangen. Ich weiß, dass sich jemand die Mühe gemacht hat, FFMPEG mit emscripten in JavaScript zu kompilieren, sodass Sie dies möglicherweise nutzen können.

stevendesu
quelle
Beeindruckend! Vielen Dank dafür! Ich bin wirklich erstaunt über die Anstrengungen, die Sie unternommen haben. Ich werde Ihre Ergebnisse durchgehen und sehen, ob ich die Dinge zum Laufen bringen kann.
André
1
Mit Ihren Hinweisen, die in Chrome funktionieren, war ich der Lösung nahe, hatte aber keine Ahnung, wie ich damit fortfahren sollte. Vielen Dank, dass Sie die Schritte erklärt haben, die Sie unternommen haben, um es aufzuspüren. Nochmals vielen Dank, Ihre Antwort war sehr hilfreich und ich habe viel gelernt.
André