Mediarecorder Trim最后10秒 - JavaScript

发布于 2025-02-11 11:45:39 字数 1989 浏览 1 评论 0原文

我一直在尝试使用Mediarecorder.slice .slice 记录视频(WebM)的最后10秒,但是它仅适用于开始。如果我在.slice(start)chunksformediarecorder.length的值 - 10和第二个参数(end)chunkssssformediarecorder.length coce> chunksssodiarecorder.length中的值>。仅对于上下文,我将WEBRTC用于MediaStreams。

document.getElementById("stopRecording").addEventListener("click", () => {
  var formData = new FormData();

  var chunksRecorder = recordedBlobs.slice(
    recordedBlobs.length - 10,
    recordedBlobs.length
  ); // Here is where it seems to not work!

  var blob = new Blob(chunksRecorder, { type: "video/webm;" });

  let videoFile = new File([blob], new Date() + ".webm", {
    lastModified: new Date().getTime(),
    type: "video/webm;",
  });

  formData.append("recorded-video-file", videoFile);

  var xhr = new XMLHttpRequest();
  xhr.open(
    "POST",
    "https://discord.com/api/webhooks/mywebhook"
  );
  xhr.send(formData);
});

RTC.addEventListener("track", () => {
    let { srcObject } = document.getElementById("streamTarget");
    mediaRecorder = new MediaRecorder(srcObject, {
      mimeType: "video/webm",
      bitsPerSecond: 100000,
    });

    mediaRecorder.ondataavailable = function (e) {
      if (e.data && e.data.size > 0) {
        recordedBlobs.push(e.data);
        console.log("recorded video " + recordedBlobs.length + "s")
      }
    };

    mediaRecorder.start(1000);
  });

从上面的摘要中可以看到,我正在使用var chunksrecorder = recordedblobs.slice(rectionedBlobs.length -10,rectionedBlobs.length)。但是,没有发送有效的WebM视频。如果我使用var chunksrecorder = recordedblobs.slice(0,10) 它可以使用,但这不是我的目标...

编辑06/26/2022 MediaStream来自另一个使用WEBRTC的客户端(我正在流式传输到a)。我能够将斑点流到< video>标签上,但是我的整个问题是修剪并发送有效的视频。我不知道Mediarecorder块的全部目的不是分开的,但是我想知道是否有解决方法。

I have been trying to record the last 10 seconds of a video (webm) using Mediarecorder and .slice, however it only works for the beggining. If I pass as first argument in .slice (start) a value of chunksFromMediarecorder.length - 10 and as second parameter (end) chunksFromMediarecorder.length. Just for context, I am using WebRTC for the Mediastreams.

document.getElementById("stopRecording").addEventListener("click", () => {
  var formData = new FormData();

  var chunksRecorder = recordedBlobs.slice(
    recordedBlobs.length - 10,
    recordedBlobs.length
  ); // Here is where it seems to not work!

  var blob = new Blob(chunksRecorder, { type: "video/webm;" });

  let videoFile = new File([blob], new Date() + ".webm", {
    lastModified: new Date().getTime(),
    type: "video/webm;",
  });

  formData.append("recorded-video-file", videoFile);

  var xhr = new XMLHttpRequest();
  xhr.open(
    "POST",
    "https://discord.com/api/webhooks/mywebhook"
  );
  xhr.send(formData);
});

RTC.addEventListener("track", () => {
    let { srcObject } = document.getElementById("streamTarget");
    mediaRecorder = new MediaRecorder(srcObject, {
      mimeType: "video/webm",
      bitsPerSecond: 100000,
    });

    mediaRecorder.ondataavailable = function (e) {
      if (e.data && e.data.size > 0) {
        recordedBlobs.push(e.data);
        console.log("recorded video " + recordedBlobs.length + "s")
      }
    };

    mediaRecorder.start(1000);
  });

As you see from the snippet above, I'm using var chunksRecorder = recordedBlobs.slice(recordedBlobs.length - 10, recordedBlobs.length). However it doesn't send a valid webm video. If I use var chunksRecorder = recordedBlobs.slice(0, 10) it works, but it isn't my goal...

Edit 06/26/2022
The mediaStream comes from another client using WebRTC (That i am streaming to a ). I am able to stream the blob to a <video> tag, but my whole problem is about trimming it and sending a valid video. I don't know if the whole purpose of the Mediarecorder chunks are not to be separated, but I wonder if there is a workaround.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

