เนื้อหานี้จะขอต่อยอดจากตอนที่แล้ว เกี่ยวกับการจัดการไฟล์
ซึ่งตอนที่แล้วเราได้รู้จักการจัดการเกี่ยวกับไฟล์เสียงหรือ auido ไฟล์
เนื้อหานี้เราจะมาดูเกี่ยวกับการจัดการไฟล์รูปภาพ และไฟล์วิดีโอ ซึ่ง
สำหรับไฟล์รูปภาพ เราได้มีเนื้อหาบ้างแล้วเกี่ยวกับการแสดงรูปในแบบ
Photo Gallery นั่นคือนำข้อมูลรูปภาพจากไฟล์ข้อมูลที่เรามีอยู่แล้ว
มาแสดง แต่ในตอนนี้เราจะพูดถึงการบันทึกหรือภารถ่ายภาพและบันทึก
เป็นไฟล์ในแอป
ทบทวนเกี่ยวกับ Photo Gallery ได้ที่
สร้าง Photo Gallery จาก API อย่างง่าย ใน Flutter http://niik.in/1071
นอกจากรูปภาพหรือไฟล์ภาพแล้ว เราจะพูดถึงการบันทึกวิดีโอหรือไฟล์ video ซึ่งเนื้อหา
เกี่ยวกับการแสดงวิดีโอนั้นเราพูดไปแล้วในบทความด้านล่าง
การจัดการและใช้งาน เกี่ยวกับ Video ใน Flutter ตอนที่ 1 http://niik.in/1113
ประยุกต์แสดงคลิป Video คล้าย TikTok ใน Flutter ตอนที่ 2 http://niik.in/1114
ซึ่งเป็นการนำข้อมูลไฟล์วิดีโอที่เรามีอยู่แล้วมาใช้งานหรือแสดง แต่ในบทความนี้เราจะพูดถึงการบันทึก
ไฟล์วิดีโอผ่านกล้องหรือ camera เพื่อใช้งานในแอป และแน่นอนว่าเราจะมีการนำโค้ดจากบทความ
ที่เกี่ยวข้องมาใช้งาน เช่นการเปิดแสดงรูปภาพที่เราบันทึกไปแล้ว หรือการเล่นไฟล์วิดีโอที่เราได้บันทึก
ไป แบบนี้เป็นต้น
การกำหนดการขอสิทธิ์เข้าถึงการใช้งานข้อมูล
ก่อนที่จะลงรายละเอียด สิ่งแรกที่เราต้องทำคือการขอสิทธิ์การเข้าถึงส่วนจัดการต่างๆ ในบทความนี้
จะมีเนื้อหาจากตอนที่แล้วด้วย ก็จะมีทั้ง การบันทึกเสียง การใช้กล้อง การอ่านไฟล์ เขียนไฟล์ ให้เรา
กำหนดดังนี้
ไฟล์ android > app > src > main > AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"/>
<!-- For Android 13+ -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<application ....
....
</manifest>
นอกจากการขอสิทธิ์ต่างเหล่านี้ในไฟล์ AndroidManifest.xml ในการกำหนดนี้แล้ว เรายังอาจจะ
ต้องกำหนดการขอใช้สิทธิ์ในการใช้งานตอนรันแอป (runtime permission) เพิ่มด้วย
ติดตั้ง package ที่จำเป็นเพิ่มเติม ตามรายการด้านล่าง
แพ็กเก็จที่จำเป็นต้องติดตั้งเพิ่มเติม สำหรับการทำงานมีดังนี้
font_awesome_flutter: ^10.7.0 path_provider: ^2.1.4 record: ^5.1.2 permission_handler: ^11.3.1 audioplayers: ^6.1.0 http: ^1.2.2 intl: ^0.17.0 file_picker: ^8.1.2 camera: ^0.11.0+2 image_picker: ^1.1.2 video_player: ^2.9.1
ในการบันทึกวิดีโอหรือรูปภาพด้วยกล้อง เราจะแนะนำการใช้งานทั้งสอง package คือ image_picker
และ camera โดยตัว image_picker จะมีรูปแบบการใช้งานหรือการตั้งค่าที่ง่ายกว่า รองรับทั้งการ
ถ่ายรูปด้วยกล้องและการดึงรูปภาพจาก gallery ในขณะที่ camera จะรองรับการปรับแต่งที่มากกว่า
แต่ก็จะมาพร้อมกับการตั้งค่าที่ซับซ้อนขึ้น ดังนั้น เราเลือกใช้ได้ตามความเหมาะสมกับการใช้งาน
การใช้งาน Image_Picker โหลดหรือบันทึกไฟล์ภาพและวิดีโอ
ในตัวอย่างเราจะสร้างไฟล์หน้าสำหรับใช้งาน image_picker คำอธิบายแสดงในโค้ด โดยในโค้ดจะ
เป็นการใช้งานฟังก์ชั่นเกี่ยวกับการโหลดไฟล์ภาพหรือไฟล์วิดีโอจาก gallery และการบันทึกไฟล์ หรือ
ไฟล์วิดีโอจากกล้อง camera ให้เป็นแนวทางนำไปปรับใช้งานได้
ไฟล์ imagepicker.dart
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
class Picker extends StatefulWidget {
static const routeName = '/picker';
const Picker({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return _PickerState();
}
}
class _PickerState extends State<Picker> {
final ImagePicker _picker = ImagePicker();
XFile? _videoFile;
XFile? _imageFile; // ตัวแปรสำหรับเก็บไฟล์รูปภาพที่เลือก
// ฟังก์ชันสำหรับการเลือกวิดีโอ
Future<void> pickVideo(ImageSource source) async {
XFile? video = await _picker.pickVideo(source: source);
setState(() {
_videoFile = video;
});
}
// ฟังก์ชันสำหรับการเลือกภาพ
Future<void> pickImage(ImageSource source) async {
XFile? image = await _picker.pickImage(source: source);
setState(() {
_imageFile = image;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Image & Video Picker'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Image & Video Picker Screen'),
// แสดงภาพที่เลือก
_imageFile != null
? Image.file(
File(_imageFile!.path),
height: 200,
)
: Text("No image selected"),
// แสดงวิดีโอที่เลือก
_videoFile != null
? Text("Video saved to: ${_videoFile!.path}")
: Text("No video selected"),
// ปุ่มสำหรับเลือกภาพจากแกลเลอรี
ElevatedButton(
onPressed: () => pickImage(ImageSource.gallery),
child: Text('Pick Image from Gallery'),
),
// ปุ่มสำหรับเลือกภาพจากกล้อง
ElevatedButton(
onPressed: () => pickImage(ImageSource.camera),
child: Text('Capture Image from Camera'),
),
// ปุ่มสำหรับเลือกวิดีโอจากแกลเลอรี
ElevatedButton(
onPressed: () => pickVideo(ImageSource.gallery),
child: Text('Pick Video from Gallery'),
),
// ปุ่มสำหรับบันทึกวิดีโอ
ElevatedButton(
onPressed: () => pickVideo(ImageSource.camera),
child: Text('Record Video from Camera'),
),
],
)),
);
}
}
ผลลัพธ์ที่ได้

จากตัวอย่างโค้ด จะเห็นว่ามีวิธีการเรียกใช้งานที่ค่อนข้างง่ายไม่ยุ่งยาก หลักการงานคือ ถ้าเราเลือกเป็น
การโหลดไฟล์ภาพหรือวิดีโอจาก gallery หรือจากในเครื่อง ข้อมูลไฟล์ก็จะถูกนำไปเก็บในโฟลเดอร์
แคช (cache) โดยมีโฟลเดอร์สำหรับแคซไฟล์เก็บไฟล์เหล่านั้นไว้อีกที ในขณะที่ถ้าเราเลือกเป้นการ
ถ่ายรูปหรือวิดีโอจากกล้อง ก็จะเป็นการการเก็บไว้ในโฟลเดอร์แคชเช่นกัน แต่ไม่มีโฟลเดอร์เก็บไฟล์นั้น
ในตัวอย่าง ถ้าเป็นไฟล์รูป เราสามารถใช้การตรวจสอบชื่อไฟล์และแสดงรูปได้ทันที ในขณะที่ถ้าเป็นไฟล์
วิดีโอ เราจะแสดงที่ path หรือตำแหน่งของไฟล์ที่ถูกบันทึกไว้ ซึ่งนี้ทำให้เราเห็นแนวทางการทำงานและ
การจัดเก็บไฟล์เท่านั้น หากนำไปใช้งานจริง เราอาจจะต้องนำไฟล์เหล่านี้ไปไว้ที่ app_flutter เพื่อนำ
ไปใช้งานในแอปต่อไป เพราะไฟล์ในโฟลเดอร์แคชจะเป็นไฟล์ชั่วคราว อาจจะโดนลบจากระบบในตอนไหน
ก็เป็นได้
เราจะปรับแต่งโค้ดและแยกเป็นอีกตัวอย่างเพื่อให้เปรียบเทียบกัน โดยตัวแก้ไข เราจะมีการคัดลอกไฟล์
จากแคชมายังโฟลเดอร์ app_flutter หรือโฟลเดอร์ข้อมูลของแอป และมีการกำหนดชื่อไฟล์ใหม่ใน
รูปแบบ video_ddMMyyyyTHHmmss และ image_ddMMyyyyTHHmmss ดังโค้ดด้านล่าง
ไฟล์ imagepicker.dart (ปรับปรุง)
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:path_provider/path_provider.dart';
import 'package:intl/intl.dart';
class Picker extends StatefulWidget {
static const routeName = '/picker';
const Picker({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return _PickerState();
}
}
class _PickerState extends State<Picker> {
final ImagePicker _picker = ImagePicker();
XFile? _videoFile;
XFile? _imageFile; // ตัวแปรสำหรับเก็บไฟล์รูปภาพที่เลือก
Directory? _currentFolder;
@override
void initState() {
super.initState();
_getAppDirectory();
}
// ฟังก์ชันเพื่อให้ได้ path ของโฟลเดอร์แอป
Future<void> _getAppDirectory() async {
_currentFolder = await getApplicationDocumentsDirectory();
}
// ฟังก์ชันสำหรับการเลือกวิดีโอ
Future<void> pickVideo(ImageSource source) async {
XFile? video = await _picker.pickVideo(source: source);
try {
if (await Permission.storage.request().isGranted) {
if (video != null) {
// สร้างชื่อและ path ใหม่สำหรับไฟล์วิดีโอ
String timestamp =
DateFormat('ddMMyyyyTHHmmss').format(DateTime.now());
String newFileName = 'video_$timestamp';
String newPath =
"${_currentFolder!.path}/$newFileName${video.name.substring(video.name.lastIndexOf('.'))}";
// กำหนด file object
var cacheFile = File(video.path);
// คัดลอกไฟล์จาก cache ไปยัง path ใหม่ใน app
await cacheFile.copy(newPath);
// ใช้ File เพื่อเข้าถึงข้อมูลเกี่ยวกับไฟล์
File newFile = File(newPath);
int fileSize = await newFile.length(); // ใช้ await เพื่อรับขนาดไฟล์
// แสดงข้อมูลไฟล์
print("Video file copied to: $newPath");
print("File name: ${video.name}");
print("File size: $fileSize bytes");
print("File path: ${video.path}");
// โหลดข้อมูลใหม่อีกครั้งถ้าต้องการ
setState(() {
_videoFile = video; // เก็บไฟล์วิดีโอใน state
});
} else {
print("No video selected.");
}
} else {
print("Storage permission is denied.");
}
} catch (e) {
print("Error occurred: $e");
}
}
// ฟังก์ชันสำหรับการเลือกภาพ
Future<void> pickImage(ImageSource source) async {
XFile? image = await _picker.pickImage(source: source);
try {
if (await Permission.storage.request().isGranted) {
if (image != null) {
// สร้าง ชื่อใหม่ และ path ใหม่สำหรับไฟล์ภาพ
String timestamp =
DateFormat('ddMMyyyyTHHmmss').format(DateTime.now());
String newFileName = 'image_$timestamp';
String newPath =
"${_currentFolder!.path}/$newFileName${image.name.substring(image.name.lastIndexOf('.'))}";
// กำหนด file object
var cacheFile = File(image.path);
// คัดลอกไฟล์จาก cache ไปยัง path ใหม่ใน app
await cacheFile.copy(newPath);
// ใช้ File เพื่อเข้าถึงข้อมูลเกี่ยวกับไฟล์
File newFile = File(newPath);
int fileSize = await newFile.length(); // ใช้ await เพื่อรับขนาดไฟล์
// แสดงข้อมูลไฟล์
print("Image file copied to: $newPath");
print("File name: ${image.name}");
print("File size: $fileSize bytes");
print("File path: ${image.path}");
// โหลดข้อมูลใหม่อีกครั้งถ้าต้องการ
setState(() {
_imageFile = image; // เก็บไฟล์ภาพใน state
});
} else {
print("No image selected.");
}
} else {
print("Storage permission is denied.");
}
} catch (e) {
print("Error occurred: $e");
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Image & Video Picker'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Image & Video Picker Screen'),
// แสดงภาพที่เลือก
_imageFile != null
? Image.file(
File(_imageFile!.path),
height: 200,
)
: Text("No image selected"),
// แสดงวิดีโอที่เลือก
_videoFile != null
? Text("Video saved to: ${_videoFile!.path}")
: Text("No video selected"),
// ปุ่มสำหรับเลือกภาพจากแกลเลอรี
ElevatedButton(
onPressed: () => pickImage(ImageSource.gallery),
child: Text('Pick Image from Gallery'),
),
// ปุ่มสำหรับเลือกภาพจากกล้อง
ElevatedButton(
onPressed: () => pickImage(ImageSource.camera),
child: Text('Capture Image from Camera'),
),
// ปุ่มสำหรับเลือกวิดีโอจากแกลเลอรี
ElevatedButton(
onPressed: () => pickVideo(ImageSource.gallery),
child: Text('Pick Video from Gallery'),
),
// ปุ่มสำหรับบันทึกวิดีโอ
ElevatedButton(
onPressed: () => pickVideo(ImageSource.camera),
child: Text('Record Video from Camera'),
),
],
)),
);
}
}
ผลลัพธ์ที่ได้

