HTML Video im Browser abspielen?

CyborgBeta schrieb:
Ich habe noch https://github.com/jplayer/jPlayer gefunden, aber leider ist dieser auch schon fast 10 Jahre alt (und wurde irgendwann nicht mehr weiterentwickelt).
Du kannst 100'000 verschiedene JavaScript "Player" ausprobieren. An der Problematik wird sich nichts ändern. Es wurde dir hier mehrmals erklärt diese Player sind nur ein Interface.

Wie sieht der Play Button / Pause Button aus. Wiedergabegeschwindigkeit steuern usw. Es sind keine Videoplayer wie auf dem Computer mit eigenen Codecs oder sowas.

Was du willst funktioniert nicht. Dein Format wird von Browsern schlicht nicht unterstützt. Wenn das Video also im Browser abgespielt werden soll musst du es umkonvertieren.

Entweder machst du das selber oder über YoutTube & Co.
 
  • Gefällt mir
Reaktionen: mental.dIseASe und CyborgBeta
kim88 schrieb:
Du kannst 100'000 verschiedene JavaScript "Player" ausprobieren. An der Problematik wird sich nichts ändern. Es wurde dir hier mehrmals erklärt diese Player sind nur ein Interface.

Hi, dem würde ich bedingt widersprechen. Mittlerweile kann man mithilfe von Webassambly genau solche heavy-computing-tasks, wie live-encoding auslagern, und mit bindings via Javascript ausführen.

Hier hat sich scheinbar jemand die Mühe gemacht, einen Player genau mit WASM zu implementieren: https://bestmediaplayer.org/

Bin mir nur nicht sicher, wie zielführend das ist, da der TE ja leider nicht so versiert ist.
Aber eventuell einen Blick wert
 
  • Gefällt mir
Reaktionen: mental.dIseASe, sh., BeBur und eine weitere Person
Danke, nach so etwas hatte ich gesucht, selber wollte ich aber keinen Player "programmieren"...


Broxxa schrieb:
leider nicht so versiert ist.
Nicht schon wieder... :rolleyes:

Viele Fragen zu stellen, ist also kein Zeichen von Intelligenz... Ich habe es jetzt verstanden.
 
Was nützt es denn Dir etwas hinzuwerfen, was definitv nicht trivial ist, und das nicht zu erwähnen.
Man muss Dir hier auch nicht helfen.
 
@Broxxa

Ich kann den leider nicht starten:

Code:
$ tree
.
├── all.sh
├── app.js
├── img
│   ├── favicon-16x16.png
│   ├── favicon-32x32.png
│   └── favicon.ico
├── index.html
├── LICENSE
├── README.md
├── tests
│   ├── avi.avi
│   ├── flac.flac
│   ├── flv.flv
│   ├── gif.gif
│   ├── heic2.heic
│   ├── heic.heic
│   ├── ico.ico
│   ├── jfif.jfif
│   ├── jpeg.jpeg
│   ├── jpg.jpg
│   ├── m4a.m4a
│   ├── mkv.mkv
│   ├── mkvwithsound.mkv
│   ├── mov.mov
│   ├── mp3.mp3
│   ├── mp4.mp4
│   ├── mpg.mpg
│   ├── noaudio.flv
│   ├── ogg.ogg
│   ├── png.png
│   ├── swf.swf
│   ├── tiff.tiff
│   ├── vob.vob
│   ├── wav.wav
│   ├── webm.webm
│   ├── webmwithsound.webm
│   ├── webp.webp
│   └── wmv.wmv
├── thirdparty
│   ├── ffmpeg-core.js
│   ├── ffmpeg-core.wasm
│   ├── ffmpeg-core.worker.js
│   ├── ffmpeg.min.js
│   ├── mux.min.js
│   ├── mvp.css
│   └── vue.min.js
└── tools
    ├── server.sh
    └── testserver.php

4 directories, 45 files

all.sh (zum Starten) :

Bash:
#!/bin/sh
apt update && apt install ffmpeg -y
nohup bash -c "php -S 0.0.0.0:92 tools/testserver.php" &
php -S 0.0.0.0:80

In der Browser Konsole erscheint folgende Fehlermeldung:

Code:
Uncaught (in promise) ReferenceError: SharedArrayBuffer is not defined
    createFFmpegCore ffmpeg-core.js:334
    e ffmpeg.min.js:1
    f ffmpeg.min.js:1
    _invoke ffmpeg.min.js:1
    O ffmpeg.min.js:1
    i ffmpeg.min.js:1
    c ffmpeg.min.js:1
    promise callback*i ffmpeg.min.js:1
    c ffmpeg.min.js:1
    a ffmpeg.min.js:1
    a ffmpeg.min.js:1
    P ffmpeg.min.js:1
    newffmpeg app.js:26
    mounted app.js:267
    VueJS 7
    <anonymous> app.js:163

Zeile 334 ist hier:

1689799215814.png


Was hab ich vergessen oder falsch gemacht?
Ergänzung ()

Genau die gleiche Fehlermeldung (neben einem CORS-Fehler, da der Server falsch konfiguriert wurde ...) übrigens auch, wenn ich nicht lokal starte, sondern die Seite https://bestmediaplayer.org/ direkt aufrufe:

1689801398577.png


Kurze Zwischenfrage: Die Konvertierung des Videos (on-the-fly) via ffmpeg erfolgt doch serverseitig - oder habe ich jetzt einen Denkfehler?

Kann von meinen Gästen nämlich nicht erwarten, dass die alle ffmpeg installiert haben...
 
Zuletzt bearbeitet:
Kurze Zwischenfrage: Die Konvertierung des Videos (on-the-fly) via ffmpeg erfolgt doch serverseitig - oder habe ich jetzt einen Denkfehler?
Nein, die Idee hinter der Geschichte ist hier, dass das ffmpeg im wasm-file gebundelt ist. Das wird dann beim Aufrufen der Seite geladen, und alles weitere wird dann Browserseitig konvertiert.


Hm, jo - production ready ist was anderes.
Ich konnts zumindest Lokal starten. Mit Änderungen an den Checks, die an der Stelle das Objekt nicht kennen.
Im Anschluss fliegt dann aber ein Fehler, beim Call von ffmpeg, was ihn aber stört, wird nicht geloggt.
Da in seinem Repo kein wirklicher Sourcecode, sondern schon gebundelte / minified Scripts abliegen wird das müßig da nun den Fehler zu finden. Wahrscheinlich ist man schneller, wenn man an der der Stelle hier ansetzt:
https://github.com/ffmpegwasm
Ist halt alles definitiv mit Aufwand verbunden. Eventuell findest du ja noch etwas in die Richtung, das schon fertig ist...
 
  • Gefällt mir
Reaktionen: CyborgBeta
Broxxa schrieb:
die Idee hinter der Geschichte ist hier, dass das ffmpeg im wasm-file gebundelt ist. Das wird dann beim Aufrufen der Seite geladen, und alles weitere wird dann Browserseitig konvertiert.
Danke für deine Erklärung! :)

Aber das würde ja meine Anforderung erfüllen, dass Besucher meiner Website nicht etwas lokal installiert haben müssen ...

Das mit dem Fehler ist echt schräg 🫤

"Normalerweise" legt man im Repo nicht die minifizierten Scripts ab, sondern die "originalen" plus build scripts, die das Ganze dann "zusammenschustern".

Die Forks sind leider auch schon mind. 3 Jahre alt, und dort fand ich auch nichts.

Werde dann wohl als Nächstes mal einen Blick auf https://github.com/ffmpegwasm werfen.

BTW: Ich starte die oben erwähnte all.sh in einem Docker-Container ... vielleicht hat das für Verwirrung gesorgt. Production-ready ist das aber wie von dir gesagt noch gar nicht. Ich hätte aber eh noch ein paar Änderungen an der index.html gemacht, zumal da zum Beispiel Google Analytics Code drin ist, der dort nun wirklich nicht zu sein braucht.

Mist, schon wieder so spät. :D
 
  • Gefällt mir
