问题排查


修正“不允许明文 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 启用此策略,该策略可以使用 setMp3ExtractorFlagsDefaultExtractorsFactory 上进行设置。请注意,它无法很好地扩展到大型 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 分别进行播放。可以使用 setTsExtractorFlagsDefaultExtractorsFactory 上设置这些标志,也可以使用构造函数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,这将导致提取器完全忽略编辑列表。可以使用 setMp4ExtractorFlagssetFragmentedMp4ExtractorFlagsDefaultExtractorsFactory 上设置这些属性。

为什么有些直播会因 HTTP 响应代码 301 或 302 而失败?

HTTP 响应代码 301 和 302 都表示重定向。您可以在维基百科上找到简短说明。当 ExoPlayer 发出请求并收到状态代码为 301 或 302 的响应时,通常会遵循重定向并正常开始播放。默认情况下,只有在跨协议重定向的情况下,系统不会执行此操作。跨协议重定向是指从 HTTPS 重定向到 HTTP 或反之(或者不太常见的,在另一对协议之间重定向)。您可以使用 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.mp3https://secondserver.example.net/test.mp3。两者都是 HTTPS,因此这不是跨协议重定向。第二个重定向是从 https://secondserver.example.net/test.mp3http://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_MPDMimeTypes.APPLICATION_M3U8MimeTypes.APPLICATION_SS 传递给 MediaItem.BuildersetMimeType,以明确指定流的类型。

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

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

在 Android M 及更低版本上运行应用的调试 build 时,使用 setPlaybackParameters API 可能会导致性能不流畅、出现可听到的伪影和 CPU 利用率过高。这是因为对于在这些 Android 版本上运行的调试 build,对相应 API 很重要的一项优化已被停用。

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

“Player is accessed on the wrong thread”错误是什么意思?

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

如何解决“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 媒体格式文档如何获取可加载并用于播放的解码库?也可能很有用。

如何获取解码库以加载并用于播放?

  • 大多数解码器库都有用于检出和构建依赖项的手动步骤,因此请确保您已按照相关库的自述文件中的步骤操作。 例如,对于 ExoPlayer FFmpeg 库,必须按照 libraries/decoder_ffmpeg/README.md 中的说明操作,包括传递配置标志以启用解码器来播放任何您想播放的格式。
  • 对于包含原生代码的库,请确保您使用的是 README 中指定的正确版本的 Android NDK,并留意配置和构建期间出现的任何错误。按照 README 中的步骤操作后,您应该会在库路径的 libs 子目录中看到针对每个受支持架构的 .so 文件。
  • 如需尝试在演示应用中使用该库进行播放,请参阅启用捆绑的解码器。如需了解如何从您自己的应用中使用该库,请参阅该库的 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 Surface 的二进制兼容性。保证二进制兼容性的部分标记为 @UnstableApi。为了明确区分,不稳定的 API 符号的使用会生成 lint 错误,除非它们使用 @OptIn 进行注释。

@UnstableApi 注释并不表示 API 的质量或性能,仅表示该 API 不是“API 冻结”。

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

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

Android Studio 可帮助您添加注释:

屏幕截图:如何添加选择启用注释
图 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 注解。