HTMLMediaElement - preload预加载

enum DeferredLoadState

// "Deferred loading" state (for preload=none).
enum DeferredLoadState {
  // The load is not deferred.
  kNotDeferred,
  // The load is deferred, and waiting for the task to set the
  // delaying-the-load-event flag (to false).
  kWaitingForStopDelayingLoadEventTask,
  // The load is the deferred, and waiting for a triggering event.
  kWaitingForTrigger,
  // The load is deferred, and waiting for the task to set the
  // delaying-the-load-event flag, after which the load will be executed.
  kExecuteOnStopDelayingLoadEventTask
};
DeferredLoadState deferred_load_state_;
//定时器对象,用于延迟执行媒体资源的加载(例如等待页面其他内容加载完成后再加载媒体)
HeapTaskRunnerTimer<HTMLMediaElement> deferred_load_timer_;

设计背景

当 HTML 媒体元素的 preload 属性设置为 none 时,浏览器会延迟加载媒体资源,直到用户显式触发(如点击播放)。这种机制可以节省带宽和资源。
此枚举定义了延迟加载过程中不同阶段的状态,用于协调加载逻辑与页面事件(如 load 事件)的时序。

枚举值解释

  1. kNotDeferred

    • 含义:未启用延迟加载,媒体资源会立即开始加载。
    • 场景:当 preload 不是 none(如 autometadata)时使用此状态。
  2. kWaitingForStopDelayingLoadEventTask

    • 含义:延迟加载已启用,但正在等待一个任务(Task)来关闭 delaying-the-load-event 标志。
    • 背景
      • 页面加载完成时,浏览器会触发 load 事件。
      • 如果媒体元素需要延迟加载,它会设置 delaying-the-load-event 标志,强制延迟 load 事件直到媒体也加载完成。
      • 此状态表示已计划一个任务来关闭此标志(但尚未执行),此时仍需保持延迟。
  3. kWaitingForTrigger

    • 含义:延迟加载已启用,正在等待用户触发(如点击播放按钮)。
    • 行为
      • 媒体元素不会加载资源,直到用户显式交互(如播放操作)。
      • 这是 preload="none" 的典型初始状态。
  4. kExecuteOnStopDelayingLoadEventTask

    • 含义:准备执行加载,将在 delaying-the-load-event 标志关闭后立即执行加载。
    • 流程
      • 当用户触发加载(如点击播放)后,媒体元素不会立即加载,而是等待之前计划的 StopDelayingLoadEventTask 任务完成。
      • 该任务会将 delaying-the-load-event 标志设为 false,随后触发实际加载。

事件时序控制:确保 load 事件不会被无限期延迟(通过 delaying-the-load-event 标志管理)。


状态流转图

可能的步骤:

  1. 创建HTMLMediaElement时,deferred_load_state_ = kNotDeferred;(很短暂的状态)
  2. 如果HTML媒体元素的preload属性设置为none时,浏览器会延迟加载媒体资源,此时会调用DeferLoad(),deferred_load_state_ = kWaitingForStopDelayingLoadEventTask;同时开启定时器deferred_load_timer_;
  3. 分两种情况:
    • 定时器任务还未执行时,用户触发Play()或StartDeferredLoad(),状态会更新为kExecuteOnStopDelayingLoadEventTask,加载操作将来由定时器任务调用;
    • 定时器任务已执行,状态会更新为kWaitingForTrigger,后续再触发Play()或StartDeferredLoad(),加载操作将由StartDeferredLoad()调用;

用户触发Play() 最终会调用 StartDeferredLoad();
加载操作 指的是 ExecuteDeferredLoad();
deferred_load_state_ 记录着状态;

preload="none" 初始化,DeferLoad()
定时器回调DeferredLoadTimerFired(TimerBase*)
用户触发Play()
用户触发Play()或调用StartDeferredLoad()
StopDelayingLoadEventTask 完成
kWaitingForStopDelayingLoadEventTask
kWaitingForTrigger
kNotDeferred
kExecuteOnStopDelayingLoadEventTask

HTMLMediaElement 构造函数的初始化列表中,将deferred_load_timer_初始化 ↓

