useCamera.js 12 KB

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