Chrome扩展程序captureStream

3
我正在开发一个Chrome扩展程序,它可以完成以下功能:
  • 获取从chrome.tabCapture.capture获得的流(暂时忽略音频捕捉,因为它与我面临的问题无关)

  • 将此tabStream传递给URL.createObjectURL(tabStream)

  • 使用生成的URL作为DOM视频元素videoEl.src = URL.createObjectURL(tabStream)src

  • 调用videoEl.play()并在调用canplay事件时

  • videoEl作为参数传递给画布的上下文drawImage方法

  • 由于现在视频帧被渲染到画布元素中,因此可以对这些帧执行许多有用的操作(裁剪、加水印等)

到目前为止,所有工作都进行得很完美。但是接下来的两个步骤不起作用:

  • 使用canvasStream = canvasEl.captureStream(20)从画布元素创建一个流

  • 将此流传递给MediaRecorderrecorder = new MediaRecorder(canvasStream))并开始录制:recorder.start()

本质上,如果在Chrome扩展程序的背景之外使用此方法(例如在这里:https://jsfiddle.net/williamwallacebrave/2rgv7pgj/7/),所有工作都进行得很完美。但是当在Chrome扩展程序的后台中使用时,我可以清楚地检测到视频帧被发送并渲染在画布元素中,但某种方式要么canvasEl.captureStream()没有推送数据,要么记录器无法捕获它们。同样,如果在内容脚本中使用该方法,一切都进行得很完美。但是在内容脚本中,我无法访问tabCapture流。

这是我的清单文件:

{
    "name": "super app",
    "manifest_version": 2,
    "description": "...",
    "version": "0.0.1",
    "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
    "page_action": {
        "default_title": "app",
        "default_icon": "static/images/logo.png"
    },
    "icons": {
        "128": "static/images/logo.png"
    },
    "background": {
        "page": "background.html"
    },
    "content_scripts": [
        {
            "matches": ["<all_urls>"],
            "exclude_matches": ["http://localhost:3000/*"],
            "css": [
                "static/css/style.css"
            ],
            "js": [
                "vendor/system.js",
                "vendor/jquery.min.js",
                "content/config.js",
                "content/index.js"
            ]
        }
    ],
    "web_accessible_resources": [
        "background/*",
        "vendor/*",
        "content/*",
        "common/*.js",
        "capturer.html",
        "static/*",
        "*"
    ],
    "externally_connectable": {
        "matches": [
            "http://localhost:3000/*"
        ]
    },
    "permissions": [
        "tabs",
        "activeTab",
        "<all_urls>",
        "clipboardRead",
        "clipboardWrite",
        "tabCapture",
        "notifications",
        "tts"
    ]
}

以下是模拟代码,作为内容脚本运行正常,但作为后台脚本则无法正常工作:

// SOURCE: https://dev59.com/hlkS5IYBdhLWcg3w6qMz
var cStream,
    aStream,
    recorder,
    chunks = [],
    canvasEl = document.createElement('canvas');

canvasEl.width = 400;
canvasEl.height = 400;
document.body.appendChild(canvasEl);

/*
   create and run external video
*/
var videoEl = document.createElement('video');
videoEl.crossOrigin = 'anonymous';
videoEl.src = 'https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4';
videoEl.play();
videoEl.addEventListener('play', function(){
    var audioCtx = new AudioContext();
    var dest = audioCtx.createMediaStreamDestination();

    aStream = dest.stream;
    var sourceNode = audioCtx.createMediaElementSource(this);
    console.log('connected audio');
    sourceNode.connect(dest);

    // output to our headphones  
    sourceNode.connect(audioCtx.destination)

    var canvasCtx = canvasEl.getContext('2d');
    console.log('play video in canvas');
    draw(this, canvasCtx);

    startRecording();
    setTimeout(() => {
        stopRecording();
    }, 10000)      

}, false);


function exportStream(e) {
    console.log('exportStream', chunks.length);
    if (chunks.length) {
        var blob = new Blob(chunks),
            videoURL = URL.createObjectURL(blob),
            resultVideoEl = document.createElement('video');

        resultVideoEl.controls = true;
        resultVideoEl.src = videoURL;
        resultVideoEl.onend = function() {
            URL.revokeObjectURL(videoURL);
        }
        document.body.insertBefore(resultVideoEl, canvasEl);

    } else {
        document.body.insertBefore(
            document.createTextNode('no data saved'), canvasEl);
    }
}


function saveChunks(e) {
    console.log('save chunks', e.data.size);
    e.data.size && chunks.push(e.data);
}


function stopRecording() {
    console.log('STOP RECORDING');
    videoEl.pause();
    recorder.stop();
}


function startRecording() {
    console.log('START RECORDING');
    cStream = canvasEl.captureStream(30);
    cStream.addTrack(aStream.getAudioTracks()[0]);

    recorder = new MediaRecorder(cStream);
    recorder.start();

    // =============================================
    // THIS PART IS NOT FIRED WHEN RUN IN BACKGROUND
    // and final chunks is always an empty array. 
    // =============================================
    recorder.ondataavailable = saveChunks;
    recorder.onstop = exportStream;
}


function draw(v,ctx) {
    if(videoEl.paused || videoEl.ended) return false;

    // here I'm cropping the video frames and taking only 400 by 400
    // square shifted by 100, 100 vector
    ctx.drawImage(v, 100, 100, 400, 400, 0, 0, 400,400);
    setTimeout(draw,20,v,ctx);
}

请注意,此captureStream和MediaRecorder相对较新,因此您需要Chrome 51+才能运行该示例。


你能在问题中包含 manifest.json 吗?在 document.body.insertBefore(document.createTextNode('no data saved'), canvas); 中出现了 Uncaught ReferenceError: canvas is not defined 错误;在 play() 请求被 pause() 调用打断时,出现了 Uncaught (in promise) DOMException - guest271314
请将问题[编辑]为主题:包括复制问题的完整[mcve]。通常,包括一个manifest.json、一些背景内容脚本。寻求调试帮助的问题(“为什么代码不起作用?”)必须包括:►所需的行为,►特定的问题或错误►在问题本身中重现它所需的最短代码。没有明确问题陈述的问题对其他读者没有用。见:“**如何创建一个[mcve]**”,我可以在这里问什么话题?和[提问]。 - Makyen
我已经添加了清单文件和示例代码,这应该清楚地概述了我的问题。 - msobczak
没有错误,真的什么都没有。只有在后台运行时,控制台才会显示“save chunks 0”和“exportStream 0”。当一切正常时,我会得到整个一堆“save chunks <number>”,始终带有一些非零数字(表示块长度)。 - msobczak
当不使用扩展时,javascript 是否返回预期结果? - guest271314
显示剩余2条评论
1个回答

0

这很可能与我几个月前提交的这个Chromium bug有关:https://bugs.chromium.org/p/chromium/issues/detail?id=639105

这是负责此API的Chrome工程师的回复:

当处于后台时,Canvas不会绘制/渲染新帧,因此画布捕获不包含任何新项。据我所知,目前没有一种方式可以在使用Chrome的演示文稿上以您期望的方式在标签后台中绘制和捕获画布。

请在那里发布您的用例并支持修复此问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接