webar.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. /**
  2. * WebAR基础类
  3. * 摄像头设置参数请查看: https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints
  4. * 如果打开摄像头后,播放视频有卡顿,请尝试设置 frameRate,height与width
  5. */
  6. class WebAR {
  7. /**
  8. * 初始化Web AR
  9. * @param interval 识别间隔(毫秒)
  10. * @param recognizeUrl 识别服务地址
  11. * @param isDebug 是否输入调试信息
  12. * @param token 非必需,使用token认证识别
  13. */
  14. constructor(interval, recognizeUrl, token) {
  15. this.isRecognizing = false;
  16. // 前/后置摄像头
  17. this.cameras = ["user", "environment"];
  18. this.interval = interval;
  19. this.recognizeUrl = recognizeUrl;
  20. this.token = token;
  21. }
  22. /**
  23. * 列表设备上的所有摄像头
  24. * @param videoDevice
  25. * @returns {Promise<T>}
  26. */
  27. listCamera(videoDevice) {
  28. return new Promise((resolve, reject) => {
  29. navigator.mediaDevices
  30. .enumerateDevices()
  31. .then(devices => {
  32. let index = 0;
  33. devices.find(device => {
  34. if (device.kind === "videoinput") {
  35. const option = document.createElement("option");
  36. console.log(device);
  37. // 在iOS12.2上deviceId为空
  38. if (device.deviceId == "") {
  39. option.text =
  40. device.label ||
  41. "camera " + this.cameras[index];
  42. option.value = JSON.stringify({
  43. audio: false,
  44. video: {
  45. facingMode: {
  46. exact: this.cameras[index]
  47. }
  48. }
  49. });
  50. index++;
  51. } else {
  52. option.text =
  53. device.label ||
  54. "camera " +
  55. (videoDevice.length + 1).toString();
  56. option.value = JSON.stringify({
  57. audio: false,
  58. video: {
  59. deviceId: { exact: device.deviceId }
  60. }
  61. });
  62. }
  63. // 将摄像头信息存储在select元素中,方便切换前、后置摄像头
  64. videoDevice.appendChild(option);
  65. }
  66. return false;
  67. });
  68. if (videoDevice.length === 0) {
  69. reject("没有可使用的视频设备");
  70. } else {
  71. this.initVideo();
  72. this.initCanvas();
  73. resolve(true);
  74. }
  75. })
  76. .catch(err => {
  77. reject(err);
  78. });
  79. });
  80. }
  81. /**
  82. * 打开摄像头
  83. * 摄像头设置参数请查看: https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints
  84. * @param videoDeviceIndex
  85. * @returns {Promise<T>}
  86. */
  87. openCamera(constraints) {
  88. // 如果是切换摄像头,则需要先关闭。
  89. if (this.videoElement && this.videoElement.srcObject) {
  90. this.videoElement.srcObject.getTracks().forEach(track => {
  91. track.stop();
  92. });
  93. }
  94. return new Promise((resolve, reject) => {
  95. navigator.mediaDevices
  96. .getUserMedia(constraints)
  97. .then(stream => {
  98. this.videoElement.srcObject = stream;
  99. this.videoElement.style.display = "block";
  100. this.videoElement.play();
  101. this.videoElement.onloadedmetadata = () => {
  102. const cameraSize = {
  103. width: this.videoElement.offsetWidth,
  104. height: this.videoElement.offsetHeight
  105. };
  106. console.info(JSON.stringify(cameraSize));
  107. if (window.innerWidth < window.innerHeight) {
  108. // 竖屏
  109. if (cameraSize.height < window.innerHeight) {
  110. this.videoElement.setAttribute(
  111. "height",
  112. window.innerHeight.toString() + "px"
  113. );
  114. }
  115. } else {
  116. // 横屏
  117. if (cameraSize.width < window.innerWidth) {
  118. this.videoElement.setAttribute(
  119. "width",
  120. window.innerWidth.toString() + "px"
  121. );
  122. }
  123. }
  124. resolve(true);
  125. };
  126. })
  127. .catch(err => {
  128. reject(err);
  129. });
  130. });
  131. }
  132. /**
  133. * 截取摄像头图片
  134. * @returns {string}
  135. */
  136. captureVideo() {
  137. this.canvasContext.drawImage(
  138. this.videoElement,
  139. 0,
  140. 0,
  141. this.videoElement.offsetWidth,
  142. this.videoElement.offsetHeight
  143. );
  144. return this.canvasElement
  145. .toDataURL("image/jpeg", 0.5)
  146. .split("base64,")[1];
  147. }
  148. /**
  149. * 创建视频详情元素,播放摄像头视频流
  150. */
  151. initVideo() {
  152. this.videoElement = document.createElement("video");
  153. this.videoElement.setAttribute("playsinline", "playsinline");
  154. document.body.appendChild(this.videoElement);
  155. }
  156. /**
  157. * 创建canvas,截取摄像头图片时使用
  158. */
  159. initCanvas() {
  160. this.canvasElement = document.createElement("canvas");
  161. this.canvasElement.setAttribute(
  162. "width",
  163. window.innerWidth.toString() + "px"
  164. );
  165. this.canvasElement.setAttribute(
  166. "height",
  167. window.innerHeight.toString() + "px"
  168. );
  169. this.canvasContext = this.canvasElement.getContext("2d");
  170. // document.body.appendChild(this.canvasElement);
  171. }
  172. /**
  173. * 识别
  174. * @param callback
  175. */
  176. startRecognize(callback) {
  177. this.timer = window.setInterval(() => {
  178. // 等待上一次识别结果
  179. if (this.isRecognizing) {
  180. return;
  181. }
  182. this.isRecognizing = true;
  183. // 从摄像头中抓取一张图片
  184. const image = { image: this.captureVideo() };
  185. // 发送到服务器识别
  186. this.httpPost(image)
  187. .then(msg => {
  188. this.stopRecognize();
  189. callback(msg);
  190. })
  191. .catch(err => {
  192. this.isRecognizing = false;
  193. console.info(err);
  194. });
  195. }, this.interval);
  196. }
  197. /**
  198. * 停止识别
  199. */
  200. stopRecognize() {
  201. if (this.timer) {
  202. window.clearInterval(this.timer);
  203. this.isRecognizing = false;
  204. }
  205. }
  206. httpPost(image) {
  207. return new Promise((resolve, reject) => {
  208. const http = new XMLHttpRequest();
  209. http.onload = () => {
  210. try {
  211. const msg = JSON.parse(http.responseText);
  212. if (http.status === 200) {
  213. if (msg.statusCode === 0) {
  214. resolve(msg.result);
  215. } else {
  216. reject(msg);
  217. }
  218. } else {
  219. reject(msg);
  220. }
  221. } catch (err) {
  222. reject(err);
  223. }
  224. };
  225. http.onerror = err => {
  226. reject(err);
  227. };
  228. http.open("POST", this.recognizeUrl);
  229. http.setRequestHeader(
  230. "Content-Type",
  231. "application/json;Charset=UTF-8"
  232. );
  233. if (this.token) {
  234. // 将云识别认证token写在请求头中
  235. http.setRequestHeader("Authorization", this.token);
  236. }
  237. http.send(JSON.stringify(image));
  238. });
  239. }
  240. }
  241. //# sourceMappingURL=webar.js.map