| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:async'; |
| import 'dart:html' as html; |
| import 'dart:math'; |
| |
| import 'package:camera_platform_interface/camera_platform_interface.dart'; |
| import 'package:camera_web/src/camera_settings.dart'; |
| import 'package:camera_web/src/types/types.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:flutter/services.dart'; |
| import 'package:flutter_web_plugins/flutter_web_plugins.dart'; |
| |
| /// The web implementation of [CameraPlatform]. |
| /// |
| /// This class implements the `package:camera` functionality for the web. |
| class CameraPlugin extends CameraPlatform { |
| /// Creates a new instance of [CameraPlugin] |
| /// with the given [cameraSettings] utility. |
| CameraPlugin({required CameraSettings cameraSettings}) |
| : _cameraSettings = cameraSettings; |
| |
| /// Registers this class as the default instance of [CameraPlatform]. |
| static void registerWith(Registrar registrar) { |
| CameraPlatform.instance = CameraPlugin( |
| cameraSettings: CameraSettings(), |
| ); |
| } |
| |
| final CameraSettings _cameraSettings; |
| |
| /// Metadata associated with each camera description. |
| /// Populated in [availableCameras]. |
| @visibleForTesting |
| final camerasMetadata = <CameraDescription, CameraMetadata>{}; |
| |
| /// The current browser window used to access media devices. |
| @visibleForTesting |
| html.Window? window = html.window; |
| |
| @override |
| Future<List<CameraDescription>> availableCameras() async { |
| final mediaDevices = window?.navigator.mediaDevices; |
| final cameras = <CameraDescription>[]; |
| |
| // Throw a not supported exception if the current browser window |
| // does not support any media devices. |
| if (mediaDevices == null) { |
| throw CameraException( |
| CameraErrorCodes.notSupported, |
| 'The camera is not supported on this device.', |
| ); |
| } |
| |
| // Request available media devices. |
| final devices = await mediaDevices.enumerateDevices(); |
| |
| // Filter video input devices. |
| final videoInputDevices = devices |
| .whereType<html.MediaDeviceInfo>() |
| .where((device) => device.kind == MediaDeviceKind.videoInput) |
| |
| /// The device id property is currently not supported on Internet Explorer: |
| /// https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo/deviceId#browser_compatibility |
| .where((device) => device.deviceId != null); |
| |
| // Map video input devices to camera descriptions. |
| for (final videoInputDevice in videoInputDevices) { |
| // Get the video stream for the current video input device |
| // to later use for the available video tracks. |
| final videoStream = await _getVideoStreamForDevice( |
| mediaDevices, |
| videoInputDevice.deviceId!, |
| ); |
| |
| // Get all video tracks in the video stream |
| // to later extract the lens direction from the first track. |
| final videoTracks = videoStream.getVideoTracks(); |
| |
| if (videoTracks.isNotEmpty) { |
| // Get the facing mode from the first available video track. |
| final facingMode = _cameraSettings.getFacingModeForVideoTrack( |
| videoTracks.first, |
| ); |
| |
| // Get the lens direction based on the facing mode. |
| // Fallback to the external lens direction |
| // if the facing mode is not available. |
| final lensDirection = facingMode != null |
| ? _cameraSettings.mapFacingModeToLensDirection(facingMode) |
| : CameraLensDirection.external; |
| |
| // Create a camera description. |
| // |
| // The name is a camera label which might be empty |
| // if no permissions to media devices have been granted. |
| // |
| // MediaDeviceInfo.label: |
| // https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo/label |
| // |
| // Sensor orientation is currently not supported. |
| final cameraLabel = videoInputDevice.label ?? ''; |
| final camera = CameraDescription( |
| name: cameraLabel, |
| lensDirection: lensDirection, |
| sensorOrientation: 0, |
| ); |
| |
| final cameraMetadata = CameraMetadata( |
| deviceId: videoInputDevice.deviceId!, |
| facingMode: facingMode, |
| ); |
| |
| cameras.add(camera); |
| |
| camerasMetadata[camera] = cameraMetadata; |
| } else { |
| // Ignore as no video tracks exist in the current video input device. |
| continue; |
| } |
| } |
| |
| return cameras; |
| } |
| |
| @override |
| Future<int> createCamera( |
| CameraDescription cameraDescription, |
| ResolutionPreset? resolutionPreset, { |
| bool enableAudio = false, |
| }) { |
| throw UnimplementedError('createCamera() is not implemented.'); |
| } |
| |
| @override |
| Future<void> initializeCamera( |
| int cameraId, { |
| ImageFormatGroup imageFormatGroup = ImageFormatGroup.unknown, |
| }) { |
| throw UnimplementedError('initializeCamera() is not implemented.'); |
| } |
| |
| @override |
| Stream<CameraInitializedEvent> onCameraInitialized(int cameraId) { |
| throw UnimplementedError('onCameraInitialized() is not implemented.'); |
| } |
| |
| @override |
| Stream<CameraResolutionChangedEvent> onCameraResolutionChanged(int cameraId) { |
| throw UnimplementedError('onCameraResolutionChanged() is not implemented.'); |
| } |
| |
| @override |
| Stream<CameraClosingEvent> onCameraClosing(int cameraId) { |
| throw UnimplementedError('onCameraClosing() is not implemented.'); |
| } |
| |
| @override |
| Stream<CameraErrorEvent> onCameraError(int cameraId) { |
| throw UnimplementedError('onCameraError() is not implemented.'); |
| } |
| |
| @override |
| Stream<VideoRecordedEvent> onVideoRecordedEvent(int cameraId) { |
| throw UnimplementedError('onVideoRecordedEvent() is not implemented.'); |
| } |
| |
| @override |
| Stream<DeviceOrientationChangedEvent> onDeviceOrientationChanged() { |
| throw UnimplementedError( |
| 'onDeviceOrientationChanged() is not implemented.', |
| ); |
| } |
| |
| @override |
| Future<void> lockCaptureOrientation( |
| int cameraId, |
| DeviceOrientation orientation, |
| ) { |
| throw UnimplementedError('lockCaptureOrientation() is not implemented.'); |
| } |
| |
| @override |
| Future<void> unlockCaptureOrientation(int cameraId) { |
| throw UnimplementedError('unlockCaptureOrientation() is not implemented.'); |
| } |
| |
| @override |
| Future<XFile> takePicture(int cameraId) { |
| throw UnimplementedError('takePicture() is not implemented.'); |
| } |
| |
| @override |
| Future<void> prepareForVideoRecording() { |
| throw UnimplementedError('prepareForVideoRecording() is not implemented.'); |
| } |
| |
| @override |
| Future<void> startVideoRecording(int cameraId, {Duration? maxVideoDuration}) { |
| throw UnimplementedError('startVideoRecording() is not implemented.'); |
| } |
| |
| @override |
| Future<XFile> stopVideoRecording(int cameraId) { |
| throw UnimplementedError('stopVideoRecording() is not implemented.'); |
| } |
| |
| @override |
| Future<void> pauseVideoRecording(int cameraId) { |
| throw UnimplementedError('pauseVideoRecording() is not implemented.'); |
| } |
| |
| @override |
| Future<void> resumeVideoRecording(int cameraId) { |
| throw UnimplementedError('resumeVideoRecording() is not implemented.'); |
| } |
| |
| @override |
| Future<void> setFlashMode(int cameraId, FlashMode mode) { |
| throw UnimplementedError('setFlashMode() is not implemented.'); |
| } |
| |
| @override |
| Future<void> setExposureMode(int cameraId, ExposureMode mode) { |
| throw UnimplementedError('setExposureMode() is not implemented.'); |
| } |
| |
| @override |
| Future<void> setExposurePoint(int cameraId, Point<double>? point) { |
| throw UnimplementedError('setExposurePoint() is not implemented.'); |
| } |
| |
| @override |
| Future<double> getMinExposureOffset(int cameraId) { |
| throw UnimplementedError('getMinExposureOffset() is not implemented.'); |
| } |
| |
| @override |
| Future<double> getMaxExposureOffset(int cameraId) { |
| throw UnimplementedError('getMaxExposureOffset() is not implemented.'); |
| } |
| |
| @override |
| Future<double> getExposureOffsetStepSize(int cameraId) { |
| throw UnimplementedError('getExposureOffsetStepSize() is not implemented.'); |
| } |
| |
| @override |
| Future<double> setExposureOffset(int cameraId, double offset) { |
| throw UnimplementedError('setExposureOffset() is not implemented.'); |
| } |
| |
| @override |
| Future<void> setFocusMode(int cameraId, FocusMode mode) { |
| throw UnimplementedError('setFocusMode() is not implemented.'); |
| } |
| |
| @override |
| Future<void> setFocusPoint(int cameraId, Point<double>? point) { |
| throw UnimplementedError('setFocusPoint() is not implemented.'); |
| } |
| |
| @override |
| Future<double> getMaxZoomLevel(int cameraId) { |
| throw UnimplementedError('getMaxZoomLevel() is not implemented.'); |
| } |
| |
| @override |
| Future<double> getMinZoomLevel(int cameraId) { |
| throw UnimplementedError('getMinZoomLevel() is not implemented.'); |
| } |
| |
| @override |
| Future<void> setZoomLevel(int cameraId, double zoom) { |
| throw UnimplementedError('setZoomLevel() is not implemented.'); |
| } |
| |
| @override |
| Widget buildPreview(int cameraId) { |
| throw UnimplementedError('buildPreview() is not implemented.'); |
| } |
| |
| @override |
| Future<void> dispose(int cameraId) { |
| throw UnimplementedError('dispose() is not implemented.'); |
| } |
| |
| /// Returns a media video stream for the device with the given [deviceId]. |
| Future<html.MediaStream> _getVideoStreamForDevice( |
| html.MediaDevices mediaDevices, |
| String deviceId, |
| ) { |
| // Create camera options with the desired device id. |
| final cameraOptions = CameraOptions( |
| video: VideoConstraints(deviceId: deviceId), |
| ); |
| |
| return mediaDevices.getUserMedia(cameraOptions.toJson()); |
| } |
| } |