JS控制设备摄像头初探

原文链接

主要内容


获取设备摄像头信息

html中添加一个video元素,设置属性为autoplay,以便在获取视频流之后立即播放,也可以在获取视频流之后手动控制播放(play方法)。

<video autoplay></video>
1

利用navigator.mediaDevices.getUserMedia获取视频流。之前的方法是navigator.getUserMedia(),为向后兼容此方法已废弃。

注意

目前pc端高级浏览器支持比较好,手机端貌似只有Safari支持😢 。

let video = document.querySelector('video');
navigator.mediaDevices.getUserMedia({ 
    audio: false, 
    video: true
}).then(stream => {
    video.srcObject = stream;
}).catch(err => {
    alert('error: ' + err.message);
});
1
2
3
4
5
6
7
8
9

利用摄像头截图并保存

截图的思路无非就是把动态的video存为瞬间的静态的image。利用canvas可以很简单快速的把视频帧存为静态图片,首先在video上层覆盖一个透明的canvas容器。

<div class="wrapper">
    <video autoplay playsinline></video>
    <a class="button snap" href="javascript:;">截图</a>
    <a class="button save hide" download="test.png">保存</a>
    <canvas></canvas>
</div>
1
2
3
4
5
6

保存,利用a标签的新属性download,只要把图片地址赋给download属性即可以实现下载。图片地址的生成利用URL.createObjectURL方法,具体请看下面代码。

let flag = true,
    video = document.querySelector('video'),
    snapBtn = document.querySelector('.snap'),
    saveBtn = document.querySelector('.save'),
    canvas = document.querySelector('canvas'),
    cxt = canvas.getContext('2d');
navigator.mediaDevices.getUserMedia({ 
    audio: false, 
    video: true
}).then(stream => {
    video.srcObject = stream;
    snapBtn.onclick = () => {
        saveBtn.classList.remove('hide', 'show');
        if(flag) {
            cxt.drawImage(video, 0, 0, canvas.width, canvas.height);
            snapBtn.innerText = '取消';
            saveBtn.classList.add('show');
            flag = false;
            //canvas转图片保存
            canvas.toBlob((blob) => {
                let url = URL.createObjectURL(blob);
                    saveBtn.href = url;
            });
        } else {
            cxt.clearRect(0, 0, canvas.width, canvas.height);
            snapBtn.innerText = '截图';
            saveBtn.classList.add('hide');
            flag = true;
        }
    }
}).catch(err => {
    alert('error: ' + err.message);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

这里不足的是canvas和video的适配还有问题,导致截图和视频的比例很多时候不一致,还望大神指点🙏 。

手动控制前后置摄像头

获取可用摄像头

添加select用于获取可用摄像头并手动选择指定摄像头:

<div class="wrapper">
    <video autoplay playsinline></video>
    <select></select>
    <a class="button confirm" href="javascript:;">确定</a>
    <a class="button snap" href="javascript:;">截图</a>
    <a class="button save hide" download="test.png">保存</a>
    <canvas></canvas>
</div>
1
2
3
4
5
6
7
8

navigator.mediaDevices.enumerateDevices用于获取所有可用音频和视频输入设备的方式。

在移动端,MediaTrackConstraints.facingMode用于前后置摄像头的调整比较方便,其值为'user(前置)'、'environment(后置)'、'left(前置左)'、'right(前置右)',后两种不常见,目前还没有两个前置摄像头的手机吧😂 。

切换摄像头时首先切断当前视频流(所有轨道),用stream.getTracks()获取所轨道,stop()方法停止该轨道。

let flag = true,
    select = document.querySelector('select'),
    confirmBtn = document.querySelector('.confirm'),
    video = document.querySelector('video'),
    snapBtn = document.querySelector('.snap'),
    saveBtn = document.querySelector('.save'),
    canvas = document.querySelector('canvas'),
    cxt = canvas.getContext('2d');

navigator.mediaDevices.enumerateDevices().then(getDevices);

navigator.mediaDevices.getUserMedia({ 
    audio: false, 
    video: true
}).then(stream => {
    video.srcObject = stream;
    snapBtn.onclick = () => {
        saveBtn.classList.remove('hide', 'show');
        if(flag) {
            cxt.drawImage(video, 0, 0, canvas.width, canvas.height);
            snapBtn.innerText = '取消';
            saveBtn.classList.add('show');
            flag = false;

            canvas.toBlob((blob) => {
                let url = URL.createObjectURL(blob);
                    saveBtn.href = url;
            })
        } else {
            cxt.clearRect(0, 0, canvas.width, canvas.height);
            snapBtn.innerText = '截图';
            saveBtn.classList.add('hide');
            flag = true;
        }
    }
}).catch(err => {
    alert('error: ' + err.message);
});

confirmBtn.onclick = () => {
    if (typeof currentStream !== 'undefined') {
        stopMediaTracks(currentStream);
    }
    const videoConstraints = {};
    if (select.value === '') {
        videoConstraints.facingMode = 'environment';
    } else {
        videoConstraints.deviceId = { exact: select.value };
    }
    const constraints = {
        video: videoConstraints,
        audio: false
    };
    navigator.mediaDevices
        .getUserMedia(constraints)
        .then(stream => {
            currentStream = stream;
            video.srcObject = stream;
        })
        .catch(error => {
            console.error(error);
        });
}

function getDevices(devices) {
    select.innerHTML = '';
    select.appendChild(document.createElement('option'));
    let count = 1;
    devices.forEach(device => {
        if (device.kind === 'videoinput') {
            const option = document.createElement('option');
            option.value = device.deviceId;
            const label = device.label || `相机 ${count++}`;
            const textNode = document.createTextNode(label);
            option.appendChild(textNode);
            select.appendChild(option);
        }
    });
}

function stopMediaTracks(stream) {
    stream.getTracks().forEach(track => {
        track.stop();
    });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85

或者看这里

(没有?刷新之后‘允许使用摄像头’试试?)

还可以做什么

  • 结合上一篇的人脸识别,就可以实现视频中的人脸识别了
  • 直播,尤其是手机浏览器端直播
  • 等你来头脑风暴

参考资料

最近更新: 4/23/2019, 11:11:51 PM