문제 해결


'일반 텍스트 HTTP 트래픽이 허용되지 않음' 오류 수정

이 오류는 앱의 네트워크 보안 구성에서 허용하지 않는데 앱이 일반 텍스트 HTTP 트래픽 (즉, https://이 아닌 http://)을 요청하는 경우 발생합니다. 앱이 Android 9 (API 수준 28) 이상을 타겟팅하는 경우 기본 구성에 따라 일반 텍스트 HTTP 트래픽이 사용 중지됩니다.

앱이 일반 텍스트 HTTP 트래픽과 작동해야 하는 경우 이를 허용하는 네트워크 보안 구성을 사용해야 합니다. 자세한 내용은 Android의 네트워크 보안 문서를 참고하세요. 모든 일반 텍스트 HTTP 트래픽을 사용 설정하려면 앱의 AndroidManifest.xml application 요소에 android:usesCleartextTraffic="true"를 추가하면 됩니다.

ExoPlayer 데모 앱은 기본 네트워크 보안 구성을 사용하므로 일반 텍스트 HTTP 트래픽을 허용하지 않습니다. 위의 안내에 따라 사용 설정할 수 있습니다.

'SSLHandshakeException', 'CertPathValidatorException', 'ERR_CERT_AUTHORITY_INVALID' 오류 수정

SSLHandshakeException, CertPathValidatorException, ERR_CERT_AUTHORITY_INVALID는 모두 서버의 SSL 인증서에 문제가 있음을 나타냅니다. 이러한 오류는 ExoPlayer에만 해당하지 않습니다. 자세한 내용은 Android의 SSL 문서를 참고하세요.

일부 미디어 파일을 탐색할 수 없는 이유는 무엇인가요?

기본적으로 ExoPlayer는 정확한 탐색 작업을 실행하는 유일한 방법이 플레이어가 전체 파일을 스캔하고 색인을 생성하는 미디어의 탐색을 지원하지 않습니다. ExoPlayer는 이러한 파일을 탐색할 수 없는 것으로 간주합니다. 대부분의 최신 미디어 컨테이너 형식에는 탐색을 위한 메타데이터 (예: 샘플 색인)가 포함되어 있거나, 잘 정의된 탐색 알고리즘 (예: Ogg의 보간된 이분 검색)이 있거나, 콘텐츠가 고정 비트 전송률임을 나타냅니다. 이러한 경우 효율적인 탐색 작업이 가능하며 ExoPlayer에서 지원합니다.

탐색이 필요하지만 탐색할 수 없는 미디어가 있는 경우 콘텐츠를 변환하여 더 적절한 컨테이너 형식을 사용하는 것이 좋습니다. MP3, ADTS, AMR 파일의 경우 파일의 비트 전송률이 일정하다고 가정하고 여기에 설명된 대로 탐색을 사용 설정할 수도 있습니다.

일부 MP3 파일에서 탐색이 부정확한 이유는 무엇인가요?

가변 비트 전송률 (VBR) MP3 파일은 정확한 탐색이 필요한 사용 사례에 적합하지 않습니다. 여기에는 다음과 같은 두 가지 이유가 있습니다.

  1. 정확한 탐색의 경우 컨테이너 형식은 헤더에 정확한 시간-바이트 매핑을 제공하는 것이 좋습니다. 이 매핑을 통해 플레이어는 요청된 탐색 시간을 해당 바이트 오프셋에 매핑하고 해당 오프셋에서 미디어를 요청, 파싱, 재생할 수 있습니다. MP3에서 이 매핑을 지정하는 데 사용할 수 있는 헤더 (예: XING 헤더)는 불행히도 정확하지 않은 경우가 많습니다.
  2. 정확한 시간-바이트 매핑 (또는 시간-바이트 매핑)을 제공하지 않는 컨테이너 형식의 경우 컨테이너에 스트림의 절대 샘플 타임스탬프가 포함되어 있으면 정확한 탐색을 실행할 수 있습니다. 이 경우 플레이어는 탐색 시간을 해당 바이트 오프셋의 최적 추측에 매핑하고, 해당 오프셋에서 미디어 요청을 시작하고, 첫 번째 절대 샘플 타임스탬프를 파싱하고, 올바른 샘플을 찾을 때까지 효과적으로 미디어로 안내된 이진 검색을 실행할 수 있습니다. 안타깝게도 MP3에는 스트림에 절대 샘플 타임스탬프가 포함되지 않으므로 이 접근 방식은 불가능합니다.

이러한 이유로 VBR MP3 파일에서 정확한 탐색을 실행하는 유일한 방법은 전체 파일을 스캔하고 플레이어에서 시간-바이트 매핑을 수동으로 빌드하는 것입니다. 이 전략은 FLAG_ENABLE_INDEX_SEEKING를 사용하여 사용 설정할 수 있으며, setMp3ExtractorFlags를 사용하여 DefaultExtractorsFactory에 설정할 수 있습니다. 특히 사용자가 재생을 시작한 직후 스트림 끝으로 이동하려고 하면 큰 MP3 파일로 잘 확장되지 않습니다. 이 경우 플레이어는 이동을 실행하기 전에 전체 스트림이 다운로드되고 색인이 생성될 때까지 기다려야 합니다. ExoPlayer에서는 이 경우 정확도보다 속도를 최적화하기로 결정했으므로 FLAG_ENABLE_INDEX_SEEKING가 기본적으로 사용 중지됩니다.

재생 중인 미디어를 제어하는 경우 MP4와 같은 더 적절한 컨테이너 형식을 사용하는 것이 좋습니다. MP3가 미디어 형식으로 가장 적합한 사용 사례는 없습니다.

내 동영상에서 탐색이 느린 이유는 무엇인가요?

플레이어가 동영상에서 새로운 재생 위치를 탐색할 때는 다음 두 가지 작업을 실행해야 합니다.

  1. 새 재생 위치에 해당하는 데이터를 버퍼에 로드합니다(이 데이터가 이미 버퍼링된 경우 필요하지 않을 수 있음).
  2. 대부분의 동영상 압축 형식에서 사용되는 프레임 내 코딩으로 인해 동영상 디코더를 플러시하고 새 재생 위치 전의 I 프레임 (키프레임)에서 디코딩을 시작합니다. 탐색이 정확하도록(즉, 탐색 위치에서 정확하게 재생이 시작되도록) 이전 I 프레임과 탐색 위치 사이의 모든 프레임은 디코딩되어 화면에 표시되지 않고 즉시 삭제되어야 합니다.

(1)로 인해 발생하는 지연 시간은 플레이어에서 메모리에 버퍼링되는 데이터 양을 늘리거나 데이터를 디스크에 사전 캐싱하여 완화할 수 있습니다.

(2)로 인해 발생하는 지연 시간은 ExoPlayer.setSeekParameters를 사용하여 탐색의 정확도를 줄이거나 I-프레임이 더 자주 표시되도록 동영상을 다시 인코딩하여 완화할 수 있습니다 (이 경우 출력 파일이 더 커짐).

일부 MPEG-TS 파일이 재생되지 않는 이유는 무엇인가요?

일부 MPEG-TS 파일에는 액세스 단위 구분자 (AUD)가 포함되어 있지 않습니다. 기본적으로 ExoPlayer는 AUD를 사용하여 프레임 경계를 저렴하게 감지합니다. 마찬가지로 일부 MPEG-TS 파일에는 IDR 키프레임이 포함되어 있지 않습니다. 기본적으로 ExoPlayer에서 고려하는 유일한 유형의 키프레임입니다.

AUD 또는 IDR 키프레임이 없는 MPEG-TS 파일을 재생하도록 요청하면 ExoPlayer가 버퍼링 상태에서 멈춘 것처럼 보입니다. 이러한 파일을 재생해야 하는 경우 각각 FLAG_DETECT_ACCESS_UNITSFLAG_ALLOW_NON_IDR_KEYFRAMES을 사용하여 재생할 수 있습니다. 이러한 플래그는 setTsExtractorFlags를 사용하여 DefaultExtractorsFactory에서 설정하거나 생성자를 사용하여 DefaultHlsExtractorFactory에서 설정할 수 있습니다. FLAG_DETECT_ACCESS_UNITS 사용은 AUD 기반 프레임 경계 감지에 비해 계산 비용이 많이 든다는 점 외에는 부작용이 없습니다. FLAG_ALLOW_NON_IDR_KEYFRAMES를 사용하면 일부 MPEG-TS 파일을 재생할 때 재생 시작 시와 탐색 직후에 일시적인 시각적 손상이 발생할 수 있습니다.

일부 MPEG-TS 파일에서 자막을 찾을 수 없는 이유는 무엇인가요?

일부 MPEG-TS 파일에는 CEA-608 트랙이 포함되어 있지만 컨테이너 메타데이터에서 선언되지 않으므로 ExoPlayer에서 이를 감지할 수 없습니다. MPEG-TS 스트림에서 자막을 식별하는 데 사용할 수 있는 접근성 채널을 비롯해 예상되는 자막 형식 목록을 DefaultExtractorsFactory에 제공하여 자막 트랙을 수동으로 지정할 수 있습니다.

Kotlin

val extractorsFactory =
  DefaultExtractorsFactory()
    .setTsSubtitleFormats(
      listOf(
        Format.Builder()
          .setSampleMimeType(MimeTypes.APPLICATION_CEA608)
          .setAccessibilityChannel(accessibilityChannel)
          // Set other subtitle format info, such as language.
          .build()
      )
    )
val player: Player =
  ExoPlayer.Builder(context, DefaultMediaSourceFactory(context, extractorsFactory)).build()

자바

DefaultExtractorsFactory extractorsFactory =
    new DefaultExtractorsFactory()
        .setTsSubtitleFormats(
            ImmutableList.of(
                new Format.Builder()
                    .setSampleMimeType(MimeTypes.APPLICATION_CEA608)
                    .setAccessibilityChannel(accessibilityChannel)
                    // Set other subtitle format info, such as language.
                    .build()));
Player player =
    new ExoPlayer.Builder(context, new DefaultMediaSourceFactory(context, extractorsFactory))
        .build();

일부 MP4/FMP4 파일이 잘못 재생되는 이유는 무엇인가요?

일부 MP4/FMP4 파일에는 샘플 목록을 건너뛰거나 이동하거나 반복하여 미디어 타임라인을 다시 쓰는 수정 목록이 포함되어 있습니다. ExoPlayer는 수정 목록 적용을 부분적으로 지원합니다. 예를 들어 동기화 샘플에서 시작하는 샘플 그룹을 지연하거나 반복할 수 있지만 동기화 샘플에서 시작하지 않는 수정사항의 오디오 샘플이나 프리롤 미디어는 자르지 않습니다.

미디어의 일부가 예기치 않게 누락되거나 반복되는 경우 추출기가 수정 목록을 완전히 무시하도록 Mp4Extractor.FLAG_WORKAROUND_IGNORE_EDIT_LISTS 또는 FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_EDIT_LISTS를 설정해 보세요. setMp4ExtractorFlags 또는 setFragmentedMp4ExtractorFlags를 사용하여 DefaultExtractorsFactory에서 설정할 수 있습니다.

일부 스트림이 HTTP 응답 코드 301 또는 302로 실패하는 이유는 무엇인가요?

HTTP 응답 코드 301과 302는 모두 리디렉션을 나타냅니다. 간략한 설명은 Wikipedia에서 확인할 수 있습니다. ExoPlayer가 요청을 하고 상태 코드 301 또는 302가 포함된 응답을 수신하면 일반적으로 리디렉션을 따르고 정상적으로 재생을 시작합니다. 기본적으로 이 문제가 발생하지 않는 한 가지 경우는 교차 프로토콜 리디렉션입니다. 크로스 프로토콜 리디렉션은 HTTPS에서 HTTP로 또는 그 반대로 리디렉션되는 리디렉션입니다 (또는 덜 일반적이지만 다른 프로토콜 쌍 간에 리디렉션). 다음과 같이 wget 명령줄 도구를 사용하여 URL이 교차 프로토콜 리디렉션을 유발하는지 테스트할 수 있습니다.

wget "https://yourserver.example.com/test.mp3" 2>&1  | grep Location

출력은 다음과 같이 표시됩니다.

Location: https://secondserver.example.net/test.mp3 [following]
Location: http://thirdserver.example.org/test.mp3 [following]

이 예시에는 두 개의 리디렉션이 있습니다. 첫 번째 리디렉션은 https://yourserver.example.com/test.mp3에서 https://secondserver.example.net/test.mp3로의 리디렉션입니다. 둘 다 HTTPS이므로 교차 프로토콜 리디렉션이 아닙니다. 두 번째 리디렉션은 https://secondserver.example.net/test.mp3에서 http://thirdserver.example.org/test.mp3로의 리디렉션입니다. 이렇게 하면 HTTPS에서 HTTP로 리디렉션되므로 교차 프로토콜 리디렉션입니다. ExoPlayer는 기본 구성에서 이 리디렉션을 따르지 않으므로 재생이 실패합니다.

필요한 경우 애플리케이션에서 사용되는 DefaultHttpDataSource.Factory 인스턴스를 인스턴스화할 때 교차 프로토콜 리디렉션을 따르도록 ExoPlayer를 구성할 수 있습니다. 여기에서 네트워크 스택 선택 및 구성에 대해 알아보세요.

일부 스트림이 UnrecognizedInputFormatException으로 실패하는 이유는 무엇인가요?

이 질문은 다음과 같은 재생 실패와 관련이 있습니다.

UnrecognizedInputFormatException: None of the available extractors
(MatroskaExtractor, FragmentedMp4Extractor, ...) could read the stream.

이 오류의 원인은 두 가지일 수 있습니다. 가장 일반적인 원인은 DASH (mpd), HLS (m3u8), SmoothStreaming (ism, isml) 콘텐츠를 재생하려고 하는데 플레이어가 프로그레시브 스트림으로 재생하려고 하는 것입니다. 이러한 스트림을 재생하려면 해당 ExoPlayer 모듈에 종속되어야 합니다. 스트림 URI가 표준 파일 확장자로 끝나지 않는 경우 MimeTypes.APPLICATION_MPD, MimeTypes.APPLICATION_M3U8 또는 MimeTypes.APPLICATION_SSMediaItem.BuildersetMimeType에 전달하여 스트림 유형을 명시적으로 지정할 수도 있습니다.

두 번째로 덜 일반적인 원인은 ExoPlayer가 재생하려는 미디어의 컨테이너 형식을 지원하지 않는 것입니다. 이 경우 실패는 의도한 대로 작동하는 것이지만 컨테이너 형식과 테스트 스트림의 세부정보를 포함하여 문제 추적기에 기능 요청을 제출하셔도 됩니다. 새 요청을 제출하기 전에 기존 기능 요청을 검색하세요.

일부 기기에서 setPlaybackParameters가 제대로 작동하지 않는 이유는 무엇인가요?

Android M 이하에서 앱의 디버그 빌드를 실행할 때 setPlaybackParameters API를 사용하면 성능이 불안정하고, 들리는 아티팩트가 있으며, CPU 사용률이 높을 수 있습니다. 이 API에 중요한 최적화가 이러한 Android 버전에서 실행되는 디버그 빌드에 사용 중지되어 있기 때문입니다.

이 문제는 디버그 빌드에만 영향을 미친다는 점에 유의하세요. 최적화가 항상 사용 설정된 출시 빌드에는 영향을 미치지 않습니다. 따라서 최종 사용자에게 제공하는 출시 버전은 이 문제의 영향을 받지 않습니다.

'플레이어가 잘못된 스레드에서 액세스됨' 오류는 무엇을 의미하나요?

시작하기 페이지의 스레딩 관련 참고사항을 참고하세요.

'예상치 못한 상태 줄: ICY 200 OK'를 해결하려면 어떻게 해야 하나요?

이 문제는 서버 응답에 HTTP 규격이 아닌 ICY 상태 라인이 포함된 경우에 발생할 수 있습니다. ICY 상태 줄은 지원 중단되었으며 사용해서는 안 됩니다. 따라서 서버를 제어하는 경우 HTTP 규격 응답을 제공하도록 업데이트해야 합니다. 이 작업을 할 수 없는 경우 ExoPlayer OkHttp 라이브러리를 사용하면 ICY 상태 줄을 올바르게 처리할 수 있으므로 문제가 해결됩니다.

재생 중인 스트림이 라이브 스트림인지 어떻게 쿼리할 수 있나요?

플레이어의 isCurrentWindowLive 메서드를 쿼리할 수 있습니다. 또한 isCurrentWindowDynamic을 확인하여 창이 동적인지(즉, 시간이 지남에 따라 계속 업데이트되는지) 확인할 수 있습니다.

앱이 백그라운드에 있을 때 오디오를 계속 재생하려면 어떻게 해야 하나요?

앱이 백그라운드에 있을 때 오디오가 계속 재생되도록 하려면 다음 단계를 따르세요.

  1. 실행 중인 포그라운드 서비스가 있어야 합니다. 이렇게 하면 시스템에서 리소스를 확보하기 위해 프로세스를 종료하는 것을 방지할 수 있습니다.
  2. WifiLockWakeLock을 보유해야 합니다. 이렇게 하면 시스템에서 Wi-Fi 라디오와 CPU를 절전 모드로 전환하지 않습니다. ExoPlayer을 사용하는 경우 setWakeMode를 호출하여 필요한 잠금을 올바른 시간에 자동으로 획득하고 해제하면 됩니다.

오디오가 더 이상 재생되지 않으면 setWakeMode를 사용하지 않는 경우 잠금을 해제하고 서비스를 중지하는 것이 중요합니다.

ExoPlayer는 내 콘텐츠를 지원하는데 ExoPlayer Cast 라이브러리는 지원하지 않는 이유는 무엇인가요?

재생하려는 콘텐츠가 CORS 지원되지 않을 수 있습니다. 전송 프레임워크를 사용하려면 콘텐츠를 재생하기 위해 CORS를 사용 설정해야 합니다.

콘텐츠가 재생되지 않는데 오류가 표시되지 않는 이유는 무엇인가요?

콘텐츠를 재생하는 기기에서 특정 미디어 샘플 형식을 지원하지 않을 수 있습니다. 플레이어에 EventLogger을 리스너로 추가하고 Logcat에서 다음과 유사한 줄을 찾아보면 쉽게 확인할 수 있습니다.

[ ] Track:x, id=x, mimeType=mime/type, ... , supported=NO_UNSUPPORTED_TYPE

NO_UNSUPPORTED_TYPE은 기기가 mimeType로 지정된 미디어 샘플 형식을 디코딩할 수 없음을 의미합니다. 지원되는 샘플 형식에 관한 자세한 내용은 Android 미디어 형식 문서를 참고하세요. 디코딩 라이브러리를 로드하여 재생에 사용하려면 어떻게 해야 하나요?도 유용할 수 있습니다.

재생을 위해 로드되고 사용될 디코딩 라이브러리는 어떻게 가져올 수 있나요?

  • 대부분의 디코더 라이브러리에는 종속 항목을 체크아웃하고 빌드하는 수동 단계가 있으므로 관련 라이브러리의 리드미에 있는 단계를 따랐는지 확인하세요. 예를 들어 ExoPlayer FFmpeg 라이브러리의 경우 libraries/decoder_ffmpeg/README.md의 안내를 따라 재생하려는 형식의 디코더를 사용 설정하는 구성 플래그를 전달해야 합니다.
  • 네이티브 코드가 있는 라이브러리의 경우 README에 지정된 올바른 버전의 Android NDK를 사용하고 있는지 확인하고 구성 및 빌드 중에 표시되는 오류를 확인하세요. README의 단계를 따른 후 지원되는 각 아키텍처의 라이브러리 경로에 있는 libs 하위 디렉터리에 .so 파일이 표시됩니다.
  • 데모 애플리케이션에서 라이브러리를 사용하여 재생을 시도하려면 번들 디코더 사용 설정을 참고하세요. 자체 앱에서 라이브러리를 사용하는 방법에 관한 안내는 라이브러리의 README를 참고하세요.
  • DefaultRenderersFactory를 사용하는 경우 디코더가 로드될 때 Logcat에 'Loaded FfmpegAudioRenderer'와 같은 정보 수준 로그 줄이 표시됩니다. 이 라이브러리가 누락된 경우 애플리케이션에 디코딩 라이브러리에 대한 종속 항목이 있는지 확인합니다.
  • Logcat에 LibraryLoader의 경고 수준 로그가 표시되면 라이브러리의 네이티브 구성요소 로드에 실패한 것입니다. 이 문제가 발생하면 라이브러리의 README에 나온 단계를 올바르게 따랐는지, 안내를 따르는 동안 오류가 출력되지 않았는지 확인하세요.

디코딩 라이브러리 사용에 여전히 문제가 있는 경우 Media3 문제 추적기에서 관련 최근 문제를 확인하세요. 새 문제를 신고해야 하고 라이브러리의 네이티브 부분 빌드와 관련된 경우 문제를 진단할 수 있도록 리드미 안내 실행에서 전체 명령줄 출력을 포함하세요.

ExoPlayer로 YouTube 동영상을 직접 재생할 수 있나요?

아니요, ExoPlayer는 https://www.youtube.com/watch?v=... 형식의 URL과 같은 YouTube 동영상을 재생할 수 없습니다. 대신 Android에서 YouTube 동영상을 재생하는 공식 방법인 YouTube iFrame Player API를 사용해야 합니다.

동영상 재생이 끊김

예를 들어 콘텐츠 비트 전송률 또는 해상도가 기기 기능을 초과하는 경우 기기에서 콘텐츠를 충분히 빠르게 디코딩하지 못할 수 있습니다. 이러한 기기에서 좋은 성능을 얻으려면 품질이 낮은 콘텐츠를 사용해야 할 수 있습니다.

Android 6.0 (API 수준 23)부터 Android 11 (API 수준 30)까지의 Android 버전을 실행하는 기기에서 특히 DRM으로 보호된 콘텐츠나 높은 프레임 속도 콘텐츠를 재생할 때 동영상 끊김 현상이 발생하는 경우 비동기 버퍼 대기열 사용 설정을 시도해 볼 수 있습니다.

불안정한 API 린트 오류

Media3는 API 노출 영역의 하위 집합에 대해 바이너리 호환성을 보장합니다. 바이너리 호환성을 보장하지 않는 부분은 @UnstableApi로 표시됩니다. 이 구분을 명확하게 하기 위해 불안정한 API 기호의 사용은 @OptIn로 주석 처리되지 않는 한 린트 오류를 생성합니다.

@UnstableApi 주석은 API의 품질이나 성능에 관해 아무것도 의미하지 않으며, 'API 고정'이 아니라는 사실만 의미합니다.

불안정한 API 린트 오류를 처리하는 방법에는 두 가지가 있습니다.

  • 동일한 결과를 얻을 수 있는 안정적인 API를 사용하도록 전환하세요.
  • 불안정한 API를 계속 사용하고 나중에 표시된 대로 @OptIn로 사용에 주석을 답니다.
@OptIn 주석 추가

Android 스튜디오를 사용하면 주석을 추가할 수 있습니다.

스크린샷: 선택 주석을 추가하는 방법
그림 2: Android 스튜디오를 사용하여 @androidx.annotations.OptIn 주석을 추가합니다.

Kotlin에서 특정 사용 사이트에 수동으로 주석을 달 수도 있습니다.

import androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi

@OptIn(UnstableApi::class)
fun functionUsingUnstableApi() { ... }

Java에서도 마찬가지입니다.

import androidx.annotation.OptIn;
import androidx.media3.common.util.UnstableApi;

@OptIn(markerClass = UnstableApi.class)
private void methodUsingUnstableApis() { ... }

package-info.java 파일을 추가하여 전체 패키지를 선택할 수 있습니다.

@OptIn(markerClass = UnstableApi.class)
package name.of.your.package;

import androidx.annotation.OptIn;
import androidx.media3.common.util.UnstableApi;

lint.xml 파일에서 특정 린트 오류를 억제하여 전체 프로젝트를 선택할 수 있습니다.

 <?xml version="1.0" encoding="utf-8"?>
 <lint>
   <issue id="UnsafeOptInUsageError">
     <option name="opt-in" value="androidx.media3.common.util.UnstableApi" />
   </issue>
 </lint>

사용해서는 안 되는 kotlin.OptIn 주석도 있습니다. androidx.annotation.OptIn 주석을 사용하는 것이 중요합니다.