스마트시대

19 VIDEO RECORDING Introduction 19.1 Installation 19.2 CameraController 19.3 Selfie Mode 19.4 Flash Mode 19.5 Recording Animation 19.6 startVideoRecording 19.7 GallerySaver 19.8 ImagePicker 19.9 AppLifecycleState 본문

Programing/Flutter

19 VIDEO RECORDING Introduction 19.1 Installation 19.2 CameraController 19.3 Selfie Mode 19.4 Flash Mode 19.5 Recording Animation 19.6 startVideoRecording 19.7 GallerySaver 19.8 ImagePicker 19.9 AppLifecycleState

스마트시대 2023. 5. 24. 23:51
728x90

iPhone으로 하고 싶으면 여기보고 하기

 

https://docs.flutter.dev/get-started/install/macos

Deploy to iOS devices

 

deverloper mode on 하고

 

이거 실행

아이디 추가하고 bundle 채우기

이 상태가 되면 재생(플레이)버튼 누르기

이에러 나면 Settings -> General -> Device Management and "trust" 이거도 해줘야 함(소스 어플에 대한 허가)

 

 

다른 맥북에서 할 경우

밑의 경우 체크

1.

Failed to create provisioning profile. There are no devices registered in your account on the developer website. Select a device run destination to have Xcode register it.

 

주소창에 내 장비가 제대로 표시되어 있는지 확인

 

재기동해야될 경우도

2.아이디는 한 PC당 한 개밖에 안되니 똑같은 아이디로 시도

 

3.iproxy는 플러터 관련 파일이니 지우지 말고 허가 해줘

4. vscode 등 다 끄고 flutter clean, run시도

5.이거는 한 번 지정하면 바꾸지말자

 

Android setup

 

 

19.1 Installation

 

https://pub.dev/packages/camera

Android

이런 상태면 밑에서 다운 받고

flutter doctor --android-licenses

실행

 

 

처음 실행할 때는 오래걸린다.

19.2 CameraController

import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';

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

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

class _VideoRecordingScreenState extends State<VideoRecordingScreen> {
  //flag
  bool _hasPermission = false;

  Future<void> initPermission() 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;
      setState(() {});
    }
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      body: Container(),
    );
  }
}

 

  bool _hasPermission = false;


---------------
  // 카메라 초기화 메소드
  Future<void> initCamera() async {
    final cameras = await availableCameras();
    print(cameras);
  }
  ---------------
  
        if (!cameraDenied && !micDenied) {
      _hasPermission = true;
      
      ---------------
      await initCamera();
            ---------------
            
      setState(() {});
    }
  }

 

 

 

 

class _VideoRecordingScreenState extends State<VideoRecordingScreen> {
  //flag
  bool _hasPermission = false;



-------------------
  late final CameraController _cameraController;

  // 카메라 초기화 메소드
  Future<void> initCamera() async {
    final cameras = await availableCameras();

    if (cameras.isEmpty) {
      return;
    }
    //전방 카메라
    _cameraController = CameraController(
      cameras[0],
      ResolutionPreset.ultraHigh,
      //후방 카메라
    );
    await _cameraController.initialize();
  }
-------------------


return Scaffold(
      backgroundColor: Colors.black,
      
      
      -----------------
      body: !_hasPermission
          ? null
          : SizedBox(
              width: MediaQuery.of(context).size.width,
              child: const Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text(
                    "Initializing...",
                    style: TextStyle(
                      color: Colors.white,
                      fontSize: Sizes.size20,
                    ),
                  ),
                  Gaps.v20,
                  CircularProgressIndicator.adaptive(),
                        -----------------
                        
                        
                ],
              ),
            ),
    );

 

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});

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

class _VideoRecordingScreenState extends State<VideoRecordingScreen> {
  //flag
  bool _hasPermission = false;

  late final CameraController _cameraController;

  // 카메라 초기화 메소드