Reaktionen: madmax2010
Falls du deine Videos für möglichst viele Besucher verfügbar machen möchtest, würde ich dir dringend raten deine Videos gescheit zu konvertieren und im unterstützten Format einzubetten. Selbst wenn du diese WASM Geschichten hinbekommst: das verbraucht doch nur unnötig Rechenleistung auf den Clients, gerade für Leute die mit Mobilgeräten das abspielen nicht sonderlich toll wegen Akkuverbrauch.

Ich würde einen schönen Player nehmen, z.B.: plyr. Dann die Video aufbereiten, z.B.:

Code:
ffmpeg -i "mein-video.mp4" -y \
    -c:v libsvtav1 -crf 28 -preset 5 -g 50 -pix_fmt yuv420p10le -vf scale=-2:480 -svtav1-params film-grain=8 -c:a libfdk_aac -b:a 128k -ar 48k -movflags +faststart "mein-video-av1-480.mp4" \
    -c:v libsvtav1 -crf 28 -preset 5 -g 50 -pix_fmt yuv420p10le -vf scale=-1:720 -svtav1-params film-grain=8 -c:a libfdk_aac -b:a 128k -ar 48k -movflags +faststart "mein-video-av1-720.mp4" \
    -c:v libsvtav1 -crf 28 -preset 5 -g 50 -pix_fmt yuv420p10le -vf scale=-1:1080 -svtav1-params film-grain=8 -c:a libfdk_aac -b:a 128k -ar 48k -movflags +faststart "mein-video-av1-1080.mp4" \
    -c:v libvpx-vp9 -crf 26 -g 50 -pix_fmt yuv420p -vf scale=-2:480 -c:a libopus -b:a 128k -ar 48k "mein-video-vp9-480.webm" \
    -c:v libvpx-vp9 -crf 26 -g 50 -pix_fmt yuv420p -vf scale=-1:720 -c:a libopus -b:a 128k -ar 48k "mein-video-vp9-720.webm" \
    -c:v libvpx-vp9 -crf 26 -g 50 -pix_fmt yuv420p -vf scale=-1:1080 -c:a libopus -b:a 128k -ar 48k "mein-video-vp9-1080.webm" \
    -c:v libx264 -crf 22 -preset slower -g 50 -pix_fmt yuv420p -vf scale=-2:480 -c:a libfdk_aac -b:a 128k -ar 48k -movflags +faststart "mein-video-h264-480.mp4" \
    -c:v libx264 -crf 22 -preset slower -g 50 -pix_fmt yuv420p -vf scale=-1:720 -c:a libfdk_aac -b:a 128k -ar 48k -movflags +faststart "mein-video-h264-720.mp4" \
    -c:v libx264 -crf 22 -preset slower -g 50 -pix_fmt yuv420p -vf scale=-1:1080 -c:a libfdk_aac -b:a 128k -ar 48k -movflags +faststart "mein-video-h264-1080.mp4" \
    -c:v libx265 -crf 25 -preset slower -g 50 -pix_fmt yuv420p -vf scale=-2:480 -tag:v hvc1 -c:a libfdk_aac -b:a 128k -ar 48k -movflags +faststart "mein-video-h265-480.mp4" \
    -c:v libx265 -crf 25 -preset slower -g 50 -pix_fmt yuv420p -vf scale=-1:720 -tag:v hvc1 -c:a libfdk_aac -b:a 128k -ar 48k -movflags +faststart "mein-video-h265-720.mp4" \
    -c:v libx265 -crf 25 -preset slower -g 50 -pix_fmt yuv420p -vf scale=-1:1080 -tag:v hvc1 -c:a libfdk_aac -b:a 128k -ar 48k -movflags +faststart "mein-video-h265-1080.mp4"
(kann man vielleicht noch optimieren)

Und einbetten:

HTML:
<video data-poster="/img/1536/mein-video.webp" playsinline="" src="/videos/mein-video-av1-1080.mp4">
    <source src="/videos/mein-video-av1-1080.mp4" type='video/mp4;codecs="av01.0.08M.10,mp4a.40.2"' size="1080" />
    <source src="/videos/mein-video-av1-720.mp4" type='video/mp4;codecs="av01.0.05M.10,mp4a.40.2"' size="720" />
    <source src="/videos/mein-video-av1-480.mp4" type='video/mp4;codecs="av01.0.04M.10,mp4a.40.2"' size="480" />
    <source src="/videos/mein-video-vp9-1080.webm" type='video/webm;codecs="vp9,opus"' size="1080" />
    <source src="/videos/mein-video-vp9-720.webm" type='video/webm;codecs="vp9,opus"' size="720" />
    <source src="/videos/mein-video-vp9-480.webm" type='video/webm;codecs="vp9,opus"' size="480" />
    <source src="/videos/mein-video-h265-1080.mp4" type='video/mp4;codecs="hvc1,mp4a.40.2"' size="1080" />
    <source src="/videos/mein-video-h265-720.mp4" type='video/mp4;codecs="hvc1,mp4a.40.2"' size="720" />
    <source src="/videos/mein-video-h265-480.mp4" type='video/mp4;codecs="hvc1,mp4a.40.2"' size="480" />
    <source src="/videos/mein-video-h264-1080.mp4" type='video/mp4;codecs="avc1.640033,mp4a.40.2"' size="1080" />
    <source src="/videos/mein-video-h264-720.mp4" type='video/mp4;codecs="avc1.640028,mp4a.40.2"' size="720" />
    <source src="/videos/mein-video-h264-480.mp4" type='video/mp4;codecs="avc1.64001f,mp4a.40.2"' size="480" />
    <track kind="captions" label="Deutsch" srclang="de" src="/subtitles/mein-video.vtt" default="true" />
</video>
 
  • Gefällt mir
Reaktionen: Falc410, BeBur und CyborgBeta
@Broxxa Ich habe mal versucht, das Beispiel auf https://github.com/ffmpegwasm/ffmpeg.wasm/tree/master/examples/browser zum Laufen zu bekommen ... aber es scheitert immer an CORS (vermutlich). Vielleicht hast du eine Idee?

Code:
$ tree
.
├── a.mkv
├── docker-compose.yml
├── install-docker.sh
└── mediaplayer
    ├── index.html
    └── index.js

2 directories, 5 files

index.js:

Javascript:
const fs = require('fs');
const { createFFmpeg, fetchFile } = require('@ffmpeg/ffmpeg');
const ffmpeg = self.FFmpeg.createFFmpeg({log: true});

onmessage = async (event) => {
    try {
        const {buffer, name, inType, outType} = event.data;
        if (!ffmpeg.isLoaded()) {
            await ffmpeg.load();
        }

        ffmpeg.FS('writeFile', `${name}.${inType}`, new Uint8Array(buffer));
        await ffmpeg.run('-i', `${name}.${inType}`, `${name}.${outType}`);
        const data = ffmpeg.FS('readFile', `${name}.${outType}`);

        postMessage({buffer: data.buffer, type: "result"}, [data.buffer]);

        // delete files from memory
        ffmpeg.FS('unlink', `${name}.${inType}`);
        ffmpeg.FS('unlink', `${name}.${outType}`);
    } catch (e) {
        postMessage({type: "error", error: e});
    }
}

index.html:

HTML:
<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ffmpeg/0.11.6/ffmpeg.min.js" integrity="sha512-91IRkhfv1tLVYAdH5KTV+KntIZP/7VzQ9E/qbihXFSj0igeacWWB7bQrdiuaJVMXlCVREL4Z5r+3C4yagAlwEw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <style>
      html, body {
        margin: 0;
        width: 100%;
        height: 100%
      }
      body {
        display: flex;
        flex-direction: column;
        align-items: center;
      }
    </style>
  </head>
  <body>
    <h3>Upload a video to transcode to mp4 (x264) and play!</h3>
    <video id="output-video" controls></video><br/>
    <input type="file" id="uploader">
    <button onClick="cancel()">Cancel</button>
    <p id="message"></p>
    <script>
      const { createFFmpeg, fetchFile } = FFmpeg;
      let ffmpeg = null;

      const transcode = async ({ target: { files } }) => {
        if (ffmpeg === null) {
          ffmpeg = createFFmpeg({ log: true });
        }
        const message = document.getElementById('message');
        const { name } = files[0];
        message.innerHTML = 'Loading ffmpeg-core.js';
        if (!ffmpeg.isLoaded()) {
          await ffmpeg.load();
        }
        ffmpeg.FS('writeFile', name, await fetchFile(files[0]));
        message.innerHTML = 'Start transcoding';
        await ffmpeg.run('-i', name,  'output.mp4');
        message.innerHTML = 'Complete transcoding';
        const data = ffmpeg.FS('readFile', 'output.mp4');

        const video = document.getElementById('output-video');
        video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
      }
      const elm = document.getElementById('uploader');
      elm.addEventListener('change', transcode);

      const cancel = () => {
        try {
          ffmpeg.exit();
        } catch(e) {}
        ffmpeg = null;
      }
    </script>
  </body>
