1import { UnavailabilityError } from 'expo-modules-core'; 2 3import { 4 CameraCapturedPicture, 5 CameraPictureOptions, 6 CameraType, 7 PermissionResponse, 8 PermissionStatus, 9} from './Camera.types'; 10import { ExponentCameraRef } from './ExponentCamera.web'; 11import { 12 canGetUserMedia, 13 isBackCameraAvailableAsync, 14 isFrontCameraAvailableAsync, 15} from './WebUserMediaManager'; 16 17function getUserMedia(constraints: MediaStreamConstraints): Promise<MediaStream> { 18 if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { 19 return navigator.mediaDevices.getUserMedia(constraints); 20 } 21 22 // Some browsers partially implement mediaDevices. We can't just assign an object 23 // with getUserMedia as it would overwrite existing properties. 24 // Here, we will just add the getUserMedia property if it's missing. 25 26 // First get ahold of the legacy getUserMedia, if present 27 const getUserMedia = 28 // TODO: this method is deprecated, migrate to https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia 29 navigator.getUserMedia || 30 navigator.webkitGetUserMedia || 31 navigator.mozGetUserMedia || 32 function () { 33 const error: any = new Error('Permission unimplemented'); 34 error.code = 0; 35 error.name = 'NotAllowedError'; 36 throw error; 37 }; 38 39 return new Promise((resolve, reject) => { 40 getUserMedia.call(navigator, constraints, resolve, reject); 41 }); 42} 43 44function handleGetUserMediaError({ message }: { message: string }): PermissionResponse { 45 // name: NotAllowedError 46 // code: 0 47 if (message === 'Permission dismissed') { 48 return { 49 status: PermissionStatus.UNDETERMINED, 50 expires: 'never', 51 canAskAgain: true, 52 granted: false, 53 }; 54 } else { 55 // TODO: Bacon: [OSX] The system could deny access to chrome. 56 // TODO: Bacon: add: { status: 'unimplemented' } 57 return { 58 status: PermissionStatus.DENIED, 59 expires: 'never', 60 canAskAgain: true, 61 granted: false, 62 }; 63 } 64} 65 66async function handleRequestPermissionsAsync(): Promise<PermissionResponse> { 67 try { 68 await getUserMedia({ 69 video: true, 70 }); 71 return { 72 status: PermissionStatus.GRANTED, 73 expires: 'never', 74 canAskAgain: true, 75 granted: true, 76 }; 77 } catch ({ message }) { 78 return handleGetUserMediaError({ message }); 79 } 80} 81 82async function handlePermissionsQueryAsync( 83 query: 'camera' | 'microphone' 84): Promise<PermissionResponse> { 85 if (!navigator?.permissions?.query) { 86 throw new UnavailabilityError('expo-camera', 'navigator.permissions API is not available'); 87 } 88 89 const { state } = await navigator.permissions.query({ name: query }); 90 switch (state) { 91 case 'prompt': 92 return { 93 status: PermissionStatus.UNDETERMINED, 94 expires: 'never', 95 canAskAgain: true, 96 granted: false, 97 }; 98 case 'granted': 99 return { 100 status: PermissionStatus.GRANTED, 101 expires: 'never', 102 canAskAgain: true, 103 granted: true, 104 }; 105 case 'denied': 106 return { 107 status: PermissionStatus.DENIED, 108 expires: 'never', 109 canAskAgain: true, 110 granted: false, 111 }; 112 } 113} 114 115export default { 116 get name(): string { 117 return 'ExponentCameraManager'; 118 }, 119 get Type() { 120 return { 121 back: 'back', 122 front: 'front', 123 }; 124 }, 125 get FlashMode() { 126 return { 127 on: 'on', 128 off: 'off', 129 auto: 'auto', 130 torch: 'torch', 131 }; 132 }, 133 get AutoFocus() { 134 return { 135 on: 'on', 136 off: 'off', 137 auto: 'auto', 138 singleShot: 'singleShot', 139 }; 140 }, 141 get WhiteBalance() { 142 return { 143 auto: 'auto', 144 continuous: 'continuous', 145 manual: 'manual', 146 }; 147 }, 148 get VideoQuality() { 149 return {}; 150 }, 151 get VideoStabilization() { 152 return {}; 153 }, 154 async isAvailableAsync(): Promise<boolean> { 155 return canGetUserMedia(); 156 }, 157 async takePicture( 158 options: CameraPictureOptions, 159 camera: ExponentCameraRef 160 ): Promise<CameraCapturedPicture> { 161 return await camera.takePicture(options); 162 }, 163 async pausePreview(camera: ExponentCameraRef): Promise<void> { 164 await camera.pausePreview(); 165 }, 166 async resumePreview(camera: ExponentCameraRef): Promise<void> { 167 return await camera.resumePreview(); 168 }, 169 async getAvailableCameraTypesAsync(): Promise<string[]> { 170 if (!canGetUserMedia() || !navigator.mediaDevices.enumerateDevices) return []; 171 172 const devices = await navigator.mediaDevices.enumerateDevices(); 173 174 const types: (string | null)[] = await Promise.all([ 175 (await isFrontCameraAvailableAsync(devices)) && CameraType.front, 176 (await isBackCameraAvailableAsync()) && CameraType.back, 177 ]); 178 179 return types.filter(Boolean) as string[]; 180 }, 181 async getAvailablePictureSizes(ratio: string, camera: ExponentCameraRef): Promise<string[]> { 182 return await camera.getAvailablePictureSizes(ratio); 183 }, 184 /* async getSupportedRatios(camera: ExponentCameraRef): Promise<string[]> { 185 // TODO: Support on web 186 }, 187 async record( 188 options?: CameraRecordingOptions, 189 camera: ExponentCameraRef 190 ): Promise<{ uri: string }> { 191 // TODO: Support on web 192 }, 193 async stopRecording(camera: ExponentCameraRef): Promise<void> { 194 // TODO: Support on web 195 }, */ 196 async getPermissionsAsync(): Promise<PermissionResponse> { 197 return handlePermissionsQueryAsync('camera'); 198 }, 199 async requestPermissionsAsync(): Promise<PermissionResponse> { 200 return handleRequestPermissionsAsync(); 201 }, 202 async getCameraPermissionsAsync(): Promise<PermissionResponse> { 203 return handlePermissionsQueryAsync('camera'); 204 }, 205 async requestCameraPermissionsAsync(): Promise<PermissionResponse> { 206 return handleRequestPermissionsAsync(); 207 }, 208 async getMicrophonePermissionsAsync(): Promise<PermissionResponse> { 209 return handlePermissionsQueryAsync('microphone'); 210 }, 211 async requestMicrophonePermissionsAsync(): Promise<PermissionResponse> { 212 try { 213 await getUserMedia({ 214 audio: true, 215 }); 216 return { 217 status: PermissionStatus.GRANTED, 218 expires: 'never', 219 canAskAgain: true, 220 granted: true, 221 }; 222 } catch ({ message }) { 223 return handleGetUserMediaError({ message }); 224 } 225 }, 226}; 227