개발노트

41. [Flutter] Camera 녹화하고 gallery_saver 로 영상 저장하기 (with Permission_handler 장치 본문

앱 개발/Flutter

41. [Flutter] Camera 녹화하고 gallery_saver 로 영상 저장하기 (with Permission_handler 장치

mroh1226 2023. 11. 2. 17:03
반응형

이번 포스팅에서는 카메라 사용권한을 사용자에게 얻은 뒤, 카메라로 영상을 녹화하고, 원하는 앨범명을 설정한 뒤, 저장하는 기능을 구현합니다.

Flutter App 내에서 Camera를 사용해야한다면 pubdev에서 제공되는 Camera Package를 적용하면됩니다.

+ Camera녹화전 카메라와 마이크 녹음 권한허용을 사용자에게 요청하기위해 Permission_handler를 사용합니다.

+ 녹화된 영상을 저장하기 위해 gallery_saver를 사용합니다.


준비물 및 세팅.

1. 아래 링크를 통해 Project에 Camera를 설치합니다.
- pubdev Camera 패키지 설치 링크: https://pub.dev/packages/camera/versions/0.10.5+5

 

camera | Flutter Package

A Flutter plugin for controlling the camera. Supports previewing the camera feed, capturing images and video, and streaming image buffers to Dart.

pub.dev

 
2-1. (안드로이드일 경우) Camera 패키지를 사용하기 위해 필요한 Android SDK 최소 버전을 맞춰줍니다.

  • 설치 링크에서 사용할 camera 버전 문서를 클릭하고 요구되는 SDK 버전을 확인합니다.

  • Android는 최소 SDK 21 부터 지원합니다.
  • android\app\build.gradle 경로에 접속하여 소스파일을 열어줍니다.

  • defaultConfig를 찾아 minSdkVersion 21 로 작성해줍니다.
  • 기존에 있는 minSdkVersion flutter.minSdkVersion은 지워줍니다.

2-2 (IOS일 경우) Info.plist 파일에 아래 키를 추가합니다.

	<key>NSCameraUsageDescription</key>
	<string>카메라 사용을 허용하시겠습니까?</string>
	<key>NSMicrophoneUsageDescription</key>
	<string>음성 녹음을 허용하시겠습니까?</string>

3. 카메라 뿐 아니라 다양한 권한 승인요청을 확장하기 위해 Permission_handler 패키지를 설치합니다.
- pubdev permission_handler 설치링크: https://pub.dev/packages/permission_handler/install

 

permission_handler | Flutter Package

Permission plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API to request and check permissions.

pub.dev

4. 녹화된 영상을 갤러리에 저장하기 위해 Gallery Saver 패키지를 설치합니다.
- pubdev gallery_saver 설치링크: https://pub.dev/packages/gallery_saver

 

gallery_saver | Flutter Package

Saves images and videos from network or temporary file to external storage. Both images and videos will be visible in Android Gallery and iOS Photos.

pub.dev

 
위 준비가 완료되었다면, 앱 내 Camera 사용 권한 승인 요청부터 카메라 실행, 녹화, 저장하는 방법에 대해서 설명하겠습니다.


1.  권한요청하기

1) 앱 사용자에게 장치권한 요청하기(카메라, 마이크 녹음)

  • 카메라와 마이크 녹음 권한을 받아오기 위해 아래와 같이 Permission을 작성합니다.
   final cameraPermission = await Permission.camera.request();
    //카메라 사용권한
    final micPermission = await Permission.microphone.request();
    //마이크 녹음 사용권한

이 외에도 여러가지 장치 사용권한을 요청할 수 있습니다.

 
2) 사용자가 권한을 허용 했는지 알아보기

  • 카메라와 마이크 녹음 권한을 허용했는지 true, false로 값을 받아오기 위해 아래와 같이 작성합니다.
  • isDenied: 지금 허용하지않음
  • isPermanentlyDeined: 항상 허용하지않음
    final cameraDenied = cameraPermission.isDenied || cameraPermission.isPermanentlyDenied;
    final micDenied = micPermission.isDenied || micPermission.isPermanentlyDenied;
    //isDenied 이번만 허용하지 않음 선택(다시 요청함)
    //isPermanentlyDenied 항상 허용하지 않음 선택(요청하지 않음)

 
사용자가 권한을 허용했다면, 카메라를 실행 시킵니다.


2.  카메라 실행하기

1) CameraController 생성하기

  • Camera의 동작 상태 및 값들을 실시간으로 다루기 위해 CameraController를 생성합니다.
  • availableCameras() 메소드로 사용가능한 카메라 목록을 가져옵니다.
  • 생성된 컨트롤러는 아래와 같은 인자 값들이 요구됩니다.
    CameraController CameraController( CameraDescription description,  ResolutionPreset resolutionPreset,
                     {bool enableAudio = true,  ImageFormatGroup? imageFormatGroup,})
    • CameraDescription description: 이 매개변수는 사용할 카메라의 설명(description)을 나타냅니다.
      availableCameras(); 메소드로 디바이스에 연결된 카메라 리스트를 가져올 수 있으며, 그 중 어떤 카메라를 사용할 것인지를 description으로 선택할 수 있습니다.
      일반적으로 0: 후면 카메라, 1: 전면 카메라 로 설정됩니다.(이를 이용해서 카메라 전환 버튼을 만들 수 있음)
    • ResolutionPreset resolutionPreset: 
      카메라 해상도 설정으로 낮은 해상도, 중간 해상도, 높은 해상도 등을 선택할 수 있습니다.
    • bool enableAudio:
      true로 설정하면 카메라로 오디오를 녹음하고 false로 설정하면 오디오 녹음을 비활성화합니다.
    • ImageFormatGroup? imageFormatGroup:
      이미지 형식 그룹을 지정합니다. 카메라에서 생성된 이미지의 형식을  RAW, JPEG, PNG 등 다양한 이미지 형식 그룹을 선택하고 설정합니다.
CameraController 생성 예시.
late CameraController _cameraController;

  Future<void> initCamera() async {
  //카메라를 초기화하는 비동기식 메소드 생성
    final cameras = await availableCameras();
    	//availableCameras() 메소드로 사용 가능한 카메라 목록을 가져옴
    if (cameras.isEmpty) {
      return;
    }
    _cameraController = CameraController(
        	//CameraController 설정
        cameras[0],
        	//CameraDescription(0,CameraLensDirection.back,90) 후면 카메라
        	//CameraDescription(1,CameraLensDirection.front,270) 전면 카메라
        ResolutionPreset.ultraHigh,
        	//매우 높은 화질 선택
        enableAudio: true,
        	//오디오 녹음 활성화
        imageFormatGroup: ImageFormatGroup.jpeg
        	//jpeg 이미지 형식 지정
        );

    await _cameraController.initialize();
    	//카메라 초기화
    await _cameraController.prepareForVideoRecording();
   		// IOS 운영체제에서 녹화할때 싱크를 맞추기 위해 필요한 소스한줄(안드로이드,Web 등은 필요없음)

  }

 

CameraDescription &amp;nbsp;description&amp;nbsp; 0: 후면 카메라, 1: 전면 카메라

2) CameraPreView 위젯에 CameraController 매핑하기

  • 설정이 완료된 CameraController을 CameraPreView 위젯 매개변수로 입력합니다.
  • 빌드 해보면 CameraPreView를 통해 설정된 카메라가 실행된 것을 볼 수 있습니다.

 


3. 플래쉬 모드 설정하기 FlashMode

1) CameraController.setFlashMode() 메소드 인자값으로 FlashMode 를 넣어 플래쉬를 설정할 수 있습니다.

  • FlashMode 는 아래와 같이 always 항상,auto 자동,off 끄기,torch 켜기 가 있습니다.
  • 이를 이용하여 플래쉬를 설정하는 버튼을 만들 수 있습니다.

_cameraController.setFlashMode(FlashMode.auto);

4. 카메라로 비디오 녹화하기

1) 녹화 버튼을 만들어줍니다.
 
2) 녹화 버튼에 GestureDetector를 감싸서 버튼을 TapDown 했을 때 녹화, TapUp 했을 때 녹화종료할 메소드를 만들어 줍니다.

  • onTapDown: 누르고 있을 때 동작함(녹화 진행)
  • onTapUp: 손을 떼어내면 동작함(녹화 종료)
onTapDown: _startRecording() 녹화시작
  Future<void> _startRecording() async {
    if (_cameraController.value.isRecordingVideo) return;
    //이미 녹화중이라면 return;
    await _cameraController.startVideoRecording();
    //아니라면 녹화 시작
  }
  • CameraController.value.isRecordingVideo 로 녹화가 이미 진행 중인지 확인하고 true면 return 합니다.
  • 녹화 중이 아니라면, CameraController.startVideoRecording() 메소드를 호출하여 녹화를 시작합니다.

 

onTapUp: _stopRecording()
Future<void> _stopRecording() async {
    if (!_cameraController.value.isRecordingVideo) return;
    //녹화중이 아니라면 return;
    final video = await _cameraController.stopVideoRecording();
    //녹화를 멈추고 녹화된 영상을 video라는 변수에 넣음
    }
  • CameraContoller.value.isRecordingVideo 로 녹화가 이미 진행 중인지 확인하고 녹화중이 아니라면 return 합니다.
  • 녹화 중이라면, CameraController.stopVideoRecording() 메소드를 호출하여 녹화를 종료합니다.


5. 녹화된 비디오 미리보기

1) CameraController.stopVideoRecording() 메소드로 녹화된 영상을 변수로 받아옵니다.

final video = await _cameraController.stopVideoRecording();

 
2) 녹화된 영상정보가 들어가 있는 변수를 다른 화면에서 보기 위해 화면하나를 만들고 변수를 전달합니다.

Navigator.push(context, MaterialPageRoute(
      builder: (context) {
        return VideoPreViewScreen(video: video);
      },
    ));
  • VideoPreViewScreen() 화면을 새로 만들고 영상정보가 들어있는 변수 video를 전달합니다.

 
3) 영상 정보가 담긴 변수 video 전달과 함께 호출된 화면에 VideoPlayer 위젯을 사용하여 미리보기 기능을 추가합니다.
 

녹화된 영상정보가 있는 변수 video를 전달받은 화면 소스
import 'dart:io';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';

class VideoPreViewScreen extends StatefulWidget {
  final XFile video;
  const VideoPreViewScreen({super.key, required this.video});

  @override
  State<VideoPreViewScreen> createState() => _VideoPreViewScreenState();
}

class _VideoPreViewScreenState extends State<VideoPreViewScreen> {
  late final VideoPlayerController _videoPlayerController;
  //videoPlayer 컨트롤러 생성
  
  Future<void> _initVideo() async {
  //VideoPlayer 초기화 메소드
    _videoPlayerController = VideoPlayerController.file(
      File(widget.video.path),
      //VideoRecordingScreen()에서 녹화된 video를 받아 file path를 VideoPlayerController()에 넣음
    );
    await _videoPlayerController.initialize();
    //VideoPlayer 컨트롤러 초기화
    await _videoPlayerController.setLooping(true);
    //loop 하도록 설정
    await _videoPlayerController.play();
    // 비디오 재생
    setState(() {});
  }

  @override
  void initState() {
    super.initState();
    _initVideo();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Preview"),
      ),
      body: _videoPlayerController.value.isInitialized
          ? VideoPlayer(_videoPlayerController)
          : null,
    );
  }
}

 

녹화가 완료되면 녹화된 영상정보가 포함된 video와 함께 Preview화면 으로 이동 VideoPlayer로 영상 재생


6. 녹화된 비디오 갤러리에 저장하기

1) 녹화된 영상을 갤러리에 저장하기 위해 Stack을 이용하여 VideoPlay 위젯 위에 다운로드 버튼을 생성하고 Tap 이벤트를 추가합니다.
 

Positioned로 다운로드 버튼 추가
Stack(alignment: Alignment.bottomCenter, children: [
              VideoPlayer(_videoPlayerController),
              Positioned(
                  bottom: MediaQuery.of(context).size.height / 18,
                  child: GestureDetector(
                    onTap: () => _saveToGallery(),
                    child: Container(
                      height: 80,
                      width: 80,
                      decoration: BoxDecoration(
                          shape: BoxShape.circle,
                          color: _isSavedVideo
                              ? Colors.green.shade400
                              : Colors.blue.shade300),
                      child: Icon(
                        _isSavedVideo ? Icons.download_done : Icons.download,
                        size: 40,
                      ),
                    ),
                  ))
            ])
  • Tap 되었을 때 _saveToGallery() 라는 메소드를 호출합니다. (Gallery saver로 영상 저장 기능이 구현될 메소드)
  • Tap 되었을 때 Download가 완료되었다는 것을 표시하기 위해 bool _isSavedVideo 을 만들고 이에 따라 아이콘이 변하도록 만들어줍니다.

2) Android, IOS에 각각 아래와 같이 파일 저장 권한을 허용 해줍니다.

2-1) 안드로이드 일 경우

  • \android\app\src\main\AndroidManifest.xml 매니페스트에 아래 소스
  • <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 를 추가합니다.
<manifest ...>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

 
2-2) IOS 일 경우

  • \ios\Runner\Info.plist 파일에 <dict> 안에 아래 소스를 적어넣습니다.
<key>NSPhotoLibraryUsageDescription</key>
<string>Your app needs access to the photo library to select photos and videos.</string>

 
3) 권한허용이 끝났다면, saveVideo() 메소드로 영상을 저장합니다.

  • widget.video.path 으로 부모로 부터 video를 상속받아 path를 설정하고
  • albumName: 에 앨범명(디렉토리명)을 적어줍니다.
  • bool _isSaveVideo 를 통해 다운로드가 완료되었다는 것을 변수에 담아 다운로드 완료 Icon 변경 등에 활용합니다.
late bool _isSavedVideo = false;
  
  Future<void> _saveToGallery() async {
    if (_isSavedVideo) return;

    await GallerySaver.saveVideo(widget.video.path, albumName: "MyFlutterApp");
    //albumName이 디렉토리명이 됨
    _isSavedVideo = true;
    setState(() {});
  }


1 ~ 6 번의 내용을 이용하여 만든 카메라 앱

출처: 노마드코더


 

좀더 쉽게 Camera를 사용하려면 구현이 쉬운 카메라 위젯을 사용하면됩니다.
camerawesome 패키지: https://pub.dev/packages/camerawesome

 

camerawesome | Flutter Package

Easiest Flutter camera Plugin supporting capturing images, streaming images, video recording, switch sensors, autofocus, flash... on Android and iOS

pub.dev

 

반응형
Comments