</html>

docker-compose.yml:

Code:
version: "3"

services:
  website6:
    image: "node:latest"
    user: "node"
    working_dir: /home/node/app
    environment:
      - NODE_ENV=production
    volumes:
      - ./mediaplayer/:/home/node/app
    network_mode: "host"
    expose:
      - "8080:8080"
    command: >
      bash -c "npm install http-server cors &&
               npm install @ffmpeg/ffmpeg @ffmpeg/core &&
               npx http-server --cors='*' --experimental-wasm-threads"

docker compose up -d
docker logs -tf downloads-website6-1

Wenn ich nun (mit dem FF) http://127.0.0.1:8080 aufrufe und a.mkv "abspielen lassen" möchte, bekomme ich wieder folgende Fehlermeldung:

Code:
[info] use ffmpeg.wasm v0.11.6 createFFmpeg.js:43:14
[info] load ffmpeg-core createFFmpeg.js:43:14
[info] loading ffmpeg-core createFFmpeg.js:43:14
Uncaught (in promise) ReferenceError: SharedArrayBuffer is not defined
    createFFmpegCore 2dc5d57e-b7ae-44f3-bfe8-7ab58f1c813d:22
    e createFFmpeg.js:117
    u runtime.js:63
    _invoke runtime.js:293
    j runtime.js:118
    o ffmpeg.min.js:1
    c ffmpeg.min.js:1
    promise callback*o ffmpeg.min.js:1
    c ffmpeg.min.js:1
    a ffmpeg.min.js:1
    a ffmpeg.min.js:1
    R createFFmpeg.js:103
    transcode (index):36
2dc5d57e-b7ae-44f3-bfe8-7ab58f1c813d:22:152

Woran liegt das? Bin in den node Sachen leider nicht so versiert. :(
Ergänzung ()

@jb_alvarado Pre-compilen / Konvertieren möchte ich nicht, dafür habe ich, wie gesagt, nicht genügend Server-Ressourcen zur Verfügung. Aber generell ist deine Antwort auch hilfreich ...
 
Zuletzt bearbeitet:
Ach herr je, der Server (die index.js) läuft gar nicht, es wird nur die index.html ausgeliefert. 😬 Hab ich übersehen.
 
Zum
SharedArrayBuffer is not defined
Error, ich hab den Check ausgebaut, da ich im Debugger gesehen hab, dass die variable genau den gewünschten Typ hat.
Zum Cors-Problem kann ich gerade nichts sagen, müsst ich mir nochmal anschauen. Komme aber vor morgen nicht dazu
 
  • Gefällt mir
Reaktionen: CyborgBeta
Kurzer Zwischenstand (ich bin etwas am Zusammenschustern ...): Die index.html und die index.js hat sich nicht geändert. Es sind aber zwei .js hinzugekommen (server.js und webpack.config.dev.js) und die docker-compose.yml hat sich geändert:

docker-compose.yml:

Code:
#...
    command: >
      bash -c "npm install webpack webpack-dev-middleware express cors @babel/core @babel/preset-env babel-loader fs path os &&
               npm install @ffmpeg/ffmpeg @ffmpeg/core &&
               node --experimental-worker server.js"
#...

(install fs, path und os unnötig...)

server.js:

Javascript:
const webpack = require('webpack');
const middleware = require('webpack-dev-middleware');
const express = require('express');
const path = require('path');
const cors = require('cors');
const webpackConfig = require('./webpack.config.dev');

const compiler = webpack(webpackConfig);
const app = express();

function coi(req, res, next) {
  res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
  res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
  next();
}

app.use(cors());
app.use(coi);

app.use('/', express.static(path.resolve(__dirname, '.')));
app.use(middleware(compiler, { publicPath: '/dist', writeToDisk: true }));

module.exports = app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

(Port ist jetzt 3000)

webpack.config.dev.js:

Javascript:
const path = require('path');
//const common = require('./webpack.config.common');

const genConfig = ({
  entry, filename, library, libraryTarget,
}) => ({
  module: {
    rules: [
      {
        test: /\.m?js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              [
                '@babel/preset-env',
                {
                  targets: 'last 2 versions',
                },
              ],
            ],
          },
        },
      },
    ],
  },
  mode: 'development',
  entry,
  output: {
    filename,
    library,
    libraryTarget,
  },
  devServer: {
    allowedHosts: ['localhost', '.gitpod.io'],
  },
});

