Compare commits
3 Commits
Author | SHA1 | Date |
---|---|---|
Vitaliy Zarubin | 9c83303f10 | 1 year ago |
Vitaliy Zarubin | 8388511327 | 1 year ago |
Vitaliy Zarubin | b3679eec50 | 1 year ago |
46 changed files with 81 additions and 2188 deletions
@ -1,64 +0,0 @@ |
|||||||
// SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
// SPDX-License-Identifier: BSD-3-Clause |
|
||||||
import 'dart:io'; |
|
||||||
|
|
||||||
import 'package:camera/camera.dart'; |
|
||||||
import 'package:flutter/foundation.dart'; |
|
||||||
import 'package:image/image.dart' as img; |
|
||||||
import 'package:path_provider/path_provider.dart' as provider; |
|
||||||
import 'package:path_provider/path_provider.dart'; |
|
||||||
|
|
||||||
import 'uint8_list.dart'; |
|
||||||
|
|
||||||
extension ExtCameraController on CameraController { |
|
||||||
/// Get photo |
|
||||||
Future<File?> takeImageFile() async { |
|
||||||
if (!value.isInitialized) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
if (value.isTakingPicture) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
try { |
|
||||||
// Get image |
|
||||||
final picture = await takePicture(); |
|
||||||
// Get bytes |
|
||||||
final bytes = await picture.readAsBytes(); |
|
||||||
// Get path |
|
||||||
final directory = await provider.getExternalStorageDirectories( |
|
||||||
type: StorageDirectory.pictures); |
|
||||||
// Save to file |
|
||||||
final file = await bytes.writeToFile(directory![0], picture); |
|
||||||
// Return saved file |
|
||||||
return file; |
|
||||||
} on CameraException catch (e) { |
|
||||||
debugPrint(e.description); |
|
||||||
return null; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// Get video |
|
||||||
Future<File?> takeVideoFileAndStopRecording() async { |
|
||||||
if (!value.isInitialized) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
if (!value.isRecordingVideo) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
try { |
|
||||||
// Stop recording |
|
||||||
final video = await stopVideoRecording(); |
|
||||||
// Get bytes |
|
||||||
final bytes = await video.readAsBytes(); |
|
||||||
// Get path |
|
||||||
final directory = await provider.getApplicationDocumentsDirectory(); |
|
||||||
// Save to file |
|
||||||
final file = await bytes.writeToFile(directory, video); |
|
||||||
// Return saved file |
|
||||||
return file; |
|
||||||
} on CameraException catch (e) { |
|
||||||
debugPrint(e.description); |
|
||||||
return null; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,35 +0,0 @@ |
|||||||
// SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
// SPDX-License-Identifier: BSD-3-Clause |
|
||||||
import 'package:camera/camera.dart'; |
|
||||||
import 'package:flutter/material.dart'; |
|
||||||
|
|
||||||
extension ExtCameraDescription on CameraDescription { |
|
||||||
/// Get [CameraController] |
|
||||||
Future<CameraController?> getCameraController() async { |
|
||||||
final cameraController = CameraController( |
|
||||||
this, |
|
||||||
ResolutionPreset.medium, |
|
||||||
enableAudio: true, |
|
||||||
imageFormatGroup: ImageFormatGroup.jpeg, |
|
||||||
); |
|
||||||
try { |
|
||||||
await cameraController.initialize(); |
|
||||||
return cameraController; |
|
||||||
} on CameraException catch (e) { |
|
||||||
debugPrint(e.description); |
|
||||||
return null; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// Get Icon by direction |
|
||||||
IconData getIcon() { |
|
||||||
switch (lensDirection) { |
|
||||||
case CameraLensDirection.back: |
|
||||||
return Icons.camera_rear; |
|
||||||
case CameraLensDirection.front: |
|
||||||
return Icons.camera_front; |
|
||||||
case CameraLensDirection.external: |
|
||||||
return Icons.camera; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,7 +0,0 @@ |
|||||||
// SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
// SPDX-License-Identifier: BSD-3-Clause |
|
||||||
library camera; |
|
||||||
|
|
||||||
export './camera_controller.dart'; |
|
||||||
export './camera_description.dart'; |
|
||||||
export './uint8_list.dart'; |
|
@ -1,21 +0,0 @@ |
|||||||
// SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
// SPDX-License-Identifier: BSD-3-Clause |
|
||||||
import 'dart:async'; |
|
||||||
import 'dart:io'; |
|
||||||
import 'dart:typed_data'; |
|
||||||
|
|
||||||
import 'package:camera/camera.dart'; |
|
||||||
import 'package:path/path.dart' as p; |
|
||||||
|
|
||||||
extension ExtUint8List on Uint8List { |
|
||||||
Future<File> writeToFile(Directory directory, XFile file) { |
|
||||||
if (file.name.isEmpty) { |
|
||||||
/// @todo XFile.fromData not work name file |
|
||||||
return File(p.join(directory.path, |
|
||||||
'${DateTime.now().millisecondsSinceEpoch}.${file.mimeType == 'video/mp4' ? 'mp4' : 'jpg'}')) |
|
||||||
.writeAsBytes(this); |
|
||||||
} else { |
|
||||||
return File(p.join(directory.path, file.name)).writeAsBytes(this); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,11 +0,0 @@ |
|||||||
// SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
// SPDX-License-Identifier: BSD-3-Clause |
|
||||||
import 'package:flutter/widgets.dart'; |
|
||||||
import 'package:scoped_model/scoped_model.dart'; |
|
||||||
|
|
||||||
/// Model for [CameraPage] |
|
||||||
class CameraModel extends Model { |
|
||||||
/// Get [ScopedModel] |
|
||||||
static CameraModel of(BuildContext context) => |
|
||||||
ScopedModel.of<CameraModel>(context); |
|
||||||
} |
|
@ -1,26 +0,0 @@ |
|||||||
// SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
// SPDX-License-Identifier: BSD-3-Clause |
|
||||||
import 'package:flutter_example_packages/base/package/package_page.dart'; |
|
||||||
import 'package:get_it/get_it.dart'; |
|
||||||
|
|
||||||
import 'model.dart'; |
|
||||||
import 'page.dart'; |
|
||||||
|
|
||||||
/// Package values |
|
||||||
final packageCamera = PackagePage( |
|
||||||
key: 'camera', |
|
||||||
descEN: ''' |
|
||||||
A Flutter plugin for Aurora, iOS, Android and Web allowing access |
|
||||||
to the device cameras. |
|
||||||
''', |
|
||||||
descRU: ''' |
|
||||||
Плагин Flutter для Aurora, iOS, Android и Интернета, обеспечивающий доступ |
|
||||||
к камерам устройства. |
|
||||||
''', |
|
||||||
version: '0.10.5+2', |
|
||||||
isPlatformDependent: true, |
|
||||||
page: () => CameraPage(), |
|
||||||
init: () { |
|
||||||
GetIt.instance.registerFactory<CameraModel>(() => CameraModel()); |
|
||||||
}, |
|
||||||
); |
|
@ -1,162 +0,0 @@ |
|||||||
// SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
// SPDX-License-Identifier: BSD-3-Clause |
|
||||||
import 'dart:io'; |
|
||||||
|
|
||||||
import 'package:camera/camera.dart'; |
|
||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:flutter_example_packages/base/di/app_di.dart'; |
|
||||||
import 'package:flutter_example_packages/base/package/package.dart'; |
|
||||||
import 'package:flutter_example_packages/packages/camera/extension/export.dart'; |
|
||||||
import 'package:flutter_example_packages/packages/camera/widgets/camera_body.dart'; |
|
||||||
import 'package:flutter_example_packages/packages/camera/widgets/camera_control_panel.dart'; |
|
||||||
import 'package:flutter_example_packages/packages/camera/widgets/cameras_loading.dart'; |
|
||||||
import 'package:flutter_example_packages/packages/camera/widgets/cameras_select.dart'; |
|
||||||
import 'package:flutter_example_packages/theme/colors.dart'; |
|
||||||
import 'package:flutter_example_packages/widgets/base/export.dart'; |
|
||||||
import 'package:flutter_example_packages/widgets/layouts/block_layout.dart'; |
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; |
|
||||||
|
|
||||||
import 'model.dart'; |
|
||||||
import 'package.dart'; |
|
||||||
|
|
||||||
class CameraPage extends AppStatefulWidget { |
|
||||||
CameraPage({ |
|
||||||
super.key, |
|
||||||
}); |
|
||||||
|
|
||||||
final Package package = packageCamera; |
|
||||||
|
|
||||||
@override |
|
||||||
State<CameraPage> createState() => _CameraPageState(); |
|
||||||
} |
|
||||||
|
|
||||||
class _CameraPageState extends AppState<CameraPage> { |
|
||||||
CameraController? _cameraController; |
|
||||||
File? _photo; |
|
||||||
File? _video; |
|
||||||
bool _loading = false; |
|
||||||
|
|
||||||
@override |
|
||||||
Widget buildWide( |
|
||||||
BuildContext context, |
|
||||||
MediaQueryData media, |
|
||||||
AppLocalizations l10n, |
|
||||||
) { |
|
||||||
return BlockLayout<CameraModel>( |
|
||||||
model: getIt<CameraModel>(), |
|
||||||
title: widget.package.key, |
|
||||||
builder: (context, child, model) { |
|
||||||
return Padding( |
|
||||||
padding: const EdgeInsets.all(20), |
|
||||||
child: CamerasLoading( |
|
||||||
package: widget.package, |
|
||||||
builder: (context, cameras) { |
|
||||||
return Column( |
|
||||||
crossAxisAlignment: CrossAxisAlignment.start, |
|
||||||
children: [ |
|
||||||
Flexible( |
|
||||||
flex: 1, |
|
||||||
child: Column( |
|
||||||
children: [ |
|
||||||
Flexible( |
|
||||||
flex: 0, |
|
||||||
child: CamerasSelect( |
|
||||||
disable: _loading, |
|
||||||
cameras: cameras, |
|
||||||
onChange: (controller) => setState(() { |
|
||||||
_photo = null; |
|
||||||
_cameraController = controller; |
|
||||||
}), |
|
||||||
), |
|
||||||
), |
|
||||||
const SizedBox(height: 5), |
|
||||||
Flexible( |
|
||||||
flex: 1, |
|
||||||
child: CameraBody( |
|
||||||
loading: _loading, |
|
||||||
controller: _cameraController, |
|
||||||
photo: _photo, |
|
||||||
), |
|
||||||
), |
|
||||||
CameraControlPanel( |
|
||||||
disable: _loading, |
|
||||||
controller: _cameraController, |
|
||||||
photo: _photo, |
|
||||||
// Start record video |
|
||||||
onRecordingStart: () => _cameraController |
|
||||||
?.startVideoRecording() |
|
||||||
.then((picture) { |
|
||||||
if (mounted) { |
|
||||||
setState(() {}); |
|
||||||
} |
|
||||||
}), |
|
||||||
// Pause record video if record already start |
|
||||||
onRecordingPause: () => _cameraController |
|
||||||
?.pauseVideoRecording() |
|
||||||
.then((value) { |
|
||||||
if (mounted) { |
|
||||||
setState(() {}); |
|
||||||
} |
|
||||||
}), |
|
||||||
// Resume record video if record already start and will pause |
|
||||||
onRecordingResume: () => _cameraController |
|
||||||
?.resumeVideoRecording() |
|
||||||
.then((value) { |
|
||||||
if (mounted) { |
|
||||||
setState(() {}); |
|
||||||
} |
|
||||||
}), |
|
||||||
// Clear photo |
|
||||||
onClearPhoto: () { |
|
||||||
if (mounted) { |
|
||||||
setState(() { |
|
||||||
_photo = null; |
|
||||||
}); |
|
||||||
} |
|
||||||
}, |
|
||||||
// Stop record video and save to file (custom extension) |
|
||||||
onRecordingStop: () => _cameraController |
|
||||||
?.takeVideoFileAndStopRecording() |
|
||||||
.then((video) { |
|
||||||
if (mounted) { |
|
||||||
showMessage(video); |
|
||||||
setState(() { |
|
||||||
_video = video; |
|
||||||
}); |
|
||||||
} |
|
||||||
}), |
|
||||||
// Take photo and save to file (custom extension) |
|
||||||
onTakePhoto: () => setState(() { |
|
||||||
_loading = true; |
|
||||||
_cameraController?.takeImageFile().then((photo) { |
|
||||||
if (mounted) { |
|
||||||
showMessage(photo); |
|
||||||
setState(() { |
|
||||||
_loading = false; |
|
||||||
_photo = photo; |
|
||||||
}); |
|
||||||
} |
|
||||||
}); |
|
||||||
}), |
|
||||||
), |
|
||||||
], |
|
||||||
), |
|
||||||
), |
|
||||||
], |
|
||||||
); |
|
||||||
}, |
|
||||||
), |
|
||||||
); |
|
||||||
}, |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
void showMessage(File? file) { |
|
||||||
if (file != null) { |
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar( |
|
||||||
content: Text("File save to: ${file.path}"), |
|
||||||
backgroundColor: AppColors.secondary, |
|
||||||
)); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,109 +0,0 @@ |
|||||||
// SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
// SPDX-License-Identifier: BSD-3-Clause |
|
||||||
import 'dart:io'; |
|
||||||
|
|
||||||
import 'package:camera/camera.dart'; |
|
||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:flutter_example_packages/theme/radius.dart'; |
|
||||||
import 'package:flutter_example_packages/widgets/base/export.dart'; |
|
||||||
import 'package:flutter_example_packages/widgets/texts/export.dart'; |
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; |
|
||||||
|
|
||||||
class CameraBody extends AppStatefulWidget { |
|
||||||
const CameraBody({ |
|
||||||
super.key, |
|
||||||
required this.loading, |
|
||||||
required this.controller, |
|
||||||
required this.photo, |
|
||||||
}); |
|
||||||
|
|
||||||
final bool loading; |
|
||||||
final CameraController? controller; |
|
||||||
final File? photo; |
|
||||||
|
|
||||||
@override |
|
||||||
State<CameraBody> createState() => _CameraBodyState(); |
|
||||||
} |
|
||||||
|
|
||||||
class _CameraBodyState extends AppState<CameraBody> { |
|
||||||
@override |
|
||||||
void dispose() { |
|
||||||
widget.controller?.dispose(); |
|
||||||
super.dispose(); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Widget buildWide( |
|
||||||
BuildContext context, |
|
||||||
MediaQueryData media, |
|
||||||
AppLocalizations l10n, |
|
||||||
) { |
|
||||||
return ClipRRect( |
|
||||||
borderRadius: AppRadius.small, |
|
||||||
child: Container( |
|
||||||
color: Colors.black87, |
|
||||||
child: Stack( |
|
||||||
children: [ |
|
||||||
if (widget.loading) |
|
||||||
const Center( |
|
||||||
child: TextTitleLarge( |
|
||||||
'Loading...', |
|
||||||
color: Colors.white, |
|
||||||
), |
|
||||||
), |
|
||||||
// Show info if not select camera |
|
||||||
if (!widget.loading && |
|
||||||
widget.controller == null && |
|
||||||
widget.photo == null) |
|
||||||
const Center( |
|
||||||
child: TextTitleLarge( |
|
||||||
'Select camera', |
|
||||||
color: Colors.white, |
|
||||||
), |
|
||||||
), |
|
||||||
|
|
||||||
// Show camera preview |
|
||||||
if (!widget.loading && |
|
||||||
widget.controller != null && |
|
||||||
widget.controller!.value.isInitialized && |
|
||||||
widget.photo == null) |
|
||||||
Container( |
|
||||||
width: double.infinity, |
|
||||||
height: double.infinity, |
|
||||||
alignment: Alignment.center, |
|
||||||
child: widget.controller!.buildPreview(), |
|
||||||
), |
|
||||||
|
|
||||||
// Show dot when recording is active |
|
||||||
if (!widget.loading && |
|
||||||
(widget.controller?.value.isRecordingVideo ?? false) && |
|
||||||
!(widget.controller?.value.isRecordingPaused ?? false)) |
|
||||||
Padding( |
|
||||||
padding: const EdgeInsets.all(16), |
|
||||||
child: ClipOval( |
|
||||||
child: Container( |
|
||||||
color: Colors.red, |
|
||||||
width: 16, |
|
||||||
height: 16, |
|
||||||
), |
|
||||||
), |
|
||||||
), |
|
||||||
|
|
||||||
// Show take phone |
|
||||||
if (!widget.loading && widget.photo != null) |
|
||||||
Container( |
|
||||||
width: double.infinity, |
|
||||||
height: double.infinity, |
|
||||||
alignment: Alignment.center, |
|
||||||
child: Image.file( |
|
||||||
widget.photo!, |
|
||||||
fit: BoxFit.fill, |
|
||||||
filterQuality: FilterQuality.high, |
|
||||||
), |
|
||||||
), |
|
||||||
], |
|
||||||
), |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
@ -1,130 +0,0 @@ |
|||||||
// SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
// SPDX-License-Identifier: BSD-3-Clause |
|
||||||
import 'dart:io'; |
|
||||||
|
|
||||||
import 'package:camera/camera.dart'; |
|
||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:flutter_example_packages/theme/colors.dart'; |
|
||||||
import 'package:flutter_example_packages/widgets/base/export.dart'; |
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; |
|
||||||
|
|
||||||
class CameraControlPanel extends AppStatefulWidget { |
|
||||||
const CameraControlPanel({ |
|
||||||
super.key, |
|
||||||
required this.disable, |
|
||||||
required this.controller, |
|
||||||
required this.photo, |
|
||||||
required this.onRecordingStart, |
|
||||||
required this.onRecordingStop, |
|
||||||
required this.onRecordingPause, |
|
||||||
required this.onRecordingResume, |
|
||||||
required this.onTakePhoto, |
|
||||||
required this.onClearPhoto, |
|
||||||
}); |
|
||||||
|
|
||||||
final bool disable; |
|
||||||
final CameraController? controller; |
|
||||||
final File? photo; |
|
||||||
final void Function() onRecordingStart; |
|
||||||
final void Function() onRecordingStop; |
|
||||||
final void Function() onRecordingPause; |
|
||||||
final void Function() onRecordingResume; |
|
||||||
final void Function() onTakePhoto; |
|
||||||
final void Function() onClearPhoto; |
|
||||||
|
|
||||||
@override |
|
||||||
State<CameraControlPanel> createState() => _CameraControlPanelState(); |
|
||||||
} |
|
||||||
|
|
||||||
class _CameraControlPanelState extends AppState<CameraControlPanel> { |
|
||||||
@override |
|
||||||
Widget buildWide( |
|
||||||
BuildContext context, |
|
||||||
MediaQueryData media, |
|
||||||
AppLocalizations l10n, |
|
||||||
) { |
|
||||||
final isPhoto = widget.photo != null; |
|
||||||
final isRecordingVideo = widget.controller?.value.isRecordingVideo ?? false; |
|
||||||
final isRecordingPaused = |
|
||||||
widget.controller?.value.isRecordingPaused ?? false; |
|
||||||
|
|
||||||
return Visibility( |
|
||||||
visible: widget.controller != null, |
|
||||||
child: Column( |
|
||||||
children: [ |
|
||||||
const SizedBox(height: 10), |
|
||||||
Row( |
|
||||||
children: [ |
|
||||||
ClipOval( |
|
||||||
child: Material( |
|
||||||
child: IconButton( |
|
||||||
icon: Icon( |
|
||||||
isRecordingVideo ? Icons.stop_circle : Icons.videocam, |
|
||||||
color: AppColors.primary.withOpacity(isPhoto || |
|
||||||
widget.disable || |
|
||||||
true /* @todo disable video record */ |
|
||||||
? 0.5 |
|
||||||
: 1), |
|
||||||
), |
|
||||||
onPressed: isPhoto || |
|
||||||
widget.disable || |
|
||||||
true // @todo disable video record |
|
||||||
? null |
|
||||||
: () { |
|
||||||
if (isRecordingVideo) { |
|
||||||
widget.onRecordingStop.call(); |
|
||||||
} else { |
|
||||||
widget.onRecordingStart.call(); |
|
||||||
} |
|
||||||
}, |
|
||||||
), |
|
||||||
), |
|
||||||
), |
|
||||||
if (isRecordingVideo) |
|
||||||
ClipOval( |
|
||||||
child: Material( |
|
||||||
child: IconButton( |
|
||||||
icon: Icon( |
|
||||||
isRecordingPaused |
|
||||||
? Icons.play_circle |
|
||||||
: Icons.pause_circle, |
|
||||||
color: AppColors.primary, |
|
||||||
), |
|
||||||
onPressed: () { |
|
||||||
if (isRecordingPaused) { |
|
||||||
widget.onRecordingResume.call(); |
|
||||||
} else { |
|
||||||
widget.onRecordingPause.call(); |
|
||||||
} |
|
||||||
}, |
|
||||||
), |
|
||||||
), |
|
||||||
), |
|
||||||
const Spacer(), |
|
||||||
ClipOval( |
|
||||||
child: Material( |
|
||||||
child: IconButton( |
|
||||||
icon: Icon( |
|
||||||
isPhoto ? Icons.image_not_supported : Icons.photo_camera, |
|
||||||
color: AppColors.primary.withOpacity( |
|
||||||
isRecordingVideo || widget.disable ? 0.5 : 1), |
|
||||||
), |
|
||||||
onPressed: isRecordingVideo || widget.disable |
|
||||||
? null |
|
||||||
: () { |
|
||||||
if (isPhoto) { |
|
||||||
widget.onClearPhoto.call(); |
|
||||||
} else { |
|
||||||
widget.onTakePhoto.call(); |
|
||||||
} |
|
||||||
}, |
|
||||||
), |
|
||||||
), |
|
||||||
), |
|
||||||
], |
|
||||||
), |
|
||||||
], |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
@ -1,96 +0,0 @@ |
|||||||
// SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
// SPDX-License-Identifier: BSD-3-Clause |
|
||||||
import 'package:camera/camera.dart'; |
|
||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:flutter_example_packages/base/package/package.dart'; |
|
||||||
import 'package:flutter_example_packages/widgets/base/export.dart'; |
|
||||||
import 'package:flutter_example_packages/widgets/blocks/block_alert.dart'; |
|
||||||
import 'package:flutter_example_packages/widgets/blocks/block_info_package.dart'; |
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; |
|
||||||
|
|
||||||
class CamerasLoading extends AppStatefulWidget { |
|
||||||
const CamerasLoading({ |
|
||||||
super.key, |
|
||||||
required this.package, |
|
||||||
required this.builder, |
|
||||||
}); |
|
||||||
|
|
||||||
final Package package; |
|
||||||
final Widget Function(BuildContext context, List<CameraDescription> cameras) |
|
||||||
builder; |
|
||||||
|
|
||||||
@override |
|
||||||
State<CamerasLoading> createState() => _CamerasLoadingState(); |
|
||||||
} |
|
||||||
|
|
||||||
class _CamerasLoadingState extends AppState<CamerasLoading> { |
|
||||||
List<CameraDescription>? _cameras; |
|
||||||
String? _error; |
|
||||||
|
|
||||||
@override |
|
||||||
void initState() { |
|
||||||
super.initState(); |
|
||||||
// Get list cameras |
|
||||||
try { |
|
||||||
availableCameras().then((cameras) { |
|
||||||
if (mounted) { |
|
||||||
setState(() { |
|
||||||
_cameras = cameras; |
|
||||||
}); |
|
||||||
} |
|
||||||
}).catchError((e) { |
|
||||||
setState(() { |
|
||||||
_error = e.toString(); |
|
||||||
}); |
|
||||||
}); |
|
||||||
} catch (e) { |
|
||||||
setState(() { |
|
||||||
_error = e.toString(); |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Widget buildWide( |
|
||||||
BuildContext context, |
|
||||||
MediaQueryData media, |
|
||||||
AppLocalizations l10n, |
|
||||||
) { |
|
||||||
return Column( |
|
||||||
crossAxisAlignment: CrossAxisAlignment.start, |
|
||||||
children: [ |
|
||||||
Flexible( |
|
||||||
flex: 0, |
|
||||||
child: Column( |
|
||||||
children: [ |
|
||||||
BlockInfoPackage( |
|
||||||
widget.package, |
|
||||||
), |
|
||||||
], |
|
||||||
), |
|
||||||
), |
|
||||||
Visibility( |
|
||||||
visible: _cameras == null && _error == null, |
|
||||||
child: const Flexible( |
|
||||||
flex: 1, |
|
||||||
child: Center( |
|
||||||
child: CircularProgressIndicator(), |
|
||||||
), |
|
||||||
), |
|
||||||
), |
|
||||||
Visibility( |
|
||||||
visible: _cameras?.isEmpty ?? false || _error != null, |
|
||||||
child: Flexible( |
|
||||||
flex: 0, |
|
||||||
child: BlockAlert(_error ?? 'No camera found.'), |
|
||||||
), |
|
||||||
), |
|
||||||
if (_cameras?.isNotEmpty ?? false) |
|
||||||
Flexible( |
|
||||||
flex: 1, |
|
||||||
child: widget.builder.call(context, _cameras!), |
|
||||||
), |
|
||||||
], |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
@ -1,83 +0,0 @@ |
|||||||
// SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
// SPDX-License-Identifier: BSD-3-Clause |
|
||||||
import 'package:camera/camera.dart'; |
|
||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:flutter_example_packages/packages/camera/extension/export.dart'; |
|
||||||
import 'package:flutter_example_packages/theme/colors.dart'; |
|
||||||
import 'package:flutter_example_packages/widgets/base/export.dart'; |
|
||||||
import 'package:flutter_example_packages/widgets/texts/export.dart'; |
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; |
|
||||||
|
|
||||||
class CamerasSelect extends AppStatefulWidget { |
|
||||||
const CamerasSelect({ |
|
||||||
super.key, |
|
||||||
required this.disable, |
|
||||||
required this.cameras, |
|
||||||
required this.onChange, |
|
||||||
}); |
|
||||||
|
|
||||||
final bool disable; |
|
||||||
final List<CameraDescription> cameras; |
|
||||||
final void Function(CameraController controller) onChange; |
|
||||||
|
|
||||||
@override |
|
||||||
State<CamerasSelect> createState() => _CamerasSelectState(); |
|
||||||
} |
|
||||||
|
|
||||||
class _CamerasSelectState extends AppState<CamerasSelect> { |
|
||||||
CameraController? _cameraController; |
|
||||||
|
|
||||||
Future<void> initCamera(CameraDescription? camera) async { |
|
||||||
if (camera != null) { |
|
||||||
if (_cameraController != null) { |
|
||||||
// Check and stop if need image stream |
|
||||||
if (_cameraController!.value.isStreamingImages) { |
|
||||||
await _cameraController!.stopImageStream(); |
|
||||||
} |
|
||||||
// Check and stop if need video recording |
|
||||||
if (_cameraController!.value.isRecordingVideo) { |
|
||||||
await _cameraController!.stopVideoRecording(); |
|
||||||
} |
|
||||||
// Change camera |
|
||||||
await _cameraController!.setDescription(camera); |
|
||||||
} else { |
|
||||||
_cameraController = await camera.getCameraController(); |
|
||||||
} |
|
||||||
// Send signal about change camera |
|
||||||
if (mounted) { |
|
||||||
widget.onChange(_cameraController!); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Widget buildWide( |
|
||||||
BuildContext context, |
|
||||||
MediaQueryData media, |
|
||||||
AppLocalizations l10n, |
|
||||||
) { |
|
||||||
return Row( |
|
||||||
children: [ |
|
||||||
const TextTitleLarge('Cameras:'), |
|
||||||
const SizedBox(width: 8), |
|
||||||
for (final CameraDescription camera in widget.cameras) |
|
||||||
ClipOval( |
|
||||||
child: Material( |
|
||||||
child: IconButton( |
|
||||||
icon: Icon( |
|
||||||
camera.getIcon(), |
|
||||||
color: _cameraController?.description == camera |
|
||||||
? AppColors.secondary |
|
||||||
: AppColors.primary, |
|
||||||
), |
|
||||||
onPressed: |
|
||||||
_cameraController?.description == camera || widget.disable |
|
||||||
? null |
|
||||||
: () => initCamera(camera), |
|
||||||
), |
|
||||||
), |
|
||||||
), |
|
||||||
], |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
@ -1,30 +0,0 @@ |
|||||||
# Miscellaneous |
|
||||||
*.class |
|
||||||
*.log |
|
||||||
*.pyc |
|
||||||
*.swp |
|
||||||
.DS_Store |
|
||||||
.atom/ |
|
||||||
.buildlog/ |
|
||||||
.history |
|
||||||
.svn/ |
|
||||||
migrate_working_dir/ |
|
||||||
|
|
||||||
# IntelliJ related |
|
||||||
*.iml |
|
||||||
*.ipr |
|
||||||
*.iws |
|
||||||
.idea/ |
|
||||||
|
|
||||||
# The .vscode folder contains launch configuration and tasks you configure in |
|
||||||
# VS Code which you may wish to be included in version control, so this line |
|
||||||
# is commented out by default. |
|
||||||
.vscode/ |
|
||||||
|
|
||||||
# Flutter/Dart/Pub related |
|
||||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. |
|
||||||
/pubspec.lock |
|
||||||
**/doc/api/ |
|
||||||
.dart_tool/ |
|
||||||
.packages |
|
||||||
build/ |
|
@ -1,25 +0,0 @@ |
|||||||
# camera_aurora |
|
||||||
|
|
||||||
The Aurora implementation of [camera](https://pub.dev/packages/camera). |
|
||||||
|
|
||||||
## Usage |
|
||||||
This package is not an _endorsed_ implementation of `camera`. |
|
||||||
Therefore, you have to include `camera_aurora` alongside `camera` as dependencies in your `pubspec.yaml` file. |
|
||||||
|
|
||||||
***.desktop** |
|
||||||
|
|
||||||
```desktop |
|
||||||
Permissions=Camera |
|
||||||
``` |
|
||||||
|
|
||||||
**pubspec.yaml** |
|
||||||
|
|
||||||
```yaml |
|
||||||
dependencies: |
|
||||||
camera: ^0.10.5+5 |
|
||||||
camera_aurora: |
|
||||||
git: |
|
||||||
url: https://gitlab.com/omprussia/flutter/flutter-plugins.git |
|
||||||
ref: master |
|
||||||
path: packages/device_info_plus/device_info_plus_aurora |
|
||||||
``` |
|
@ -1,4 +0,0 @@ |
|||||||
# SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
# SPDX-License-Identifier: BSD-3-Clause |
|
||||||
|
|
||||||
include: package:flutter_lints/flutter.yaml |
|
@ -1,40 +0,0 @@ |
|||||||
# SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
# SPDX-License-Identifier: BSD-3-Clause |
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.10) |
|
||||||
|
|
||||||
set(PROJECT_NAME camera_aurora) |
|
||||||
set(PLUGIN_NAME camera_aurora_platform_plugin) |
|
||||||
|
|
||||||
project(${PROJECT_NAME} LANGUAGES CXX) |
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17) |
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON) |
|
||||||
|
|
||||||
set(CMAKE_CXX_FLAGS "-Wall -Wextra -Wno-psabi") |
|
||||||
set(CMAKE_CXX_FLAGS_RELEASE "-O3") |
|
||||||
|
|
||||||
find_package(PkgConfig REQUIRED) |
|
||||||
find_package(Qt5 COMPONENTS Core Multimedia REQUIRED) |
|
||||||
|
|
||||||
pkg_check_modules(FlutterEmbedder REQUIRED IMPORTED_TARGET flutter-embedder) |
|
||||||
pkg_check_modules(GLES REQUIRED IMPORTED_TARGET glesv2) |
|
||||||
pkg_check_modules(SC REQUIRED IMPORTED_TARGET streamcamera) |
|
||||||
|
|
||||||
add_library(${PLUGIN_NAME} SHARED |
|
||||||
texture_camera_pixels_helper.cpp |
|
||||||
texture_camera_egl_helper.cpp |
|
||||||
texture_camera.cpp |
|
||||||
camera_aurora_plugin.cpp |
|
||||||
) |
|
||||||
|
|
||||||
set_target_properties(${PLUGIN_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden AUTOMOC ON) |
|
||||||
|
|
||||||
target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::FlutterEmbedder PkgConfig::GLES PkgConfig::SC) |
|
||||||
target_link_libraries(${PLUGIN_NAME} PUBLIC Qt5::Core Qt5::Multimedia) |
|
||||||
|
|
||||||
target_include_directories(${PLUGIN_NAME} PRIVATE ${FLUTTER_DIR}) |
|
||||||
target_include_directories(${PLUGIN_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) |
|
||||||
target_include_directories(${PLUGIN_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include/${PROJECT_NAME}) |
|
||||||
|
|
||||||
target_compile_definitions(${PLUGIN_NAME} PRIVATE PLUGIN_IMPL) |
|
@ -1,217 +0,0 @@ |
|||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
* SPDX-License-Identifier: BSD-3-Clause |
|
||||||
*/ |
|
||||||
#include <camera_aurora/camera_aurora_plugin.h> |
|
||||||
#include <flutter/method-channel.h> |
|
||||||
#include <flutter/platform-data.h> |
|
||||||
#include <flutter/platform-events.h> |
|
||||||
#include <flutter/platform-methods.h> |
|
||||||
|
|
||||||
#include <QtCore> |
|
||||||
#include <QBuffer> |
|
||||||
#include <QCamera> |
|
||||||
#include <QCameraInfo> |
|
||||||
#include <QMediaRecorder> |
|
||||||
#include <QCameraImageCapture> |
|
||||||
|
|
||||||
#include <unistd.h> |
|
||||||
|
|
||||||
namespace CameraAuroraMethods |
|
||||||
{ |
|
||||||
constexpr auto PluginKey = "camera_aurora"; |
|
||||||
|
|
||||||
constexpr auto AvailableCameras = "availableCameras"; |
|
||||||
constexpr auto CreateCamera = "createCamera"; |
|
||||||
constexpr auto Dispose = "dispose"; |
|
||||||
constexpr auto StartCapture = "startCapture"; |
|
||||||
constexpr auto StopCapture = "stopCapture"; |
|
||||||
constexpr auto TakePicture = "takePicture"; |
|
||||||
constexpr auto StartVideoRecording = "startVideoRecording"; |
|
||||||
constexpr auto StopVideoRecording = "stopVideoRecording"; |
|
||||||
constexpr auto PauseVideoRecording = "pauseVideoRecording"; |
|
||||||
constexpr auto ResumeVideoRecording = "resumeVideoRecording"; |
|
||||||
} |
|
||||||
|
|
||||||
namespace CameraAuroraEvents |
|
||||||
{ |
|
||||||
constexpr auto StateChanged = "cameraAuroraStateChanged"; |
|
||||||
} |
|
||||||
|
|
||||||
CameraAuroraPlugin::CameraAuroraPlugin() |
|
||||||
{ |
|
||||||
PlatformEvents::SubscribeOrientationChanged([this]([[maybe_unused]] DisplayRotation orientation) { |
|
||||||
if (this->m_isEnableStateChanged) { |
|
||||||
auto state = this->m_textureCamera->GetState(); |
|
||||||
EventChannel(CameraAuroraEvents::StateChanged, MethodCodecType::Standard).SendEvent(state); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
void CameraAuroraPlugin::RegisterWithRegistrar(PluginRegistrar ®istrar) |
|
||||||
{ |
|
||||||
m_textureCamera = new TextureCamera(registrar.GetRegisterTexture(), [this]() { |
|
||||||
auto state = this->m_textureCamera->GetState(); |
|
||||||
EventChannel(CameraAuroraEvents::StateChanged, MethodCodecType::Standard).SendEvent(state); |
|
||||||
}); |
|
||||||
|
|
||||||
RegisterMethods(registrar); |
|
||||||
RegisterEvents(registrar); |
|
||||||
} |
|
||||||
|
|
||||||
void CameraAuroraPlugin::RegisterMethods(PluginRegistrar ®istrar) |
|
||||||
{ |
|
||||||
auto methods = [this](const MethodCall &call) |
|
||||||
{ |
|
||||||
const auto &method = call.GetMethod(); |
|
||||||
|
|
||||||
if (method == CameraAuroraMethods::AvailableCameras) |
|
||||||
{ |
|
||||||
onAvailableCameras(call); |
|
||||||
return; |
|
||||||
} |
|
||||||
if (method == CameraAuroraMethods::CreateCamera) |
|
||||||
{ |
|
||||||
onCreateCamera(call); |
|
||||||
return; |
|
||||||
} |
|
||||||
if (method == CameraAuroraMethods::Dispose) |
|
||||||
{ |
|
||||||
onDispose(call); |
|
||||||
return; |
|
||||||
} |
|
||||||
if (method == CameraAuroraMethods::StartCapture) |
|
||||||
{ |
|
||||||
onStartCapture(call); |
|
||||||
return; |
|
||||||
} |
|
||||||
if (method == CameraAuroraMethods::StopCapture) |
|
||||||
{ |
|
||||||
onStopCapture(call); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if (method == CameraAuroraMethods::TakePicture) |
|
||||||
{ |
|
||||||
onTakePicture(call); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
if (method == CameraAuroraMethods::StartVideoRecording) |
|
||||||
{ |
|
||||||
onStartVideoRecording(call); |
|
||||||
return; |
|
||||||
} |
|
||||||
if (method == CameraAuroraMethods::StopVideoRecording) |
|
||||||
{ |
|
||||||
onStopVideoRecording(call); |
|
||||||
return; |
|
||||||
} |
|
||||||
if (method == CameraAuroraMethods::PauseVideoRecording) |
|
||||||
{ |
|
||||||
onPauseVideoRecording(call); |
|
||||||
return; |
|
||||||
} |
|
||||||
if (method == CameraAuroraMethods::ResumeVideoRecording) |
|
||||||
{ |
|
||||||
onResumeVideoRecording(call); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
unimplemented(call); |
|
||||||
}; |
|
||||||
|
|
||||||
registrar.RegisterMethodChannel( |
|
||||||
CameraAuroraMethods::PluginKey, |
|
||||||
MethodCodecType::Standard, |
|
||||||
methods); |
|
||||||
} |
|
||||||
|
|
||||||
void CameraAuroraPlugin::RegisterEvents(PluginRegistrar ®istrar) |
|
||||||
{ |
|
||||||
registrar.RegisterEventChannel( |
|
||||||
CameraAuroraEvents::StateChanged, MethodCodecType::Standard, |
|
||||||
[this](const Encodable &) |
|
||||||
{ this->m_isEnableStateChanged = true; return EventResponse(); }, |
|
||||||
[this](const Encodable &) |
|
||||||
{ this->m_isEnableStateChanged = true; return EventResponse(); }); |
|
||||||
} |
|
||||||
|
|
||||||
/**
|
|
||||||
* Camera |
|
||||||
*/ |
|
||||||
void CameraAuroraPlugin::onAvailableCameras(const MethodCall &call) |
|
||||||
{ |
|
||||||
call.SendSuccessResponse(m_textureCamera->GetAvailableCameras()); |
|
||||||
} |
|
||||||
|
|
||||||
void CameraAuroraPlugin::onCreateCamera(const MethodCall &call) |
|
||||||
{ |
|
||||||
auto cameraName = call.GetArgument<Encodable::String>("cameraName"); |
|
||||||
|
|
||||||
auto state = m_textureCamera->Register(cameraName); |
|
||||||
|
|
||||||
EventChannel(CameraAuroraEvents::StateChanged, MethodCodecType::Standard).SendEvent(state); |
|
||||||
|
|
||||||
call.SendSuccessResponse(state); |
|
||||||
} |
|
||||||
|
|
||||||
void CameraAuroraPlugin::onDispose(const MethodCall &call) |
|
||||||
{ |
|
||||||
auto state = m_textureCamera->Unregister(); |
|
||||||
|
|
||||||
EventChannel(CameraAuroraEvents::StateChanged, MethodCodecType::Standard).SendEvent(state); |
|
||||||
|
|
||||||
unimplemented(call); |
|
||||||
} |
|
||||||
|
|
||||||
void CameraAuroraPlugin::onStartCapture(const MethodCall &call) |
|
||||||
{ |
|
||||||
auto width = call.GetArgument<Encodable::Int>("width"); |
|
||||||
auto height = call.GetArgument<Encodable::Int>("height"); |
|
||||||
|
|
||||||
auto state = m_textureCamera->StartCapture(width, height); |
|
||||||
|
|
||||||
EventChannel(CameraAuroraEvents::StateChanged, MethodCodecType::Standard).SendEvent(state); |
|
||||||
|
|
||||||
unimplemented(call); |
|
||||||
} |
|
||||||
|
|
||||||
void CameraAuroraPlugin::onStopCapture(const MethodCall &call) |
|
||||||
{ |
|
||||||
m_textureCamera->StopCapture(); |
|
||||||
|
|
||||||
unimplemented(call); |
|
||||||
} |
|
||||||
|
|
||||||
void CameraAuroraPlugin::onTakePicture(const MethodCall &call) |
|
||||||
{ |
|
||||||
call.SendSuccessResponse(m_textureCamera->GetImageBase64()); |
|
||||||
} |
|
||||||
|
|
||||||
void CameraAuroraPlugin::onStartVideoRecording(const MethodCall &call) |
|
||||||
{ |
|
||||||
unimplemented(call); |
|
||||||
} |
|
||||||
|
|
||||||
void CameraAuroraPlugin::onStopVideoRecording(const MethodCall &call) |
|
||||||
{ |
|
||||||
unimplemented(call); |
|
||||||
} |
|
||||||
|
|
||||||
void CameraAuroraPlugin::onPauseVideoRecording(const MethodCall &call) |
|
||||||
{ |
|
||||||
unimplemented(call); |
|
||||||
} |
|
||||||
|
|
||||||
void CameraAuroraPlugin::onResumeVideoRecording(const MethodCall &call) |
|
||||||
{ |
|
||||||
unimplemented(call); |
|
||||||
} |
|
||||||
|
|
||||||
void CameraAuroraPlugin::unimplemented(const MethodCall &call) |
|
||||||
{ |
|
||||||
call.SendSuccessResponse(nullptr); |
|
||||||
} |
|
||||||
|
|
||||||
#include "moc_camera_aurora_plugin.cpp" |
|
@ -1,47 +0,0 @@ |
|||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
* SPDX-License-Identifier: BSD-3-Clause |
|
||||||
*/ |
|
||||||
#ifndef FLUTTER_PLUGIN_CAMERA_AURORA_PLUGIN_H |
|
||||||
#define FLUTTER_PLUGIN_CAMERA_AURORA_PLUGIN_H |
|
||||||
|
|
||||||
#include <flutter/plugin-interface.h> |
|
||||||
#include <camera_aurora/globals.h> |
|
||||||
|
|
||||||
#include <camera_aurora/texture_camera.h> |
|
||||||
|
|
||||||
class PLUGIN_EXPORT CameraAuroraPlugin final |
|
||||||
: public QObject, |
|
||||||
public PluginInterface |
|
||||||
{ |
|
||||||
Q_OBJECT |
|
||||||
|
|
||||||
public: |
|
||||||
CameraAuroraPlugin(); |
|
||||||
void RegisterWithRegistrar(PluginRegistrar ®istrar) override; |
|
||||||
|
|
||||||
private: |
|
||||||
void RegisterMethods(PluginRegistrar ®istrar); |
|
||||||
void RegisterEvents(PluginRegistrar ®istrar); |
|
||||||
|
|
||||||
void onAvailableCameras(const MethodCall &call); |
|
||||||
void onCreateCamera(const MethodCall &call); |
|
||||||
void onDispose(const MethodCall &call); |
|
||||||
void onStartCapture(const MethodCall &call); |
|
||||||
void onStopCapture(const MethodCall &call); |
|
||||||
|
|
||||||
void onTakePicture(const MethodCall &call); |
|
||||||
|
|
||||||
void onStartVideoRecording(const MethodCall &call); |
|
||||||
void onStopVideoRecording(const MethodCall &call); |
|
||||||
void onPauseVideoRecording(const MethodCall &call); |
|
||||||
void onResumeVideoRecording(const MethodCall &call); |
|
||||||
|
|
||||||
void unimplemented(const MethodCall &call); |
|
||||||
|
|
||||||
private: |
|
||||||
TextureCamera *m_textureCamera; |
|
||||||
bool m_isEnableStateChanged = false; |
|
||||||
}; |
|
||||||
|
|
||||||
#endif /* FLUTTER_PLUGIN_CAMERA_AURORA_PLUGIN_H */ |
|
@ -1,14 +0,0 @@ |
|||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
* SPDX-License-Identifier: BSD-3-Clause |
|
||||||
*/ |
|
||||||
#ifndef FLUTTER_PLUGIN_CAMERA_AURORA_PLUGIN_GLOBALS_H |
|
||||||
#define FLUTTER_PLUGIN_CAMERA_AURORA_PLUGIN_GLOBALS_H |
|
||||||
|
|
||||||
#ifdef PLUGIN_IMPL |
|
||||||
#define PLUGIN_EXPORT __attribute__((visibility("default"))) |
|
||||||
#else |
|
||||||
#define PLUGIN_EXPORT |
|
||||||
#endif |
|
||||||
|
|
||||||
#endif /* FLUTTER_PLUGIN_CAMERA_AURORA_PLUGIN_GLOBALS_H */ |
|
@ -1,57 +0,0 @@ |
|||||||
/**
|
|
||||||
* SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
* SPDX-License-Identifier: BSD-3-Clause |
|
||||||
*/ |
|
||||||
#ifndef TEXTURE_CAMERA_BUFFER_H |
|
||||||
#define TEXTURE_CAMERA_BUFFER_H |
|
||||||
|
|
||||||
#include <flutter/plugin-interface.h> |
|
||||||
#include <streamcamera/streamcamera.h> |
|
||||||
|
|
||||||
#include <QImage> |
|
||||||
#include <QtCore> |
|
||||||
|
|
||||||
typedef std::function<void()> CameraErrorHandler; |
|
||||||
|
|
||||||
class TextureCamera : public Aurora::StreamCamera::CameraListener |
|
||||||
{ |
|
||||||
public: |
|
||||||
TextureCamera(TextureRegistrar *plugin, const CameraErrorHandler &onError); |
|
||||||
|
|
||||||
void onCameraError(const std::string &errorDescription) override; |
|
||||||
void onCameraFrame(std::shared_ptr<Aurora::StreamCamera::GraphicBuffer> buffer) override; |
|
||||||
void onCameraParameterChanged(Aurora::StreamCamera::CameraParameter, |
|
||||||
const std::string &value) override; |
|
||||||
|
|
||||||
std::vector<Encodable> GetAvailableCameras(); |
|
||||||
std::map<Encodable, Encodable> Register(std::string cameraName); |
|
||||||
std::map<Encodable, Encodable> Unregister(); |
|
||||||
std::map<Encodable, Encodable> StartCapture(size_t width, size_t height); |
|
||||||
void StopCapture(); |
|
||||||
std::map<Encodable, Encodable> GetState(); |
|
||||||
std::string GetImageBase64(); |
|
||||||
|
|
||||||
private: |
|
||||||
bool CreateCamera(std::string cameraName); |
|
||||||
void SendError(std::string error); |
|
||||||
|
|
||||||
private: |
|
||||||
TextureRegistrar *m_plugin; |
|
||||||
|
|
||||||
CameraErrorHandler m_onError; |
|
||||||
std::string m_error; |
|
||||||
|
|
||||||
Aurora::StreamCamera::CameraManager *m_manager; |
|
||||||
std::shared_ptr<Aurora::StreamCamera::Camera> m_camera; |
|
||||||
|
|
||||||
int64_t m_textureId = 0; |
|
||||||
size_t m_captureWidth = 0; |
|
||||||
size_t m_captureHeight = 0; |
|
||||||
size_t m_viewWidth = 0; |
|
||||||
size_t m_viewHeight = 0; |
|
||||||
|
|
||||||
TextureVariant *m_variant; |
|
||||||
QImage *m_image; |
|
||||||
}; |
|
||||||
|
|
||||||
#endif /* TEXTURE_CAMERA_BUFFER_H */ |
|
@ -1,21 +0,0 @@ |
|||||||
/**
|
|
||||||
* SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
* SPDX-License-Identifier: BSD-3-Clause |
|
||||||
*/ |
|
||||||
#ifndef TEXTURE_CAMERA_EGL_HELPER_H |
|
||||||
#define TEXTURE_CAMERA_EGL_HELPER_H |
|
||||||
|
|
||||||
#include <streamcamera/streamcamera.h> |
|
||||||
|
|
||||||
#include <EGL/egl.h> |
|
||||||
#include <EGL/eglext.h> |
|
||||||
|
|
||||||
class TextureCameraEGLHelper |
|
||||||
{ |
|
||||||
public: |
|
||||||
static void EGLInit(); |
|
||||||
static EGLImageKHR EGLCreateImage(std::shared_ptr<Aurora::StreamCamera::GraphicBuffer> buffer); |
|
||||||
static void EGLDestroyImage(EGLImageKHR image); |
|
||||||
}; |
|
||||||
|
|
||||||
#endif /* TEXTURE_CAMERA_EGL_HELPER_H */ |
|
@ -1,33 +0,0 @@ |
|||||||
/**
|
|
||||||
* SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
* SPDX-License-Identifier: BSD-3-Clause |
|
||||||
*/ |
|
||||||
#ifndef TEXTURE_CAMERA_PIXELS_HELPER_H |
|
||||||
#define TEXTURE_CAMERA_PIXELS_HELPER_H |
|
||||||
|
|
||||||
#include <flutter/plugin-interface.h> |
|
||||||
#include <streamcamera/streamcamera.h> |
|
||||||
|
|
||||||
#include <QImage> |
|
||||||
#include <QtCore> |
|
||||||
|
|
||||||
class TextureCameraPixelsHelper |
|
||||||
{ |
|
||||||
public: |
|
||||||
static QImage *YUVtoARGB(std::shared_ptr<const Aurora::StreamCamera::YCbCrFrame> frame); |
|
||||||
|
|
||||||
private: |
|
||||||
static quint32 yuvToArgb(qint32 y, qint32 rv, qint32 guv, qint32 bu, qint32 a); |
|
||||||
static void planarYuv420ToArgb(const uchar *y, |
|
||||||
const uchar *u, |
|
||||||
const uchar *v, |
|
||||||
qint32 yStride, |
|
||||||
qint32 uStride, |
|
||||||
qint32 vStride, |
|
||||||
qint32 uvPixelStride, |
|
||||||
quint32 *rgb, |
|
||||||
qint32 width, |
|
||||||
qint32 height); |
|
||||||
}; |
|
||||||
|
|
||||||
#endif /* TEXTURE_CAMERA_PIXELS_HELPER_H */ |
|
@ -1,218 +0,0 @@ |
|||||||
/**
|
|
||||||
* SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
* SPDX-License-Identifier: BSD-3-Clause |
|
||||||
*/ |
|
||||||
#include <camera_aurora/texture_camera.h> |
|
||||||
#include <camera_aurora/texture_camera_egl_helper.h> |
|
||||||
#include <camera_aurora/texture_camera_pixels_helper.h> |
|
||||||
|
|
||||||
#include <flutter/platform-data.h> |
|
||||||
#include <flutter/platform-methods.h> |
|
||||||
|
|
||||||
#include <QtCore> |
|
||||||
#include <QBuffer> |
|
||||||
|
|
||||||
TextureCamera::TextureCamera(TextureRegistrar *plugin, const CameraErrorHandler &onError) |
|
||||||
: m_plugin(plugin) |
|
||||||
, m_onError(onError) |
|
||||||
, m_variant(nullptr) |
|
||||||
, m_image(nullptr) |
|
||||||
, m_manager(StreamCameraManager()) |
|
||||||
{ |
|
||||||
TextureCameraEGLHelper::EGLInit(); |
|
||||||
} |
|
||||||
|
|
||||||
std::vector<Encodable> TextureCamera::GetAvailableCameras() |
|
||||||
{ |
|
||||||
std::vector<Encodable> cameras; |
|
||||||
auto count = m_manager->getNumberOfCameras(); |
|
||||||
|
|
||||||
for (int index = 0; index < count; index++) { |
|
||||||
Aurora::StreamCamera::CameraInfo info; |
|
||||||
if (m_manager->getCameraInfo(index, info)) { |
|
||||||
cameras.push_back(std::map<Encodable, Encodable>{ |
|
||||||
{"id", info.id}, |
|
||||||
{"name", info.name}, |
|
||||||
{"provider", info.provider}, |
|
||||||
{"mountAngle", info.mountAngle}, |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return cameras; |
|
||||||
} |
|
||||||
|
|
||||||
std::string TextureCamera::GetImageBase64() |
|
||||||
{ |
|
||||||
if (m_image && m_camera) { |
|
||||||
Aurora::StreamCamera::CameraInfo info; |
|
||||||
if (m_camera->getInfo(info)) { |
|
||||||
QBuffer qbuffer; |
|
||||||
qbuffer.open(QIODevice::WriteOnly); |
|
||||||
QImage rotatedImg = m_image->transformed(QMatrix().rotate(info.mountAngle)); |
|
||||||
rotatedImg.save(&qbuffer, "JPEG"); |
|
||||||
return qbuffer.data().toBase64().toStdString(); |
|
||||||
} |
|
||||||
} |
|
||||||
return ""; |
|
||||||
} |
|
||||||
|
|
||||||
std::map<Encodable, Encodable> TextureCamera::GetState() |
|
||||||
{ |
|
||||||
Aurora::StreamCamera::CameraInfo info; |
|
||||||
|
|
||||||
if (m_camera && m_camera->getInfo(info)) { |
|
||||||
|
|
||||||
auto orientation = static_cast<int>(PlatformMethods::GetOrientation()); |
|
||||||
|
|
||||||
return std::map<Encodable, Encodable>{ |
|
||||||
{"id", info.id}, |
|
||||||
{"textureId", m_textureId}, |
|
||||||
{"width", m_captureWidth}, |
|
||||||
{"height", m_captureHeight}, |
|
||||||
{"rotationCamera", info.mountAngle}, |
|
||||||
{"rotationDisplay", orientation}, |
|
||||||
{"error", m_error}, |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
return std::map<Encodable, Encodable>{ |
|
||||||
{"error", m_error} |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
bool TextureCamera::CreateCamera(std::string cameraName) |
|
||||||
{ |
|
||||||
if (auto count = m_manager->getNumberOfCameras()) { |
|
||||||
for (int index = 0; index < count; index++) { |
|
||||||
Aurora::StreamCamera::CameraInfo info; |
|
||||||
if (m_manager->getCameraInfo(index, info)) { |
|
||||||
if (info.id == cameraName) { |
|
||||||
m_camera = m_manager->openCamera(info.id); |
|
||||||
if (m_camera) { |
|
||||||
m_camera->setListener(this); |
|
||||||
return true; |
|
||||||
} else { |
|
||||||
Unregister(); |
|
||||||
SendError("Stream camera error open camera"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
void TextureCamera::SendError(std::string error) |
|
||||||
{ |
|
||||||
m_error = error; |
|
||||||
m_onError(); |
|
||||||
} |
|
||||||
|
|
||||||
std::map<Encodable, Encodable> TextureCamera::StartCapture(size_t width, size_t height) |
|
||||||
{ |
|
||||||
m_viewWidth = width; |
|
||||||
m_viewHeight = height; |
|
||||||
|
|
||||||
if (m_camera) { |
|
||||||
if (m_camera->captureStarted()) { |
|
||||||
m_camera->stopCapture(); |
|
||||||
} |
|
||||||
Aurora::StreamCamera::CameraInfo info; |
|
||||||
if (m_camera->getInfo(info)) { |
|
||||||
std::vector<Aurora::StreamCamera::CameraCapability> caps; |
|
||||||
|
|
||||||
if (m_manager->queryCapabilities(info.id, caps)) { |
|
||||||
for(unsigned int i = 0; i< caps.size(); i++) { |
|
||||||
if (width + height <= caps[i].width + caps[i].height || caps.size() == i - 1) { |
|
||||||
|
|
||||||
m_captureWidth = caps[i].width; |
|
||||||
m_captureHeight = caps[i].height; |
|
||||||
|
|
||||||
if (!m_camera->startCapture(caps[i])) { |
|
||||||
Unregister(); |
|
||||||
SendError("Stream camera error start capture"); |
|
||||||
} |
|
||||||
|
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return GetState(); |
|
||||||
} |
|
||||||
|
|
||||||
void TextureCamera::StopCapture() |
|
||||||
{ |
|
||||||
if (m_camera && m_camera->captureStarted()) { |
|
||||||
m_camera->stopCapture(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
std::map<Encodable, Encodable> TextureCamera::Register(std::string cameraName) |
|
||||||
{ |
|
||||||
m_textureId = m_plugin->RegisterTexture( |
|
||||||
[this]([[maybe_unused]] size_t width, [[maybe_unused]] size_t height) { |
|
||||||
return m_variant; |
|
||||||
}); |
|
||||||
|
|
||||||
if (CreateCamera(cameraName) && m_viewWidth != 0 && m_viewHeight != 0) { |
|
||||||
StartCapture(m_viewWidth, m_viewHeight); |
|
||||||
} |
|
||||||
|
|
||||||
return GetState(); |
|
||||||
} |
|
||||||
|
|
||||||
std::map<Encodable, Encodable> TextureCamera::Unregister() |
|
||||||
{ |
|
||||||
StopCapture(); |
|
||||||
|
|
||||||
m_error = ""; |
|
||||||
m_textureId = 0; |
|
||||||
m_captureWidth = 0; |
|
||||||
m_captureHeight = 0; |
|
||||||
m_variant = nullptr; |
|
||||||
m_camera = nullptr; |
|
||||||
|
|
||||||
m_plugin->UnregisterTexture(m_textureId); |
|
||||||
|
|
||||||
return GetState(); |
|
||||||
} |
|
||||||
|
|
||||||
void TextureCamera::onCameraFrame(std::shared_ptr<Aurora::StreamCamera::GraphicBuffer> buffer) |
|
||||||
{ |
|
||||||
if (buffer->handleType == Aurora::StreamCamera::HandleType::EGL && false) { |
|
||||||
// @todo not tested
|
|
||||||
auto eglImage = TextureCameraEGLHelper::EGLCreateImage(buffer); |
|
||||||
m_variant = new TextureVariant(FlutterEGLImage{ |
|
||||||
eglImage, |
|
||||||
buffer->width, |
|
||||||
buffer->height, |
|
||||||
}); |
|
||||||
} else { |
|
||||||
m_image = TextureCameraPixelsHelper::YUVtoARGB(buffer->mapYCbCr()); |
|
||||||
auto pixels = static_cast<uint8_t *>(m_image->bits()); |
|
||||||
m_variant = new TextureVariant(FlutterPixelBuffer{ |
|
||||||
pixels, |
|
||||||
buffer->width, |
|
||||||
buffer->height, |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
m_plugin->MarkTextureAvailable(m_textureId); |
|
||||||
} |
|
||||||
|
|
||||||
void TextureCamera::onCameraError(const std::string &errorDescription) |
|
||||||
{ |
|
||||||
Unregister(); |
|
||||||
SendError(errorDescription); |
|
||||||
} |
|
||||||
|
|
||||||
void TextureCamera::onCameraParameterChanged([[maybe_unused]] Aurora::StreamCamera::CameraParameter parameter, |
|
||||||
const std::string &value) |
|
||||||
{ |
|
||||||
std::cout << "onCameraParameterChanged: " << value << std::endl;
|
|
||||||
} |
|
@ -1,42 +0,0 @@ |
|||||||
/**
|
|
||||||
* SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
* SPDX-License-Identifier: BSD-3-Clause |
|
||||||
*/ |
|
||||||
#include <camera_aurora/texture_camera_egl_helper.h> |
|
||||||
#include <flutter/platform-methods.h> |
|
||||||
|
|
||||||
#include <GLES2/gl2.h> |
|
||||||
#include <GLES2/gl2ext.h> |
|
||||||
|
|
||||||
static PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR; |
|
||||||
static PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR; |
|
||||||
|
|
||||||
void TextureCameraEGLHelper::EGLInit() |
|
||||||
{ |
|
||||||
eglCreateImageKHR = reinterpret_cast<PFNEGLCREATEIMAGEKHRPROC>( |
|
||||||
eglGetProcAddress("eglCreateImageKHR")); |
|
||||||
eglDestroyImageKHR = reinterpret_cast<PFNEGLDESTROYIMAGEKHRPROC>( |
|
||||||
eglGetProcAddress("eglDestroyImageKHR")); |
|
||||||
} |
|
||||||
|
|
||||||
EGLImageKHR TextureCameraEGLHelper::EGLCreateImage( |
|
||||||
std::shared_ptr<Aurora::StreamCamera::GraphicBuffer> buffer) |
|
||||||
{ |
|
||||||
auto display = PlatformMethods::GetEGLDisplay(); |
|
||||||
auto context = PlatformMethods::GetEGLContext(); |
|
||||||
|
|
||||||
const void *handle = buffer->handle; |
|
||||||
GLint eglImgAttrs[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE, EGL_NONE}; |
|
||||||
return eglCreateImageKHR(display, |
|
||||||
context, |
|
||||||
EGL_NATIVE_BUFFER_ANDROID, |
|
||||||
(EGLClientBuffer) handle, |
|
||||||
eglImgAttrs); |
|
||||||
} |
|
||||||
|
|
||||||
void TextureCameraEGLHelper::EGLDestroyImage(EGLImageKHR image) |
|
||||||
{ |
|
||||||
auto display = PlatformMethods::GetEGLDisplay(); |
|
||||||
|
|
||||||
eglDestroyImageKHR(display, image); |
|
||||||
} |
|
@ -1,77 +0,0 @@ |
|||||||
/**
|
|
||||||
* SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
* SPDX-License-Identifier: BSD-3-Clause |
|
||||||
*/ |
|
||||||
#include <camera_aurora/texture_camera_pixels_helper.h> |
|
||||||
|
|
||||||
QImage *TextureCameraPixelsHelper::YUVtoARGB(std::shared_ptr<const Aurora::StreamCamera::YCbCrFrame> frame) |
|
||||||
{ |
|
||||||
QSize size(frame->width, frame->height); |
|
||||||
QImage *image = new QImage(size, QImage::Format_RGBA8888); |
|
||||||
|
|
||||||
planarYuv420ToArgb(frame->y, |
|
||||||
frame->cr, |
|
||||||
frame->cb, |
|
||||||
frame->yStride, |
|
||||||
frame->cStride, |
|
||||||
frame->cStride, |
|
||||||
frame->chromaStep, |
|
||||||
reinterpret_cast<quint32 *>(image->bits()), |
|
||||||
frame->width, |
|
||||||
frame->height); |
|
||||||
|
|
||||||
return image; |
|
||||||
} |
|
||||||
|
|
||||||
quint32 TextureCameraPixelsHelper::yuvToArgb(qint32 y, qint32 rv, qint32 guv, qint32 bu, qint32 a = 255) |
|
||||||
{ |
|
||||||
qint32 yy = (y - 16) * 298; |
|
||||||
|
|
||||||
return (a << 24) |
|
||||||
| qBound(0, (yy + rv) >> 8, 255) << 16 |
|
||||||
| qBound(0, (yy - guv) >> 8, 255) << 8 |
|
||||||
| qBound(0, (yy + bu) >> 8, 255); |
|
||||||
} |
|
||||||
|
|
||||||
void TextureCameraPixelsHelper::planarYuv420ToArgb(const uchar *y, |
|
||||||
const uchar *u, |
|
||||||
const uchar *v, |
|
||||||
qint32 yStride, |
|
||||||
qint32 uStride, |
|
||||||
qint32 vStride, |
|
||||||
qint32 uvPixelStride, |
|
||||||
quint32 *rgb, |
|
||||||
qint32 width, |
|
||||||
qint32 height) |
|
||||||
{ |
|
||||||
quint32 *rgb0 = rgb; |
|
||||||
quint32 *rgb1 = rgb + width; |
|
||||||
|
|
||||||
for (qint32 j = 0; j < height; j += 2) { |
|
||||||
const uchar *lineY0 = y; |
|
||||||
const uchar *lineY1 = y + yStride; |
|
||||||
const uchar *lineU = u; |
|
||||||
const uchar *lineV = v; |
|
||||||
|
|
||||||
for (qint32 i = 0; i < width; i += 2) { |
|
||||||
const qint32 uu = *lineU - 128; |
|
||||||
const qint32 vv = *lineV - 128; |
|
||||||
const qint32 rv = 409 * vv + 128; |
|
||||||
const qint32 guv = 100 * uu + 208 * vv + 128; |
|
||||||
const qint32 bu = 516 * uu + 128; |
|
||||||
|
|
||||||
lineU += uvPixelStride; |
|
||||||
lineV += uvPixelStride; |
|
||||||
*rgb0++ = yuvToArgb(*lineY0++, rv, guv, bu); |
|
||||||
*rgb0++ = yuvToArgb(*lineY0++, rv, guv, bu); |
|
||||||
*rgb1++ = yuvToArgb(*lineY1++, rv, guv, bu); |
|
||||||
*rgb1++ = yuvToArgb(*lineY1++, rv, guv, bu); |
|
||||||
} |
|
||||||
|
|
||||||
y += yStride << 1; |
|
||||||
u += uStride; |
|
||||||
v += vStride; |
|
||||||
rgb0 += width; |
|
||||||
rgb1 += width; |
|
||||||
} |
|
||||||
} |
|
@ -1,96 +0,0 @@ |
|||||||
// SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
// SPDX-License-Identifier: BSD-3-Clause |
|
||||||
import 'dart:async'; |
|
||||||
|
|
||||||
import 'package:camera_aurora/camera_viewfinder.dart'; |
|
||||||
import 'package:camera_platform_interface/camera_platform_interface.dart'; |
|
||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:flutter/services.dart'; |
|
||||||
|
|
||||||
import 'camera_aurora_platform_interface.dart'; |
|
||||||
|
|
||||||
class CameraAurora extends CameraPlatform { |
|
||||||
/// Registers this class as the default instance of [CameraPlatform]. |
|
||||||
static void registerWith() { |
|
||||||
CameraPlatform.instance = CameraAurora(); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Future<List<CameraDescription>> availableCameras() => |
|
||||||
CameraAuroraPlatform.instance.availableCameras(); |
|
||||||
|
|
||||||
@override |
|
||||||
Future<void> initializeCamera( |
|
||||||
int cameraId, { |
|
||||||
ImageFormatGroup imageFormatGroup = ImageFormatGroup.unknown, |
|
||||||
}) async {} |
|
||||||
|
|
||||||
@override |
|
||||||
Future<int> createCamera( |
|
||||||
CameraDescription cameraDescription, |
|
||||||
ResolutionPreset? resolutionPreset, { |
|
||||||
bool enableAudio = false, |
|
||||||
}) async { |
|
||||||
return (await CameraAuroraPlatform.instance |
|
||||||
.createCamera(cameraDescription.name)) |
|
||||||
.textureId; |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Future<void> dispose(int cameraId) => CameraAuroraPlatform.instance.dispose(); |
|
||||||
|
|
||||||
@override |
|
||||||
Future<XFile> takePicture(int cameraId) => |
|
||||||
CameraAuroraPlatform.instance.takePicture(cameraId); |
|
||||||
|
|
||||||
@override |
|
||||||
Future<void> startVideoRecording(int cameraId, |
|
||||||
{Duration? maxVideoDuration}) => |
|
||||||
CameraAuroraPlatform.instance.startVideoRecording(cameraId); |
|
||||||
|
|
||||||
@override |
|
||||||
Future<XFile> stopVideoRecording(int cameraId) => |
|
||||||
CameraAuroraPlatform.instance.stopVideoRecording(cameraId); |
|
||||||
|
|
||||||
@override |
|
||||||
Future<void> pauseVideoRecording(int cameraId) => |
|
||||||
CameraAuroraPlatform.instance.pauseVideoRecording(cameraId); |
|
||||||
|
|
||||||
@override |
|
||||||
Future<void> resumeVideoRecording(int cameraId) => |
|
||||||
CameraAuroraPlatform.instance.resumeVideoRecording(cameraId); |
|
||||||
|
|
||||||
@override |
|
||||||
Stream<CameraInitializedEvent> onCameraInitialized(int cameraId) async* { |
|
||||||
yield CameraInitializedEvent( |
|
||||||
cameraId, |
|
||||||
0, |
|
||||||
0, |
|
||||||
ExposureMode.auto, |
|
||||||
true, |
|
||||||
FocusMode.auto, |
|
||||||
true, |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Stream<DeviceOrientationChangedEvent> onDeviceOrientationChanged() async* { |
|
||||||
yield const DeviceOrientationChangedEvent(DeviceOrientation.landscapeLeft); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Widget buildPreview(int cameraId) { |
|
||||||
if (cameraId != 0) { |
|
||||||
return LayoutBuilder(builder: ( |
|
||||||
BuildContext context, |
|
||||||
BoxConstraints constraints, |
|
||||||
) { |
|
||||||
return CameraViewfinder( |
|
||||||
width: constraints.maxWidth, |
|
||||||
height: constraints.maxHeight, |
|
||||||
); |
|
||||||
}); |
|
||||||
} |
|
||||||
return const SizedBox.shrink(); |
|
||||||
} |
|
||||||
} |
|
@ -1,159 +0,0 @@ |
|||||||
// SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
// SPDX-License-Identifier: BSD-3-Clause |
|
||||||
import 'dart:convert'; |
|
||||||
|
|
||||||
import 'package:camera_platform_interface/camera_platform_interface.dart'; |
|
||||||
import 'package:flutter/foundation.dart'; |
|
||||||
import 'package:flutter/services.dart'; |
|
||||||
|
|
||||||
import 'camera_aurora_platform_interface.dart'; |
|
||||||
import 'camera_data.dart'; |
|
||||||
|
|
||||||
enum CameraAuroraMethods { |
|
||||||
availableCameras, |
|
||||||
createCamera, |
|
||||||
dispose, |
|
||||||
startCapture, |
|
||||||
stopCapture, |
|
||||||
takePicture, |
|
||||||
startVideoRecording, |
|
||||||
stopVideoRecording, |
|
||||||
pauseVideoRecording, |
|
||||||
resumeVideoRecording, |
|
||||||
} |
|
||||||
|
|
||||||
enum CameraAuroraEvents { |
|
||||||
cameraAuroraStateChanged, |
|
||||||
} |
|
||||||
|
|
||||||
/// An implementation of [CameraAuroraPlatform] that uses method channels. |
|
||||||
class MethodChannelCameraAurora extends CameraAuroraPlatform { |
|
||||||
/// The method channel used to interact with the native platform. |
|
||||||
@visibleForTesting |
|
||||||
final methodsChannel = const MethodChannel('camera_aurora'); |
|
||||||
|
|
||||||
@override |
|
||||||
Stream<CameraState> onChangeState() async* { |
|
||||||
await for (final data |
|
||||||
in EventChannel(CameraAuroraEvents.cameraAuroraStateChanged.name) |
|
||||||
.receiveBroadcastStream()) { |
|
||||||
yield CameraState.fromJson(data); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Future<List<CameraDescription>> availableCameras() async { |
|
||||||
final List<CameraDescription> result = []; |
|
||||||
|
|
||||||
final cameras = await methodsChannel.invokeMethod<List<dynamic>?>( |
|
||||||
CameraAuroraMethods.availableCameras.name) ?? |
|
||||||
[]; |
|
||||||
|
|
||||||
for (final camera in cameras) { |
|
||||||
final data = camera['id'].split(':'); |
|
||||||
var direction = CameraLensDirection.external; |
|
||||||
|
|
||||||
if (data[1].toString().contains('rear')) { |
|
||||||
direction = CameraLensDirection.back; |
|
||||||
} else if (data[1].toString().contains('front')) { |
|
||||||
direction = CameraLensDirection.front; |
|
||||||
} |
|
||||||
|
|
||||||
result.add(CameraDescription( |
|
||||||
name: camera['id'], |
|
||||||
lensDirection: direction, |
|
||||||
sensorOrientation: camera['mountAngle'], |
|
||||||
)); |
|
||||||
} |
|
||||||
|
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Future<CameraState> createCamera(String cameraName) async { |
|
||||||
final data = await methodsChannel |
|
||||||
.invokeMethod<Map<dynamic, dynamic>?>('createCamera', { |
|
||||||
'cameraName': cameraName, |
|
||||||
}); |
|
||||||
return CameraState.fromJson(data ?? {}); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Future<void> startCapture(double width, double height) async { |
|
||||||
await methodsChannel |
|
||||||
.invokeMethod<Object?>(CameraAuroraMethods.startCapture.name, { |
|
||||||
'width': width.round(), |
|
||||||
'height': height.round(), |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Future<void> stopCapture() async { |
|
||||||
await methodsChannel |
|
||||||
.invokeMethod<Object?>(CameraAuroraMethods.stopCapture.name, {}); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Future<void> dispose() async { |
|
||||||
await methodsChannel |
|
||||||
.invokeMethod<Object?>(CameraAuroraMethods.dispose.name); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Future<XFile> takePicture(int cameraId) async { |
|
||||||
final image = await methodsChannel.invokeMethod<String?>( |
|
||||||
CameraAuroraMethods.takePicture.name, |
|
||||||
{'cameraId': cameraId}, |
|
||||||
); |
|
||||||
final bytes = base64Decode(image!); |
|
||||||
return XFile.fromData( |
|
||||||
bytes, |
|
||||||
name: 'temp.jpg', |
|
||||||
mimeType: 'image/jpeg', |
|
||||||
length: bytes.length, |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Future<void> startVideoRecording(int cameraId, |
|
||||||
{Duration? maxVideoDuration}) async { |
|
||||||
await methodsChannel |
|
||||||
.invokeMethod<Object?>(CameraAuroraMethods.startVideoRecording.name, { |
|
||||||
'cameraId': cameraId, |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Future<XFile> stopVideoRecording(int cameraId) async { |
|
||||||
await methodsChannel |
|
||||||
.invokeMethod<Object?>(CameraAuroraMethods.stopVideoRecording.name, { |
|
||||||
'cameraId': cameraId, |
|
||||||
}); |
|
||||||
|
|
||||||
// @todo Save empty data |
|
||||||
return XFile.fromData( |
|
||||||
Uint8List(0), |
|
||||||
name: 'file_name.mp4', |
|
||||||
|
|
||||||
/// @todo not set |
|
||||||
mimeType: 'video/mp4', |
|
||||||
length: 0, |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Future<void> pauseVideoRecording(int cameraId) async { |
|
||||||
await methodsChannel |
|
||||||
.invokeMethod<Object?>(CameraAuroraMethods.pauseVideoRecording.name, { |
|
||||||
'cameraId': cameraId, |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Future<void> resumeVideoRecording(int cameraId) async { |
|
||||||
await methodsChannel |
|
||||||
.invokeMethod<Object?>(CameraAuroraMethods.resumeVideoRecording.name, { |
|
||||||
'cameraId': cameraId, |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
@ -1,77 +0,0 @@ |
|||||||
// SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
// SPDX-License-Identifier: BSD-3-Clause |
|
||||||
import 'package:camera_platform_interface/camera_platform_interface.dart'; |
|
||||||
import 'package:plugin_platform_interface/plugin_platform_interface.dart'; |
|
||||||
|
|
||||||
import 'camera_aurora_method_channel.dart'; |
|
||||||
import 'camera_data.dart'; |
|
||||||
|
|
||||||
abstract class CameraAuroraPlatform extends PlatformInterface { |
|
||||||
/// Constructs a CameraAuroraPlatform. |
|
||||||
CameraAuroraPlatform() : super(token: _token); |
|
||||||
|
|
||||||
static final Object _token = Object(); |
|
||||||
|
|
||||||
static CameraAuroraPlatform _instance = MethodChannelCameraAurora(); |
|
||||||
|
|
||||||
/// The default instance of [CameraAuroraPlatform] to use. |
|
||||||
/// |
|
||||||
/// Defaults to [MethodChannelCameraAurora]. |
|
||||||
static CameraAuroraPlatform get instance => _instance; |
|
||||||
|
|
||||||
/// Platform-specific implementations should set this with their own |
|
||||||
/// platform-specific class that extends [CameraAuroraPlatform] when |
|
||||||
/// they register themselves. |
|
||||||
static set instance(CameraAuroraPlatform instance) { |
|
||||||
PlatformInterface.verifyToken(instance, _token); |
|
||||||
_instance = instance; |
|
||||||
} |
|
||||||
|
|
||||||
Stream<CameraState> onChangeState() { |
|
||||||
throw UnimplementedError('onChangeState() has not been implemented.'); |
|
||||||
} |
|
||||||
|
|
||||||
/// Camera |
|
||||||
Future<List<CameraDescription>> availableCameras() { |
|
||||||
throw UnimplementedError('availableCameras() has not been implemented.'); |
|
||||||
} |
|
||||||
|
|
||||||
Future<void> startCapture(double width, double height) { |
|
||||||
throw UnimplementedError('startCapture() has not been implemented.'); |
|
||||||
} |
|
||||||
|
|
||||||
Future<void> stopCapture() { |
|
||||||
throw UnimplementedError('startCapture() has not been implemented.'); |
|
||||||
} |
|
||||||
|
|
||||||
Future<CameraState> createCamera(String cameraName) { |
|
||||||
throw UnimplementedError('createCamera() has not been implemented.'); |
|
||||||
} |
|
||||||
|
|
||||||
Future<void> dispose() { |
|
||||||
throw UnimplementedError('dispose() has not been implemented.'); |
|
||||||
} |
|
||||||
|
|
||||||
// make photo |
|
||||||
Future<XFile> takePicture(int cameraId) { |
|
||||||
throw UnimplementedError('takePicture() has not been implemented.'); |
|
||||||
} |
|
||||||
|
|
||||||
// record video |
|
||||||
Future<void> startVideoRecording(int cameraId) { |
|
||||||
throw UnimplementedError('startVideoRecording() has not been implemented.'); |
|
||||||
} |
|
||||||
|
|
||||||
Future<XFile> stopVideoRecording(int cameraId) { |
|
||||||
throw UnimplementedError('stopVideoRecording() has not been implemented.'); |
|
||||||
} |
|
||||||
|
|
||||||
Future<void> pauseVideoRecording(int cameraId) { |
|
||||||
throw UnimplementedError('pauseVideoRecording() has not been implemented.'); |
|
||||||
} |
|
||||||
|
|
||||||
Future<void> resumeVideoRecording(int cameraId) { |
|
||||||
throw UnimplementedError( |
|
||||||
'resumeVideoRecording() has not been implemented.'); |
|
||||||
} |
|
||||||
} |
|
@ -1,38 +0,0 @@ |
|||||||
// SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
// SPDX-License-Identifier: BSD-3-Clause |
|
||||||
|
|
||||||
enum OrientationEvent { |
|
||||||
undefined, |
|
||||||
portrait, |
|
||||||
landscape, |
|
||||||
portraitFlipped, |
|
||||||
landscapeFlipped, |
|
||||||
} |
|
||||||
|
|
||||||
class CameraState { |
|
||||||
CameraState.fromJson(Map<dynamic, dynamic> json) |
|
||||||
: id = json['id'] ?? "", |
|
||||||
textureId = json['textureId'] ?? -1, |
|
||||||
width = (json['width'] ?? 0).toDouble(), |
|
||||||
height = (json['height'] ?? 0).toDouble(), |
|
||||||
rotationCamera = json['rotationCamera'] ?? 0, |
|
||||||
rotationDisplay = json['rotationDisplay'] ?? 0, |
|
||||||
error = json['error'] ?? ''; |
|
||||||
|
|
||||||
final String id; |
|
||||||
final int textureId; |
|
||||||
final double width; |
|
||||||
final double height; |
|
||||||
final int rotationCamera; |
|
||||||
final int rotationDisplay; |
|
||||||
final String error; |
|
||||||
|
|
||||||
bool isNotEmpty() => textureId != -1; |
|
||||||
|
|
||||||
bool hasError() => error.isNotEmpty; |
|
||||||
|
|
||||||
@override |
|
||||||
String toString() { |
|
||||||
return '{id: $id, textureId: $textureId, width: $width, height: $height, rotationCamera: $rotationCamera, rotationDisplay: $rotationDisplay, error: $error}'; |
|
||||||
} |
|
||||||
} |
|
@ -1,99 +0,0 @@ |
|||||||
// SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
// SPDX-License-Identifier: BSD-3-Clause |
|
||||||
import 'package:camera_aurora/camera_aurora_platform_interface.dart'; |
|
||||||
import 'package:flutter/material.dart'; |
|
||||||
|
|
||||||
import 'camera_data.dart'; |
|
||||||
|
|
||||||
class CameraViewfinder extends StatefulWidget { |
|
||||||
const CameraViewfinder({ |
|
||||||
super.key, |
|
||||||
required this.width, |
|
||||||
required this.height, |
|
||||||
}); |
|
||||||
|
|
||||||
final double width; |
|
||||||
final double height; |
|
||||||
|
|
||||||
@override |
|
||||||
State<CameraViewfinder> createState() => _CameraViewfinderState(); |
|
||||||
} |
|
||||||
|
|
||||||
class _CameraViewfinderState extends State<CameraViewfinder> { |
|
||||||
CameraState _cameraState = CameraState.fromJson({}); |
|
||||||
|
|
||||||
@override |
|
||||||
initState() { |
|
||||||
super.initState(); |
|
||||||
CameraAuroraPlatform.instance.startCapture(widget.width, widget.height); |
|
||||||
CameraAuroraPlatform.instance.onChangeState().listen((event) { |
|
||||||
if (mounted) { |
|
||||||
setState(() { |
|
||||||
_cameraState = event; |
|
||||||
}); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
void dispose() { |
|
||||||
super.dispose(); |
|
||||||
CameraAuroraPlatform.instance.stopCapture(); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(BuildContext context) { |
|
||||||
if (_cameraState.hasError()) { |
|
||||||
return Center( |
|
||||||
child: Text( |
|
||||||
'Error: ${_cameraState.error}', |
|
||||||
style: |
|
||||||
const TextStyle(fontWeight: FontWeight.bold, color: Colors.white), |
|
||||||
), |
|
||||||
); |
|
||||||
} else if (_cameraState.isNotEmpty()) { |
|
||||||
int turn = 0; |
|
||||||
|
|
||||||
switch (_cameraState.rotationDisplay) { |
|
||||||
case 0: |
|
||||||
turn = _cameraState.id.contains('front') ? -1 : 1; |
|
||||||
break; |
|
||||||
case 90: |
|
||||||
turn = 0; |
|
||||||
break; |
|
||||||
case 180: |
|
||||||
turn = _cameraState.id.contains('front') ? 1 : -1; |
|
||||||
break; |
|
||||||
default: // 270 |
|
||||||
turn = 2; |
|
||||||
} |
|
||||||
|
|
||||||
double height = 10; |
|
||||||
double width = 10; |
|
||||||
|
|
||||||
if (_cameraState.height != 0 && _cameraState.width != 0) { |
|
||||||
if (_cameraState.rotationDisplay == 90 || |
|
||||||
_cameraState.rotationDisplay == 270) { |
|
||||||
width = widget.width * _cameraState.height / _cameraState.width; |
|
||||||
height = widget.height * _cameraState.width / _cameraState.height; |
|
||||||
} else { |
|
||||||
width = _cameraState.height * widget.height / _cameraState.width; |
|
||||||
height = _cameraState.width * widget.width / _cameraState.height; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return RotatedBox( |
|
||||||
quarterTurns: turn, |
|
||||||
child: SizedBox( |
|
||||||
width: height, // height |
|
||||||
height: width, // widht |
|
||||||
child: Opacity( |
|
||||||
opacity: _cameraState.height == 0 ? 0 : 1, |
|
||||||
child: Texture(textureId: _cameraState.textureId), |
|
||||||
), |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
return const SizedBox.shrink(); |
|
||||||
} |
|
||||||
} |
|
@ -1,30 +0,0 @@ |
|||||||
# SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru> |
|
||||||
# SPDX-License-Identifier: BSD-3-Clause |
|
||||||
|
|
||||||
name: camera_aurora |
|
||||||
description: Aurora implementation of the camera plugin. |
|
||||||
version: 0.0.1 |
|
||||||
|
|
||||||
environment: |
|
||||||
sdk: '>=2.18.6 <4.0.0' |
|
||||||
flutter: ">=3.0.0" |
|
||||||
|
|
||||||
dependencies: |
|
||||||
flutter: |
|
||||||
sdk: flutter |
|
||||||
plugin_platform_interface: ^2.0.2 |
|
||||||
camera_platform_interface: ^2.6.0 |
|
||||||
image: ^4.1.3 |
|
||||||
async: ^2.9.0 |
|
||||||
|
|
||||||
dev_dependencies: |
|
||||||
flutter_test: |
|
||||||
sdk: flutter |
|
||||||
flutter_lints: ^2.0.0 |
|
||||||
|
|
||||||
flutter: |
|
||||||
plugin: |
|
||||||
platforms: |
|
||||||
aurora: |
|
||||||
pluginClass: CameraAuroraPlugin |
|
||||||
dartPluginClass: CameraAurora |
|
Loading…
Reference in new issue