네트워크 스택

ExoPlayer는 일반적으로 인터넷을 통한 미디어 스트리밍에 사용됩니다. 기본 네트워크 요청을 실행하기 위한 여러 네트워크 스택을 지원합니다. 어떤 네트워크 스택을 선택하면 스트리밍 성능에 상당한 영향을 미칠 수 있습니다.

이 페이지에서는 선택한 네트워크 스택을 사용하도록 ExoPlayer를 구성하는 방법을 설명하고, 사용 가능한 옵션을 나열하고, 앱의 네트워크 스택을 선택하는 방법에 관한 안내를 제공하고, 스트리밍된 미디어에 캐싱을 사용 설정하는 방법을 설명합니다.

특정 네트워크 스택을 사용하도록 ExoPlayer 구성

ExoPlayer는 앱 코드에서 삽입된 DataSource.Factory 인스턴스에서 가져온 DataSource 구성요소를 통해 데이터를 로드합니다.

앱이 http(s) 콘텐츠만 재생해야 하는 경우 네트워크 스택은 앱에서 삽입하는 DataSource.Factory 인스턴스를 사용하려는 네트워크 스택에 해당하는 HttpDataSource.Factory의 인스턴스로 업데이트하기만 하면 됩니다. 앱에서 로컬 파일과 같이 http가 아닌 콘텐츠도 재생해야 한다면 DefaultDataSource.Factory를 사용합니다.

Kotlin

DefaultDataSource.Factory(
  ...
  /* baseDataSourceFactory= */ PreferredHttpDataSource.Factory(...))

Java

new DefaultDataSource.Factory(
    ...
    /* baseDataSourceFactory= */ new PreferredHttpDataSource.Factory(...));

이 예에서 PreferredHttpDataSource.Factory는 선호하는 네트워크 스택에 해당하는 팩토리입니다. DefaultDataSource.Factory 레이어는 로컬 파일과 같이 http가 아닌 소스 지원을 추가합니다.

다음 예시에서는 Cronet 네트워크 스택을 사용하고 http가 아닌 콘텐츠의 재생도 지원하는 ExoPlayer를 빌드하는 방법을 보여줍니다.

Kotlin

// Given a CronetEngine and Executor, build a CronetDataSource.Factory.
val cronetDataSourceFactory = CronetDataSource.Factory(cronetEngine, executor)

// Wrap the CronetDataSource.Factory in a DefaultDataSource.Factory, which adds
// in support for requesting data from other sources (such as files, resources,
// etc).
val dataSourceFactory =
  DefaultDataSource.Factory(context, /* baseDataSourceFactory= */ cronetDataSourceFactory)

// Inject the DefaultDataSource.Factory when creating the player.
val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(
      DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory)
    )
    .build()

Java

// Given a CronetEngine and Executor, build a CronetDataSource.Factory.
CronetDataSource.Factory cronetDataSourceFactory =
    new CronetDataSource.Factory(cronetEngine, executor);

// Wrap the CronetDataSource.Factory in a DefaultDataSource.Factory, which adds
// in support for requesting data from other sources (such as files, resources,
// etc).
DefaultDataSource.Factory dataSourceFactory =
    new DefaultDataSource.Factory(
        context, /* baseDataSourceFactory= */ cronetDataSourceFactory);

// Inject the DefaultDataSource.Factory when creating the player.
ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            new DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory))
        .build();

지원되는 네트워크 스택

ExoPlayer는 Cronet, OkHttp, Android의 내장 네트워크 스택을 직접 지원합니다. ExoPlayer는 Android에서 작동하는 다른 모든 네트워크 스택을 지원하도록 확장할 수도 있습니다.

Cronet

Cronet은 Android 앱에서 라이브러리로 사용할 수 있는 Chromium 네트워크 스택입니다. Cronet은 지연 시간을 줄이고 ExoPlayer의 요청을 포함하여 앱이 작동해야 하는 네트워크 요청의 처리량을 늘리는 여러 기술을 활용합니다. 기본적으로 QUIC 프로토콜을 통해 HTTP, HTTP/2, HTTP/3를 지원합니다. Cronet은 YouTube를 포함하여 세계 최대의 스트리밍 앱에서 사용됩니다.

ExoPlayer는 Cronet 라이브러리를 통해 Cronet을 지원합니다. 사용 방법에 관한 자세한 안내는 라이브러리의 README.md을 참고하세요. Cronet 라이브러리는 세 가지 기본 Cronet 구현을 사용할 수 있습니다.

  1. Google Play 서비스: 대부분의 경우 이 구현을 사용하고 Google Play 서비스를 사용할 수 없는 경우에는 Android의 내장 네트워크 스택(DefaultHttpDataSource)으로 대체하는 것이 좋습니다.
  2. Cronet Embedded: 사용자의 대다수가 Google Play 서비스가 광범위하게 제공되지 않는 시장에 있거나 사용 중인 Cronet 구현의 정확한 버전을 제어하려는 경우에 적합합니다. Cronet Embedded의 주요 단점은 앱에 약 8MB가 추가된다는 점입니다.
  3. Cronet 대체: Cronet의 대체 구현은 Android의 기본 제공 네트워크 스택의 래퍼로 Cronet의 API를 구현합니다. Android의 내장 네트워크 스택을 직접 (DefaultHttpDataSource를 사용하여) 사용하는 것이 더 효율적이므로 ExoPlayer와 함께 사용하면 안 됩니다.

OkHttp

OkHttp는 여러 인기 Android 앱에서 널리 사용되는 또 다른 최신 네트워크 스택입니다. HTTP 및 HTTP/2는 지원하지만 QUIC를 통한 HTTP/3는 아직 지원하지 않습니다.

ExoPlayer는 OkHttp 라이브러리를 통해 OkHttp를 지원합니다. 사용 방법에 관한 자세한 안내는 라이브러리의 README.md을 참고하세요. OkHttp 라이브러리를 사용하면 네트워크 스택이 앱 내에 삽입됩니다. 이는 Cronet Embedded와 유사하지만 OkHttp가 상당히 작아서 앱에 1MB 미만을 추가합니다.

Android의 내장 네트워크 스택

ExoPlayer는 핵심 ExoPlayer 라이브러리의 일부인 DefaultHttpDataSourceDefaultHttpDataSource.Factory와 함께 Android의 내장 네트워크 스택 사용을 지원합니다.

정확한 네트워크 스택 구현은 기본 기기에서 실행되는 소프트웨어에 따라 다릅니다. 대부분의 기기 (2021년 기준)에서는 HTTP만 지원됩니다 (즉, QUIC을 통한 HTTP/2 및 HTTP/3는 지원되지 않음).

기타 네트워크 스택

앱은 다른 네트워크 스택을 ExoPlayer와 통합할 수도 있습니다. 이렇게 하려면 네트워크 스택을 상응하는 HttpDataSource.Factory와 함께 래핑하는 HttpDataSource를 구현합니다. ExoPlayer의 Cronet 및 OkHttp 라이브러리가 이 방법의 좋은 예입니다.

순수 자바 네트워크 스택과 통합할 때는 DataSourceContractTest를 구현하여 HttpDataSource 구현이 올바르게 작동하는지 확인하는 것이 좋습니다. OkHttp 라이브러리의 OkHttpDataSourceContractTest가 이 작업을 보여주는 좋은 예입니다.

네트워크 스택 선택

다음 표에는 ExoPlayer에서 지원하는 네트워크 스택의 장단점이 요약되어 있습니다.

네트워크 스택 프로토콜 APK 크기의 영향 Notes
Cronet (Google Play 서비스) QUIC을 통한 HTTP
HTTP/2
HTTP/3
작게
(100KB 미만)
Google Play 서비스가 필요합니다. Cronet 버전이 자동으로 업데이트됨
Cronet (삽입됨) QUIC을 통한 HTTP
HTTP/2
HTTP/3
대용량
(약 8MB)
앱 개발자가 제어하는 Cronet 버전
Cronet (대체) HTTP
(기기마다 다름)
작게
(100KB 미만)
ExoPlayer에는 권장하지 않음
OkHttp HTTP
HTTP/2
소형
(1MB 미만)
Kotlin 런타임 필요
기본 제공 네트워크 스택 HTTP
(기기마다 다름)
없음 구현은 기기에 따라 다릅니다.

