[camera] update for camera ^0.10.0+1 / flutter 3.3.0 release (#57)

* Update for camera ^0.10.0+1 / flutter 3.3.0 release

Signed-off-by: Hidenori Matsubayashi <hidenori.matsubayashi@gmail.com>
This commit is contained in:
Hidenori Matsubayashi
2022-09-19 14:47:24 +09:00
committed by GitHub
parent 02260a1eab
commit 1dd2b8bbfa
9 changed files with 389 additions and 197 deletions

View File

@ -1,3 +1,6 @@
## 0.2.1
* Update for camera v0.10.0+1 / flutter 3.3.0 release
## 0.2.0 ## 0.2.0
* Fix wrong orientation issue. * Fix wrong orientation issue.
* Change the camera type from back to external. * Change the camera type from back to external.

View File

@ -19,7 +19,7 @@ $ sudo apt install libgstreamer-plugins-base1.0-dev \
### pubspec.yaml ### pubspec.yaml
```yaml ```yaml
dependencies: dependencies:
camera: ^0.8.1+7 camera: ^0.10.0+1
camera_elinux: camera_elinux:
git: git:
url: https://github.com/sony/flutter-elinux-plugins.git url: https://github.com/sony/flutter-elinux-plugins.git

View File

@ -1,4 +1,6 @@
cmake_minimum_required(VERSION 3.15) 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) project(runner LANGUAGES CXX)
set(BINARY_NAME "camera_example") set(BINARY_NAME "camera_example")
@ -7,21 +9,12 @@ cmake_policy(SET CMP0063 NEW)
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Root filesystem for cross-building. # Basically we use this include when we got the following error:
if(FLUTTER_TARGET_PLATFORM_SYSROOT) # fatal error: 'bits/c++config.h' file not found
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) include_directories(SYSTEM ${FLUTTER_SYSTEM_INCLUDE_DIRECTORIES})
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE 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()
# Configure build options. # Configure build options.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)

View File