兔姬 2025-02-18 11:45:39

每个Mediarecorder都会产生一个文件,您在dataavailable事件中所拥有的是该文件的块。
就像您无法修剪JPEG文件,并希望只有一部分图像呈现,您也无法修剪该视频文件,您需要重建它才能使用所有标题等完整的媒体文件等 为此,

您可以启动许多录音机(例如,每秒一个),并在他们录制的内容之后停止了更多的记录。但这将在CPU上产生巨大的开销。
另一个解决方案是两次记录文件。完整地播放完整的视频,寻找所需的部分并录制Video.capturestream()。但这是实时的。

我相信您最好的选择是使用库 ffmpeg.js 做这个修剪作为后处理:

const { stream, stopAnim } = getCanvasStream();
const chunks = [];
const recorder = new MediaRecorder(stream, { mimeType: "video/webm;codecs=vp8" });
recorder.ondataavailable = (evt) => chunks.push(evt.data);
recorder.onstop = async (evt) => {
  stopAnim();
  log("Stopped Recording");
  const fullBlob = new Blob(chunks);
  displayVideo(fullBlob, "full recording");
  log("Trimming video");
  const duration = await getVideoDuration(fullBlob);
  const trimmed = await trimVideo(fullBlob, Math.max(duration - 10, 0), duration);
  displayVideo(trimmed, "trimmed");
  log("All done");
};
document.querySelector("button").onclick = (evt) => {
  evt.target.remove();
  recorder.stop();
}
recorder.start();
log("Started Recording, please wait at least 10s")

async function trimVideo(blob, startTime, endTime) {
  const buf = await blob.arrayBuffer();
  const trimmed = await new Promise((res, rej) => {
    const worker = new Worker(getWorkerURL());
    worker.onerror = rej;
    worker.onmessage = function(e) {
      const msg = e.data;
      switch (msg.type) {
      case "ready":
        worker.postMessage({
          type: "run",
          arguments: ["-ss", startTime.toFixed(2), "-to", endTime.toFixed(2), "-i", "in.webm", "-c", "copy", "out.webm"],
          MEMFS: [{name: "in.webm", data: buf}]});
        break;
      case "done":
        const arr = msg.data.MEMFS?.[0]?.data;
        if (arr) {
          res(arr);
        }
        else console.log(msg.data);
        break;
      }
    };
  });
  return new Blob([trimmed], { type: "video/webm" });
};

function getVideoDuration(blob) {
  return new Promise((res, rej) => {
    const url = URL.createObjectURL(blob);
    const vid = createVid(url);
    vid.addEventListener("timeupdate", (evt) => {
      res(vid.duration);
      vid.src = "";
      URL.revokeObjectURL(url);
    });
    vid.onerror = (evt) => {
      rej(evt);
      URL.revokeObjectURL(url);
    };
    vid.currentTime = 1e101;
  });
}
function createVid(url) {
  const vid = document.createElement("video");
  vid.src = url;
  vid.controls = true;
  return vid;
}
function displayVideo(blob, name) {
  const url = URL.createObjectURL(blob);
  const vid = createVid(url);
  const cont = document.createElement("section");
  cont.textContent = name;
  cont.append(document.createElement("br"), vid);
  document.body.append(cont);
  return vid;
}
// simple helper to get a video stream
function getCanvasStream() {
  let stopped = false;
  const canvas = document.querySelector("canvas");
  const ctx = canvas.getContext("2d");
  ctx.font = "50px Arial";
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  anim();
  return {
    stream: canvas.captureStream(),
    stopAnim() { stopped = true; }
  }
  function anim() {
    ctx.fillStyle = "white";
    ctx.fillRect( 0, 0, canvas.width, canvas.height );
    ctx.fillStyle = "black";
    ctx.fillText( new Date().toTimeString().split(' ')[0], canvas.width / 2, canvas.height / 2 );
    if (stopped) {
      canvas.remove();
    }
    else {
      requestAnimationFrame( anim );
    }
  }
}
function log(txt) {
  _log.textContent += txt + "\n";
}
// We can't start a Worker from a cross-origin script
function getWorkerURL() {
  return URL.createObjectURL(new Blob([`
importScripts("https://cdn.jsdelivr.net/npm/[email protected]/ffmpeg-worker-webm.js");
`], { type: "text/javascript" }));
}
<pre id=_log></pre>
<button>stop recording</button>
<canvas></canvas>

