forked from flutter/photobooth
-
Notifications
You must be signed in to change notification settings - Fork 5
feat(camera_web): add camera with options to play, stop and take a picture #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from 2 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
d6990d3
feat(camera_web): add camera with options to play, stop and take a pi…
bselwe a19f383
test(camera_web): add camera tests
bselwe ca6a65b
Merge branch 'feat/camera-options' into feat/camera
bselwe 44508fc
Merge branch 'feat/camera-options' into feat/camera
bselwe 2b93790
refactor: replace VideoElement extension to apply default styles with…
bselwe 55904a3
docs: add camera comments
bselwe cfadeea
Merge branch 'feat/camera-options' into feat/camera
bselwe 2894e2c
Merge branch 'feat/camera-options' into feat/camera
bselwe bce3a7a
test: update test video source
bselwe File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| import 'dart:html' as html; | ||
| import 'dart:ui' as ui; | ||
|
|
||
| import 'package:camera_platform_interface/camera_platform_interface.dart'; | ||
| import 'package:camera_web/src/types/camera_error_codes.dart'; | ||
| import 'package:camera_web/src/types/camera_options.dart'; | ||
|
|
||
| String _getViewType(int cameraId) => 'plugins.flutter.io/camera_$cameraId'; | ||
|
|
||
| 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 CameraException( | ||
| CameraErrorCodes.notSupported, | ||
| 'The camera is not supported on this device.', | ||
| ); | ||
| } | ||
|
|
||
| videoElement = html.VideoElement()..applyDefaultStyles(); | ||
| divElement = html.DivElement() | ||
| ..style.setProperty('object-fit', 'cover') | ||
| ..append(videoElement); | ||
|
|
||
| // ignore: avoid_dynamic_calls | ||
| ui.platformViewRegistry.registerViewFactory( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now that you have all these... Wouldn't it be super easy to add an initial version of
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True! Could be one of the next PRs. |
||
| _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) { | ||
bselwe marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| switch (e.name) { | ||
| case 'NotFoundError': | ||
| case 'DevicesNotFoundError': | ||
| throw CameraException( | ||
| CameraErrorCodes.notFound, | ||
| 'No camera found for the given camera options.', | ||
| ); | ||
| case 'NotReadableError': | ||
| case 'TrackStartError': | ||
| throw CameraException( | ||
| CameraErrorCodes.notReadable, | ||
| 'The camera is not readable due to a hardware error ' | ||
| 'that prevented access to the device.', | ||
| ); | ||
| case 'OverconstrainedError': | ||
| case 'ConstraintNotSatisfiedError': | ||
| throw CameraException( | ||
| CameraErrorCodes.overconstrained, | ||
| 'Provided camera options are impossible to satisfy.', | ||
| ); | ||
| case 'NotAllowedError': | ||
| case 'PermissionDeniedError': | ||
| throw CameraException( | ||
| CameraErrorCodes.permissionDenied, | ||
| 'The camera cannot be used or the permission ' | ||
| 'to access the camera is not granted.', | ||
| ); | ||
| case 'TypeError': | ||
| throw CameraException( | ||
| CameraErrorCodes.type, | ||
| 'Provided camera options are incorrect.', | ||
| ); | ||
| default: | ||
| throw CameraException( | ||
| CameraErrorCodes.unknown, | ||
| 'An unknown error occured when initializing the camera.', | ||
| ); | ||
| } | ||
| } catch (_) { | ||
| throw CameraException( | ||
| CameraErrorCodes.unknown, | ||
| 'An unknown error occured when initializing the camera.', | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| 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; | ||
| } | ||
|
|
||
| Future<XFile> 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 XFile(html.Url.createObjectUrl(blob)); | ||
| } | ||
|
|
||
| void dispose() { | ||
| stop(); | ||
| videoElement | ||
| ..srcObject = null | ||
| ..load(); | ||
| } | ||
| } | ||
|
|
||
| extension on html.VideoElement { | ||
| void applyDefaultStyles() { | ||
| style | ||
bselwe marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ..removeProperty('transform-origin') | ||
| ..setProperty('pointer-events', 'none') | ||
| ..setProperty('width', '100%') | ||
| ..setProperty('height', '100%') | ||
| ..setProperty('object-fit', 'cover') | ||
| ..setProperty('transform', 'scaleX(-1)') | ||
| ..setProperty('-webkit-transform', 'scaleX(-1)') | ||
| ..setProperty('-moz-transform', 'scaleX(-1)'); | ||
| } | ||
| } | ||
9 changes: 9 additions & 0 deletions
9
packages/camera/camera_web/lib/src/types/camera_error_codes.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| abstract class CameraErrorCodes { | ||
| static const notSupported = 'cameraNotSupported'; | ||
| static const notFound = 'cameraNotFound'; | ||
| static const notReadable = 'cameraNotReadable'; | ||
| static const overconstrained = 'cameraOverconstrained'; | ||
| static const permissionDenied = 'cameraPermission'; | ||
| static const type = 'cameraType'; | ||
| static const unknown = 'cameraUnknown'; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export 'mocks.dart'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import 'dart:html'; | ||
|
|
||
| import 'package:mocktail/mocktail.dart'; | ||
|
|
||
| class MockWindow extends Mock implements Window {} | ||
|
|
||
| class MockNavigator extends Mock implements Navigator {} | ||
|
|
||
| class MockMediaDevices extends Mock implements MediaDevices {} | ||
|
|
||
| class FakeMediaStreamTrack extends Fake implements MediaStreamTrack {} | ||
|
|
||
| /// A fake [DomException] that returns the provided [errorName]. | ||
| class FakeDomException extends Fake implements DomException { | ||
| FakeDomException(this.errorName); | ||
|
|
||
| final String errorName; | ||
|
|
||
| @override | ||
| String get name => errorName; | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.