Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions packages/camera/camera/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ flutter:
dependencies:
flutter:
sdk: flutter
camera_platform_interface:
path: ../camera_platform_interface
camera_web:
path: ../camera_web

Expand Down
311 changes: 133 additions & 178 deletions packages/camera/camera_web/lib/src/camera_web.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import 'dart:async';
import 'dart:html' as html;
import 'dart:ui' as ui;
import 'dart:math';

import 'package:camera_platform_interface/camera_platform_interface.dart';
import 'package:camera_web/camera_web.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';

String _getViewType(int cameraId) => 'plugins.flutter.io/camera_$cameraId';

/// The web implementation of [CameraPlatform].
///
/// This class implements the `package:camera` functionality for the web.
Expand All @@ -18,213 +16,170 @@ class CameraPlugin extends CameraPlatform {
CameraPlatform.instance = CameraPlugin();
}

final _cameras = <int, Camera>{};
var _textureCounter = 1;

@visibleForTesting
html.Window? window;

@override
Future<void> init() async {
_disposeAllCameras();
Future<List<CameraDescription>> availableCameras() {
throw UnimplementedError('availableCameras() is not implemented.');
}

@override
Future<int> createCamera(
CameraDescription cameraDescription,
ResolutionPreset? resolutionPreset, {
bool enableAudio = false,
}) {
throw UnimplementedError('createCamera() is not implemented.');
}

@override
Future<void> dispose(int textureId) async {
_cameras[textureId]!.dispose();
_cameras.remove(textureId);
Future<void> initializeCamera(
int cameraId, {
ImageFormatGroup imageFormatGroup = ImageFormatGroup.unknown,
}) {
throw UnimplementedError('initializeCamera() is not implemented.');
}

@override
Future<int> create(CameraOptions options) async {
final textureId = _textureCounter;
_textureCounter++;
Stream<CameraInitializedEvent> onCameraInitialized(int cameraId) {
throw UnimplementedError('onCameraInitialized() is not implemented.');
}

final camera = Camera(
options: options,
textureId: textureId,
window: window,
);
await camera.initialize();
@override
Stream<CameraResolutionChangedEvent> onCameraResolutionChanged(int cameraId) {
throw UnimplementedError('onCameraResolutionChanged() is not implemented.');
}

_cameras[textureId] = camera;
return textureId;
@override
Stream<CameraClosingEvent> onCameraClosing(int cameraId) {
throw UnimplementedError('onCameraClosing() is not implemented.');
}

@override
Widget buildView(int textureId) {
return HtmlElementView(viewType: _getViewType(textureId));
Stream<CameraErrorEvent> onCameraError(int cameraId) {
throw UnimplementedError('onCameraError() is not implemented.');
}

@override
Future<void> play(int textureId) => _cameras[textureId]!.play();
Stream<VideoRecordedEvent> onVideoRecordedEvent(int cameraId) {
throw UnimplementedError('onVideoRecordedEvent() is not implemented.');
}

@override
Future<void> stop(int textureId) async => _cameras[textureId]!.stop();
Stream<DeviceOrientationChangedEvent> onDeviceOrientationChanged() {
throw UnimplementedError(
'onDeviceOrientationChanged() is not implemented.');
}

@override
Future<CameraImage> takePicture(int textureId) {
return _cameras[textureId]!.takePicture();
Future<void> lockCaptureOrientation(
int cameraId, DeviceOrientation orientation) {
throw UnimplementedError('lockCaptureOrientation() is not implemented.');
}

void _disposeAllCameras() {
for (final camera in _cameras.values) {
camera.dispose();
}
_cameras.clear();
@override
Future<void> unlockCaptureOrientation(int cameraId) {
throw UnimplementedError('unlockCaptureOrientation() is not implemented.');
}

@override
Future<List<MediaDeviceInfo>> getMediaDevices() async {
final videoDevices = <MediaDeviceInfo>[];
if (html.window.navigator.mediaDevices != null) {
final devices =
await html.window.navigator.mediaDevices?.enumerateDevices() ?? [];
for (var deviceIndex = 0; deviceIndex < devices.length; deviceIndex++) {
dynamic device = devices[deviceIndex];
if (device is html.MediaDeviceInfo && device.kind == 'videoinput') {
videoDevices.add(
MediaDeviceInfo(deviceId: device.deviceId, label: device.label));
}
}
}
return videoDevices;
Future<XFile> takePicture(int cameraId) {
throw UnimplementedError('takePicture() is not implemented.');
}

@override
Future<String?> getDefaultDeviceId() async {
String? defaultId = VideoConstraints.defaultDeviceId;
if (browserEngine != BrowserEngine.blink) {
/// For browsers other than Chrome, enumerate video devices and return
/// first one since it is the default selected for the browser.
final devices = await getMediaDevices();
if (devices.isNotEmpty) {
defaultId = devices[0].deviceId;
}
}
return defaultId;
Future<void> prepareForVideoRecording() {
throw UnimplementedError('prepareForVideoRecording() is not implemented.');
}
}

class Camera {
Camera({
required this.textureId,
this.options = const CameraOptions(),
html.Window? window,
}) : window = window ?? html.window;

late html.VideoElement videoElement;
late html.DivElement divElement;
final CameraOptions options;
final int textureId;
final html.Window window;

Future<void> initialize() async {
final isSupported = window.navigator.mediaDevices?.getUserMedia != null;
if (!isSupported) {
throw const CameraNotSupportedException();
}

videoElement = html.VideoElement()..applyDefaultStyles();
divElement = html.DivElement()
..style.setProperty('object-fit', 'cover')
..append(videoElement);
// ignore: avoid_dynamic_calls
ui.platformViewRegistry.registerViewFactory(
_getViewType(textureId),
(_) => divElement,
);

final stream = await _getMediaStream();
videoElement
..autoplay = false
..muted = !options.audio.enabled
..srcObject = stream
..setAttribute('playsinline', '');
}

Future<html.MediaStream> _getMediaStream() async {
try {
final constraints = await options.toJson();
return await window.navigator.mediaDevices!.getUserMedia(
constraints,
);
} on html.DomException catch (e) {
switch (e.name) {
case 'NotFoundError':
case 'DevicesNotFoundError':
throw const CameraNotFoundException();
case 'NotReadableError':
case 'TrackStartError':
throw const CameraNotReadableException();
case 'OverconstrainedError':
case 'ConstraintNotSatisfiedError':
throw const CameraOverconstrainedException();
case 'NotAllowedError':
case 'PermissionDeniedError':
throw const CameraNotAllowedException();
case 'TypeError':
throw const CameraTypeException();
default:
throw const CameraUnknownException();
}
} catch (_) {
throw const CameraUnknownException();
}
}

Future<void> play() async {
if (videoElement.srcObject == null) {
final stream = await _getMediaStream();
videoElement.srcObject = stream;
}
await videoElement.play();
}

void stop() {
final tracks = videoElement.srcObject?.getVideoTracks();
if (tracks != null) {
for (final track in tracks) {
track.stop();
}
}
videoElement.srcObject = null;
}

void dispose() {
stop();
videoElement
..srcObject = null
..load();
}

Future<CameraImage> takePicture() async {
final videoWidth = videoElement.videoWidth;
final videoHeight = videoElement.videoHeight;
final canvas = html.CanvasElement(width: videoWidth, height: videoHeight);
canvas.context2D
..translate(videoWidth, 0)
..scale(-1, 1)
..drawImageScaled(videoElement, 0, 0, videoWidth, videoHeight);
final blob = await canvas.toBlob();
return CameraImage(
data: html.Url.createObjectUrl(blob),
width: videoWidth,
height: videoHeight,
);
@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.');
}
}

extension on html.VideoElement {
void applyDefaultStyles() {
style
..removeProperty('transform-origin')
..setProperty('pointer-events', 'none')
..setProperty('width', '100%')
..setProperty('height', '100%')
..setProperty('transform', 'scaleX(-1)')
..setProperty('object-fit', 'cover')
..setProperty('-webkit-transform', 'scaleX(-1)')
..setProperty('-moz-transform', 'scaleX(-1)');
@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.');
}
}
3 changes: 1 addition & 2 deletions packages/camera/camera_web/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ dependencies:
sdk: flutter
flutter_web_plugins:
sdk: flutter
camera_platform_interface:
path: ../camera_platform_interface
camera_platform_interface: ^2.0.1

dev_dependencies:
flutter_test:
Expand Down
Loading