HTMLMediaElement::HTMLMediaElement(const QualifiedName& tag_name,
                                   Document& document)
    : HTMLElement(tag_name, document),
      ......
      load_state_(kWaitingForSource),
      deferred_load_state_(kNotDeferred),
      deferred_load_timer_(document.GetTaskRunner(TaskType::kInternalMedia),
                           this,
                           &HTMLMediaElement::DeferredLoadTimerFired),
      ......
      ...... {
}

DeferLoad()

//实现了规范中资源加载算法的 “remote mode” 步骤(W3C 规范中的媒体资源加载流程)
//主要用于暂停媒体加载流程并管理加载事件的状态
void HTMLMediaElement::DeferLoad() {
  // This implements the "optional" step 4 from the resource fetch algorithm
  // "If mode is remote".
  DCHECK(!deferred_load_timer_.IsActive()); //确保延迟加载定时器未激活
  DCHECK_EQ(deferred_load_state_, kNotDeferred); //验证当前未处于延迟加载状态
  // 1. Set the networkState to NETWORK_IDLE.
  // 2. Queue a task to fire a simple event named suspend at the element.
  //将网络状态从 NETWORK_LOADING 改为 NETWORK_IDLE
  //触发 suspend 事件(通过任务队列异步触发)
  ChangeNetworkStateFromLoadingToIdle();
  // 3. Queue a task to set the element's delaying-the-load-event
  // flag to false. This stops delaying the load event.
  //使用零延迟定时器(立即执行)
  //主要目的是停止延迟加载事件标志(delaying-the-load-event flag)
  //通过异步任务队列确保操作符合事件循环机制
  deferred_load_timer_.StartOneShot(base::TimeDelta(), FROM_HERE);
  // 4. Wait for the task to be run.
  //更新状态机到 等待停止延迟加载事件任务 的状态
  deferred_load_state_ = kWaitingForStopDelayingLoadEventTask;
  // Continued in executeDeferredLoad().
  //后续处理将在 executeDeferredLoad() 中继续
}

典型应用场景
当浏览器需要暂停媒体加载(如遇到更高优先级的资源,或用户主动暂停)时,该机制可以:

  1. 正确维护网络状态
  2. 触发必要的事件通知
  3. 管理页面加载事件的阻塞状态
  4. 为后续恢复加载做好准备

ExecuteDeferredLoad()

//执行被延迟的媒体资源加载流程,属于HTML媒体资源获取算法的一部分。
//当用户触发播放等操作时,继续之前被暂停的资源加载过程。
void HTMLMediaElement::ExecuteDeferredLoad() {
  DCHECK_GE(deferred_load_state_, kWaitingForTrigger);//确保延迟加载状态至少处于等待触发状态(kWaitingForTrigger)

  // resource fetch algorithm step 4 - continued from deferLoad().

  // 5. Wait for an implementation-defined event (e.g. the user requesting that
  // the media element begin playback).  This is assumed to be whatever 'event'
  // ended up calling this method.
  // 5.等待一个实现定义的事件(比如用户请求开始播放),当这个事件触发后,就会调用当前函数来继续加载媒体资源。
  
  //终止之前的延迟加载状态
  //可能包括取消超时计时器/清除延迟加载标志位
  //防止重复加载或状态冲突
  CancelDeferredLoad();
  // 6. Set the element's delaying-the-load-event flag back to true (this
  // delays the load event again, in case it hasn't been fired yet).
  //将元素的delaying-the-load-event标志重置为true
  //延迟文档的load事件触发,直到媒体加载完成
  //符合HTML规范中媒体元素需要延迟load事件的要求
  SetShouldDelayLoadEvent(true);
  // 7. Set the networkState to NETWORK_LOADING.
  //将网络状态设置为LOADING
  //触发HTMLMediaElement的networkState属性变更
  //对应HTML规范中资源加载算法的状态变更步骤
  SetNetworkState(kNetworkLoading);
  //启动进度事件定时器
  //定期触发progress事件(如timeupdate事件)
  //用于更新媒体加载进度显示
  StartProgressEventTimer();
  //实际启动媒体资源的加载
  //调用底层媒体播放器(如Chromium的MediaPlayer)
  //开始获取媒体数据流(可能涉及网络请求或本地文件读取)
  StartPlayerLoad();
}

对应规范流程
实现W3C HTML规范中定义的媒体加载算法:

  • 步骤5:等待用户触发事件(如播放请求)
  • 步骤6:重新启用load事件延迟
  • 步骤7:设置网络状态为LOADING

设计意义

  • 延迟加载机制:优化页面加载性能,避免同时发起过多网络请求
  • 状态管理:确保符合HTML规范定义的状态转换流程
  • 事件控制:正确处理load事件和媒体相关事件的触发时机

该函数是连接用户交互(如播放操作)和实际媒体资源加载的关键桥梁,体现了HTML媒体元素按需加载的核心设计理念。


CancelDeferredLoad()

//取消媒体元素的延迟加载操作
void HTMLMediaElement::CancelDeferredLoad() {
  //deferred_load_timer_ 是一个定时器对象,用于延迟执行媒体资源的加载(例如等待页面其他内容加载完成后再加载媒体)。
  //Stop() 方法调用会停止该定时器,防止原计划的延迟加载操作被触发。
  deferred_load_timer_.Stop();
  deferred_load_state_ = kNotDeferred;
}

上下文背景

  • 媒体元素可能因资源优化策略(如preload="none")或等待用户交互(如自动播放策略)而延迟加载。
  • 当某些事件发生(例如用户主动开始播放、元素被移除、资源需立即加载),需取消延迟加载,此时调用此函数。

典型应用场景

  1. 用户点击播放按钮,需立即加载媒体而非等待延迟加载。
  2. 页面卸载或媒体元素被移除时,清理未执行的延迟任务。
  3. 开发者通过JavaScript强制触发加载(如调用load()方法)。

StartDeferredLoad()

void HTMLMediaElement::StartDeferredLoad() {
  if (deferred_load_state_ == kWaitingForTrigger) {//立即执行加载
    ExecuteDeferredLoad();
    return;
  }
  // 无操作(加载已计划)
  if (deferred_load_state_ == kExecuteOnStopDelayingLoadEventTask)//已计划在`停止延迟加载事件任务`时执行
    return;//直接返回,避免重复触发。(加载操作已安排在未来某个任务中执行,当前无需处理。)
  DCHECK_EQ(deferred_load_state_, kWaitingForStopDelayingLoadEventTask);
  //由其他代码在任务结束时触发加载。大概是这个定时器任务DeferredLoadTimerFired
  deferred_load_state_ = kExecuteOnStopDelayingLoadEventTask;
}

DeferredLoadTimerFired(TimerBase*)

//HeapTaskRunnerTimer<HTMLMediaElement> deferred_load_timer_;是一个
//定时器对象,用于延迟执行媒体资源的加载(例如等待页面其他内容加载完成后再加载媒体)
//触发此函数 ↓
void HTMLMediaElement::DeferredLoadTimerFired(TimerBase*) {
  SetShouldDelayLoadEvent(false);

  if (deferred_load_state_ == kExecuteOnStopDelayingLoadEventTask) {
    ExecuteDeferredLoad();
    return;
  }
  DCHECK_EQ(deferred_load_state_, kWaitingForStopDelayingLoadEventTask);
  //状态迁移:将状态更新为 kWaitingForTrigger,表示延迟加载的条件已满足,后续可能由其他逻辑(如用户交互或资源可用性变化)触发加载。
  deferred_load_state_ = kWaitingForTrigger;
}

函数作用
DeferredLoadTimerFired 是延迟加载定时器触发时的回调函数,用于处理媒体资源加载的时序控制,确保在合适的时机执行加载操作,同时避免阻塞主线程或影响页面其他关键事件。

状态机与设计意图

  • 状态 kExecuteOnStopDelayingLoadEventTask
    表示需要立即执行延迟加载(如页面已准备好,或关键任务已完成)。定时器触发后直接加载资源。

  • 状态 kWaitingForStopDelayingLoadEventTaskkWaitingForTrigger
    表示延迟加载的触发依赖于其他条件。定时器仅用于解除延迟事件阻塞,实际加载由外部事件(如用户点击播放)触发。

  • 设计目标
    通过状态机管理复杂的加载时序,避免资源竞争或冗余请求。例如,延迟加载可能用于:

    • 等待页面关键内容优先加载。
    • 防止自动播放策略违规(如浏览器对自动播放的限制)。

应用场景示例

  • <video> 设置了 preload="none" 时,资源加载会被延迟。代码通过状态机确保在用户显式请求播放时才触发加载。
  • 页面初始化时,推迟非关键媒体加载以优化加载性能。

SetShouldDelayLoadEvent(bool should_delay)

//设置媒体元素是否延迟文档的 load 事件
void HTMLMediaElement::SetShouldDelayLoadEvent(bool should_delay) {
  if (should_delay_load_event_ == should_delay)
    return;

  DVLOG(3) << "setShouldDelayLoadEvent(" << *this << ", "
           << base::ToString(should_delay) << ")";

  should_delay_load_event_ = should_delay;
  if (should_delay)
    GetDocument().IncrementLoadEventDelayCount();//增加文档的 load 事件延迟计数,表示有一个资源需要延迟 load 事件。
  else
    GetDocument().DecrementLoadEventDelayCount();//减少延迟计数,表示一个资源已满足条件,可以尝试触发 load 事件。
}

核心逻辑

  • 目的:媒体资源(如 <video><audio>)可能需要较长时间加载,延迟 load 事件可确保文档的 load 事件在所有关键资源(包括媒体)加载完成后触发。
  • 机制
    • should_delaytrue 时,增加文档的延迟计数,阻止 load 事件。
    • should_delayfalse 时,减少延迟计数,当计数归零时触发 load 事件。

应用场景

  • 媒体元素开始加载资源时调用 SetShouldDelayLoadEvent(true)
  • 当资源加载完成、发生错误或中止时调用 SetShouldDelayLoadEvent(false)

此代码通过控制延迟计数,确保文档生命周期与媒体资源加载状态同步。


SetPlayerPreload()

//配置媒体播放器的预加载策略 并在需要时触发延迟加载
void HTMLMediaElement::SetPlayerPreload() {
  if (web_media_player_)
    web_media_player_->SetPreload(EffectivePreloadType());

  // LoadIsDeferred() 检查是否因性能优化策略(如媒体不在可视区域)而延迟加载。
  // 如果预加载策略不是 none,则通过 StartDeferredLoad() 启动实际加载。
  if (LoadIsDeferred() &&
      EffectivePreloadType() != WebMediaPlayer::kPreloadNone)
    StartDeferredLoad(); //仅被当前函数调用
}

应用场景示例

  • <video preload="auto"> 被添加到页面但不可见时,可能先延迟加载,直到用户滚动到附近区域才会触发 StartDeferredLoad()
  • 如果用户设置 preload="none",则两个条件均不满足,媒体不会自动加载。

LoadIsDeferred()

bool HTMLMediaElement::LoadIsDeferred() const {
  return deferred_load_state_ != kNotDeferred;
}

SetIgnorePreloadNone()

//强制忽略preload="none"的设置,并更新播放器的预加载策略
void HTMLMediaElement::SetIgnorePreloadNone() {
  DVLOG(3) << "setIgnorePreloadNone(" << *this << ")";
  ignore_preload_none_ = true;
  SetPlayerPreload();
}

为何需要 ignore_preload_none_

  • 当用户主动与媒体交互(如开始播放)时,即使 preload="none",浏览器也需加载媒体数据以确保播放流畅。此时通过设置 ignore_preload_none_ = true 来绕过预加载限制。

设计意图

  • 优化用户体验:在用户显式操作后,忽略保守的preload设置,避免因未预加载导致的播放卡顿。
  • 资源策略动态调整:根据运行时状态(如用户交互)动态更新预加载策略。

典型应用场景

  • 用户点击播放按钮后,媒体引擎可能调用此函数,强制加载内容,即使页面原本设置了preload="none"以节省带宽。

总结
这段代码是Chromium处理HTML媒体元素预加载策略的关键逻辑之一,通过标志位和动态策略调整,平衡了页面初始化时的资源节省需求与用户交互后的流畅播放体验。