  Future<void> initCamera() async {
    final cameras = await availableCameras();

    if (cameras.isEmpty) {
      return;
    }
    //전방 카메라
    _cameraController = CameraController(
      cameras[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(() {});
    }
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      body: SizedBox(
        width: MediaQuery.of(context).size.width,
        child: !_hasPermission || !_cameraController.value.isInitialized
            ? const Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text(
                    "Initializing...",
                    style:
                        TextStyle(color: Colors.white, fontSize: Sizes.size20),
                  ),
                  Gaps.v20,
                  CircularProgressIndicator.adaptive()
                ],
              )
            : Stack(
                alignment: Alignment.center,
                children: [
                  CameraPreview(_cameraController),
                ],
              ),
      ),
    );
  }
}

 

IOS 실제폰에는 이거를 추가해야함

Check Settings -> Privacy -> Local Network.

이걸 on 해주기

 

참고 트러블 슈팅

1.info.plist

https://github.com/Jinwook-Song/tiktok_flutter/commit/c61a1cdae6ffed43f67106ec5f7850a9530546e0

permission handler의 경우
2.Podfile에도 permission 을 허용해주도록 수정해야합니다

 

3. 이거 문제는 아닌 듯해서 주석처리

Future initCamera()async {
...
setState(() {});
}

  Future<void> initCamera() async {
    final cameras = await availableCameras();

    if (cameras.isEmpty) {
      return;
    }

    _cameraController = CameraController(
      // 0:back, 1: front
      cameras[_isSelfieMode ? 1 : 0],
      ResolutionPreset.ultraHigh,
    );

    await _cameraController.initialize();

    _flashMode = _cameraController.value.flashMode;
        --------------------
    //iOS권한문제 수정
    // setState(() {});
        --------------------
        
  }

  //권한 관련 메소드
  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(() {});
    }
  }

  @override
  void initState() {
    super.initState();
    initPermissions();
    
    
    --------------------
    //iOS권한문제 수정
    // setState(() {});
        --------------------
  }

  Future<void> _toggleSelfieMode() async {
    _isSelfieMode = !_isSelfieMode;
    await initCamera();
    setState(() {});
  }

  Future<void> _setFlashMode(FlashMode newFlashMode) async {
    await _cameraController.setFlashMode(newFlashMode);
    _flashMode = newFlashMode;
    setState(() {});
  }

  @override

 

 

 

19.3 Selfie Mode

class _VideoRecordingScreenState extends State<VideoRecordingScreen> {
  //flag
  bool _hasPermission = false;
  

----------------
  bool _isSelfieMode = false;
----------------


  late CameraController _cameraController;

  // 카메라 초기화 메소드

  Future<void> initCamera() async {
    final cameras = await availableCameras();


----------------
    if (cameras.isEmpty) {
      return;
    }

    _cameraController = CameraController(
      // 0:back, 1: front
      cameras[_isSelfieMode ? 1 : 0],
      ResolutionPreset.ultraHigh,
    );

    await _cameraController.initialize();
  }
----------------


initPermissions();
  }
  
-----------------
  Future<void> _toggleSelfieMode() async {
    _isSelfieMode = !_isSelfieMode;
    await initCamera();
    setState(() {});
  }
-----------------

  @override
  Widget build(BuildContext context) {
  
  
children: [
                  CameraPreview(_cameraController),
                  
                  -------------------
                  Positioned(
                    top: Sizes.size20,
                    left: Sizes.size20,
                    child: IconButton(
                      color: Colors.white,
                      onPressed: _toggleSelfieMode,
                      icon: const Icon(
                        Icons.cameraswitch,
                        color: Colors.black,
                                          -------------------
                      ),
                    ),
                  ),
                ],
              ),
      ),
    );

 

19.4 Flash Mode

 

  bool _isSelfieMode = false;


---------------
  late FlashMode _flashMode;
---------------


  late CameraController _cameraController;

  // 카메라 초기화 메소드

  Future<void> initCamera() async {
  
  

ResolutionPreset.ultraHigh,
    );

    await _cameraController.initialize();
-----------------
    _flashMode = _cameraController.value.flashMode;
    -----------------
    
    
  }

  //권한 관련 메소드
  Future<void> initPermissions() async {



setState(() {});
  }


-----------------
  Future<void> _setFlashMode(FlashMode newFlashMode) async {
    await _cameraController.setFlashMode(newFlashMode);
    _flashMode = newFlashMode;
    setState(() {});
  }
