使用 Puppeteer 手动单击页面后,相机才会打开

问题描述 投票:0回答:1

我编写了一个 NodeJS 应用程序,它执行以下操作:

  1. 创建一个能够使用
    http.createServer
    函数提供一些静态文件的服务器
  2. 接下来它启动一个 puppeteer 进程来启动 Chrome 浏览器(我必须关闭 headless 才能调试)。浏览器被指示启动一个本地主机 URL,该 URL 在步骤 1 中创建的服务器上运行。Puppetter 模拟页面上按钮上的单击操作。
  3. 来自本地主机的网页(index.html)包含一些face-api.js 实现。它有一个按钮。当木偶操纵者单击按钮时,预计会打开相机,开始视频流并对捕获的流/图像执行一些面部识别操作。最后它应该用结果更新网页上的 div。
  4. Puppeteer 最后等待 div 选择器,当出现时,读取 div 的内容并在 NodeJS 应用程序的控制台中打印。

我遇到的问题是 - 一切都很顺利,直到相机应该打开的步骤。按钮点击事件被很好地触发。但执行会在

await navigator.mediaDevices.getUserMedia()
调用时停止。至少在浏览器中打开的网页上完成一些手动用户交互后,相机才会打开。因此,即使我点击页面正文的任何部分,它也会恢复其余的 JS 执行(即打开相机并执行面部识别步骤)并成功完成该过程。

我给出了我的index.js文件的代码,其中包含NodeJS代码和index.html文件,其中包含页面布局和face-api.js人脸识别所需的必要JS代码。有人可以告诉我为什么浏览器在打开相机之前期望与网页进行一些用户交互吗?这似乎是安全功能,但我想知道有没有办法使用 Puppeteer 绕过它?我的最终目标是在运行 NodeJS 程序时实现无头并且根本不显示 Chrome 窗口。

index.js 代码

const puppeteer = require('puppeteer');
const http = require('http');
const fs = require('fs');
require('dotenv').config();


const getViewUrl = (url) => {
    url = url == '/' ? 'index.html' : url;
    url = url.indexOf('/') === 0 ? url.substring(1) : url;
    return `public/${url}`;
};

const getContentType = (url) => {
    if (url.endsWith('.js')) {
        return 'text/javascript';
    } else if (url.endsWith('.json')) {
        return 'application/json';
    } else if (url.endsWith('.html')) {
        return 'text/html';
    }
    return 'application/octet-stream';
}

var server = null;
const PORT = process.env.PORT || 55193;

function startServer() {
    server = http.createServer((request, response) => {
        let viewUrl = getViewUrl(request.url);
        fs.readFile(viewUrl, (error, data) => {
            if (error) {
                response.writeHead(404);
                response.write("<h1>File Not Found</h1>")
            } else {
                response.writeHead(200, {
                    'Content-type': getContentType(viewUrl)
                });
                response.write(data);
            }
            response.end();
        })
    });

    server.listen(PORT);
}

(async() => {
    startServer();
    // Launch the browser and open a new blank page
    const browser = await puppeteer.launch({
        headless: false,
        dumpio: true,
        args: ['--no-sandbox',
            '--use-file-for-fake-video-capture=C:/Users/adm/Downloads/test.mjpeg'
        ]
    });
    var context = browser.defaultBrowserContext();
    context.clearPermissionOverrides();
    await context.overridePermissions("http://localhost:" + PORT + "/", ['camera', 'microphone']);
    const page = await context.newPage();

    page
        .on('console', message =>
            console.log(`${message.type().substr(0, 3).toUpperCase()} ${message.text()}`))
        .on('pageerror', ({ message }) => console.log(message))
        .on('response', response =>
            console.log(`${response.status()} ${response.url()}`))
        .on('requestfailed', request =>
            console.log(`${request.failure().errorText} ${request.url()}`));

    // Navigate the page to a URL
    await page.goto('http://localhost:' + PORT + '/', { waitUntil: 'load' });

    // Set screen size
    await page.setViewport({ width: 1080, height: 1024 });

    // Wait and click on first result
    const searchResultSelector = await page.waitForSelector('#runBtn');
    await page.click('#runBtn');

    // Locate the full title with a unique string
    const textSelector = await page.waitForSelector('.positionDiv');
    const fullTitle = await textSelector.evaluate(el => el.textContent);
    await browser.close();
    console.log(fullTitle);
    server.close();
})();