จะเห็นว่าไฟล์ที่เราทำการเลือกเข้ามาหรือถ่ายด้วยกล้องจะถูกคัดลอกมาไว้ในโฟลเดอร์ app_flutter
พร้อมใช้รูปแบบชื่อไฟล์ใหม่ตามที่เราต้องการ
การใช้งาน Camera บันทึกไฟล์ภาพและวิดีโอ
ส่วนของการใช้งาน camera plugin ถึงแม้จะรองรับการจัดการที่หลากหลายแต่ก็ไม่รองรับการ
เลือกหรือเรียกดูไฟล์จาก gallery ได้ จะเป็นลักษณะการใช้งานจากกล้องเป็นหลัก คือการบันทึกรูปภาพ
หรือการบันทึกไฟล์วิดีโอจากกล้อง โค้ดด้านล่างเป็นตัวอย่างอย่างง่ายในการใช้งาน ที่เราสามารถแสดง
พรีวิวหน้าจอกล้อง โดยสามารถกดเพื่อบันทึกวิดีโอ หรือถ่ายเป็นไฟล์รูปภาพแล้วบันทึกไว้ในโฟลเดอร์
app_flutter คำอธิบายแสดงในโค้ด
ไฟล์ camera.dart
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:camera/camera.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:intl/intl.dart';
class Camera extends StatefulWidget {
static const routeName = '/camara';
const Camera({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return _CameraState();
}
}
class _CameraState extends State<Camera> {
// ส่วนควบคุมกล้อง
CameraController? _cameraController;
List<CameraDescription>? cameras;
bool isRecording = false;
String? _imagePath;
String? _videoPath;
Directory? _currentFolder;
@override
void initState() {
super.initState();
_getAppDirectory();
initializeCamera();
}
// ฟังก์ชันเพื่อให้ได้ path ของโฟลเดอร์แอป
Future<void> _getAppDirectory() async {
_currentFolder = await getApplicationDocumentsDirectory();
}
// เตรียมความพร้อมในส่วนของกล้อง
Future<void> initializeCamera() async {
// Request camera permission
final cameraStatus = await Permission.camera.request();
if (cameraStatus.isGranted) {
cameras = await availableCameras();
_cameraController = CameraController(cameras![0], ResolutionPreset.high);
await _cameraController!.initialize();
setState(() {});
} else {
// Handle the case when the user denies permission
print("Camera permission denied.");
}
}
// ฟังก์ชั่นทำงานการบันทึกวิดีโอจากกล้อง
Future<void> startVideoRecording() async {
try {
if (await Permission.storage.request().isGranted) {
if (_cameraController != null &&
!_cameraController!.value.isRecordingVideo) {
// สร้างชื่อไฟล์วิดีโอ
String timestamp =
DateFormat('ddMMyyyyTHHmmss').format(DateTime.now());
_videoPath = '${_currentFolder!.path}/video_$timestamp.mp4';
await _cameraController!.startVideoRecording();
setState(() {
isRecording = true;
});
}
} else {
print("Storage permission is denied.");
}
} catch (e) {
print("Error occurred: $e");
}
}
// ฟังก์ชั่นหยุดการบันทึกวิดีโอ
Future<void> stopVideoRecording() async {
if (_cameraController != null &&
_cameraController!.value.isRecordingVideo) {
XFile videoFile = await _cameraController!.stopVideoRecording();
setState(() {
isRecording = false;
});
print("Video saved to: ${videoFile.path}");
// บันทึกไฟล์วิดีโอไปยัง app โฟลเดอร์ app_flutter
String newVideoPath =
'${_currentFolder!.path}/video_${DateFormat('ddMMyyyyTHHmmss').format(DateTime.now())}.mp4';
await videoFile.saveTo(newVideoPath);
print("Video moved to: $newVideoPath");
}
}
// ฟังก์ชั่นสำหรับบันทึกไฟล์ภาพ หรือจับภาพจากกล้อง
Future<void> takePicture() async {
try {
if (await Permission.storage.request().isGranted) {
if (_cameraController != null &&
!_cameraController!.value.isTakingPicture) {
// สร้างชื่อไฟล์รูปภาพ
String timestamp =
DateFormat('ddMMyyyyTHHmmss').format(DateTime.now());
_imagePath = '${_currentFolder!.path}/image_$timestamp.jpg';
// ถ่ายรูป
XFile imageFile = await _cameraController!.takePicture();
// บันทึกไฟล์รูปไปยัง app โฟลเดอร์ app_flutter
await imageFile.saveTo(_imagePath!);
print("Image saved to: $_imagePath");
}
} else {
print("Storage permission is denied.");
}
} catch (e) {
print("Error occurred: $e");
}
}
@override
void dispose() {
_cameraController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Camera'),
),
body: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_cameraController != null && _cameraController!.value.isInitialized
? Column(
children: [
CameraPreview(_cameraController!),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: isRecording
? stopVideoRecording
: startVideoRecording,
child: Text(isRecording
? 'Stop Recording'
: 'Start Recording'),
),
ElevatedButton(
onPressed: takePicture,
child: Text('Take Picture'),
),
],
),
],
)
: Center(child: CircularProgressIndicator()),
],
),
),
);
}
}
ผลลัพธ์ที่ได้

