当前位置: 首页 > 知识库问答 >
问题:

如何使用流将MediaCorder Web API输出保存到磁盘

靳睿
2023-03-14

我正在Electronic(因此是node.js)中试验纵隔流记录API,希望将输出作为流处理。作为流处理将允许我在保存到磁盘之前处理MediaCorder输出-例如,我可以加密它。对于我的特定用例,我只关心音频,所以我没有任何视频元素记录。

下面是一个突出我所尝试的项目。中的关键代码是record.js,在save()函数中。

最终,我将尝试创建一个合适的readstream来插入使用常量writeStream=fs.createwritestream(fPath)创建的writeStream使用readstream.pipe(writeStream)

总结起来,我尝试了以下几种:

无法将blob转换为readstream,只能将readablestreamreadablestreamdefaultreaderuint8array转换为readstream

2.blob文件(在内存中),然后使用fs.createeadstream()

我似乎不能在fs.createReadStream(url)中使用objecturl,它坚持要附加一个本地路径。对此问题的回答表明这是fs.createReadStream()的限制,在我的情况下使用http.get()request()不合适,因为我不是试图访问远程资源。

3.blobbuffer,然后使用fs.createeadstream()

我无法将blob转换为可在fs.createReadStream(buffer)中使用的buffer中使用的buffer只能转换为arraybuffer或具有null字节

非常感谢任何帮助!

项目:

节点12.13.0、铬80.0.3987.158和电子8.2.0。

    null
{
  "name": "mediarecorderapi",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "electron ."
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "electron": "^8.2.0"
  }
}
const { app, BrowserWindow, ipcMain } = require('electron');

function createWindow () {
  // Create the browser window.
  let win = new BrowserWindow({
    width: 1000,
    height: 800,
    x:0,
    y:0,
    title: "Media Recorder Example",
    webPreferences: {
      nodeIntegration: true,
      devTools: true
    } 
  })
  win.openDevTools();
  win.loadFile('index.html')
}

app.whenReady().then(createWindow)
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
    <!-- https://electronjs.org/docs/tutorial/security#csp-meta-tag -->
    <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
  </head>
  <body>
    <h1>Hello World!</h1>
    We are using node <script>document.write(process.versions.node)</script>,
    Chrome <script>document.write(process.versions.chrome)</script>,
    and Electron <script>document.write(process.versions.electron)</script>.
    <br/><br/>
    <div>
      <button id="button_rec">Record</button>
      <p>recorder state: <span id="rec_status">inactive</span></p>
    </div>
  </body>

  <script src="record.js"></script>

</html>

record.js:

console.log("hello world from record.js()");

const remote = require('electron').remote;
const path = require('path');
const fs = require('fs');
const appDir = remote.app.getPath('userData');

var recButton = document.getElementById("button_rec");
var recStatusSpan = document.getElementById("rec_status");
var recorder;

init = async function () {
    // html page event handlers:
    recButton.addEventListener("click", () => {record()});

    // SET UP MEDIA RECORDER:
    var audioStream = await navigator.mediaDevices.getUserMedia({audio: true});
    recorder = new MediaRecorder(audioStream, {mimeType: 'audio/webm'});
    chunks = [];
    recorder.onstart = (event) => {
        // ...
    }
    recorder.ondataavailable = (event) => {       
        chunks.push(event.data);
    }
    recorder.onstop = async (event) => {
        let fileName = `audiofile_${Date.now().toString()}.webm`;
        // download(chunks, fileName); // <== This works at downloading the file to disk, but this is not a stream. Use to prove that audio is being recorded and that it can be saved.
        save(chunks, fileName);     // <== Trying to save using a stream 
        chunks = [];
    }
}

record = function() {
    if(recorder.state == "inactive"){
        recorder.start();
        recButton.innerHTML = "Stop Recording";
    } else {
        recorder.stop();
        recButton.innerHTML = "Record";
    }
    recStatusSpan.innerHTML = recorder.state;
}

download = function (audioToSave, fName) {  
    let audioBlob = new Blob(audioToSave, {
      type: "audio/webm"
    });
    let url = URL.createObjectURL(audioBlob);
    let a = document.createElement("a");
    a.style = "display: none";
    a.href = url;
    document.body.appendChild(a);
    a.download = fName;
    a.click();

    // release / remove
    window.URL.revokeObjectURL(url);
    document.body.removeChild(a);
}

