疑难解答


修复“不允许明文 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”错误

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 来启用,该标志可以使用 DefaultExtractorsFactory 使用 setMp3ExtractorFlags 来设置。请注意,它不能很好地扩展到大型 MP3 文件,特别是如果用户在开始播放后不久尝试查找流的结尾附近,这需要播放器等待下载并索引整个流才能执行查找。在 ExoPlayer 中,我们决定在这种情况下优先考虑速度而不是准确性,因此 FLAG_ENABLE_INDEX_SEEKING 默认情况下处于禁用状态。

如果您控制正在播放的媒体,我们强烈建议您使用更合适的容器格式,例如 MP4。据我们所知,没有用例表明 MP3 是最佳的媒体格式选择。

为什么我的视频搜索速度很慢?

当播放器在视频中查找新的播放位置时,它需要执行两件事:

  1. 将对应于新的播放位置的数据加载到缓冲区中(如果此数据已缓冲,则可能不需要)。
  2. 刷新视频解码器并从新的播放位置之前的 I 帧(关键帧)开始解码,这是由于大多数视频压缩格式使用 帧内编码。为了确保查找是准确的(即,播放从查找位置开始精确开始),需要解码前一个 I 帧和查找位置之间的所有帧并立即丢弃(无需在屏幕上显示)。

可以通过增加播放器在内存中缓冲的数据量或 预缓存数据到磁盘 来减轻 (1) 带来的延迟。

可以通过使用 ExoPlayer.setSeekParameters 来降低查找的精度,或者重新编码视频以使其具有更频繁的 I 帧(这将导致输出文件更大)来减轻 (2) 带来的延迟。

为什么某些 MPEG-TS 文件无法播放?

某些 MPEG-TS 文件不包含访问单元分隔符 (AUD)。默认情况下,ExoPlayer 依赖 AUD 来廉价地检测帧边界。同样,某些 MPEG-TS 文件不包含 IDR 关键帧。默认情况下,这些是 ExoPlayer 考虑的唯一类型的关键帧。

当被要求播放缺少 AUD 或 IDR 关键帧的 MPEG-TS 文件时,ExoPlayer 似乎会卡在缓冲状态。如果需要播放此类文件,可以使用 FLAG_DETECT_ACCESS_UNITSFLAG_ALLOW_NON_IDR_KEYFRAMES。这些标志可以使用 DefaultExtractorsFactory 使用 setTsExtractorFlags 或使用 构造函数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,这将导致提取器完全忽略编辑列表。这些可以使用 DefaultExtractorsFactory 使用 setMp4ExtractorFlagssetFragmentedMp4ExtractorFlags 来设置。

为什么某些流会因 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 利用率高等问题。这是因为对该 API 至关重要的优化已针对在这些 Android 版本上运行的调试版本禁用。

需要注意的是,此问题仅影响调试版本。它 *不会* 影响发行版本,因为发行版本始终启用此优化。因此,您提供给最终用户的版本不会受到此问题的影响。

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

请参阅入门页面上的 关于线程的说明

如何修复“意外状态行: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=... 形式的 URL。相反,您应该使用 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 注解。