index.html代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <meta name="author" content="Prithwiraj Bose <sribasu.com>" />
    <title>FaceAPI</title>
    <script src="js/face-api.min.js" type="text/javascript"></script>
</head>

<body>

    <div id="content">
        <div id="myDiv01">...</div><br>

        <input type="button" value="run" id="runBtn" onclick="javascript: run();"><br><br>

        <video onplay="onPlay(this)" id="inputVideo" autoplay muted width="640" height="480" style=" border: 1px solid #ddd;"></video><br>
        <canvas id="overlay" width="640" height="480" style="position:relative; top:-487px; border: 1px solid #ddd;"></canvas><br>
    </div>
    <!-- Core theme JS-->
    <script type="text/javascript">
        function resizeCanvasAndResults(dimensions, canvas, results) {
            const {
                width,
                height
            } = dimensions instanceof HTMLVideoElement
                ?
                faceapi.getMediaDimensions(dimensions) :
                dimensions
            canvas.width = width
            canvas.height = height

            return results
        }

        async function onPlay() {
            const videoEl = document.getElementById('inputVideo')
            const options = new faceapi.TinyFaceDetectorOptions({
                inputSize: 128,
                scoreThreshold: 0.3
            })


            result = await faceapi.detectSingleFace(videoEl, options).withFaceLandmarks(true)
            if (result) {

                var nose = result.landmarks.getNose();
                var x = nose[3]._x;
                document.getElementById('myDiv01').innerHTML = x > (640 / 2) ? (x > 500 ? 'Extreme Right' : 'Right') : (x < 100 ? 'Extreme Left' : 'Left');
                document.getElementById('myDiv01').classList.add('positionDiv');
                // Just printing the first of 68 face landmark x and y 


            }

            setTimeout(() => onPlay())
        }

        async function run() {
            await faceapi.loadTinyFaceDetectorModel('models/')
            await faceapi.loadFaceLandmarkTinyModel('models/')
            console.log("Step 1");
            const stream = await navigator.mediaDevices.getUserMedia({
                audio: false,
                video: true
            })
            console.log("Step 2");
            const videoEl = document.getElementById('inputVideo')
            videoEl.srcObject = stream
        }
    </script>
</body>

</html>

NodeJS 控制台输出(直到手动页面被单击)

C:\Program Files\nodejs\node.exe .\index.js
200 http://localhost:8080/
index.js:67
200 http://localhost:8080/js/face-api.min.js
index.js:67
200 http://localhost:8080/favicon.ico
index.js:67
200 http://localhost:8080/models/tiny_face_detector_model-weights_manifest.json
index.js:67
200 http://localhost:8080/models/tiny_face_detector_model-shard1
index.js:67
200 http://localhost:8080/models/face_landmark_68_tiny_model-weights_manifest.json
index.js:67
200 http://localhost:8080/models/face_landmark_68_tiny_model-shard1
index.js:67
LOG Step 1

NodeJS 控制台输出(手动单击页面后)

LOG Step 2
index.js:64
Right

我尝试使用 puppeteer 强制模拟页面主体上的点击。但只有在网页上发生真正的人机交互之前,一切都不起作用!

html node.js puppeteer webcam google-chrome-headless
1个回答
0
投票

我终于成功了。是的,我了解到,由于 Chrome 的安全功能,在没有任何真正的人工干预的情况下,真正的 UI 无法以编程方式进行交互。因此 Chrome 支持一个参数,可以将其传递给 Puppetter。这就是所谓的

--use-fake-ui-for-media-stream
。所以我的浏览器启动代码现在看起来像这样。我的问题中给出的原始代码中的其他所有内容都按预期工作。

    const browser = await puppeteer.launch({
      headless: "new",
      args: [
        '--no-sandbox',
        '--use-fake-ui-for-media-stream'
      ]
    });
© www.soinside.com 2019 - 2024. All rights reserved.