flutter camera パッケージの Example を読んでいく
途中で同じことをしている人を見つけました。そちらを見てください。
これ。
今後更新されるかもしれないので、現状のexampleソースコードをコピーしておく。
バージョンは0.10.5+9。早くバージョン1.0.0に到達して欲しい。
※ Fluter 公式docs にもあるが、こちらは最低限って感じです。今回は見ません。
今回は android、iosをスコープに作業する。
はじめにandroid実機で動作確認し、その後iosでも動かす。
パッケージがないって言われるのでインストール。video_playerも入れるのね。
flutter pub add camera
flutter pub add video_player
サンプルを android 実機(API33)で実行。早速エラー。
camera の README にあるように、android/app/build.gradle
の minSdkVersion
を 21 以上にしないといけない。
表示されるエラー
Launching lib/main.dart on Pixel 7a in debug mode...
/Users/apple/ghq/github.com/ken-ty/flutter_camera_sample/android/app/src/debug/AndroidManifest.xml Error:
uses-sdk:minSdkVersion 19 cannot be smaller than version 21 declared in library [:camera_android] /Users/apple/ghq/github.com/ken-ty/flutter_camera_sample/build/camera_android/intermediates/merged_manifest/debug/AndroidManifest.xml as the library might be using APIs not available in 19
Suggestion: use a compatible library with a minSdk of at most 19,
or increase this project's minSdk version to at least 21,
or use tools:overrideLibrary="io.flutter.plugins.camera" to force usage (may lead to runtime failures)
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:processDebugMainManifest'.
> Manifest merger failed : uses-sdk:minSdkVersion 19 cannot be smaller than version 21 declared in library [:camera_android] /Users/apple/ghq/github.com/ken-ty/flutter_camera_sample/build/camera_android/intermediates/merged_manifest/debug/AndroidManifest.xml as the library might be using APIs not available in 19
Suggestion: use a compatible library with a minSdk of at most 19,
or increase this project's minSdk version to at least 21,
or use tools:overrideLibrary="io.flutter.plugins.camera" to force usage (may lead to runtime failures)
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
* Get more help at https://help.gradle.org
BUILD FAILED in 12s
minSdkVersionを21に変更して再度ビルド。動いた。
機能めちゃ充実してる。
1056行あるmain.dartを見ていく。
ざっくり構成として、トップ階層にいる奴らを見ていく。
要素名 | 種類 | 説明 |
---|---|---|
main | メソッド | 処理の開始地点。_camerasに有効なカメラを格納している。CameraAppを立ち上げている。 |
_cameras | 変数 | 利用できるカメラ群。 |
CameraApp | クラス | よくある MyApp と同じ構成。homeに CameraExampleHome を指定している。 |
CameraExampleHome | クラス | カメラ画面。シンプルなStatefulWidget。 |
_CameraExampleHomeState | クラス | CameraExampleHome の Stete。主な実装はここ。 |
getCameraLensIcon | メソッド | 引数からカメラの向きに応じたアイコンを返す |
_logError | メソッド | シンプルなログ出力メソッド |
main で早々に _cameras = await availableCameras();
が実行されているのが気になった。
それ以外は、主な実装である _CameraExampleHomeState を読んでいけば良さそう。
_CameraExampleHomeState には44メソッド定義されている。平均21行なので読みやすい。
メソッドを見る前に、 この class に with している Mixin を見ていく。
class _CameraExampleHomeState extends State<CameraExampleHome>
with WidgetsBindingObserver, TickerProviderStateMixin {...}
Mixin | 説明 |
---|---|
WidgetsBindingObserver | ライフサイクルを検知できたりする |
TickerProviderStateMixin | 複数アニメーションの為のcreateTickerメソッドを提供する |
WidgetsBindingObserver を mixin してライフサイクル変更時に処理を行う。
変更を検知する為に、initState で observer に自身を add している。dispose で remove することも忘れずに。
didChangeAppLifecycleState(state) を override して、 ライフサイクルが変更した時の動作を定義できる。
@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) {
// 再開したらコントローラを初期化(disposeされたらdescriptionにアクセスできないのでは...?)
_initializeCameraController(cameraController.description);
}
}
_initializeCameraController(cameraDescription) を見てみる。
エラー列挙されていて、今後の実装の参考に助かる。
cameracontroller が ValueNotifier<CameraValue> を継承しており、ValueNotifier の仕様ちゃんと理解するべきだと感じた。
camera_controller.dart も読み込みたい。
Future<void> _initializeCameraController(
CameraDescription cameraDescription) async {
final CameraController cameraController = CameraController(
cameraDescription,
kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium,
enableAudio: enableAudio,
imageFormatGroup: ImageFormatGroup.jpeg, // 指定できるんか、知らんかった。
);
controller = cameraController;
// カメラコントローラが変更されたら発火する
// If the controller is updated then update the UI.
cameraController.addListener(() {
// mounted == false の時に setStateを呼び出すとエラーになる為
// https://api.flutter.dev/flutter/widgets/State/mounted.html
if (mounted) {
// UI 更新
setState(() {});
}
// エラーならスナックバーを表示
if (cameraController.value.hasError) {
showInSnackBar(
'Camera error ${cameraController.value.errorDescription}');
}
});
try {
await cameraController.initialize();
// 非同期実行。 [] の処理が全部終わるまで待機。
await Future.wait(<Future<Object?>>[
// The exposure mode is currently not supported on the web.
...!kIsWeb
? <Future<Object?>>[
// 露出オフセットのmin-maxを更新
cameraController.getMinExposureOffset().then(
(double value) => _minAvailableExposureOffset = value),
cameraController
.getMaxExposureOffset()
.then((double value) => _maxAvailableExposureOffset = value)
]
: <Future<Object?>>[],
// zoomのmin-maxを更新
cameraController
.getMaxZoomLevel()
.then((double value) => _maxAvailableZoom = value),
// zoomのmin-maxを更新
.getMinZoomLevel()
.then((double value) => _minAvailableZoom = value),
]);
} on CameraException catch (e) {
switch (e.code) {
case 'CameraAccessDenied':
showInSnackBar('You have denied camera access.');
case 'CameraAccessDeniedWithoutPrompt':
// iOS only
showInSnackBar('Please go to Settings app to enable camera access.');
case 'CameraAccessRestricted':
// iOS only
showInSnackBar('Camera access is restricted.');
case 'AudioAccessDenied':
showInSnackBar('You have denied audio access.');
case 'AudioAccessDeniedWithoutPrompt':
// iOS only
showInSnackBar('Please go to Settings app to enable audio access.');
case 'AudioAccessRestricted':
// iOS only
showInSnackBar('Audio access is restricted.');
default:
_showCameraException(e);
break;
}
}
if (mounted) {
setState(() {}); // 初期化が終わったらUI更新する
}
}
同じような記事を見つけたので、以降は個人的なメモにする。
_cameraPreviewWidget() をみると、CameraPreview(..., child: ... ) と、 プレビューの child に Widget を追加している。そんなことできたのか...!
@override void didChangeAppLifecycleState(AppLifecycleState state) { ... // 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) { // 再開したらコントローラを初期化(disposeされたらdescriptionにアクセスできないのでは...?) _initializeCameraController(cameraController.description); } }
再開したらコントローラを初期化(disposeされたらdescriptionにアクセスできないのでは...?)について、disepose をちゃんと理解する。
dispose() 実行前後で cameraController を print してみると、同じ結果が得られた。
CameraController.dispose() の実装を見てみると、カメラのリソースの開放をしているだけで、controllerのインスタンス自体が破棄されるわけではなかった。つまり、セットしている discription にはアクセスできるということになる。
/// Releases the resources of this camera.
@override
Future<void> dispose() async {
if (_isDisposed) {
return;
}
_unawaited(_deviceOrientationSubscription?.cancel());
_isDisposed = true;
super.dispose();
if (_initializeFuture != null) {
await _initializeFuture;
await CameraPlatform.instance.dispose(_cameraId);
}
}
カメラパッケージのPR一覧をチラ見。勉強になる。
おおよそ眺めた。満足。
iOS もビルドしておく。
差分として Podfile.lock
, project.pbxproj
, contents.xcworkspacedate
が自動で発生した。コミットする。
カメラを起動しようとすると、案の定クラッシュ。知ってた。
This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSCameraUsageDescription key with a string value explaining to the user how the app uses this data.
camera の README にあるように、ios/Runner/Info.plist に以下を追加しないといけない。
<key>NSCameraUsageDescription</key>
<string>your usage description here</string>
<key>NSMicrophoneUsageDescription</key>
<string>your usage description here</string>
ビルドして、問題なくexampleが実行できることを確認。
自分用に Repo 作成