-----------------


  @override
  Widget build(BuildContext context) {
  
  

CameraPreview(_cameraController),
                  Positioned(
                    top: Sizes.size20,
                    
                    
                    
                    -----------------
                    right: Sizes.size20,
                    child: Column(
                      children: [
                        IconButton(
                          color: Colors.black,
                          onPressed: _toggleSelfieMode,
                          icon: const Icon(
                            Icons.cameraswitch,
                          ),
                        ),
                        Gaps.v10,
                        IconButton(
                          color: _flashMode == FlashMode.off
                              ? Colors.purple
                              : Colors.black,
                          onPressed: () => _setFlashMode(FlashMode.off),
                          icon: const Icon(
                            Icons.flash_off_rounded,
                          ),
                        ),
                        Gaps.v10,
                        IconButton(
                          color: _flashMode == FlashMode.always
                              ? Colors.purple
                              : Colors.black,
                          onPressed: () => _setFlashMode(FlashMode.always),
                          icon: const Icon(
                            Icons.flash_on_rounded,
                          ),
                        ),
                        Gaps.v10,
                        IconButton(
                          color: _flashMode == FlashMode.auto
                              ? Colors.purple
                              : Colors.black,
                          onPressed: () => _setFlashMode(FlashMode.auto),
                          icon: const Icon(
                            Icons.flash_auto_rounded,
                          ),
                        ),
                        Gaps.v10,
                        IconButton(
                          color: _flashMode == FlashMode.torch
                              ? Colors.purple
                              : Colors.black,
                          onPressed: () => _setFlashMode(FlashMode.torch),
                          icon: const Icon(
                            Icons.flashlight_on_rounded,
                                                -----------------
                                                
                                                
                          ),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
      ),
    );
  }
}

 

19.5 Recording Animation

positioned는 stack 안에서의 독립적인 공간

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

class _VideoRecordingScreenState extends State<VideoRecordingScreen>

----------------
    with SingleTickerProviderStateMixin {
    ----------------
    
  //flag
  bool _hasPermission = false;


    ----------------
  //동영상 버튼용
  late final AnimationController _animationController = AnimationController(
    vsync: this,
    duration: const Duration(
      microseconds: 300,
    ),
  );

  late final Animation<double> _buttonAnimation =
      Tween(begin: 1.0, end: 1.3).animate(_animationController);
    ----------------
    
   _flashMode = newFlashMode;
    setState(() {});
  }
  
  
    ----------------
    
  //손가락 한 번 누르면 동영상 녹화
  void _onTapDown(TapDownDetails _) {
    _animationController.forward();
  }

  //손가락 한 번 더 누르면 녹화 중지
  void _onTapUp(TapUpDetails _) {
    _animationController.reverse();
  }
    ----------------
    
    
    
  @override
  Widget build(BuildContext context) {
  
  
  
 
 
  late FlashMode _flashMode;



icon: const Icon(
                            Icons.flashlight_on_rounded,
                          ),
                        ),
                      ],
                    ),
                  ),
                  
                  
                  -------------------
                  Positioned(
                    bottom: Sizes.size40,
                    child: GestureDetector(
                      onTapDown: _onTapDown,
                      onTapUp: _onTapUp,
                      child: ScaleTransition(
                        scale: _buttonAnimation,
                        child: Container(
                          width: Sizes.size80,
                          height: Sizes.size80,
                          decoration: BoxDecoration(
                            shape: BoxShape.circle,
                            color: Colors.red.shade400,
                                              -------------------
                          ),
                        ),
                      ),
                    ),

 

class _VideoRecordingScreenState extends State<VideoRecordingScreen>

-----------------
    //두개 이상 애니일 때는 이거
    with
        TickerProviderStateMixin {
-----------------


  //flag
  bool _hasPermission = false;
  
  
  ----------------------
  //동영상 버튼용
  late final AnimationController _buttonAnimationController =
      AnimationController(
    vsync: this,
    duration: const Duration(
      microseconds: 300,
    ),
  );

  late final Animation<double> _buttonAnimation =
      Tween(begin: 1.0, end: 1.3).animate(_buttonAnimationController);

  late final AnimationController _progressAnimationController =
      AnimationController(
          vsync: this,
          duration: const Duration(
            seconds: 10,
          ),
          lowerBound: 0.0,
          upperBound: 1.0);
----------------------


  late FlashMode _flashMode;
  
  
  
       await initCamera();
      setState(() {});
    }
  }



    ------------------------
  @override
  void initState() {
    super.initState();
    initPermissions();
    _progressAnimationController.addListener(() {
      setState(() {});
    });
    //addStatusListener는 애니메이션이 끝난 걸 알려주는 이벤트 리스너
    _progressAnimationController.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _stopRecording();
      }
    });
    ------------------------
    
    //iOS권한문제 수정
    // setState(() {});
  }
  
  
  
   _flashMode = newFlashMode;
    setState(() {});
  }


    ------------------------
  //손가락 한 번 누르면 동영상 녹화(계속 누르고 있어야함)
  void _startRecording(TapDownDetails _) {
    _buttonAnimationController.forward();
    _progressAnimationController.forward();
  }

  //손을 때던 10초 지나면 녹화 중지
  void _stopRecording() {
    _buttonAnimationController.reverse();
    _progressAnimationController.reset();
  }
      ------------------------
      
      
      

  @override
  Widget build(BuildContext context) {
  
  
  
      Icons.flashlight_on_rounded,
                          ),
                        ),
                      ],
                    ),
                  ),
                  Positioned(
                    bottom: Sizes.size40,
                    child: GestureDetector(
                      onTapDown: _startRecording,
                      
                      ----------------------
                      onTapUp: (details) => _stopRecording(),
                                            ----------------------
                                            
                                            
                      child: ScaleTransition(
                        scale: _buttonAnimation,
                        child: Stack(
                          alignment: Alignment.center,
                          children: [
                          
                          
                          ----------------------
                            SizedBox(
                              width: Sizes.size80 + Sizes.size14,
                              height: Sizes.size80 + Sizes.size14,
                              child: CircularProgressIndicator(
                                color: Colors.red.shade400,
                                strokeWidth: Sizes.size6,
                                //value로 동영상 진행상태 표시
                                value: _progressAnimationController.value,
                              ),
                            ),
                            ----------------------
                            
                            
                            
                            Container(
                              width: Sizes.size80,
                              height: Sizes.size80,

 

 

19.6 startVideoRecording

 

  Future<void> initCamera() async {
    final cameras = await availableCameras();

    if (cameras.isEmpty) {
      return;
    }

    _cameraController = CameraController(
      // 0:back, 1: front
      cameras[_isSelfieMode ? 1 : 0],
      ResolutionPreset.ultraHigh,


----------------
      //Android 에뮬레이터 동영상 저장 플레이 안될때 버그 픽스
      enableAudio: false,
    );
----------------



    await _cameraController.initialize();


----------------
    //iOS만을 위한 설정
    await _cameraController.prepareForVideoRecording();
----------------


_flashMode = _cameraController.value.flashMode;
    //iOS권한문제 수정
    // setState(() {});
  }
  
  
  
  //손을 때던 10초 지나면 녹화 중지
  Future<void> _stopRecording() async {
    if (!_cameraController.value.isRecordingVideo) return;

    _buttonAnimationController.reverse();
    _progressAnimationController.reset();

    final video = await _cameraController.stopVideoRecording();
    
    
    
------------------
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => VideoPreviewScreen(
          video: video,
        ),
      ),
    );
  }
  ------------------
  
  
  
  
------------------
  @override
  void dispose() {
    _progressAnimationController.dispose();
    _buttonAnimationController.dispose();
    _cameraController.dispose();

    super.dispose();
  }
------------------
  @override
  Widget build(BuildContext context) {
    return Scaffold(

 

 

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;

  Future<void> _initVideo() async {
    _videoPlayerController = VideoPlayerController.file(
      File(widget.video.path),
    );

    await _videoPlayerController.initialize();

    await _videoPlayerController.setLooping(true);

    await _videoPlayerController.play();
  }

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

  //dispose 메소드는 항상 구현해주자
  @override
  void dispose() {
    _videoPlayerController.dispose();

    super.dispose();
  }

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

 

 

19.7 GallerySaver

 

여기에다가 permission handler 쓰지 않고 안드로이드 관련 마이크, 카메라도 허가 가능

 

class _VideoPreviewScreenState extends State<VideoPreviewScreen> {
  late final VideoPlayerController _videoPlayerController;

------------------
  bool _savedVideo = false;
------------------


  Future<void> _initVideo() async {
    _videoPlayerController = VideoPlayerController.file(
    
    
    
    
    super.dispose();
  }
  
  
---------------
  Future<void> _saveToGallery() async {
    if (_savedVideo) return;

    await GallerySaver.saveVideo(widget.video.path, albumName: "TikTok Clone");

    _savedVideo = true;

    setState(() {});
  }
---------------



      backgroundColor: Colors.black,
      appBar: AppBar(
        title: const Text("preview video"),
        
        
        ---------------
        actions: [
          IconButton(
            onPressed: _saveToGallery,
            icon: FaIcon(_savedVideo
                ? FontAwesomeIcons.check
                : FontAwesomeIcons.download),
          ),
        ],
      ),
      body: _videoPlayerController.value.isInitialized
          ? VideoPlayer(_videoPlayerController)
          : null,
          ---------------
          
          
          
    );
  }
}

  @override
  Widget build(BuildContext context) {

 

19.8 ImagePicker

 

class VideoPreviewScreen extends StatefulWidget {
  final XFile video;
  
  
  -------------
  //동영상 저장 아이콘은 사진찍고 확인할 스크린에서만 표시
  final bool isPicked;
---------------

  const VideoPreviewScreen({
    super.key,
    required this.video,
    required this.isPicked,
    
    
    
            title: const Text("preview video"),
        actions: [
        
        ------------------
          //동영상 저장 아이콘은 사진찍고 확인할 스크린에서만 표시
          if (!widget.isPicked)
                  ------------------
                  
                  
            IconButton(
              onPressed: _saveToGallery,
              icon: FaIcon(
                _savedVideo

 

    final video = await _cameraController.stopVideoRecording();


----------------
    if (!mounted) return;
----------------


    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => VideoPreviewScreen(
          video: video,
          
          ----------------
          //동영상 저장 아이콘은 사진찍고 확인할 스크린에서만 표시
          isPicked: false,
          ----------------
        ),
      ),
    );
  }
  
  
  
-------------------
  //갤러리 아이콘에서 비디오 보는 메소드
  Future<void> _onPickVideoPressed() async {
    final video = await ImagePicker().pickVideo(
      source: ImageSource.gallery,
    );
    if (video == null) return;

    if (!mounted) return;

    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => VideoPreviewScreen(
          video: video,
          //동영상 저장 아이콘은 사진찍고 확인할 스크린에서만 표시
          isPicked: true,
        ),
      ),
    );
  }
-------------------


     icon: const Icon(
                            Icons.flashlight_on_rounded,
                          ),
                        ),
                      ],
                    ),
                  ),
                  
    -------------------
    
                  Positioned(
                    bottom: Sizes.size40,
                    width: MediaQuery.of(context).size.width,
                    child: Row(
                      children: [
                        //빈 공간처럼 표시되어 레코딩 버튼을 중간으로 이동
                        const Spacer(),
                        GestureDetector(
                        -------------------
                        
                        
                          onTapDown: _startRecording,
                          onTapUp: (details) => _stopRecording(),
                          child: ScaleTransition(
                            scale: _buttonAnimation,
                            
                            -------------------
                            child: Stack(
                              alignment: Alignment.center,
                              -------------------
                              
                              
                              children: [
                                SizedBox(
                                  width: Sizes.size80 + Sizes.size14,
                                  height: Sizes.size80 + Sizes.size14,
                                  child: CircularProgressIndicator(
                                    color: Colors.red.shade400,
                                    strokeWidth: Sizes.size6,
                                    //value로 동영상 진행상태 표시
                                    value: _progressAnimationController.value,
                                  ),
                                ),
                                
                        
                                Container(
                                  width: Sizes.size80,
                                  height: Sizes.size80,
                                  decoration: BoxDecoration(
                                    shape: BoxShape.circle,
                                    color: Colors.red.shade400,
                                  ),
                                ),
                              ],
                            ),
                          ),
                        ),
                        
                        -------------------
                        Expanded(
                          child: Container(
                            alignment: Alignment.center,
                            child: IconButton(
                                onPressed: _onPickVideoPressed,
                                icon: const FaIcon(
                                  FontAwesomeIcons.image,
                                  color: Colors.black,
                                  -------------------
                                  
                                  
                                )),
                          ),
                        )
                      ],
                    ),
                  )
                ],
              ),
      ),
    );
  }
}

  @override
  Widget build(BuildContext context) {
    return Scaffold(

 

19.9 AppLifecycleState

지금 버전의 카메라 플러그인의 lifecycle은 수동으로 바꿔줘야한다. state를 우리가 따로 관리 해줘야한다.

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

class _VideoRecordingScreenState extends State<VideoRecordingScreen>
    //두개 이상 애니일 때는 이거
    with
        TickerProviderStateMixin,
        
        
---------------------
        // camera가 백그라운드에 있을때는 dispose하는 것
        WidgetsBindingObserver {
        ---------------------
        
        
  //flag
  bool _hasPermission = false;

  bool _isSelfieMode = false;
  
  
  
    @override
  void initState() {
    super.initState();
    initPermissions();
    
    
    ---------------------
    // camera가 백그라운드에 있을때는 dispose하는 것
    WidgetsBinding.instance.addObserver(this);
    ---------------------
    
    
    
    _progressAnimationController.addListener(() {
      setState(() {});
    });
    
    
       _cameraController.dispose();

    super.dispose();
  }

  @override
  
      ---------------------
  // camera가 백그라운드에 있을때는 dispose하는 것
  Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
    //권한 관련 버그 픽스

    if (state == AppLifecycleState.inactive) {
      // 오직 카메라가 initialize디어을 때만 dispose 호출하기
      if (!_cameraController.value.isInitialized) return;
      _cameraController.dispose();
    } else if (state == AppLifecycleState.resumed) {
      await initCamera();
      setState(() {});
    }
  }
    ---------------------
    
    
  //갤러리 아이콘에서 비디오 보는 메소드
  Future<void> _onPickVideoPressed() async {

 

 

 

//어플을 새로깔 때 권한 다시 묻는데 지금 코드 순서는 권한을 가져온 후 카메라를 initialize하기 때문에 플러터는 어픟이 비활성화 되었다고 생각한다.

즉, 앱은 유저가 떠났을 때만 비활성화되는게 아니다. 권한 요청창이 앺 앞에서 나타날 때도 고려해야함

그래서 이런식으로 고쳐 줌

1. 우리가 권한을 가지고 있지 않은 것을(즉, camera controller가 initialize 되지 않은 것) 확인하기

  // 카메라 초기화 메소드

  Future<void> initCamera() async {
    final cameras = await availableCameras();

    if (cameras.isEmpty) {
      return;
    }

    _cameraController = CameraController(
      // 0:back, 1: front
      cameras[_isSelfieMode ? 1 : 0],
      ResolutionPreset.ultraHigh,

      //Android 에뮬레이터 동영상 저장 플레이 안될때 버그 픽스
      enableAudio: false,
    );

    await _cameraController.initialize();

    //iOS만을 위한 설정
    await _cameraController.prepareForVideoRecording();

    _flashMode = _cameraController.value.flashMode;
    //iOS권한문제 수정
    // setState(() {});
    
    --------------
    setState(() {});
        --------------
  }

  //권한 관련 메소드
  Future<void> initPermissions() async {


@override
  // camera가 백그라운드에 있을때는 dispose하는 것
  void didChangeAppLifecycleState(AppLifecycleState state) {
  
  
  ----------------
    //권한 관련 버그 픽스1
    if (!_hasPermission) return;
    //권한 관련 버그 픽스2
    // 오직 카메라가 initialize되어을 때만 dispose 호출하기
    if (!_cameraController.value.isInitialized) return;
    ----------------
    
    
    if (state == AppLifecycleState.inactive) {
      _cameraController.dispose();
    } else if (state == AppLifecycleState.resumed) {
      initCamera();
    }
  }

  //갤러리 아이콘에서 비디오 보는 메소드
  Future<void> _onPickVideoPressed() async {

didChangeAppLifecycleState future없애고 initCamera에 setState옮긴건 관계는 없는데 

옮긴다면 이런식으로 옮길것

 

 

직접 카메라 UI 구현하고 싶다면 camera package보다 camerAwesome package를 더 추천한다. 더 feature가 많다.

728x90
반응형
Comments