播放器内核专项(二)PlayerMessage

PlayerMessage 的设计核心是一个状态机(State Machine),用于确保消息在复杂的播放器线程模型中能够安全、准确地传递。我们可以将这些字段分为三大类来理解:


1. 基础配置(定义消息是什么)

这些字段在消息创建时设定,定义了“谁来处理”、“处理什么”以及“何时处理”。

  • target: 消息的接收者(实现了 Target 接口)。这是处理逻辑的核心,通常是 Renderer 或其他播放器组件。
  • sender: 消息的发送者,负责将消息放入播放器的消息队列。
  • type: 消息类型(整数常量),用于告知 target 该如何处理该消息。
  • payload: 携带的具体数据,可以是任何对象(如设置参数、配置信息等)。
  • looper: 指定消息处理的线程。这非常重要,因为 ExoPlayer 是多线程环境,该字段确保消息被发送到正确的线程处理,避免线程不安全问题。
  • mediaItemIndex & positionMs: 定义了消息的触发时机(在哪一段视频的哪个时间点触发)。如果不设置,通常意味着立即触发。

2. 状态标志(追踪生命周期)

这些字段用于记录消息在传递过程中的状态,防止非法操作(例如:消息发出了还能修改吗?)。

  • isSent: 消息是否已经调用了 send()。一旦变为 true,上述所有配置字段(Type, Payload, Position 等)都不可再更改,保证了线程安全。
  • isDelivered: 消息是否已经成功抵达 target 并被执行。
  • isProcessed: 消息是否处理完毕(无论是因为成功执行了,还是因为被取消了)。
  • isCanceled: 消息是否在发送后被手动取消。

3. 控制与同步(决定消息如何终结)

  • deleteAfterDelivery: 决定消息是一次性的还是重复触发的。如果为 false,则当播放器循环播放回到该位置时,消息会再次触发。
  • clock: 用于 blockUntilDelivered(timeoutMs) 方法中,计算超时时间。

PlayerMessage 生命周期流程图

理解这些状态的关键在于理解消息从创建到销毁的旅程:

  1. 创建 (Initialization): 设置 Target、Timeline、Looper。
  2. 配置 (Configuration): 调用 setPayload, setPosition 等。此时 isSentfalse,允许修改。
  3. 发送 (Send): 调用 send()。状态 isSent 置为 true。此时配置被锁定。
  4. 排队与执行 (Delivery): 播放器在 looper 指定的线程中处理消息,调用 target.handleMessage()
  5. 结束 (Completion):
    • 成功:执行完毕,调用 markAsProcessed(true)
    • 取消:调用 cancel(),状态 isCanceled 置为 true,调用 markAsProcessed(false)
    • 标记为已处理 (isProcessed = true) 后,blockUntilDelivered() 等待的方法会收到通知并返回。

核心设计哲学

您可以看到代码中大量使用了 checkState(!isSent)。这体现了不可变性(Immutability)的设计原则:一旦消息投入发送队列,其状态就不应该被外部随意更改,从而避免了极其难以调试的竞态条件(Race Conditions)。

这种机制确保了即便您在 UI 线程创建消息,也可以安全地将其发送到播放器的播放线程(Playback Thread)进行执行。

,