ข้างต้นเป็นรูปแบบการใช้งานอย่างง่าย ในกรณีเราไม่ต้องการโค้ดหรือการทำงานที่ซับซ้อน อย่างไรก็ดี
ด้วยความสามารถของตัว plugin ยังสามารถปรับแต่งส่วนต่างๆ หากต้องการได้ ไม่ว่าจะเป็นการกำหนด
เกี่ยวกับกล้องหน้ากล้องหลัง การใช้ไฟล์แฟลซ การโฟกัส เหล่านี้เป็นต้น ดูตัวอย่างรูปแบบปรับแต่ง เพิ่ม
เติมได้ที่โค้ดด้านล่าง สามารถเลือกเอาส่วนที่ต้องการไปประยุกต์ใช้งานได้
ไฟล์ camera.dart (ปรับแต่งเพิ่มเติมจากโค้ดตัวอย่าง)
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:camera/camera.dart';
import 'package:video_player/video_player.dart';
import 'package:flutter/scheduler.dart';
class Camera extends StatefulWidget {
static const routeName = '/camara';
const Camera({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return _CameraState();
}
}
class _CameraState extends State<Camera>
with WidgetsBindingObserver, TickerProviderStateMixin {
CameraController? controller;
bool _cameraReady = false;
XFile? imageFile;
XFile? videoFile;
VideoPlayerController? videoController;
VoidCallback? videoPlayerListener;
// ค่าสำหรับกำหนดมีเสียงหรือไม่มีสียงเมื่อบันทึกวิดีโอ
bool enableAudio = true;
// ค่าสำหรับการกำหนดการชดเชยแสง
double _minAvailableExposureOffset = 0.0;
double _maxAvailableExposureOffset = 0.0;
double _currentExposureOffset = 0.0;
// ค่าจัดการเกี่ยวกับ animation การซ่อนแสดงปุ่มต่างๆ
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;
// เก็บจำนวนนิ้วผู้ใช้บนหน้าจอที่กำลังกดหรือใช้งานอยู่
int _pointers = 0;
@override
void initState() {
super.initState();
initialCamera();
// ส่วนสำหรับตรวจจับสถาะของ state เพื่อใช้งาน AppLifecycle
WidgetsBinding.instance.addObserver(this);
// ส่วนจัดการ animation การแสดงของปุ่มต่างๆ
_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,
);
}
// ฟังก์ชั่นเตรียมพร้อมกล้องเพื่อใช้งาน
Future<void> initialCamera() async {
try {
WidgetsFlutterBinding.ensureInitialized();
_cameras = await availableCameras();
if (_cameras.isNotEmpty) {
setState(() {
_cameraReady = true;
});
}
} on CameraException catch (e) {
_logError(e.code, e.description);
}
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_flashModeControlRowAnimationController.dispose();
_exposureModeControlRowAnimationController.dispose();
super.dispose();
}
// #docregion AppLifecycle
@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) {
_initializeCameraController(cameraController.description);
}
}
// #enddocregion AppLifecycle
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('การใข้งานกล้อง'),
),
body: Column(
children: <Widget>[
Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.black,
border: Border.all(
color:
controller != null && controller!.value.isRecordingVideo
? Colors.redAccent
: Colors.grey,
width: 3.0,
),
),
child: Padding(
padding: const EdgeInsets.all(1.0),
child: Center(
child: _cameraPreviewWidget(),
),
),
),
),
_captureControlRowWidget(),
_modeControlRowWidget(),
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
children: <Widget>[
_cameraTogglesRowWidget(),
_thumbnailWidget(),
],
),
),
],
),
);
}
// สร้างภาพหรือวิดีโอตัวอย่างจากกล้องที่กำลังเปิดใช้งานรอบันทึก
Widget _cameraPreviewWidget() {
final CameraController? cameraController = controller;
if (cameraController == null || !cameraController.value.isInitialized) {
return const Text(
'เลือกกล้อง(ถ้ามี)',
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: (TapDownDetails details) =>
onViewFinderTap(details, constraints),
);
}),
),
);
}
}
// ฟังก์ชั่นจัดการสเกลค่าเริ่มต้น
void _handleScaleStart(ScaleStartDetails details) {
_baseScale = _currentScale;
}
// ฟังก์ชั่นสำหรับจัดการสเกลการซูมกล้อง โดยใช้มือซูมเข้าออกหรือปรับตำแหน่งโฟกัส
Future<void> _handleScaleUpdate(ScaleUpdateDetails details) async {
if (controller == null || _pointers != 2) {
return;
}
_currentScale = (_baseScale * details.scale)
.clamp(_minAvailableZoom, _maxAvailableZoom);
await controller!.setZoomLevel(_currentScale);
}
// ฟังก์ชั่นแสดง thumbnail ของรูปหรือวิดีโอ
Widget _thumbnailWidget() {
final VideoPlayerController? localVideoController = videoController;
return Expanded(
child: Align(
alignment: Alignment.centerRight,
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
if (localVideoController == null && imageFile == null)
Container()
else
SizedBox(
width: 64.0,
height: 64.0,
child: (localVideoController == null)
? (
kIsWeb
? Image.network(imageFile!.path)
: Image.file(File(imageFile!.path)))
: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.pink)),
child: Center(
child: AspectRatio(
aspectRatio:
localVideoController.value.aspectRatio,
child: VideoPlayer(localVideoController)),
),
),
),
],
),
),
);
}
// ฟังก์ชั่นสำหรับสร้างปุ่มควบคุมรวมหลัก
Widget _modeControlRowWidget() {
return Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
IconButton(
icon: const Icon(Icons.flash_on),
color: Colors.blue,
onPressed: controller != null ? onFlashModeButtonPressed : null,
),
...!kIsWeb
? <Widget>[
IconButton(
icon: const Icon(Icons.exposure),
color: Colors.blue,
onPressed: controller != null
? onExposureModeButtonPressed
: null,
),
IconButton(
icon: const Icon(Icons.filter_center_focus),
color: Colors.blue,
onPressed:
controller != null ? onFocusModeButtonPressed : null,
)
]
: <Widget>[],
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,
children: <Widget>[
IconButton(
icon: const Icon(Icons.flash_off),
color: controller?.value.flashMode == FlashMode.off
? Colors.orange
: Colors.blue,
onPressed: controller != null
? () => onSetFlashModeButtonPressed(FlashMode.off)
: null,
),
IconButton(
icon: const Icon(Icons.flash_auto),
color: controller?.value.flashMode == FlashMode.auto
? Colors.orange
: Colors.blue,
onPressed: controller != null
? () => onSetFlashModeButtonPressed(FlashMode.auto)
: null,
),
IconButton(
icon: const Icon(Icons.flash_on),
color: controller?.value.flashMode == FlashMode.always
? Colors.orange
: Colors.blue,
onPressed: controller != null
? () => onSetFlashModeButtonPressed(FlashMode.always)
: null,
),
IconButton(
icon: const 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(
foregroundColor: controller?.value.exposureMode == ExposureMode.auto
? Colors.orange
: Colors.blue,
);
final ButtonStyle styleLocked = TextButton.styleFrom(
foregroundColor: controller?.value.exposureMode == ExposureMode.locked
? Colors.orange
: Colors.blue,
);
return SizeTransition(
sizeFactor: _exposureModeControlRowAnimation,
child: ClipRect(
child: ColoredBox(
color: Colors.grey.shade50,
child: Column(
children: <Widget>[
const Center(
child: Text('Exposure Mode'),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
TextButton(
style: styleAuto,
onPressed: controller != null
? () =>
onSetExposureModeButtonPressed(ExposureMode.auto)
: null,
onLongPress: () {
if (controller != null) {
controller!.setExposurePoint(null);
showInSnackBar('Resetting exposure point');
}
},
child: const Text('AUTO'),
),
TextButton(
style: styleLocked,
onPressed: controller != null
? () =>
onSetExposureModeButtonPressed(ExposureMode.locked)
: null,
child: const Text('LOCKED'),
),
TextButton(
style: styleLocked,
onPressed: controller != null
? () => controller!.setExposureOffset(0.0)
: null,
child: const Text('RESET OFFSET'),
),
],
),
const Center(
child: Text('Exposure Offset'),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
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(
foregroundColor: controller?.value.focusMode == FocusMode.auto
? Colors.orange
: Colors.blue,
);
final ButtonStyle styleLocked = TextButton.styleFrom(
foregroundColor: controller?.value.focusMode == FocusMode.locked
? Colors.orange
: Colors.blue,
);
return SizeTransition(
sizeFactor: _focusModeControlRowAnimation,
child: ClipRect(
child: ColoredBox(
color: Colors.grey.shade50,
child: Column(
children: <Widget>[
const Center(
child: Text('Focus Mode'),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
TextButton(
style: styleAuto,
onPressed: controller != null
? () => onSetFocusModeButtonPressed(FocusMode.auto)
: null,
onLongPress: () {
if (controller != null) {
controller!.setFocusPoint(null);
}
showInSnackBar('Resetting focus point');
},
child: const Text('AUTO'),
),
TextButton(
style: styleLocked,
onPressed: controller != null
? () => onSetFocusModeButtonPressed(FocusMode.locked)
: null,
child: const Text('LOCKED'),
),
],
),
],
),
),
),
);
}
// ฟังก์ชั่นสร้างปุ่มควบคุมรูปภาพหรือวิดีโอ
Widget _captureControlRowWidget() {
final CameraController? cameraController = controller;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
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
? const Icon(Icons.play_arrow)
: const 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,
),
IconButton(
icon: const Icon(Icons.pause_presentation),
color:
cameraController != null && cameraController.value.isPreviewPaused
? Colors.red
: Colors.blue,
onPressed:
cameraController == null ? null : onPausePreviewButtonPressed,
),
],
);
}
/// แถวแสดงข้อมูลกล้องให้เลือก ถ้ามี หรือแสดงข้อความถ้าไม่มีกล้องที่รองรับ
Widget _cameraTogglesRowWidget() {
final List<Widget> toggles = <Widget>[];
void onChanged(CameraDescription? description) {
if (description == null) {
return;
}
// เมื่อเลือกกล้องที่จะใช้งาน
onNewCameraSelected(description);
}
if (!_cameraReady) {
return const Text('กำลังเตรียมกล้อง..');
} else if (_cameras.isEmpty) {
SchedulerBinding.instance.addPostFrameCallback((_) async {
showInSnackBar('ไม่พบกล้องพร้อมใช้งาน.');
});
return const Text('ไม่พบกล้องพร้อมใช้งาน');
} else {
for (final CameraDescription cameraDescription in _cameras) {
toggles.add(
SizedBox(
width: 90.0,
child: RadioListTile<CameraDescription>(
title: Icon(getCameraLensIcon(cameraDescription.lensDirection)),
groupValue: controller?.description,
value: cameraDescription,
onChanged: onChanged,
),
),
);
}
}
return Row(children: toggles);
}
// ฟังก์ชั่นเลือกกล้องเพื่อใช้งาน
Future<void> onNewCameraSelected(CameraDescription cameraDescription) async {
if (controller != null) {
return controller!.setDescription(cameraDescription);
} else {
return _initializeCameraController(cameraDescription);
}
}
// เตรียมตัวควบคุมกล้องตามกล้องที่เลือก
Future<void> _initializeCameraController(
CameraDescription cameraDescription) async {
final CameraController cameraController = CameraController(
cameraDescription,
kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium,
enableAudio: enableAudio,
imageFormatGroup: ImageFormatGroup.jpeg,
);
controller = cameraController;
// เมื่อตัวควบคุมมีการเปลี่ยนแปลง ให้อัปเดท UI
cameraController.addListener(() {
if (mounted) {
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?>>[
cameraController.getMinExposureOffset().then(
(double value) => _minAvailableExposureOffset = value),
cameraController
.getMaxExposureOffset()
.then((double value) => _maxAvailableExposureOffset = value)
]
: <Future<Object?>>[],
cameraController
.getMaxZoomLevel()
.then((double value) => _maxAvailableZoom = value),
cameraController
.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(() {});
}
}
// ฟังก์ชั่นคืนค่าข้อความเวลา ณ ปัจจุบัน
String timestamp() => DateTime.now().millisecondsSinceEpoch.toString();
// ฟังก์ชั่นแสดงข้อความใน SnackBar
void showInSnackBar(String message) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(message)));
}
// ฟังก์ชันควบคุมเมื่อกดระบุตำแหน่งในจอที่จะทำการโฟกัสหรือการชดเชยแสง
void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) {
if (controller == null) {
return;
}
final CameraController cameraController = controller!;
final Offset offset = Offset(
details.localPosition.dx / constraints.maxWidth,
details.localPosition.dy / constraints.maxHeight,
);
cameraController.setExposurePoint(offset);
cameraController.setFocusPoint(offset);
}
// ฟังก์ชั่นส่วนทำงานการ ถ่าย รูปภาพ
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() {
// ตรวจสอบว่าแอนิเมชันของแถบควบคุมแฟลชเสร็จสิ้นหรือยัง
// (value == 1 หมายถึงแอนิเมชันได้เสร็จสิ้นแล้ว)
if (_flashModeControlRowAnimationController.value == 1) {
// ถ้าแอนิเมชันของแถบควบคุมแฟลชเสร็จสิ้นแล้ว (แสดงว่าแถบแฟลชเปิดอยู่)
// จะเรียกใช้ reverse() เพื่อเลื่อนแถบควบคุมแฟลชกลับไปปิด
_flashModeControlRowAnimationController.reverse();
} else {
// เรียกใช้ forward() เพื่อเริ่มแอนิเมชันแถบควบคุมแฟลชให้เลื่อนออกมา (เปิดแถบควบคุมแฟลช)
_flashModeControlRowAnimationController.forward();
// ปิด (เลื่อนกลับ) แถบควบคุมการชดเชยแสง (Exposure) หากเปิดอยู่
_exposureModeControlRowAnimationController.reverse();
// ปิด (เลื่อนกลับ) แถบควบคุมโหมดโฟกัส หากเปิดอยู่
_focusModeControlRowAnimationController.reverse();
}
}
// ฟังก์ชั่นควบคุมการชดเชยแสง เมื่อปุ่มควบคุมการชดเชยแสงถูกกด
void onExposureModeButtonPressed() {
// ตรวจสอบว่าแถบควบคุมการชดเชยแสงถูกเปิดอยู่หรือไม่
// (value == 1 หมายถึงแถบควบคุมเปิดเต็มที่)
if (_exposureModeControlRowAnimationController.value == 1) {
// หากแถบควบคุมการชดเชยแสงเปิดอยู่ จะเลื่อนแถบนี้กลับไปปิด (ย้อนกลับแอนิเมชัน)
_exposureModeControlRowAnimationController.reverse();
} else {
// เริ่มแอนิเมชันเพื่อเปิดแถบควบคุมการชดเชยแสง
_exposureModeControlRowAnimationController.forward();
// หากแถบควบคุมแฟลชเปิดอยู่ จะปิด (เลื่อนกลับ) แถบควบคุมแฟลชลง
_flashModeControlRowAnimationController.reverse();
// หากแถบควบคุมโหมดโฟกัสเปิดอยู่ จะปิด (เลื่อนกลับ) แถบควบคุมโหมดโฟกัสลง
_focusModeControlRowAnimationController.reverse();
}
}
// ตรวจสอบว่าแถบควบคุมโหมดโฟกัสถูกเปิดอยู่หรือไม่
// (value == 1 หมายถึงแถบควบคุมเปิดเต็มที่)
void onFocusModeButtonPressed() {
if (_focusModeControlRowAnimationController.value == 1) {
// ถ้าแถบควบคุมโหมดโฟกัสเปิดอยู่ จะเลื่อนแถบนี้กลับลงไปปิด (ย้อนกลับแอนิเมชัน)
_focusModeControlRowAnimationController.reverse();
} else {
// เริ่มแอนิเมชันเพื่อเปิดแถบควบคุมโหมดโฟกัส
_focusModeControlRowAnimationController.forward();
// ถ้าแถบควบคุมแฟลชเปิดอยู่ จะปิด (เลื่อนกลับ) แถบควบคุมแฟลชลง
_flashModeControlRowAnimationController.reverse();
// ถ้าแถบควบคุมการชดเชยแสงเปิดอยู่ จะปิด (เลื่อนกลับ) แถบควบคุมการชดเชยแสงลง
_exposureModeControlRowAnimationController.reverse();
}
}
// เปลี่ยนค่าของตัวแปร enableAudio ให้ตรงข้ามกับค่าปัจจุบัน
//(ถ้าเปิดเสียงอยู่ก็จะปิด ถ้าปิดอยู่ก็จะเปิด)
void onAudioModeButtonPressed() {
enableAudio = !enableAudio;
if (controller != null) {
// เรียกฟังก์ชัน onNewCameraSelected โดยส่งคำอธิบายของกล้องที่ใช้อยู่ (description)
// ไปเป็นพารามิเตอร์ เพื่อเลือกกล้องใหม่และอัปเดตการตั้งค่าเสียงตามสถานะของ enableAudio
onNewCameraSelected(controller!.description);
}
}
// เมื่อกดปุ่มควบคุมการล็อกแนวกล้อง
Future<void> onCaptureOrientationLockButtonPressed() async {
try {
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}');
}
}
} on CameraException catch (e) {
_showCameraException(e);
}
}
// ฟังก์ชั่นสำหรับควบคุมปุ่มกดการตั้งค่าแฟลช
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((XFile? file) {
if (mounted) {
setState(() {});
}
if (file != null) {
showInSnackBar('Video recorded to ${file.path}');
videoFile = file;
_startVideoPlayer();
}
});
}
// ฟังก์ชันสำหรับการหยุถพรีวิววิดีโอจากกล้อง
Future<void> onPausePreviewButtonPressed() async {
final CameraController? cameraController = controller;
if (cameraController == null || !cameraController.value.isInitialized) {
showInSnackBar('Error: select a camera first.');
return;
}
if (cameraController.value.isPreviewPaused) {
await cameraController.resumePreview();
} else {
await cameraController.pausePreview();
}
if (mounted) {
setState(() {});
}
}
// ฟังก์ชันสำหรับควบคุมการหยุดการบันทึกวิดีโอชั่วคราว
void onPauseButtonPressed() {
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;
}
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;
}
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 = kIsWeb
? VideoPlayerController.networkUrl(Uri.parse(videoFile!.path))
: VideoPlayerController.file(File(videoFile!.path));
videoPlayerListener = () {
if (videoController != 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 {
final 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}');
}
}
List<CameraDescription> _cameras = <CameraDescription>[];
/// จัดการไอคอนของกล้องที่เหมาะสม [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;
}
// ignore: dead_code
return Icons.camera;
}
void _logError(String code, String? message) {
// ignore: avoid_print
print('Error: $code${message == null ? '' : 'nError Message: $message'}');
}
ผลลัพธ์ที่ได้

โค้ดข้างต้นเป็นโค้ดการปรับแต่งและการใช้งานทั้งหมดของ plugin camera ที่เราสามารถปรับแต่ง
ส่วนต่างๆ ได้เอง สามารถเลือกส่วนที่ต้องการไปปรับใช้งานได้
*จากที่ทดสอบการทำงานยังมี bug ในส่วนของการหยุดภาพพรีวิว กับการกำหนดเรื่องการชดเชยแสง
จากการใช้งานทั้งสองแพ็กเก็จ ทั้ง image_picker และ camera ที่ใช้สำหรับบันภาพหรือวิดีโอ
จะเห็นว่าตัว image_picker จะใช้งานง่ายและสะดวกกว่า กรณีเราต้องการความเรียบง่าย ไม่ต้อง
ปรับแต่งอะไรเยอะ เพราะใช้การจัดการของกล้องของระบบ ในขณะที่ถ้าเราใช้งาน camera หากต้อง
การปรับแต่งค่าๆ ต่างเป็นแบบที่ต้องการก็จะสามารถทำได้มากกว่า แต่ก็จะยากและซับซ้อนเพิ่มชึ้น
เนื้อหานี้เรานำเสนอให้เข้าใจถึงแนวทางและวิธีการใช้งานการบันทึกไฟล์วิดีโอใน flutter สำหรับนำ
ไปปรับใช้กับโปรเจ็คแอปของเรา กรณีที่ต้องการจัดการในส่วนนี้ หวังว่าจะเป็นประโยชน์ ไม่มากก็น้อย
สำหรับในตอนหน้าจะเป็นอะไร รอติดตาม