걸어서 딴 영토를 8초짜리 영상으로 만들어 공유하는 기능을 설계하고 있었습니다. iOS, 안드로이드, 웹 세 곳에 붙여야 합니다.
정석은 분명합니다. 각 플랫폼의 네이티브 인코더를 씁니다.
iOS AVAssetWriter
Android MediaCodec
Web WebCodecs품질이 가장 좋고, 오프라인에서도 되고, 속도도 제일 빠릅니다. 어느 리뷰어에게 물어도 이게 맞다고 할 겁니다.
저는 이걸 안 했습니다. 이유는 기술이 아니었습니다.
숫자 하나
이 앱의 안드로이드 활성기기는 53대입니다.
그 숫자를 앞에 두고 다시 보면, 네이티브 3벌은 이런 뜻입니다. 연출을 한 줄 고칠 때마다 세 번 고치고 세 번 검증합니다. 인코더 함정도 세 세트입니다. 카메라 이징을 0.2초 당기고 싶으면 Swift, Kotlin, TypeScript를 각각 열어야 합니다.
53대에서 그 유지비는 회수되지 않습니다. 품질이 조금 더 좋아지는 것으로는 안 됩니다.
당신의 아키텍처 결정에서, 마지막으로 사용자 수를 변수로 넣어본 게 언제입니까?
대신 고른 것
연출을 웹에 한 벌만 만들었습니다. 앱은 화면 밖 WebView로 그 페이지를 띄우고, 페이지가 만든 mp4를 받아옵니다.
lib/reel/draw.ts drawFrame(ctx, scene, t) ← 순수 함수
├─ 랜딩 페이지 requestAnimationFrame 으로 t 를 흘려보냄
└─ /render 셸 t 를 1/30 초씩 고정 스텝 → 결정적 인코딩같은 함수가 웹에서 재생되고 앱에서 인코딩됩니다. 연출을 고치면 세 플랫폼에 동시에 반영됩니다. 배포도 웹 한 번입니다.
부수 효과가 하나 더 있었습니다. 랜딩 페이지와 공유 영상이 같은 코드에서 나오므로 절대 어긋나지 않습니다. 링크 미리보기가 영상과 다른 그림을 보여주는 사고가 구조적으로 불가능합니다.
이 선택을 죽일 수 있었던 전제
전부 하나에 걸려 있었습니다. 임베드 WebView 안에서 WebCodecs 가 실제로 동작하는가.
Safari 17+ 와 Chrome 94+ 가 지원한다는 건 압니다. 하지만 WKWebView 는 Safari 가 아니고, 안드로이드 WebView 는 Chrome 이 아닙니다. 문서로는 답이 안 나옵니다.
그래서 코드를 쓰기 전에 반나절짜리 스파이크를 돌렸습니다. 캔버스 10프레임을 실제로 H.264 로 인코딩해 봤습니다.
| 대상 | VideoEncoder | isConfigSupported | 결과 |
|---|---|---|---|
| iOS WKWebView | true | true | 10 chunks / 39,594 B |
| Android WebView 145 | true | true | 10 chunks / 55,433 B |
안드로이드는 하드웨어 인코더가 없는 에뮬레이터에서 소프트웨어 폴백으로 통과했습니다. 최악 조건을 넘긴 셈입니다.
여기서 false 가 하나라도 나왔다면 이 구조는 통째로 폐기였습니다. 그래서 설계 문서를 쓰기 전에 이걸 먼저 했습니다.
값은 치렀습니다
공짜는 아닙니다. 정직하게 적으면 이렇습니다.
- 네트워크가 필요합니다. 앱이 원격 페이지를 띄우므로 오프라인에서는 영상이 안 나옵니다(정지 카드로 폴백합니다).
- 웹 배포가 구버전 앱을 깨뜨릴 수 있습니다. payload 에 버전을 넣고, 렌더러가 모르는 버전이면 즉시 실패를 돌려주게 했습니다.
- 디버깅이 한 단계 멉니다. 앱 안에서 도는 웹 페이지라 로그가 두 프로세스에 흩어집니다.
그리고 실제로 물렸습니다. 브리지가 postMessage 를 소유자에서 떼어내 호출해 WKWebView 가 조용히 거부한 적이 있고, 뷰 계층 밖의 WebView 가 콘텐츠 프로세스를 못 받아 페이지 오류처럼 안 보이는 에러가 난 적도 있습니다. 네이티브 3벌이었다면 없었을 문제들입니다.
그래도 다시 이렇게 합니다
숫자로 답이 나옵니다. 연출은 이번 작업에서 다섯 번 고쳤습니다 — 색 대비, 오프닝 훅, 지도 밝기, 워터마크 위치, 카운터 중복. 네이티브 3벌이었다면 열다섯 번입니다. 그리고 그중 두 번은 실제로 눈으로 보고서야 잡힌 것이라, 세 벌이었다면 세 번 다 따로 발견해야 했습니다.
자가진단 3가지
- 당신의 사용자가 100명이라면 그 아키텍처를 고르겠습니까? 정석은 규모를 가정하고 있습니다. 그 가정이 당신에게 참인지 확인하세요.
- 한 번 고칠 때 몇 곳을 고칩니까? 그 수가 곧 앞으로의 속도입니다. 기능 수가 아니라 수정 횟수 × 중복 벌수가 유지비입니다.
- 구조 전체가 걸린 미검증 전제가 있습니까? 있다면 설계 문서보다 먼저 반나절을 써서 재보세요. 문서를 다 쓰고 나면 되돌리기 어려워집니다.
솔직한 부분
품질은 네이티브가 낫습니다. 인코딩도 더 빠를 겁니다. 저는 그걸 알고 안 골랐습니다.
규모를 안 보고 정석을 따르는 실수는 제품 바깥에서도 같습니다 — 내 병목은 한 번도 제품이 아니었다에서 같은 착각을 다뤘습니다.
그리고 이건 "작은 팀이니까 대충"이 아닙니다. 53대라는 숫자가 설계에 넣어야 할 정보라는 뜻입니다. 그 숫자를 안 보고 정석을 따랐다면, 저는 앞으로 모든 연출 수정을 세 번씩 하면서 왜 이렇게 느리지 의아해했을 겁니다.
당신의 다음 아키텍처 결정에, 사용자 수를 한 번 넣어보세요. 답이 바뀝니까?