[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:
committed by
GitHub
parent
4c37b98e79
commit
999fdad59c
@@ -0,0 +1,7 @@
|
||||
.DS_Store
|
||||
.dart_tool/
|
||||
|
||||
.packages
|
||||
.pub/
|
||||
|
||||
build/
|
||||
@@ -0,0 +1,2 @@
|
||||
## 0.1.0
|
||||
* First draft version.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
```
|
||||
@@ -0,0 +1 @@
|
||||
flutter/
|
||||
@@ -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_
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user