Camera

샤워실의 바보·2024년 2월 13일
0
post-thumbnail
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:tiktok_clone/constants/gaps.dart';
import 'package:tiktok_clone/constants/sizes.dart';

// 비디오 녹화 화면을 위한 StatefulWidget
class VideoRecordingScreen extends StatefulWidget {
  const VideoRecordingScreen({super.key});

  
  State<VideoRecordingScreen> createState() => _VideoRecordingScreenState();
}

class _VideoRecordingScreenState extends State<VideoRecordingScreen> {
  bool _hasPermission = false; // 카메라 및 마이크 권한 보유 여부
  bool _isSelfieMode = false; // 셀피 모드 활성화 여부

  late CameraController _cameraController; // 카메라 제어를 위한 컨트롤러

  // 카메라 초기화 메서드
  Future<void> initCamera() async {
    final cameras = await availableCameras(); // 사용 가능한 카메라 목록 조회

    if (cameras.isEmpty) {
      // 사용 가능한 카메라가 없는 경우 초기화 종료
      return;
    }

    // 셀피 모드 여부에 따라 사용할 카메라 선택
    _cameraController = CameraController(
      cameras[_isSelfieMode ? 1 : 0], // 셀피 모드면 두 번째 카메라(일반적으로 전면), 아니면 첫 번째 카메라(일반적으로 후면)
      ResolutionPreset.ultraHigh,
    );

    await _cameraController.initialize(); // 카메라 컨트롤러 초기화
  }

  // 권한 초기화 메서드
  Future<void> initPermissions() async {
    final cameraPermission = await Permission.camera.request(); // 카메라 권한 요청
    final micPermission = await Permission.microphone.request(); // 마이크 권한 요청

    // 권한 거부 여부 확인
    final cameraDenied =
        cameraPermission.isDenied || cameraPermission.isPermanentlyDenied;
    final micDenied =
        micPermission.isDenied || micPermission.isPermanentlyDenied;

    if (!cameraDenied && !micDenied) {
      // 모든 권한이 허용된 경우
      _hasPermission = true;
      await initCamera(); // 카메라 초기화
      setState(() {}); // UI 업데이트를 위한 상태 변경
    }
  }

  
  void initState() {
    super.initState();
    initPermissions(); // 권한 및 카메라 초기화 시작
  }

  // 셀피 모드 전환 메서드
  Future<void> _toggleSelfieMode() async {
    _isSelfieMode = !_isSelfieMode; // 셀피 모드 상태 전환
    await initCamera(); // 카메라 재초기화
    setState(() {}); // UI 업데이트
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      body: SizedBox(
        width: MediaQuery.of(context).size.width,
        child: !_hasPermission || !_cameraController.value.isInitialized
            ? Column(
                // 권한이 없거나 카메라가 초기화되지 않은 경우 로딩 화면 표시
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisAlignment: MainAxisAlignment.center,
                children: const [
                  Text("Initializing...", style: TextStyle(color: Colors.white, fontSize: Sizes.size20)),
                  Gaps.v20,
                  CircularProgressIndicator.adaptive()
                ],
              )
            : Stack(
                // 카메라 미리보기와 셀피 모드 전환 버튼 표시
                alignment: Alignment.center,
                children: [
                  CameraPreview(_cameraController),
                  Positioned(
                    top: Sizes.size20,
                    left: Sizes.size20,
                    child: IconButton(
                      color: Colors.white,
                      onPressed: _toggleSelfieMode, // 셀피 모드 전환 버튼 액션
                      icon: const Icon(Icons.cameraswitch),
                    ),
                  ),
                ],
              ),
      ),
    );
  }
}

이 코드는 Flutter 앱에서 카메라를 사용하여 비디오 녹화 화면을 구현하는 VideoRecordingScreen 클래스입니다. 이 클래스는 카메라와 마이크 권한을 요청하고, 권한이 허용되면 카메라를 초기화하여 사용자에게 카메라 미리보기를 제공합니다.

