问题排查


修复“不允许明文 HTTP 流量”错误

如果您的应用在网络安全配置不允许的情况下请求明文 HTTP 流量(即 http:// 而不是 https://),则会发生此错误。如果您的应用以 Android 9(API 级别 28)或更高版本为目标,则默认配置会禁用明文 HTTP 流量。

如果您的应用需要使用明文 HTTP 流量,则需要使用允许它的网络安全配置。有关详细信息,请参阅 Android 的网络安全文档。要启用所有明文 HTTP 流量,您只需将 android:usesCleartextTraffic="true" 添加到应用 AndroidManifest.xmlapplication 元素中。

ExoPlayer 演示应用使用默认的网络安全配置,因此它不允许明文 HTTP 流量。您可以使用上述说明启用它。

修复“SSLHandshakeException”、“CertPathValidatorException”和“ERR_CERT_AUTHORITY_INVALID”错误

SSLHandshakeExceptionCertPathValidatorExceptionERR_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 无法检测到它们。您可以通过向 DefaultExtractorsFactory 提供预期字幕格式列表(包括可用于在 MPEG-TS 流中识别它们的无障碍通道)来手动指定任何字幕轨道

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()

Java

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_LISTSFragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_EDIT_LISTS,这将导致提取器完全忽略编辑列表。这些可以使用 setMp4ExtractorFlagssetFragmentedMp4ExtractorFlags DefaultExtractorsFactory 上设置

为什么有些流在 HTTP 响应代码 301 或 302 时失败?

HTTP 响应代码 301 和 302 都表示重定向。您可以在 维基百科上找到简要说明。当 ExoPlayer 发出请求并收到状态代码为 301 或 302 的响应时,它通常会遵循重定向并正常开始播放。默认情况下不会发生这种情况的一种情况是跨协议重定向。跨协议重定向是指从 HTTPS 重定向到 HTTP 或反之(或较少见地,在另一对协议之间)。您可以使用 wget 命令行工具测试 URL 是否会导致跨协议重定向,如下所示:

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

输出应如下所示:

Location: https://second.com/test.mp3 [following]
Location: http://third.com/test.mp3 [following]

在此示例中,有两个重定向。第一个重定向是从 https://yourserver.com/test.mp3https://second.com/test.mp3。两者都是 HTTPS,因此这不是跨协议重定向。第二个重定向是从 https://second.com/test.mp3http://third.com/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_MPDMimeTypes.APPLICATION_M3U8MimeTypes.APPLICATION_SS 传递给 MediaItem.BuildersetMimeType,以明确指定流类型。

第二个,较不常见的原因是 ExoPlayer 不支持您尝试播放的媒体的容器格式。在这种情况下,失败是按预期工作的,但欢迎向我们的问题跟踪器提交功能请求,包括容器格式的详细信息和测试流。在提交新请求之前,请先搜索现有功能请求。

为什么 setPlaybackParameters 在某些设备上无法正常工作?

当您在 Android M 及更早版本上运行应用的调试版本时,使用 setPlaybackParameters API 时,您可能会遇到卡顿的性能、可听见的伪影和高 CPU 利用率。这是因为对于在这些 Android 版本上运行的调试版本,此 API 的一项重要优化已被禁用。

重要的是,此问题仅影响调试版本。它*不*影响发布版本,对于发布版本,此优化始终启用。因此,您提供给最终用户的发布版本不应受此问题影响。

“播放器在错误的线程上访问”错误是什么意思?

请参阅入门页面上的关于线程的注意事项

如何修复“Unexpected status line: ICY 200 OK”?

如果服务器响应包含 ICY 状态行,而不是符合 HTTP 规范的状态行,则可能会发生此问题。ICY 状态行已弃用,不应使用,因此如果您控制服务器,则应更新它以提供符合 HTTP 规范的响应。如果您无法执行此操作,则使用 ExoPlayer OkHttp 库将解决此问题,因为它能够正确处理 ICY 状态行。

如何查询正在播放的流是否为直播流?

您可以查询播放器的 isCurrentWindowLive 方法。此外,您还可以检查 isCurrentWindowDynamic 以了解窗口是否是动态的(即仍在随时间更新)。

当我的应用在后台时,如何保持音频播放?

请按照以下步骤确保您的应用在后台时音频持续播放:

  1. 您需要有一个正在运行的前台服务。这可以防止系统终止您的进程以释放资源。
  2. 您需要持有 WifiLockWakeLock。这些可确保系统保持 WiFi 无线电和 CPU 处于唤醒状态。如果使用 ExoPlayer,可以通过调用 setWakeMode 轻松完成此操作,这会自动在正确的时间获取和释放所需的锁。

重要的是,一旦不再播放音频,您必须释放锁(如果未使用 setWakeMode)并停止服务。

为什么 ExoPlayer 支持我的内容,但 ExoPlayer Cast 库不支持?

您尝试播放的内容可能未启用 CORSCast 框架要求内容启用 CORS 才能播放。

为什么内容无法播放,但没有显示错误?

您播放内容的设备可能不支持特定的媒体样本格式。通过将 EventLogger 作为监听器添加到您的播放器中,并在 Logcat 中查找类似此行,可以轻松确认:

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

NO_UNSUPPORTED_TYPE 表示设备无法解码 mimeType 指定的媒体样本格式。有关支持的样本格式的信息,请参阅 Android 媒体格式文档如何加载解码库并将其用于播放?可能也很有用。

如何加载解码库并将其用于播放?

  • 大多数解码器库都有手动步骤来检出和构建依赖项,因此请确保您已按照相关库的 README 中的步骤操作。例如,对于 ExoPlayer FFmpeg 库,必须遵循 libraries/decoder_ffmpeg/README.md 中的说明,包括向 启用解码器 传递配置标志以支持您想要播放的任何格式。
  • 对于包含原生代码的库,请确保您正在使用 README 中指定的正确版本的 Android NDK,并留意在配置和构建过程中出现的任何错误。在遵循 README 中的步骤后,您应该会看到 .so 文件出现在库路径的 libs 子目录中,每个受支持的架构都会有一个。
  • 要尝试使用演示应用中的库进行播放,请参阅启用捆绑解码器。有关如何在您自己的应用中使用该库的说明,请参阅该库的 README。
  • 如果您正在使用 DefaultRenderersFactory,您应该在 Logcat 中看到一条信息级别日志行,例如“Loaded FfmpegAudioRenderer”,表示解码器已加载。如果缺少此信息,请确保应用程序依赖于解码库。
  • 如果您在 Logcat 中看到来自 LibraryLoader 的警告级别日志,这表示库的原生组件加载失败。如果发生这种情况,请检查您是否正确遵循了库 README 中的步骤,并且在遵循说明时没有输出任何错误。

如果您在使用解码库时仍然遇到问题,请查看 Media3 问题跟踪器以查找任何相关的近期问题。如果您需要提交新问题,并且该问题与构建库的原生部分有关,请附上运行 README 说明的完整命令行输出,以帮助我们诊断问题。

我可以直接使用 ExoPlayer 播放 YouTube 视频吗?

不,ExoPlayer 无法播放 YouTube 视频,例如 https://www.youtube.com/watch?v=... 形式的网址。相反,您应该使用 YouTube IFrame Player API,这是在 Android 上播放 YouTube 视频的官方方式。

视频播放卡顿

设备可能无法足够快地解码内容,例如,如果内容比特率或分辨率超出设备功能。您可能需要使用较低质量的内容才能在此类设备上获得良好性能。

如果您在运行 Android 6.0(API 级别 23)到 Android 11(API 级别 30)的设备上遇到视频卡顿,特别是在播放受 DRM 保护或高帧率内容时,您可以尝试启用异步缓冲区排队

不稳定 API lint 错误

Media3 保证 API 表面的子集具有二进制兼容性。**不**保证二进制兼容性的部分标有 @UnstableApi。为了明确区分,除非它们被 @OptIn 注解,否则不稳定 API 符号的使用会生成 lint 错误。

@UnstableApi 注解不意味着 API 的质量或性能,仅表示它不是“API 冻结”的。

您有两种选择来处理不稳定 API lint 错误:

  • 切换到使用实现相同结果的稳定 API。
  • 继续使用不稳定 API 并用 @OptIn 注解其用法,如下所示。
添加 @OptIn 注解

Android Studio 可以帮助您添加注解

Screenshot: How to add the Optin annotation
图 2:使用 Android Studio 添加 @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 文件中抑制特定的 lint 错误来选择整个项目

 <?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 注解。