简体   繁体   English

使用 MediaRecorder API(和 ffmpeg?)创建延时视频

[英]create a timelapse video using MediaRecorder API ( and ffmpeg? )

Summary概括

I have a version of my code already working on Chrome and Edge (Mac Windows and Android), but I need some fixes for it to work on IOS (Safari/Chrome).我的代码版本已经在 Chrome 和 Edge(Mac Windows 和 Android)上运行,但我需要一些修复才能在 IOS(Safari/Chrome)上运行。
My objective is to record around 25 minutes and download a timelapse version of the recording.我的目标是录制大约 25 分钟并下载录制的延时版本。
final product requirements:最终产品要求:

speed: 3fps
length: ~25s

(I need to record one frame every 20 seconds for 25 mins)

this.secondStream settings: this.secondStream设置:

this.secondStream = await navigator.mediaDevices.getUserMedia({
    audio: false,
    video: {width: 430, height: 430, facingMode: "user"}
});

My code for IOS so far:到目前为止,我的 IOS 代码:

        startIOSVideoRecording: function() {
            console.log("setting up recorder");
            var self = this;
            this.data = [];

            if (MediaRecorder.isTypeSupported('video/mp4')) {
                // IOS does not support webm, so I will be using mp4
                var options = {mimeType: 'video/mp4', videoBitsPerSecond : 1000000};
            } else {
                console.log("ERROR: mp4 is not supported, trying to default to webm");
                var options = {mimeType: 'video/webm'};
            }
            console.log("options settings:");
            console.log(options);

            this.recorder = new MediaRecorder(this.secondStream, options);

            this.recorder.ondataavailable = function(evt) {
                if (evt.data && evt.data.size > 0) {
                    self.data.push(evt.data);
                    console.log('chunk size: ' + evt.data.size);
                }
            }

            this.recorder.onstop = function(evt) {
                console.log('recorder stopping');
                var blob = new Blob(self.data, {type: "video/mp4"});
                self.download(blob, "mp4");
                self.sendMail(videoBlob);
            }

            console.log("finished setup, starting")
            this.recorder.start(1200);

            function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms));}

            async function looper() {
                // I am trying to pick one second every 20 more or less
                await sleep(500);
                self.recorder.pause();
                await sleep(18000);
                self.recorder.resume();
                looper();
            }
            looper();
        },

Issues问题

Only one call to getUserMedia()只有一次调用 getUserMedia()

I am already using this.secondstream elsewhere, and I need the settings to stay as they are for the other functionality.我已经在其他地方使用this.secondstream ,我需要这些设置保持原样用于其他功能。
On Chrome and Edge, I could just call getUserMedia() again with different settings, and the issue would be solved, but on IOS calling getUserMedia() a second time kills the first stream.在 Chrome 和 Edge 上,我可以使用不同的设置再次调用getUserMedia() ,问题就会得到解决,但在 IOS 上,第二次调用getUserMedia()会杀死第一个 stream。
The settings that I was planning to use (works for Chrome and Edge):我计划使用的设置(适用于 Chrome 和 Edge):

navigator.mediaDevices.getUserMedia({
    audio: false,
    video: { 
        width: 360, height: 240, facingMode: "user", 
        frameRate: { min:0, ideal: 0.05, max:0.1 } 
    },
}

The timelapse library I am using does not support mp4 (ffmpeg as alternative?)我使用的延时库不支持 mp4(ffmpeg 作为替代方案?)

I am forced to use mp4 on IOS apparently, but this does not allow me to use the library I was relying on so I need an alternative.我显然被迫在 IOS 上使用 mp4,但这不允许我使用我所依赖的库,所以我需要一个替代方案。
I am thinking of using ffmpeg but cannot find any documentation to make it interact with the blob before the download.我正在考虑使用ffmpeg但在下载之前找不到任何文档使其与 blob 交互。
I do not want to edit the video after downloading it, but I want to be able to download the already edited version, so no terminal commands.我不想在下载后编辑视频,但我希望能够下载已经编辑的版本,所以没有终端命令。

MediaRecorder pause and resume are not ideal MediaRecorder 暂停和恢复并不理想

On Chrome and Edge I would keep one frame every 20 seconds by setting the frameRate to 0.05, but this does not seem to work on IOS for two reasons.在 Chrome 和 Edge 上,我会通过将 frameRate 设置为 0.05 每 20 秒保持一帧,但这似乎不适用于 IOS,原因有两个。
First one is related to the first issue of not being able to change the settings of getUserMedia() without destroying the initial stream in the first place.第一个与第一个问题有关,即在不破坏初始 stream 的情况下无法更改getUserMedia()的设置。
And even after changing the settings, It seems that setting the frame rate below 1 is not supported on IOS.即使更改设置后,IOS 似乎也不支持将帧速率设置为 1 以下。 Maybe I wrote something else wrong, but I was not able to open the downloaded file.也许我写了其他错误,但我无法打开下载的文件。
Therefore I tried relying on pausing and resuming the MediaRecorder, but this brings forth another two issues:因此,我尝试依靠暂停和恢复 MediaRecorder,但这带来了另外两个问题:
I am currently saving 1 second every 20 seconds and not 1 frame every 20 seconds, and I cannot find any workarounds.我目前每 20 秒节省 1 秒,而不是每 20 秒 1 帧,我找不到任何解决方法。
Pause and Resume take a little bit of time, making the code unreliable, as I sometimes pick 2/20 seconds instead of 1/20, and I have no reliability that the loop is actually running every 20 seconds (might be 18 might be 25).暂停和恢复需要一点时间,使代码不可靠,因为我有时会选择 2/20 秒而不是 1/20,而且我无法确定循环实际上每 20 秒运行一次(可能是 18 可能是 25 )。

My working code for other platforms我在其他平台上的工作代码

This is my code for the other platforms, hope it helps!这是我在其他平台上的代码,希望对您有所帮助!
Quick note: you will need to give it a bit of time between setup and start.快速说明:您需要在设置和启动之间给它一点时间。
The timelapse library is here延时摄影库在这里


        setupVideoRecording: function() {
            let video  = { 
                width: 360, height: 240, facingMode: "user", 
                frameRate: { min:0, ideal: 0.05, max:0.1 } 
            };
            navigator.mediaDevices.getUserMedia({
                audio: false,
                video: video,
            }).then((stream) => {
                // this is a video element
                const recVideo = document.getElementById('self-recorder');
                recVideo.muted = true;
                recVideo.autoplay = true;
                recVideo.srcObject = stream;
                recVideo.play();
            });
        },

        startVideoRecording: function() {
            console.log("setting up recorder");
            var self = this;
            this.data = [];

            var video = document.getElementById('self-recorder');

            if (MediaRecorder.isTypeSupported('video/webm; codecs=vp9')) {
                var options = {mimeType: 'video/webm; codecs=vp9'};
            } else  if (MediaRecorder.isTypeSupported('video/webm')) {
                var options = {mimeType: 'video/webm'};
            }
            console.log("options settings:");
            console.log(options);

            this.recorder = new MediaRecorder(video.captureStream(), options);

            this.recorder.ondataavailable = function(evt) {
                self.data.push(evt.data);
                console.log('chunk size: ' + evt.data.size);
            }

            this.recorder.onstop = function(evt) {
                console.log('recorder stopping');
                timelapse(self.data, 3, function(blob) {
                    self.download(blob, "webm");
                });
            }

            console.log("finished setup, starting");
            this.recorder.start(40000);
        }

I found the solution!我找到了解决方案!

First half上半场

1) Only one call to getUserMedia()
3) MediaRecorder pause and resume are not ideal

