비디오의 재생 가능 여부를 파악하는 방법

들어가며 — "이 영상, 재생될까?"라는 질문

영상을 화면에 띄우기 전에 우리는 한 가지 질문에 답해야 합니다. "지금 이 기기가 이 영상을 재생할 수 있는가?" 코덱, 컨테이너, 해상도, 프레임레이트, 그리고 DRM까지 — 영상을 구성하는 조건은 많고, 그중 하나라도 기기가 감당하지 못하면 재생은 실패합니다.

특히 스마트 TV에서 구동되는 OTT 웹 애플리케이션을 개발할 때 이 질문은 단순한 호환성 체크를 넘어섭니다. 4K·8K 같은 고해상도 영상은 소프트웨어 디코딩(SW decoding)만으로는 한계가 있어, 기기에 내장된 하드웨어 디코더(HW decoding)로 처리해야 합니다. 그런데 어떤 코덱·해상도를 하드웨어로 디코딩할 수 있는지는 기기(모델·연식·칩셋)마다 다릅니다. 그래서 OTT 환경에서는 재생 가능 여부를, 그것도 하드웨어 디코딩 가능 여부까지 기기별로 면밀하게 파악하는 일이 매우 중요한 작업이 됩니다.

이를 위해 웹 플랫폼은 세 가지 API를 제공합니다.

  • MediaSource.isTypeSupported — MSE 레벨에서 해당 타입을 다룰 수 있는가
  • HTMLMediaElement.prototype.canPlayType — 미디어 엘리먼트가 이 타입을 재생할 수 있는가
  • navigator.mediaCapabilities.decodingInfo — 재생할 수 있을 뿐 아니라, 매끄럽고 전력 효율적으로 디코딩할 수 있는가

세 API는 등장 시기도, 정밀도도, 알려주는 정보의 깊이도 다릅니다. 하나씩 살펴보겠습니다.

MediaSource.isTypeSupported — MSE 레벨의 지원 여부

MSE(Media Source Extensions) 환경에서 가장 먼저 만나는 API입니다. MediaSource정적 메서드로, MIME 타입과 코덱 문자열을 받아 불리언을 반환합니다.

1const mimeCodec = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
2
3if ("MediaSource" in window && MediaSource.isTypeSupported(mimeCodec)) {
4  // 이 타입으로 SourceBuffer를 만들 수 있다
5  mediaSource = getMediaSource();
6  video.src = URL.createObjectURL(mediaSource);
7  mediaSource.addEventListener("sourceopen", sourceOpen);
8} else {
9  console.error("지원하지 않는 MIME 타입 또는 코덱: ", mimeCodec);
10}

여기서 짚어야 할 점이 두 가지 있습니다.

  1. 정적 메서드다: 미디어 엘리먼트 인스턴스가 아니라 MediaSource 생성자에서 바로 호출합니다. 그래서 <video>를 만들기 전에도 호출할 수 있고, Dedicated Web Worker에서도 사용 가능합니다.
  2. 불리언이지만 "보장"은 아니다: false라면 user agent가 "확실히 재생할 수 없다"고 단정한 것입니다. 하지만 true는 "아마도(probably) 재생할 수 있다"는 뜻에 가깝습니다. 즉 true를 받았다고 해서 실제 재생이 100% 보장되지는 않으며, 코드는 여전히 재생 실패 가능성을 다뤄야 합니다.

isTypeSupported는 이름 그대로 이 타입으로 SourceBuffer를 만들 수 있는가를 묻는 도구입니다. MSE 기반 스트리밍 플레이어가 매니페스트의 여러 화질 후보 중 이 기기에서 버퍼에 넣을 수 있는 것만 추려낼 때 주로 사용합니다.

WARNING

isTypeSupported디코딩이 가능한가까지는 알려주지 않습니다. MSE가 해당 코덱 타입을 다룰 수 있다는 것과, 그 영상을 실제로 매끄럽게 디코딩한다는 것은 별개입니다. 고해상도 영상의 하드웨어 디코딩 가능 여부는 뒤에서 볼 decodingInfo의 영역입니다.

HTMLMediaElement.prototype.canPlayType — 가장 오래된 판별 API

canPlayType은 미디어 엘리먼트(<video>, <audio>)의 인스턴스 메서드로, 세 API 중 가장 오래되고 가장 널리 지원됩니다. 특징은 반환값이 불리언이 아니라 세 가지 문자열 중 하나라는 점입니다.