주요 기능 및 구성 요소

  • 카메라 권한 요청: initPermissions 메서드를 통해 카메라와 마이크 권한을 사용자에게 요청합니다. 사용자가 권한을 거부하면, 카메라 사용이 불가능합니다.
  • 카메라 초기화: 권한이 허용되면, initCamera 메서드를 호출하여 사용 가능한 카메라 목록을 가져오고, 첫 번째 카메라를 선택하여 CameraController를 사용해 초기화합니다.
  • 화면 구성: 카메라 권한이 허용되고 카메라가 성공적으로 초기화되면, CameraPreview 위젯을 사용하여 카메라 미리보기를 화면에 표시합니다. 권한이 거부되거나 카메라 초기화가 완료되지 않은 상태에서는 "Initializing..." 텍스트와 함께 로딩 인디케이터를 표시합니다.

코드 상세

  • initPermissions 메서드에서는 permission_handler 패키지를 사용하여 카메라와 마이크 권한을 요청하고, 권한 상태를 확인합니다. 모든 권한이 허용되면 _hasPermissiontrue로 설정하고 initCamera 메서드를 호출합니다.
  • initCamera 메서드에서는 camera 패키지의 availableCameras 함수를 사용해 사용 가능한 카메라 목록을 가져옵니다. 선택된 카메라로 CameraController를 생성하고 초기화합니다.
  • build 메서드에서는 화면에 카메라 미리보기(CameraPreview)를 표시하거나, 권한 요청/카메라 초기화가 진행 중일 때 로딩 화면을 표시합니다.

주의 사항

  • 카메라와 마이크 권한은 사용자의 프라이버시와 관련된 중요한 요소이므로, 권한 요청 시 사용자에게 해당 권한이 필요한 이유를 명확하게 설명해야 합니다.
  • 실제 장치에서만 카메라 기능을 테스트할 수 있습니다. 에뮬레이터나 시뮬레이터에서는 카메라 기능을 사용할 수 없습니다.

이 코드를 통해 Flutter 앱에서 카메라를 사용하는 기본적인 방법을 이해할 수 있으며, 추가적인 카메라 기능 구현 시 이를 기반으로 확장할 수 있습니다.

위 코드의 비즈니스 로직은 크게 세 부분으로 나뉩니다: 권한 요청, 카메라 초기화, 그리고 UI 렌더링입니다. 각 단계의 흐름을 세부적으로 살펴보겠습니다.

1. 권한 요청 (initPermissions 메서드)

  • 권한 요청 시작: 앱은 사용자에게 카메라와 마이크 권한을 요청합니다. 이는 사용자가 비디오를 녹화할 때 필요한 핵심 권한입니다.
  • 권한 상태 확인: 각 권한에 대해 요청 결과를 받아, 사용자가 권한을 거부했는지, 영구적으로 거부했는지 확인합니다.
  • 권한 허용 여부 처리: 모든 필요한 권한이 허용되면, 카메라 초기화 함수(initCamera)를 호출하고, _hasPermission 플래그를 true로 설정합니다. 이 플래그는 카메라 사용 가능 여부를 UI에 알려줍니다.

2. 카메라 초기화 (initCamera 메서드)

  • 사용 가능한 카메라 목록 조회: availableCameras 함수를 호출하여 장치에서 사용 가능한 카메라 목록을 가져옵니다.
  • 카메라 선택 및 초기화: 목록에서 첫 번째 카메라를 선택하고, CameraController를 사용하여 해당 카메라를 초기화합니다. 여기서 해상도는 ResolutionPreset.ultraHigh로 설정됩니다.
  • 카메라 준비 완료: _cameraController.initialize() 호출을 통해 카메라가 사용 준비가 완료되면, UI를 업데이트하기 위해 setState를 호출합니다.

3. UI 렌더링 (build 메서드)

  • 권한 및 초기화 상태 체크: _hasPermission_cameraController.value.isInitialized를 확인하여, 권한이 허용되었고 카메라 초기화가 완료되었는지 판단합니다.
  • 카메라 미리보기 표시: 모든 조건이 충족되면, CameraPreview 위젯을 사용하여 화면에 카메라 미리보기를 표시합니다.
  • 로딩 화면 표시: 권한이 거부되었거나 카메라 초기화가 아직 진행 중인 경우, "Initializing..." 텍스트와 함께 로딩 인디케이터를 표시합니다.

요약

이 로직은 사용자에게 필요한 권한을 요청하고, 권한이 허용된 후에는 카메라를 초기화하여 카메라 미리보기를 제공합니다. 사용자가 권한을 거부하거나 카메라 초기화에 실패한 경우에는 적절한 피드백(로딩 화면)을 제공합니다. 이 과정을 통해 사용자는 앱에서 비디오 녹화 기능을 원활하게 사용할 수 있습니다.

import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:tiktok_clone/constants/gaps.dart';
import 'package:tiktok_clone/constants/sizes.dart';

class VideoRecordingScreen extends StatefulWidget {
  const VideoRecordingScreen({super.key});

  
  State<VideoRecordingScreen> createState() => _VideoRecordingScreenState();
}

class _VideoRecordingScreenState extends State<VideoRecordingScreen> {
  bool _hasPermission = false;
  bool _isSelfieMode = false;
  late FlashMode _flashMode; // 현재 플래시 모드를 저장하는 변수
  late CameraController _cameraController;

  // 카메라 초기화 함수
  Future<void> initCamera() async {
    final cameras = await availableCameras();
    if (cameras.isEmpty) {
      return;
    }

    _cameraController = CameraController(
      cameras[_isSelfieMode ? 1 : 0], // 셀피 모드에 따라 카메라 선택
      ResolutionPreset.ultraHigh,
    );

    await _cameraController.initialize();
    _flashMode = _cameraController.value.flashMode; // 초기 플래시 모드 설정
  }

  // 권한 확인 및 요청 함수
  Future<void> initPermissions() async {
    final cameraPermission = await Permission.camera.request();
    final micPermission = await Permission.microphone.request();

    final cameraDenied =
        cameraPermission.isDenied || cameraPermission.isPermanentlyDenied;
    final micDenied =
        micPermission.isDenied || micPermission.isPermanentlyDenied;

    if (!cameraDenied && !micDenied) {
      _hasPermission = true;
      await initCamera();
      setState(() {});
    }
  }

  
  void initState() {
    super.initState();
    initPermissions();
  }

  // 셀피 모드 전환 함수
  Future<void> _toggleSelfieMode() async {
    _isSelfieMode = !_isSelfieMode;
    await initCamera();
    setState(() {});
  }

  // 플래시 모드 설정 함수
  Future<void> _setFlashMode(FlashMode newFlashMode) async {
    await _cameraController.setFlashMode(newFlashMode); // 카메라 컨트롤러를 사용하여 플래시 모드 설정
    _flashMode = newFlashMode; // 현재 플래시 모드 업데이트
    setState(() {}); // UI 업데이트
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      body: SizedBox(
        width: MediaQuery.of(context).size.width,
        child: !_hasPermission || !_cameraController.value.isInitialized
            ? Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisAlignment: MainAxisAlignment.center,
                children: const [
                  Text("Initializing...", style: TextStyle(color: Colors.white, fontSize: Sizes.size20)),
                  Gaps.v20,
                  CircularProgressIndicator.adaptive()
                ],
              )
            : Stack(
                alignment: Alignment.center,
                children: [
                  CameraPreview(_cameraController),
                  Positioned(
                    top: Sizes.size20,
                    right: Sizes.size20,
                    child: Column(
                      children: [
                        IconButton(
                          color: Colors.white,
                          onPressed: _toggleSelfieMode,
                          icon: const Icon(Icons.cameraswitch),
                        ),
                        Gaps.v10,
                        // 플래시 모드 설정 버튼들
                        IconButton(
                          color: _flashMode == FlashMode.off ? Colors.amber.shade200 : Colors.white,
                          onPressed: () => _setFlashMode(FlashMode.off),
                          icon: const Icon(Icons.flash_off_rounded),
                        ),
                        Gaps.v10,
                        IconButton(
                          color: _flashMode == FlashMode.always ? Colors.amber.shade200 : Colors.white,
                          onPressed: () => _setFlashMode(FlashMode.always),
                          icon: const Icon(Icons.flash_on_rounded),
                        ),
                        Gaps.v10,
                        IconButton(
                          color: _flashMode == FlashMode.auto ? Colors.amber.shade200 : Colors.white,
                          onPressed: () => _setFlashMode(FlashMode.auto),
                          icon: const Icon(Icons.flash_auto_rounded),
                        ),
                        Gaps.v10,
                        IconButton(
                          color: _flashMode == FlashMode.torch ? Colors.amber.shade200 : Colors.white,
                          onPressed: () => _setFlashMode(FlashMode.torch),
                          icon: const Icon(Icons.flashlight_on_rounded),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
      ),
    );
  }
}

이 코드는 Flutter 앱에서 camera 패키지를 사용하여 비디오 녹화 화면을 구현하고, 사용자가 카메라의 플래시 모드를 조절할 수 있도록 하는 기능을 추가한 VideoRecordingScreen 클래스입니다. 사용자는 플래시 모드를 변경할 수 있는 여러 버튼을 통해 플래시를 제어할 수 있습니다.

플래시 모드 관리

  • 플래시 모드 초기화: 카메라 컨트롤러가 초기화될 때 현재 플래시 모드의 상태를 _flashMode 변수에 저장합니다. 이는 카메라 세션 동안 사용자가 선택한 플래시 모드를 기억하기 위함입니다.
  • 플래시 모드 전환: 사용자가 UI에서 플래시 모드 전환 버튼(플래시 꺼짐, 항상 켜짐, 자동, 토치 모드)을 탭할 때, _setFlashMode 함수가 호출되어 새 플래시 모드로 설정됩니다. 설정 후, _flashMode 변수가 업데이트되고 UI가 다시 렌더링됩니다.

UI 구성 요소

  • 카메라 미리보기: _cameraController를 사용하여 화면에 카메라 미리보기를 표시합니다. 사용자가 플래시 모드를 변경할 수 있도록 상단 우측에 플래시 관련 버튼이 배치됩니다.

  • 플래시 모드 버튼: 플래시 모드를 변경하는 버튼들은 현재 _flashMode 상태에 따라 색상이 변화합니다. 각 모드(꺼짐, 항상 켜짐, 자동, 토치)에 대응하는 버튼을 탭하면, 해당 모드로 플래시가 설정됩니다.

주요 함수

  • _setFlashMode(FlashMode newFlashMode): 이 함수는 새로운 플래시 모드를 인자로 받아 _cameraController를 통해 카메라의 플래시 모드를 설정합니다. 설정이 완료되면, UI가 업데이트되어 사용자가 현재 선택한 플래시 모드를 시각적으로 확인할 수 있습니다.

플래시 모드 옵션

  • FlashMode.off: 플래시를 사용하지 않음.
  • FlashMode.always: 사진 촬영 시 항상 플래시를 사용.
  • FlashMode.auto: 환경에 따라 자동으로 플래시를 사용할지 결정.
  • FlashMode.torch: 비디오 녹화 시 플래시를 지속적으로 켜둠(손전등 모드).

이 코드를 통해 사용자는 비디오 녹화 시 다양한 플래시 모드 중에서 선택할 수 있게 되며, 이는 특히 어두운 환경에서 녹화할 때 유용합니다. 사용자 경험을 향상시키고, 앱의 기능성을 높이는 중요한 기능입니다.

profile
공부하는 개발자

0개의 댓글