본문 바로가기
개발/플러터

[플러터] 비디오 랜더링 이슈 수정기(video-player,dispose)

by 핸디(Handy) 2023. 9. 24.

들어가며

이번 글에서는 플러터에서 20개의 비디오를 재생하면서 발생한 오류를 추적하여 개선을 한 경험에 대한 글입니다.

플러터의 video player부터 사용하는 패키지의 디테일한 설정 및 사용법을 다루진 않습니다.

대략적인 사용법과 이런 점이 문제여서 이렇게 바꿨다의 수준으로만 설명합니다.

에러부터 살펴보자

웹이나 앱 개발자들은 서버개발자와 달리 에러를 추적하기기 쉽지 않습니다.

또한 에러 발생한 것은 알아도 유저의 디바이스나 여러 외부환경에 의해서 달라질 수 있는 만큼 외부 에러추적 서비스를 이용하는 경우가 많은데요.

제가 만들어놓은 앱도 Sentry를 사용하고 있었습니다.

그런중에 어느 순간 반복적으로 특정 에러가 대한 리포트가 오기 시작하고, 실제 유저의 QA문의가 들어와서 해당 이슈를 파헤쳐보게 되었습니다.

OutOfMemoryError android.media.MediaCodec in getBuffer

크로스플랫폼의 어려움은 이런 곳에서 나옵니다. 

바로 에러로그를 따라가다보면 네이티브단의 에러를 봐야 한다는 점인데요. 이 경우에도 Sentry상에서 어떤 호출로 인해 발생했는지 트래킹을 할 수가 없습니다.

결국 MediaCodec이라는 단어 하나만 가지고 에러를 추적해보기로 합니다.

ㅜㅜ 나는 플러터를 사용하는데 왜 안드로이드가 나와

MediaCodec

우선 바로 검색부터 해봅니다.

ㅋㅋㅋ 네 큰일났습니다. 

"android.media.MediaCodec in getBuffer" 이걸로만 검색해 봐도 미디어를 변환하면서 나오는 이슈에 대해 다룬 뿐 현재 개발되어 있는 기능과 상관없는 이슈인 것 같습니다.

유저의 소리

이젠 유저가 어떤 행동을 하는 와중에 에러가 발생했는지 찾아가 봅시다.

전달받은 내용으로는 올라와있는 강의영상을 반복해서 보는 와중에 어느 순간 영상재생이 안된다는 것이었습니다.

그래서 재현을 해보았더니 대략 25개 정도의 영상을 재생, 중지, 재생 중지 하니깐 같은 이슈가 발생했습니다.

영상이라니, 뭔가 동영상을 재생하면서 mediaCodec상에서 에러가 있나 보구나. 

플러터에서 dispose를 안 해줘서 OOM이 떴구나라고 생각을 했습니다.

이제 문제점은 찾았습니다. 그러니 코드와 재현을 해보면서 에러를 해결해 보도록 합시다.

문제 해결

일단 문제 해결에 앞서 플러터의 dispose에 대해 알아보겠습니다.

dispose

플러터의 공식문서에 따르면 특정 객체가 트리에서 영구적으로 제거될 때 호출한다고 되어있습니다.

 

 

dispose method - State class - widgets library - Dart API

void dispose() Called when this object is removed from the tree permanently. The framework calls this method when this State object will never build again. After the framework calls dispose, the State object is considered unmounted and the mounted property

api.flutter.dev

추가로 스택오버플로우에서 찾아봤는데요.

일반적으로 알아서 해주는데 ( dispose() method called automatically from stateful if not defined. )

명시적으로 선언해야 할 때가 있다고 하네요. (In some cases dispose is required for example in CameraPreview, Timer... )

물론 지금 사용하고 있는 미디어의 경우 dispose 사용을 권장하고 있고 또 그렇게 구현을 해놓은 상태입니다. 그런데 과연 무엇이 문제였을까요?

서비스의 구조

앱을 간단히 살펴보면 여러 개의 카드가 있고 해당 카드의 펼치기를 클릭하면 영상이 나오는 UI를 가지고 있습니다.

그리고 유저는 원하는 카드를 펼쳐서 영상을 보면 되는 것이었죠.

문제가 되는 순간은 강의를 복습하고 싶었던 유저가 한두 가지의 영상을 보는 것에 그치지 않고 20개 30개를 계속 보는 상황이 있었다는 것입니다. 

변명하자면  QA 할 때도 해당 문제를 인식하지 못했습니다. 테스트라고 해봐야 3개의 영상을 재생하고 소리를 켜보고 중지시켜 보고 이 정도 수준이었기 때문이었죠.

아무튼 다시 돌아가서 현재 UI를 보면

UX를 위해 영상의 일정 부분이 가려지게 되면 영상을 중지하는 기능을 만들었습니다.

visibility_detector라는 패키지를 사용해서 해당 비율이 50퍼센트 이하면 재생을 중지하도록 만들었습니다.

근데 여기서 문제는 바로 재생이 중지된다는 점이었습니다.

중지가 될 뿐이지 해당 위젯이 사라지는 게 아니기 때문에 dispose 메서드가 터지지 않았던 것입니다.

이렇게 영상을 계속 보다 보면 video 객체가 수십 개가 쌓이고 더 이상 처리하지 못하는 순간이 와서 멈추게 되는 현상이 일어났던 것입니다.

기능 개선

그래서 기능을 개선해 봅시다.

안 보이면 그냥 해당 위젯을 지워버리고 보이면 다시 랜더링 해주면 문제가 깔끔하게 해결될 것 같습니다.

코드를 보기 좋게 수정했는데요.

isVisible이란 변수를 만들고 visiblePercentage에 따라 값을 조정해 주었습니다. 그리고 이에 따라서 VideoWidget를 랜더링 하거나 SizedBox를 랜더링 합니다.

SizedBox는 VideoWidget과 같은 크기를 가진 위젯인데요.

화면에 안보인 경우 위젯을 지워버린다면 해당 Column의 Height에 변화가 생기고 결국 스크롤이 변화를 생기는 사이드이펙트를 방지하기 위해서 넣었습니다.

그럼 여기서 끝인가? 아닙니다.

우린 위젯을 지워버렸습니다. 그리고 다시 화면으로 가서 위젯을 그리는데, 문제는 처음부터 재생된다는 점입니다.

그래서 마지막 재생시간을 저장하고 초기 랜더링할 때 이 값으로 시작시간을 설정해 주는 기능도 만들어봅니다.

VideoWidget의 부모위젯에서 initalPlayTime를 관리합니다.

VideoWidget에서는 initalPlayTimer으로 초기 시간을 설정합니다.

그리고 리스너를 달아서 부모위젯에게 변경된 시간을 알려줍니다.

이렇게 하면 최종 결과물은 아래와 같이 됩니다.

최종 결과

이렇게 개선한 결과 크게 2가지 이점이 생겼습니다.

첫 번째로 해당 화면의 초반 딜레이가 사라졌습니다. 

기존에는 해당 화면에 들어오면 수십 개의 영상이 랜더링을 시작하면서 버벅거림이 있었는데요.

지금은 화면에 보이는 것만 가져오게 되면 빠른 초기 속도를 가져갈 수 있게 되었습니다.

두 번째로는 이 글을 쓴 이유대로 수십 개의 영상을 재생해도 더 이상 문제가 되지 않습니다. ㅋㅋ

 

마무리

오랜만에 플러터에 관한 글을 썼습니다.

아무래도 자바스크립트, 리액트와 달리 플러터와 다트를 다루는 제 수준이 아직 낮아서 글을 쓰기가 참 어렵네요.

그래도 이렇게 글을 남기며 이슈를 정리하고 또 공유하다 보면 제 수준이 오르지 않겠습니다. ㅎㅎ

끝.

댓글