1const video = document.createElement("video");
2console.log(video.canPlayType("video/mp4")); // 예: "maybe"

반환값의 의미는 다음과 같습니다.

반환값의미
"" (빈 문자열)이 기기에서 해당 미디어를 재생할 수 없다
"maybe"재생할 수 있는지 확신할 수 없다 (실제 재생을 시도해야 알 수 있음)
"probably"아마도 재생할 수 있다

가장 강한 긍정이 "probably"라는 점이 이 API의 성격을 잘 보여줍니다. 재생을 실제로 시도하기 전에는 그 무엇도 단정하지 않는다는 것입니다. 특히 MIME 타입만 넘기고 codecs 파라미터를 생략하면, 브라우저는 컨테이너는 알지만 그 안의 코덱은 모르므로 "maybe"를 반환하기 쉽습니다.

1// codecs를 명시하면 더 정확한 판단을 유도할 수 있다
2video.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"');

canPlayType은 호환성이 넓어 폴백(fallback) 판별이나 간단한 native 재생 가능 여부 확인에는 여전히 유용합니다. 다만 "maybe"라는 모호한 응답이 자주 나오고, 해상도·비트레이트·하드웨어 디코딩 같은 세부 조건은 전혀 반영하지 못한다는 분명한 한계가 있습니다.

decodingInfo는 세 API 중 가장 최신이며, 가장 풍부한 정보를 줍니다. 비동기 Promise를 반환하고, 단순히 "재생 가능 여부"를 넘어 매끄러움(smooth)과 전력 효율(powerEfficient)까지 알려줍니다.

가장 큰 차이는 입력입니다. 단순 MIME 문자열이 아니라, 해상도·비트레이트·프레임레이트까지 포함한 설정 객체를 받습니다.

1const config = {
2  type: "media-source", // "file" | "media-source" | "webrtc"
3  video: {
4    contentType: 'video/mp4; codecs="avc1.640028"',
5    width: 3840,
6    height: 2160,
7    bitrate: 20_000_000, // bits per second
8    framerate: 60,
9  },
10};
11
12const result = await navigator.mediaCapabilities.decodingInfo(config);
13console.log(result);
14// { supported: true, smooth: true, powerEfficient: true }

반환 객체의 세 불리언이 핵심입니다.

필드의미
supported이 설정의 미디어를 디코딩할 수 있는가
smooth지정한 프레임레이트를 프레임 드롭 없이 매끄럽게 재생할 수 있는가
powerEfficient이 디코딩이 전력 효율적인가

isTypeSupportedcanPlayType이 "타입을 다룰 수 있는가"라는 이분법적 질문에 답한다면, decodingInfo는 "이 해상도와 프레임레이트의 영상을, 이 기기가 매끄럽고 효율적으로 디코딩할 수 있는가"라는 훨씬 구체적인 질문에 답합니다. 바로 이 지점이 OTT·고해상도 환경에서 결정적입니다.

INFO

type 필드는 재생 맥락을 구분합니다. 일반 파일 재생이면 "file", MSE 스트리밍이면 "media-source", WebRTC면 "webrtc"를 사용합니다. 또한 keySystemConfiguration을 함께 넘기면 DRM(암호화된 미디어)환경에서의 디코딩 가능 여부까지 질의할 수 있습니다.

하드웨어 디코딩 가능 여부 체크

이제 OTT 개발에서 가장 중요한 주제로 돌아옵니다. 고해상도 영상의 하드웨어 디코딩 가능 여부를 어떻게 확인하는가?

결론부터 말하면, 이 질문에 답할 수 있는 것은 사실상 decodingInfo입니다. isTypeSupportedcanPlayType은 "타입을 다룰 수 있는가"만 알려줄 뿐, 그 디코딩이 하드웨어로 처리되는지 소프트웨어로 처리되는지는 전혀 구분하지 못하기 때문입니다.

decodingInfo가 반환하는 smoothpowerEfficient가 바로 하드웨어 디코딩의 신호입니다.

  • smooth — 지정한 프레임레이트를 드롭 없이 소화한다는 것은, 일반적으로 그 코덱·해상도에 대한 하드웨어 가속 디코딩 경로가 존재한다는 의미입니다.
  • powerEfficient — 전력 효율적이라는 것은 소프트웨어로 CPU를 갈아넣는 대신 전용 하드웨어 디코더가 일하고 있을 가능성이 높다는 신호입니다.

즉, 같은 코덱이라도 해상도를 올려가며 질의했을 때 smooth/powerEfficienttrue에서 false로 꺾이는 지점이 곧 그 기기의 하드웨어 디코딩 한계선에 해당합니다.

1async function findHwDecodableMaxResolution() {
2  const candidates = [
3    { width: 1920, height: 1080 },
4    { width: 2560, height: 1440 },
5    { width: 3840, height: 2160 }, // 4K
6    { width: 7680, height: 4320 }, // 8K
7  ];
8
9  const supported = [];
10  for (const { width, height } of candidates) {
11    const result = await navigator.mediaCapabilities.decodingInfo({
12      type: "media-source",
13      video: {
14        contentType: 'video/mp4; codecs="avc1.640028"',
15        width,
16        height,
17        bitrate: 20_000_000,
18        framerate: 60,
19      },
20    });
21    // supported + smooth + powerEfficient를 모두 만족하는 해상도만 채택
22    if (result.supported && result.smooth && result.powerEfficient) {
23      supported.push(`${width}x${height}`);
24    }
25  }
26  return supported;
27}
WARNING

주의할 점이 하나 있습니다. 브라우저는 실제 재생 통계가 쌓이기 전까지, 지원하는 설정을 일단 smooth·powerEfficient로 낙관적으로 보고하는 경향이 있습니다. 따라서 decodingInfo의 결과는 "확정된 하드웨어 성능 측정값"이 아니라 현재 시점의 최선의 추정으로 받아들이는 편이 안전합니다. 그럼에도, 기기별로 재생 가능한 해상도·코덱의 윤곽을 사전에 그려보는 데에는 다른 어떤 API보다 정밀합니다.

스마트 TV처럼 모델·연식·칩셋에 따라 디코딩 능력이 천차만별인 환경에서는, 이렇게 decodingInfo기기별 재생 가능 범위를 사전에 프로파일링해 두는 것이 끊김 없는 재생 경험의 출발점이 됩니다.

세 API 비교 테이블

지금까지의 내용을 한 표로 정리하면 다음과 같습니다.

구분isTypeSupportedcanPlayTypedecodingInfo
소속MediaSource (정적)HTMLMediaElement (인스턴스)navigator.mediaCapabilities
동기/비동기동기동기비동기 (Promise)
입력MIME + codecs 문자열MIME + codecs 문자열설정 객체 (해상도·비트레이트·프레임레이트 포함)
반환boolean"" / "maybe" / "probably"{ supported, smooth, powerEfficient }
판별 정밀도낮음 (타입 지원 여부)낮음 (모호한 3단계)높음 (구체적 조건별)
하드웨어 디코딩 정보✓ (smooth·powerEfficient)
주 사용처MSE SourceBuffer 생성 가능 여부폭넓은 호환성의 폴백 판별고해상도·OTT의 정밀 프로파일링

마무리 — 무엇을 언제 쓸 것인가

세 API는 경쟁 관계라기보다 정밀도의 층위가 다른 도구들입니다. 실전에서의 선택 가이드는 다음과 같습니다.

  • MSE 스트리밍에서 코덱 타입을 추려낼 때MediaSource.isTypeSupported. 동기적으로 빠르게 "이 타입을 버퍼에 넣을 수 있나"를 거른다.
  • 넓은 호환성으로 간단한 native 재생 가능 여부만 볼 때canPlayType. 단, "maybe"라는 모호함을 감안한다.
  • 해상도·프레임레이트별로, 특히 하드웨어 디코딩 가능 여부까지 정밀하게 판별할 때navigator.mediaCapabilities.decodingInfo. 현대적인 OTT·고해상도 환경의 1순위 선택지.

한 줄로 요약하면 다음과 같습니다.

"isTypeSupportedcanPlayType은 '다룰 수 있는가'를 묻고, decodingInfo는 '매끄럽고 효율적으로 디코딩할 수 있는가'를 묻는다. 고해상도 영상을 다루는 스마트 TV OTT 환경일수록 후자의 질문이 중요해진다."

재생 버튼을 누르기 전에 기기의 능력을 먼저 묻는 일 — 그것이 끊김 없는 영상 경험을 설계하는 첫걸음입니다.