diff --git a/packages/camera/CHANGELOG.md b/packages/camera/CHANGELOG.md index 56cecdf..ca52bc3 100644 --- a/packages/camera/CHANGELOG.md +++ b/packages/camera/CHANGELOG.md @@ -1,3 +1,6 @@ +## 0.2.1 +* Update for camera v0.10.0+1 / flutter 3.3.0 release + ## 0.2.0 * Fix wrong orientation issue. * Change the camera type from back to external. diff --git a/packages/camera/README.md b/packages/camera/README.md index 5d5da50..0cd9a5e 100644 --- a/packages/camera/README.md +++ b/packages/camera/README.md @@ -19,7 +19,7 @@ $ sudo apt install libgstreamer-plugins-base1.0-dev \ ### pubspec.yaml ```yaml dependencies: - camera: ^0.8.1+7 + camera: ^0.10.0+1 camera_elinux: git: url: https://github.com/sony/flutter-elinux-plugins.git diff --git a/packages/camera/example/elinux/CMakeLists.txt b/packages/camera/example/elinux/CMakeLists.txt index 94678a5..05c959b 100644 --- a/packages/camera/example/elinux/CMakeLists.txt +++ b/packages/camera/example/elinux/CMakeLists.txt @@ -1,4 +1,6 @@ cmake_minimum_required(VERSION 3.15) +# stop cmake from taking make from CMAKE_SYSROOT +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) project(runner LANGUAGES CXX) set(BINARY_NAME "camera_example") @@ -7,21 +9,12 @@ cmake_policy(SET CMP0063 NEW) set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") -# Root filesystem for cross-building. -if(FLUTTER_TARGET_PLATFORM_SYSROOT) - set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) - set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) - set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) - set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) - set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) - set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) - - # Basically we use this include when we got the following error: - # fatal error: 'bits/c++config.h' file not found - if(FLUTTER_TARGET_PLATFORM_SYSROOT) - include_directories(SYSTEM ${FLUTTER_SYSTEM_INCLUDE_DIRECTORIES}) - endif() -endif() +# Basically we use this include when we got the following error: +# fatal error: 'bits/c++config.h' file not found +include_directories(SYSTEM ${FLUTTER_SYSTEM_INCLUDE_DIRECTORIES}) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) # Configure build options. if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) diff --git a/packages/camera/example/elinux/runner/command_options.h b/packages/camera/example/elinux/runner/command_options.h index 977cdc8..b0de931 100644 --- a/packages/camera/example/elinux/runner/command_options.h +++ b/packages/camera/example/elinux/runner/command_options.h @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2022 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -13,8 +13,6 @@ #include #include -// todo: Supports other types besides int, string. - namespace commandline { namespace { @@ -39,30 +37,49 @@ class CommandOptions { CommandOptions() = default; ~CommandOptions() = default; - void AddWithoutValue(const std::string& name, const std::string& short_name, - const std::string& description, bool required) { + void AddWithoutValue(const std::string& name, + const std::string& short_name, + const std::string& description, + bool required) { Add(name, short_name, description, "", ReaderString(), required, false); } - void AddInt(const std::string& name, const std::string& short_name, - const std::string& description, const int& default_value, + void AddInt(const std::string& name, + const std::string& short_name, + const std::string& description, + const int& default_value, bool required) { Add(name, short_name, description, default_value, ReaderInt(), required, true); } - void AddString(const std::string& name, const std::string& short_name, + void AddDouble(const std::string& name, + const std::string& short_name, + const std::string& description, + const double& default_value, + bool required) { + Add(name, short_name, description, default_value, + ReaderDouble(), required, true); + } + + void AddString(const std::string& name, + const std::string& short_name, const std::string& description, - const std::string& default_value, bool required) { + const std::string& default_value, + bool required) { Add(name, short_name, description, default_value, ReaderString(), required, true); } template - void Add(const std::string& name, const std::string& short_name, - const std::string& description, const T default_value, - F reader = F(), bool required = true, bool required_value = true) { + void Add(const std::string& name, + const std::string& short_name, + const std::string& description, + const T default_value, + F reader = F(), + bool required = true, + bool required_value = true) { if (options_.find(name) != options_.end()) { std::cerr << "Already registered option: " << name << std::endl; return; @@ -213,7 +230,7 @@ class CommandOptions { } size_t index_adjust = 0; - constexpr int kSpacerNum = 5; + constexpr int kSpacerNum = 10; auto need_value = registration_order_options_[i]->IsRequiredValue(); ostream << kOptionStyleNormal << registration_order_options_[i]->GetName(); @@ -240,10 +257,17 @@ class CommandOptions { std::string operator()(const std::string& value) { return value; } }; + struct ReaderDouble { + double operator()(const std::string& value) { return std::stod(value); } + }; + class Option { public: - Option(const std::string& name, const std::string& short_name, - const std::string& description, bool required, bool required_value) + Option(const std::string& name, + const std::string& short_name, + const std::string& description, + bool required, + bool required_value) : name_(name), short_name_(short_name), description_(description), @@ -288,9 +312,12 @@ class CommandOptions { template class OptionValue : public Option { public: - OptionValue(const std::string& name, const std::string& short_name, - const std::string& description, const T& default_value, - bool required, bool required_value) + OptionValue(const std::string& name, + const std::string& short_name, + const std::string& description, + const T& default_value, + bool required, + bool required_value) : Option(name, short_name, description, required, required_value), default_value_(default_value), value_(default_value){}; @@ -316,10 +343,18 @@ class CommandOptions { template class OptionValueReader : public OptionValue { public: - OptionValueReader(const std::string& name, const std::string& short_name, - const std::string& description, const T default_value, - F reader, bool required, bool required_value) - : OptionValue(name, short_name, description, default_value, required, + OptionValueReader(const std::string& name, + const std::string& short_name, + const std::string& description, + const T default_value, + F reader, + bool required, + bool required_value) + : OptionValue(name, + short_name, + description, + default_value, + required, required_value), reader_(reader) {} ~OptionValueReader() = default; diff --git a/packages/camera/example/elinux/runner/flutter_embedder_options.h b/packages/camera/example/elinux/runner/flutter_embedder_options.h index 636fdda..fbb8190 100644 --- a/packages/camera/example/elinux/runner/flutter_embedder_options.h +++ b/packages/camera/example/elinux/runner/flutter_embedder_options.h @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2022 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -14,18 +14,24 @@ class FlutterEmbedderOptions { public: FlutterEmbedderOptions() { - options_.AddString("bundle", "b", "Path to Flutter app bundle", "./", - false); + options_.AddString("bundle", "b", "Path to Flutter project bundle", + "./bundle", true); options_.AddWithoutValue("no-cursor", "n", "No mouse cursor/pointer", false); + options_.AddInt("rotation", "r", + "Window rotation(degree) [0(default)|90|180|270]", 0, + false); + options_.AddDouble("force-scale-factor", "s", + "Force a scale factor instead using default value", 1.0, + false); #if defined(FLUTTER_TARGET_BACKEND_GBM) || \ defined(FLUTTER_TARGET_BACKEND_EGLSTREAM) // no more options. #elif defined(FLUTTER_TARGET_BACKEND_X11) options_.AddWithoutValue("fullscreen", "f", "Always full-screen display", false); - options_.AddInt("width", "w", "Flutter app window width", 1280, false); - options_.AddInt("height", "h", "Flutter app window height", 720, false); + options_.AddInt("width", "w", "Window width", 1280, false); + options_.AddInt("height", "h", "Window height", 720, false); #else // FLUTTER_TARGET_BACKEND_WAYLAND options_.AddWithoutValue("onscreen-keyboard", "k", "Enable on-screen keyboard", false); @@ -33,8 +39,8 @@ class FlutterEmbedderOptions { "Enable window decorations", false); options_.AddWithoutValue("fullscreen", "f", "Always full-screen display", false); - options_.AddInt("width", "w", "Flutter app window width", 1280, false); - options_.AddInt("height", "h", "Flutter app window height", 720, false); + options_.AddInt("width", "w", "Window width", 1280, false); + options_.AddInt("height", "h", "Window height", 720, false); #endif } ~FlutterEmbedderOptions() = default; @@ -48,6 +54,34 @@ class FlutterEmbedderOptions { bundle_path_ = options_.GetValue("bundle"); use_mouse_cursor_ = !options_.Exist("no-cursor"); + if (options_.Exist("rotation")) { + switch (options_.GetValue("rotation")) { + case 90: + window_view_rotation_ = + flutter::FlutterViewController::ViewRotation::kRotation_90; + break; + case 180: + window_view_rotation_ = + flutter::FlutterViewController::ViewRotation::kRotation_180; + break; + case 270: + window_view_rotation_ = + flutter::FlutterViewController::ViewRotation::kRotation_270; + break; + default: + window_view_rotation_ = + flutter::FlutterViewController::ViewRotation::kRotation_0; + break; + } + } + + if (options_.Exist("force-scale-factor")) { + is_force_scale_factor_ = true; + scale_factor_ = options_.GetValue("force-scale-factor"); + } else { + is_force_scale_factor_ = false; + scale_factor_ = 1.0; + } #if defined(FLUTTER_TARGET_BACKEND_GBM) || \ defined(FLUTTER_TARGET_BACKEND_EGLSTREAM) @@ -86,6 +120,11 @@ class FlutterEmbedderOptions { } int WindowWidth() const { return window_width_; } int WindowHeight() const { return window_height_; } + flutter::FlutterViewController::ViewRotation WindowRotation() const { + return window_view_rotation_; + } + bool IsForceScaleFactor() const { return is_force_scale_factor_; } + double ScaleFactor() const { return scale_factor_; } private: commandline::CommandOptions options_; @@ -98,6 +137,10 @@ class FlutterEmbedderOptions { flutter::FlutterViewController::ViewMode::kNormal; int window_width_ = 1280; int window_height_ = 720; + flutter::FlutterViewController::ViewRotation window_view_rotation_ = + flutter::FlutterViewController::ViewRotation::kRotation_0; + bool is_force_scale_factor_; + double scale_factor_; }; #endif // FLUTTER_EMBEDDER_OPTIONS_ diff --git a/packages/camera/example/elinux/runner/main.cc b/packages/camera/example/elinux/runner/main.cc index 92a6513..dd3e12d 100644 --- a/packages/camera/example/elinux/runner/main.cc +++ b/packages/camera/example/elinux/runner/main.cc @@ -1,4 +1,4 @@ -// Copyright 2021 Sony Corporation. All rights reserved. +// Copyright 2022 Sony Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -29,9 +29,12 @@ int main(int argc, char** argv) { view_properties.width = options.WindowWidth(); view_properties.height = options.WindowHeight(); view_properties.view_mode = options.WindowViewMode(); + view_properties.view_rotation = options.WindowRotation(); view_properties.use_mouse_cursor = options.IsUseMouseCursor(); view_properties.use_onscreen_keyboard = options.IsUseOnscreenKeyboard(); view_properties.use_window_decoration = options.IsUseWindowDecoraation(); + view_properties.force_scale_factor = options.IsForceScaleFactor(); + view_properties.scale_factor = options.ScaleFactor(); // The Flutter instance hosted by this window. FlutterWindow window(view_properties, project); diff --git a/packages/camera/example/lib/main.dart b/packages/camera/example/lib/main.dart index 3d1f990..c5804e7 100644 --- a/packages/camera/example/lib/main.dart +++ b/packages/camera/example/lib/main.dart @@ -2,19 +2,22 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// ignore_for_file: public_member_api_docs - import 'dart:async'; import 'dart:io'; import 'package:camera/camera.dart'; -import 'package:flutter/gestures.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; import 'package:video_player/video_player.dart'; +/// Camera example home widget. class CameraExampleHome extends StatefulWidget { + /// Default Constructor + const CameraExampleHome({Key? key}) : super(key: key); + @override - _CameraExampleHomeState createState() { + State createState() { return _CameraExampleHomeState(); } } @@ -33,7 +36,7 @@ IconData getCameraLensIcon(CameraLensDirection direction) { } } -void logError(String code, String? message) { +void _logError(String code, String? message) { if (message != null) { print('Error: $code\nError Message: $message'); } else { @@ -105,6 +108,7 @@ class _CameraExampleHomeState extends State super.dispose(); } + // #docregion AppLifecycle @override void didChangeAppLifecycleState(AppLifecycleState state) { final CameraController? cameraController = controller; @@ -120,13 +124,11 @@ class _CameraExampleHomeState extends State onNewCameraSelected(cameraController.description); } } - - final GlobalKey _scaffoldKey = GlobalKey(); + // #enddocregion AppLifecycle @override Widget build(BuildContext context) { return Scaffold( - key: _scaffoldKey, appBar: AppBar( title: const Text('Camera example'), ), @@ -134,12 +136,6 @@ class _CameraExampleHomeState extends State children: [ Expanded( child: Container( - child: Padding( - padding: const EdgeInsets.all(1.0), - child: Center( - child: _cameraPreviewWidget(), - ), - ), decoration: BoxDecoration( color: Colors.black, border: Border.all( @@ -150,6 +146,12 @@ class _CameraExampleHomeState extends State width: 3.0, ), ), + child: Padding( + padding: const EdgeInsets.all(1.0), + child: Center( + child: _cameraPreviewWidget(), + ), + ), ), ), _captureControlRowWidget(), @@ -157,7 +159,6 @@ class _CameraExampleHomeState extends State Padding( padding: const EdgeInsets.all(5.0), child: Row( - mainAxisAlignment: MainAxisAlignment.start, children: [ _cameraTogglesRowWidget(), _thumbnailWidget(), @@ -194,7 +195,8 @@ class _CameraExampleHomeState extends State behavior: HitTestBehavior.opaque, onScaleStart: _handleScaleStart, onScaleUpdate: _handleScaleUpdate, - onTapDown: (details) => onViewFinderTap(details, constraints), + onTapDown: (TapDownDetails details) => + onViewFinderTap(details, constraints), ); }), ), @@ -228,27 +230,34 @@ class _CameraExampleHomeState extends State child: Row( mainAxisSize: MainAxisSize.min, children: [ - localVideoController == null && imageFile == null - ? Container() - : SizedBox( - child: (localVideoController == null) - ? Image.file(File(imageFile!.path)) - : Container( - child: Center( - child: AspectRatio( - aspectRatio: - localVideoController.value.size != null - ? localVideoController - .value.aspectRatio - : 1.0, - child: VideoPlayer(localVideoController)), - ), - decoration: BoxDecoration( - border: Border.all(color: Colors.pink)), - ), - width: 64.0, - height: 64.0, - ), + if (localVideoController == null && imageFile == null) + Container() + else + SizedBox( + width: 64.0, + height: 64.0, + child: (localVideoController == null) + ? ( + // The captured image on the web contains a network-accessible URL + // pointing to a location within the browser. It may be displayed + // either with Image.network or Image.memory after loading the image + // bytes to memory. + kIsWeb + ? Image.network(imageFile!.path) + : Image.file(File(imageFile!.path))) + : Container( + decoration: BoxDecoration( + border: Border.all(color: Colors.pink)), + child: Center( + child: AspectRatio( + aspectRatio: + localVideoController.value.size != null + ? localVideoController.value.aspectRatio + : 1.0, + child: VideoPlayer(localVideoController)), + ), + ), + ), ], ), ), @@ -258,27 +267,33 @@ class _CameraExampleHomeState extends State /// Display a bar with buttons to change the flash and exposure modes Widget _modeControlRowWidget() { return Column( - children: [ + children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, - mainAxisSize: MainAxisSize.max, children: [ IconButton( - icon: Icon(Icons.flash_on), + icon: const Icon(Icons.flash_on), color: Colors.blue, onPressed: controller != null ? onFlashModeButtonPressed : null, ), - IconButton( - icon: Icon(Icons.exposure), - color: Colors.blue, - onPressed: - controller != null ? onExposureModeButtonPressed : null, - ), - IconButton( - icon: Icon(Icons.filter_center_focus), - color: Colors.blue, - onPressed: controller != null ? onFocusModeButtonPressed : null, - ), + // The exposure and focus mode are currently not supported on the web. + ...!kIsWeb + ? [ + IconButton( + icon: const Icon(Icons.exposure), + color: Colors.blue, + onPressed: controller != null + ? onExposureModeButtonPressed + : null, + ), + IconButton( + icon: const Icon(Icons.filter_center_focus), + color: Colors.blue, + onPressed: + controller != null ? onFocusModeButtonPressed : null, + ) + ] + : [], IconButton( icon: Icon(enableAudio ? Icons.volume_up : Icons.volume_mute), color: Colors.blue, @@ -308,10 +323,9 @@ class _CameraExampleHomeState extends State child: ClipRect( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, - mainAxisSize: MainAxisSize.max, - children: [ + children: [ IconButton( - icon: Icon(Icons.flash_off), + icon: const Icon(Icons.flash_off), color: controller?.value.flashMode == FlashMode.off ? Colors.orange : Colors.blue, @@ -320,7 +334,7 @@ class _CameraExampleHomeState extends State : null, ), IconButton( - icon: Icon(Icons.flash_auto), + icon: const Icon(Icons.flash_auto), color: controller?.value.flashMode == FlashMode.auto ? Colors.orange : Colors.blue, @@ -329,7 +343,7 @@ class _CameraExampleHomeState extends State : null, ), IconButton( - icon: Icon(Icons.flash_on), + icon: const Icon(Icons.flash_on), color: controller?.value.flashMode == FlashMode.always ? Colors.orange : Colors.blue, @@ -338,7 +352,7 @@ class _CameraExampleHomeState extends State : null, ), IconButton( - icon: Icon(Icons.highlight), + icon: const Icon(Icons.highlight), color: controller?.value.flashMode == FlashMode.torch ? Colors.orange : Colors.blue, @@ -354,11 +368,15 @@ class _CameraExampleHomeState extends State Widget _exposureModeControlRowWidget() { final ButtonStyle styleAuto = TextButton.styleFrom( + // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 + // ignore: deprecated_member_use primary: controller?.value.exposureMode == ExposureMode.auto ? Colors.orange : Colors.blue, ); final ButtonStyle styleLocked = TextButton.styleFrom( + // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 + // ignore: deprecated_member_use primary: controller?.value.exposureMode == ExposureMode.locked ? Colors.orange : Colors.blue, @@ -370,16 +388,14 @@ class _CameraExampleHomeState extends State child: Container( color: Colors.grey.shade50, child: Column( - children: [ - Center( - child: Text("Exposure Mode"), + children: [ + const Center( + child: Text('Exposure Mode'), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, - mainAxisSize: MainAxisSize.max, - children: [ + children: [ TextButton( - child: Text('AUTO'), style: styleAuto, onPressed: controller != null ? () => @@ -391,24 +407,31 @@ class _CameraExampleHomeState extends State showInSnackBar('Resetting exposure point'); } }, + child: const Text('AUTO'), ), TextButton( - child: Text('LOCKED'), style: styleLocked, onPressed: controller != null ? () => onSetExposureModeButtonPressed(ExposureMode.locked) : null, + child: const Text('LOCKED'), + ), + TextButton( + style: styleLocked, + onPressed: controller != null + ? () => controller!.setExposureOffset(0.0) + : null, + child: const Text('RESET OFFSET'), ), ], ), - Center( - child: Text("Exposure Offset"), + const Center( + child: Text('Exposure Offset'), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, - mainAxisSize: MainAxisSize.max, - children: [ + children: [ Text(_minAvailableExposureOffset.toString()), Slider( value: _currentExposureOffset, @@ -432,11 +455,15 @@ class _CameraExampleHomeState extends State Widget _focusModeControlRowWidget() { final ButtonStyle styleAuto = TextButton.styleFrom( + // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 + // ignore: deprecated_member_use primary: controller?.value.focusMode == FocusMode.auto ? Colors.orange : Colors.blue, ); final ButtonStyle styleLocked = TextButton.styleFrom( + // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 + // ignore: deprecated_member_use primary: controller?.value.focusMode == FocusMode.locked ? Colors.orange : Colors.blue, @@ -448,31 +475,32 @@ class _CameraExampleHomeState extends State child: Container( color: Colors.grey.shade50, child: Column( - children: [ - Center( - child: Text("Focus Mode"), + children: [ + const Center( + child: Text('Focus Mode'), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, - mainAxisSize: MainAxisSize.max, - children: [ + children: [ TextButton( - child: Text('AUTO'), style: styleAuto, onPressed: controller != null ? () => onSetFocusModeButtonPressed(FocusMode.auto) : null, onLongPress: () { - if (controller != null) controller!.setFocusPoint(null); + if (controller != null) { + controller!.setFocusPoint(null); + } showInSnackBar('Resetting focus point'); }, + child: const Text('AUTO'), ), TextButton( - child: Text('LOCKED'), style: styleLocked, onPressed: controller != null ? () => onSetFocusModeButtonPressed(FocusMode.locked) : null, + child: const Text('LOCKED'), ), ], ), @@ -489,7 +517,6 @@ class _CameraExampleHomeState extends State return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, - mainAxisSize: MainAxisSize.max, children: [ IconButton( icon: const Icon(Icons.camera_alt), @@ -512,8 +539,8 @@ class _CameraExampleHomeState extends State IconButton( icon: cameraController != null && cameraController.value.isRecordingPaused - ? Icon(Icons.play_arrow) - : Icon(Icons.pause), + ? const Icon(Icons.play_arrow) + : const Icon(Icons.pause), color: Colors.blue, onPressed: cameraController != null && cameraController.value.isInitialized && @@ -531,7 +558,16 @@ class _CameraExampleHomeState extends State cameraController.value.isRecordingVideo ? onStopButtonPressed : null, - ) + ), + IconButton( + icon: const Icon(Icons.pause_presentation), + color: + cameraController != null && cameraController.value.isPreviewPaused + ? Colors.red + : Colors.blue, + onPressed: + cameraController == null ? null : onPausePreviewButtonPressed, + ), ], ); } @@ -540,18 +576,21 @@ class _CameraExampleHomeState extends State Widget _cameraTogglesRowWidget() { final List toggles = []; - final onChanged = (CameraDescription? description) { + void onChanged(CameraDescription? description) { if (description == null) { return; } onNewCameraSelected(description); - }; + } - if (cameras.isEmpty) { - return const Text('No camera found'); + if (_cameras.isEmpty) { + _ambiguate(SchedulerBinding.instance)?.addPostFrameCallback((_) async { + showInSnackBar('No camera found.'); + }); + return const Text('None'); } else { - for (CameraDescription cameraDescription in cameras) { + for (final CameraDescription cameraDescription in _cameras) { toggles.add( SizedBox( width: 90.0, @@ -575,8 +614,8 @@ class _CameraExampleHomeState extends State String timestamp() => DateTime.now().millisecondsSinceEpoch.toString(); void showInSnackBar(String message) { - // ignore: deprecated_member_use - _scaffoldKey.currentState?.showSnackBar(SnackBar(content: Text(message))); + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text(message))); } void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) { @@ -586,7 +625,7 @@ class _CameraExampleHomeState extends State final CameraController cameraController = controller!; - final offset = Offset( + final Offset offset = Offset( details.localPosition.dx / constraints.maxWidth, details.localPosition.dy / constraints.maxHeight, ); @@ -594,21 +633,32 @@ class _CameraExampleHomeState extends State cameraController.setFocusPoint(offset); } - void onNewCameraSelected(CameraDescription cameraDescription) async { - if (controller != null) { - await controller!.dispose(); + Future onNewCameraSelected(CameraDescription cameraDescription) async { + final CameraController? oldController = controller; + if (oldController != null) { + // `controller` needs to be set to null before getting disposed, + // to avoid a race condition when we use the controller that is being + // disposed. This happens when camera permission dialog shows up, + // which triggers `didChangeAppLifecycleState`, which disposes and + // re-creates the controller. + controller = null; + await oldController.dispose(); } + final CameraController cameraController = CameraController( cameraDescription, - ResolutionPreset.medium, + kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium, enableAudio: enableAudio, imageFormatGroup: ImageFormatGroup.jpeg, ); + controller = cameraController; // If the controller is updated then update the UI. cameraController.addListener(() { - if (mounted) setState(() {}); + if (mounted) { + setState(() {}); + } if (cameraController.value.hasError) { showInSnackBar( 'Camera error ${cameraController.value.errorDescription}'); @@ -617,22 +667,52 @@ class _CameraExampleHomeState extends State try { await cameraController.initialize(); - await Future.wait([ - cameraController - .getMinExposureOffset() - .then((value) => _minAvailableExposureOffset = value), - cameraController - .getMaxExposureOffset() - .then((value) => _maxAvailableExposureOffset = value), + await Future.wait(>[ + // The exposure mode is currently not supported on the web. + ...!kIsWeb + ? >[ + cameraController.getMinExposureOffset().then( + (double value) => _minAvailableExposureOffset = value), + cameraController + .getMaxExposureOffset() + .then((double value) => _maxAvailableExposureOffset = value) + ] + : >[], cameraController .getMaxZoomLevel() - .then((value) => _maxAvailableZoom = value), + .then((double value) => _maxAvailableZoom = value), cameraController .getMinZoomLevel() - .then((value) => _minAvailableZoom = value), + .then((double value) => _minAvailableZoom = value), ]); } on CameraException catch (e) { - _showCameraException(e); + switch (e.code) { + case 'CameraAccessDenied': + showInSnackBar('You have denied camera access.'); + break; + case 'CameraAccessDeniedWithoutPrompt': + // iOS only + showInSnackBar('Please go to Settings app to enable camera access.'); + break; + case 'CameraAccessRestricted': + // iOS only + showInSnackBar('Camera access is restricted.'); + break; + case 'AudioAccessDenied': + showInSnackBar('You have denied audio access.'); + break; + case 'AudioAccessDeniedWithoutPrompt': + // iOS only + showInSnackBar('Please go to Settings app to enable audio access.'); + break; + case 'AudioAccessRestricted': + // iOS only + showInSnackBar('Audio access is restricted.'); + break; + default: + _showCameraException(e); + break; + } } if (mounted) { @@ -648,7 +728,9 @@ class _CameraExampleHomeState extends State videoController?.dispose(); videoController = null; }); - if (file != null) showInSnackBar('Picture saved to ${file.path}'); + if (file != null) { + showInSnackBar('Picture saved to ${file.path}'); + } } }); } @@ -690,50 +772,64 @@ class _CameraExampleHomeState extends State } } - void onCaptureOrientationLockButtonPressed() async { - if (controller != null) { - final CameraController cameraController = controller!; - if (cameraController.value.isCaptureOrientationLocked) { - await cameraController.unlockCaptureOrientation(); - showInSnackBar('Capture orientation unlocked'); - } else { - await cameraController.lockCaptureOrientation(); - showInSnackBar( - 'Capture orientation locked to ${cameraController.value.lockedCaptureOrientation.toString().split('.').last}'); + Future onCaptureOrientationLockButtonPressed() async { + try { + if (controller != null) { + final CameraController cameraController = controller!; + if (cameraController.value.isCaptureOrientationLocked) { + await cameraController.unlockCaptureOrientation(); + showInSnackBar('Capture orientation unlocked'); + } else { + await cameraController.lockCaptureOrientation(); + showInSnackBar( + 'Capture orientation locked to ${cameraController.value.lockedCaptureOrientation.toString().split('.').last}'); + } } + } on CameraException catch (e) { + _showCameraException(e); } } void onSetFlashModeButtonPressed(FlashMode mode) { setFlashMode(mode).then((_) { - if (mounted) setState(() {}); + if (mounted) { + setState(() {}); + } showInSnackBar('Flash mode set to ${mode.toString().split('.').last}'); }); } void onSetExposureModeButtonPressed(ExposureMode mode) { setExposureMode(mode).then((_) { - if (mounted) setState(() {}); + if (mounted) { + setState(() {}); + } showInSnackBar('Exposure mode set to ${mode.toString().split('.').last}'); }); } void onSetFocusModeButtonPressed(FocusMode mode) { setFocusMode(mode).then((_) { - if (mounted) setState(() {}); + if (mounted) { + setState(() {}); + } showInSnackBar('Focus mode set to ${mode.toString().split('.').last}'); }); } void onVideoRecordButtonPressed() { startVideoRecording().then((_) { - if (mounted) setState(() {}); + if (mounted) { + setState(() {}); + } }); } void onStopButtonPressed() { - stopVideoRecording().then((file) { - if (mounted) setState(() {}); + stopVideoRecording().then((XFile? file) { + if (mounted) { + setState(() {}); + } if (file != null) { showInSnackBar('Video recorded to ${file.path}'); videoFile = file; @@ -742,16 +838,39 @@ class _CameraExampleHomeState extends State }); } + Future onPausePreviewButtonPressed() async { + final CameraController? cameraController = controller; + + if (cameraController == null || !cameraController.value.isInitialized) { + showInSnackBar('Error: select a camera first.'); + return; + } + + if (cameraController.value.isPreviewPaused) { + await cameraController.resumePreview(); + } else { + await cameraController.pausePreview(); + } + + if (mounted) { + setState(() {}); + } + } + void onPauseButtonPressed() { pauseVideoRecording().then((_) { - if (mounted) setState(() {}); + if (mounted) { + setState(() {}); + } showInSnackBar('Video recording paused'); }); } void onResumeButtonPressed() { resumeVideoRecording().then((_) { - if (mounted) setState(() {}); + if (mounted) { + setState(() {}); + } showInSnackBar('Video recording resumed'); }); } @@ -796,7 +915,7 @@ class _CameraExampleHomeState extends State final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isRecordingVideo) { - return null; + return; } try { @@ -811,7 +930,7 @@ class _CameraExampleHomeState extends State final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isRecordingVideo) { - return null; + return; } try { @@ -882,12 +1001,16 @@ class _CameraExampleHomeState extends State return; } - final VideoPlayerController vController = - VideoPlayerController.file(File(videoFile!.path)); + final VideoPlayerController vController = kIsWeb + ? VideoPlayerController.network(videoFile!.path) + : VideoPlayerController.file(File(videoFile!.path)); + videoPlayerListener = () { if (videoController != null && videoController!.value.size != null) { // Refreshing the state to update video player with the correct ratio. - if (mounted) setState(() {}); + if (mounted) { + setState(() {}); + } videoController!.removeListener(videoPlayerListener!); } }; @@ -917,7 +1040,7 @@ class _CameraExampleHomeState extends State } try { - XFile file = await cameraController.takePicture(); + final XFile file = await cameraController.takePicture(); return file; } on CameraException catch (e) { _showCameraException(e); @@ -926,41 +1049,35 @@ class _CameraExampleHomeState extends State } void _showCameraException(CameraException e) { - logError(e.code, e.description); + _logError(e.code, e.description); showInSnackBar('Error: ${e.code}\n${e.description}'); } } +/// CameraApp is the Main Application. class CameraApp extends StatelessWidget { + /// Default Constructor + const CameraApp({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { - return MaterialApp( - scrollBehavior: MyCustomScrollBehavior(), + return const MaterialApp( home: CameraExampleHome(), ); } } -class MyCustomScrollBehavior extends MaterialScrollBehavior { - // Override behavior methods and getters like dragDevices - @override - Set get dragDevices => { - PointerDeviceKind.touch, - PointerDeviceKind.mouse, - }; -} - -List cameras = []; +List _cameras = []; Future main() async { // Fetch the available cameras before initializing the app. try { WidgetsFlutterBinding.ensureInitialized(); - cameras = await availableCameras(); + _cameras = await availableCameras(); } on CameraException catch (e) { - logError(e.code, e.description); + _logError(e.code, e.description); } - runApp(CameraApp()); + runApp(const CameraApp()); } /// This allows a value of type T or T? to be treated as a value of type T?. diff --git a/packages/camera/example/pubspec.yaml b/packages/camera/example/pubspec.yaml index 75850fb..901cc31 100644 --- a/packages/camera/example/pubspec.yaml +++ b/packages/camera/example/pubspec.yaml @@ -4,18 +4,17 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=1.22.0" + flutter: ">=2.10.0" dependencies: - camera: ^0.8.1+7 + camera: ^0.10.0+1 camera_elinux: path: ../ flutter: sdk: flutter - path_provider: ^2.0.0 path_provider_elinux: path: ../../path_provider - video_player: ^2.1.4 + video_player: ^2.4.7 video_player_elinux: path: ../../video_player @@ -26,7 +25,6 @@ dev_dependencies: sdk: flutter integration_test: sdk: flutter - pedantic: ^1.10.0 flutter: uses-material-design: true diff --git a/packages/camera/pubspec.yaml b/packages/camera/pubspec.yaml index a526d5e..9c418de 100644 --- a/packages/camera/pubspec.yaml +++ b/packages/camera/pubspec.yaml @@ -2,13 +2,13 @@ name: camera_elinux description: A Flutter plugin for getting information about and controlling the camera on eLinux. Supports previewing the camera feed, capturing images, capturing video, and streaming image buffers to dart. -version: 0.2.0 +version: 0.2.1 homepage: https://github.com/sony/flutter-elinux-plugins repository: https://github.com/sony/flutter-elinux-plugins/tree/main/packages/camera environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=1.20.0" + flutter: ">=2.10.0" dependencies: flutter: