文章目录
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
事件)的时序。
枚举值解释
-
kNotDeferred
- 含义:未启用延迟加载,媒体资源会立即开始加载。
- 场景:当
preload
不是none
(如auto
或metadata
)时使用此状态。
-
kWaitingForStopDelayingLoadEventTask
- 含义:延迟加载已启用,但正在等待一个任务(Task)来关闭
delaying-the-load-event
标志。 - 背景:
- 页面加载完成时,浏览器会触发
load
事件。 - 如果媒体元素需要延迟加载,它会设置
delaying-the-load-event
标志,强制延迟load
事件直到媒体也加载完成。 - 此状态表示已计划一个任务来关闭此标志(但尚未执行),此时仍需保持延迟。
- 页面加载完成时,浏览器会触发
- 含义:延迟加载已启用,但正在等待一个任务(Task)来关闭
-
kWaitingForTrigger
- 含义:延迟加载已启用,正在等待用户触发(如点击播放按钮)。
- 行为:
- 媒体元素不会加载资源,直到用户显式交互(如播放操作)。
- 这是
preload="none"
的典型初始状态。
-
kExecuteOnStopDelayingLoadEventTask
- 含义:准备执行加载,将在
delaying-the-load-event
标志关闭后立即执行加载。 - 流程:
- 当用户触发加载(如点击播放)后,媒体元素不会立即加载,而是等待之前计划的
StopDelayingLoadEventTask
任务完成。 - 该任务会将
delaying-the-load-event
标志设为false
,随后触发实际加载。
- 当用户触发加载(如点击播放)后,媒体元素不会立即加载,而是等待之前计划的
- 含义:准备执行加载,将在
事件时序控制:确保 load
事件不会被无限期延迟(通过 delaying-the-load-event
标志管理)。
状态流转图
可能的步骤:
- 创建HTMLMediaElement时,deferred_load_state_ =
kNotDeferred
;(很短暂的状态) - 如果HTML媒体元素的
preload
属性设置为none
时,浏览器会延迟加载媒体资源,此时会调用DeferLoad(),deferred_load_state_ =kWaitingForStopDelayingLoadEventTask
;同时开启定时器deferred_load_timer_; - 分两种情况:
- 定时器任务还未执行时,用户触发Play()或StartDeferredLoad(),状态会更新为
kExecuteOnStopDelayingLoadEventTask
,加载操作将来由定时器任务调用; - 定时器任务已执行,状态会更新为
kWaitingForTrigger
,后续再触发Play()或StartDeferredLoad(),加载操作
将由StartDeferredLoad()调用;
- 定时器任务还未执行时,用户触发Play()或StartDeferredLoad(),状态会更新为
用户触发Play() 最终会调用 StartDeferredLoad();
加载操作 指的是 ExecuteDeferredLoad();
deferred_load_state_ 记录着状态;
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() 中继续
}
典型应用场景:
当浏览器需要暂停媒体加载(如遇到更高优先级的资源,或用户主动暂停)时,该机制可以:
- 正确维护网络状态
- 触发必要的事件通知
- 管理页面加载事件的阻塞状态
- 为后续恢复加载做好准备
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"
)或等待用户交互(如自动播放策略)而延迟加载。 - 当某些事件发生(例如用户主动开始播放、元素被移除、资源需立即加载),需取消延迟加载,此时调用此函数。
典型应用场景:
- 用户点击播放按钮,需立即加载媒体而非等待延迟加载。
- 页面卸载或媒体元素被移除时,清理未执行的延迟任务。
- 开发者通过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
表示需要立即执行延迟加载(如页面已准备好,或关键任务已完成)。定时器触发后直接加载资源。 -
状态
kWaitingForStopDelayingLoadEventTask
→kWaitingForTrigger
表示延迟加载的触发依赖于其他条件。定时器仅用于解除延迟事件阻塞,实际加载由外部事件(如用户点击播放)触发。 -
设计目标 :
通过状态机管理复杂的加载时序,避免资源竞争或冗余请求。例如,延迟加载可能用于:- 等待页面关键内容优先加载。
- 防止自动播放策略违规(如浏览器对自动播放的限制)。
应用场景示例:
- 当
<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_delay
为true
时,增加文档的延迟计数,阻止load
事件。 - 当
should_delay
为false
时,减少延迟计数,当计数归零时触发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;
}