问题排查


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

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

如果您的应用需要处理明文 HTTP 流量,则您需要使用允许该流量的网络安全配置。如需了解详情,请参阅 Android 的网络安全文档。如需启用所有明文 HTTP 流量,您只需将 android:usesCleartextTraffic="true" 添加到应用的 AndroidManifest.xml 的 application 元素中。

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_UNITS 和 FLAG_ALLOW_NON_IDR_KEYFRAMES。这些标志可以在 DefaultExtractorsFactory 上设置(使用 setTsExtractorFlags),也可以在 DefaultHlsExtractorFactory 上设置(使用构造函数)。使用 FLAG_DETECT_ACCESS_UNITS 不会产生任何副作用,只是相对于基于 AUD 的帧边界检测,计算开销较大。在播放某些 MPEG-TS 文件时,使用 FLAG_ALLOW_NON_IDR_KEYFRAMES 可能会导致在播放开始时和跳转后立即出现暂时性视觉损坏。

为什么在某些 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_LISTS 或 FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_EDIT_LISTS,这将导致提取器完全忽略修改列表。这些属性可以在 DefaultExtractorsFactory 上设置,方法是使用 setMp4ExtractorFlags 或 setFragmentedMp4ExtractorFlags。

为什么有些直播会失败并显示 HTTP 响应代码 301 或 302?

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

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 在其默认配置中不会遵循此重定向,这意味着播放将失败。

如有需要,您可以将 ExoPlayer 配置为在实例化应用中使用的 DefaultHttpDataSource.Factory 实例时遵循跨协议重定向。如需了解如何选择和配置网络栈,请点击此处。

为什么有些流会失败并显示 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_SS 传递给 MediaItem.Builder 的 setMimeType,以明确指定数据流的类型。

第二个不太常见的原因是 ExoPlayer 不支持您尝试播放的媒体的容器格式。在这种情况下,失败是预期行为,但您可以随时向我们的问题跟踪器提交功能请求,并附上容器格式和测试串流的详细信息。在提交新功能请求之前,请先搜索现有功能请求。

为什么 setPlaybackParameters 在某些设备上无法正常运行?

在 Android M 及更低版本上运行应用的调试 build 时,使用 setPlaybackParameters API 可能会导致性能不稳定、出现可听到的伪影以及 CPU 利用率较高。这是因为,对于在这些 Android 版本上运行的调试 build,对此 API 至关重要的优化功能处于停用状态。

请务必注意,此问题仅影响调试 build。它不会影响发布 build,因为发布 build 始终会启用优化。因此,您提供给最终用户的版本应该不会受到此问题的影响。

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

请参阅“使用入门”页面上的关于线程处理的说明。

如何解决“Unexpected status line: ICY 200 OK”问题?

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

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

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

如何在应用处于后台时保持音频播放?

请按照以下步骤操作,确保应用在后台运行时继续播放音频:

  1. 您需要有一个正在运行的前台服务。这样可以防止系统终止您的进程以释放资源。
  2. 您需要持有 WifiLock 和 WakeLock。这些权限可确保系统使 Wi-Fi 无线装置和 CPU 保持唤醒状态。如果使用 ExoPlayer,则可以通过调用 setWakeMode 轻松完成此操作,这将在正确的时间自动获取和释放所需的锁。

请务必在音频停止播放后立即释放锁定(如果不使用 setWakeMode)并停止服务。

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

您尝试播放的内容可能未启用 CORS。Cast 框架要求内容必须启用 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 版本,并留意配置和构建过程中出现的任何错误。按照自述文件中的步骤操作后,您应该会看到 .so 文件出现在库路径中每个受支持架构的 libs 子目录中。
  • 如需在演示应用中使用该库尝试播放,请参阅启用捆绑式解码器。如需了解如何从您自己的应用中使用该库,请参阅该库的自述文件。
  • 如果您使用的是 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 Surface 的二进制文件兼容性。不保证二进制文件兼容性的部分标有 @UnstableApi。为了明确区分,使用不稳定的 API 符号会生成 lint 错误,除非它们带有 @OptIn 注解。

@UnstableApi 注解并不意味着 API 的质量或性能存在问题,而只是表明它尚未“API 冻结”。

您可以通过以下两种方式处理不稳定的 API lint 错误:

  • 请改用可实现相同结果的稳定 API。
  • 继续使用不稳定的 API,并使用 @OptIn 注释用法,如下所示。
添加 @OptIn 注解

Android Studio 可以帮助您添加注解:

屏幕截图:如何添加 Optin 注释
图 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 文件来选择启用整个软件包:

Kotlin

// In your package-info.kt
@OptIn(UnstableApi::class)
package name.of.your.package

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

Java

// In your 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 注解。

如何测试或隔离由新优化或功能引起的问题?

Media3 包含功能标志基础架构,其中新引入的功能和优化在 Canary 模式下默认处于启用状态。

如果您怀疑应用中的问题或回归与新引入的功能或优化有关,可以暂时停用 Canary 模式:

Flags.setCanaryModeEnabled(false);

或者,使用 Flags.disableFlag(flagId) 单独停用特定功能标志。如需详细了解如何配置 Canary 模式、替换标志和设置远程终止开关,请参阅功能标志和 Canary 模式指南。