wwf
17 小时以前 4e6f18dfa08e2f2f4f02aaa1b8e8e51852b7a9a1
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
<template>
  <div ref="cameraBox" class="camera_box" :style="cameraStyle" v-show="isCameraOpening">
    <video ref="videoEl" :width="videoWidth" :height="videoHeight" autoplay></video>
    <canvas ref="canvasEl" :width="videoWidth" :height="videoHeight" style="display:none;"></canvas>
    
    <el-row justify="center" class="btn_box">
      <el-button plain text @click="closeCamera()">
        <Icon icon="material-symbols:cancel" width="28" height="28"  style="color: #FA5252" />
      </el-button>
      <el-button plain text @click="startRecordImage()">
        <Icon icon="ant-design:camera-filled" width="28" height="28"  style="color: #FA5252" />
      </el-button>
    </el-row>
  </div>
</template>
 
<script>
import { uploadByBase64 } from '@/utils/tool.js'
export default {
  data () {
    return {
      recorderOptions: { // recorder 配置项
        mimeType: 'video/webm;codecs=vp8,opus'
      },
      isCameraOpening: false,
    }
  },
  computed: {
    videoWidth: function () {
      return 400
    },
    videoHeight: function(){
      return (this.videoWidth * 3) / 4
    },
    cameraStyle: function(){
      return {
        width: `${this.videoWidth}px`,
        height: `${this.videoHeight}px`
      }
    },
  },
  beforeUnmount(){
    this.closeCamera()
  },
  mounted () {
    this.initNavigatorMedia()
    this.openCamera()
  },
  methods: {
    initNavigatorMedia: function(){
      // 获取媒体属性,旧版本浏览器可能不支持mediaDevices,设置一个空对象
      if (navigator.mediaDevices === undefined) {
        navigator.mediaDevices = {};
      }
      // 使用getUserMedia,因为它会覆盖现有的属性。
      // 这里,如果缺少getUserMedia属性,就添加它。
      if (navigator.mediaDevices.getUserMedia === undefined) {
        this.initGetUserMedia()
      }
    },
    initGetUserMedia: function(){
      navigator.mediaDevices.getUserMedia = (constraints) => {
        // 首先获取现存的getUserMedia(如果存在)
        let getUserMedia =
          navigator.webkitGetUserMedia ||
          navigator.mozGetUserMedia ||
          navigator.getUserMedia;
        // 有些浏览器不支持,会返回错误信息
        // 保持接口一致
        if (!getUserMedia) {//不存在则报错
          return Promise.reject(
            new Error("getUserMedia is not implemented in this browser")
          );
        }
        // 否则,使用Promise将调用包装到旧的navigator.getUserMedia
        return new Promise((resolve, reject) => {
          getUserMedia.call(navigator, constraints, resolve, reject);
        });
      };
    },
    openCamera: function(){ // 打开摄像头
      navigator.mediaDevices.getUserMedia({
        audio: false,
        video: {
          width: this.videoWidth,
          height: this.videoHeight,
        }
      }).then((stream) => {
        this.isCameraOpening = true
        this.mediaStream = stream
        this.initVideoSrcObject()
      }).catch(err => {
        console.log(`${err.name}:${err.message}`);
        this.closeCamera()
        this.mediaErrorHandler(err)
      });
    },
    closeCamera() { // 关闭摄像头
      if (!this.isCameraOpening) return false
 
      this.isCameraOpening = false
 
      if (this.mediaStream) {
        let tracks = this.mediaStream.getTracks()
        tracks.forEach(track => track.stop());
      }
 
      if (this.mediaRecorder) this.mediaRecorder.stop();
      this.$emit('close')
    },
    initVideoSrcObject: function(){ // 初始化 视频录制 的 video srcObject
      // 旧的浏览器可能没有srcObject
      if ("srcObject" in this.$refs.videoEl) {
        this.$refs.videoEl.srcObject = this.mediaStream;
        this.initVideoMoveListener()
      } else {
        console.log('浏览器不支持')
      }
    },
    initVideoMoveListener: function(){ // 初始化 视频窗口 的 移动事件
      let eventState = {}
      let startMoving = (e) => { // 开始移动的回调
        e.preventDefault()
        e.stopPropagation()
        eventState = {
          left: this.$refs.cameraBox.offsetLeft,
          top: this.$refs.cameraBox.offsetTop,
          x: e.clientX,
          y: e.clientY
        }
        document.addEventListener('mousemove', moving)
        document.addEventListener('mouseup', endMoving)
      }
      let moving = (e) => { // 移动的回调
        e.preventDefault()
        e.stopPropagation()
 
        let margin = 10
        let left = e.clientX - (eventState.x - eventState.left)
        let top = e.clientY - (eventState.y - eventState.top)
        let maxLeft = document.documentElement.clientWidth - this.videoWidth - margin
        let maxTop = document.documentElement.clientHeight - this.videoHeight - margin
 
        // 限制移动截图区域不能移到可视区域外
        if (left < 10) left = margin
        if (top < 10) top = margin
        if (left > maxLeft) left = maxLeft
        if (top > maxTop) top = maxTop
 
        this.$refs.cameraBox.style.left = `${left}px`
        this.$refs.cameraBox.style.top = `${top}px`
      }
      let endMoving = (e) => { // 结束移动的回调
        e.preventDefault()
        document.removeEventListener('mousemove', moving)
        document.removeEventListener('mouseup', endMoving)
      }
      this.$refs.cameraBox.addEventListener('mousedown', startMoving)
    },
    initUploadChunk: async function(blobs){ // 初始化上传任务
      let tmpBlob = new Blob(blobs, { 'type': this.recorderOptions.mimeType });
      // let file = new File(blobs, fileName, { type: 'video/webm' });
      let file = null
      try {
        // 解决 Webm 视频 duration 问题
        file = await fixWebmMetaInfo(tmpBlob)
      } catch (error) {
        file = tmpBlob
        console.log(error)
      }
 
      let path = await uploadChunk(file, '学习视频', 'webm')
      if (!path) return false
 
      if (this.recordingFlag || this.recordCameraFlag) {
        this.$store.commit('authCamera/recordVideoUrl', path)
        this.$store.commit('authCamera/faceCaptureAll', { flag: false, imgList: this.faceCaptureAllImgList })
      } else {
        this.$store.commit('authCamera/biopsyVideoUrl', path)
      }
    },
    startRecordImage() { // 开始录制图像(拍照)
      if (!this.$refs.canvasEl) return false
 
      this.drawImage()
      // this.closeCamera()
    },
    drawImage: function(){ // 绘制图片
      this.$refs.canvasEl.getContext('2d').drawImage(
        this.$refs.videoEl,
        0,
        0,
        this.videoWidth,
        this.videoHeight
      );
      this.uploadBase64()
    },
    async uploadBase64(){ // 上传图片
      let base64 = this.$refs.canvasEl.toDataURL("image/png", 1);
      // const url = await uploadByBase64(base64, '核验照片')
      if (base64) {
        this.$emit('handlerSuccess', base64)
        this.closeCamera()
      } else {
        this.$message.error('拍摄失败')
      }
    },
    uploadErrorHandler: function (evt) { // 上传失败回调
      console.log(JSON.stringify(evt.target))
    },
    mediaErrorHandler: function(){ // 开启摄像头或录制屏幕异常处理
      this.$message.error('当前浏览器不支持,请更换浏览器')
    },
  }
};
</script>
 
<style lang="scss" scoped>
.camera_box{
  position: fixed;
  bottom: 10px;
  right: 10px;
  z-index: 300;
}
.status_box{
  position: absolute;
  left: 0;
  right: 0;
  padding: 6px;
  margin-top: -38px;
  font-size: 0.8rem;
  color: white;
  background: rgba(0,0,0,0.4);
  .normal > span,
  .abnormal > span{
    width: 6px;
    height: 6px;
    border-radius: 28px;
    display: inline-block;
    margin-right: 5px;
  }
  .normal > span{
    background-color: #00E63C;
  }
  .abnormal > span{
    background-color: #FF3232;
  }
}
.btn_box{
  background: rgba(0,0,0,0.4);
  margin-top: -63px;
  padding-top: 10px;
  padding-bottom: 7px;
  position: absolute;
  left: 0;
  right: 0;
}
.duration{
  position: absolute;
  top: 0;
  right: 0;
  color: white;
  text-shadow: 1px 1px 1px black, 1px -1px 1px black, -1px -1px 1px black, -1px 1px 1px black;
}
</style>