選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

camera-source.ts 5.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  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. /** Video element */
  53. private _cameraVideo: HTMLVideoElement;
  54. /** Options of the constructor */
  55. private _options: Required<CameraSourceOptions>;
  56. /**
  57. * Constructor
  58. */
  59. constructor(options: CameraSourceOptions)
  60. {
  61. const video = document.createElement('video');
  62. super(video);
  63. this._cameraVideo = video;
  64. this._options = Object.assign({}, DEFAULT_CAMERA_OPTIONS, options);
  65. }
  66. /**
  67. * Camera resolution
  68. */
  69. get resolution(): Resolution
  70. {
  71. return this._options.resolution;
  72. }
  73. /**
  74. * Initialize this source of data
  75. * @returns a promise that resolves as soon as this source of data is initialized
  76. * @internal
  77. */
  78. _init(): SpeedyPromise<void>
  79. {
  80. Utils.log('Accessing the webcam...');
  81. // validate
  82. if(!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia)
  83. throw new NotSupportedError('Unsupported browser: no navigator.mediaDevices.getUserMedia()');
  84. // set up media constraints
  85. const options = this._options;
  86. const size = Utils.resolution(options.resolution, options.aspectRatio);
  87. const constraints: MediaStreamConstraints = {
  88. audio: false,
  89. video: {
  90. width: size.width,
  91. height: size.height,
  92. ...options.constraints,
  93. }
  94. };
  95. // load camera stream
  96. return new Speedy.Promise<HTMLVideoElement>((resolve, reject) => {
  97. navigator.mediaDevices.getUserMedia(constraints).then(stream => {
  98. const video = this._cameraVideo;
  99. video.onloadedmetadata = () => {
  100. const promise = video.play();
  101. const success = 'Access to the webcam has been granted.';
  102. // handle older browsers
  103. if(promise === undefined) {
  104. Utils.log(success);
  105. resolve(video);
  106. return;
  107. }
  108. // handle promise
  109. promise.then(() => {
  110. Utils.log(success);
  111. resolve(video);
  112. }).catch(error => {
  113. reject(new IllegalOperationError(
  114. 'Webcam error!',
  115. error
  116. ));
  117. });
  118. };
  119. video.setAttribute('playsinline', '');
  120. video.setAttribute('autoplay', '');
  121. video.setAttribute('muted', '');
  122. video.srcObject = stream;
  123. }).catch(error => {
  124. reject(new AccessDeniedError(
  125. 'Please give access to the webcam and reload the page.',
  126. error
  127. ));
  128. });
  129. }).then(_ => super._init()); // this will call VideoSource._handleBrowserQuirks()
  130. }
  131. /**
  132. * Release this source of data
  133. * @returns a promise that resolves as soon as this source of data is released
  134. * @internal
  135. */
  136. _release(): SpeedyPromise<void>
  137. {
  138. const stream = this._cameraVideo.srcObject as MediaStream;
  139. const tracks = stream.getTracks();
  140. // stop camera feed
  141. tracks.forEach(track => track.stop());
  142. this._cameraVideo.onloadedmetadata = null;
  143. this._cameraVideo.srcObject = null;
  144. // release the media
  145. return super._release();
  146. }
  147. }