opencast-studio icon indicating copy to clipboard operation
opencast-studio copied to clipboard

Recording does not allow seeking and does not store the duration

Open LukasKalbertodt opened this issue 6 years ago • 3 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] https://github.com/w3c/mediacapture-record/issues/119 [2] https://www.npmjs.com/package/ts-ebml

I found these two StackOverflow posts discussing this problem:

  • https://stackoverflow.com/q/50586612/2408867
  • https://stackoverflow.com/q/38443084/2408867

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.

LukasKalbertodt avatar Mar 26 '20 16:03 LukasKalbertodt

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.

guest271314 avatar Apr 12 '20 16:04 guest271314

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: https://github.com/w3c/mediacapture-record/issues/119

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

LukasKalbertodt avatar May 30 '22 14:05 LukasKalbertodt

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));
      };
    }

guest271314 avatar May 30 '22 14:05 guest271314