So the main Problem these two points are causing is that I am unable to change the frame rate for the recording.所以这两点导致的主要问题是我无法更改录制的帧速率。 This was somehow manageable with the use of a <canvas> .这可以通过使用<canvas>以某种方式进行管理。

// the rest of the code

// self-ios-recorder is a canvas element!
// here I can set the frame rate!
let stream = document.getElementById('self-ios-recorder').captureStream(0.05);
this.recorder = new MediaRecorder(stream, options);

// the rest of the code

this.recorder.start(40000);
this.recorder.pause();

ctxrec = document.getElementById('self-ios-recorder').getContext('2d');
async function draw_2DRecorder(){
    ctxrec.clearRect(0,0, 400, 400);
    ctxrec.drawImage(self.video, 0, 0, 400, 400);
    requestAnimationFrame(draw_2DRecorder);
}
draw_2DRecorder();

This allows me to change the frame rate to 0.05 fps as intended, and also change the size by manipulating the canvas (although the video might get stretched).这使我可以按预期将帧速率更改为 0.05 fps,还可以通过操作 canvas 来更改大小(尽管视频可能会被拉伸)。

Second half下半场

2) The timelapse library I am using does not support mp4

This still leaves the biggest issue: How do we change the frame rate from 0.05 to 3 if we cannot use the timelapse library?这仍然留下了最大的问题:如果我们不能使用延时库,我们如何将帧率从 0.05 更改为 3?
Well, we will have to use the pause and resume that we had just managed to avoid.... However this time we will be merging the pause and resume together with the canvas frame function, so that it can only catch one frame.好吧,我们将不得不使用我们刚刚设法避免的暂停和恢复....但是这次我们将暂停和恢复与 canvas 帧 function 合并在一起,因此它只能捕获一帧。
The overall code will be looking like this:整体代码将如下所示:

// the rest of the code

// give the "fake" frame rate of 3
let stream = document.getElementById('self-ios-recorder').captureStream(3);
this.recorder = new MediaRecorder(stream, options);

// the rest of the code

this.recorder.start(1000);
this.recorder.pause();

ctxrec = document.getElementById('self-ios-recorder').getContext('2d');
async function draw_2DRecorder(){
    ctxrec.clearRect(0,0, 400, 400);
    ctxrec.drawImage(self.video, 0, 0, 400, 400);
    self.recorder.resume();
    // acts like the fps is 3
    setTimeout(function(){
        self.recorder.pause();
    }, 333);
    // actually updates the frame only every 20 seconds
    setTimeout(function(){
        requestAnimationFrame(draw_2DRecorder);
    }, 20000);
}
draw_2DRecorder();

This is still not perfect, as my previous working solution (for Mac Windows and Android) would have a final product of perfectly 1 minute -> 1 second .这仍然不是完美的,因为我之前的工作解决方案(适用于 Mac Windows 和 Android)的最终结果是完美的1 minute -> 1 second seconds 。 This code will however output a file of about 30~35 seconds for a 25 minutes video, which can probably be adjusted by changing the length of the first timeout.但是,此代码将为 25 分钟的视频 output 一个大约 30~35 秒的文件,这可能可以通过更改第一次超时的长度来调整。 But it will never be consistent .但它永远不会是一致的。

    setTimeout(function(){
        self.recorder.pause();
    }, /* ADJUST HERE */);

I came up this solution after looking at this thread看了这个帖子后我想出了这个解决方案

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM