[camera][draft] Add camera plugin (#29)

Added the first draft version, but there are still a lot of unimplemented features.
This commit is contained in:
Hidenori Matsubayashi
2021-08-16 16:43:28 +09:00
committed by GitHub
parent 4c37b98e79
commit 999fdad59c
45 changed files with 3874 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
.DS_Store
.dart_tool/
.packages
.pub/
build/
+2
View File
@@ -0,0 +1,2 @@
## 0.1.0
* First draft version.
+26
View File
@@ -0,0 +1,26 @@
Copyright (c) 2021 Sony Group Corporation. All rights reserved.
Copyright (c) 2013 The Flutter Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the names of the copyright holders nor the names of the
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+47
View File
@@ -0,0 +1,47 @@
# camera_elinux
The implementation of the camera plugin for flutter elinux. APIs are designed to be API compatible with the the official [`camera`](https://github.com/flutter/plugins/tree/master/packages/camera).
## Required libraries
This plugin uses [GStreamer](https://gstreamer.freedesktop.org/) internally.
```Shell
$ sudo apt install libglib2.0-dev
$ sudo apt install libgstreamer1.0-dev
# Install as needed.
$ sudo apt libgstreamer-plugins-base1.0-dev \
gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav
```
## Usage
### pubspec.yaml
```yaml
dependencies:
camera: ^0.8.1+7
camera_elinux:
git:
url: https://github.com/sony/flutter-elinux-plugins.git
path: packages/camera
ref: main
```
### Source code
Import `camera` in your Dart code:
```dart
import 'package:camera/camera.dart';
```
## Troubleshoting
If you get the following error:
```Shell
Wrong JPEG library version: library is 62, caller expects 80
```
, try the following:
```Shell
sudo mv /usr/lib/x86_64-linux-gnu/gstreamer-1.0/libgstjpeg.so /usr/lib/x86_64-linux-gnu/gstreamer-1.0/libgstjpeg.so.org
```
+1
View File
@@ -0,0 +1 @@
flutter/
+47
View File
@@ -0,0 +1,47 @@
cmake_minimum_required(VERSION 3.15)
set(PROJECT_NAME "camera_elinux")
project(${PROJECT_NAME} LANGUAGES CXX)
# This value is used when generating builds using this plugin, so it must
# not be changed
set(PLUGIN_NAME "camera_elinux_plugin")
find_package(PkgConfig)
pkg_check_modules(GLIB REQUIRED glib-2.0)
pkg_check_modules(GSTREAMER REQUIRED gstreamer-1.0)
add_library(${PLUGIN_NAME} SHARED
"camera_elinux_plugin.cc"
"gst_camera.cc"
"types/exposure_mode.cc"
"types/focus_mode.cc"
"types/orientation.cc"
"method_channel/method_channel_camera.cc"
"method_channel/method_channel_device.cc"
)
apply_standard_settings(${PLUGIN_NAME})
set_target_properties(${PLUGIN_NAME} PROPERTIES
CXX_VISIBILITY_PRESET hidden)
target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL)
target_include_directories(${PLUGIN_NAME} INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin)
target_include_directories(${PLUGIN_NAME}
PRIVATE
"./"
${GLIB_INCLUDE_DIRS}
${GSTREAMER_INCLUDE_DIRS}
)
target_link_libraries(${PLUGIN_NAME}
PRIVATE
${GLIB_LIBRARIES}
${GSTREAMER_LIBRARIES}
)
# List of absolute paths to libraries that should be bundled with the plugin
set(camera_elinux_bundled_libraries
""
PARENT_SCOPE
)
@@ -0,0 +1,340 @@
// Copyright 2021 Sony Group Corporation. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "include/camera_elinux/camera_elinux_plugin.h"
#include <flutter/method_channel.h>
#include <flutter/plugin_registrar.h>
#include <flutter/standard_method_codec.h>
#include <memory>
#include "camera_stream_handler_impl.h"
#include "events/camera_initialized_event.h"
#include "gst_camera.h"
#include "messages/messages.h"
#include "method_channel/method_channel_camera.h"
#include "method_channel/method_channel_device.h"
namespace {
constexpr char kCameraChannelName[] = "plugins.flutter.io/camera";
constexpr char kCameraChannelApiAvailableCameras[] = "availableCameras";
constexpr char kCameraChannelApiCreate[] = "create";
constexpr char kCameraChannelApiInitialize[] = "initialize";
constexpr char kCameraChannelApiTakePicture[] = "takePicture";
constexpr char kCameraChannelApiPrepareForVideoRecording[] =
"prepareForVideoRecording";
constexpr char kCameraChannelApiStartVideoRecording[] = "startVideoRecording";
constexpr char kCameraChannelApiStopVideoRecording[] = "stopVideoRecording";
constexpr char kCameraChannelApiPauseVideoRecording[] = "pauseVideoRecording";
constexpr char kCameraChannelApiResumeVideoRecording[] = "resumeVideoRecording";
constexpr char kCameraChannelApiSetFlashMode[] = "setFlashMode";
constexpr char kCameraChannelApiSetExposureMode[] = "setExposureMode";
constexpr char kCameraChannelApiSetExposurePoint[] = "setExposurePoint";
constexpr char kCameraChannelApiGetMinExposureOffset[] = "getMinExposureOffset";
constexpr char kCameraChannelApiGetMaxExposureOffset[] = "getMaxExposureOffset";
constexpr char kCameraChannelApiGetExposureOffsetStepSize[] =
"getExposureOffsetStepSize";
constexpr char kCameraChannelApiSetExposureOffset[] = "setExposureOffset";
constexpr char kCameraChannelApiSetFocusMode[] = "setFocusMode";
constexpr char kCameraChannelApiSetFocusPoint[] = "setFocusPoint";
constexpr char kCameraChannelApiStartImageStream[] = "startImageStream";
constexpr char kCameraChannelApiStopImageStream[] = "stopImageStream";
constexpr char kCameraChannelApiGetMaxZoomLevel[] = "getMaxZoomLevel";
constexpr char kCameraChannelApiGetMinZoomLevel[] = "getMinZoomLevel";
constexpr char kCameraChannelApiSetZoomLevel[] = "setZoomLevel";
constexpr char kCameraChannelApiLockCaptureOrientation[] =
"lockCaptureOrientation";
constexpr char kCameraChannelApiUnlockCaptureOrientation[] =
"unlockCaptureOrientation";
constexpr char kCameraChannelApiDispose[] = "dispose";
class CameraPlugin : public flutter::Plugin {
public:
static void RegisterWithRegistrar(flutter::PluginRegistrar* registrar);
CameraPlugin(flutter::PluginRegistrar* plugin_registrar,
flutter::TextureRegistrar* texture_registrar)
: plugin_registrar_(plugin_registrar),
texture_registrar_(texture_registrar) {
GstCamera::GstLibraryLoad();
}
virtual ~CameraPlugin() {
if (camera_) {
camera_->Stop();
camera_ = nullptr;
}
GstCamera::GstLibraryUnload();
}
private:
void HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue>& method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
void HandleAvailableCamerasCall(
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
void HandleCreateCall(
const flutter::EncodableValue* message,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
void HandleInitializeCall(
const flutter::EncodableValue* message,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
void HandleGetMaxZoomLevelCall(
const flutter::EncodableValue* message,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
void HandleGetMinZoomLevelCall(
const flutter::EncodableValue* message,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
void HandleSetZoomLevelCall(
const flutter::EncodableValue* message,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
void HandleLockCaptureOrientationCall(
const flutter::EncodableValue* message,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
void HandleDisposeCall(
const flutter::EncodableValue* message,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
flutter::PluginRegistrar* plugin_registrar_;
flutter::TextureRegistrar* texture_registrar_;
std::unique_ptr<FlutterDesktopPixelBuffer> buffer_;
std::unique_ptr<flutter::TextureVariant> texture_;
std::unique_ptr<GstCamera> camera_ = nullptr;
int64_t texture_id_;
std::unique_ptr<MethodChannelCamera> method_channel_camera_;
std::unique_ptr<MethodChannelDevice> method_channel_device_;
};
// static
void CameraPlugin::RegisterWithRegistrar(flutter::PluginRegistrar* registrar) {
auto plugin =
std::make_unique<CameraPlugin>(registrar, registrar->texture_registrar());
auto channel =
std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
registrar->messenger(), kCameraChannelName,
&flutter::StandardMethodCodec::GetInstance());
channel->SetMethodCallHandler(
[plugin_pointer = plugin.get()](const auto& call, auto result) {
plugin_pointer->HandleMethodCall(call, std::move(result));
});
registrar->AddPlugin(std::move(plugin));
}
void CameraPlugin::HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue>& method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
const std::string& method_name = method_call.method_name();
if (!method_name.compare(kCameraChannelApiAvailableCameras)) {
HandleAvailableCamerasCall(std::move(result));
} else if (!method_name.compare(kCameraChannelApiCreate)) {
HandleCreateCall(method_call.arguments(), std::move(result));
} else if (!method_name.compare(kCameraChannelApiInitialize)) {
HandleInitializeCall(method_call.arguments(), std::move(result));
} else if (!method_name.compare(kCameraChannelApiTakePicture)) {
result->NotImplemented();
} else if (!method_name.compare(kCameraChannelApiPrepareForVideoRecording)) {
result->NotImplemented();
} else if (!method_name.compare(kCameraChannelApiStartVideoRecording)) {
result->NotImplemented();
} else if (!method_name.compare(kCameraChannelApiStopVideoRecording)) {
result->NotImplemented();
} else if (!method_name.compare(kCameraChannelApiPauseVideoRecording)) {
result->NotImplemented();
} else if (!method_name.compare(kCameraChannelApiResumeVideoRecording)) {
result->NotImplemented();
} else if (!method_name.compare(kCameraChannelApiSetFlashMode)) {
result->NotImplemented();
} else if (!method_name.compare(kCameraChannelApiSetExposureMode)) {
result->NotImplemented();
} else if (!method_name.compare(kCameraChannelApiSetExposurePoint)) {
result->NotImplemented();
} else if (!method_name.compare(kCameraChannelApiGetMinExposureOffset)) {
result->NotImplemented();
} else if (!method_name.compare(kCameraChannelApiGetMaxExposureOffset)) {
result->NotImplemented();
} else if (!method_name.compare(kCameraChannelApiGetExposureOffsetStepSize)) {
result->NotImplemented();
} else if (!method_name.compare(kCameraChannelApiSetExposureOffset)) {
result->NotImplemented();
} else if (!method_name.compare(kCameraChannelApiSetFocusMode)) {
result->NotImplemented();
} else if (!method_name.compare(kCameraChannelApiSetFocusPoint)) {
result->NotImplemented();
} else if (!method_name.compare(kCameraChannelApiStartImageStream)) {
result->NotImplemented();
} else if (!method_name.compare(kCameraChannelApiStopImageStream)) {
result->NotImplemented();
} else if (!method_name.compare(kCameraChannelApiGetMaxZoomLevel)) {
HandleGetMaxZoomLevelCall(method_call.arguments(), std::move(result));
} else if (!method_name.compare(kCameraChannelApiGetMinZoomLevel)) {
HandleGetMinZoomLevelCall(method_call.arguments(), std::move(result));
} else if (!method_name.compare(kCameraChannelApiSetZoomLevel)) {
HandleSetZoomLevelCall(method_call.arguments(), std::move(result));
} else if (!method_name.compare(kCameraChannelApiLockCaptureOrientation)) {
HandleLockCaptureOrientationCall(method_call.arguments(),
std::move(result));
} else if (!method_name.compare(kCameraChannelApiUnlockCaptureOrientation)) {
result->NotImplemented();
} else if (!method_name.compare(kCameraChannelApiDispose)) {
HandleDisposeCall(method_call.arguments(), std::move(result));
} else {
result->NotImplemented();
}
}
void CameraPlugin::HandleAvailableCamerasCall(
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
flutter::EncodableList cameras;
// TODO: add multi camera support.
for (int i = 0; i < 1; i++) {
AvailableCamerasMessage camera;
camera.SetName("camera" + std::to_string(i));
camera.SetSensorOrientation(0);
camera.SetLensFacing("back");
cameras.push_back(camera.ToMap());
}
result->Success(flutter::EncodableValue(cameras));
}
void CameraPlugin::HandleCreateCall(
const flutter::EncodableValue* message,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
// auto meta = CreateMessage::FromMap(message);
buffer_ = std::make_unique<FlutterDesktopPixelBuffer>();
texture_ =
std::make_unique<flutter::TextureVariant>(flutter::PixelBufferTexture(
[host = this](size_t width,
size_t height) -> const FlutterDesktopPixelBuffer* {
host->buffer_->width = host->camera_->GetPreviewWidth();
host->buffer_->height = host->camera_->GetPreviewHeight();
host->buffer_->buffer = host->camera_->GetPreviewFrameBuffer();
return host->buffer_.get();
}));
auto texture_id = texture_registrar_->RegisterTexture(texture_.get());
auto stream_handler = std::make_unique<CameraStreamHandlerImpl>(
// OnNotifyInitialized
[]() {},
// OnNotifyFrameDecoded
[texture_id, host = this]() {
host->texture_registrar_->MarkTextureFrameAvailable(texture_id);
},
// OnNotifyCompleted
[]() {});
camera_ = std::make_unique<GstCamera>(std::move(stream_handler));
texture_id_ = texture_id;
flutter::EncodableMap reply;
reply[flutter::EncodableValue("cameraId")] =
flutter::EncodableValue(texture_id);
result->Success(flutter::EncodableValue(reply));
}
void CameraPlugin::HandleInitializeCall(
const flutter::EncodableValue* message,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
camera_->Play();
double preview_width = camera_->GetPreviewWidth();
double preview_height = camera_->GetPreviewHeight();
{
method_channel_camera_ =
std::make_unique<MethodChannelCamera>(plugin_registrar_, texture_id_);
CameraInitializedEvent message;
message.SetPreviewWidth(preview_width);
message.SetPreviewHeight(preview_height);
message.SetFocusMode(FocusMode::kAuto);
message.SetExposureMode(ExposureMode::kAuto);
message.SetFocusPointSupported(false);
message.SetExposurePointSupported(false);
method_channel_camera_->SendInitializedEvent(message);
}
{
method_channel_device_ =
std::make_unique<MethodChannelDevice>(plugin_registrar_);
auto orientation = DeviceOrientation::kLandscapeRight;
method_channel_device_->SendDeviceOrientationChangeEvent(orientation);
}
result->Success();
}
void CameraPlugin::HandleGetMaxZoomLevelCall(
const flutter::EncodableValue* message,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
if (!camera_) {
result->Error("Not found an active camera",
"Check for creating a camera device");
return;
}
result->Success(flutter::EncodableValue(camera_->GetMaxZoomLevel()));
}
void CameraPlugin::HandleGetMinZoomLevelCall(
const flutter::EncodableValue* message,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
if (!camera_) {
result->Error("Not found an active camera",
"Check for creating a camera device");
return;
}
result->Success(flutter::EncodableValue(camera_->GetMinZoomLevel()));
}
void CameraPlugin::HandleSetZoomLevelCall(
const flutter::EncodableValue* message,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
if (!camera_) {
result->Error("Not found an active camera",
"Check for creating a camera device");
return;
}
auto meta = ZoomLevelMessage::FromMap(*message);
if (camera_->SetZoomLevel(meta.GetZoom())) {
result->Success();
} else {
result->Error("Failed to change the zoom level", "Check the zoom level");
}
}
void CameraPlugin::HandleLockCaptureOrientationCall(
const flutter::EncodableValue* message,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
result->NotImplemented();
}
void CameraPlugin::HandleDisposeCall(
const flutter::EncodableValue* message,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
// TODO: add multi camera support.
if (camera_) {
camera_->Stop();
camera_ = nullptr;
texture_registrar_->UnregisterTexture(texture_id_);
}
result->Success();
}
} // namespace
void CameraElinuxPluginRegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar) {
CameraPlugin::RegisterWithRegistrar(
flutter::PluginRegistrarManager::GetInstance()
->GetRegistrar<flutter::PluginRegistrar>(registrar));
}
@@ -0,0 +1,32 @@
// Copyright 2021 Sony Group Corporation. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PACKAGES_CAMERA_CAMERA_ELINUX_CAMERA_STREAM_HANDLER_H_
#define PACKAGES_CAMERA_CAMERA_ELINUX_CAMERA_STREAM_HANDLER_H_
class CameraStreamHandler {
public:
CameraStreamHandler() = default;
virtual ~CameraStreamHandler() = default;
// Prevent copying.
CameraStreamHandler(CameraStreamHandler const&) = delete;
CameraStreamHandler& operator=(CameraStreamHandler const&) = delete;
// Notifies the completion of initializing the video player.
void OnNotifyInitialized() { OnNotifyInitializedInternal(); }
// Notifies the completion of decoding a video frame.
void OnNotifyFrameDecoded() { OnNotifyFrameDecodedInternal(); }
// Notifies the completion of playing a video.
void OnNotifyCompleted() { OnNotifyCompletedInternal(); }
protected:
virtual void OnNotifyInitializedInternal() = 0;
virtual void OnNotifyFrameDecodedInternal() = 0;
virtual void OnNotifyCompletedInternal() = 0;
};
#endif // PACKAGES_CAMERA_CAMERA_ELINUX_CAMERA_STREAM_HANDLER_H_
@@ -0,0 +1,57 @@
// Copyright 2021 Sony Group Corporation. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PACKAGES_CAMERA_CAMERA_ELINUX_CAMERA_STREAM_HANDLER_IMPL_H_
#define PACKAGES_CAMERA_CAMERA_ELINUX_CAMERA_STREAM_HANDLER_IMPL_H_
#include <functional>
#include "camera_stream_handler.h"
class CameraStreamHandlerImpl : public CameraStreamHandler {
public:
using OnNotifyInitialized = std::function<void()>;
using OnNotifyFrameDecoded = std::function<void()>;
using OnNotifyCompleted = std::function<void()>;
CameraStreamHandlerImpl(OnNotifyInitialized on_notify_initialized,
OnNotifyFrameDecoded on_notify_frame_decoded,
OnNotifyCompleted on_notify_completed)
: on_notify_initialized_(on_notify_initialized),
on_notify_frame_decoded_(on_notify_frame_decoded),
on_notify_completed_(on_notify_completed) {}
virtual ~CameraStreamHandlerImpl() = default;
// Prevent copying.
CameraStreamHandlerImpl(CameraStreamHandlerImpl const&) = delete;
CameraStreamHandlerImpl& operator=(CameraStreamHandlerImpl const&) = delete;
protected:
// |CameraStreamHandler|
void OnNotifyInitializedInternal() {
if (on_notify_initialized_) {
on_notify_initialized_();
}
}
// |CameraStreamHandler|
void OnNotifyFrameDecodedInternal() {
if (on_notify_frame_decoded_) {
on_notify_frame_decoded_();
}
}
// |CameraStreamHandler|
void OnNotifyCompletedInternal() {
if (on_notify_completed_) {
on_notify_completed_();
}
}
OnNotifyInitialized on_notify_initialized_;
OnNotifyFrameDecoded on_notify_frame_decoded_;
OnNotifyCompleted on_notify_completed_;
};
#endif // PACKAGES_CAMERA_CAMERA_ELINUX_CAMERA_STREAM_HANDLER_IMPL_H_
@@ -0,0 +1,128 @@
// Copyright 2021 Sony Group Corporation. All rights reserved.
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PACKAGES_CAMERA_CAMERA_ELINUX_CAMERA_EVENT_CAMERA_INITIALIZED_EVENT_H_
#define PACKAGES_CAMERA_CAMERA_ELINUX_CAMERA_EVENT_CAMERA_INITIALIZED_EVENT_H_
#include <flutter/binary_messenger.h>
#include <flutter/encodable_value.h>
#include <string>
#include <variant>
#include "types/exposure_mode.h"
#include "types/focus_mode.h"
// See:
// flutter/plugins/packages/camera/camera_platform_interface/lib/src/events/camera_event.dart
class CameraInitializedEvent {
public:
CameraInitializedEvent() = default;
~CameraInitializedEvent() = default;
// Prevent copying.
CameraInitializedEvent(CameraInitializedEvent const&) = default;
CameraInitializedEvent& operator=(CameraInitializedEvent const&) = default;
void SetPreviewWidth(const double& width) { preview_width_ = width; }
double GetPreviewWidth() const { return preview_width_; }
void SetPreviewHeight(const double& height) { preview_height_ = height; }
double GetPreviewHeight() const { return preview_height_; }
void SetFocusMode(const FocusMode& focus_mode) { focus_mode_ = focus_mode; }
FocusMode GetFocusMode() const { return focus_mode_; }
void SetExposureMode(const ExposureMode& exposure_mode) {
exposure_mode_ = exposure_mode;
}
ExposureMode GetExposureMode() const { return exposure_mode_; }
void SetFocusPointSupported(const bool& supported) {
focus_point_supported_ = supported;
}
bool GetFocusPointSupported() const { return focus_point_supported_; }
void SetExposurePointSupported(const bool& supported) {
exposure_point_supported_ = supported;
}
bool GetExposurePointSupported() const { return exposure_point_supported_; }
flutter::EncodableValue ToMap() {
flutter::EncodableMap map = {
{flutter::EncodableValue("previewWidth"),
flutter::EncodableValue(preview_width_)},
{flutter::EncodableValue("previewHeight"),
flutter::EncodableValue(preview_height_)},
{flutter::EncodableValue("focusMode"),
flutter::EncodableValue(SerializeFocusMode(focus_mode_))},
{flutter::EncodableValue("exposureMode"),
flutter::EncodableValue(SerializeExposureMode(exposure_mode_))},
{flutter::EncodableValue("focusPointSupported"),
flutter::EncodableValue(focus_point_supported_)},
{flutter::EncodableValue("exposurePointSupported"),
flutter::EncodableValue(exposure_point_supported_)},
};
return flutter::EncodableValue(map);
}
static CameraInitializedEvent FromMap(const flutter::EncodableValue& value) {
CameraInitializedEvent message;
if (std::holds_alternative<flutter::EncodableMap>(value)) {
auto map = std::get<flutter::EncodableMap>(value);
flutter::EncodableValue& preview_width =
map[flutter::EncodableValue("previewWidth")];
if (std::holds_alternative<double>(preview_width)) {
message.SetPreviewWidth(std::get<double>(preview_width));
}
flutter::EncodableValue& preview_height =
map[flutter::EncodableValue("previewHeight")];
if (std::holds_alternative<double>(preview_height)) {
message.SetPreviewHeight(std::get<double>(preview_height));
}
flutter::EncodableValue& focus_mode =
map[flutter::EncodableValue("focusMode")];
if (std::holds_alternative<std::string>(focus_mode)) {
message.SetFocusMode(
DeserializeFocusMode(std::get<std::string>(focus_mode)));
}
flutter::EncodableValue& exposure_mode =
map[flutter::EncodableValue("exposureMode")];
if (std::holds_alternative<std::string>(exposure_mode)) {
message.SetExposureMode(
DeserializeExposureMode(std::get<std::string>(exposure_mode)));
}
flutter::EncodableValue& focus_point_supported =
map[flutter::EncodableValue("focusPointSupported")];
if (std::holds_alternative<bool>(focus_point_supported)) {
message.SetFocusPointSupported(std::get<bool>(focus_point_supported));
}
flutter::EncodableValue& exposure_point_supported =
map[flutter::EncodableValue("exposurePointSupported")];
if (std::holds_alternative<bool>(exposure_point_supported)) {
message.SetExposurePointSupported(
std::get<bool>(exposure_point_supported));
}
}
return message;
}
private:
double preview_width_;
double preview_height_;
FocusMode focus_mode_;
ExposureMode exposure_mode_;
bool focus_point_supported_;
bool exposure_point_supported_;
};
#endif // PACKAGES_CAMERA_CAMERA_ELINUX_CAMERA_EVENT_CAMERA_INITIALIZED_EVENT_H_
+309
View File
@@ -0,0 +1,309 @@
// Copyright 2021 Sony Group Corporation. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "gst_camera.h"
#include <iostream>
GstCamera::GstCamera(std::unique_ptr<CameraStreamHandler> handler)
: stream_handler_(std::move(handler)) {
gst_.pipeline = nullptr;
gst_.camerabin = nullptr;
gst_.video_convert = nullptr;
gst_.video_sink = nullptr;
gst_.output = nullptr;
gst_.bus = nullptr;
gst_.buffer = nullptr;
if (!CreatePipeline()) {
std::cerr << "Failed to create a pipeline" << std::endl;
DestroyPipeline();
return;
}
// Prerolls before getting information from the pipeline.
Preroll();
GetZoomMaxMinSize(max_zoom_level_, min_zoom_level_);
}
GstCamera::~GstCamera() {
Stop();
DestroyPipeline();
}
// static
void GstCamera::GstLibraryLoad() { gst_init(NULL, NULL); }
// static
void GstCamera::GstLibraryUnload() { gst_deinit(); }
bool GstCamera::Play() {
auto result = gst_element_set_state(gst_.pipeline, GST_STATE_PLAYING);
if (result == GST_STATE_CHANGE_FAILURE) {
std::cerr << "Failed to change the state to PLAYING" << std::endl;
return false;
}
// Waits until the state becomes GST_STATE_PLAYING.
if (result == GST_STATE_CHANGE_ASYNC) {
GstState state;
result =
gst_element_get_state(gst_.pipeline, &state, NULL, GST_CLOCK_TIME_NONE);
if (result == GST_STATE_CHANGE_FAILURE) {
std::cerr << "Failed to get the current state" << std::endl;
}
}
return true;
}
bool GstCamera::Pause() {
if (gst_element_set_state(gst_.pipeline, GST_STATE_PAUSED) ==
GST_STATE_CHANGE_FAILURE) {
std::cerr << "Failed to change the state to PAUSED" << std::endl;
return false;
}
return true;
}
bool GstCamera::Stop() {
if (gst_element_set_state(gst_.pipeline, GST_STATE_READY) ==
GST_STATE_CHANGE_FAILURE) {
std::cerr << "Failed to change the state to READY" << std::endl;
return false;
}
return true;
}
bool GstCamera::SetZoomLevel(float zoom) {
if (zoom_level_ == zoom) {
return true;
}
if (max_zoom_level_ < zoom) {
std::cerr << "zoom level(" << zoom << ") is over the max-zoom level("
<< max_zoom_level_ << ")" << std::endl;
return false;
}
if (min_zoom_level_ > zoom) {
std::cerr << "zoom level(" << zoom << ") is under the min-zoom level("
<< min_zoom_level_ << ")" << std::endl;
return false;
}
g_object_set(gst_.camerabin, "zoom", zoom, NULL);
zoom_level_ = zoom;
return true;
}
const uint8_t* GstCamera::GetPreviewFrameBuffer() {
std::shared_lock<std::shared_mutex> lock(mutex_buffer_);
if (!gst_.buffer) {
return nullptr;
}
const uint32_t pixel_bytes = width_ * height_ * 4;
gst_buffer_extract(gst_.buffer, 0, pixels_.get(), pixel_bytes);
return reinterpret_cast<const uint8_t*>(pixels_.get());
}
// Creats a camra pipeline using camerabin.
// $ gst-launch-1.0 camerabin viewfinder-sink="videoconvert !
// video/x-raw,format=RGBA ! fakesink"
bool GstCamera::CreatePipeline() {
gst_.pipeline = gst_pipeline_new("pipeline");
if (!gst_.pipeline) {
std::cerr << "Failed to create a pipeline" << std::endl;
return false;
}
gst_.camerabin = gst_element_factory_make("camerabin", "camerabin");
if (!gst_.camerabin) {
std::cerr << "Failed to create a source" << std::endl;
return false;
}
gst_.video_convert = gst_element_factory_make("videoconvert", "videoconvert");
if (!gst_.video_convert) {
std::cerr << "Failed to create a videoconvert" << std::endl;
return false;
}
gst_.video_sink = gst_element_factory_make("fakesink", "videosink");
if (!gst_.video_sink) {
std::cerr << "Failed to create a videosink" << std::endl;
return false;
}
gst_.output = gst_bin_new("output");
if (!gst_.output) {
std::cerr << "Failed to create an output" << std::endl;
return false;
}
gst_.bus = gst_pipeline_get_bus(GST_PIPELINE(gst_.pipeline));
if (!gst_.bus) {
std::cerr << "Failed to create a bus" << std::endl;
return false;
}
gst_bus_set_sync_handler(gst_.bus, (GstBusSyncHandler)HandleGstMessage, this,
NULL);
// Sets properties to fakesink to get the callback of a decoded frame.
g_object_set(G_OBJECT(gst_.video_sink), "sync", TRUE, "qos", FALSE, NULL);
g_object_set(G_OBJECT(gst_.video_sink), "signal-handoffs", TRUE, NULL);
g_signal_connect(G_OBJECT(gst_.video_sink), "handoff",
G_CALLBACK(HandoffHandler), this);
gst_bin_add_many(GST_BIN(gst_.output), gst_.video_convert, gst_.video_sink,
NULL);
// Adds caps to the converter to convert the color format to RGBA.
auto* caps = gst_caps_from_string("video/x-raw,format=RGBA");
auto link_ok =
gst_element_link_filtered(gst_.video_convert, gst_.video_sink, caps);
gst_caps_unref(caps);
if (!link_ok) {
std::cerr << "Failed to link elements" << std::endl;
return false;
}
auto* sinkpad = gst_element_get_static_pad(gst_.video_convert, "sink");
auto* ghost_sinkpad = gst_ghost_pad_new("sink", sinkpad);
gst_pad_set_active(ghost_sinkpad, TRUE);
gst_element_add_pad(gst_.output, ghost_sinkpad);
// Sets properties to camerabin.
g_object_set(gst_.camerabin, "viewfinder-sink", gst_.output, NULL);
gst_bin_add_many(GST_BIN(gst_.pipeline), gst_.camerabin, NULL);
return true;
}
void GstCamera::Preroll() {
if (!gst_.camerabin) {
return;
}
auto result = gst_element_set_state(gst_.pipeline, GST_STATE_PAUSED);
if (result == GST_STATE_CHANGE_FAILURE) {
std::cerr << "Failed to change the state to PAUSED" << std::endl;
return;
}
// Waits until the state becomes GST_STATE_PAUSED.
if (result == GST_STATE_CHANGE_ASYNC) {
GstState state;
result =
gst_element_get_state(gst_.pipeline, &state, NULL, GST_CLOCK_TIME_NONE);
if (result == GST_STATE_CHANGE_FAILURE) {
std::cerr << "Failed to get the current state" << std::endl;
}
}
}
void GstCamera::DestroyPipeline() {
if (gst_.video_sink) {
g_object_set(G_OBJECT(gst_.video_sink), "signal-handoffs", FALSE, NULL);
}
if (gst_.pipeline) {
gst_element_set_state(gst_.pipeline, GST_STATE_NULL);
}
if (gst_.buffer) {
gst_buffer_unref(gst_.buffer);
gst_.buffer = nullptr;
}
if (gst_.bus) {
gst_object_unref(gst_.bus);
gst_.bus = nullptr;
}
if (gst_.pipeline) {
gst_object_unref(gst_.pipeline);
gst_.pipeline = nullptr;
}
if (gst_.camerabin) {
gst_.camerabin = nullptr;
}
if (gst_.output) {
gst_.output = nullptr;
}
if (gst_.video_sink) {
gst_.video_sink = nullptr;
}
if (gst_.video_convert) {
gst_.video_convert = nullptr;
}
}
void GstCamera::GetZoomMaxMinSize(float& max, float& min) {
if (!gst_.pipeline || !gst_.camerabin) {
std::cerr << "The pileline hasn't initialized yet.";
return;
}
g_object_get(gst_.camerabin, "max-zoom", &max, NULL);
min = 1.0;
}
// static
void GstCamera::HandoffHandler(GstElement* fakesink, GstBuffer* buf,
GstPad* new_pad, gpointer user_data) {
auto* self = reinterpret_cast<GstCamera*>(user_data);
auto* caps = gst_pad_get_current_caps(new_pad);
auto* structure = gst_caps_get_structure(caps, 0);
int width;
int height;
gst_structure_get_int(structure, "width", &width);
gst_structure_get_int(structure, "height", &height);
if (width != self->width_ || height != self->height_) {
self->width_ = width;
self->height_ = height;
self->pixels_.reset(new uint32_t[width * height]);
std::cout << "Pixel buffer size: width = " << width
<< ", height = " << height << std::endl;
}
std::lock_guard<std::shared_mutex> lock(self->mutex_buffer_);
if (self->gst_.buffer) {
gst_buffer_unref(self->gst_.buffer);
self->gst_.buffer = nullptr;
}
self->gst_.buffer = gst_buffer_ref(buf);
self->stream_handler_->OnNotifyFrameDecoded();
}
// static
gboolean GstCamera::HandleGstMessage(GstBus* bus, GstMessage* message,
gpointer user_data) {
switch (GST_MESSAGE_TYPE(message)) {
case GST_MESSAGE_WARNING: {
gchar* debug;
GError* error;
gst_message_parse_warning(message, &error, &debug);
g_printerr("WARNING from element %s: %s\n", GST_OBJECT_NAME(message->src),
error->message);
g_printerr("Warning details: %s\n", debug);
g_free(debug);
g_error_free(error);
break;
}
case GST_MESSAGE_ERROR: {
gchar* debug;
GError* error;
gst_message_parse_error(message, &error, &debug);
g_printerr("ERROR from element %s: %s\n", GST_OBJECT_NAME(message->src),
error->message);
g_printerr("Error details: %s\n", debug);
g_free(debug);
g_error_free(error);
break;
}
default:
break;
}
return TRUE;
}
+68
View File
@@ -0,0 +1,68 @@
// Copyright 2021 Sony Group Corporation. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PACKAGES_CAMERA_CAMERA_ELINUX_GST_CAMERA_H_
#define PACKAGES_CAMERA_CAMERA_ELINUX_GST_CAMERA_H_
#include <gst/gst.h>
#include <memory>
#include <shared_mutex>
#include <string>
#include "camera_stream_handler.h"
class GstCamera {
public:
GstCamera(std::unique_ptr<CameraStreamHandler> handler);
~GstCamera();
static void GstLibraryLoad();
static void GstLibraryUnload();
bool Play();
bool Pause();
bool Stop();
bool SetZoomLevel(float zoom);
float GetMaxZoomLevel() const { return max_zoom_level_; };
float GetMinZoomLevel() const { return min_zoom_level_; };
const uint8_t* GetPreviewFrameBuffer();
int32_t GetPreviewWidth() const { return width_; };
int32_t GetPreviewHeight() const { return height_; };
private:
struct GstCameraElements {
GstElement* pipeline;
GstElement* camerabin;
GstElement* video_convert;
GstElement* video_sink;
GstElement* output;
GstBus* bus;
GstBuffer* buffer;
};
static void HandoffHandler(GstElement* fakesink, GstBuffer* buf,
GstPad* new_pad, gpointer user_data);
static gboolean HandleGstMessage(GstBus* bus, GstMessage* message,
gpointer user_data);
bool CreatePipeline();
void DestroyPipeline();
void Preroll();
void GetZoomMaxMinSize(float& max, float& min);
GstCameraElements gst_;
std::unique_ptr<uint32_t> pixels_;
int32_t width_ = -1;
int32_t height_ = -1;
std::shared_mutex mutex_buffer_;
std::unique_ptr<CameraStreamHandler> stream_handler_;
float max_zoom_level_;
float min_zoom_level_;
float zoom_level_ = 1.0f;
};
#endif // PACKAGES_CAMERA_CAMERA_ELINUX_GST_CAMERA_H_
@@ -0,0 +1,23 @@
#ifndef FLUTTER_PLUGIN_CAMERA_CAMERA_ELINUX_PLUGIN_H_
#define FLUTTER_PLUGIN_CAMERA_CAMERA_ELINUX_PLUGIN_H_
#include <flutter_plugin_registrar.h>
#ifdef FLUTTER_PLUGIN_IMPL
#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default")))
#else
#define FLUTTER_PLUGIN_EXPORT
#endif
#if defined(__cplusplus)
extern "C" {
#endif
FLUTTER_PLUGIN_EXPORT void CameraElinuxPluginRegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar);
#if defined(__cplusplus)
} // extern "C"
#endif
#endif // FLUTTER_PLUGIN_CAMERA_CAMERA_ELINUX_PLUGIN_H_
@@ -0,0 +1,79 @@
// Copyright 2021 Sony Group Corporation. All rights reserved.
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PACKAGES_CAMERA_CAMERA_ELINUX_MESSAGES_AVAILABLE_CAMERAS_MESSAGE_H_
#define PACKAGES_CAMERA_CAMERA_ELINUX_MESSAGES_AVAILABLE_CAMERAS_MESSAGE_H_
#include <flutter/binary_messenger.h>
#include <flutter/encodable_value.h>
#include <string>
#include <variant>
class AvailableCamerasMessage {
public:
AvailableCamerasMessage() = default;
~AvailableCamerasMessage() = default;
// Prevent copying.
AvailableCamerasMessage(AvailableCamerasMessage const&) = default;
AvailableCamerasMessage& operator=(AvailableCamerasMessage const&) = default;
void SetName(const std::string& name) { name_ = name; }
std::string GetName() const { return name_; }
void SetSensorOrientation(const int& sensor_orientation) {
sensor_orientation_ = sensor_orientation;
}
int GetSensorOrientation() const { return sensor_orientation_; }
void SetLensFacing(const std::string& lens_facing) {
lens_facing_ = lens_facing;
}
std::string GetLensFacing() const { return lens_facing_; }
flutter::EncodableValue ToMap() {
flutter::EncodableMap map = {
{flutter::EncodableValue("name"), flutter::EncodableValue(name_)},
{flutter::EncodableValue("sensorOrientation"),
flutter::EncodableValue(sensor_orientation_)},
{flutter::EncodableValue("lensFacing"),
flutter::EncodableValue(lens_facing_)}};
return flutter::EncodableValue(map);
}
static AvailableCamerasMessage FromMap(const flutter::EncodableValue& value) {
AvailableCamerasMessage message;
if (std::holds_alternative<flutter::EncodableMap>(value)) {
auto map = std::get<flutter::EncodableMap>(value);
flutter::EncodableValue& name = map[flutter::EncodableValue("name")];
if (std::holds_alternative<std::string>(name)) {
message.SetName(std::get<std::string>(name));
}
flutter::EncodableValue& sensor_orientation =
map[flutter::EncodableValue("sensorOrientation")];
if (std::holds_alternative<int>(sensor_orientation)) {
message.SetSensorOrientation(std::get<int>(sensor_orientation));
}
flutter::EncodableValue& lens_facing =
map[flutter::EncodableValue("lensFacing")];
if (std::holds_alternative<std::string>(lens_facing)) {
message.SetLensFacing(std::get<std::string>(lens_facing));
}
}
return message;
}
private:
std::string name_;
int sensor_orientation_;
std::string lens_facing_;
};
#endif // PACKAGES_CAMERA_CAMERA_ELINUX_MESSAGES_AVAILABLE_CAMERAS_MESSAGE_H_
@@ -0,0 +1,13 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PACKAGES_CAMERA_CAMERA_ELINUX_MESSAGES_MESSAGES_H_
#define PACKAGES_CAMERA_CAMERA_ELINUX_MESSAGES_MESSAGES_H_
#include "available_cameras_message.h"
#include "orientation_message.h"
#include "texture_message.h"
#include "zoom_level_message.h"
#endif // PACKAGES_CAMERA_CAMERA_ELINUX_MESSAGES_MESSAGES_H_
@@ -0,0 +1,57 @@
// Copyright 2021 Sony Group Corporation. All rights reserved.
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PACKAGES_CAMERA_CAMERA_ELINUX_MESSAGES_ORIENTATION_MESSAGE_H_
#define PACKAGES_CAMERA_CAMERA_ELINUX_MESSAGES_ORIENTATION_MESSAGE_H_
#include <flutter/binary_messenger.h>
#include <flutter/encodable_value.h>
#include <string>
#include <variant>
#include "types/orientation.h"
class OrientationMessage {
public:
OrientationMessage() = default;
~OrientationMessage() = default;
// Prevent copying.
OrientationMessage(OrientationMessage const&) = default;
OrientationMessage& operator=(OrientationMessage const&) = default;
void SetOrientation(DeviceOrientation orientation) {
orientation_ = orientation;
}
DeviceOrientation GetOrientation() const { return orientation_; }
flutter::EncodableValue ToMap() {
flutter::EncodableMap map = {
{flutter::EncodableValue("orientation"),
flutter::EncodableValue(SerializeDeviceOrientation(orientation_))}};
return flutter::EncodableValue(map);
}
static OrientationMessage FromMap(const flutter::EncodableValue& value) {
OrientationMessage message;
if (std::holds_alternative<flutter::EncodableMap>(value)) {
auto map = std::get<flutter::EncodableMap>(value);
flutter::EncodableValue& orientation =
map[flutter::EncodableValue("orientation")];
if (std::holds_alternative<std::string>(orientation)) {
message.SetOrientation(
DeserializeDeviceOrientation(std::get<std::string>(orientation)));
}
}
return message;
}
private:
DeviceOrientation orientation_;
};
#endif // PACKAGES_CAMERA_CAMERA_ELINUX_MESSAGES_ORIENTATION_MESSAGE_H_
@@ -0,0 +1,50 @@
// Copyright 2021 Sony Group Corporation. All rights reserved.
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PACKAGES_CAMERA_CAMERA_ELINUX_MESSAGES_TEXTURE_MESSAGE_H_
#define PACKAGES_CAMERA_CAMERA_ELINUX_MESSAGES_TEXTURE_MESSAGE_H_
#include <flutter/binary_messenger.h>
#include <flutter/encodable_value.h>
class TextureMessage {
public:
TextureMessage() = default;
~TextureMessage() = default;
// Prevent copying.
TextureMessage(TextureMessage const&) = default;
TextureMessage& operator=(TextureMessage const&) = default;
void SetTextureId(int64_t texture_id) { texture_id_ = texture_id; }
int64_t GetTextureId() const { return texture_id_; }
flutter::EncodableValue ToMap() {
flutter::EncodableMap map = {{flutter::EncodableValue("textureId"),
flutter::EncodableValue(texture_id_)}};
return flutter::EncodableValue(map);
}
static TextureMessage FromMap(const flutter::EncodableValue& value) {
TextureMessage message;
if (std::holds_alternative<flutter::EncodableMap>(value)) {
auto map = std::get<flutter::EncodableMap>(value);
flutter::EncodableValue& texture_id =
map[flutter::EncodableValue("textureId")];
if (std::holds_alternative<int32_t>(texture_id) ||
std::holds_alternative<int64_t>(texture_id)) {
message.SetTextureId(texture_id.LongValue());
}
}
return message;
}
private:
int64_t texture_id_ = 0;
};
#endif // PACKAGES_CAMERA_CAMERA_ELINUX_MESSAGES_TEXTURE_MESSAGE_H_
@@ -0,0 +1,49 @@
// Copyright 2021 Sony Group Corporation. All rights reserved.
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PACKAGES_CAMERA_CAMERA_ELINUX_MESSAGES_ZOOM_LEVEL_MESSAGE_H_
#define PACKAGES_CAMERA_CAMERA_ELINUX_MESSAGES_ZOOM_LEVEL_MESSAGE_H_
#include <flutter/binary_messenger.h>
#include <flutter/encodable_value.h>
#include <variant>
class ZoomLevelMessage {
public:
ZoomLevelMessage() = default;
~ZoomLevelMessage() = default;
// Prevent copying.
ZoomLevelMessage(ZoomLevelMessage const&) = default;
ZoomLevelMessage& operator=(ZoomLevelMessage const&) = default;
void SetZoom(double zoom) { zoom_ = zoom; }
double GetZoom() const { return zoom_; }
flutter::EncodableValue ToMap() {
flutter::EncodableMap map = {
{flutter::EncodableValue("zoom"), flutter::EncodableValue(zoom_)}};
return flutter::EncodableValue(map);
}
static ZoomLevelMessage FromMap(const flutter::EncodableValue& value) {
ZoomLevelMessage message;
if (std::holds_alternative<flutter::EncodableMap>(value)) {
auto map = std::get<flutter::EncodableMap>(value);
flutter::EncodableValue& zoom = map[flutter::EncodableValue("zoom")];
if (std::holds_alternative<double>(zoom)) {
message.SetZoom(std::get<double>(zoom));
}
}
return message;
}
private:
double zoom_;
};
#endif // PACKAGES_CAMERA_CAMERA_ELINUX_MESSAGES_ZOOM_LEVEL_MESSAGE_H_
@@ -0,0 +1,33 @@
// Copyright 2021 Sony Group Corporation. All rights reserved.
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "method_channel/method_channel_camera.h"
#include <flutter/standard_method_codec.h>
namespace {
constexpr char kChannelName[] = "flutter.io/cameraPlugin/camera";
constexpr char kChannelMethodInitialized[] = "initialized";
}; // namespace
MethodChannelCamera::MethodChannelCamera(flutter::PluginRegistrar* registrar,
int64_t camera_id) {
std::string channel_name = kChannelName + std::to_string(camera_id);
channel_ = std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
registrar->messenger(), channel_name.c_str(),
&flutter::StandardMethodCodec::GetInstance());
}
void MethodChannelCamera::SendInitializedEvent(
CameraInitializedEvent& message) {
auto value = std::make_unique<flutter::EncodableValue>(message.ToMap());
Send(kChannelMethodInitialized, std::move(value));
}
void MethodChannelCamera::Send(
const std::string& method,
std::unique_ptr<flutter::EncodableValue>&& arguments) {
channel_->InvokeMethod(method, std::move(arguments));
}
@@ -0,0 +1,37 @@
// Copyright 2021 Sony Group Corporation. All rights reserved.
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PACKAGES_CAMERA_CAMERA_ELINUX_CAMERA_METHOD_CHANNEL_METHOD_CHANNEL_CAMERA_H_
#define PACKAGES_CAMERA_CAMERA_ELINUX_CAMERA_METHOD_CHANNEL_METHOD_CHANNEL_CAMERA_H_
#include <flutter/encodable_value.h>
#include <flutter/method_channel.h>
#include <flutter/plugin_registrar.h>
#include <string>
#include "events/camera_initialized_event.h"
enum class CameraEventType {
kError,
kCameraClosing,
kInitialized,
};
class MethodChannelCamera {
public:
MethodChannelCamera(flutter::PluginRegistrar* registrar, int64_t camera_id);
~MethodChannelCamera() = default;
void SendInitializedEvent(CameraInitializedEvent& message);
private:
void Send(const std::string& method,
std::unique_ptr<flutter::EncodableValue>&& arguments);
std::unique_ptr<flutter::MethodChannel<flutter::EncodableValue>> channel_;
};
#endif // PACKAGES_CAMERA_CAMERA_ELINUX_CAMERA_METHOD_CHANNEL_METHOD_CHANNEL_CAMERA_H_
@@ -0,0 +1,37 @@
// Copyright 2021 Sony Group Corporation. All rights reserved.
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "method_channel/method_channel_device.h"
#include <flutter/standard_method_codec.h>
namespace {
constexpr char kChannelName[] = "flutter.io/cameraPlugin/device";
constexpr char kChannelMethodInitialized[] = "initialized";
constexpr char kOrientation[] = "orientation";
}; // namespace
MethodChannelDevice::MethodChannelDevice(flutter::PluginRegistrar* registrar) {
channel_ = std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
registrar->messenger(), kChannelName,
&flutter::StandardMethodCodec::GetInstance());
}
void MethodChannelDevice::SendDeviceOrientationChangeEvent(
const DeviceOrientation& orientation) {
flutter::EncodableMap mp;
mp[flutter::EncodableValue(kOrientation)] =
flutter::EncodableValue(SerializeDeviceOrientation(orientation));
auto value = std::make_unique<flutter::EncodableValue>(mp);
Send(kChannelMethodInitialized, std::move(value));
}
void MethodChannelDevice::Send(
const std::string& method,
std::unique_ptr<flutter::EncodableValue>&& arguments) {
channel_->InvokeMethod(method, std::move(arguments));
}
@@ -0,0 +1,31 @@
// Copyright 2021 Sony Group Corporation. All rights reserved.
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PACKAGES_CAMERA_CAMERA_ELINUX_CAMERA_METHOD_CHANNEL_METHOD_CHANNEL_DEVICE_H_
#define PACKAGES_CAMERA_CAMERA_ELINUX_CAMERA_METHOD_CHANNEL_METHOD_CHANNEL_DEVICE_H_
#include <flutter/encodable_value.h>
#include <flutter/method_channel.h>
#include <flutter/plugin_registrar.h>
#include <string>
#include "types/orientation.h"
class MethodChannelDevice {
public:
MethodChannelDevice(flutter::PluginRegistrar* registrar);
~MethodChannelDevice() = default;
void SendDeviceOrientationChangeEvent(const DeviceOrientation& orientation);
private:
void Send(const std::string& method,
std::unique_ptr<flutter::EncodableValue>&& arguments);
std::unique_ptr<flutter::MethodChannel<flutter::EncodableValue>> channel_;
};
#endif // PACKAGES_CAMERA_CAMERA_ELINUX_CAMERA_METHOD_CHANNEL_METHOD_CHANNEL_DEVICE_H_
@@ -0,0 +1,29 @@
// Copyright 2021 Sony Group Corporation. All rights reserved.
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "types/exposure_mode.h"
std::string SerializeExposureMode(ExposureMode exposure_mode) {
switch (exposure_mode) {
case ExposureMode::kLocked:
return "locked";
case ExposureMode::kAuto:
return "auto";
default:
return "auto";
std::cerr << "Unknown ExposureMode value" << std::endl;
}
}
ExposureMode DeserializeExposureMode(std::string str) {
if (!str.compare("locked")) {
return ExposureMode::kLocked;
}
if (!str.compare("auto")) {
return ExposureMode::kAuto;
}
std::cerr << str.c_str() << " is not a valid ExposureMode value" << std::endl;
return ExposureMode::kAuto;
}
@@ -0,0 +1,22 @@
// Copyright 2021 Sony Group Corporation. All rights reserved.
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PACKAGES_CAMERA_CAMERA_ELINUX_CAMERA_TYPES_EXPOSURE_MODE_H_
#define PACKAGES_CAMERA_CAMERA_ELINUX_CAMERA_TYPES_EXPOSURE_MODE_H_
#include <iostream>
#include <string>
// See:
// flutter/plugins/packages/camera/camera_platform_interface/lib/src/types/exposure_mode.dart
enum class ExposureMode {
kAuto,
kLocked,
};
std::string SerializeExposureMode(ExposureMode exposure_mode);
ExposureMode DeserializeExposureMode(std::string str);
#endif // PACKAGES_CAMERA_CAMERA_ELINUX_CAMERA_TYPES_EXPOSURE_MODE_H_
@@ -0,0 +1,29 @@
// Copyright 2021 Sony Group Corporation. All rights reserved.
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "types/focus_mode.h"
std::string SerializeFocusMode(FocusMode focus_mode) {
switch (focus_mode) {
case FocusMode::kLocked:
return "locked";
case FocusMode::kAuto:
return "auto";
default:
std::cerr << "Unknown FocusMode value" << std::endl;
return "auto";
}
}
FocusMode DeserializeFocusMode(std::string str) {
if (!str.compare("locked")) {
return FocusMode::kLocked;
}
if (!str.compare("auto")) {
return FocusMode::kAuto;
}
std::cerr << str.c_str() << " is not a valid FocusMode value" << std::endl;
return FocusMode::kAuto;
}
+22
View File
@@ -0,0 +1,22 @@
// Copyright 2021 Sony Group Corporation. All rights reserved.
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PACKAGES_CAMERA_CAMERA_ELINUX_CAMERA_TYPES_FOCUS_MODE_H_
#define PACKAGES_CAMERA_CAMERA_ELINUX_CAMERA_TYPES_FOCUS_MODE_H_
#include <iostream>
#include <string>
// See:
// flutter/plugins/packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart
enum class FocusMode {
kAuto,
kLocked,
};
std::string SerializeFocusMode(FocusMode focus_mode);
FocusMode DeserializeFocusMode(std::string str);
#endif // PACKAGES_CAMERA_CAMERA_ELINUX_CAMERA_TYPES_FOCUS_MODE_H_
@@ -0,0 +1,40 @@
// Copyright 2021 Sony Group Corporation. All rights reserved.
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "types/orientation.h"
std::string SerializeDeviceOrientation(DeviceOrientation orientation) {
switch (orientation) {
case DeviceOrientation::kPortraitUp:
return "portraitUp";
case DeviceOrientation::kLandscapeLeft:
return "landscapeLeft";
case DeviceOrientation::kPortraitDown:
return "portraitDown";
case DeviceOrientation::kLandscapeRight:
return "landscapeRight";
default:
std::cerr << "Unknown DeviceOrientation value" << std::endl;
return "landscapeLeft";
}
}
DeviceOrientation DeserializeDeviceOrientation(std::string str) {
if (!str.compare("portraitUp")) {
return DeviceOrientation::kPortraitUp;
}
if (!str.compare("landscapeLeft")) {
return DeviceOrientation::kLandscapeLeft;
}
if (!str.compare("portraitDown")) {
return DeviceOrientation::kPortraitDown;
}
if (!str.compare("landscapeRight")) {
return DeviceOrientation::kLandscapeRight;
}
std::cerr << str.c_str() << " is not a valid DeviceOrientation value"
<< std::endl;
return DeviceOrientation::kLandscapeLeft;
}
@@ -0,0 +1,24 @@
// Copyright 2021 Sony Group Corporation. All rights reserved.
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PACKAGES_CAMERA_CAMERA_ELINUX_CAMERA_TYPES_ORIENTATION_H_
#define PACKAGES_CAMERA_CAMERA_ELINUX_CAMERA_TYPES_ORIENTATION_H_
#include <iostream>
#include <string>
// See: [DeviceOrientation] in
// flutter/packages/flutter/lib/src/services/system_chrome.dart
enum class DeviceOrientation {
kPortraitUp,
kLandscapeLeft,
kPortraitDown,
kLandscapeRight,
};
std::string SerializeDeviceOrientation(DeviceOrientation orientation);
DeviceOrientation DeserializeDeviceOrientation(std::string str);
#endif // PACKAGES_CAMERA_CAMERA_ELINUX_CAMERA_TYPES_ORIENTATION_H_
@@ -0,0 +1 @@
flutter/ephemeral/
@@ -0,0 +1,110 @@
cmake_minimum_required(VERSION 3.15)
project(runner LANGUAGES CXX)
set(BINARY_NAME "camera_example")
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()
# Configure build options.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
# Configure build option to target backend.
if (NOT FLUTTER_TARGET_BACKEND_TYPE)
set(FLUTTER_TARGET_BACKEND_TYPE "wayland" CACHE
STRING "Flutter target backend type" FORCE)
set_property(CACHE FLUTTER_TARGET_BACKEND_TYPE PROPERTY STRINGS
"wayland" "gbm" "eglstream" "x11")
endif()
# Compilation settings that should be applied to most targets.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_17)
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
endfunction()
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
# Flutter library and tool build rules.
add_subdirectory(${FLUTTER_MANAGED_DIR})
# Application build
add_subdirectory("runner")
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# By default, "installing" just makes a relocatable bundle in the build
# directory.
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
# Start with a clean build bundle directory every time.
install(CODE "
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
" COMPONENT Runtime)
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_EMBEDDER_LIBRARY}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
if(PLUGIN_BUNDLED_LIBRARIES)
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()
@@ -0,0 +1,108 @@
cmake_minimum_required(VERSION 3.15)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
# which isn't available in 3.10.
function(list_prepend LIST_NAME PREFIX)
set(NEW_LIST "")
foreach(element ${${LIST_NAME}})
list(APPEND NEW_LIST "${PREFIX}${element}")
endforeach(element)
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
endfunction()
# === Flutter Library ===
# System-level dependencies.
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_engine.so")
if(FLUTTER_TARGET_BACKEND_TYPE MATCHES "gbm")
set(FLUTTER_EMBEDDER_LIBRARY "${EPHEMERAL_DIR}/libflutter_elinux_gbm.so")
elseif(FLUTTER_TARGET_BACKEND_TYPE MATCHES "eglstream")
set(FLUTTER_EMBEDDER_LIBRARY "${EPHEMERAL_DIR}/libflutter_elinux_eglstream.so")
elseif(FLUTTER_TARGET_BACKEND_TYPE MATCHES "x11")
set(FLUTTER_EMBEDDER_LIBRARY "${EPHEMERAL_DIR}/libflutter_elinux_x11.so")
else()
set(FLUTTER_EMBEDDER_LIBRARY "${EPHEMERAL_DIR}/libflutter_elinux_wayland.so")
endif()
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_EMBEDDER_LIBRARY ${FLUTTER_EMBEDDER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/elinux/" PARENT_SCOPE)
set(AOT_LIBRARY "${EPHEMERAL_DIR}/libapp.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"flutter_export.h"
"flutter_plugin_registrar.h"
"flutter_messenger.h"
"flutter_texture_registrar.h"
"flutter_elinux.h"
"flutter_platform_views.h"
)
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
target_link_libraries(flutter INTERFACE "${FLUTTER_EMBEDDER_LIBRARY}")
add_dependencies(flutter flutter_assemble)
# === Wrapper ===
list(APPEND CPP_WRAPPER_SOURCES_CORE
"core_implementations.cc"
"standard_codec.cc"
)
list_prepend(CPP_WRAPPER_SOURCES_CORE "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
"plugin_registrar.cc"
)
list_prepend(CPP_WRAPPER_SOURCES_PLUGIN "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_APP
"flutter_engine.cc"
"flutter_view_controller.cc"
)
list_prepend(CPP_WRAPPER_SOURCES_APP "${WRAPPER_ROOT}/")
# Wrapper sources needed for a plugin.
add_library(flutter_wrapper_plugin STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
)
apply_standard_settings(flutter_wrapper_plugin)
set_target_properties(flutter_wrapper_plugin PROPERTIES
POSITION_INDEPENDENT_CODE ON)
set_target_properties(flutter_wrapper_plugin PROPERTIES
CXX_VISIBILITY_PRESET hidden)
target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
target_include_directories(flutter_wrapper_plugin PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_plugin flutter_assemble)
# Wrapper sources needed for the runner.
add_library(flutter_wrapper_app STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_APP}
)
apply_standard_settings(flutter_wrapper_app)
target_link_libraries(flutter_wrapper_app PUBLIC flutter)
target_include_directories(flutter_wrapper_app PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_app flutter_assemble)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
"${FLUTTER_EMBEDDER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
)
@@ -0,0 +1,13 @@
//
// Generated file. Do not edit.
//
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter/plugin_registry.h>
// Registers Flutter plugins.
void RegisterPlugins(flutter::PluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_
@@ -0,0 +1,17 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
camera_elinux
video_player_elinux
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/elinux plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
@@ -0,0 +1,12 @@
//
// Generated file. Do not edit.
//
// @dart=2.12
import 'package:camera_example/main.dart' as entrypoint;
import 'generated_plugin_registrant.dart';
Future<void> main() async {
registerPlugins();
entrypoint.main();
}
@@ -0,0 +1,23 @@
cmake_minimum_required(VERSION 3.15)
project(runner LANGUAGES CXX)
if(FLUTTER_TARGET_BACKEND_TYPE MATCHES "gbm")
add_definitions(-DFLUTTER_TARGET_BACKEND_GBM)
elseif(FLUTTER_TARGET_BACKEND_TYPE MATCHES "eglstream")
add_definitions(-DFLUTTER_TARGET_BACKEND_EGLSTREAM)
elseif(FLUTTER_TARGET_BACKEND_TYPE MATCHES "x11")
add_definitions(-DFLUTTER_TARGET_BACKEND_X11)
else()
add_definitions(-DFLUTTER_TARGET_BACKEND_WAYLAND)
endif()
add_executable(${BINARY_NAME}
"flutter_window.cc"
"main.cc"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
)
apply_standard_settings(${BINARY_NAME})
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
add_dependencies(${BINARY_NAME} flutter_assemble)
@@ -0,0 +1,367 @@
// Copyright 2021 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.
#ifndef COMMAND_OPTIONS_
#define COMMAND_OPTIONS_
#include <iostream>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <vector>
// todo: Supports other types besides int, string.
namespace commandline {
namespace {
constexpr char kOptionStyleNormal[] = "--";
constexpr char kOptionStyleShort[] = "-";
constexpr char kOptionValueForHelpMessage[] = "=<value>";
} // namespace
class Exception : public std::exception {
public:
Exception(const std::string& msg) : msg_(msg) {}
~Exception() throw() {}
const char* what() const throw() { return msg_.c_str(); }
private:
std::string msg_;
};
class CommandOptions {
public:
CommandOptions() = default;
~CommandOptions() = default;
void AddWithoutValue(const std::string& name, const std::string& short_name,
const std::string& description, bool required) {
Add<std::string, ReaderString>(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,
bool required) {
Add<int, ReaderInt>(name, short_name, description, default_value,
ReaderInt(), required, true);
}
void AddString(const std::string& name, const std::string& short_name,
const std::string& description,
const std::string& default_value, bool required) {
Add<std::string, ReaderString>(name, short_name, description, default_value,
ReaderString(), required, true);
}
template <typename T, typename F>
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;
}
if (lut_short_options_.find(short_name) != lut_short_options_.end()) {
std::cerr << short_name << "is already registered" << std::endl;
return;
}
lut_short_options_[short_name] = name;
options_[name] = std::make_unique<OptionValueReader<T, F>>(
name, short_name, description, default_value, reader, required,
required_value);
// register to show help message.
registration_order_options_.push_back(options_[name].get());
}
bool Exist(const std::string& name) {
auto itr = options_.find(name);
return itr != options_.end() && itr->second->HasValue();
}
template <typename T>
const T& GetValue(const std::string& name) {
auto itr = options_.find(name);
if (itr == options_.end()) {
throw Exception("Not found: " + name);
}
auto* option_value = dynamic_cast<const OptionValue<T>*>(itr->second.get());
if (!option_value) {
throw Exception("Type mismatch: " + name);
}
return option_value->GetValue();
}
bool Parse(int argc, const char* const* argv) {
if (argc < 1) {
errors_.push_back("No options");
return false;
}
command_name_ = argv[0];
for (auto i = 1; i < argc; i++) {
const std::string arg(argv[i]);
// normal options: e.g. --bundle=/data/sample/bundle --fullscreen
if (arg.length() > 2 &&
arg.substr(0, 2).compare(kOptionStyleNormal) == 0) {
const size_t option_value_len = arg.find("=") != std::string::npos
? (arg.length() - arg.find("="))
: 0;
const bool has_value = option_value_len != 0;
std::string option_name =
arg.substr(2, arg.length() - 2 - option_value_len);
if (options_.find(option_name) == options_.end()) {
errors_.push_back("Not found option: " + option_name);
continue;
}
if (!has_value && options_[option_name]->IsRequiredValue()) {
errors_.push_back(option_name + " requres an option value");
continue;
}
if (has_value && !options_[option_name]->IsRequiredValue()) {
errors_.push_back(option_name + " doesn't requres an option value");
continue;
}
if (has_value) {
SetOptionValue(option_name, arg.substr(arg.find("=") + 1));
} else {
SetOption(option_name);
}
}
// short options: e.g. -f /foo/file.txt -h 640 -abc
else if (arg.length() > 1 &&
arg.substr(0, 1).compare(kOptionStyleShort) == 0) {
for (size_t j = 1; j < arg.length(); j++) {
const std::string option_name{argv[i][j]};
if (lut_short_options_.find(option_name) ==
lut_short_options_.end()) {
errors_.push_back("Not found short option: " + option_name);
break;
}
if (j == arg.length() - 1 &&
options_[lut_short_options_[option_name]]->IsRequiredValue()) {
if (i == argc - 1) {
errors_.push_back("Invalid format option: " + option_name);
break;
}
SetOptionValue(lut_short_options_[option_name], argv[++i]);
} else {
SetOption(lut_short_options_[option_name]);
}
}
} else {
errors_.push_back("Invalid format option: " + arg);
}
}
for (size_t i = 0; i < registration_order_options_.size(); i++) {
if (registration_order_options_[i]->IsRequired() &&
!registration_order_options_[i]->HasValue()) {
errors_.push_back(
std::string(registration_order_options_[i]->GetName()) +
" option is mandatory.");
}
}
return errors_.size() == 0;
}
std::string GetError() { return errors_.size() > 0 ? errors_[0] : ""; }
std::vector<std::string>& GetErrors() { return errors_; }
std::string ShowHelp() {
std::ostringstream ostream;
ostream << "Usage: " << command_name_ << " ";
for (size_t i = 0; i < registration_order_options_.size(); i++) {
if (registration_order_options_[i]->IsRequired()) {
ostream << registration_order_options_[i]->GetHelpShortMessage() << " ";
}
}
ostream << std::endl;
ostream << "Global options:" << std::endl;
size_t max_name_len = 0;
for (size_t i = 0; i < registration_order_options_.size(); i++) {
max_name_len = std::max(
max_name_len, registration_order_options_[i]->GetName().length());
}
for (size_t i = 0; i < registration_order_options_.size(); i++) {
if (!registration_order_options_[i]->GetShortName().empty()) {
ostream << kOptionStyleShort
<< registration_order_options_[i]->GetShortName() << ", ";
} else {
ostream << std::string(4, ' ');
}
size_t index_adjust = 0;
constexpr int kSpacerNum = 5;
auto need_value = registration_order_options_[i]->IsRequiredValue();
ostream << kOptionStyleNormal
<< registration_order_options_[i]->GetName();
if (need_value) {
ostream << kOptionValueForHelpMessage;
index_adjust += std::string(kOptionValueForHelpMessage).length();
}
ostream << std::string(
max_name_len + kSpacerNum - index_adjust -
registration_order_options_[i]->GetName().length(),
' ');
ostream << registration_order_options_[i]->GetDescription() << std::endl;
}
return ostream.str();
}
private:
struct ReaderInt {
int operator()(const std::string& value) { return std::stoi(value); }
};
struct ReaderString {
std::string operator()(const std::string& value) { return value; }
};
class Option {
public:
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),
is_required_(required),
is_required_value_(required_value),
value_set_(false){};
virtual ~Option() = default;
const std::string& GetName() const { return name_; };
const std::string& GetShortName() const { return short_name_; };
const std::string& GetDescription() const { return description_; };
const std::string GetHelpShortMessage() const {
std::string message = kOptionStyleNormal + name_;
if (is_required_value_) {
message += kOptionValueForHelpMessage;
}
return message;
}
bool IsRequired() const { return is_required_; };
bool IsRequiredValue() const { return is_required_value_; };
void Set() { value_set_ = true; };
virtual bool SetValue(const std::string& value) = 0;
virtual bool HasValue() const = 0;
protected:
std::string name_;
std::string short_name_;
std::string description_;
bool is_required_;
bool is_required_value_;
bool value_set_;
};
template <typename T>
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)
: Option(name, short_name, description, required, required_value),
default_value_(default_value),
value_(default_value){};
virtual ~OptionValue() = default;
bool SetValue(const std::string& value) {
value_ = Read(value);
value_set_ = true;
return true;
}
bool HasValue() const { return value_set_; }
const T& GetValue() const { return value_; }
protected:
virtual T Read(const std::string& s) = 0;
T default_value_;
T value_;
};
template <typename T, typename F>
class OptionValueReader : public OptionValue<T> {
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<T>(name, short_name, description, default_value, required,
required_value),
reader_(reader) {}
~OptionValueReader() = default;
private:
T Read(const std::string& value) { return reader_(value); }
F reader_;
};
bool SetOption(const std::string& name) {
auto itr = options_.find(name);
if (itr == options_.end()) {
errors_.push_back("Unknown option: " + name);
return false;
}
itr->second->Set();
return true;
}
bool SetOptionValue(const std::string& name, const std::string& value) {
auto itr = options_.find(name);
if (itr == options_.end()) {
errors_.push_back("Unknown option: " + name);
return false;
}
if (!itr->second->SetValue(value)) {
errors_.push_back("Invalid option value: " + name + " = " + value);
return false;
}
return true;
}
std::string command_name_;
std::unordered_map<std::string, std::unique_ptr<Option>> options_;
std::unordered_map<std::string, std::string> lut_short_options_;
std::vector<Option*> registration_order_options_;
std::vector<std::string> errors_;
};
} // namespace commandline
#endif // COMMAND_OPTIONS_
@@ -0,0 +1,103 @@
// Copyright 2021 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.
#ifndef FLUTTER_EMBEDDER_OPTIONS_
#define FLUTTER_EMBEDDER_OPTIONS_
#include <flutter/flutter_view_controller.h>
#include <string>
#include "command_options.h"
class FlutterEmbedderOptions {
public:
FlutterEmbedderOptions() {
options_.AddString("bundle", "b", "Path to Flutter app bundle", "./",
false);
options_.AddWithoutValue("no-cursor", "n", "No mouse cursor/pointer",
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);
#else // FLUTTER_TARGET_BACKEND_WAYLAND
options_.AddWithoutValue("onscreen-keyboard", "k",
"Enable on-screen keyboard", false);
options_.AddWithoutValue("window-decoration", "d",
"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);
#endif
}
~FlutterEmbedderOptions() = default;
bool Parse(int argc, char** argv) {
if (!options_.Parse(argc, argv)) {
std::cerr << options_.GetError() << std::endl;
std::cout << options_.ShowHelp();
return false;
}
bundle_path_ = options_.GetValue<std::string>("bundle");
use_mouse_cursor_ = !options_.Exist("no-cursor");
#if defined(FLUTTER_TARGET_BACKEND_GBM) || \
defined(FLUTTER_TARGET_BACKEND_EGLSTREAM)
use_onscreen_keyboard_ = false;
use_window_decoration_ = false;
window_view_mode_ = flutter::FlutterViewController::ViewMode::kFullscreen;
#elif defined(FLUTTER_TARGET_BACKEND_X11)
use_onscreen_keyboard_ = false;
use_window_decoration_ = false;
window_view_mode_ =
options_.Exist("fullscreen")
? flutter::FlutterViewController::ViewMode::kFullscreen
: flutter::FlutterViewController::ViewMode::kNormal;
window_width_ = options_.GetValue<int>("width");
window_height_ = options_.GetValue<int>("height");
#else // FLUTTER_TARGET_BACKEND_WAYLAND
use_onscreen_keyboard_ = options_.Exist("onscreen-keyboard");
use_window_decoration_ = options_.Exist("window-decoration");
window_view_mode_ =
options_.Exist("fullscreen")
? flutter::FlutterViewController::ViewMode::kFullscreen
: flutter::FlutterViewController::ViewMode::kNormal;
window_width_ = options_.GetValue<int>("width");
window_height_ = options_.GetValue<int>("height");
#endif
return true;
}
std::string BundlePath() const { return bundle_path_; }
bool IsUseMouseCursor() const { return use_mouse_cursor_; }
bool IsUseOnscreenKeyboard() const { return use_onscreen_keyboard_; }
bool IsUseWindowDecoraation() const { return use_window_decoration_; }
flutter::FlutterViewController::ViewMode WindowViewMode() const {
return window_view_mode_;
}
int WindowWidth() const { return window_width_; }
int WindowHeight() const { return window_height_; }
private:
commandline::CommandOptions options_;
std::string bundle_path_;
bool use_mouse_cursor_ = true;
bool use_onscreen_keyboard_ = false;
bool use_window_decoration_ = false;
flutter::FlutterViewController::ViewMode window_view_mode_ =
flutter::FlutterViewController::ViewMode::kNormal;
int window_width_ = 1280;
int window_height_ = 720;
};
#endif // FLUTTER_EMBEDDER_OPTIONS_
@@ -0,0 +1,79 @@
// Copyright 2021 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.
#include "flutter_window.h"
#include <chrono>
#include <cmath>
#include <iostream>
#include <thread>
#include "flutter/generated_plugin_registrant.h"
FlutterWindow::FlutterWindow(
const flutter::FlutterViewController::ViewProperties view_properties,
const flutter::DartProject project)
: view_properties_(view_properties), project_(project) {}
bool FlutterWindow::OnCreate() {
flutter_view_controller_ = std::make_unique<flutter::FlutterViewController>(
view_properties_, project_);
// Ensure that basic setup of the controller was successful.
if (!flutter_view_controller_->engine() ||
!flutter_view_controller_->view()) {
return false;
}
// Register Flutter plugins.
RegisterPlugins(flutter_view_controller_->engine());
return true;
}
void FlutterWindow::OnDestroy() {
if (flutter_view_controller_) {
flutter_view_controller_ = nullptr;
}
}
void FlutterWindow::Run() {
// Main loop.
auto next_flutter_event_time =
std::chrono::steady_clock::time_point::clock::now();
while (flutter_view_controller_->view()->DispatchEvent()) {
// Wait until the next event.
{
auto wait_duration =
std::max(std::chrono::nanoseconds(0),
next_flutter_event_time -
std::chrono::steady_clock::time_point::clock::now());
std::this_thread::sleep_for(
std::chrono::duration_cast<std::chrono::milliseconds>(wait_duration));
}
// Processes any pending events in the Flutter engine, and returns the
// number of nanoseconds until the next scheduled event (or max, if none).
auto wait_duration = flutter_view_controller_->engine()->ProcessMessages();
{
auto next_event_time = std::chrono::steady_clock::time_point::max();
if (wait_duration != std::chrono::nanoseconds::max()) {
next_event_time =
std::min(next_event_time,
std::chrono::steady_clock::time_point::clock::now() +
wait_duration);
} else {
// Wait for the next frame if no events.
auto frame_rate = flutter_view_controller_->view()->GetFrameRate();
next_event_time = std::min(
next_event_time,
std::chrono::steady_clock::time_point::clock::now() +
std::chrono::milliseconds(
static_cast<int>(std::trunc(1000000.0 / frame_rate))));
}
next_flutter_event_time =
std::max(next_flutter_event_time, next_event_time);
}
}
}
@@ -0,0 +1,34 @@
// Copyright 2021 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.
#ifndef FLUTTER_WINDOW_
#define FLUTTER_WINDOW_
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <memory>
class FlutterWindow {
public:
explicit FlutterWindow(
const flutter::FlutterViewController::ViewProperties view_properties,
const flutter::DartProject project);
~FlutterWindow() = default;
// Prevent copying.
FlutterWindow(FlutterWindow const&) = delete;
FlutterWindow& operator=(FlutterWindow const&) = delete;
bool OnCreate();
void OnDestroy();
void Run();
private:
flutter::FlutterViewController::ViewProperties view_properties_;
flutter::DartProject project_;
std::unique_ptr<flutter::FlutterViewController> flutter_view_controller_;
};
#endif // FLUTTER_WINDOW_
@@ -0,0 +1,45 @@
// Copyright 2021 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.
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <iostream>
#include <memory>
#include <string>
#include "flutter_embedder_options.h"
#include "flutter_window.h"
int main(int argc, char** argv) {
FlutterEmbedderOptions options;
if (!options.Parse(argc, argv)) {
return 0;
}
// Creates the Flutter project.
const auto bundle_path = options.BundlePath();
const std::wstring fl_path(bundle_path.begin(), bundle_path.end());
flutter::DartProject project(fl_path);
auto command_line_arguments = std::vector<std::string>();
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
flutter::FlutterViewController::ViewProperties view_properties = {};
view_properties.width = options.WindowWidth();
view_properties.height = options.WindowHeight();
view_properties.view_mode = options.WindowViewMode();
view_properties.use_mouse_cursor = options.IsUseMouseCursor();
view_properties.use_onscreen_keyboard = options.IsUseOnscreenKeyboard();
view_properties.use_window_decoration = options.IsUseWindowDecoraation();
// The Flutter instance hosted by this window.
FlutterWindow window(view_properties, project);
if (!window.OnCreate()) {
return 0;
}
window.Run();
window.OnDestroy();
return 0;
}
@@ -0,0 +1,235 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'dart:ui';
import 'package:camera/camera.dart';
import 'package:flutter/painting.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:path_provider/path_provider.dart';
import 'package:video_player/video_player.dart';
import 'package:integration_test/integration_test.dart';
void main() {
late Directory testDir;
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() async {
final Directory extDir = await getTemporaryDirectory();
testDir = await Directory('${extDir.path}/test').create(recursive: true);
});
tearDownAll(() async {
await testDir.delete(recursive: true);
});
final Map<ResolutionPreset, Size> presetExpectedSizes =
<ResolutionPreset, Size>{
ResolutionPreset.low:
Platform.isAndroid ? const Size(240, 320) : const Size(288, 352),
ResolutionPreset.medium:
Platform.isAndroid ? const Size(480, 720) : const Size(480, 640),
ResolutionPreset.high: const Size(720, 1280),
ResolutionPreset.veryHigh: const Size(1080, 1920),
ResolutionPreset.ultraHigh: const Size(2160, 3840),
// Don't bother checking for max here since it could be anything.
};
/// Verify that [actual] has dimensions that are at least as large as
/// [expectedSize]. Allows for a mismatch in portrait vs landscape. Returns
/// whether the dimensions exactly match.
bool assertExpectedDimensions(Size expectedSize, Size actual) {
expect(actual.shortestSide, lessThanOrEqualTo(expectedSize.shortestSide));
expect(actual.longestSide, lessThanOrEqualTo(expectedSize.longestSide));
return actual.shortestSide == expectedSize.shortestSide &&
actual.longestSide == expectedSize.longestSide;
}
// This tests that the capture is no bigger than the preset, since we have
// automatic code to fall back to smaller sizes when we need to. Returns
// whether the image is exactly the desired resolution.
Future<bool> testCaptureImageResolution(
CameraController controller, ResolutionPreset preset) async {
final Size expectedSize = presetExpectedSizes[preset]!;
print(
'Capturing photo at $preset (${expectedSize.width}x${expectedSize.height}) using camera ${controller.description.name}');
// Take Picture
final file = await controller.takePicture();
// Load picture
final File fileImage = File(file.path);
final Image image = await decodeImageFromList(fileImage.readAsBytesSync());
// Verify image dimensions are as expected
expect(image, isNotNull);
return assertExpectedDimensions(
expectedSize, Size(image.height.toDouble(), image.width.toDouble()));
}
testWidgets('Capture specific image resolutions',
(WidgetTester tester) async {
final List<CameraDescription> cameras = await availableCameras();
if (cameras.isEmpty) {
return;
}
for (CameraDescription cameraDescription in cameras) {
bool previousPresetExactlySupported = true;
for (MapEntry<ResolutionPreset, Size> preset
in presetExpectedSizes.entries) {
final CameraController controller =
CameraController(cameraDescription, preset.key);
await controller.initialize();
final bool presetExactlySupported =
await testCaptureImageResolution(controller, preset.key);
assert(!(!previousPresetExactlySupported && presetExactlySupported),
'The camera took higher resolution pictures at a lower resolution.');
previousPresetExactlySupported = presetExactlySupported;
await controller.dispose();
}
}
}, skip: !Platform.isAndroid);
// This tests that the capture is no bigger than the preset, since we have
// automatic code to fall back to smaller sizes when we need to. Returns
// whether the image is exactly the desired resolution.
Future<bool> testCaptureVideoResolution(
CameraController controller, ResolutionPreset preset) async {
final Size expectedSize = presetExpectedSizes[preset]!;
print(
'Capturing video at $preset (${expectedSize.width}x${expectedSize.height}) using camera ${controller.description.name}');
// Take Video
await controller.startVideoRecording();
sleep(const Duration(milliseconds: 300));
final file = await controller.stopVideoRecording();
// Load video metadata
final File videoFile = File(file.path);
final VideoPlayerController videoController =
VideoPlayerController.file(videoFile);
await videoController.initialize();
final Size video = videoController.value.size;
// Verify image dimensions are as expected
expect(video, isNotNull);
return assertExpectedDimensions(
expectedSize, Size(video.height, video.width));
}
testWidgets('Capture specific video resolutions',
(WidgetTester tester) async {
final List<CameraDescription> cameras = await availableCameras();
if (cameras.isEmpty) {
return;
}
for (CameraDescription cameraDescription in cameras) {
bool previousPresetExactlySupported = true;
for (MapEntry<ResolutionPreset, Size> preset
in presetExpectedSizes.entries) {
final CameraController controller =
CameraController(cameraDescription, preset.key);
await controller.initialize();
await controller.prepareForVideoRecording();
final bool presetExactlySupported =
await testCaptureVideoResolution(controller, preset.key);
assert(!(!previousPresetExactlySupported && presetExactlySupported),
'The camera took higher resolution pictures at a lower resolution.');
previousPresetExactlySupported = presetExactlySupported;
await controller.dispose();
}
}
}, skip: !Platform.isAndroid);
testWidgets('Pause and resume video recording', (WidgetTester tester) async {
final List<CameraDescription> cameras = await availableCameras();
if (cameras.isEmpty) {
return;
}
final CameraController controller = CameraController(
cameras[0],
ResolutionPreset.low,
enableAudio: false,
);
await controller.initialize();
await controller.prepareForVideoRecording();
int startPause;
int timePaused = 0;
await controller.startVideoRecording();
final int recordingStart = DateTime.now().millisecondsSinceEpoch;
sleep(const Duration(milliseconds: 500));
await controller.pauseVideoRecording();
startPause = DateTime.now().millisecondsSinceEpoch;
sleep(const Duration(milliseconds: 500));
await controller.resumeVideoRecording();
timePaused += DateTime.now().millisecondsSinceEpoch - startPause;
sleep(const Duration(milliseconds: 500));
await controller.pauseVideoRecording();
startPause = DateTime.now().millisecondsSinceEpoch;
sleep(const Duration(milliseconds: 500));
await controller.resumeVideoRecording();
timePaused += DateTime.now().millisecondsSinceEpoch - startPause;
sleep(const Duration(milliseconds: 500));
final file = await controller.stopVideoRecording();
final int recordingTime =
DateTime.now().millisecondsSinceEpoch - recordingStart;
final File videoFile = File(file.path);
final VideoPlayerController videoController = VideoPlayerController.file(
videoFile,
);
await videoController.initialize();
final int duration = videoController.value.duration.inMilliseconds;
await videoController.dispose();
expect(duration, lessThan(recordingTime - timePaused));
}, skip: !Platform.isAndroid);
testWidgets(
'Android image streaming',
(WidgetTester tester) async {
final List<CameraDescription> cameras = await availableCameras();
if (cameras.isEmpty) {
return;
}
final CameraController controller = CameraController(
cameras[0],
ResolutionPreset.low,
enableAudio: false,
);
await controller.initialize();
bool _isDetecting = false;
await controller.startImageStream((CameraImage image) {
if (_isDetecting) return;
_isDetecting = true;
expectLater(image, isNotNull).whenComplete(() => _isDetecting = false);
});
expect(controller.value.isStreamingImages, true);
sleep(const Duration(milliseconds: 500));
await controller.stopImageStream();
await controller.dispose();
},
skip: !Platform.isAndroid,
);
}
+971
View File
@@ -0,0 +1,971 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// 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/material.dart';
import 'package:video_player/video_player.dart';
class CameraExampleHome extends StatefulWidget {
@override
_CameraExampleHomeState createState() {
return _CameraExampleHomeState();
}
}
/// Returns a suitable camera icon for [direction].
IconData getCameraLensIcon(CameraLensDirection direction) {
switch (direction) {
case CameraLensDirection.back:
return Icons.camera_rear;
case CameraLensDirection.front:
return Icons.camera_front;
case CameraLensDirection.external:
return Icons.camera;
default:
throw ArgumentError('Unknown lens direction');
}
}
void logError(String code, String? message) {
if (message != null) {
print('Error: $code\nError Message: $message');
} else {
print('Error: $code');
}
}
class _CameraExampleHomeState extends State<CameraExampleHome>
with WidgetsBindingObserver, TickerProviderStateMixin {
CameraController? controller;
XFile? imageFile;
XFile? videoFile;
VideoPlayerController? videoController;
VoidCallback? videoPlayerListener;
bool enableAudio = true;
double _minAvailableExposureOffset = 0.0;
double _maxAvailableExposureOffset = 0.0;
double _currentExposureOffset = 0.0;
late AnimationController _flashModeControlRowAnimationController;
late Animation<double> _flashModeControlRowAnimation;
late AnimationController _exposureModeControlRowAnimationController;
late Animation<double> _exposureModeControlRowAnimation;
late AnimationController _focusModeControlRowAnimationController;
late Animation<double> _focusModeControlRowAnimation;
double _minAvailableZoom = 1.0;
double _maxAvailableZoom = 1.0;
double _currentScale = 1.0;
double _baseScale = 1.0;
// Counting pointers (number of user fingers on screen)
int _pointers = 0;
@override
void initState() {
super.initState();
_ambiguate(WidgetsBinding.instance)?.addObserver(this);
_flashModeControlRowAnimationController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
_flashModeControlRowAnimation = CurvedAnimation(
parent: _flashModeControlRowAnimationController,
curve: Curves.easeInCubic,
);
_exposureModeControlRowAnimationController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
_exposureModeControlRowAnimation = CurvedAnimation(
parent: _exposureModeControlRowAnimationController,
curve: Curves.easeInCubic,
);
_focusModeControlRowAnimationController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
_focusModeControlRowAnimation = CurvedAnimation(
parent: _focusModeControlRowAnimationController,
curve: Curves.easeInCubic,
);
}
@override
void dispose() {
_ambiguate(WidgetsBinding.instance)?.removeObserver(this);
_flashModeControlRowAnimationController.dispose();
_exposureModeControlRowAnimationController.dispose();
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
final CameraController? cameraController = controller;
// App state changed before we got the chance to initialize.
if (cameraController == null || !cameraController.value.isInitialized) {
return;
}
if (state == AppLifecycleState.inactive) {
cameraController.dispose();
} else if (state == AppLifecycleState.resumed) {
onNewCameraSelected(cameraController.description);
}
}
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: const Text('Camera example'),
),
body: Column(
children: <Widget>[
Expanded(
child: Container(
child: Padding(
padding: const EdgeInsets.all(1.0),
child: Center(
child: _cameraPreviewWidget(),
),
),
decoration: BoxDecoration(
color: Colors.black,
border: Border.all(
color:
controller != null && controller!.value.isRecordingVideo
? Colors.redAccent
: Colors.grey,
width: 3.0,
),
),
),
),
_captureControlRowWidget(),
_modeControlRowWidget(),
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
_cameraTogglesRowWidget(),
_thumbnailWidget(),
],
),
),
],
),
);
}
/// Display the preview from the camera (or a message if the preview is not available).
Widget _cameraPreviewWidget() {
final CameraController? cameraController = controller;
if (cameraController == null || !cameraController.value.isInitialized) {
return const Text(
'Tap a camera',
style: TextStyle(
color: Colors.white,
fontSize: 24.0,
fontWeight: FontWeight.w900,
),
);
} else {
return Listener(
onPointerDown: (_) => _pointers++,
onPointerUp: (_) => _pointers--,
child: CameraPreview(
controller!,
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onScaleStart: _handleScaleStart,
onScaleUpdate: _handleScaleUpdate,
onTapDown: (details) => onViewFinderTap(details, constraints),
);
}),
),
);
}
}
void _handleScaleStart(ScaleStartDetails details) {
_baseScale = _currentScale;
}
Future<void> _handleScaleUpdate(ScaleUpdateDetails details) async {
// When there are not exactly two fingers on screen don't scale
if (controller == null || _pointers != 2) {
return;
}
_currentScale = (_baseScale * details.scale)
.clamp(_minAvailableZoom, _maxAvailableZoom);
await controller!.setZoomLevel(_currentScale);
}
/// Display the thumbnail of the captured image or video.
Widget _thumbnailWidget() {
final VideoPlayerController? localVideoController = videoController;
return Expanded(
child: Align(
alignment: Alignment.centerRight,
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
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,
),
],
),
),
);
}
/// Display a bar with buttons to change the flash and exposure modes
Widget _modeControlRowWidget() {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
IconButton(
icon: 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,
),
IconButton(
icon: Icon(enableAudio ? Icons.volume_up : Icons.volume_mute),
color: Colors.blue,
onPressed: controller != null ? onAudioModeButtonPressed : null,
),
IconButton(
icon: Icon(controller?.value.isCaptureOrientationLocked ?? false
? Icons.screen_lock_rotation
: Icons.screen_rotation),
color: Colors.blue,
onPressed: controller != null
? onCaptureOrientationLockButtonPressed
: null,
),
],
),
_flashModeControlRowWidget(),
_exposureModeControlRowWidget(),
_focusModeControlRowWidget(),
],
);
}
Widget _flashModeControlRowWidget() {
return SizeTransition(
sizeFactor: _flashModeControlRowAnimation,
child: ClipRect(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: [
IconButton(
icon: Icon(Icons.flash_off),
color: controller?.value.flashMode == FlashMode.off
? Colors.orange
: Colors.blue,
onPressed: controller != null
? () => onSetFlashModeButtonPressed(FlashMode.off)
: null,
),
IconButton(
icon: Icon(Icons.flash_auto),
color: controller?.value.flashMode == FlashMode.auto
? Colors.orange
: Colors.blue,
onPressed: controller != null
? () => onSetFlashModeButtonPressed(FlashMode.auto)
: null,
),
IconButton(
icon: Icon(Icons.flash_on),
color: controller?.value.flashMode == FlashMode.always
? Colors.orange
: Colors.blue,
onPressed: controller != null
? () => onSetFlashModeButtonPressed(FlashMode.always)
: null,
),
IconButton(
icon: Icon(Icons.highlight),
color: controller?.value.flashMode == FlashMode.torch
? Colors.orange
: Colors.blue,
onPressed: controller != null
? () => onSetFlashModeButtonPressed(FlashMode.torch)
: null,
),
],
),
),
);
}
Widget _exposureModeControlRowWidget() {
final ButtonStyle styleAuto = TextButton.styleFrom(
primary: controller?.value.exposureMode == ExposureMode.auto
? Colors.orange
: Colors.blue,
);
final ButtonStyle styleLocked = TextButton.styleFrom(
primary: controller?.value.exposureMode == ExposureMode.locked
? Colors.orange
: Colors.blue,
);
return SizeTransition(
sizeFactor: _exposureModeControlRowAnimation,
child: ClipRect(
child: Container(
color: Colors.grey.shade50,
child: Column(
children: [
Center(
child: Text("Exposure Mode"),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: [
TextButton(
child: Text('AUTO'),
style: styleAuto,
onPressed: controller != null
? () =>
onSetExposureModeButtonPressed(ExposureMode.auto)
: null,
onLongPress: () {
if (controller != null) {
controller!.setExposurePoint(null);
showInSnackBar('Resetting exposure point');
}
},
),
TextButton(
child: Text('LOCKED'),
style: styleLocked,
onPressed: controller != null
? () =>
onSetExposureModeButtonPressed(ExposureMode.locked)
: null,
),
],
),
Center(
child: Text("Exposure Offset"),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: [
Text(_minAvailableExposureOffset.toString()),
Slider(
value: _currentExposureOffset,
min: _minAvailableExposureOffset,
max: _maxAvailableExposureOffset,
label: _currentExposureOffset.toString(),
onChanged: _minAvailableExposureOffset ==
_maxAvailableExposureOffset
? null
: setExposureOffset,
),
Text(_maxAvailableExposureOffset.toString()),
],
),
],
),
),
),
);
}
Widget _focusModeControlRowWidget() {
final ButtonStyle styleAuto = TextButton.styleFrom(
primary: controller?.value.focusMode == FocusMode.auto
? Colors.orange
: Colors.blue,
);
final ButtonStyle styleLocked = TextButton.styleFrom(
primary: controller?.value.focusMode == FocusMode.locked
? Colors.orange
: Colors.blue,
);
return SizeTransition(
sizeFactor: _focusModeControlRowAnimation,
child: ClipRect(
child: Container(
color: Colors.grey.shade50,
child: Column(
children: [
Center(
child: Text("Focus Mode"),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: [
TextButton(
child: Text('AUTO'),
style: styleAuto,
onPressed: controller != null
? () => onSetFocusModeButtonPressed(FocusMode.auto)
: null,
onLongPress: () {
if (controller != null) controller!.setFocusPoint(null);
showInSnackBar('Resetting focus point');
},
),
TextButton(
child: Text('LOCKED'),
style: styleLocked,
onPressed: controller != null
? () => onSetFocusModeButtonPressed(FocusMode.locked)
: null,
),
],
),
],
),
),
),
);
}
/// Display the control bar with buttons to take pictures and record videos.
Widget _captureControlRowWidget() {
final CameraController? cameraController = controller;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
IconButton(
icon: const Icon(Icons.camera_alt),
color: Colors.blue,
onPressed: cameraController != null &&
cameraController.value.isInitialized &&
!cameraController.value.isRecordingVideo
? onTakePictureButtonPressed
: null,
),
IconButton(
icon: const Icon(Icons.videocam),
color: Colors.blue,
onPressed: cameraController != null &&
cameraController.value.isInitialized &&
!cameraController.value.isRecordingVideo
? onVideoRecordButtonPressed
: null,
),
IconButton(
icon: cameraController != null &&
cameraController.value.isRecordingPaused
? Icon(Icons.play_arrow)
: Icon(Icons.pause),
color: Colors.blue,
onPressed: cameraController != null &&
cameraController.value.isInitialized &&
cameraController.value.isRecordingVideo
? (cameraController.value.isRecordingPaused)
? onResumeButtonPressed
: onPauseButtonPressed
: null,
),
IconButton(
icon: const Icon(Icons.stop),
color: Colors.red,
onPressed: cameraController != null &&
cameraController.value.isInitialized &&
cameraController.value.isRecordingVideo
? onStopButtonPressed
: null,
)
],
);
}
/// Display a row of toggle to select the camera (or a message if no camera is available).
Widget _cameraTogglesRowWidget() {
final List<Widget> toggles = <Widget>[];
final onChanged = (CameraDescription? description) {
if (description == null) {
return;
}
onNewCameraSelected(description);
};
if (cameras.isEmpty) {
return const Text('No camera found');
} else {
for (CameraDescription cameraDescription in cameras) {
toggles.add(
SizedBox(
width: 90.0,
child: RadioListTile<CameraDescription>(
title: Icon(getCameraLensIcon(cameraDescription.lensDirection)),
groupValue: controller?.description,
value: cameraDescription,
onChanged:
controller != null && controller!.value.isRecordingVideo
? null
: onChanged,
),
),
);
}
}
return Row(children: toggles);
}
String timestamp() => DateTime.now().millisecondsSinceEpoch.toString();
void showInSnackBar(String message) {
// ignore: deprecated_member_use
_scaffoldKey.currentState?.showSnackBar(SnackBar(content: Text(message)));
}
void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) {
if (controller == null) {
return;
}
final CameraController cameraController = controller!;
final offset = Offset(
details.localPosition.dx / constraints.maxWidth,
details.localPosition.dy / constraints.maxHeight,
);
cameraController.setExposurePoint(offset);
cameraController.setFocusPoint(offset);
}
void onNewCameraSelected(CameraDescription cameraDescription) async {
if (controller != null) {
await controller!.dispose();
}
final CameraController cameraController = CameraController(
cameraDescription,
ResolutionPreset.medium,
enableAudio: enableAudio,
imageFormatGroup: ImageFormatGroup.jpeg,
);
controller = cameraController;
// If the controller is updated then update the UI.
cameraController.addListener(() {
if (mounted) setState(() {});
if (cameraController.value.hasError) {
showInSnackBar(
'Camera error ${cameraController.value.errorDescription}');
}
});
try {
await cameraController.initialize();
await Future.wait([
cameraController
.getMinExposureOffset()
.then((value) => _minAvailableExposureOffset = value),
cameraController
.getMaxExposureOffset()
.then((value) => _maxAvailableExposureOffset = value),
cameraController
.getMaxZoomLevel()
.then((value) => _maxAvailableZoom = value),
cameraController
.getMinZoomLevel()
.then((value) => _minAvailableZoom = value),
]);
} on CameraException catch (e) {
_showCameraException(e);
}
if (mounted) {
setState(() {});
}
}
void onTakePictureButtonPressed() {
takePicture().then((XFile? file) {
if (mounted) {
setState(() {
imageFile = file;
videoController?.dispose();
videoController = null;
});
if (file != null) showInSnackBar('Picture saved to ${file.path}');
}
});
}
void onFlashModeButtonPressed() {
if (_flashModeControlRowAnimationController.value == 1) {
_flashModeControlRowAnimationController.reverse();
} else {
_flashModeControlRowAnimationController.forward();
_exposureModeControlRowAnimationController.reverse();
_focusModeControlRowAnimationController.reverse();
}
}
void onExposureModeButtonPressed() {
if (_exposureModeControlRowAnimationController.value == 1) {
_exposureModeControlRowAnimationController.reverse();
} else {
_exposureModeControlRowAnimationController.forward();
_flashModeControlRowAnimationController.reverse();
_focusModeControlRowAnimationController.reverse();
}
}
void onFocusModeButtonPressed() {
if (_focusModeControlRowAnimationController.value == 1) {
_focusModeControlRowAnimationController.reverse();
} else {
_focusModeControlRowAnimationController.forward();
_flashModeControlRowAnimationController.reverse();
_exposureModeControlRowAnimationController.reverse();
}
}
void onAudioModeButtonPressed() {
enableAudio = !enableAudio;
if (controller != null) {
onNewCameraSelected(controller!.description);
}
}
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}');
}
}
}
void onSetFlashModeButtonPressed(FlashMode mode) {
setFlashMode(mode).then((_) {
if (mounted) setState(() {});
showInSnackBar('Flash mode set to ${mode.toString().split('.').last}');
});
}
void onSetExposureModeButtonPressed(ExposureMode mode) {
setExposureMode(mode).then((_) {
if (mounted) setState(() {});
showInSnackBar('Exposure mode set to ${mode.toString().split('.').last}');
});
}
void onSetFocusModeButtonPressed(FocusMode mode) {
setFocusMode(mode).then((_) {
if (mounted) setState(() {});
showInSnackBar('Focus mode set to ${mode.toString().split('.').last}');
});
}
void onVideoRecordButtonPressed() {
startVideoRecording().then((_) {
if (mounted) setState(() {});
});
}
void onStopButtonPressed() {
stopVideoRecording().then((file) {
if (mounted) setState(() {});
if (file != null) {
showInSnackBar('Video recorded to ${file.path}');
videoFile = file;
_startVideoPlayer();
}
});
}
void onPauseButtonPressed() {
pauseVideoRecording().then((_) {
if (mounted) setState(() {});
showInSnackBar('Video recording paused');
});
}
void onResumeButtonPressed() {
resumeVideoRecording().then((_) {
if (mounted) setState(() {});
showInSnackBar('Video recording resumed');
});
}
Future<void> startVideoRecording() async {
final CameraController? cameraController = controller;
if (cameraController == null || !cameraController.value.isInitialized) {
showInSnackBar('Error: select a camera first.');
return;
}
if (cameraController.value.isRecordingVideo) {
// A recording is already started, do nothing.
return;
}
try {
await cameraController.startVideoRecording();
} on CameraException catch (e) {
_showCameraException(e);
return;
}
}
Future<XFile?> stopVideoRecording() async {
final CameraController? cameraController = controller;
if (cameraController == null || !cameraController.value.isRecordingVideo) {
return null;
}
try {
return cameraController.stopVideoRecording();
} on CameraException catch (e) {
_showCameraException(e);
return null;
}
}
Future<void> pauseVideoRecording() async {
final CameraController? cameraController = controller;
if (cameraController == null || !cameraController.value.isRecordingVideo) {
return null;
}
try {
await cameraController.pauseVideoRecording();
} on CameraException catch (e) {
_showCameraException(e);
rethrow;
}
}
Future<void> resumeVideoRecording() async {
final CameraController? cameraController = controller;
if (cameraController == null || !cameraController.value.isRecordingVideo) {
return null;
}
try {
await cameraController.resumeVideoRecording();
} on CameraException catch (e) {
_showCameraException(e);
rethrow;
}
}
Future<void> setFlashMode(FlashMode mode) async {
if (controller == null) {
return;
}
try {
await controller!.setFlashMode(mode);
} on CameraException catch (e) {
_showCameraException(e);
rethrow;
}
}
Future<void> setExposureMode(ExposureMode mode) async {
if (controller == null) {
return;
}
try {
await controller!.setExposureMode(mode);
} on CameraException catch (e) {
_showCameraException(e);
rethrow;
}
}
Future<void> setExposureOffset(double offset) async {
if (controller == null) {
return;
}
setState(() {
_currentExposureOffset = offset;
});
try {
offset = await controller!.setExposureOffset(offset);
} on CameraException catch (e) {
_showCameraException(e);
rethrow;
}
}
Future<void> setFocusMode(FocusMode mode) async {
if (controller == null) {
return;
}
try {
await controller!.setFocusMode(mode);
} on CameraException catch (e) {
_showCameraException(e);
rethrow;
}
}
Future<void> _startVideoPlayer() async {
if (videoFile == null) {
return;
}
final VideoPlayerController vController =
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(() {});
videoController!.removeListener(videoPlayerListener!);
}
};
vController.addListener(videoPlayerListener!);
await vController.setLooping(true);
await vController.initialize();
await videoController?.dispose();
if (mounted) {
setState(() {
imageFile = null;
videoController = vController;
});
}
await vController.play();
}
Future<XFile?> takePicture() async {
final CameraController? cameraController = controller;
if (cameraController == null || !cameraController.value.isInitialized) {
showInSnackBar('Error: select a camera first.');
return null;
}
if (cameraController.value.isTakingPicture) {
// A capture is already pending, do nothing.
return null;
}
try {
XFile file = await cameraController.takePicture();
return file;
} on CameraException catch (e) {
_showCameraException(e);
return null;
}
}
void _showCameraException(CameraException e) {
logError(e.code, e.description);
showInSnackBar('Error: ${e.code}\n${e.description}');
}
}
class CameraApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
scrollBehavior: MyCustomScrollBehavior(),
home: CameraExampleHome(),
);
}
}
class MyCustomScrollBehavior extends MaterialScrollBehavior {
// Override behavior methods and getters like dragDevices
@override
Set<PointerDeviceKind> get dragDevices => {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
};
}
List<CameraDescription> cameras = [];
Future<void> main() async {
// Fetch the available cameras before initializing the app.
try {
WidgetsFlutterBinding.ensureInitialized();
cameras = await availableCameras();
} on CameraException catch (e) {
logError(e.code, e.description);
}
runApp(CameraApp());
}
/// This allows a value of type T or T? to be treated as a value of type T?.
///
/// We use this so that APIs that have become non-nullable can still be used
/// with `!` and `?` on the stable branch.
// TODO(ianh): Remove this once we roll stable in late 2021.
T? _ambiguate<T>(T? value) => value;
+32
View File
@@ -0,0 +1,32 @@
name: camera_example
description: Demonstrates how to use the camera plugin.
publish_to: none
environment:
sdk: ">=2.12.0 <3.0.0"
flutter: ">=1.22.0"
dependencies:
camera: ^0.8.1+7
camera_elinux:
path: ../
flutter:
sdk: flutter
path_provider: ^2.0.0
path_provider_elinux:
path: ../../path_provider
video_player: ^2.1.4
video_player_elinux:
path: ../../video_player
dev_dependencies:
flutter_test:
sdk: flutter
flutter_driver:
sdk: flutter
integration_test:
sdk: flutter
pedantic: ^1.10.0
flutter:
uses-material-design: true
@@ -0,0 +1,64 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter_driver/flutter_driver.dart';
const String _examplePackage = 'io.flutter.plugins.cameraexample';
Future<void> main() async {
if (!(Platform.isLinux || Platform.isMacOS)) {
print('This test must be run on a POSIX host. Skipping...');
exit(0);
}
final bool adbExists =
Process.runSync('which', <String>['adb']).exitCode == 0;
if (!adbExists) {
print('This test needs ADB to exist on the \$PATH. Skipping...');
exit(0);
}
print('Granting camera permissions...');
Process.runSync('adb', <String>[
'shell',
'pm',
'grant',
_examplePackage,
'android.permission.CAMERA'
]);
Process.runSync('adb', <String>[
'shell',
'pm',
'grant',
_examplePackage,
'android.permission.RECORD_AUDIO'
]);
print('Starting test.');
final FlutterDriver driver = await FlutterDriver.connect();
final String data = await driver.requestData(
null,
timeout: const Duration(minutes: 1),
);
await driver.close();
print('Test finished. Revoking camera permissions...');
Process.runSync('adb', <String>[
'shell',
'pm',
'revoke',
_examplePackage,
'android.permission.CAMERA'
]);
Process.runSync('adb', <String>[
'shell',
'pm',
'revoke',
_examplePackage,
'android.permission.RECORD_AUDIO'
]);
final Map<String, dynamic> result = jsonDecode(data);
exit(result['result'] == 'true' ? 0 : 1);
}
+21
View File
@@ -0,0 +1,21 @@
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.1.0
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"
dependencies:
flutter:
sdk: flutter
flutter:
plugin:
platforms:
elinux:
pluginClass: CameraElinuxPlugin