useCamera.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. const errorMessage = {
  2. ms1: '请使用微信、Chrome、Firefox或Safari浏览器,如果浏览器没有问题,请联系管理员',
  3. ms2: '请使用微信、Chrome、Firefox或Safari',
  4. ms3: 'navigator对象无媒体属性', // 错误提示同ms2
  5. ms4: '未找到摄像头,请确认摄像头是否正常以及当前摄像头是否被禁用',
  6. ms5: '检测到当前摄像头已被占用,请关闭摄像头后重新尝试',
  7. ms6: '摄像头硬件无法满足使用要求,请更换摄像头后重新尝试',
  8. ms7: '请开启浏览器摄像头权限',
  9. ms8: '未获取摄像头数据,请检测摄像头是否正常',
  10. ms9: '当前浏览器不支持,请更换浏览器后尝试',
  11. ms10: '当前Android系统版本低于9!请更新操作系统',
  12. ms11: '当前IOS系统版本低于14.3!请更新操作系统',
  13. ms12: '推荐使用safari浏览器或微信,使用其他浏览器可能会在考试过程中出现摄像头问题,影响考试结果,导致重考,不建议使用其他浏览器。',
  14. ms13: '推荐使用火狐浏览器、谷歌浏览器或微信,使用其他浏览器可能会在考试过程中出现摄像头问题,影响考试结果,导致重考,不建议使用其他浏览器',
  15. };
  16. export {
  17. errorMessage
  18. };
  19. function checkPlatform2() {
  20. const ua = navigator.userAgent.toLowerCase();
  21. // 安卓系统
  22. if (/android/i.test(navigator.userAgent)) {
  23. let test = /android\s([\w.]+)/;
  24. let match = test.exec(ua);
  25. let version = match[1].split('.')[0];
  26. if (version < 9) {
  27. Toast.fail(errorMessage.ms10);
  28. return {
  29. data: false,
  30. waitCode: false
  31. };
  32. }
  33. // 判断 浏览器 Android
  34. if (!checkAndroidForBrowser()) {
  35. // 提示信息
  36. Toast.fail({
  37. message: errorMessage.ms13,
  38. duration: 5000
  39. });
  40. return {
  41. data: true,
  42. waitCode: true
  43. };
  44. }
  45. return {
  46. data: true,
  47. waitCode: false
  48. };
  49. }
  50. // ios 系统
  51. if (/(iphone | ipad | ipod | iOS)/i.test(navigator.userAgent)) {
  52. let test = /os\s([\w]+)/;
  53. let match = test.exec(ua);
  54. let vs = match[1].split('_');
  55. let version = '';
  56. if (vs.length > 2) {
  57. version = `${vs[0]}.${vs[1]}`;
  58. } else if (vs.length == 2) {
  59. version = `${vs[0]}.${vs[1]}`;
  60. } else {
  61. version = `${vs[0]}.0`;
  62. }
  63. if (version < 14.3) {
  64. Toast.fail(errorMessage.ms11);
  65. return {
  66. data: false,
  67. waitCode: false
  68. };
  69. }
  70. // 判断浏览器兼容 判断 ios 浏览器提示信息
  71. // 判断 浏览器 Android
  72. if (!checkIosForBrowser()) {
  73. // 提示信息
  74. Toast.fail({
  75. message: errorMessage.ms12,
  76. duration: 5000
  77. });
  78. return {
  79. data: true,
  80. waitCode: true
  81. };
  82. }
  83. return {
  84. data: true,
  85. waitCode: false
  86. };
  87. }
  88. }
  89. function checkIosForBrowser() {
  90. let u = navigator.userAgent;
  91. let result = false;
  92. let curname = getBrowser();
  93. // 如果是 苹果
  94. if (curname === 'safari') {
  95. result = true;
  96. }
  97. // 如果是 微信
  98. if (u.indexOf('MicroMessenger') > -1) {
  99. result = true;
  100. }
  101. return result;
  102. }
  103. function checkAndroidForBrowser() {
  104. let curname = getBrowser();
  105. let result = false;
  106. // 如果是 谷歌
  107. if (curname === 'chrome') {
  108. result = true;
  109. }
  110. if (curname === 'firefox') {
  111. result = true;
  112. }
  113. // 如果是 微信
  114. if (curname === 'wechat') {
  115. result = true;
  116. }
  117. return result;
  118. }
  119. function getBrowser() {
  120. var u = navigator.userAgent;
  121. var bws = [{
  122. name: 'sgssapp',
  123. it: /sogousearch/i.test(u),
  124. }, {
  125. name: 'wechat',
  126. it: /MicroMessenger/i.test(u),
  127. }, {
  128. name: 'weibo',
  129. it: !!u.match(/Weibo/i),
  130. }, {
  131. name: 'uc',
  132. it: !!u.match(/UCBrowser/i) || u.indexOf(' UBrowser') > -1,
  133. }, {
  134. name: 'sogou',
  135. it: u.indexOf('MetaSr') > -1 || u.indexOf('Sogou') > -1,
  136. }, {
  137. name: 'xiaomi',
  138. it: u.indexOf('MiuiBrowser') > -1,
  139. }, {
  140. name: 'baidu',
  141. it: u.indexOf('Baidu') > -1 || u.indexOf('BIDUBrowser') > -1,
  142. }, {
  143. name: '360',
  144. it: u.indexOf('360EE') > -1 || u.indexOf('360SE') > -1,
  145. }, {
  146. name: '2345',
  147. it: u.indexOf('2345Explorer') > -1,
  148. }, {
  149. name: 'edge',
  150. it: u.indexOf('Edge') > -1,
  151. }, {
  152. name: 'ie11',
  153. it: u.indexOf('Trident') > -1 && u.indexOf('rv:11.0') > -1,
  154. }, {
  155. name: 'ie',
  156. it: u.indexOf('compatible') > -1 && u.indexOf('MSIE') > -1,
  157. }, {
  158. name: 'firefox',
  159. it: u.indexOf('Firefox') > -1,
  160. }, {
  161. name: 'safari',
  162. it: u.indexOf('Safari') > -1 && u.indexOf('Chrome') === -1 && u.indexOf('(KHTML, like Gecko) Version') >
  163. -1 && u.indexOf('MQQBrowser') === -1 && u.indexOf('FingerBrowser') === -1,
  164. }, {
  165. name: 'qqbrowser',
  166. it: u.indexOf('MQQBrowser') > -1 && u.indexOf(' QQ') === -1,
  167. }, {
  168. name: 'qq',
  169. it: u.indexOf('QQ') > -1,
  170. }, {
  171. name: 'chrome',
  172. it: u.indexOf('(KHTML, like Gecko) Chrome') > -1 && u.indexOf('MiuiBrowser') === -1 && u.indexOf(
  173. 'UCBrowser') === -1 && u.indexOf('HarmonyOS') === -1 && u.indexOf('HuaweiBrowser') === -1,
  174. }, {
  175. name: 'opera',
  176. it: u.indexOf('Opera') > -1 || u.indexOf('OPR') > -1,
  177. }, {
  178. name: 'wechat',
  179. it: /MicroMessenger/i.test(u)
  180. }, ];
  181. for (var i = 0; i < bws.length; i++) {
  182. if (bws[i].it) {
  183. return bws[i].name;
  184. }
  185. }
  186. return 'other';
  187. }
  188. export function errorFunComplete(e) {
  189. const name = e.name;
  190. if (name === 'NotFoundError' || name === 'DevicesNotFoundError') {
  191. Toast.fail(errorMessage.ms4);
  192. }
  193. if (name === 'NotReadableError' || name === 'TrackStartError') {
  194. Toast.fail(errorMessage.ms5);
  195. } else if (name === 'OverconstrainedError' || name === 'ConstraintNotSatisfiedError') {
  196. Toast.fail(errorMessage.ms6);
  197. } else if (name === 'NotAllowedError' || name === 'PermissionDeniedError') {
  198. Toast.fail(errorMessage.ms7);
  199. } else if (name === 'TypeError' || name === 'TypeError') {
  200. Toast.fail(errorMessage.ms8);
  201. } else {
  202. Toast.fail(errorMessage.ms9);
  203. }
  204. }
  205. let constraints = {
  206. audio: false,
  207. video: {
  208. width: 480,
  209. height: 320,
  210. sourceId: 'default',
  211. deviceId: 'default',
  212. transform: 'rotate(180deg)',
  213. facingMode: {
  214. // exact: 'user'
  215. },
  216. },
  217. };
  218. // 校验权限
  219. export function check(success, error, backFun) {
  220. if (!navigator) {
  221. Toast.fail({
  222. message: errorMessage.ms1,
  223. duration: 5000
  224. });
  225. // 当前浏览器版本 无媒体对象
  226. backFun && backFun();
  227. return false;
  228. }
  229. const resoutData = checkPlatform2();
  230. const timeD = resoutData.waitCode ? 5000 : 0;
  231. if (!resoutData.data) {
  232. backFun && backFun();
  233. return false;
  234. }
  235. setTimeout(() => {
  236. if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
  237. //最新的标准API 返回promise
  238. navigator.mediaDevices.getUserMedia(constraints).then(success).catch(error);
  239. } else if (navigator.webkitGetUserMedia) {
  240. //webkit核心浏览器
  241. navigator.webkitGetUserMedia(constraints, success, error);
  242. } else if (navigator.mozGetUserMedia) {
  243. //firfox浏览器
  244. navigator.mozGetUserMedia(constraints, success, error);
  245. } else if (navigator.getUserMedia) {
  246. //旧版API
  247. navigator.getUserMedia(constraints, success, error);
  248. } else {
  249. Toast.fail(errorMessage.ms2);
  250. backFun && backFun();
  251. }
  252. }, timeD);
  253. // 没有媒体对象
  254. // Toast.fail(errorMessage.ms2);
  255. // 1. ios 14.3版本一下
  256. // 2. 当前浏览器非完整版
  257. }
  258. // 抓拍确认
  259. export function check2(success, error) {
  260. if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
  261. //最新的标准API 返回promise
  262. navigator.mediaDevices.getUserMedia(constraints).then(success).catch(error);
  263. } else if (navigator.webkitGetUserMedia) {
  264. //webkit核心浏览器
  265. navigator.webkitGetUserMedia(constraints, success, error);
  266. } else if (navigator.mozGetUserMedia) {
  267. //firfox浏览器
  268. navigator.mozGetUserMedia(constraints, success, error);
  269. } else if (navigator.getUserMedia) {
  270. //旧版API
  271. navigator.getUserMedia(constraints, success, error);
  272. }
  273. }
  274. import {
  275. ref,
  276. nextTick
  277. } from "vue"
  278. import {useZhuapaiStore} from "@/store/zhuapai.js"
  279. // H5 播放抓拍功能
  280. export function useH5Camera({
  281. elVideoId,
  282. elCanvasId,
  283. onVideoSuccess, // 成功播放回调
  284. onVideoError, // 失败回调
  285. zhuapaiHttp, // 抓拍接口将base64 上传
  286. }) {
  287. const zhuapaiStore = useZhuapaiStore();
  288. const videoRef = ref('');
  289. videoRef.value = document.querySelector(`${elVideoId} .uni-video-video`);
  290. function videoSuccessFun(MediaStream) {
  291. // 赋值流
  292. videoRef.value.srcObject = MediaStream;
  293. // 设置video监听 为了确实获取视频播放判断有视频数据流
  294. addVideoListener();
  295. // 播放video 执行播放操作
  296. playVideo();
  297. }
  298. function addVideoListener() {
  299. videoRef && videoRef.value.addEventListener('play', onVideoPlay);
  300. }
  301. function removeVideoListener() {
  302. videoRef && videoRef.value.removeEventListener('play', onVideoPlay);
  303. }
  304. function playVideo() {
  305. if (videoRef.value) {
  306. videoRef.value.play();
  307. }
  308. }
  309. function onVideoPlay() {
  310. onVideoSuccess && onVideoSuccess();
  311. }
  312. function videoErrorFun(e) {
  313. console.log('错误', e)
  314. removeVideoListener();
  315. onVideoError && onVideoError(e)
  316. }
  317. // 主动开启
  318. function startH5Camera() {
  319. check2(videoSuccessFun, videoErrorFun);
  320. }
  321. // 主动关闭
  322. function stopH5Camera() {
  323. // 重置虚拟流
  324. const stream = videoRef && videoRef.value.srcObject;
  325. if (!stream) {
  326. return;
  327. }
  328. const tracks = stream.getTracks();
  329. tracks.forEach(function(track) {
  330. track.stop();
  331. });
  332. // 销毁视频资源
  333. videoRef.value.srcObject = null;
  334. // 移除监听
  335. removeVideoListener && removeVideoListener();
  336. }
  337. function handlePaiZhao() {
  338. try {
  339. const streamActive = videoRef.value.srcObject.active;
  340. // 判断视频流 是否运行
  341. if (!streamActive) {
  342. onVideoError && onVideoError(new Error('摄像头抓拍异常'))
  343. return;
  344. }
  345. } catch (e) {
  346. onVideoError && onVideoError(new Error('摄像头抓拍异常'))
  347. return;
  348. }
  349. try {
  350. if (zhuapaiStore.status == 0) {
  351. // status 为 如果抓拍过程中 应用进入后台 传递固定图片 0为正常 1为进入后台
  352. let canvas = document.querySelector(`${elCanvasId} .uni-canvas-canvas`);
  353. let context = canvas.getContext('2d');
  354. context.drawImage(videoRef.value, 0, 0, videoRef.value.clientWidth, videoRef.value.clientHeight);
  355. const ImageFile = context.canvas.toDataURL('image/png');
  356. getSnapShotImage(ImageFile);
  357. } else {
  358. const ImageFile = this.getBase64Image()
  359. getSnapShotImage(ImageFile);
  360. }
  361. } catch (err) {
  362. console.error('源 :绘图失败', err);
  363. }
  364. }
  365. function getBase64Image() {
  366. var canvas = document.createElement("canvas");
  367. var ctx = canvas.getContext("2d");
  368. canvas.width = 480;
  369. canvas.height = 320;
  370. ctx.drawImage(document.querySelector('#gdImg'), 0, 0, 480, 320);
  371. let image = new Image();
  372. image.src = canvas.toDataURL('image/png', 1)
  373. setTimeout(res => {
  374. ctx.clearRect(0, 0, 480, 320)
  375. }, 500)
  376. return image.src
  377. }
  378. function getSnapShotImage(data) {
  379. console.log('base64', data)
  380. const imgData = data.split(';base64,');
  381. if (!imgData.length) {
  382. console.error('【源 :拍照数据异常,未找到图片二进制数据分割节点: `;base64,`】');
  383. return;
  384. }
  385. const opt = {
  386. data: imgData[1],
  387. prefix: 'kaoshi/zhuapai',
  388. suffix: 'png',
  389. };
  390. console.log('optoptopt', opt)
  391. zhuapaiHttp && zhuapaiHttp(opt)
  392. .then(res => {
  393. console.log('【源 : 获取抓拍数据】');
  394. this.$emit('getImage', res.data);
  395. })
  396. .catch(err => {
  397. console.error('源 :抓拍接口异常', err);
  398. uni.showToast({
  399. icon: 'none',
  400. title: '抓拍图片异常!'
  401. })
  402. uni.redirectTo({
  403. url: "/pages/client/Kaoshi/list"
  404. })
  405. });
  406. }
  407. return {
  408. startH5Camera,
  409. stopH5Camera,
  410. handlePaiZhao
  411. }
  412. }