elan-ev / opencast-studio

Web-based recording studio for Opencast

Home Page:https://studio.opencast.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Recording does not allow seeking and does not store the duration

LukasKalbertodt opened this issue · comments

On Chrome or Edge (which is Chrome now) the recorded video is not well packaged. The container does not even know the duration and does not allow seeking. This is pretty annoying. On the Opencast side we had to adjust workflows... those work now. But if the user downloads the video or wants to skip around in the preview step, they are limited.

This was reported as bug to Chromium, but unfortunately was closed as "won't fix":

This issue was discussed in the Spec [1] and we were of the opinion that
a polyfill/complementary solution would be enough. Concretely, ts-ebml [2]
can be used to (re)construct the Cues/SeekHeads both live and as a post-
processing step. All this, of course, notwithstanding solutions or
workarounds applied to the playback (such as e.g. #23/24). In light of
this, I'm marking this issue as WontFix.

[1] w3c/mediacapture-record#119
[2] https://www.npmjs.com/package/ts-ebml

I found these two StackOverflow posts discussing this problem:

There seem to be a few workarounds, most notably using ts-ebml.

We should investigate that and probably also use a workaround. In particular, if we want to solve #454 with a simple cutting UI, we really need to fix the seeking issue first.

This plnkr (see file ts-ebml-min.js) https://plnkr.co/edit/dTEO4HepKOY3xwqBemm5?p=preview&preview includes a minimized version of ts-ebml and the code to set duration of the WebM file, in brief

  <script src="ts-ebml-min.js"></script>
  <script>
    const {
      Decoder, Encoder, tools, Reader
    } = require("ts-ebml");
    const readAsArrayBuffer = function(blob) {
      return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.readAsArrayBuffer(blob);
        reader.onloadend = () => {
          resolve(reader.result);
        };
        reader.onerror = (ev) => {
          reject(ev.error);
        };
      });
    }

    const injectMetadata = function(blob) {
      const decoder = new Decoder();
      const reader = new Reader();
      reader.logging = false;
      reader.drop_default_duration = false;

      return readAsArrayBuffer(blob).then((buffer) => {
        const elms = decoder.decode(buffer);
        elms.forEach((elm) => {
          reader.read(elm);
        });
        reader.stop();

        const refinedMetadataBuf = tools.makeMetadataSeekable(
          reader.metadatas, reader.duration, reader.cues);
        const body = buffer.slice(reader.metadataSize);

        const result = new Blob([refinedMetadataBuf, body], {
          type: blob.type
        });

        return result;
      });
    }
  </script>

at dataavailable event

recorder.addEventListener("dataavailable", async(e) => {
  try {
    const makeMediaRecorderBlobSeekable = await injectMetadata(e.data);
    chunks.push(await new Response(makeMediaRecorderBlobSeekable).arrayBuffer());
  } catch (e) {
    console.error(e);
    console.trace();
   }
});

Alternatively, the video output by Chromium MediaRecorder can be re-recorded at Firefox or Nightly; or ffmpeg or mkvtoolnix can be used to set duration of the file

$ mkvmerge -o output.webm --enable-durations input.webm

Re

The recording happens completely in the user's browser: no server is involved in that part.

Native Messaging can be utilized to run mkvmerge natively then get result in JavaScript in the browser, e.g., see https://github.com/guest271314/native-messaging-mkvmerge.

So apparently by now all browsers exhibit this problem as the specs do not allow to creating seekable files. The header data of the webm file is given to our JS code very early and the spec says that it won't be changed anymore. So by definition, the duration (or seek data) cannot be written into the header afterwards anymore. Relevant issue: w3c/mediacapture-record#119

The library webm-duration-fix might be a good and easy solution.

The library webm-duration-fix might be a good and easy solution.

I use ts-ebml minified as an Ecmancript module https://github.com/guest271314/captureSystemAudio/blob/master/native_messaging/capture_system_audio/ts-ebml.min.js

    const {
        Decoder,
        Encoder,
        tools,
        Reader,
        injectMetadata,
      } = await import(`${this.src.origin}/ts-ebml.min.js`);
      Object.assign(this, {
        Decoder,
        Encoder,
        tools,
        Reader,
        injectMetadata,
      });


   if (this.mimeType.includes('opus')) {
      this.recorder = new MediaRecorder(this.mediaStream, {
        audioBitrateMode: 'constant',
      });
      this.recorder.onstop = async (e) => {};
      this.recorder.ondataavailable = async ({ data }) => {
        this.resolve(this.injectMetadata(data));
      };
    }