네이티브 재생 방식과 무엇이 다른지, 그리고 DASH·HLS 같은 적응형 스트리밍이 왜 MSE 위에서 동작하는지를 정리합니다."> 네이티브 재생 방식과 무엇이 다른지, 그리고 DASH·HLS 같은 적응형 스트리밍이 왜 MSE 위에서 동작하는지를 정리합니다."> 네이티브 재생 방식과 무엇이 다른지, 그리고 DASH·HLS 같은 적응형 스트리밍이 왜 MSE 위에서 동작하는지를 정리합니다.">
<video src="..."> 만으로는 부족할 때웹에서 영상을 재생하는 가장 단순한 방법은 모두가 아는 그것입니다.
브라우저는 src에 적힌 파일을 받아 디코딩하고, 알아서 화면에 그려 줍니다. 우리가 할 일은 URL 한 줄을 적는 것뿐이죠. 작은 영상 하나를 보여주는 데는 이만한 방법이 없습니다.
그런데 우리가 일상적으로 쓰는 OTT·라이브 스트리밍 서비스는 이렇게 동작하지 않습니다. 네트워크 상태에 따라 화질이 알아서 오르내리고, 긴 영상도 끊김 없이 흘러나오며, 라이브 방송은 "파일"이라는 개념조차 모호합니다. 단순한 <video src="...">로는 이런 경험을 만들 수 없습니다. 재생에 사용할 미디어 데이터를 우리가 직접, 그것도 조각조각 제어할 수 없기 때문입니다.
바로 이 지점에서 Media Source Extensions(MSE) 가 등장합니다. 이 글에서는 MSE가 무엇인지, 브라우저의 네이티브 재생과 무엇이 다른지, 그리고 DASH·HLS 같은 적응형 스트리밍이 왜 MSE 위에서 굴러가는지를 차근차근 정리해 보겠습니다.
Media Source Extensions(MSE) 는 자바스크립트가 직접 미디어 스트림을 생성해 <video>·<audio> 엘리먼트에 공급할 수 있게 해 주는 W3C 표준 API입니다. 한마디로, "재생할 데이터를 브라우저에게 떠먹여 줄 수 있는" API입니다.
기존 방식에서는 <video>에 URL을 주면 그 다음은 전부 브라우저의 몫이었습니다. 다운로드도, 버퍼 관리도, 디코딩도 브라우저가 알아서 합니다. MSE는 이 흐름에 자바스크립트가 끼어들 수 있는 통로를 열어 줍니다. 핵심 객체는 두 가지입니다.
MediaSource — <video>에 연결되는 "미디어 소스"를 대표하는 객체입니다. 실제 파일 URL 대신, 이 객체가 만들어 내는 가상의 소스를 비디오 엘리먼트에 물립니다.SourceBuffer — MediaSource에 속한 버퍼로, 자바스크립트가 받아 온 미디어 데이터(바이트 조각)를 여기에 밀어 넣습니다. 비디오 트랙, 오디오 트랙처럼 트랙별로 여러 개를 둘 수 있습니다.즉 MSE의 세계에서 자바스크립트는 네트워크에서 미디어 데이터를 직접 가져와(fetch) SourceBuffer에 차곡차곡 쌓고, 브라우저는 그 버퍼에 쌓인 데이터를 꺼내 디코딩·재생만 담당합니다. "데이터를 어디서·어떻게·언제 가져올지"의 결정권이 브라우저에서 자바스크립트로 넘어온 것, 이것이 MSE의 본질입니다.
MSE는 디코딩이나 화면 렌더링까지 자바스크립트에게 넘기는 것이 아닙니다. 디코딩·렌더링은 여전히 브라우저(그리고 그 아래의 하드웨어)가 담당합니다. MSE가 자바스크립트에게 넘겨주는 것은 "버퍼를 채우는 책임"입니다.
두 방식의 차이를 한눈에 비교해 보겠습니다.
| 구분 | 네이티브 재생 (<video src>) | MSE 재생 |
|---|---|---|
| 데이터 공급 주체 | 브라우저가 URL을 보고 알아서 다운로드 | 자바스크립트가 직접 fetch해서 SourceBuffer에 주입 |
| 제어권 | 브라우저에 위임 | 어떤 조각을, 어떤 화질로, 언제 넣을지 JS가 결정 |
| 버퍼 관리 | 브라우저 내부 로직 | JS가 버퍼 범위를 직접 관찰·조정 가능 |
| 화질 전환 | 기본적으로 불가(단일 소스) | 세그먼트 단위로 화질을 갈아끼울 수 있음 |
| 구현 난이도 | URL 한 줄 | 매니페스트 파싱·버퍼 관리·전환 로직 필요 |
| 적합한 상황 | 짧고 단순한 단일 영상 | 적응형 스트리밍, 라이브, 긴 VOD |
핵심 차이는 결국 제어권의 소재입니다. 네이티브 재생은 "URL을 줄 테니 알아서 틀어 줘"라면, MSE는 "데이터는 내가 줄 테니 너는 틀기만 해"입니다.
이 차이가 만들어 내는 실익은 분명합니다. 자바스크립트가 버퍼를 직접 채우므로, 네트워크가 느려지면 낮은 화질 조각을 넣고 빨라지면 높은 화질 조각을 넣을 수 있습니다. 버퍼가 얼마나 찼는지 관찰해 미리 더 받아 둘 수도 있고, 사용자가 시청하지 않을 구간은 굳이 받지 않을 수도 있습니다. 단일 파일에 묶여 있던 재생이, 조각 단위로 자유롭게 조립할 수 있는 재생으로 바뀌는 것입니다.
MSE가 실제로 어떻게 쓰이는지 가장 단순한 형태의 코드로 살펴보겠습니다.
흐름을 짚어 보면 이렇습니다.
MediaSource 생성 후 연결 — new MediaSource()로 만든 객체를 URL.createObjectURL()로 감싸 video.src에 넣습니다. 이제 비디오 엘리먼트의 소스는 파일이 아니라 이 가상 객체입니다.sourceopen 대기 — MediaSource는 비디오에 연결된 직후 곧바로 쓸 수 있는 것이 아니라, sourceopen 이벤트가 발생해 "열린" 상태가 되어야 합니다. 이 시점부터 버퍼를 다룰 수 있습니다.addSourceBuffer로 버퍼 생성 — 어떤 코덱·컨테이너의 데이터를 넣을지 MIME 타입 문자열로 명시해 SourceBuffer를 만듭니다. 브라우저는 이 정보로 디코더를 준비합니다.appendBuffer로 데이터 주입 — 네트워크에서 받아 온 ArrayBuffer를 버퍼에 넣습니다. 이 한 줄이 바로 "자바스크립트가 브라우저에게 데이터를 떠먹이는" 순간입니다.
appendBuffer는 비동기로 처리되며, 작업이 끝나면 SourceBuffer에 updateend 이벤트가 발생합니다. 하나의 조각을 다 넣기 전에 다음 조각을 또 넣으려 하면 오류가 나므로, 실제 구현에서는 updateend를 기다렸다가 다음 세그먼트를 넣는 큐(queue) 관리가 필수입니다. 실무에서 MSE 코드가 복잡해지는 이유의 상당 부분이 이 버퍼 상태 관리에 있습니다.
이 단순한 예제는 조각 하나를 넣는 데서 끝나지만, 실제 스트리밍에서는 이 과정을 수많은 세그먼트에 대해 반복하게 됩니다. 그리고 바로 그 반복이 적응형 스트리밍의 토대가 됩니다.
DASH와 HLS 같은 적응형 스트리밍(Adaptive Streaming) 의 공통 아이디어는 다음과 같습니다.
플레이어는 이 매니페스트를 읽고, 현재 네트워크 상황에 맞는 화질의 세그먼트를 골라 차례로 다운로드합니다. 네트워크가 나빠지면 다음 세그먼트는 낮은 화질로, 좋아지면 높은 화질로 — 이렇게 세그먼트 경계마다 화질을 갈아끼우는 것이 적응형 스트리밍의 핵심입니다. 이런 화질 결정 로직을 ABR(Adaptive Bitrate) 이라고 부릅니다.
여기서 자연스러운 질문이 생깁니다. 세그먼트를 골라 받아 와서, 화질을 바꿔 가며 하나의 연속된 재생으로 이어 붙이는 일을 누가 하는가? 네이티브 재생은 이걸 못 합니다. <video src>는 하나의 소스만 알 뿐, "이번 조각은 저화질, 다음 조각은 고화질"처럼 데이터를 갈아끼우는 능력이 없기 때문입니다.
바로 이 일을 MSE가 가능하게 합니다. 앞서 본 것처럼 MSE에서는 자바스크립트가 데이터를 직접 fetch해 SourceBuffer에 넣습니다. 그렇다면 자바스크립트는,
appendBuffer로 버퍼에 이어 붙이고,브라우저 입장에서는 그저 SourceBuffer에 연속된 미디어 데이터가 들어오는 것일 뿐이지만, 그 안에서 화질이 바뀌고 있다는 사실은 데이터를 채운 자바스크립트만 압니다. MSE의 "버퍼를 직접 채우는 능력"이, 적응형 스트리밍의 "세그먼트 단위로 화질을 갈아끼우는 능력"과 정확히 맞물리는 것입니다.
DASH와 HLS는 둘 다 위에서 설명한 적응형 스트리밍 방식이지만, 매니페스트 형식과 출신이 다릅니다.
.mpd(XML) 파일을 사용합니다. 특정 플랫폼에 종속되지 않은 개방형 표준입니다..m3u8 파일을 사용합니다. Apple 생태계(Safari, iOS)에서는 네이티브로 폭넓게 지원됩니다.여기서 한 가지 짚어 둘 점이 있습니다. HLS는 일부 환경에서 네이티브로도 재생됩니다. 예를 들어 Safari나 iOS에서는 <video src="playlist.m3u8">처럼 매니페스트 URL을 그대로 넣어도 브라우저가 알아서 적응형 재생을 처리해 줍니다. 즉 이 경우엔 MSE가 굳이 필요 없습니다.
문제는 그 외의 대부분의 환경입니다. 데스크톱 Chrome·Firefox 등 상당수 브라우저는 DASH는 물론, HLS조차 네이티브로 재생하지 못합니다. 이런 환경에서 적응형 스트리밍을 구현하려면, 자바스크립트가 매니페스트를 직접 파싱하고 세그먼트를 골라 받아 버퍼에 채워 넣어야 합니다. 그리고 그 "버퍼에 채워 넣는" 일을 가능하게 하는 토대가 바로 MSE입니다.
그래서 우리가 쓰는 스트리밍 플레이어 라이브러리들은 대부분 MSE 위에 서 있습니다.
| 라이브러리 | 주로 다루는 프로토콜 | 토대 |
|---|---|---|
| dash.js | DASH | MSE |
| hls.js | HLS | MSE |
| Shaka Player | DASH·HLS | MSE (필요 시 네이티브 src= 경로로도 분기) |
이 라이브러리들이 하는 일은 결국 같습니다. 매니페스트를 파싱하고, ABR 로직으로 화질을 정하고, 세그먼트를 받아 SourceBuffer에 이어 붙입니다. 우리는 이들 덕분에 MSE의 저수준 API를 직접 만지지 않고도 적응형 스트리밍을 누릴 수 있는 것이죠.
이전 Shaka Player 글에서 살펴봤듯, Shaka Player는 콘텐츠 종류에 따라 MSE 경로와 네이티브 src= 경로로 갈라집니다. 브라우저가 네이티브로 처리할 수 있는 콘텐츠(예: Safari의 HLS)는 <video src=...>로 위임하고, 그렇지 않은 경우 MediaSourceEngine을 통해 직접 버퍼를 채웁니다. MSE가 어디에 쓰이는지를 실제 플레이어 코드에서 확인할 수 있는 좋은 사례입니다.
지금까지 살펴본 내용을 정리하면 다음과 같습니다.
| 질문 | 답 |
|---|---|
| MSE란? | 자바스크립트가 직접 미디어 데이터를 만들어 <video>에 공급하는 W3C API |
| 핵심 객체는? | MediaSource(미디어 소스)와 SourceBuffer(데이터를 넣는 버퍼) |
| 네이티브 재생과 차이는? | 데이터 공급·버퍼 관리의 제어권이 브라우저 → 자바스크립트로 이동 |
| 왜 DASH·HLS에 필요한가? | 세그먼트 단위로 화질을 갈아끼우며 버퍼에 이어 붙이려면 MSE가 있어야 함 |
| 직접 다뤄야 하나? | 보통은 dash.js·hls.js·Shaka Player 같은 라이브러리가 MSE 위에서 대신 처리 |
브라우저 네이티브 재생이 "URL을 줄 테니 알아서 틀어 줘"라면, MSE는 "데이터는 내가 줄 테니 너는 틀기만 해"입니다. 이 작은 제어권의 이동이, 단일 파일에 묶여 있던 웹 재생을 세그먼트 단위로 조립하는 적응형 스트리밍으로 확장시킨 결정적 열쇠였습니다.
한 줄로 요약하면 다음과 같습니다.
"네이티브 재생은 브라우저가 데이터를 가져오고, MSE는 자바스크립트가 데이터를 떠먹인다. 이 한 끗 차이가 DASH·HLS 같은 적응형 스트리밍을 가능하게 한다."
다음 글에서는 이렇게 MSE 위에 올라탄 플레이어들이 실제로 매니페스트를 어떻게 해석하고 세그먼트를 다운로드해 버퍼를 채우는지를 더 깊이 들여다보겠습니다.