Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recording does not allow seeking and does not store the duration #517

Open
LukasKalbertodt opened this issue Mar 26, 2020 · 3 comments
Open
Labels
crowd-funded Part of our crowdfunding compaigns

Comments

@LukasKalbertodt
Copy link
Member

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.

@LukasKalbertodt LukasKalbertodt added this to the Opencat 8.4 milestone Mar 26, 2020
@LukasKalbertodt LukasKalbertodt added the crowd-funded Part of our crowdfunding compaigns label Apr 6, 2020
@guest271314
Copy link

guest271314 commented Apr 12, 2020

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.

@JulianKniephoff JulianKniephoff self-assigned this Apr 28, 2020
@LukasKalbertodt LukasKalbertodt removed this from the Opencat 8.4 milestone May 6, 2020
@JulianKniephoff JulianKniephoff removed their assignment Apr 29, 2022
@LukasKalbertodt LukasKalbertodt changed the title Recording of Chrome and Edge does not allow seeking and does not store the duration Recording of Chrome, Edge & Safari does not allow seeking and does not store the duration May 30, 2022
@LukasKalbertodt LukasKalbertodt changed the title Recording of Chrome, Edge & Safari does not allow seeking and does not store the duration Recording does not allow seeking and does not store the duration May 30, 2022
@LukasKalbertodt
Copy link
Member Author

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.

@guest271314
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
crowd-funded Part of our crowdfunding compaigns
Projects
None yet
Development

No branches or pull requests

4 participants