WebRtcCapture.vue 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. <template>
  2. <div>
  3. <div>
  4. <div
  5. class="embed-responsive embed-responsive-16by9"
  6. style="max-height: 400px;"
  7. >
  8. <video
  9. :id="videoUuid"
  10. autoplay
  11. playsinline
  12. />
  13. </div>
  14. <div style="height:5px" />
  15. <canvas
  16. :id="canvasUuid"
  17. hidden="hidden"
  18. />
  19. <div class="row">
  20. <div
  21. v-for="(item,index) in imageSrcs"
  22. :key="index"
  23. class="col-xs-6 col-md-3"
  24. >
  25. <div class="thumbnail">
  26. <AuthImage
  27. :auth-src="item"
  28. class=""
  29. @click="$refs.imagePreview.previewImage(item)"
  30. />
  31. <div class="caption">
  32. <a
  33. class="btn btn-primary"
  34. role="button"
  35. @click="deleteImage(index)"
  36. >
  37. {{ $t('lang.webRtcCapture.remove') }}
  38. </a>
  39. </div>
  40. </div>
  41. </div>
  42. </div>
  43. <div style="height:5px" />
  44. <div>
  45. <input
  46. autocomplete="off"
  47. class="btn btn-success"
  48. type="button"
  49. :title="$t('lang.webRtcCapture.capture')"
  50. :value="$t('lang.webRtcCapture.capture')"
  51. style="width:100%"
  52. @click="capture"
  53. />
  54. </div>
  55. <div
  56. v-if="errorMessage != null && errorMessage.length > 0"
  57. class="alert alert-danger"
  58. role="alert"
  59. >
  60. {{ errorMessage }}
  61. </div>
  62. </div>
  63. <ImagePreview ref="imagePreview" />
  64. </div>
  65. </template>
  66. <script>
  67. import Common from '../common/Common.js';
  68. import AuthImage from '../widget/AuthImage.vue';
  69. import { Uuid } from 'pc-component-v3';
  70. export default {
  71. components: {
  72. AuthImage,
  73. },
  74. props: {
  75. /**
  76. * 可以抓拍多张图片
  77. */
  78. 'multiple' : {
  79. type: Boolean,
  80. default: null,
  81. },
  82. },
  83. data: function () {
  84. this.Common = Common;
  85. return {
  86. imageSrcs: [],
  87. videoUuid: Uuid.createUUID(),
  88. canvasUuid: Uuid.createUUID(),
  89. width: undefined,
  90. height: undefined,
  91. errorMessage: '',
  92. };
  93. },
  94. mounted: function () {
  95. var _self = this;
  96. _self.video = undefined;
  97. _self.audio = undefined;
  98. _self.canvas = undefined;
  99. _self.context = undefined;
  100. _self.stream = undefined;
  101. _self.$nextTick(function () {
  102. _self.init();
  103. });
  104. },
  105. beforeUnmount: function () {
  106. // 关闭摄像头
  107. this.stream.stop();
  108. this.imageSrcs.splice(0, this.imageSrcs.length);
  109. },
  110. methods: {
  111. /**
  112. * 初始化摄像头
  113. */
  114. init: function () {
  115. var _self = this;
  116. // 由于IOS必须在版本11以上才能使用webrtc,并且只有Safari支持,所以做一个小小的判断,限定在
  117. if (/(iPhone|iPad|iPod|iOS)/i.test(window.navigator.userAgent) && navigator.vender.indexOf('apple') > -1) {
  118. return;
  119. }
  120. // 实现在浏览器中打开摄像头,并且将摄像头内容显示在页面中
  121. // 想要实现这一功能,需要了解webRTC(Web Real-Time Communication)网络实时通话技术,它允许浏览器实现视频、音频、P2P文件分享等功能。
  122. // 开启视频功能,依赖window的navigator对象,采用getUserMedia方法,有版本差异,所以需要判断区分
  123. // 需要IE(Edge)15+, Safari 11+, IOS Safari 11.2+, Android 64+, UC 不支持, QQ、百度部分支持
  124. // 所以首先需要对浏览器支持情况进行判断
  125. // 先判断浏览器是否支持
  126. _self.video = document.getElementById(_self.videoUuid);
  127. _self.video.crossOrigin = 'Anonymous';
  128. _self.canvas = document.getElementById(_self.canvasUuid);
  129. _self.context = _self.canvas.getContext('2d');
  130. // 如果浏览器支持,该方法的更新是向后兼容,新版将所有功能都使用navigator.mediaDevices进行了封装
  131. navigator.mediaDevices.enumerateDevices().then(function (sourceInfos) {
  132. // 如果支持新的方法,那么就使用新的方法来获取,当然这是一种比较主流的判断方法
  133. // 如果是想旧的方法兼容,可以使用下面作为判断条件,除IOS和PC以外,均使用旧的获取方式
  134. // !(navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/) || !/(iPhone|iPad|iPod|iOS|Android)/i.test(navigator.userAgent))
  135. // 无论是旧的写法还是新的标准,思路都是通过设备信息,获取摄像头的视频流,通过转换变成blob的格式交给video的src
  136. if (!navigator.mediaDevices.getUserMedia) {
  137. // 声明一个数组,用于装载设备媒体设备的相关信息,由于回调中sourceInfos对象中携带有所有媒体对象的相关信息
  138. // 这里对信息进行遍历筛选,只选出摄像头的Id并保存在数组中
  139. _self.exArray = [];
  140. for (var i = 0; i < sourceInfos.length; ++i) {
  141. if (sourceInfos[i].kind == 'videoinput') {
  142. _self.exArray.push(sourceInfos[i].deviceId);
  143. }
  144. }
  145. _self.getMedia();
  146. } else {
  147. // 当采用最新的标准方式获取视频时,这里对生成视频进行配置
  148. var userMediaConstraints = {
  149. audio: false, // 是否获取音频
  150. video: {
  151. facingMode: 'environment', // 环境表示后置摄像头,使用user表示采用前置
  152. },
  153. };
  154. // 这里就采用新的方法来获取视频
  155. navigator.mediaDevices.getUserMedia(userMediaConstraints).then(function success(stream) {
  156. _self.video.srcObject = stream;
  157. _self.stream = stream.getTracks()[0];
  158. }).catch(function (error) {
  159. alert(error.name + error.message);
  160. });
  161. }
  162. }).catch(function (error) {
  163. alert(error.name + error.message);
  164. });
  165. navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
  166. window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
  167. },
  168. /**
  169. * 获取媒体
  170. */
  171. getMedia: function () {
  172. var _self = this;
  173. if (navigator.getUserMedia) {
  174. // 该方法可以传递3个参数,分别为获取媒体信息的配置,成功的回调函数和失败的回调函数
  175. navigator.getUserMedia({
  176. audio: false, // 表明是否获取音频
  177. video: { // 对视频信息进行配置
  178. optional: [{
  179. 'sourceId': _self.exArray[1], // 下标为0是前置摄像头,1为后置摄像头,所以PC不能进入该判断,否则画面会保持在第一帧不动
  180. }],
  181. },
  182. }, stream => {
  183. _self.stream = stream;
  184. // 对FireFox进行兼容,这里对返回流数据的处理不同
  185. if (_self.video.mozSrcObject !== undefined) {
  186. //Firefox中,video.mozSrcObject最初为null,而不是未定义的,我们可以靠这个来检测Firefox的支持
  187. _self.video.mozSrcObject = stream;
  188. } else {
  189. // 一般的浏览器需要使用createObjectURL对流数据进行处理,再交给video元素的src
  190. _self.video.src = window.URL && window.URL.createObjectURL(stream) || stream;
  191. }
  192. _self.errorMessage = null;
  193. }, error => {
  194. _self.errorMessage = '抓拍失败:' + error;
  195. });
  196. } else {
  197. _self.errorMessage = '当前的设备不支持摄像头。';
  198. }
  199. },
  200. /**
  201. * 删除图片
  202. *@author GuoZhiBo 20171201
  203. */
  204. deleteImage(index) {
  205. var _self = this;
  206. _self.imageSrcs.splice(index, 1);
  207. },
  208. /**
  209. * 抓拍图片
  210. */
  211. capture: function () {
  212. var _self = this;
  213. _self.canvas.width = _self.video.videoWidth;
  214. _self.canvas.height = _self.video.videoHeight;
  215. _self.context.drawImage(_self.video, 0, 0, _self.canvas.width, _self.canvas.height); //将video对象内指定的区域捕捉绘制到画布上指定的区域,实现拍照。
  216. var img = _self.canvas.toDataURL('image/png');
  217. _self.height = _self.canvas.height * 0.25;
  218. _self.width = _self.canvas.width * 0.25;
  219. if (_self.multiple != true) {
  220. _self.imageSrcs.splice(0, _self.imageSrcs.length);
  221. }
  222. _self.imageSrcs.push(img);
  223. },
  224. },
  225. };
  226. </script>
  227. <style scoped>
  228. .imgDiv {
  229. display: inline-block;
  230. position: relative;
  231. }
  232. .imgDiv .delete {
  233. position: absolute;
  234. top: 0px;
  235. right: 12px;
  236. width: 0px;
  237. height: 0px;
  238. color: red;
  239. }
  240. .thumbnail {
  241. display: block;
  242. padding: 4px;
  243. margin-bottom: 20px;
  244. line-height: 1.42857143;
  245. background-color: #fff;
  246. border: 1px solid #ddd;
  247. border-radius: 4px;
  248. -webkit-transition: border 0.2s ease-in-out;
  249. -o-transition: border 0.2s ease-in-out;
  250. transition: border 0.2s ease-in-out;
  251. }
  252. </style>