Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

camera-source.ts 5.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. /*
  2. * encantar.js
  3. * GPU-accelerated Augmented Reality for the web
  4. * Copyright (C) 2022-2024 Alexandre Martins <alemartf(at)gmail.com>
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Lesser General Public License as published
  8. * by the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. *
  19. * camera-source.ts
  20. * Webcam-based source of data
  21. */
  22. import Speedy from 'speedy-vision';
  23. import { SpeedyMedia } from 'speedy-vision/types/core/speedy-media';
  24. import { SpeedyPromise } from 'speedy-vision/types/core/speedy-promise';
  25. import { Utils } from '../utils/utils';
  26. import { Resolution } from '../utils/resolution';
  27. import { NotSupportedError, AccessDeniedError, IllegalOperationError } from '../utils/errors';
  28. import { VideoSource } from './video-source';
  29. /**
  30. * Options for spawning a Webcam-based source of data
  31. */
  32. export interface CameraSourceOptions
  33. {
  34. /** resolution type for the captured images */
  35. resolution?: Resolution;
  36. /** a hint for the desired aspect ratio */
  37. aspectRatio?: number;
  38. /** additional video constraints to be passed to navigator.mediaDevices.getUserMedia() */
  39. constraints?: MediaTrackConstraints;
  40. }
  41. /** Default options for camera sources */
  42. const DEFAULT_CAMERA_OPTIONS: Readonly<Required<CameraSourceOptions>> = {
  43. resolution: 'md',
  44. aspectRatio: 16/9,
  45. constraints: { facingMode: 'environment' },
  46. };
  47. /**
  48. * Webcam-based source of data
  49. */
  50. export class CameraSource extends VideoSource
  51. {
  52. /** Options of the constructor */
  53. private _options: Required<CameraSourceOptions>;
  54. /**
  55. * Constructor
  56. * @param options
  57. */
  58. constructor(options: CameraSourceOptions)
  59. {
  60. const video = document.createElement('video');
  61. super(video);
  62. this._options = Object.assign({}, DEFAULT_CAMERA_OPTIONS, options);
  63. }
  64. /**
  65. * Camera resolution
  66. */
  67. get resolution(): Resolution
  68. {
  69. return this._options.resolution;
  70. }
  71. /**
  72. * Initialize this source of data
  73. * @returns a promise that resolves as soon as this source of data is initialized
  74. * @internal
  75. */
  76. _init(): SpeedyPromise<void>
  77. {
  78. Utils.log('Accessing the webcam...');
  79. // validate
  80. if(!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia)
  81. throw new NotSupportedError('Unsupported browser: no navigator.mediaDevices.getUserMedia()');
  82. // set up media constraints
  83. const options = this._options;
  84. const size = Utils.resolution(options.resolution, options.aspectRatio);
  85. const constraints: MediaStreamConstraints = {
  86. audio: false,
  87. video: {
  88. width: size.width,
  89. height: size.height,
  90. aspectRatio: options.aspectRatio,
  91. ...options.constraints,
  92. }
  93. };
  94. // load camera stream
  95. return new Speedy.Promise<HTMLVideoElement>((resolve, reject) => {
  96. navigator.mediaDevices.getUserMedia(constraints).then(stream => {
  97. const video = this.video;
  98. video.onloadedmetadata = () => {
  99. const promise = video.play();
  100. const success = 'Access to the webcam has been granted.';
  101. // handle older browsers
  102. if(promise === undefined) {
  103. Utils.log(success);
  104. resolve(video);
  105. return;
  106. }
  107. // handle promise
  108. promise.then(() => {
  109. Utils.log(success);
  110. resolve(video);
  111. }).catch(error => {
  112. reject(new IllegalOperationError(
  113. 'Webcam error!',
  114. error
  115. ));
  116. });
  117. };
  118. video.setAttribute('playsinline', '');
  119. video.setAttribute('autoplay', '');
  120. video.setAttribute('muted', '');
  121. video.srcObject = stream;
  122. }).catch(error => {
  123. reject(new AccessDeniedError(
  124. 'Please give access to the webcam and reload the page.',
  125. error
  126. ));
  127. });
  128. }).then(_ => super._init()); // this will handle browser quirks
  129. }
  130. /**
  131. * Release this source of data
  132. * @returns a promise that resolves as soon as this source of data is released
  133. * @internal
  134. */
  135. _release(): SpeedyPromise<void>
  136. {
  137. const video = this.video;
  138. const stream = video.srcObject as MediaStream;
  139. const tracks = stream.getTracks();
  140. // stop camera feed
  141. tracks.forEach(track => track.stop());
  142. video.onloadedmetadata = null;
  143. video.srcObject = null;
  144. // release the media
  145. return super._release();
  146. }
  147. }