|| 쇼츠는 가벼워야 한다
안녕하세요 TVING에서 안드로이드 개발을 하고 있는 이동호입니다. 쇼츠 스쿼드에서는 사용자들이 숏폼 콘텐츠를 더욱 쾌적하게 즐길 수 있도록 다양한 시청 경험과 동선을 고민하고 개선하는 일을 하고 있습니다.
티빙은 2024년 12월 쇼츠 MVP 출시 이후, 단계적으로 기능을 확장하며 사용자 경험을 고도화해왔습니다. 예능, 스포츠, 뉴스 등 다양한 카테고리별 탐색을 지원하고, 유사한 테마의 콘텐츠를 가로로 이어 소비할 수 있는 체인 기능을 출시하기도 했습니다. 또한 지난 여름에는 숏드라마까지 영역을 확장하기도 했죠.
티빙의 쇼츠/숏드라마 기능들
이처럼 다양한 기능을 순차적으로 배포해 나가는 과정에서 세로형 플레이어는 단순한 재생 컴포넌트를 넘어 여러 비즈니스 요구사항을 동시에 만족해야 하는 핵심 기능이 되었습니다.
이번 포스팅에서는 티빙 쇼츠 경험의 중심에 있는 세로형 플레이어를 구현하면서 어떤 문제를 마주했고 이를 어떻게 해결해왔는지를 정리해보려 합니다.
Single Instance Player
플레이어 메모리 관리를 먼저 얘기해보려합니다.
쇼츠 플레이어는 전체 화면을 플레이어 뷰가 차지하는 UI를 제공해야하기 때문에 ViewPager또는 Pager컴포넌트를 사용합니다. MVP 버전에서는 ViewPager의 각 아이템마다 플레이어 객체를 생성·관리하는 구조를 사용했습니다.
초기 쇼츠플레이어 구조
하지만 이런 구조는 일부 저사양 단말에서는 플레이어 객체 메모리 이슈로 OOM이 발생하고, 디코더 초기화 실패로 영상이 재생되지 않기도 합니다. 인접한 여러 개의 플레이어들이 개별적으로 디코더 할당을 요청하면서, 단말이 동시에 초기화할 수 있는 하드웨어 디코더 자원 한계를 초과했기 때문이죠.
또한 여러 개의 SurfaceView 가 각 플레이어에 결합하게 되면서 화면 전환 시 View 계층과의 렌더링 흐름을 복잡하게 만들어 UI 관리 측면에 어려움을 겪을 수도 있습니다.
저사양 단말에서 쇼츠 재생시 오류 발생
SurfaceView는 별도의 Surface 레이어에서 일반 View 계층과 분리되어 있다 보니 스크롤이나 전환 과정에서 Z-order 문제나 attach/detach 타이밍 등이 고려할만한 이슈였습니다.
그래서 하나의 플레이어만 사용하기로 했습니다.
Media3의 Video Renderer는 디코딩된 프레임을 Surface에 전달하는 구조이기 때문에 렌더링 타겟은 View가 아니라 Surface로 추상화됩니다. 이 덕분에 플레이어 객체를 재사용하면서도 Surface만 교체하여 출력 대상을 전환할 수 있습니다.
단일 플레이어를 이용한 쇼츠 플레이어 아키텍쳐
Fragment 에 하나의 플레이어만 올려놓고, 페이저 컴포넌트의 화면 전환 시점과 스크롤 전환 시점에 View 참조만 변경해주는 방식입니다.
이 때 다음 재생할 아이템의 프레임 정보를 미리 알 수 없기 때문에, 서버로부터 첫 프레임 이미지를 내려받아 미리 inflate해주는 식으로 구현해야 다음 아이템으로 넘어가는 사용자 경험을 끊킴없이 부드럽게 유지할 수 있습니다.
인접한 쇼츠의 첫 프레임 이미지는 스크롤이 완료된 SCROLL_STATE_IDLE 시점에 inflate를 수행하여 UI 스레드 사용을 분산하는 것을 권장합니다.
이로써 쇼츠 플레이어는 하나의 객체만을 이용해 페이지 간 미디어 전환을 처리하도록 구성할 수 있었고, 상하 스와이프뿐만 아니라 좌우 탐색이 가능한 체인을 구현할 때도 추가적인 메모리 할당 없이 기능을 확장할 수 있었습니다.
Player Lifecycle
쇼츠 플레이어는 홈, 상세, 검색 등 다양한 화면에서 호출될 수 있습니다. 각 화면의 생애주기에 플레이어를 직접 종속시킬 경우, attach/detach하는 과정에서 상태 불일치나 리소스 누수가 발생할 수 있기 때문에 플레이어의 생애주기 관리가 중요해집니다.
검색과 쇼츠탭에서 모두 호출되는 쇼츠 플레이어
쇼츠 탭에서 검색 탭 등 다른 화면으로 이동하면 기존 플레이어를 release하여 디코더·Surface·Buffer 같은 미디어 리소스를 메모리에서 즉시 정