Videos im Browser abspielen (mit Ton!)



  • Hi, ich bin jetzt langsam mit dem Latein am Ende und viele Websuchen brachten kaum verwertbare Ergebnisse ...

    Ausgangslage: Ein mkv-Video (h264).

    Gewünscht: Abspielen in allen Browsern möglich, am besten mittels des HTML5-Video-Tags: https://www.w3schools.com/html/html5_video.asp

    Problem: Chrome kann src="video.mkv" type="video/mp4" zwar abspielen, allerdings ohne Ton. Wenn ich das Video nach mp4 "remuxe", dann kann es der FF abspielen, allerdings wieder ohne Ton.

    Versuche:
    Direkt .mkv => Chrome: kein Ton, FF: Codec nicht unterstützt
    ffmpeg -i "$f" -c copy "${f%.mkv}.mp4" => kein Bild
    ffmpeg -i "$f" -c copy -c:a aac -movflags +faststart "${f%.mkv}.mp4" => kein Ton
    ffmpeg -i "$f" -vcodec h264 -acodec aac -strict -2 "${f%.mkv}.mp4" => kein Bild und dauert sehr lange

    Habt ihr vielleicht eine Idee, wie man .mkv "streamen" kann, ohne es vorher umzuwandeln? Gibt es ein embed plugin oder ähnliches? Oder kann es ggf. "on-the-fly" konvertiert werden?



  • Hab den Nachmittag mit Internetrecherche verbracht und bin nun (etwas) schlauer ...

    Anscheinend mag kein Browser das 10-Bit H.264 Encoding ...

    Das heißt, ich komme um ein komplettes Reencoding nicht drumherum ... einfach Remuxen ginge nicht ... Und das ist schade, weil ich nicht so viel Rechenleistung und Speicher habe, um alles zu konvertieren. 😕

    Weshalb aber manchmal das Bild ohne Ton abgespielt werden kann, hab ich nicht herausgefunden. Wies auch sei, ich brauche beides + maximale Kompatibilität mit allen Browsern.

    Evtl. ginge mit Kodi headless etwas, mal schauen.



  • @Fragender sagte in Videos im Browser abspielen (mit Ton!):

    Kodi headless

    Welch ein Wunder! Kodi kann das auch nicht. 😆 Kodi bietet "nur" ein Remote-Interface an, mit dem man quasi wie mit einer Fernbedienung den lokalen Player steuern kann, aber nicht über den Browser streamen kann ...

    Mein Vorhaben ist leider nicht umsetzbar mit den zur Verfügung stehenden Ressourcen. 😕

    Ich bin natürlich weiterhin ideenoffen.



  • Vielleicht hilft dir der VLC-Player als Plugin für den Browser weiter. Der kann alles abspielen. Alternativ würde ich sagen das du den Ton als AC3 vorliegen hast, das können die Browser nicht, aber AAC geht wiederum als könnte auch helfen den Ton zu Konvertieren oder eben gleich als AAC zu machen



  • @CTecS Es lag an der 10-Bit-Codierung ...

    Habe nun mithilfe von ffmpeg.wasm etwas hinbekommen:

    <!DOCTYPE html>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    
    <h2>Client-side video transcoding with FFmpeg.wasm</h2>
    
    <video width="1280" height="720" controls id="videoPlayer" muted="muted">
      Your browser does not support HTML video.
    </video>
    
    <p><a onclick="set_ls('tests/a.mkv')" href="#">Play tests/a.mkv</a></p>
    
    
    
    <script src="thirdparty/mux.min.js"></script>
    <script src="thirdparty/ffmpeg.min.js"></script>
    <script src="thirdparty/rxjs.umd.js"></script>
    <!-- <script src="app.js"></script> -->
    
    <script>
      if (localStorage.hasOwnProperty("filename1")) {
        let fn1 = localStorage.getItem("filename1");
        localStorage.clear();
        open_stream(fn1);
      }
    
      function set_ls(filename1) {
        localStorage.setItem("filename1", filename1);
        location.reload();
      }
    
      function open_stream(filename1) {
        const { Observable, fromEvent, partition, combineLatest, zip } = rxjs;
        const { map, flatMap, take, skip } = rxjs.operators;
    
        const bufferStream = filename =>
          new Observable(async subscriber => {
            const ffmpeg = FFmpeg.createFFmpeg({
              corePath: "thirdparty/ffmpeg-core.js",
              log: false
            });
    
            const fileExists = file => ffmpeg.FS("readdir", "/").includes(file);
            const readFile = file => ffmpeg.FS("readFile", file);
    
            await ffmpeg.load();
            const sourceBuffer = await fetch(filename).then(r => r.arrayBuffer());
            ffmpeg.FS(
              "writeFile",
              "input.mkv",
              new Uint8Array(sourceBuffer, 0, sourceBuffer.byteLength)
            );
    
            let index = 0;
    
            ffmpeg
              .run(
                "-i",
                "input.mkv",
                "-map", "0",
                "-c:v", "libx264",
                "-crf", "18",
                "-vf", "format=yuv420p",
                "-c:a", "aac",
                "-g",
                "1",
                // Encode for MediaStream
                "-segment_format_options",
                "movflags=frag_keyframe+empty_moov+default_base_moof",
                // encode 15 second segments
                "-segment_time",
                "15",
                // write to files by index
                "-f",
                "segment",
                "%d.mp4"
              )
              .then(() => {
                // send out the remaining files
                while (fileExists(`${index}.mp4`)) {
                  subscriber.next(readFile(`${index}.mp4`));
                  index++;
                }
                subscriber.complete();
              });
    
            setInterval(() => {
              // periodically check for files that have been written
              if (fileExists(`${index + 1}.mp4`)) {
                subscriber.next(readFile(`${index}.mp4`));
                index++;
              }
            }, 500);
          });
    
        const mediaSource = new MediaSource();
        videoPlayer.src = URL.createObjectURL(mediaSource);
        videoPlayer.play();
    
        const mediaSourceOpen = fromEvent(mediaSource, "sourceopen");
    
        const bufferStreamReady = combineLatest(
          mediaSourceOpen,
    
          bufferStream(filename1),
    
        ).pipe(map(([, a]) => a));
    
        const sourceBufferUpdateEnd = bufferStreamReady.pipe(
          take(1),
          map(buffer => {
            // create a buffer using the correct mime type
            const mime = `video/mp4; codecs="${muxjs.mp4.probe
              .tracks(buffer)
              .map(t => t.codec)
              .join(",")}"`;
            const sourceBuf = mediaSource.addSourceBuffer(mime);
    
            // append the buffer
            mediaSource.duration = 15;
            sourceBuf.timestampOffset = 0;
            sourceBuf.appendBuffer(buffer);
    
            // create a new event stream 
            return fromEvent(sourceBuf, "updateend").pipe(map(() => sourceBuf));
          }),
          flatMap(value => value)
        );
    
        zip(sourceBufferUpdateEnd, bufferStreamReady.pipe(skip(1)))
          .pipe(
            map(([sourceBuf, buffer]) => {
              mediaSource.duration += 15;
              sourceBuf.timestampOffset += 15;
              sourceBuf.appendBuffer(buffer.buffer);
            })
          )
          .subscribe();
    
      }
    </script>
    
    

    Das Video (a.mkv, 10 Minuten) bleibt aber nach 5 bis 6 Minuten stehen. Zudem wird es erst vollständig vom Server geladen und dann decodiert (das Ganze ist also sehr speicherlastig). Hat jemand eine Idee, woran das liegen könnte, bzw. was zu verbessern wäre?

    Ich verstehe noch nicht genau, wann subscriber.complete(); (Zeile 84) aufgerufen wird. Ich vermute, da stimmt etwas nicht, und es wird "zu früh" aufgerufen.

    Danke



  • Hm, hab etwas herausgefunden. Wenn ich die Intervall-Zeit in Zeile 93 von 500ms auf 1500ms erhöhe, dann kann ich anstatt ca. 5 Minuten, ca. 7 Minuten Video schauen - als würde der Browser irgendwann sagen, so, nun sind insgesamt zu viele Anfragen "unbeantwortet geblieben/ins Leere gelaufen", jetzt breche ich die Verarbeitung ab. Aber erklären kann ich mir das nicht.

    JavaScript verhält sich manchmal merkwürdig ...


Anmelden zum Antworten