module.exports = [
  genConfig({
    entry: path.resolve(__dirname, '.', 'index.js'),
    filename: 'ffmpeg.dev.js',
    library: 'FFmpeg',
    libraryTarget: 'umd',
  }),
];

(genConfig verstehe ich net 😕 )

Jetzt läuft es zwar, aber er meckert mich an, dass i-wo etwas mit den Pfaden nicht stimmt, wahrscheinlich werde ich i-wo ein ./ oder Ähnliches vergessen haben.

Ich kann mich aber auch erst morgen wieder damit befassen. :)
 
So, hab noch ein bisschen weitergemacht, und bin jetzt bei "funktioniert". Das ist die gute Nachricht. Die Schlechte ist, auch nur bei "funktioniert".

Und zwar hab ich mir dieses https://blog.scottlogic.com/2020/11/23/ffmpeg-webassembly.html und dieses https://github.com/ColinEberhardt/ffmpeg-wasm-streaming-video-player Beispiel angeschaut, und nur eine server.js hinzugefügt und einen import angepasst.

Damit funktioniert es für das Beispielvideo so, wie ich gedacht hatte, dass es funktionieren soll ...

Bin nun noch am Überlegen, wie ich das

Javascript:
    ffmpeg
      .run(
        "-i",
        "input.mp4",
        "-g",
        "1",
        // Encode for MediaStream
        "-segment_format_options",
        "movflags=frag_keyframe+empty_moov+default_base_moof",
        // encode 5 second segments
        "-segment_time",
        "5",
        // 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();
      });

so anpassen kann, dass es auch für eine input.mkv funktionieren würde.
 
docker-compose.yml (Auszug)
Code:
    command: >
      bash -c "npm install @ffmpeg/ffmpeg @ffmpeg/core @reactivex/rxjs express cors &&
               node --experimental-worker server.js"

server.js
Javascript:
const express = require('express');
const path = require('path');
const cors = require('cors');

const app = express();

function coi(req, res, next) {
  res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
  res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
  next();
}

app.use(cors());
app.use(coi);

app.use('/', express.static(path.resolve(__dirname, '.')));

module.exports = app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

index.html
HTML:
<!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="open_stream('a.mkv')" href="#">Play a.mkv</a></p>

<!-- <p>See the <a href="https://blog.scottlogic.com/2020/11/23/ffmpeg-webassembly.html">associated blog post</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>
  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",
            "-vcodec",
            "libvpx-vp9",
            "-crf",
            "24",
            "-acodec",
            "libfdk_aac",
            // "-g",
            // "1",
            // Encode for MediaStream
            "-segment_format_options",
            "movflags=frag_keyframe+empty_moov+default_base_moof",
            // encode 5 second segments
            "-segment_time",
            "5",
            // 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++;
          }
        }, 200);
      });

    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 = 5;
        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 += 5;
          sourceBuf.timestampOffset += 5;
          sourceBuf.appendBuffer(buffer.buffer);
        })
      )
      .subscribe();

  }