QUIC 프로토콜을 통한 HTTP/2 및 HTTP/3는 미디어 스트리밍 성능을 크게 향상시킬 수 있습니다. 특히 콘텐츠 전송 네트워크 (CDN)를 사용하여 배포되는 적응형 미디어를 스트리밍할 때 이러한 프로토콜을 사용하면 CDN이 훨씬 더 효율적으로 작동할 수 있습니다. 따라서 Cronet의 QUIC를 통한 HTTP/2 및 HTTP/3 지원 (및 OkHttp의 HTTP/2 지원)은 Android의 기본 제공 네트워크 스택을 사용할 때보다 큰 이점이 있습니다. 단, 콘텐츠가 호스팅되는 서버가 이러한 프로토콜을 지원한다는 전제 하에 Android의 내장 네트워크 스택을 사용할 때보다 큰 장점이 있습니다.

격리된 미디어 스트리밍을 고려할 때는 Google Play 서비스에서 제공하는 Cronet을 사용하는 것이 좋습니다. Google Play 서비스를 사용할 수 없다면 DefaultHttpDataSource로 대체합니다. 이 권장사항은 대부분의 기기에서 QUIC를 통해 HTTP/2 및 HTTP/3을 사용 설정하는 것과 APK 크기가 크게 늘어나지 않게 하는 것 사이에서 적절한 균형을 이룹니다. 이 권장사항에는 예외가 있습니다. 앱을 실행할 기기 중 상당수에서 Google Play 서비스를 사용할 수 없는 경우에는 Cronet Embedded 또는 OkHttp를 사용하는 것이 더 적합할 수 있습니다. APK 크기가 중요한 문제이거나 미디어 스트리밍이 앱 기능의 일부에 불과한 경우에는 내장된 네트워크 스택 사용이 허용될 수도 있습니다.

미디어 외에도 앱에서 실행하는 모든 네트워킹에 대해 단일 네트워크 스택을 선택하는 것이 좋습니다. 이렇게 하면 리소스(예: 소켓)를 효율적으로 풀링하고 ExoPlayer와 다른 앱 구성요소 간에 공유할 수 있습니다.

앱은 미디어 재생과 관련 없는 네트워킹을 실행해야 할 가능성이 가장 높기 때문에 네트워크 스택을 선택할 때는 궁극적으로 격리된 미디어 스트리밍에 관한 위의 권장사항, 네트워킹을 실행하는 다른 구성요소의 요구사항 및 앱에 대한 상대적 중요도를 고려해야 합니다.

미디어 캐싱

ExoPlayer는 네트워크에서 동일한 바이트를 반복적으로 로드하지 않도록 로드된 바이트를 디스크에 캐시하는 것을 지원합니다. 이는 현재 미디어에서 다시 탐색하거나 동일한 항목을 반복할 때 유용합니다.

캐싱을 위해서는 전용 캐시 디렉터리를 가리키는 SimpleCache 인스턴스와 CacheDataSource.Factory가 필요합니다.

Kotlin

// Note: This should be a singleton in your app.
val databaseProvider = StandaloneDatabaseProvider(context)

// An on-the-fly cache should evict media when reaching a maximum disk space limit.
val cache =
    SimpleCache(
        downloadDirectory, LeastRecentlyUsedCacheEvictor(maxBytes), databaseProvider)

// Configure the DataSource.Factory with the cache and factory for the desired HTTP stack.
val cacheDataSourceFactory =
    CacheDataSource.Factory()
        .setCache(cache)
        .setUpstreamDataSourceFactory(httpDataSourceFactory)

// Inject the DefaultDataSource.Factory when creating the player.
val player =
    ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            DefaultMediaSourceFactory(context).setDataSourceFactory(cacheDataSourceFactory))
        .build()

Java

// Note: This should be a singleton in your app.
DatabaseProvider databaseProvider = new StandaloneDatabaseProvider(context);

// An on-the-fly cache should evict media when reaching a maximum disk space limit.
Cache cache =
    new SimpleCache(
        downloadDirectory, new LeastRecentlyUsedCacheEvictor(maxBytes), databaseProvider);

// Configure the DataSource.Factory with the cache and factory for the desired HTTP stack.
DataSource.Factory cacheDataSourceFactory =
    new CacheDataSource.Factory()
        .setCache(cache)
        .setUpstreamDataSourceFactory(httpDataSourceFactory);

// Inject the DefaultDataSource.Factory when creating the player.
ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            new DefaultMediaSourceFactory(context).setDataSourceFactory(cacheDataSourceFactory))
        .build();