save = async function (audioToSave, fName){
    let fPath = path.join(appDir, fName);
    console.log(`Tring to save to: ${fPath}`);

    // create the writeStream - this line creates the 0kb file, ready to be written to
    const writeStream = fs.createWriteStream(`${fPath}`);
    console.log(writeStream); // :) WriteStream {...}

    // The following lines are ultimately trying to get to a suitable readStream to pipe into the writeStream using readStream.pipe(writeStream):
    // Multiple attempts written out - uncomment the method you are trying...

    // The incoming data 'audioToSave' is an array containing a single blob of data.
    console.log(audioToSave); // [Blob]
    
    // ================
    // METHOD 1: Stream a Blob:
    // Issue: I cannot find a method to convert a Blob to a "readStream"
    // ================

    // Lets convert the data to a Blob
    var audioBlob = new Blob(audioToSave, {
        type: "audio/webm"
    });
    console.log(audioBlob); // Blob {size: 9876, type: "audio/webm"}
    // And lets convert the Blob to a Stream
    var audioBlobReadableStream  = audioBlob.stream();  // https://developer.mozilla.org/en-US/docs/Web/API/Blob/stream
    console.log(audioBlobReadableStream ); // ReadableStream {locked: false}
    // audioBlobReadableStream.pipe(writeStream);       // ERROR: Uncaught (in promise) TypeError: audioBlobReadableStream .pipe is not a function
    // audioBlobReadableStream.pipeTo(writeStream);     // ERROR: TypeError: Failed to execute 'pipeTo' on 'audioBlobReadableStream': Illegal invocation

    // converting the ReadableStream into a ReadableStreamDefaultReader:
    var audioBlobReadableStreamDefaultReader  = await audioBlobReadableStream.getReader();
    console.log(audioBlobReadableStreamDefaultReader) // ReadableStreamDefaultReader {closed: Promise}
    // audioBlobReadableStreamDefaultReader.pipe(writeStream);      // ERROR: TypeError: audioBlobReadableStreamDefaultReader.pipe is not a function
    // audioBlobReadableStreamDefaultReader.pipeTo(writeStream);    // ERROR: TypeError: audioBlobReadableStreamDefaultReader.pipeTo is not a function

    // And read the reader:
    var audioBlobReadStream = await audioBlobReadableStreamDefaultReader.read();
    console.log(audioBlobReadStream); // {value: Uint8Array(9876), done: false}
    // audioBlobReadStream.pipe(writeStream);       // ERROR: TypeError: audioBlobReadStream.pipe is not a function
    // audioBlobReadStream.pipeTo(writeStream);     // ERROR: TypeError: audioBlobReadStream.pipeTo is not a function

    // ================
    // METHOD 2: Blob to file, use fs
    // Note, fs.createReadStream() requires a string, Buffer, or URL
    // Issue: I cannot convert a Blob to a file i can access with fs without downloading it
    // ================
    // // Or convert to a file (to try to help fs.read)
    var audioFile = new File([audioBlob], "audioFileName", { type: 'audio/webm' });
    console.log(audioFile); // File {...}

    // ====
    // a: url
    // Issue: fs.createReadStream(url) adds a local path to the objectURL created, and this local path obviously doesn't exist
    // ====
    var url = URL.createObjectURL(audioFile);   
    console.log(url); // blob:file:///{GUID}
    const fileReadStream = fs.createReadStream(url); // ERROR: events.js:187  ENOENT: no such file or directory, open 'C:\... [Local Path] ...\blob:file:\19428f7d-768a-4eff-b551-4068daa8ceb6'
    console.log(fileReadStream); // ReadStream {... path: "blob:file:///{GUID}" ...}
    // fileReadStream.pipe(writeStream); 
    
    // ====
    // b: buffer
    // Issue: I cannot convert a blob to a buffer that I can insert into fs.createReadStream(buffer)
    // ====
    var audioArrayBuffer = await audioBlob.arrayBuffer();
    console.log(audioArrayBuffer); // ArrayBuffer(9876)
    // bufferReadStream = fs.createReadStream(audioArrayBuffer); // ERROR: TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be one of type string, Buffer, or URL. Received type object 
    let audioBuffer = toBuffer(audioArrayBuffer)
    console.log(audioBuffer);
    let bufferReadStream = fs.createReadStream(audioBuffer); // ERROR: TypeError [ERR_INVALID_ARG_VALUE]: The argument 'path' must be a string or Uint8Array without null bytes. Received <Buffer 1a 45 ...
    
    function toBuffer(ab) {
        // FROM: https://stackoverflow.com/questions/8609289/convert-a-binary-nodejs-buffer-to-javascript-arraybuffer
        var buf = Buffer.alloc(ab.byteLength);
        var view = new Uint8Array(ab);
        for (var i = 0; i < buf.length; ++i) {
            buf[i] = view[i];
        }
        return buf;
    }
}


init();

运行以下操作:

npm install -D electron
npm start

共有1个答案

梅安平
2023-03-14

好的,我破解了……最终,挑战的关键是:

如何在node.js中将blob转换为readablestream

总之,总而言之,我发现工作的步骤是:blob>arraybuffer>array>buffer>readstream

let { Readable } = require('stream') ;

function bufferToStream(buffer) {
    let stream = new Readable ();
    stream.push(buffer);
    stream.push(null);
    return stream;
}
save = async function (audioToSave, fPath) {
    console.log(`Trying to save to: ${fPath}`);

    // create the writeStream - this line creates the 0kb file, ready to be written to
    const writeStream = fs.createWriteStream(fPath);
    console.log(writeStream); // WriteStream {...}

    // The incoming data 'audioToSave' is an array containing a single blob of data.
    console.log(audioToSave); // [Blob]

    // Lets convert the data to a Blob
    var audioBlob = new Blob(audioToSave, {
        type: "audio/webm"
    });
    console.log(audioBlob); // Blob {size: 17955, type: "audio/webm"}
    // note: audioBlob = audio[0] has same effect

    // now we go through the following process: blob > arrayBuffer > array > buffer > readStream:
    const arrayBuffer = await audioBlob.arrayBuffer();
    console.log(arrayBuffer); // ArrayBuffer(17955) {}

    const array = new Uint8Array(arrayBuffer);
    console.log(array); // Uint8Array(17955) [26, 69, ... ]

    const buffer = Buffer.from(array);
    console.log(buffer); // Buffer(17955) [26, 69, ... ]

    let readStream = bufferToStream(buffer);
    console.log(readStream); // Readable {_readableState: ReadableState, readable: true, ... }

    // and now we can pipe:
    readStream.pipe(writeStream);

}

我终于可以管道并可以继续在数据和保存之间使用其他流函数,例如,加密。:)

希望这也能帮助别人。

 类似资料:
  • 问题内容: 几天前,我被要求检查从互联网下载视频时播放视频的难度。我知道这很容易,因为有人在不久前告诉我。所以,我检查了一下,这非常容易。 问题是我想将视频保存到磁盘上,而不是强迫用户一次又一次地下载它。 问题是访问缓冲区并将其存储到磁盘。 Stackoverflow中的许多答案都说不可能。特别是视频。 我播放视频的原始代码: 问题答案: 解决此问题的方法是使用和: 第一步是添加一条通知,以了解视

  • 问题内容: 我已经使用JavaScript 将源内容从html标记转换为base64String。图像显示清晰。现在,我想使用javascript将该图像保存到用户磁盘。 当我将图像路径设置为html标签的源代码时,此代码效果很好。但是,当我将source传递为base64String时不起作用。 如何实现我想要的? 问题答案: 仅允许用户下载图像或其他文件,您可以使用HTML5 属性。 静态文件

  • 问题内容: 我尝试使用,但它并没有涉及整个结构和层次结构。 另一方面这样做,但我无法保存。 在输出中,我可以将所有子项一个一个地展开,然后选择并复制/粘贴,但是结构非常重要。 问题答案: 更新: 您现在可以右键单击 在“控制台”面板中右键单击>另存为,将记录的消息保存到文件中。 原始答案: 您可以使用下面显示的devtools片段创建console.save方法。它从输入中创建一个FileBlob

  • 问题内容: 当前不支持在file.contents为流时向多个目标发送消息。解决此问题的方法是什么? 问题答案: 当前,在将file.contents用作流时,每个目标必须使用两个流。将来可能会解决此问题。 编辑:此错误现已在gulp中修复。您原始帖子中的代码应该可以正常工作。

  • 我试图深入理解火花洗牌过程。当我开始阅读时,我发现了以下一点。 Spark在完成后将映射任务(ShuffleMapTask)输出直接写入磁盘。 如果有很多小的中间文件作为输出,spark如何处理网络和I/O瓶颈?

  • 问题内容: 我想将此命令保存到另一个文本:awk’{print $ 2}’,它从文本中提取出来。现在我想将输出另存为文本。谢谢 问题答案: =>这将重定向到文件。如果文件不存在,它将创建它。如果文件存在,它将清除(有效)内容并向其中写入新数据 =>这与上面的意思相同,但是如果文件存在,它将向其追加新数据。 例如: 或者,您可以使用命令进行重定向。该命令将重定向到指定的文件以及终端屏幕 有关外壳重定