@ -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 // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
@ -13,8 +13,6 @@
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
// todo: Supports other types besides int, string.
namespace commandline { namespace commandline {
namespace { namespace {
@ -39,30 +37,49 @@ class CommandOptions {
CommandOptions() = default; CommandOptions() = default;
~CommandOptions() = default; ~CommandOptions() = default;
void AddWithoutValue(const std::string& name, const std::string& short_name, void AddWithoutValue(const std::string& name,
const std::string& description, bool required) { const std::string& short_name,
const std::string& description,
bool required) {
Add<std::string, ReaderString>(name, short_name, description, "", Add<std::string, ReaderString>(name, short_name, description, "",
ReaderString(), required, false); ReaderString(), required, false);
} }
void AddInt(const std::string& name, const std::string& short_name, void AddInt(const std::string& name,
const std::string& description, const int& default_value, const std::string& short_name,
const std::string& description,
const int& default_value,
bool required) { bool required) {
Add<int, ReaderInt>(name, short_name, description, default_value, Add<int, ReaderInt>(name, short_name, description, default_value,
ReaderInt(), required, true); 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<double, ReaderDouble>(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& description,
const std::string& default_value, bool required) { const std::string& default_value,
bool required) {
Add<std::string, ReaderString>(name, short_name, description, default_value, Add<std::string, ReaderString>(name, short_name, description, default_value,
ReaderString(), required, true); ReaderString(), required, true);
} }
template <typename T, typename F> template <typename T, typename F>
void Add(const std::string& name, const std::string& short_name, void Add(const std::string& name,
const std::string& description, const T default_value, const std::string& short_name,
F reader = F(), bool required = true, bool required_value = true) { const std::string& description,
const T default_value,
F reader = F(),
bool required = true,
bool required_value = true) {
if (options_.find(name) != options_.end()) { if (options_.find(name) != options_.end()) {
std::cerr << "Already registered option: " << name << std::endl; std::cerr << "Already registered option: " << name << std::endl;
return; return;
@ -213,7 +230,7 @@ class CommandOptions {
} }
size_t index_adjust = 0; size_t index_adjust = 0;
constexpr int kSpacerNum = 5; constexpr int kSpacerNum = 10;
auto need_value = registration_order_options_[i]->IsRequiredValue(); auto need_value = registration_order_options_[i]->IsRequiredValue();
ostream << kOptionStyleNormal ostream << kOptionStyleNormal
<< registration_order_options_[i]->GetName(); << registration_order_options_[i]->GetName();
@ -240,10 +257,17 @@ class CommandOptions {
std::string operator()(const std::string& value) { return value; } std::string operator()(const std::string& value) { return value; }
}; };
struct ReaderDouble {
double operator()(const std::string& value) { return std::stod(value); }
};
class Option { class Option {
public: public:
Option(const std::string& name, const std::string& short_name, Option(const std::string& name,
const std::string& description, bool required, bool required_value) const std::string& short_name,
const std::string& description,
bool required,
bool required_value)
: name_(name), : name_(name),
short_name_(short_name), short_name_(short_name),
description_(description), description_(description),
@ -288,9 +312,12 @@ class CommandOptions {
template <typename T> template <typename T>
class OptionValue : public Option { class OptionValue : public Option {
public: public:
OptionValue(const std::string& name, const std::string& short_name, OptionValue(const std::string& name,
const std::string& description, const T& default_value, const std::string& short_name,
bool required, bool required_value) const std::string& description,
const T& default_value,
bool required,
bool required_value)
: Option(name, short_name, description, required, required_value), : Option(name, short_name, description, required, required_value),
default_value_(default_value), default_value_(default_value),
value_(default_value){}; value_(default_value){};
@ -316,10 +343,18 @@ class CommandOptions {
template <typename T, typename F> template <typename T, typename F>
class OptionValueReader : public OptionValue<T> { class OptionValueReader : public OptionValue<T> {
public: public:
OptionValueReader(const std::string& name, const std::string& short_name, OptionValueReader(const std::string& name,
const std::string& description, const T default_value, const std::string& short_name,
F reader, bool required, bool required_value) const std::string& description,
: OptionValue<T>(name, short_name, description, default_value, required, const T default_value,
F reader,
bool required,
bool required_value)
: OptionValue<T>(name,
short_name,
description,
default_value,
required,
required_value), required_value),
reader_(reader) {} reader_(reader) {}
~OptionValueReader() = default; ~OptionValueReader() = default;

View File

@ -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 // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
@ -14,18 +14,24 @@
class FlutterEmbedderOptions { class FlutterEmbedderOptions {
public: public:
FlutterEmbedderOptions() { FlutterEmbedderOptions() {
options_.AddString("bundle", "b", "Path to Flutter app bundle", "./", options_.AddString("bundle", "b", "Path to Flutter project bundle",
false); "./bundle", true);
options_.AddWithoutValue("no-cursor", "n", "No mouse cursor/pointer", options_.AddWithoutValue("no-cursor", "n", "No mouse cursor/pointer",
false); 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) || \ #if defined(FLUTTER_TARGET_BACKEND_GBM) || \
defined(FLUTTER_TARGET_BACKEND_EGLSTREAM) defined(FLUTTER_TARGET_BACKEND_EGLSTREAM)
// no more options. // no more options.
#elif defined(FLUTTER_TARGET_BACKEND_X11) #elif defined(FLUTTER_TARGET_BACKEND_X11)
options_.AddWithoutValue("fullscreen", "f", "Always full-screen display", options_.AddWithoutValue("fullscreen", "f", "Always full-screen display",
false); false);
options_.AddInt("width", "w", "Flutter app window width", 1280, false); options_.AddInt("width", "w", "Window width", 1280, false);
options_.AddInt("height", "h", "Flutter app window height", 720, false); options_.AddInt("height", "h", "Window height", 720, false);
#else // FLUTTER_TARGET_BACKEND_WAYLAND #else // FLUTTER_TARGET_BACKEND_WAYLAND
options_.AddWithoutValue("onscreen-keyboard", "k", options_.AddWithoutValue("onscreen-keyboard", "k",
"Enable on-screen keyboard", false); "Enable on-screen keyboard", false);
@ -33,8 +39,8 @@ class FlutterEmbedderOptions {
"Enable window decorations", false); "Enable window decorations", false);
options_.AddWithoutValue("fullscreen", "f", "Always full-screen display", options_.AddWithoutValue("fullscreen", "f", "Always full-screen display",
false); false);
options_.AddInt("width", "w", "Flutter app window width", 1280, false); options_.AddInt("width", "w", "Window width", 1280, false);
options_.AddInt("height", "h", "Flutter app window height", 720, false); options_.AddInt("height", "h", "Window height", 720, false);
#endif #endif
} }
~FlutterEmbedderOptions() = default; ~FlutterEmbedderOptions() = default;
@ -48,6 +54,34 @@ class FlutterEmbedderOptions {
bundle_path_ = options_.GetValue<std::string>("bundle"); bundle_path_ = options_.GetValue<std::string>("bundle");
use_mouse_cursor_ = !options_.Exist("no-cursor"); use_mouse_cursor_ = !options_.Exist("no-cursor");
if (options_.Exist("rotation")) {
switch (options_.GetValue<int>("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<double>("force-scale-factor");
} else {
is_force_scale_factor_ = false;
scale_factor_ = 1.0;
}
#if defined(FLUTTER_TARGET_BACKEND_GBM) || \ #if defined(FLUTTER_TARGET_BACKEND_GBM) || \
defined(FLUTTER_TARGET_BACKEND_EGLSTREAM) defined(FLUTTER_TARGET_BACKEND_EGLSTREAM)
@ -86,6 +120,11 @@ class FlutterEmbedderOptions {
} }
int WindowWidth() const { return window_width_; } int WindowWidth() const { return window_width_; }
int WindowHeight() const { return window_height_; } 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: private:
commandline::CommandOptions options_; commandline::CommandOptions options_;
@ -98,6 +137,10 @@ class FlutterEmbedderOptions {
flutter::FlutterViewController::ViewMode::kNormal; flutter::FlutterViewController::ViewMode::kNormal;
int window_width_ = 1280; int window_width_ = 1280;
int window_height_ = 720; 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_ #endif // FLUTTER_EMBEDDER_OPTIONS_

View File

@ -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 // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
@ -29,9 +29,12 @@ int main(int argc, char** argv) {
view_properties.width = options.WindowWidth(); view_properties.width = options.WindowWidth();
view_properties.height = options.WindowHeight(); view_properties.height = options.WindowHeight();
view_properties.view_mode = options.WindowViewMode(); view_properties.view_mode = options.WindowViewMode();
view_properties.view_rotation = options.WindowRotation();
view_properties.use_mouse_cursor = options.IsUseMouseCursor(); view_properties.use_mouse_cursor = options.IsUseMouseCursor();
view_properties.use_onscreen_keyboard = options.IsUseOnscreenKeyboard(); view_properties.use_onscreen_keyboard = options.IsUseOnscreenKeyboard();
view_properties.use_window_decoration = options.IsUseWindowDecoraation(); 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. // The Flutter instance hosted by this window.
FlutterWindow window(view_properties, project); FlutterWindow window(view_properties, project);

View File

@ -2,19 +2,22 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// ignore_for_file: public_member_api_docs
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:camera/camera.dart'; import 'package:camera/camera.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:video_player/video_player.dart'; import 'package:video_player/video_player.dart';
/// Camera example home widget.
class CameraExampleHome extends StatefulWidget { class CameraExampleHome extends StatefulWidget {
/// Default Constructor
const CameraExampleHome({Key? key}) : super(key: key);
@override @override
_CameraExampleHomeState createState() { State<CameraExampleHome> createState() {
return _CameraExampleHomeState(); 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) { if (message != null) {
print('Error: $code\nError Message: $message'); print('Error: $code\nError Message: $message');
} else { } else {
@ -105,6 +108,7 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
super.dispose(); super.dispose();
} }
// #docregion AppLifecycle
@override @override
void didChangeAppLifecycleState(AppLifecycleState state) { void didChangeAppLifecycleState(AppLifecycleState state) {
final CameraController? cameraController = controller; final CameraController? cameraController = controller;
@ -120,13 +124,11 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
onNewCameraSelected(cameraController.description); onNewCameraSelected(cameraController.description);
} }
} }
// #enddocregion AppLifecycle
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
key: _scaffoldKey,
appBar: AppBar( appBar: AppBar(
title: const Text('Camera example'), title: const Text('Camera example'),
), ),
@ -134,12 +136,6 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: Container( child: Container(
child: Padding(
padding: const EdgeInsets.all(1.0),
child: Center(
child: _cameraPreviewWidget(),
),
),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.black, color: Colors.black,
border: Border.all( border: Border.all(
@ -150,6 +146,12 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
width: 3.0, width: 3.0,
), ),
), ),
child: Padding(
padding: const EdgeInsets.all(1.0),
child: Center(
child: _cameraPreviewWidget(),
),
),
), ),
), ),
_captureControlRowWidget(), _captureControlRowWidget(),
@ -157,7 +159,6 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
Padding( Padding(
padding: const EdgeInsets.all(5.0), padding: const EdgeInsets.all(5.0),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[ children: <Widget>[
_cameraTogglesRowWidget(), _cameraTogglesRowWidget(),
_thumbnailWidget(), _thumbnailWidget(),
@ -194,7 +195,8 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onScaleStart: _handleScaleStart, onScaleStart: _handleScaleStart,
onScaleUpdate: _handleScaleUpdate, onScaleUpdate: _handleScaleUpdate,
onTapDown: (details) => onViewFinderTap(details, constraints), onTapDown: (TapDownDetails details) =>
onViewFinderTap(details, constraints),
); );
}), }),
), ),
@ -228,27 +230,34 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
localVideoController == null && imageFile == null if (localVideoController == null && imageFile == null)
? Container() Container()
: SizedBox( else
child: (localVideoController == null) SizedBox(
? Image.file(File(imageFile!.path)) width: 64.0,
: Container( height: 64.0,
child: Center( child: (localVideoController == null)
child: AspectRatio( ? (
aspectRatio: // The captured image on the web contains a network-accessible URL
localVideoController.value.size != null // pointing to a location within the browser. It may be displayed
? localVideoController // either with Image.network or Image.memory after loading the image
.value.aspectRatio // bytes to memory.
: 1.0, kIsWeb
child: VideoPlayer(localVideoController)), ? Image.network(imageFile!.path)
), : Image.file(File(imageFile!.path)))
decoration: BoxDecoration( : Container(
border: Border.all(color: Colors.pink)), decoration: BoxDecoration(
), border: Border.all(color: Colors.pink)),
width: 64.0, child: Center(
height: 64.0, child: AspectRatio(
), aspectRatio:
localVideoController.value.size != null
? localVideoController.value.aspectRatio
: 1.0,
child: VideoPlayer(localVideoController)),
),
),
),
], ],
), ),
), ),
@ -258,27 +267,33 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
/// Display a bar with buttons to change the flash and exposure modes /// Display a bar with buttons to change the flash and exposure modes
Widget _modeControlRowWidget() { Widget _modeControlRowWidget() {
return Column( return Column(
children: [ children: <Widget>[
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: <Widget>[ children: <Widget>[
IconButton( IconButton(
icon: Icon(Icons.flash_on), icon: const Icon(Icons.flash_on),
color: Colors.blue, color: Colors.blue,
onPressed: controller != null ? onFlashModeButtonPressed : null, onPressed: controller != null ? onFlashModeButtonPressed : null,
), ),
IconButton( // The exposure and focus mode are currently not supported on the web.
icon: Icon(Icons.exposure), ...!kIsWeb
color: Colors.blue, ? <Widget>[
onPressed: IconButton(
controller != null ? onExposureModeButtonPressed : null, icon: const Icon(Icons.exposure),
), color: Colors.blue,
IconButton( onPressed: controller != null
icon: Icon(Icons.filter_center_focus), ? onExposureModeButtonPressed
color: Colors.blue, : null,
onPressed: controller != null ? onFocusModeButtonPressed : null, ),
), IconButton(
icon: const Icon(Icons.filter_center_focus),
color: Colors.blue,
onPressed:
controller != null ? onFocusModeButtonPressed : null,
)
]
: <Widget>[],
IconButton( IconButton(
icon: Icon(enableAudio ? Icons.volume_up : Icons.volume_mute), icon: Icon(enableAudio ? Icons.volume_up : Icons.volume_mute),
color: Colors.blue, color: Colors.blue,
@ -308,10 +323,9 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
child: ClipRect( child: ClipRect(
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max, children: <Widget>[
children: [
IconButton( IconButton(
icon: Icon(Icons.flash_off), icon: const Icon(Icons.flash_off),
color: controller?.value.flashMode == FlashMode.off color: controller?.value.flashMode == FlashMode.off
? Colors.orange ? Colors.orange
: Colors.blue, : Colors.blue,
@ -320,7 +334,7 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
: null, : null,
), ),
IconButton( IconButton(
icon: Icon(Icons.flash_auto), icon: const Icon(Icons.flash_auto),
color: controller?.value.flashMode == FlashMode.auto color: controller?.value.flashMode == FlashMode.auto
? Colors.orange ? Colors.orange
: Colors.blue, : Colors.blue,
@ -329,7 +343,7 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
: null, : null,
), ),
IconButton( IconButton(
icon: Icon(Icons.flash_on), icon: const Icon(Icons.flash_on),
color: controller?.value.flashMode == FlashMode.always color: controller?.value.flashMode == FlashMode.always
? Colors.orange ? Colors.orange
: Colors.blue, : Colors.blue,
@ -338,7 +352,7 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
: null, : null,
), ),
IconButton( IconButton(
icon: Icon(Icons.highlight), icon: const Icon(Icons.highlight),
color: controller?.value.flashMode == FlashMode.torch color: controller?.value.flashMode == FlashMode.torch
? Colors.orange ? Colors.orange
: Colors.blue, : Colors.blue,
@ -354,11 +368,15 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
Widget _exposureModeControlRowWidget() { Widget _exposureModeControlRowWidget() {
final ButtonStyle styleAuto = TextButton.styleFrom( 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 primary: controller?.value.exposureMode == ExposureMode.auto
? Colors.orange ? Colors.orange
: Colors.blue, : Colors.blue,
); );
final ButtonStyle styleLocked = TextButton.styleFrom( 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 primary: controller?.value.exposureMode == ExposureMode.locked
? Colors.orange ? Colors.orange
: Colors.blue, : Colors.blue,
@ -370,16 +388,14 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
child: Container( child: Container(
color: Colors.grey.shade50, color: Colors.grey.shade50,
child: Column( child: Column(
children: [ children: <Widget>[
Center( const Center(
child: Text("Exposure Mode"), child: Text('Exposure Mode'),
), ),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max, children: <Widget>[
children: [
TextButton( TextButton(
child: Text('AUTO'),
style: styleAuto, style: styleAuto,
onPressed: controller != null onPressed: controller != null
? () => ? () =>
@ -391,24 +407,31 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
showInSnackBar('Resetting exposure point'); showInSnackBar('Resetting exposure point');
} }
}, },
child: const Text('AUTO'),
), ),
TextButton( TextButton(
child: Text('LOCKED'),
style: styleLocked, style: styleLocked,
onPressed: controller != null onPressed: controller != null
? () => ? () =>
onSetExposureModeButtonPressed(ExposureMode.locked) onSetExposureModeButtonPressed(ExposureMode.locked)
: null, : null,
child: const Text('LOCKED'),
),
TextButton(
style: styleLocked,
onPressed: controller != null
? () => controller!.setExposureOffset(0.0)
: null,
child: const Text('RESET OFFSET'),
), ),
], ],
), ),
Center( const Center(
child: Text("Exposure Offset"), child: Text('Exposure Offset'),
), ),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max, children: <Widget>[
children: [
Text(_minAvailableExposureOffset.toString()), Text(_minAvailableExposureOffset.toString()),
Slider( Slider(
value: _currentExposureOffset, value: _currentExposureOffset,
@ -432,11 +455,15 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
Widget _focusModeControlRowWidget() { Widget _focusModeControlRowWidget() {
final ButtonStyle styleAuto = TextButton.styleFrom( 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 primary: controller?.value.focusMode == FocusMode.auto
? Colors.orange ? Colors.orange
: Colors.blue, : Colors.blue,
); );
final ButtonStyle styleLocked = TextButton.styleFrom( 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 primary: controller?.value.focusMode == FocusMode.locked
? Colors.orange ? Colors.orange
: Colors.blue, : Colors.blue,
@ -448,31 +475,32 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
child: Container( child: Container(
color: Colors.grey.shade50, color: Colors.grey.shade50,
child: Column( child: Column(
children: [ children: <Widget>[
Center( const Center(
child: Text("Focus Mode"), child: Text('Focus Mode'),
), ),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max, children: <Widget>[
children: [
TextButton( TextButton(
child: Text('AUTO'),
style: styleAuto, style: styleAuto,
onPressed: controller != null onPressed: controller != null
? () => onSetFocusModeButtonPressed(FocusMode.auto) ? () => onSetFocusModeButtonPressed(FocusMode.auto)
: null, : null,
onLongPress: () { onLongPress: () {
if (controller != null) controller!.setFocusPoint(null); if (controller != null) {
controller!.setFocusPoint(null);
}
showInSnackBar('Resetting focus point'); showInSnackBar('Resetting focus point');
}, },
child: const Text('AUTO'),
), ),
TextButton( TextButton(
child: Text('LOCKED'),
style: styleLocked, style: styleLocked,
onPressed: controller != null onPressed: controller != null
? () => onSetFocusModeButtonPressed(FocusMode.locked) ? () => onSetFocusModeButtonPressed(FocusMode.locked)
: null, : null,
child: const Text('LOCKED'),
), ),
], ],
), ),
@ -489,7 +517,6 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: <Widget>[ children: <Widget>[
IconButton( IconButton(
icon: const Icon(Icons.camera_alt), icon: const Icon(Icons.camera_alt),
@ -512,8 +539,8 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
IconButton( IconButton(
icon: cameraController != null && icon: cameraController != null &&
cameraController.value.isRecordingPaused cameraController.value.isRecordingPaused
? Icon(Icons.play_arrow) ? const Icon(Icons.play_arrow)
: Icon(Icons.pause), : const Icon(Icons.pause),
color: Colors.blue, color: Colors.blue,
onPressed: cameraController != null && onPressed: cameraController != null &&
cameraController.value.isInitialized && cameraController.value.isInitialized &&
@ -531,7 +558,16 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
cameraController.value.isRecordingVideo cameraController.value.isRecordingVideo
? onStopButtonPressed ? onStopButtonPressed
: null, : 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<CameraExampleHome>
Widget _cameraTogglesRowWidget() { Widget _cameraTogglesRowWidget() {
final List<Widget> toggles = <Widget>[]; final List<Widget> toggles = <Widget>[];
final onChanged = (CameraDescription? description) { void onChanged(CameraDescription? description) {
if (description == null) { if (description == null) {
return; return;
} }
onNewCameraSelected(description); onNewCameraSelected(description);
}; }
if (cameras.isEmpty) { if (_cameras.isEmpty) {
return const Text('No camera found'); _ambiguate(SchedulerBinding.instance)?.addPostFrameCallback((_) async {
showInSnackBar('No camera found.');
});
return const Text('None');
} else { } else {
for (CameraDescription cameraDescription in cameras) { for (final CameraDescription cameraDescription in _cameras) {
toggles.add( toggles.add(
SizedBox( SizedBox(
width: 90.0, width: 90.0,
@ -575,8 +614,8 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
String timestamp() => DateTime.now().millisecondsSinceEpoch.toString(); String timestamp() => DateTime.now().millisecondsSinceEpoch.toString();
void showInSnackBar(String message) { void showInSnackBar(String message) {
// ignore: deprecated_member_use ScaffoldMessenger.of(context)
_scaffoldKey.currentState?.showSnackBar(SnackBar(content: Text(message))); .showSnackBar(SnackBar(content: Text(message)));
} }
void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) { void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) {
@ -586,7 +625,7 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
final CameraController cameraController = controller!; final CameraController cameraController = controller!;
final offset = Offset( final Offset offset = Offset(
details.localPosition.dx / constraints.maxWidth, details.localPosition.dx / constraints.maxWidth,
details.localPosition.dy / constraints.maxHeight, details.localPosition.dy / constraints.maxHeight,
); );
@ -594,21 +633,32 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
cameraController.setFocusPoint(offset); cameraController.setFocusPoint(offset);
} }
void onNewCameraSelected(CameraDescription cameraDescription) async { Future<void> onNewCameraSelected(CameraDescription cameraDescription) async {
if (controller != null) { final CameraController? oldController = controller;
await controller!.dispose(); 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( final CameraController cameraController = CameraController(
cameraDescription, cameraDescription,
ResolutionPreset.medium, kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium,
enableAudio: enableAudio, enableAudio: enableAudio,
imageFormatGroup: ImageFormatGroup.jpeg, imageFormatGroup: ImageFormatGroup.jpeg,
); );
controller = cameraController; controller = cameraController;
// If the controller is updated then update the UI. // If the controller is updated then update the UI.
cameraController.addListener(() { cameraController.addListener(() {
if (mounted) setState(() {}); if (mounted) {
setState(() {});
}
if (cameraController.value.hasError) { if (cameraController.value.hasError) {
showInSnackBar( showInSnackBar(
'Camera error ${cameraController.value.errorDescription}'); 'Camera error ${cameraController.value.errorDescription}');
@ -617,22 +667,52 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
try { try {
await cameraController.initialize(); await cameraController.initialize();
await Future.wait([ await Future.wait(<Future<Object?>>[
cameraController // The exposure mode is currently not supported on the web.
.getMinExposureOffset() ...!kIsWeb
.then((value) => _minAvailableExposureOffset = value), ? <Future<Object?>>[
cameraController cameraController.getMinExposureOffset().then(
.getMaxExposureOffset() (double value) => _minAvailableExposureOffset = value),
.then((value) => _maxAvailableExposureOffset = value), cameraController
.getMaxExposureOffset()
.then((double value) => _maxAvailableExposureOffset = value)
]
: <Future<Object?>>[],
cameraController cameraController
.getMaxZoomLevel() .getMaxZoomLevel()
.then((value) => _maxAvailableZoom = value), .then((double value) => _maxAvailableZoom = value),
cameraController cameraController
.getMinZoomLevel() .getMinZoomLevel()
.then((value) => _minAvailableZoom = value), .then((double value) => _minAvailableZoom = value),
]); ]);
} on CameraException catch (e) { } 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) { if (mounted) {
@ -648,7 +728,9 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
videoController?.dispose(); videoController?.dispose();
videoController = null; 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<CameraExampleHome>
} }
} }
void onCaptureOrientationLockButtonPressed() async { Future<void> onCaptureOrientationLockButtonPressed() async {
if (controller != null) { try {
final CameraController cameraController = controller!; if (controller != null) {
if (cameraController.value.isCaptureOrientationLocked) { final CameraController cameraController = controller!;
await cameraController.unlockCaptureOrientation(); if (cameraController.value.isCaptureOrientationLocked) {
showInSnackBar('Capture orientation unlocked'); await cameraController.unlockCaptureOrientation();
} else { showInSnackBar('Capture orientation unlocked');
await cameraController.lockCaptureOrientation(); } else {
showInSnackBar( await cameraController.lockCaptureOrientation();
'Capture orientation locked to ${cameraController.value.lockedCaptureOrientation.toString().split('.').last}'); showInSnackBar(
'Capture orientation locked to ${cameraController.value.lockedCaptureOrientation.toString().split('.').last}');
}
} }
} on CameraException catch (e) {
_showCameraException(e);
} }
} }
void onSetFlashModeButtonPressed(FlashMode mode) { void onSetFlashModeButtonPressed(FlashMode mode) {
setFlashMode(mode).then((_) { setFlashMode(mode).then((_) {
if (mounted) setState(() {}); if (mounted) {
setState(() {});
}
showInSnackBar('Flash mode set to ${mode.toString().split('.').last}'); showInSnackBar('Flash mode set to ${mode.toString().split('.').last}');
}); });
} }
void onSetExposureModeButtonPressed(ExposureMode mode) { void onSetExposureModeButtonPressed(ExposureMode mode) {
setExposureMode(mode).then((_) { setExposureMode(mode).then((_) {
if (mounted) setState(() {}); if (mounted) {
setState(() {});
}
showInSnackBar('Exposure mode set to ${mode.toString().split('.').last}'); showInSnackBar('Exposure mode set to ${mode.toString().split('.').last}');
}); });
} }
void onSetFocusModeButtonPressed(FocusMode mode) { void onSetFocusModeButtonPressed(FocusMode mode) {
setFocusMode(mode).then((_) { setFocusMode(mode).then((_) {
if (mounted) setState(() {}); if (mounted) {
setState(() {});
}
showInSnackBar('Focus mode set to ${mode.toString().split('.').last}'); showInSnackBar('Focus mode set to ${mode.toString().split('.').last}');
}); });
} }
void onVideoRecordButtonPressed() { void onVideoRecordButtonPressed() {
startVideoRecording().then((_) { startVideoRecording().then((_) {
if (mounted) setState(() {}); if (mounted) {
setState(() {});
}
}); });
} }
void onStopButtonPressed() { void onStopButtonPressed() {
stopVideoRecording().then((file) { stopVideoRecording().then((XFile? file) {
if (mounted) setState(() {}); if (mounted) {
setState(() {});
}
if (file != null) { if (file != null) {
showInSnackBar('Video recorded to ${file.path}'); showInSnackBar('Video recorded to ${file.path}');
videoFile = file; videoFile = file;
@ -742,16 +838,39 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
}); });
} }
Future<void> 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() { void onPauseButtonPressed() {
pauseVideoRecording().then((_) { pauseVideoRecording().then((_) {
if (mounted) setState(() {}); if (mounted) {
setState(() {});
}
showInSnackBar('Video recording paused'); showInSnackBar('Video recording paused');
}); });
} }
void onResumeButtonPressed() { void onResumeButtonPressed() {
resumeVideoRecording().then((_) { resumeVideoRecording().then((_) {
if (mounted) setState(() {}); if (mounted) {
setState(() {});
}
showInSnackBar('Video recording resumed'); showInSnackBar('Video recording resumed');
}); });
} }
@ -796,7 +915,7 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
final CameraController? cameraController = controller; final CameraController? cameraController = controller;
if (cameraController == null || !cameraController.value.isRecordingVideo) { if (cameraController == null || !cameraController.value.isRecordingVideo) {
return null; return;
} }
try { try {
@ -811,7 +930,7 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
final CameraController? cameraController = controller; final CameraController? cameraController = controller;
if (cameraController == null || !cameraController.value.isRecordingVideo) { if (cameraController == null || !cameraController.value.isRecordingVideo) {
return null; return;
} }
try { try {
@ -882,12 +1001,16 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
return; return;
} }
final VideoPlayerController vController = final VideoPlayerController vController = kIsWeb
VideoPlayerController.file(File(videoFile!.path)); ? VideoPlayerController.network(videoFile!.path)
: VideoPlayerController.file(File(videoFile!.path));
videoPlayerListener = () { videoPlayerListener = () {
if (videoController != null && videoController!.value.size != null) { if (videoController != null && videoController!.value.size != null) {
// Refreshing the state to update video player with the correct ratio. // Refreshing the state to update video player with the correct ratio.
if (mounted) setState(() {}); if (mounted) {
setState(() {});
}
videoController!.removeListener(videoPlayerListener!); videoController!.removeListener(videoPlayerListener!);
} }
}; };
@ -917,7 +1040,7 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
} }
try { try {
XFile file = await cameraController.takePicture(); final XFile file = await cameraController.takePicture();
return file; return file;
} on CameraException catch (e) { } on CameraException catch (e) {
_showCameraException(e); _showCameraException(e);
@ -926,41 +1049,35 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
} }
void _showCameraException(CameraException e) { void _showCameraException(CameraException e) {
logError(e.code, e.description); _logError(e.code, e.description);
showInSnackBar('Error: ${e.code}\n${e.description}'); showInSnackBar('Error: ${e.code}\n${e.description}');
} }
} }
/// CameraApp is the Main Application.
class CameraApp extends StatelessWidget { class CameraApp extends StatelessWidget {
/// Default Constructor
const CameraApp({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return const MaterialApp(
scrollBehavior: MyCustomScrollBehavior(),
home: CameraExampleHome(), home: CameraExampleHome(),
); );
} }
} }
class MyCustomScrollBehavior extends MaterialScrollBehavior { List<CameraDescription> _cameras = <CameraDescription>[];
// Override behavior methods and getters like dragDevices
@override
Set<PointerDeviceKind> get dragDevices => {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
};
}
List<CameraDescription> cameras = [];
Future<void> main() async { Future<void> main() async {
// Fetch the available cameras before initializing the app. // Fetch the available cameras before initializing the app.
try { try {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
cameras = await availableCameras(); _cameras = await availableCameras();
} on CameraException catch (e) { } 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?. /// This allows a value of type T or T? to be treated as a value of type T?.

View File

@ -4,18 +4,17 @@ publish_to: none
environment: environment:
sdk: ">=2.12.0 <3.0.0" sdk: ">=2.12.0 <3.0.0"
flutter: ">=1.22.0" flutter: ">=2.10.0"
dependencies: dependencies:
camera: ^0.8.1+7 camera: ^0.10.0+1
camera_elinux: camera_elinux:
path: ../ path: ../
flutter: flutter:
sdk: flutter sdk: flutter
path_provider: ^2.0.0
path_provider_elinux: path_provider_elinux:
path: ../../path_provider path: ../../path_provider
video_player: ^2.1.4 video_player: ^2.4.7
video_player_elinux: video_player_elinux:
path: ../../video_player path: ../../video_player
@ -26,7 +25,6 @@ dev_dependencies:
sdk: flutter sdk: flutter
integration_test: integration_test:
sdk: flutter sdk: flutter
pedantic: ^1.10.0
flutter: flutter:
uses-material-design: true uses-material-design: true

View File

@ -2,13 +2,13 @@ name: camera_elinux
description: A Flutter plugin for getting information about and controlling the description: A Flutter plugin for getting information about and controlling the
camera on eLinux. Supports previewing the camera feed, capturing images, capturing video, camera on eLinux. Supports previewing the camera feed, capturing images, capturing video,
and streaming image buffers to dart. and streaming image buffers to dart.
version: 0.2.0 version: 0.2.1
homepage: https://github.com/sony/flutter-elinux-plugins homepage: https://github.com/sony/flutter-elinux-plugins
repository: https://github.com/sony/flutter-elinux-plugins/tree/main/packages/camera repository: https://github.com/sony/flutter-elinux-plugins/tree/main/packages/camera
environment: environment:
sdk: ">=2.12.0 <3.0.0" sdk: ">=2.12.0 <3.0.0"
flutter: ">=1.20.0" flutter: ">=2.10.0"
dependencies: dependencies:
flutter: flutter: