zhouhao
2022-01-04 83feeb994ed1e33474c62951356c7f38d3143583
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/**
* 组件: 调用摄像头拍摄的构造函数
* @params {Object} options 参数如下:
*             video {DOM} video元素
*             width {Number} 画布宽度
*             height {Number} 画布高度
*             onShoot {Function} 录像回调函数
*             onError {Function} error回调函数
*         {Object} canvasOption 参数如下:
*             marginTop {Number} 银行卡可视区域距离屏幕顶部的距离,用作画布坐标
*             marginLeft {Number} 银行卡可视区域距离屏幕左侧的距离,用作画布坐标
*             width {Number} 银行卡可视区域宽度
*             height {Number} 银行卡可视区域高度
*调用:
*    Camera.create(options, canvasOption)
*/
function Camera(options, canvasOption) {
  this.video = options.video;
  this.width = options.width || 640;
  this.height = options.height || 480;
  this.shadeMarginTop = canvasOption.marginTop;
  this.shadeMarginLeft = canvasOption.marginLeft;
  this.shadeWidth = canvasOption.width;
  this.shadeHeight = canvasOption.height;
  this.onError = options.onError;
  this.onShoot = options.onShoot;
  this.mediaStreamTrack = null; // 存放视频流
}
 
Camera.prototype = {
  init: function() {
      if(navigator.mediaDevices === undefined) {
          navigator.mediaDevices = {}
      }
      // 一些浏览器部分支持 mediaDevices。 我们不能直接给对象设置 getUserMedia
      // 因为这样可能会覆盖已有的属性。这里我们只会在没有getUserMedia属性的时候添加它。
      if (navigator.mediaDevices.getUserMedia === undefined) {
          navigator.mediaDevices.getUserMedia = function(constraints) {
              // 首先,如果有getUserMedia的话,就获取它
              var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
              
              // 一些浏览器根本没实现它 — 那么就返回一个error到promise的reject来保持一个统一的接口
              if (!getUserMedia) {
                  return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
              }
          
              // 否则,为老的navigator.getUserMedia方法包裹一个Promise
              return new Promise(function(resolve, reject) {
                  getUserMedia.call(navigator, constraints, resolve, reject);
              });
          }
      }
      this.video.autoplay = 'true';
      this.canvasDom = document.createElement('canvas');
      this.canvasDom.width = this.width; 
      this.canvasDom.height = this.height;
      this.canvasDom.style.display = 'none';
      document.querySelector('body').appendChild(this.canvasDom);
  },
  
  // 检查摄像头是否可用
  canCameraUse: function() {
      return (navigator.mediaDevices.getUserMedia && window.URL);
  },
 
  // 获取录像流到video中
  shoot: function() {
      var self = this;
      var video = self.video;
      if (self.canCameraUse) {
          navigator.mediaDevices.getUserMedia({
              video: { facingMode: { exact: "environment"} } // 后置摄像头设置
          })
          .then(function(stream) {
              self.mediaStreamTrack = stream
              // 旧的浏览器可能没有srcObject
              if ("srcObject" in video) {
                  video.srcObject = stream;
              }else {
                  // 防止在新的浏览器里使用它,因为它已经不再支持了
                  video.src = window.URL.createObjectURL(stream);
              }
              video.onloadedmetadata = function(e) {
                  video.play();
              };
              video.addEventListener('error', function(error) {
                  stream.stop();
                  self.onError && self.onError(error);
              }, false);
          })
          .catch(function(err) {
              self.onError && self.onError(err)
          })
      }
  },
  
  // 将录像绘制到canvas
  drawVideo: function() {
      var canvasDom = this.canvasDom;
      var ctx = canvasDom.getContext('2d');
      ctx.fillStyle = "#000000";
      // 视频可能不会充满屏幕,这时候,需要按照视频缩放的比例来剪切所需要的部分
      let videoHeight = this.video.videoHeight // 视频本身的高度
      let videoWidth = this.video.videoWidth // 视频本身的宽度
      let videoWidthScale = 1
      if (videoWidth && this.width !== videoWidth) { // 计算当前视频的缩放比例
          videoWidthScale = videoWidth / this.width
      }
      let newHeight = videoHeight / videoWidthScale // 当前显示的视频高度
      let videoMarginTop = this.video.clientHeight - newHeight > 0 ?
                           (this.video.clientHeight - newHeight) / 2 : 0 // 当前视频距离顶部高度
      let x = Math.floor(this.shadeMarginLeft * 1) // 计算剪切视频的x坐标
      let y = Math.floor((this.shadeMarginTop - videoMarginTop) * 1) // 计算剪切视频的y坐标
      let w = videoWidth - x * 2, h = Math.ceil(videoHeight - y * 2) // 计算剪切视频的宽高
      ctx.fillRect(this.shadeMarginLeft, this.shadeMarginTop, this.shadeWidth, this.shadeHeight); // 填充画布
      ctx.drawImage(this.video, x, y, w, h, this.shadeMarginLeft, this.shadeMarginTop, this.shadeWidth, this.shadeHeight) // 剪切视频并绘制成图片
      this.ctx = ctx
  },
  
  // 录像事件绑定
  addShootEvent: function() {
      let self = this;
      let video = self.video;
      // 正在录像
      video.addEventListener('timeupdate', function() {
          self.drawVideo();
          self.onShoot && self.onShoot()
      }, false)
  },
 
  // 将录像成图片
  snapshot: function(cb, imageType) {
      let self = this
      let canvas = self.canvasDom;
      imageType = imageType || 'jpeg';
      let imageSrc = canvas.toDataURL('image/' + imageType);
      cb && cb(imageSrc);
  },
 
  // 开始录像
  play: function() {
      this.video.play();
  },
 
  // 停止录像
  pause: function() {
      this.video.pause();
      let self = this
      // 关闭摄像头
      self.mediaStreamTrack.getVideoTracks().forEach(function(track) {
          track.stop();
          self.ctx.clearRect(0, 0, self.canvasDom.width, self.canvasDom.height); // 清除画布
      });
  },
  
  render: function() { // 初始化
      this.init();
      this.shoot();
      this.drawVideo();
      this.addShootEvent();
  }
  
};
 
Camera.create = function(options, canvasOption) {
  var camera = new Camera(options, canvasOption.canvasStyle);
  camera.render();
  return camera;
};
 
export default Camera