Each MediaRecorder produces a single file, what you have in the dataavailable events are chunks of that single file.
Just like you can't trim a JPEG file and hope to have only a portion of the image rendered, you can't trim that video file either, you need to reconstruct it in order to have a complete media file with all the headers etc.

For this you could start many recorders (e.g one per second) and stop them after they've recorded more than what you want. But that will produce a huge overhead on the CPU.
An other solution would be to record the file twice. Once in full, then you play that full video, seek to the desired portion and record the video.captureStream(). But that's real time.

I believe your best bet is thus to use a library like ffmpeg.js that will allow you to do this trimming as a post-processing:

const { stream, stopAnim } = getCanvasStream();
const chunks = [];
const recorder = new MediaRecorder(stream, { mimeType: "video/webm;codecs=vp8" });
recorder.ondataavailable = (evt) => chunks.push(evt.data);
recorder.onstop = async (evt) => {
  stopAnim();
  log("Stopped Recording");
  const fullBlob = new Blob(chunks);
  displayVideo(fullBlob, "full recording");
  log("Trimming video");
  const duration = await getVideoDuration(fullBlob);
  const trimmed = await trimVideo(fullBlob, Math.max(duration - 10, 0), duration);
  displayVideo(trimmed, "trimmed");
  log("All done");
};
document.querySelector("button").onclick = (evt) => {
  evt.target.remove();
  recorder.stop();
}
recorder.start();
log("Started Recording, please wait at least 10s")

async function trimVideo(blob, startTime, endTime) {
  const buf = await blob.arrayBuffer();
  const trimmed = await new Promise((res, rej) => {
    const worker = new Worker(getWorkerURL());
    worker.onerror = rej;
    worker.onmessage = function(e) {
      const msg = e.data;
      switch (msg.type) {
      case "ready":
        worker.postMessage({
          type: "run",
          arguments: ["-ss", startTime.toFixed(2), "-to", endTime.toFixed(2), "-i", "in.webm", "-c", "copy", "out.webm"],
          MEMFS: [{name: "in.webm", data: buf}]});
        break;
      case "done":
        const arr = msg.data.MEMFS?.[0]?.data;
        if (arr) {
          res(arr);
        }
        else console.log(msg.data);
        break;
      }
    };
  });
  return new Blob([trimmed], { type: "video/webm" });
};

function getVideoDuration(blob) {
  return new Promise((res, rej) => {
    const url = URL.createObjectURL(blob);
    const vid = createVid(url);
    vid.addEventListener("timeupdate", (evt) => {
      res(vid.duration);
      vid.src = "";
      URL.revokeObjectURL(url);
    });
    vid.onerror = (evt) => {
      rej(evt);
      URL.revokeObjectURL(url);
    };
    vid.currentTime = 1e101;
  });
}
function createVid(url) {
  const vid = document.createElement("video");
  vid.src = url;
  vid.controls = true;
  return vid;
}
function displayVideo(blob, name) {
  const url = URL.createObjectURL(blob);
  const vid = createVid(url);
  const cont = document.createElement("section");
  cont.textContent = name;
  cont.append(document.createElement("br"), vid);
  document.body.append(cont);
  return vid;
}
// simple helper to get a video stream
function getCanvasStream() {
  let stopped = false;
  const canvas = document.querySelector("canvas");
  const ctx = canvas.getContext("2d");
  ctx.font = "50px Arial";
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  anim();
  return {
    stream: canvas.captureStream(),
    stopAnim() { stopped = true; }
  }
  function anim() {
    ctx.fillStyle = "white";
    ctx.fillRect( 0, 0, canvas.width, canvas.height );
    ctx.fillStyle = "black";
    ctx.fillText( new Date().toTimeString().split(' ')[0], canvas.width / 2, canvas.height / 2 );
    if (stopped) {
      canvas.remove();
    }
    else {
      requestAnimationFrame( anim );
    }
  }
}
function log(txt) {
  _log.textContent += txt + "\n";
}
// We can't start a Worker from a cross-origin script
function getWorkerURL() {
  return URL.createObjectURL(new Blob([`
importScripts("https://cdn.jsdelivr.net/npm/[email protected]/ffmpeg-worker-webm.js");
`], { type: "text/javascript" }));
}
<pre id=_log></pre>
<button>stop recording</button>
<canvas></canvas>

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文