</script>

Das Problem ist hier einfach folgendes: Er lädt die komplette Videodatei in den Speicher, also herunter, konvertiert es und kann den Videocodec dann nicht abspielen, leider. :( Das könnte an Zeile 47 bis 54 liegen, denke ich.

Ich mache erst mal Schluss für heute. :)
 
Du konvertierst in Videocodec VP9 ("-vcodec libvpx-vp9") und als Container MP4. (".mp4")
Das passt nicht. In MP4 gehen u.a. H.264 und AV1 als Codec. VP9 Videos müssen in einen WebM Container.
Das Audio muss auch entsprechend passen. MP4 hat i.d.R. AAC als Audiocodec, WebM Opus oder das ältere Vorbis.
Die beste Kompatibilität mit allen Browsern wirst du erreichen mit H.264+AAC in MP4. (Halt solange das H.264 Video nicht 10bit Farbtiefe hat... das kann quasi kein Player außer die guten Softwareplayer wie VLC, mpv u.s.w.)

Ferner möchte ich auch nochmal darauf hinweisen wie aberwitzig umständlich das ganze Unterfangen ist... du könntest das Video einmal auf dem Server (oder vor dem Upload) mit dem gleichen ffmpeg-Befehl konvertieren und hast dann die fertige, kompatible MP4 da liegen die du mit einem normalen <video> Tag einbinden kannst. Das ist die deutlich angenehmere Nutzererfahrung. Die Video-Konvertierung ist sehr rechenaufwändig und ffmpeg wird durch die WASM-Portierung nochmal langsamer. Wenn das Video 1000x geschaut wird ist der Stromverbrauch der 999 unnötigen Konvertierungen schon beachtlich...
 
Zuletzt bearbeitet:
Marco01_809 schrieb:
Die beste Kompatibilität mit allen Browsern wirst du erreichen mit H.264+AAC in MP4. (Halt solange das H.264 Video nicht 10bit Farbtiefe hat... das kann quasi kein Player außer die guten Softwareplayer wie VLC, mpv u.s.w.)

Moin, hast du dazu eine Idee? Wie kann ich die 10Bit-Farbtiefe loswerden? Das scheint momentan das Problem zu sein.
Ergänzung ()

Ist ja lustig, es funktioniert jetzt. 😊

Ich hab folgende Einstellungen verwendet: https://superuser.com/a/1381151

HTML:
<script>
  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++;
          }
        }, 200);
      });

    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>

Ich schaue am Computer und die CPU-Belastung ist schon enorm ...

1689915909485.png


Zudem bleibt es zurzeit bei Minute 3:30 stehen ...

@Marco01_809 Ich habe vielleicht 3-5 Besucher auf der Website. :D nicht 995 andere. ;)
 
Zuletzt bearbeitet:
Wenn ich das Abfrageintervall in Zeile 63 von 200ms auf 500ms erhöhe, dann läuft es flüssig bei mir, also bleibt net stehen. :)
 
Es funktioniert auch am Handy. :D Segement time 15s ist ganz gut und zum Beispiel ein 2-Stunden-Video sollte man vorher mit ffmpeg -i input.mkv -c copy -map 0 -segment_time 00:10:00 -f segment -reset_timestamps 1 output%03d.mkv in 10-Minuten-Teile aufteilen ...

Ich hab noch nicht herausgefunden, wieso, aber er lädt immer zuerst das komplette Video-File und decodiert es dann.

Thema gelöst, falls jemand das komplette Setup sehen möchte, einfach melden.
 
CyborgBeta schrieb:
Wie kann ich die 10Bit-Farbtiefe loswerden? Das scheint momentan das Problem zu sein.
Mit -pix_fmt yuv420p, CRF könnte auch noch etwas höher, und ich würde auch ein schnelleres Preset wählen, z.B. -preset veryfast order superfast. Dadurch wird noch mal Rechenleistung gespart, schlägt sich allerdings auf die Bitrate nieder.
 
  • Gefällt mir
Reaktionen: CyborgBeta
Zurück
Oben