enum Preload

 enum Preload {
   kPreloadNone,     //不主动加载任何数据          = 不预加载
   kPreloadMetaData, //仅加载元数据(如时长/尺寸)
   kPreloadAuto,     //尽可能预加载媒体文件        = 积极预加载
 };

EffectivePreloadType()

//算出HTML媒体元素(如<video>/<audio>)的有效预加载策略
WebMediaPlayer::Preload HTMLMediaElement::EffectivePreloadType() const {
  // 1. 自动播放处理:
  //当媒体元素设置了autoplay属性,且当前环境不需要用户手势(如点击)即可自动播放时,
  //强制使用kPreloadAuto策略。这是为了确保自动播放时能快速加载足够的媒体数据,实现流畅播放。
  if (Autoplay() && !autoplay_policy_->IsGestureNeededForPlayback())
    return WebMediaPlayer::kPreloadAuto;

  // 2. 常规预加载策略:
  //获取元素通过preload属性显式设置的预加载策略(如html的preload="metadata")
  WebMediaPlayer::Preload preload = PreloadType();

  // 3. 特殊策略覆盖:
  //当系统需要忽略none设置时(比如用户已与页面交互),即使preload属性设为none,也会升级为metadata预加载。
  //这通常发生在需要优化播放体验但又不希望完全自动加载的场景。
  if (ignore_preload_none_ && preload == WebMediaPlayer::kPreloadNone)
    return WebMediaPlayer::kPreloadMetaData;

  //其他情况直接返回元素原本的preload设置
  return preload;
}
bool HTMLMediaElement::Autoplay() const {
  //判断HTML媒体元素(如 <video> 或 <audio>)是否设置了 autoplay 属性
  return FastHasAttribute(html_names::kAutoplayAttr);
}

PreloadType()

//根据 <video>/<audio> 标签的 preload 属性值,返回对应的预加载策略枚举值 WebMediaPlayer::Preload。
//该策略会影响浏览器如何预加载媒体资源。
WebMediaPlayer::Preload HTMLMediaElement::PreloadType() const {
  //从 HTML 元素中获取 preload 属性的值(如 "none"、"metadata"、"auto")。
  const AtomicString& preload = FastGetAttribute(html_names::kPreloadAttr);
  if (EqualIgnoringASCIICase(preload, "none")) {
    UseCounter::Count(GetDocument(), WebFeature::kHTMLMediaElementPreloadNone);
    return WebMediaPlayer::kPreloadNone;
  }

  if (EqualIgnoringASCIICase(preload, "metadata")) {
    UseCounter::Count(GetDocument(),
                      WebFeature::kHTMLMediaElementPreloadMetadata);
    return WebMediaPlayer::kPreloadMetaData;
  }

  // Force preload to 'metadata' on cellular connections.
  // 蜂窝网络强制降级
  // 如果设备处于蜂窝网络(移动数据),强制使用 metadata 预加载,避免消耗过多流量。
  if (GetNetworkStateNotifier().IsCellularConnectionType()) {
    UseCounter::Count(GetDocument(),
                      WebFeature::kHTMLMediaElementPreloadForcedMetadata);
    return WebMediaPlayer::kPreloadMetaData;
  }

  // Per HTML spec, "The empty string ... maps to the Automatic state."
  // https://html.spec.whatwg.org/C/#attr-media-preload
  if (EqualIgnoringASCIICase(preload, "auto") ||
      EqualIgnoringASCIICase(preload, "")) {
    UseCounter::Count(GetDocument(), WebFeature::kHTMLMediaElementPreloadAuto);
    return WebMediaPlayer::kPreloadAuto;//默认行为由浏览器决定,可能根据网络环境、设备性能等动态调整。
  }

  // "The attribute's missing value default is user-agent defined, though the
  // Metadata state is suggested as a compromise between reducing server load
  // and providing an optimal user experience."

  // The spec does not define an invalid value default:
  // https://www.w3.org/Bugs/Public/show_bug.cgi?id=28950
  // 无效值的默认回退
  // 如果 preload 属性值非法(如 "invalid"),回退到 metadata 模式。
  UseCounter::Count(GetDocument(), WebFeature::kHTMLMediaElementPreloadDefault);
  return WebMediaPlayer::kPreloadMetaData;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值