媒体源扩展™

W3C 工作草案

关于本文档的更多信息
此版本:
https://www.w3.org/TR/2025/WD-media-source-2-20251031/
最新发布版本:
https://www.w3.org/TR/media-source-2/
最新编辑草案:
https://w3c.github.io/media-source/
历史记录:
https://www.w3.org/standards/history/media-source-2/
提交历史
最新推荐标准:
https://www.w3.org/TR/2016/REC-media-source-20161117/
编辑:
(Apple公司)
Mark Watson (Netflix公司)
前编辑:
(W3C 特邀专家) - 截止至
Jerry Smith (微软公司) - 截止至
Aaron Colwell (谷歌公司) - 截止至
Adrian Bateman (微软公司) - 截止至
反馈:
GitHub w3c/media-source (拉取请求, 新建议, 开放议题)
public-media-wg@w3.org 邮件主题行请使用 [media-source-2] … 消息主题 … (邮件存档)
浏览器支持:
caniuse.com

摘要

本规范扩展了 HTMLMediaElement [HTML], 允许 JavaScript 生成用于播放的媒体流。JavaScript 生成流可满足多种使用场景,如自适应流媒体和直播时移等。

本文档状态

本节描述本文档在发布时的状态。当前 W3C 发布文档及本技术报告最新修订版可在 W3C 标准与草案索引中查阅。

除编辑性更新外,自 W3C 推荐标准 2016年11月发布以来的实质性变化包括:

完整变更列表请参见 提交历史

工作组维护着编辑尚未处理的所有问题报告列表

实现者需注意本规范并不稳定。未参与相关讨论的实现者可能会发现规范发生不兼容变化。有兴趣在本规范进入候选推荐阶段前进行实现的厂商应关注 GitHub 仓库并参与讨论。

本文档由 媒体工作组推荐标准流程发布为工作草案。

作为工作草案发布,并不意味着 W3C 及其成员的认可。

本文档为草稿,可能随时被更新、替换或废止。除“进行中的工作”外,不适宜引用本文件。

本文档由遵循 W3C 专利政策 的小组制作。 W3C 维护着 本小组相关专利公开的公共列表; 该页面还包含专利披露说明。个人如获知某专利涉及 必要权利要求 ,须遵循 W3C 专利政策第6节进行披露。

本文档适用 2025年8月18日 W3C 流程文件

1. 引言

本节为非规范内容。

本规范允许 JavaScript 动态构建 <audio> 和 <video> 的媒体流。它定义了一个 MediaSource 对象,可作为 HTMLMediaElement 的媒体数据源。MediaSource 对象包含一个或多个 SourceBuffer 对象。应用可以把数据分段追加到 SourceBuffer 对象,并能根据系统性能等因素调整追加数据的质量。SourceBuffer 对象中的数据会作为音频、视频和文本轨道缓冲区进行管理,解码后播放。与这些扩展配套使用的字节流规范可在字节流格式注册表 [MSE-REGISTRY] 中查阅。

媒体源管道模型图
1 媒体源管道模型图

1.1 目标

本规范设计时考虑了以下目标:

本规范定义了:

2. 定义

活动轨道缓冲区

轨道缓冲区编码帧提供数据,供 enabled audioTracksselected videoTracks, 以及 "showing""hidden" textTracks使用。 这些轨道都关联于 SourceBuffer 对象,并在 activeSourceBuffers 列表中。

追加窗口

用于追加时过滤 编码帧展示时间戳范围。追加窗口表示一个连续的时间区间,拥有起始和结束时间。展示时间戳在该区间内的编码帧可追加到 SourceBuffer,区间外的编码帧会被过滤。追加窗口的起止时间分别由 appendWindowStartappendWindowEnd 属性控制。

编码帧

一组包含 展示时间戳解码时间戳编码帧时长的媒体数据单元。

编码帧时长

指某个 编码帧的时长。对于视频和文本,时长表示帧或文本应被显示的时间;对于音频,时长表示编码帧包含的所有采样的总和。例如,若音频帧包含441个采样,采样率为44100Hz,则帧时长为10毫秒。

编码帧结束时间戳

某个 编码帧展示时间戳加上其 编码帧时长,表示紧跟该编码帧之后的展示时间戳。

编码帧组

一组相邻、解码时间戳单调递增且无间隙的 编码帧。由 编码帧处理算法和 abort() 调用检测到的间断将触发新编码帧组的开始。

解码时间戳

解码时间戳指示帧需要被解码的最晚时间(假设该帧及其依赖帧均瞬时解码和渲染),等于最早依赖该帧的 展示时间戳(按 展示顺序)。如能按非展示顺序解码,字节流中必须存在或可推导解码时间戳,否则用户代理必须运行 追加错误算法。若不能按非展示顺序解码且字节流无解码时间戳,则解码时间戳等于展示时间戳。

初始化分段

包含解码一组 媒体分段所需全部初始化信息的字节序列,包括编解码器初始化数据、复用分段的 轨道ID映射、时间戳偏移(如编辑列表)等。

字节流格式注册表中的 字节流格式规范包含格式相关示例。

媒体分段

包含某一 媒体时间线片段的分包及带时间戳的媒体数据的字节序列。媒体分段总是与最近追加的 初始化分段关联。

字节流格式注册表中的 字节流格式规范包含格式相关示例。

MediaSource 对象 URL

一个 MediaSource 对象 URL 是通过 createObjectURL() 创建的唯一 blob URL。它用于将 MediaSource 对象绑定到 HTMLMediaElement。

这些 URL 与 blob URLs 相同,但其定义中凡涉及 FileBlob 对象的内容,也适用于 MediaSource 对象。

MediaSource 对象 URL 的 origin 是调用 createObjectURL()相关设置对象this

例如,MediaSource 对象 URL 的 origin 会影响媒体元素被 canvas 消费的方式。

父媒体源

某个 SourceBuffer 对象的父媒体源是创建它的 MediaSource 对象。

展示起始时间

展示起始时间是演示中最早的时间点,指定初始 播放位置最早可能位置。所有用本规范创建的演示的展示起始时间均为0。

在判断 HTMLMediaElementbuffered 是否包含当前播放位置时,允许实现在展示起始时间及其后、首个 TimeRanges 之前的当前播放位置播放首个 TimeRanges, 如果该区间在展示起始时间后很短(如1秒)内开始。这样可兼容复用流通常不会让所有轨道精确从展示起始时间开始的实际情况。实现必须报告实际缓冲区范围,不受此允许影响。

展示区间

某个 编码帧的展示区间是从其 展示时间戳展示时间戳加上 编码帧时长的时间区间。例如,若编码帧展示时间戳为10秒,时长为100毫秒,则展示区间为[10-10.1)。注意区间起点为包含,终点为不包含。

展示顺序

编码帧在演示中被渲染的顺序。展示顺序通过按编码帧的 展示时间戳单调递增排序实现。

展示时间戳

演示中某一特定时间的引用。编码帧中的展示时间戳表示该帧应被渲染的时间。

随机访问点

媒体分段中可在不依赖前序数据的情况下开始解码和连续播放的位置。视频通常为I帧位置,音频多数帧都可视为随机访问点。由于视频轨道随机访问点分布稀疏,复用流通常以这些点为流的随机访问点。

SourceBuffer 字节流格式规范

描述 SourceBuffer 实例可接受字节流格式的 字节流格式规范字节流格式规范最初由创建对象时 addSourceBuffer() 所传 type 决定,也可通过 changeType() 方法更新。

SourceBuffer 配置

由单个 MediaSource 实例拥有的、分布于一个或多个 SourceBuffer 的特定轨道集合。

实现必须至少支持1个 MediaSource 对象,并具备如下配置:

  • 一个 SourceBuffer,含1个音频轨道和/或1个视频轨道。
  • 两个 SourceBuffer,一个用于处理单一音频轨道,另一个用于处理单一视频轨道。

MediaSource 对象须支持上述每种配置,但仅需同时支持一种。支持多种或更多配置属于实现质量问题。

轨道描述

字节流格式相关结构,包含单轨道的 轨道ID、编解码器配置及其他元数据。单个初始化分段中的每个轨道描述均有唯一 轨道ID。如轨道ID不唯一,用户代理必须运行 追加错误算法。

轨道ID

轨道ID是字节流格式相关的标识符,用于标记字节流中属于某轨道的部分。轨道描述中的轨道ID标识媒体分段的哪些部分属于该轨道。

3. MediaSource 接口

MediaSource 接口表示用于 HTMLMediaElement 的媒体数据源。它会记录此源的 readyState 状态,以及可以用于向演示添加媒体数据的 SourceBuffer 对象列表。MediaSource 对象由 Web 应用创建,然后附加到 HTMLMediaElement。应用通过 SourceBuffer 对象与 sourceBuffers 向该源添加媒体数据。HTMLMediaElement 在播放时会从 MediaSource 对象获取媒体数据。

每个 MediaSource 对象都拥有一个 [[live seekable range]] 内部槽,用于存储一个 标准化的 TimeRanges 对象。该对象在 MediaSource 创建时初始化为空的 TimeRanges 对象,通过 setLiveSeekableRange()clearLiveSeekableRange() 维护,并在 10. HTMLMediaElement 扩展 中用于修改 HTMLMediaElementseekable 行为。

每个 MediaSource 对象都有一个 [[has ever been attached]] 内部槽,用于存储一个 boolean。在 MediaSource 创建时初始化为 false,在扩展的 HTMLMediaElement资源获取算法中(参见 绑定到媒体元素 算法)被设置为 true。扩展的 资源获取算法会用此内部槽条件性地阻止通过 MediaSourceHandle 绑定到 HTMLMediaElementsrcObject 属性的 MediaSource 对象绑定。

WebIDLenum ReadyState {
  "closed",
  "open",
  "ended",
};
closed
表示该源当前未绑定到媒体元素。
open
该源已被媒体元素打开,可以向 SourceBuffer 对象(在 MediaSourcesourceBuffers 属性中)追加数据。
ended
该源仍绑定于媒体元素,但已调用 endOfStream()
Issue 276: MSE-in-Workers: 建议增加 "closing" readyState 以解释关闭过程中出现的新 `InvalidStateError` 异常 mse-in-workers
WebIDLenum EndOfStreamError {
  "network",
  "decode",
};
network

终止播放并表示发生了网络错误。

JavaScript 应用 应当 使用该状态码以网络错误终止播放。例如,获取媒体数据时发生网络错误。

decode

终止播放并表示发生了解码错误。

JavaScript 应用 应当 使用该状态码以解码错误终止播放。例如,处理带外媒体数据时发生解析错误。

WebIDL[Exposed=(Window,DedicatedWorker)]
interface MediaSource : EventTarget {
    constructor();

    [SameObject, Exposed=DedicatedWorker]
    readonly  attribute MediaSourceHandle handle;
    readonly  attribute SourceBufferList sourceBuffers;
    readonly  attribute SourceBufferList activeSourceBuffers;
    readonly  attribute ReadyState readyState;

    attribute unrestricted double duration;
    attribute EventHandler onsourceopen;
    attribute EventHandler onsourceended;
    attribute EventHandler onsourceclose;

    static readonly attribute boolean canConstructInDedicatedWorker;

    SourceBuffer addSourceBuffer(DOMString type);
    undefined removeSourceBuffer(SourceBuffer sourceBuffer);
    undefined endOfStream(optional EndOfStreamError error);
    undefined setLiveSeekableRange(double start, double end);
    undefined clearLiveSeekableRange();
    static boolean isTypeSupported(DOMString type);
};

3.1 handle 属性

包含一个用于将专用 worker MediaSource 对象通过 srcObject 附加到 HTMLMediaElement 的句柄。此句柄在本 MediaSource 对象多次访问时保持为同一个对象,但每个 MediaSource 对象各自独立。

注意

本规范未来可能允许在主 Window 上可见此属性。如果是这样,规范需要谨慎处理,以避免出现向后不兼容的变化,例如访问此属性时抛出异常等情况。

获取时,按以下步骤执行:

  1. 如果本 MediaSource 对象的句柄尚未创建,则执行以下步骤:
    1. created handle 为新建 MediaSourceHandle 对象及相关资源的结果,并与本 MediaSource 内部关联。
    2. 将属性值更新为 created handle
  2. 返回当前属性值的 MediaSourceHandle 对象。

3.2 sourceBuffers 属性

包含与本 SourceBuffer 关联的 MediaSource 对象列表。当 MediaSourcereadyState 等于 "closed" 时,此列表为空。当 readyState 状态变为 "open" 时,可以使用 addSourceBuffer() 向该列表添加 SourceBuffer 对象。

3.3 activeSourceBuffers 属性

包含 sourceBuffers 的子集,这些缓冲区为 selected 视频轨道、 enabled 音频轨道,以及 "showing""hidden" 文本轨道提供数据。

本列表中的 SourceBuffer 对象 必须sourceBuffers 属性中的顺序一致;例如,若仅 sourceBuffers[0] 和 sourceBuffers[3] 在 activeSourceBuffers 中,则 activeSourceBuffers[0] 必须等于 sourceBuffers[0],activeSourceBuffers[1] 必须等于 sourceBuffers[3]。

注意

3.15.5 选中/启用轨道状态变更 一节描述了此属性的更新机制。

3.4 readyState 属性

表示 MediaSource 对象当前状态。创建 MediaSource 时,readyState 必须被设置为 "closed"。

3.5 duration 属性

允许 Web 应用设置演示时长。创建 MediaSource 对象时,时长初始为 NaN。

获取时,按以下步骤执行:

  1. 如果 readyState 属性为 "closed",则返回 NaN 并终止步骤。
  2. 返回当前属性值。

设置时,按以下步骤执行:

  1. 如设置值为负数或 NaN,则抛出 TypeError 异常并终止步骤。
  2. 如果 readyState 属性不是 "open",则抛出 InvalidStateError 异常并终止步骤。
  3. 如果 updating 属性在任意 SourceBuffer(在 sourceBuffers 中)上为 true,则抛出 InvalidStateError 异常并终止步骤。
  4. 使用赋值给该属性的值作为 new duration,运行 时长变更算法。
    注意

    时长变更算法会在当前已缓冲编码帧结束时间更大时自动上调 new duration

    注意

    appendBuffer()endOfStream() 在某些情况下也会更新时长。

3.6 canConstructInDedicatedWorker 属性

返回 true。

注意

此属性用于在主线程和专用 worker 中检测是否支持在专用 worker 创建和使用 MediaSource 对象,减少需要高延迟检测 polyfill(如尝试在专用 worker 创建 MediaSource 对象,尤其在不支持该特性时)的需求。

3.7 addSourceBuffer() 方法

将新的 SourceBuffer 添加到 sourceBuffers

  1. 如果 type 为空字符串,则抛出 TypeError 异常并终止步骤。
  2. 如果 type 包含不支持的 MIME 类型,或者包含与其他 SourceBuffer 对象指定类型不兼容的 MIME 类型,则抛出 NotSupportedError 异常并终止步骤。
  3. 如果用户代理无法再处理更多 SourceBuffer 对象,或根据 type 创建 SourceBuffer 会导致不支持的 SourceBuffer 配置, 则抛出 QuotaExceededError 异常并终止步骤。
    注意

    例如,用户代理 可以 在媒体元素已达 HAVE_METADATA readyState 时抛出 QuotaExceededError 异常。这种情况可能出现在用户代理的媒体引擎不支持在播放期间添加更多轨道时。

  4. 如果 readyState 属性不处于 "open" 状态,则抛出 InvalidStateError 异常并终止步骤。
  5. buffer 如果 thisManagedMediaSource,则为新建 ManagedSourceBuffer 实例,否则为新建 SourceBuffer 实例,并关联各自的资源。
  6. buffer[[generate timestamps flag]] 设置为 Media Source Extensions™ 字节流格式注册表中与 type 关联的条目的“生成时间戳标志”值。
  7. 如果 buffer[[generate timestamps flag]] 为 true,则将 buffermode 设为 "sequence";否则设为 "segments"。
  8. 追加 bufferthissourceBuffers
  9. 排队一个任务用于 触发事件,事件名称为 addsourcebuffer,目标为 thissourceBuffers
  10. 返回 buffer

3.8 removeSourceBuffer() 方法

SourceBuffersourceBuffers 移除一个 SourceBuffer。

  1. 如果 sourceBuffer 指定的对象不在 sourceBuffers 中,则抛出 NotFoundError 异常,并终止后续步骤。
  2. 如果 sourceBuffer.updating 属性为 true,则执行以下步骤:
    1. 如正在运行,终止 缓冲区追加算法。
    2. sourceBuffer.updating 属性设为 false。
    3. 排队任务触发事件,事件名为 abort,目标为 sourceBuffer
    4. 排队任务触发事件,事件名为 updateend,目标为 sourceBuffer
  3. SourceBuffer audioTracks list 等于 sourceBuffer.audioTracks 返回的 AudioTrackList 对象。
  4. 如果 SourceBuffer audioTracks list 不为空,执行以下步骤:
    1. SourceBuffer audioTracks list 中的每个 AudioTrack 对象,执行:
      1. sourceBuffer 属性设为 null。
      2. SourceBuffer audioTracks list 中移除该 AudioTrack 对象。
        注意

        此操作应触发 AudioTrackList [HTML] 的逻辑,排队任务以 触发事件,事件名为 removetrack,使用 TrackEvent,其 track 属性初始化为该 AudioTrack 对象,目标为 SourceBuffer audioTracks list。若移除步骤开始时 enabled 属性为 true,还应触发 AudioTrackList [HTML] 的逻辑,排队任务以触发事件名为 change,目标为 SourceBuffer audioTracks list

      3. 使用 必要时镜像算法,在 Window 中移除该 AudioTrack 对象(或如 MediaSource 对象是在 DedicatedWorkerGlobalScope 构造,则移除其 Window 镜像):
        1. HTMLMediaElement audioTracks list 等于 HTMLMediaElement.audioTracks 属性返回的 AudioTrackList 对象。
        2. HTMLMediaElement audioTracks list 中移除该 AudioTrack 对象。
          注意

          此操作应触发 AudioTrackList [HTML] 的逻辑,排队任务以 触发事件,事件名为 removetrack,使用 TrackEvent,其 track 属性初始化为该 AudioTrack 对象,目标为 HTMLMediaElement audioTracks list。如果移除步骤开始时 enabled 属性为 true,还应触发 AudioTrackList [HTML] 的逻辑,排队任务以触发事件名为 change,目标为 HTMLMediaElement audioTracks list

  5. SourceBuffer videoTracks list 等于 sourceBuffer.videoTracks 返回的 VideoTrackList 对象。
  6. 如果 SourceBuffer videoTracks list 不为空,执行以下步骤:
    1. SourceBuffer videoTracks list 中的每个 VideoTrack 对象,执行:
      1. sourceBuffer 属性设为 null。
      2. SourceBuffer videoTracks list 中移除该 VideoTrack 对象。
        注意

        此操作应触发 VideoTrackList [HTML] 的逻辑,排队任务以 触发事件,事件名为 removetrack,使用 TrackEvent,其 track 属性初始化为该 VideoTrack 对象,目标为 SourceBuffer videoTracks list。若移除步骤开始时 selected 属性为 true,还应触发 VideoTrackList [HTML] 的逻辑,排队任务以触发事件名为 change,目标为 SourceBuffer videoTracks list

      3. 使用 必要时镜像算法,在 Window 中移除该 VideoTrack 对象(或如 MediaSource 对象是在 DedicatedWorkerGlobalScope 构造,则移除其 Window 镜像):
        1. HTMLMediaElement videoTracks list 等于 HTMLMediaElement.videoTracks 属性返回的 VideoTrackList 对象。
        2. HTMLMediaElement videoTracks list 中移除该 VideoTrack 对象。
          注意

          此操作应触发 VideoTrackList [HTML] 的逻辑,排队任务以 触发事件,事件名为 removetrack,使用 TrackEvent,其 track 属性初始化为该 VideoTrack 对象,目标为 HTMLMediaElement videoTracks list。若移除步骤开始时 selected 属性为 true,还应触发 VideoTrackList [HTML] 的逻辑,排队任务以触发事件名为 change,目标为 HTMLMediaElement videoTracks list

  7. SourceBuffer textTracks list 等于 sourceBuffer.textTracks 返回的 TextTrackList 对象。
  8. 如果 SourceBuffer textTracks list 不为空,执行以下步骤:
    1. SourceBuffer textTracks list 中的每个 TextTrack 对象,执行:
      1. sourceBuffer 属性设为 null。
      2. SourceBuffer textTracks list 中移除该 TextTrack 对象。
        注意

        此操作应触发 TextTrackList [HTML] 的逻辑,排队任务以 触发事件,事件名为 removetrack,使用 TrackEvent,其 track 属性初始化为该 TextTrack 对象,目标为 SourceBuffer textTracks list。若移除步骤开始时 mode 属性为 "showing""hidden",还应触发 TextTrackList [HTML] 的逻辑,排队任务以触发事件名为 change,目标为 SourceBuffer textTracks list

      3. 使用 必要时镜像算法,在 Window 中移除该 TextTrack 对象(或如 MediaSource 对象是在 DedicatedWorkerGlobalScope 构造,则移除其 Window 镜像):
        1. HTMLMediaElement textTracks list 等于 HTMLMediaElement.textTracks 属性返回的 TextTrackList 对象。
        2. HTMLMediaElement textTracks list 中移除该 TextTrack 对象。
          注意

          此操作应触发 TextTrackList [HTML] 的逻辑,排队任务以 触发事件,事件名为 removetrack,使用 TrackEvent,其 track 属性初始化为该 TextTrack 对象,目标为 HTMLMediaElement textTracks list。如果移除步骤开始时 mode 属性为 "showing""hidden",还应触发 TextTrackList [HTML] 的逻辑,排队任务以触发事件名为 change,目标为 HTMLMediaElement textTracks list

  9. 如果 sourceBufferactiveSourceBuffers 中,则将 sourceBufferactiveSourceBuffers 中移除,并排队任务以 触发事件,事件名为 removesourcebuffer,目标为 SourceBufferList(由 activeSourceBuffers 返回)。
  10. sourceBuffersourceBuffers 中移除,并排队任务以 触发事件,事件名为 removesourcebuffer,目标为 SourceBufferList(由 sourceBuffers 返回)。
  11. 销毁 sourceBuffer 的全部资源。

3.9 endOfStream() 方法

标志流结束。

  1. 如果 readyState 属性不是 "open" 状态,则抛出 InvalidStateError 异常并终止这些步骤。
  2. 如果 updating 属性在任意 SourceBuffer(在 sourceBuffers 中)上为 true,则抛出 InvalidStateError 异常并终止这些步骤。
  3. 以参数 error 调用 流结束算法。

3.10 setLiveSeekableRange() 方法

更新 [[live seekable range]],该属性用于 10. HTMLMediaElement 扩展,以修改 HTMLMediaElementseekable 行为。

调用该方法时,用户代理必须执行以下步骤:

  1. 如果 readyState 属性不是 "open",则抛出 InvalidStateError 异常并终止这些步骤。
  2. 如果 start 为负数或大于 end,则抛出 TypeError 异常并终止这些步骤。
  3. [[live seekable range]] 设为包含一个范围(起始为 start,结束为 end)的新的 标准化 TimeRanges 对象

3.11 clearLiveSeekableRange() 方法

更新 [[live seekable range]],该属性用于 10. HTMLMediaElement 扩展,以修改 HTMLMediaElementseekable 行为。

调用该方法时,用户代理必须执行以下步骤:

  1. 如果 readyState 属性不是 "open",则抛出 InvalidStateError 异常并终止这些步骤。
  2. 如果 [[live seekable range]] 包含范围,则将 [[live seekable range]] 设为新的空 TimeRanges 对象。

3.12 isTypeSupported() 方法

检查 MediaSource 能否为指定 MIME 类型创建 SourceBuffer 对象。

注意

若该方法返回 true,仅表示 MediaSource 实现能够为指定 MIME 类型创建 SourceBuffer 对象。 若资源不足,addSourceBuffer() 调用仍应当失败。

注意

此方法返回 true 意味着 HTMLMediaElementcanPlayType() 通常会返回 "maybe" 或 "probably",因为 MediaSource 不应该支持 HTMLMediaElement 明确无法播放的类型。

调用该方法时,用户代理必须执行以下步骤:

  1. 如果 type 为空字符串,则返回 false。
  2. 如果 type 不是有效的 MIME 类型字符串,则返回 false。
  3. 如果 type 包含未被 MediaSource 支持的媒体类型或子类型,则返回 false。
  4. 如果 type 包含未被 MediaSource 支持的编解码器,则返回 false。
  5. 如果 MediaSource 不支持指定的媒体类型、子类型和编解码器的组合,则返回 false。
  6. 返回 true。

3.13 事件概览

事件名称 接口 触发条件
sourceopen Event MediaSourcereadyState 状态从 "closed" 变为 "open" 或从 "ended" 变为 "open" 时触发。
sourceended Event MediaSourcereadyState 状态从 "open" 变为 "ended" 时触发。
sourceclose Event MediaSourcereadyState 状态从 "open" 变为 "closed" 或 "ended" 变为 "closed" 时触发。

3.14 跨上下文通信模型

Window 上的 HTMLMediaElement 被绑定到 DedicatedWorkerGlobalScope 上的 MediaSource 时,每个上下文都有依赖于另一方信息的算法。

注意

HTMLMediaElement 仅暴露于 Window 上下文,而本规范定义的 MediaSource 及相关对象则同时暴露于 WindowDedicatedWorkerGlobalScope 上下文。这允许应用在任一类型上下文中构建 MediaSource 对象,并通过 MediaSource 对象 URLMediaSourceHandle(见 绑定到媒体元素算法)将其绑定到 HTMLMediaElement 对象(Window 上下文)。MediaSource 对象不是 Transferable;它只在创建时的上下文可见。

本节其余部分描述了将 Window 媒体元素绑定到 DedicatedWorkerGlobalScope 的 MediaSource 时用于约束信息延迟的模型。该模型描述了通过消息传递通信,但实现也可以采用更快的方式(如共享内存与锁)。Window 上下文的 MediaSource 绑定则同步获取信息,无需跨上下文通信。

DedicatedWorkerGlobalScope 构造的 MediaSource 拥有 [[port to main]] 内部槽,用于存储在绑定期间建立、解绑期间清空的 MessagePort。Window 上下文的 [[port to main]] 始终为 null。

被本规范扩展并绑定到 DedicatedWorkerGlobalScope 的 HTMLMediaElement 同样有 [[port to worker]] 内部槽(存储 MessagePort),以及 [[channel with worker]] 内部槽(存储 MessageChannel),两者在绑定期间建立、解绑期间清空。只有绑定到 DedicatedWorkerGlobalScope 的 MediaSource[[port to worker]][[channel with worker]] 不为 null。

本规范中的算法若需在 Window 上下文的 HTMLMediaElement 与附加到 DedicatedWorkerGlobalScope 的 MediaSource 间通信,将隐式使用这些内部端口向对应方发送消息,消息的隐式处理程序根据算法描述运行步骤。

3.15 算法

3.15.1 绑定到媒体元素

MediaSource 附加到媒体元素有不同方式,取决于该 MediaSource 对象是在哪构造的:在 Window 中,还是在 DedicatedWorkerGlobalScope 中:

如果 资源获取算法 的调用中,媒体提供者对象是一个 MediaSource 对象、一个 MediaSourceHandle 对象,或其对象为 MediaSource 的 URL 记录,则令模式为本地(local),跳过 资源获取算法 的第一步(否则可能将模式设为远程),并继续执行该 资源获取算法

注意

预期 资源获取算法 的第一步最终会与“当 URL 记录的对象是媒体提供者对象时选择本地模式”的行为一致。其意图是:如果 HTMLMediaElementsrc 属性或选中的子 sourcesrc 属性在最近一次变更时是与某个 MediaSource 对象 URL 匹配的 blob: URL,那么该 MediaSource 对象会在 资源获取算法 的本地模式逻辑中被用作媒体提供者对象和当前媒体资源。这也意味着当 MediaSource 对象被附加时,包含对 preload 属性考量的远程模式逻辑会被跳过。即便未来在 [HTML] 中做出上述改变,在当前媒体资源是 MediaSource 对象时,仍需要在本地模式逻辑开头执行下列步骤。

资源获取算法 “否则(模式为本地)”部分的开头,执行以下附加步骤。

注意

相对于触发媒体元素资源选择算法的动作,这些步骤是异步的。资源获取算法 会在调用资源选择算法的任务被允许继续并达到稳定状态后运行。实现可以延迟“否则”子句中的步骤,直到 MediaSource 对象已准备好使用。

  1. 如果 资源获取算法 的调用中,媒体提供者对象是一个 MediaSource 对象、一个 MediaSourceHandle 对象,或其对象为 MediaSource 的 URL 记录,则:
    如果媒体提供者对象是一个 URL 记录,其对象为构造于 DedicatedWorkerGlobalScopeMediaSource(例如尝试使用来自 DedicatedWorkerGlobalScopeMediaSourceMediaSource 对象 URL 时)
    运行 资源获取算法媒体数据处理步骤列表如果由于网络错误无法获取任何媒体数据,导致用户代理放弃获取资源 的步骤。
    注意
    这会阻止使用 MediaSource 对象 URL 来进行 DedicatedWorker 的 MediaSource 附加。将 MediaSourcehandle 从 DedicatedWorker 传到 Window 上下文,并赋给媒体元素的 srcObject 属性,是附加此类 MediaSource 的唯一方式。
    如果媒体提供者对象是一个 MediaSourceHandle,其 [[Detached]] 内部槽为 true
    运行 资源获取算法媒体数据处理步骤列表如果由于网络错误无法获取任何媒体数据,导致用户代理放弃获取资源 的步骤。
    如果媒体提供者对象是一个 MediaSourceHandle,且其底层 MediaSource[[has ever been attached]] 内部槽为 true
    运行 资源获取算法媒体数据处理步骤列表如果由于网络错误无法获取任何媒体数据,导致用户代理放弃获取资源 的步骤。
    注意
    这可防止通过 MediaSourceHandle 对同一底层 MediaSource 进行多次加载,即便该 MediaSource 是在 Window 中构造并且此前曾通过 MediaSource 对象 URL 加载。这并不妨碍随后对某个 Window 中的 MediaSource 使用 MediaSource 对象 URL 并成功。
    如果 readyState 不是 "closed"
    运行 资源获取算法媒体数据处理步骤列表如果由于网络错误无法获取任何媒体数据,导致用户代理放弃获取资源 的步骤。
    否则
    1. MediaSource[[has ever been attached]] 内部槽设为 true。
    2. 将媒体元素的 delaying-the-load-event-flag 设为 false。
    3. 如果该 MediaSource 构造于 DedicatedWorkerGlobalScope, 则建立 worker 附加通信并打开该 MediaSource
      1. [[channel with worker]] 设为一个新的 MessageChannel
      2. [[port to worker]] 设为 port1, 来自 [[channel with worker]] 的值。
      3. port2 执行 StructuredSerializeWithTransfer, 将其同时作为值与 transferList 的唯一成员,结果记为 serialized port2
      4. 在队列中添加一个任务 到该 MediaSourceDedicatedWorkerGlobalScope,使其将会
        1. 使用 serialized port2DedicatedWorkerGlobalScoperealm 执行 StructuredDeserializeWithTransfer, 并将 [[port to main]] 设为传输的 port2 值的反序列化克隆,来自 [[channel with worker]]
        2. readyState 属性设为 "open"。
        3. 排队一个任务触发事件,事件名为 sourceopen, 目标为 MediaSource
      否则,该 MediaSource 构造于 Window
      1. [[channel with worker]] 设为 null。
      2. [[port to worker]] 设为 null。
      3. [[port to main]] 设为 null。
      4. readyState 属性设为 "open"。
      5. 排队一个任务触发事件,事件名为 sourceopen, 目标为 MediaSource
    4. 继续运行 资源获取算法 的剩余 否则(模式为本地) 步骤,并遵循以下要求:
      1. 资源获取算法媒体数据处理步骤列表 中提到的“下载”“接收的字节数”或“每当当前媒体资源有新数据可用”之类表述,均指通过 appendBuffer() 传入的数据。
      2. 资源获取算法媒体数据处理步骤列表 中与 HTTP 相关的内容不适用,因为当 MediaSource 已附加时,HTMLMediaElement 不会通过 HTTP 获取媒体数据。
注意

已附加的 MediaSource 不会使用 资源获取算法 中的远程模式步骤,因此媒体元素不会触发 “suspend” 事件。尽管本规范的未来版本可能会从附加了 MediaSource 的媒体元素中移除 “progress” 与 “stalled” 事件,但遵循本版本规范的用户代理仍可能触发这两个事件,因为这些对 [HTML] 的引用在本规范实现稳定之后才发生了变更。

3.15.2 从媒体元素解绑

在媒体元素将要切换到 NETWORK_EMPTY排队一个任务触发事件 emptied 到媒体元素的任何情况下,运行以下步骤。这些步骤应当在切换之前立即运行。

  1. 如果 MediaSource 构造于 DedicatedWorkerGlobalScope
    1. 通过向 [[port to worker]] 发送内部 detach 消息,通知 MediaSource
    2. [[port to worker]] 设为 null。
    3. [[channel with worker]] 设为 null。
    4. detach 通知的隐式消息处理程序在 DedicatedWorkerGlobalScope 中的 MediaSource 上运行余下的步骤。
    否则,MediaSource 构造于 Window
    在该 WindowMediaSource 上继续余下的步骤。
  2. [[port to main]] 设为 null。
  3. readyState 属性设为 "closed"。
  4. 如果 thisManagedMediaSource, 则将 streaming 属性设为 false
  5. duration 更新为 NaN。
  6. activeSourceBuffers 中移除所有 SourceBuffer 对象。
  7. 排队一个任务触发事件 removesourcebufferactiveSourceBuffers
  8. sourceBuffers 中移除所有 SourceBuffer 对象。
  9. 排队一个任务触发事件 removesourcebuffersourceBuffers
  10. 排队一个任务触发事件 sourcecloseMediaSource
注意

展望未来,当已附加的(如果有)MediaSource 必须从媒体元素解绑时,预期外部调用并运行此算法。除了或替代媒体元素切换到 NETWORK_EMPTY 的情形外,它可以在 HTMLMediaElement [HTML] 的 load() 操作以及 资源获取算法 失败时被调用。资源获取算法失败是指中止资源获取算法或资源选择算法的那些情况,但“最终步骤”(Final step) [HTML] 不视为触发解绑的失败。

3.15.3 定位

作为 seek 算法 中“等待用户代理确定新播放位置的媒体数据是否可用;若可用,等待其已解码足够数据以播放该位置” 步骤的一部分,运行以下步骤:

  1. 注意

    媒体元素会在 SourceBuffer 对象(位于 activeSourceBuffers 中)中查找包含 媒体分段new playback position。在当前 HTMLMediaElementbuffered 属性值中的任何 TimeRanges 范围内的位置,都已为该位置缓冲了所有必要的媒体分段。

    如果 new playback position 不在 TimeRanges 中(来自 HTMLMediaElementbuffered
    1. 如果 HTMLMediaElementreadyState 属性大于 HAVE_METADATA, 则将其 readyState 设为 HAVE_METADATA
      注意

      按照 HTMLMediaElement ready states [HTML] 的逻辑, HTMLMediaElementreadyState 变化可能触发该元素上的事件。

    2. 媒体元素等待一次 appendBuffer() 调用,使 编码帧处理 算法将 HTMLMediaElementreadyState 设为大于 HAVE_METADATA 的值。
      注意

      Web 应用可以使用 buffered 以及 HTMLMediaElementbuffered 来判定媒体元素恢复播放所需的数据。

    否则
    继续
    注意

    如果 readyState 属性为 "ended", 且 new playback position 位于当前 HTMLMediaElementbuffered 所含的某个 TimeRanges 中,那么即使一个或多个当前选中或启用的轨道缓冲区的最大范围结束时间戳小于 new playback position, 也必须在此继续完成 seek 操作。该情况应仅因 bufferedreadyState 为 "ended" 时的逻辑而出现。

  2. 媒体元素重置所有解码器,并使用相应 初始化分段 的数据对每个解码器进行初始化。
  3. 媒体元素从 活动轨道缓冲区 向解码器馈送 编码帧, 起始于位于 new playback position 之前最近的 随机访问点
  4. 在“等待稳定状态”步骤恢复 seek 算法

3.15.4 SourceBuffer 监控

在播放过程中会定期运行以下步骤,以确保 SourceBuffer 对象(位于 activeSourceBuffers 中)具有 足够数据以确保不间断播放。 对 activeSourceBuffers 的更改也会触发这些步骤,因为它们影响触发状态转换的条件。

具有 足够数据以确保不间断播放 是实现相关的条件,用户代理判定其当前拥有足够数据,可在有意义的一段时间内不发生停顿地播放。该条件被持续评估,以确定何时将媒体元素在 HAVE_ENOUGH_DATA 就绪状态之间进行转换。这些转换指示用户代理何时认为已缓冲足够数据,或分别需要更多数据。

注意

实现可以选择使用已缓冲字节数、已缓冲时间、追加速率或任何其他认为合适的度量来判断何时拥有足够数据。所用度量可以 在播放期间变化,因此 Web 应用应当仅依赖 HTMLMediaElementreadyState 的值来判定是否需要更多数据。

注意

当媒体元素需要更多数据时,用户代理应当及时将其从 HAVE_ENOUGH_DATA 切换到 HAVE_FUTURE_DATA, 以便 Web 应用能够在不造成播放中断的情况下响应。例如,当当前播放位置距离已缓冲数据的末尾还有 500ms 时进行切换, 可为应用留出约 500ms 的时间追加更多数据,以避免播放停顿。

如果 HTMLMediaElementreadyState 属性等于 HAVE_NOTHING
  1. 终止这些步骤。
如果 HTMLMediaElementbuffered 不包含覆盖当前播放位置的 TimeRanges
  1. HTMLMediaElementreadyState 属性设为 HAVE_METADATA
    注意

    按照 HTMLMediaElement ready states [HTML] 的逻辑, HTMLMediaElementreadyState 变化可能触发该元素上的事件。

  2. 终止这些步骤。
如果 HTMLMediaElementbuffered 包含覆盖当前播放位置的 TimeRanges 且具有 足够数据以确保不间断播放
  1. HTMLMediaElementreadyState 属性设为 HAVE_ENOUGH_DATA
    注意

    按照 HTMLMediaElement ready states [HTML] 的逻辑, HTMLMediaElementreadyState 变化可能触发该元素上的事件。

  2. 如果此前因切换到 HAVE_CURRENT_DATA 而暂停播放,则此时可能恢复播放。
  3. 终止这些步骤。
如果 HTMLMediaElementbuffered 包含覆盖当前播放位置并向后延伸一段时间的 TimeRanges, 则运行以下步骤:
  1. HTMLMediaElementreadyState 属性设为 HAVE_FUTURE_DATA
    注意

    按照 HTMLMediaElement ready states [HTML] 的逻辑, HTMLMediaElementreadyState 变化可能触发该元素上的事件。

  2. 如果此前因切换到 HAVE_CURRENT_DATA 而暂停播放,则此时可能恢复播放。
  3. 终止这些步骤。
如果 HTMLMediaElementbuffered 包含的 TimeRanges 正好在当前播放位置结束,且在当前位置之后没有覆盖的范围:
  1. HTMLMediaElementreadyState 属性设为 HAVE_CURRENT_DATA
    注意

    按照 HTMLMediaElement ready states [HTML] 的逻辑, HTMLMediaElementreadyState 变化可能触发该元素上的事件。

  2. 此时播放被暂停,因为媒体元素没有足够数据推进 媒体时间线
  3. 终止这些步骤。

3.15.5 选中/启用轨道状态变更

在播放过程中,如果 selected 视频轨道、 enabled 音频轨道或某个文本轨道的 mode 发生变化,则需要更新 activeSourceBuffers。 当发生一个或多个这样的变化时,需要遵循以下步骤。此外,当 MediaSource 构造于 DedicatedWorkerGlobalScope, 则每当之前由内部 create track mirror 消息的隐式处理程序创建的轨道之 Window 镜像发生变化时,也必须通过向 [[port to worker]] 发送内部 update track state 消息,将该变化应用到对应的 DedicatedWorkerGlobalScope 轨道上,其隐式处理程序执行该变更并运行以下步骤。同样地,发生在 DedicatedWorkerGlobalScope 轨道上的每个变化,也必须通过向 [[port to main]] 发送内部 update track state 消息,将该变化应用到对应的 Window 轨道镜像上,其隐式处理程序对镜像进行变更。

如果选中的视频轨道发生改变,则运行以下步骤:
  1. 如果与先前选中的视频轨道关联的 SourceBuffer 未与任何其他启用的轨道关联,则运行以下步骤:
    1. activeSourceBuffers 中移除该 SourceBuffer
    2. 排队一个任务触发事件 removesourcebufferactiveSourceBuffers
  2. 如果与新选中的视频轨道关联的 SourceBuffer 尚未位于 activeSourceBuffers 中,则运行以下步骤:
    1. 将该 SourceBuffer 添加到 activeSourceBuffers
    2. 排队一个任务触发事件 addsourcebufferactiveSourceBuffers
如果某音频轨道变为禁用,且与该轨道关联的 SourceBuffer 未与任何其他启用或选中的轨道关联,则运行以下步骤:
  1. activeSourceBuffers 中移除与该音频轨道关联的 SourceBuffer
  2. 排队一个任务触发事件 removesourcebufferactiveSourceBuffers
如果某音频轨道变为启用,且与该轨道关联的 SourceBuffer 尚未位于 activeSourceBuffers 中,则运行以下步骤:
  1. 将与该音频轨道关联的 SourceBuffer 添加到 activeSourceBuffers
  2. 排队一个任务触发事件 addsourcebufferactiveSourceBuffers
如果某文本轨道的 mode 变为 "disabled", 且与该轨道关联的 SourceBuffer 未与任何其他启用或选中的轨道关联,则运行以下步骤:
  1. activeSourceBuffers 中移除与该文本轨道关联的 SourceBuffer
  2. 排队一个任务触发事件 removesourcebufferactiveSourceBuffers
如果某文本轨道的 mode 变为 "showing""hidden", 且与该轨道关联的 SourceBuffer 尚未位于 activeSourceBuffers 中,则运行以下步骤:
  1. 将与该文本轨道关联的 SourceBuffer 添加到 activeSourceBuffers
  2. 排队一个任务触发事件 addsourcebufferactiveSourceBuffers

3.15.6 时长变更

duration 需要变更为 new duration 时,按照以下步骤操作。

  1. 如果当前 duration 的值等于 new duration,则直接返回。
  2. 如果 new duration 小于所有 SourceBuffer(在 sourceBuffers 中)缓冲的所有 编码帧的最大 展示时间戳,则抛出 InvalidStateError 异常并终止这些步骤。
    注意

    不允许缩短时长导致当前已缓冲的媒体被截断。如需截断,应先用 remove() 减少缓冲范围,再更新 duration

  3. highest end time 为所有 SourceBuffer(在 sourceBuffers 中)内所有 轨道缓冲区缓冲区范围最大结束时间。
  4. 如果 new duration 小于 highest end time,则
    注意

    该情况可能由于 编码帧移除算法会保留移除区间起始之前的编码帧而出现。

    1. new duration 更新为 highest end time
  5. duration 更新为 new duration
  6. 使用 必要时镜像算法,在 Window 上运行以下步骤以更新媒体元素的时长:
    1. 将媒体元素的 duration 更新为 new duration
    2. 运行 HTMLMediaElement 时长变更算法

3.15.7 流结束

当应用通过 endOfStream() 调用或某算法需要信号解码错误时会调用此算法。 此算法接收一个 error 参数,指示是否需要发出错误信号。

  1. readyState 属性值设置为 "ended"。
  2. 排队一个任务触发事件 sourceendedMediaSource
  3. 如果 error 未设置
    1. 以所有 SourceBuffer(在 sourceBuffers 中)内所有 轨道缓冲区缓冲区范围最大结束时间为 new duration,运行 时长变更算法。
      注意

      这样可使时长正确反映追加的媒体分段的结束。例如,若时长显式设置为10秒,实际只追加了0~5秒的媒体分段后调用了 endOfStream(), 那么时长会被更新为5秒。

    2. 通知媒体元素其已拥有全部媒体数据。
    如果 error 设为 "network"
    使用 必要时镜像算法,在 Window 上运行以下步骤:
    如果 HTMLMediaElementreadyState 属性等于 HAVE_NOTHING
    运行 资源获取算法媒体数据处理步骤列表如果由于网络错误无法获取任何媒体数据,导致用户代理放弃获取资源 的步骤。
    如果 HTMLMediaElementreadyState 属性大于 HAVE_NOTHING
    运行 资源获取算法媒体数据处理步骤列表中 “如果连接在接收到部分媒体数据后中断,导致用户代理放弃获取资源”的步骤。
    如果 error 设为 "decode"
    使用 必要时镜像算法,在 Window 上运行以下步骤:
    如果 HTMLMediaElementreadyState 属性等于 HAVE_NOTHING
    运行 资源获取算法媒体数据处理步骤列表中 “如果媒体数据可被获取但经检查后发现格式不支持,或完全无法渲染”的步骤。
    如果 HTMLMediaElementreadyState 属性大于 HAVE_NOTHING
    运行 媒体数据损坏步骤,参考 资源获取算法媒体数据处理步骤列表

3.15.8 必要时镜像

此算法用于在 Window 上运行步骤,无论 MediaSource 是在同一个 Window 还是在 DedicatedWorkerGlobalScope 上绑定,通常用于更新绑定的 HTMLMediaElement 的状态。此算法接收一个 steps 参数,列出要在 Window 上执行的步骤。

如果 MediaSource 构造于 DedicatedWorkerGlobalScope
[[port to main]] 发送内部 mirror on window 消息,其 Window 上的隐式处理程序将运行 steps。直接将控制权返回给调用者,无需等待该处理程序收到消息。
注意
镜像消息机制的目的是确保:
  1. steps 会作为其自己的任务在 Window 上异步运行,而不是在其他 Window 任务执行过程中间插入;
  2. steps 的执行不会阻塞在 DedicatedWorkerGlobalScope 上该算法的同步运行与返回。
否则:
直接运行 steps

4. MediaSourceHandle 接口

MediaSourceHandle 接口表示 MediaSource 对象的代理, 用于将 DedicatedWorkerGlobalScope 上的 MediaSource 通过 Window 上的 HTMLMediaElement 使用 srcObject 进行绑定,具体见 绑定到媒体元素 算法。

注意

为了能够跨上下文将 MediaSource 绑定到媒体元素,必须使用该独立对象,因为 MediaSource 本身不可传递(transferable),因其是事件目标。

每个 MediaSourceHandle 对象有一个 [[has ever been assigned as srcobject]] 内部槽,存储一个 boolean。该槽在 MediaSourceHandle 对象创建时初始化为 false,在 扩展的 HTMLMediaElementsrcObject setter(见 10. HTMLMediaElement 扩展)中设置为 true, 若为 true,则会阻止该 MediaSourceHandle 的成功传递,具体见 4.1 传递

MediaSourceHandle 对象是 Transferable, 每个对象有一个 [[Detached]] 内部槽,用于保证该实例在被传递后不可再次传递。

WebIDL[Transferable, Exposed=(Window,DedicatedWorker)]
interface MediaSourceHandle {};

4.1 传递

MediaSourceHandle传递步骤传递接收步骤 要求实现维护一个隐式内部槽,引用底层 MediaSource, 以支持通过 srcObject 进行 绑定到媒体元素,以及随之建立绑定的 跨上下文通信模型

注意

实现者应注意,Transferable 所暗示的“move”语义并不总是真实的。例如,postMessage 的扩展或内部广播实现可能导致被传递的 MediaSourceHandle 有多个接收方。 因此,实现建议:只有当底层 MediaSource 的任一句柄在媒体元素资源选择算法的异步部分被使用时,才确定哪个潜在克隆仍然有效。 这类似于通过 MediaSource 对象 URL 进行绑定的现有行为, 该 URL 可被轻易克隆,但所有克隆最多只允许一次绑定启动。

实现必须保证底层 MediaSource 对象只能通过 srcObject 进行一次绑定(加载),不论 MediaSourceHandle 是否因 Transferable 的不同实现而被克隆。

注意

参考 绑定到媒体元素,了解媒体元素资源选择算法的异步部分如何强制执行此要求。

MediaSourceHandle 仅暴露在 WindowDedicatedWorkerGlobalScope 上下文,且不能在不同 agent cluster [ECMASCRIPT] 间成功传递。MediaSourceHandle 对象只能在同一个 agent cluster 内传递成功。

注意

例如,将 MediaSourceHandle 对象从 WindowDedicatedWorkerGlobalScope 传递到 SharedWorker 或 ServiceWorker 都不会成功。开发者应了解该限制与 MediaSource 对象 URL 的不同,后者是可通过多种方式传递的 DOMString。即便如此,通过 MediaSource 对象 URL 进行 绑定到媒体元素 也只能用于构造于 Window 上下文的 MediaSource。另见 Web 应用 API 的 agentagent cluster 形式化,[HTML] 中相关概念如 dedicated worker agents

传递步骤 对于 MediaSourceHandle 对象 必须包括以下步骤:

  1. 如果 MediaSourceHandle[[has ever been assigned as srcobject]] 内部槽为 true,则 传递步骤必须抛出 DataCloneError 异常并失败。

5. SourceBuffer 接口

WebIDLenum AppendMode {
  "segments",
  "sequence",
};
segments
媒体分段中的时间戳决定了编码帧 在演示中的位置。媒体分段可以以任意顺序追加。
sequence
媒体分段将被视为时间上相邻,和分段中的时间戳无关。新媒体分段中的编码帧会紧接在前一个媒体分段的编码帧之后。若需要使新媒体分段和前一个分段相邻,会更新 timestampOffset 属性。设置 "sequence" 模式下的 timestampOffset 属性,可以让媒体分段放到时间线上的指定位置,而无需了解分段内时间戳。
WebIDL[Exposed=(Window,DedicatedWorker)]
interface SourceBuffer : EventTarget {
  attribute AppendMode mode;
  readonly  attribute boolean updating;
  readonly  attribute TimeRanges buffered;
  attribute double timestampOffset;
  readonly  attribute AudioTrackList audioTracks;
  readonly  attribute VideoTrackList videoTracks;
  readonly  attribute TextTrackList textTracks;
  attribute double appendWindowStart;
  attribute unrestricted double appendWindowEnd;

  attribute EventHandler onupdatestart;
  attribute EventHandler onupdate;
  attribute EventHandler onupdateend;
  attribute EventHandler onerror;
  attribute EventHandler onabort;

  undefined appendBuffer(BufferSource data);
  undefined abort();
  undefined changeType(DOMString type);
  undefined remove(double start, unrestricted double end);
};
Issue 280: MSE-in-Workers: {Audio,Video,Text}Track{,List} IDL in HTML 需要补充 DedicatedWorker 的 Exposed mse-in-workers
[HTML] AudioTrackList, VideoTrackListTextTrackList 需要支持 Window+DedicatedWorker 暴露。

5.1 属性

mode 类型 AppendMode

控制如何处理一系列 媒体分段。该属性在对象创建后由 addSourceBuffer() 初始化, 可通过 changeType() 或直接设置此属性进行更新。

读取时,返回初始值或最后一次成功设置的值。

设置时,执行如下步骤:

  1. 如果该对象已从 sourceBuffers(父媒体源)中移除,则抛出 InvalidStateError 异常并终止这些步骤。
  2. 如果 updating 属性为 true,则抛出 InvalidStateError 异常并终止这些步骤。
  3. new mode 为分配的新值。
  4. 如果 [[generate timestamps flag]] 为 true 且 new mode 为 "segments",则抛出 TypeError 异常并终止这些步骤。
  5. 如果父媒体源的 readyState 属性为 "ended",则执行:

    1. 将父媒体源的 readyState 属性设为 "open"
    2. 排队一个任务触发事件 sourceopen 到父媒体源。
  6. 如果 [[append state]]PARSING_MEDIA_SEGMENT,则抛出 InvalidStateError 并终止这些步骤。
  7. 如果 new mode 为 "sequence",则将 [[group start timestamp]] 设为 [[group end timestamp]]
  8. 将该属性更新为 new mode
updating 类型 boolean, 只读

指示 appendBuffer()remove() 操作的异步处理是否仍在进行。该属性在对象创建时初始为 false。

buffered 类型 TimeRanges, 只读

指示本 SourceBuffer 已缓冲了哪些 TimeRanges。创建时初始值为空 TimeRanges 对象。

读取该属性时,需执行以下步骤必须

  1. 如果该对象已从 sourceBuffers(父媒体源)中移除,则抛出 InvalidStateError 异常并终止这些步骤。
  2. highest end time 为本对象管理的所有 轨道缓冲区缓冲区范围的最大结束时间。
  3. intersection ranges 为一个 TimeRanges 对象,内容为从 0 到 highest end time 的单个区间。
  4. 对本对象管理的每个音频和视频 轨道缓冲区,运行以下步骤:
    注意

    文本 轨道缓冲区参与计算 highest end time, 但本处缓冲区范围计算不包含文本轨道。文本轨道不一定连续,也不应因其不连续导致其他媒体轨道在同一区间连续时播放停顿。

    1. track ranges 为当前 轨道缓冲区缓冲区范围
    2. 如果 readyState 为 "ended",则将 track ranges 最后一个区间的结束时间设为 highest end time
    3. new intersection rangesintersection rangestrack ranges 的交集。
    4. new intersection ranges 替换 intersection ranges 中的区间。
  5. 如果 intersection ranges 不包含与当前属性值完全一致的区间信息,则更新属性值为 intersection ranges
  6. 返回当前属性值。
timestampOffset 类型 double

控制追加到本 SourceBuffer 的后续 媒体分段 内时间戳应用的偏移量。初始值为 0,表示不应用偏移。

读取时,返回初始值或最后一次成功设置的值。

设置时,执行如下步骤:

  1. new timestamp offset 为分配的新值。
  2. 如果该对象已从 sourceBuffers(父媒体源)中移除,则抛出 InvalidStateError 异常并终止这些步骤。
  3. 如果 updating 属性为 true,则抛出 InvalidStateError 异常并终止这些步骤。
  4. 如果父媒体源的 readyState 属性为 "ended",则执行:

    1. 将父媒体源的 readyState 属性设为 "open"
    2. 排队一个任务触发事件 sourceopen 到父媒体源。
  5. 如果 [[append state]]PARSING_MEDIA_SEGMENT,则抛出 InvalidStateError 并终止这些步骤。
  6. 如果 mode 属性为 "sequence",则将 [[group start timestamp]] 设为 new timestamp offset
  7. 将该属性更新为 new timestamp offset
audioTracks 类型 AudioTrackList, 只读
由本对象创建的 AudioTrack 对象列表。
videoTracks 类型 VideoTrackList, 只读
由本对象创建的 VideoTrack 对象列表。
textTracks 类型 TextTrackList, 只读
由本对象创建的 TextTrack 对象列表。
appendWindowStart 类型 double

展示时间戳,用于表示 追加窗口 的起始。该属性初始设为 展示起始时间

获取时,返回初始值或最后一次成功设置的值。

设置时,执行如下步骤:

  1. 如果该对象已从 sourceBuffers(父媒体源)中移除,则抛出 InvalidStateError 异常并终止这些步骤。
  2. 如果 updating 属性为 true,则抛出 InvalidStateError 异常并终止这些步骤。
  3. 如果新值小于 0 或大于等于 appendWindowEnd,则抛出 TypeError 异常并终止这些步骤。
  4. 将该属性更新为新值。
appendWindowEnd 类型 unrestricted double

展示时间戳,表示 追加窗口 的结束。该属性初始设为正无穷大。

获取时,返回初始值或最后一次成功设置的值。

设置时,执行如下步骤:

  1. 如果该对象已从 sourceBuffers(父媒体源)中移除,则抛出 InvalidStateError 异常并终止这些步骤。
  2. 如果 updating 属性为 true,则抛出 InvalidStateError 异常并终止这些步骤。
  3. 如果新值为 NaN,则抛出 TypeError 并终止这些步骤。
  4. 如果新值小于等于 appendWindowStart,则抛出 TypeError 异常并终止这些步骤。
  5. 将该属性更新为新值。
onupdatestart 类型 EventHandler

updatestart 事件的事件处理器。

onupdate 类型 EventHandler

update 事件的事件处理器。

onupdateend 类型 EventHandler

updateend 事件的事件处理器。

onerror 类型 EventHandler

error 事件的事件处理器。

onabort 类型 EventHandler

abort 事件的事件处理器。

5.2 方法

appendBuffer

BufferSource[WEBIDL]中的分段数据追加到 SourceBuffer

调用此方法时,用户代理必须运行以下步骤:

  1. 运行 准备追加算法。
  2. data 添加到 [[input buffer]] 的末尾。
  3. updating 属性设为 true。
  4. 排队一个任务触发事件 updatestart 到本 SourceBuffer 对象。
  5. 异步运行 缓冲区追加算法。
abort

中止当前分段并重置分段解析器。

调用此方法时,用户代理必须运行以下步骤:

  1. 如果该对象已从 sourceBuffers(父媒体源)中移除,则抛出 InvalidStateError 异常并终止这些步骤。
  2. 如果父媒体源的 readyState 属性不是 "open",则抛出 InvalidStateError 异常并终止这些步骤。
  3. 如果 区间移除算法正在运行,则抛出 InvalidStateError 异常并终止这些步骤。
  4. 如果 updating 属性为 true,则运行以下步骤:
    1. 若正在运行 缓冲区追加算法,则中止该算法。
    2. updating 属性设为 false。
    3. 排队一个任务触发事件 abort 到本 SourceBuffer 对象。
    4. 排队一个任务触发事件 updateend 到本 SourceBuffer 对象。
  5. 运行 重置解析器状态算法。
  6. appendWindowStart 设为 展示开始时间
  7. appendWindowEnd 设为正无穷。
changeType

更改该对象关联的 MIME 类型。后续的 appendBuffer() 调用会要求新追加的字节与新类型一致。

调用此方法时,用户代理必须运行以下步骤:

  1. 如果 type 为空字符串,则抛出 TypeError 异常并终止这些步骤。
  2. 如果该对象已从 sourceBuffers(父媒体源)中移除,则抛出 InvalidStateError 异常并终止这些步骤。
  3. 如果 updating 属性为 true,则抛出 InvalidStateError 异常并终止这些步骤。
  4. 如果 type 包含不支持的 MIME 类型,或与 SourceBuffer(在父媒体源的 sourceBuffers 属性中)当前或先前指定的类型不兼容,则抛出 NotSupportedError 异常并终止这些步骤。
  5. 如果父媒体源的 readyState 属性为 "ended",则执行:

    1. 将父媒体源的 readyState 属性设为 "open"。
    2. 排队一个任务触发事件 sourceopen 到父媒体源。
  6. 运行 重置解析器状态算法。
  7. 根据字节流格式注册表 [MSE-REGISTRY] 中 type 对应条目的 "Generate Timestamps Flag" 列,为本 SourceBuffer 对象更新 [[generate timestamps flag]] 的值。
  8. 如果 [[generate timestamps flag]] 为 true:
    将本 mode 属性设为 "sequence",并运行属性被设置时的相关步骤。
    否则:
    保持本 mode 属性原值,不运行相关设置步骤。
  9. 将本 SourceBuffer 对象上的 [[pending initialization segment for changeType flag]] 设为 true。
remove

移除指定时间范围内的媒体。移除区间的 start(秒),从 展示开始时间起算,end(秒)同样从 展示开始时间起算。

调用此方法时,用户代理必须运行以下步骤:

  1. 如果该对象已从 sourceBuffers(父媒体源)中移除,则抛出 InvalidStateError 异常并终止这些步骤。
  2. 如果 updating 属性为 true,则抛出 InvalidStateError 异常并终止这些步骤。
  3. 如果 duration 等于 NaN,则抛出 TypeError 异常并终止这些步骤。
  4. 如果 start 为负数或大于 duration,则抛出 TypeError 异常并终止这些步骤。
  5. 如果 end 小于等于 startend 为 NaN,则抛出 TypeError 异常并终止这些步骤。
  6. 如果父媒体源的 readyState 属性为 "ended",则执行:

    1. 将父媒体源的 readyState 属性设为 "open"
    2. 排队一个任务触发事件 sourceopen 到父媒体源。
  7. startend 作为移除区间的起止,运行 区间移除算法。

5.3 轨道缓冲区

轨道缓冲区 用于存储单个轨道的 轨道描述编码帧。轨道缓冲区会在 初始化分段媒体分段追加到 SourceBuffer 时更新。

每个 轨道缓冲区有一个 上次解码时间戳 变量,用于存储当前 编码帧组中最后一个追加的 编码帧的解码时间戳。该变量初始为未设置,表示还没有追加任何编码帧。

每个 轨道缓冲区有一个 最后帧时长 变量,用于存储当前 编码帧组中最后一个追加的 编码帧时长。该变量初始为未设置,表示还没有追加任何编码帧。

每个 轨道缓冲区有一个 最大结束时间戳 变量,用于存储当前 编码帧组中追加到该轨道缓冲区的所有 编码帧的最大 编码帧结束时间戳。初始为未设置,表示还没有追加任何编码帧。

每个 轨道缓冲区有一个 需要随机访问点标志 变量,用于标记轨道缓冲区是否在等待一个 随机访问点 编码帧。该变量初始为 true,表示需要 随机访问点 编码帧后才能向该 轨道缓冲区添加内容。

每个 轨道缓冲区有一个 轨道缓冲区范围 变量,表示当前存储在该轨道缓冲区中的 编码帧所占用的展示时间区间。

注意

对于轨道缓冲区范围,这些展示时间区间基于 展示时间戳、帧时长,以及多轨道缓冲区复用的 SourceBuffer 中各编码帧组可能的起始时间。

从规范角度看,这些信息视为存储在 标准化 TimeRanges 对象中。交集 轨道缓冲区范围用于报告 HTMLMediaElementbuffered, 并必须支持在每个 HTMLMediaElementbuffered 区间内不间断播放。

注意

这些编码帧组起始时间与 编码帧处理算法中提到的略有不同,实际为所有轨道缓冲区在不连续后最早的 展示时间戳。不连续可在 编码帧处理算法内出现,也可由 编码帧移除算法导致,无论 mode 为何。轨道缓冲区范围判定不连续的阈值由实现决定。例如,为减少意外播放停顿,实现可以通过合并小于本轨道缓冲区已缓冲最大帧时长两倍的相邻区间,以近似 编码帧处理算法的不连续检测逻辑。实现还可以在复用的 SourceBuffer 中跨轨道缓冲区使用编码帧组起始时间作为区间起点,以进一步减少意外播放停顿。

5.4 事件概要

事件名 接口 何时派发...
updatestart Event SourceBufferupdating 属性从 false 变为 true 时。
update Event SourceBuffer 的追加或移除操作成功完成。 SourceBufferupdating 属性从 true 变为 false。
updateend Event SourceBuffer 的追加或移除操作结束。
error Event 在向 SourceBuffer 追加数据时发生错误。 updating 属性从 true 变为 false。
abort Event SourceBuffer 的追加操作被 abort() 调用中止。 updating 属性从 true 变为 false。

5.5 算法

5.5.1 分段解析循环

每个 SourceBuffer 对象有一个 [[append state]] 内部槽,用于记录高层分段解析状态。初始值为 WAITING_FOR_SEGMENT,追加数据时可转为以下状态。

追加状态名称 描述
WAITING_FOR_SEGMENT 等待追加 初始化分段媒体分段 的开始。
PARSING_INIT_SEGMENT 当前正在解析 初始化分段
PARSING_MEDIA_SEGMENT 当前正在解析 媒体分段

每个 SourceBuffer 对象有一个 [[input buffer]] 内部槽,是个字节缓冲区,保存 appendBuffer() 调用之间未解析的字节。创建对象时该缓冲区为空。

每个 SourceBuffer 对象有一个 [[buffer full flag]] 内部槽,用于记录 appendBuffer() 是否允许接受更多字节。对象创建时设为 false,追加和移除数据时会更新。

每个 SourceBuffer 对象有一个 [[group start timestamp]] 内部槽,用于记录 "sequence" 模式下新 编码帧组的起始时间戳。对象创建时未设置,mode 属性为 "sequence" 且 timestampOffset 属性被设置,或运行 编码帧处理算法时会更新。

每个 SourceBuffer 对象有一个 [[group end timestamp]] 内部槽,存储当前 编码帧组所有 编码帧的最大 编码帧结束时间戳。创建对象时设为 0,运行 编码帧处理算法时更新。

注意

[[group end timestamp]] 记录的是 编码帧结束时间戳 在一个 SourceBuffer 的所有 轨道缓冲区中的最大值。因此,在追加多轨道复用且时间戳未对齐分段时,需谨慎设置 mode 属性。

每个 SourceBuffer 对象有一个 [[generate timestamps flag]] 内部槽,是个布尔值,用于记录传给 编码帧 处理算法的编码帧是否需要生成时间戳。该标志由 addSourceBuffer() 创建对象时设置,changeType() 可更新。

当调用分段解析循环算法时,运行以下步骤:

  1. 循环顶部: 如果 [[input buffer]] 为空,则跳转到下方 需要更多数据 步骤。
  2. 如果 [[input buffer]] 包含违反 SourceBuffer 字节流格式规范 的字节,则运行 追加错误 算法并终止本算法。
  3. [[input buffer]] 的起始位置移除字节流格式规范要求必须忽略的字节。
  4. 如果 [[append state]]WAITING_FOR_SEGMENT,则运行以下步骤:

    1. 如果 [[input buffer]] 的起始位置表示初始化分段开始, 则将 [[append state]] 设为 PARSING_INIT_SEGMENT
    2. 如果 [[input buffer]] 的起始位置表示媒体分段开始, 则将 [[append state]] 设为 PARSING_MEDIA_SEGMENT
    3. 跳转到上方循环顶部步骤。
  5. 如果 [[append state]]PARSING_INIT_SEGMENT,则运行以下步骤:

    1. 如果 [[input buffer]] 尚未包含完整的 初始化分段,则跳转到下方需要更多数据步骤。
    2. 运行 初始化分段接收算法。
    3. [[input buffer]] 起始移除 初始化分段字节。
    4. [[append state]] 设为 WAITING_FOR_SEGMENT
    5. 跳转到上方循环顶部步骤。
  6. 如果 [[append state]]PARSING_MEDIA_SEGMENT,则运行以下步骤:

    1. 如果 [[first initialization segment received flag]] 为 false 或 [[pending initialization segment for changeType flag]] 为 true,则运行 追加错误算法并终止本算法。
    2. 如果 [[input buffer]] 包含一个或多个完整 编码帧,则运行 编码帧处理算法。
      注意

      编码帧处理算法运行频率由实现决定。其可以在输入缓冲区包含完整媒体分段时调用,也可以在完整编码帧加入输入缓冲区时多次调用。

    3. 如果本 SourceBuffer 已满且无法接受更多媒体数据,则将 [[buffer full flag]] 设为 true。
    4. 如果 [[input buffer]] 尚未包含完整的 媒体分段,则跳转到下方需要更多数据步骤。
    5. [[input buffer]] 起始移除 媒体分段字节。
    6. [[append state]] 设为 WAITING_FOR_SEGMENT
    7. 跳转到上方循环顶部步骤。
  7. 需要更多数据:将控制权返回给调用算法。

5.5.2 重置解析器状态

当需要重置解析器状态时,执行以下步骤:

  1. 如果 [[append state]] 等于 PARSING_MEDIA_SEGMENT 并且 [[input buffer]] 包含一些完整的 编码帧,则运行 编码帧处理算法,直到所有这些完整的 编码帧 都被处理完毕。
  2. 取消所有 轨道缓冲区上的 最后解码时间戳
  3. 取消所有 轨道缓冲区上的 最后帧持续时间
  4. 取消所有 轨道缓冲区上的 最高结束时间戳
  5. 将所有 轨道缓冲区上的 需要随机访问点标志 设置为 true。
  6. 如果 mode 属性等于 "sequence",则将 [[group start timestamp]] 设置为 [[group end timestamp]]
  7. 移除 [[input buffer]] 中的所有字节。
  8. [[append state]] 设置为 WAITING_FOR_SEGMENT

5.5.3 追加错误

当追加过程中发生错误时调用此算法。

  1. 运行 重置解析器状态 算法。
  2. updating 属性设为 false。
  3. 排队一个任务触发一个事件, 事件名为 error,在此 SourceBuffer 对象上。
  4. 排队一个任务触发一个事件, 事件名为 updateend,在此 SourceBuffer 对象上。
  5. 运行 end of stream 算法,并将 error 参数设为 "decode"。

5.5.4 准备追加

当追加操作开始时,运行以下步骤以验证并准备 SourceBuffer

  1. 如果 SourceBuffer 已从 sourceBuffers 属性移除(属于 父媒体源),则抛出 InvalidStateError 异常并中止这些步骤。
  2. 如果 updating 属性为 true, 则抛出 InvalidStateError 异常并中止这些步骤。
  3. recent element error 按如下方式确定:
    如果 MediaSourceWindow 中构造
    如果 HTMLMediaElementerror 属性不为 null,则 recent element error 为 true。若属性为 null,则 recent element error 为 false。
    否则
    recent element error 的值为 Window 情况下的步骤结果,但需在 Window 上 的 HTMLMediaElementerror 属性发生变化时,并通过 [[port to worker]] 隐式消息通知。如果尚未收到该消息,则 recent element error 为 false。
  4. 如果 recent element error 为 true,则抛出 InvalidStateError 异常并中止这些步骤。
  5. 如果 readyState 属性的 父媒体源 处于 "ended" 状态,则运行以下步骤:

    1. readyState 属性的 父媒体源 设置为 "open"
    2. 排队一个任务触发一个事件,事件名为 sourceopen,触发于 父媒体源
  6. 运行 编码帧清除算法。
  7. 如果 [[buffer full flag]] 等于 true, 则抛出 QuotaExceededError 异常并中止这些步骤。

    注意

    这是实现无法清除足够数据以容纳追加内容或追加内容过大的信号。Web 应用应当使用 remove() 显式释放空间和/或减少追加数据量。

5.5.5 缓冲区追加

当调用 appendBuffer() 时,执行以下步骤处理追加的数据。

  1. 运行 分段解析循环 算法。
  2. 如果上一步中的 分段解析循环 算法被中止,则中止本算法。
  3. updating 属性设置为 false。
  4. 排队一个任务触发名为 update 的事件,在此 SourceBuffer 对象上。
  5. 排队一个任务触发名为 updateend 的事件,在此 SourceBuffer 对象上。

5.5.6 范围移除

当调用者需要发起一个 JavaScript 可见的范围移除操作(该操作会阻塞其它 SourceBuffer 更新)时,按照以下步骤进行:

  1. start 等于移除范围的起始 展示时间戳,单位为从 展示起始时间 开始计的秒数。
  2. end 等于移除范围的结束 展示时间戳,单位为从 展示起始时间 开始计的秒数。
  3. updating 属性设为 true。
  4. 排队一个任务触发名为 updatestart 的事件,在此 SourceBuffer 对象上。
  5. 将控制权返回给调用方,并异步执行剩下的步骤。
  6. 运行 编码帧移除算法,startend 分别作为移除范围的起止。
  7. updating 属性设为 false。
  8. 排队一个任务触发名为 update 的事件,在此 SourceBuffer 对象上。
  9. 排队一个任务触发名为 updateend 的事件,在此 SourceBuffer 对象上。

5.5.7 接收到初始化分段

分段解析循环 成功解析出一个完整的 初始化分段 时,运行以下步骤:

每个 SourceBuffer 对象都有一个用于跟踪是否已经由该算法追加并接收到首个 初始化分段 的内部插槽 [[first initialization segment received flag]]。该标志在创建 SourceBuffer 时被设为 false,并由下述算法进行更新。

每个 SourceBuffer 对象都有一个用于跟踪自最近一次 changeType() 起是否需要 初始化分段 的内部插槽 [[pending initialization segment for changeType flag]]。该标志在创建 SourceBuffer 时设为 false,由 changeType() 设为 true,并由下述算法重置为 false。

  1. 如果当前 duration 属性值为 NaN,则进行更新:
    如果初始化分段包含一个时长:
    运行 时长变更 算法,并将 new duration 设为初始化分段中的该时长。
    否则:
    运行 时长变更 算法,并将 new duration 设为正无穷大。
  2. 如果该 初始化分段 不包含音频、视频或文本轨,则运行 追加错误 算法并中止这些步骤。
  3. 如果 [[first initialization segment received flag]] 为 true, 则运行以下步骤:
    1. 验证下列属性。如有任一检查失败,则运行 追加错误 算法并中止这些步骤。
      • 音频、视频与文本轨的数量与首个 初始化分段 中的一致。
      • 如果单一类型存在多条轨(例如 2 条音轨),则这些轨的 Track ID 与首个 初始化分段 中的一致。
      • 每条轨的编解码器均受用户代理支持。
        注意

        如果某些编解码器未在最近一次成功的 changeType() 调用中传入的 type 参数(a),或者(b)若此对象尚未进行过成功的 changeType(), 则为创建该 SourceBuffer 对象的 addSourceBuffer() 中被指定, 用户代理 MAY 将本应受支持的编解码器视为此处“不受支持”。例如,如果最近一次成功的 changeType() 使用了 'video/webm''video/webm; codecs="vp8"',而初始化分段中出现了包含 vp9 的视频轨,则即便上述另外两项检查通过,用户代理也 MAY 利用此步骤触发解码错误。鼓励实现仅在该编解码器确实不受支持或另外两项检查失败时触发错误。鼓励 Web 作者使用带有精确编解码器参数的 changeType()addSourceBuffer()isTypeSupported() 更主动地检测用户代理支持情况。当 SourceBuffer 对象的字节流格式发生变化时,必须调用 changeType()

    2. 将该 初始化分段 中相应的 轨道描述 添加到各个 轨道缓冲区
    3. 将所有轨道缓冲区上的 need random access point flag 设为 true。
  4. active track flag 等于 false。
  5. 如果 [[first initialization segment received flag]] 为 false, 则运行以下步骤:

    1. 如果该 初始化分段 包含用户代理不支持的编解码器的轨,则运行 追加错误 算法并中止这些步骤。
      注意

      如果某些编解码器未在最近一次成功的 changeType() (a)调用传入的 type 参数中指定,或者(b)若此对象尚未发生成功的 changeType(),则为创建该对象的 addSourceBuffer() 中指定, 用户代理 MAY 将本应受支持的编解码器视为“不受支持”,从而触发解码错误。例如, MediaSource.isTypeSupported('video/webm;codecs="vp8,vorbis"') 可能返回 true,但如果 addSourceBuffer() 被调用时使用了 'video/webm;codecs="vp8"',而初始化分段中出现了 Vorbis 轨,则用户代理 MAY 使用此步骤触发解码错误。 鼓励实现仅在编解码器确实不受支持时触发错误。鼓励 Web 作者使用带有精确编解码器参数的 changeType()addSourceBuffer()isTypeSupported() 更主动地检测用户代理支持情况。当 changeType()SourceBuffer 对象更改字节流格式时是必需的。

    2. 对于该 初始化分段 中的每条音轨,运行以下步骤:

      1. audio byte stream track ID 为当前待处理音轨的 Track ID
      2. audio language 为该轨在 初始化分段 中指定的 BCP 47 语言标记;若无语言信息,则为一个空字符串。
      3. 如果 audio language 等于 BCP 47 的 'und' 值,则将 audio language 设为空字符串。
      4. audio label 为该轨在 初始化分段 中指定的标签;若无标签信息,则为空字符串。
      5. audio kinds 为该轨在 初始化分段 中指定的一组 kind 字符串;若未提供 kind 信息,则为仅包含一个空字符串的序列。
      6. 对于 audio kinds 中的每个值,运行以下步骤:
        1. current audio kind 等于本次循环中来自 audio kinds 的该值。
        2. new audio track 为一个新的 AudioTrack 对象。
        3. 生成一个唯一 ID,并将其赋值给 new audio trackid 属性。
        4. audio language 赋值给 new audio tracklanguage 属性。
        5. audio label 赋值给 new audio tracklabel 属性。
        6. current audio kind 赋值给 new audio trackkind 属性。
        7. 如果该 SourceBuffer 对象的 audioTrackslength 等于 0,则运行以下步骤:

          1. new audio trackenabled 属性设为 true。
          2. active track flag 设为 true。
        8. new audio track 添加到该 audioTracks 属性中, 位于此 SourceBuffer 对象上。
          注意

          这应当触发 AudioTrackList [HTML] 逻辑,排队一个任务触发一个事件,事件名为 addtrack, 并使用 TrackEvent, 其 track 属性初始化为 new audio track,触发于由该 audioTracks 属性引用的 AudioTrackList 对象上,此对象位于该 SourceBuffer 上。

        9. 如果 父媒体源 是在 DedicatedWorkerGlobalScope 中构造的:
          [[port to main]] 发送一条内部 create track mirror 消息, 其在 Window 中的隐式处理器运行以下步骤:
          1. mirrored audio track 为一个新的 AudioTrack 对象。
          2. 将与 new audio track 确定的相同属性值赋予 mirrored audio track
          3. mirrored audio track 添加到 HTMLMediaElement 的 audioTracks 属性中。
          否则:
          new audio track 添加到 HTMLMediaElement 的 audioTracks 属性中。
          注意

          这应当触发 AudioTrackList [HTML] 逻辑,排队一个任务触发一个事件,事件名为 addtrack, 并使用 TrackEvent, 其 track 属性初始化为 mirrored audio tracknew audio track,触发于由 HTMLMediaElement 的 audioTracks 属性引用的 AudioTrackList 对象上。

      7. 创建一个新的 轨道缓冲区,用于为该轨存储 编码帧
      8. 将该轨的 轨道描述 添加到该 轨道缓冲区 中。
    3. 对于该 初始化分段 中的每条视频轨,运行以下步骤:

      1. video byte stream track ID 为当前待处理视频轨的 Track ID
      2. video language 为该轨在 初始化分段 中指定的 BCP 47 语言标记;若无语言信息,则为空字符串。
      3. 如果 video language 等于 BCP 47 的 'und' 值,则将 video language 设为空字符串。
      4. video label 为该轨在 初始化分段 中指定的标签;若无标签信息,则为空字符串。
      5. video kinds 为该轨在 初始化分段 中指定的一组 kind 字符串;若未提供 kind 信息,则为仅包含一个空字符串的序列。
      6. 对于 video kinds 中的每个值,运行以下步骤:
        1. current video kind 等于本次循环中来自 video kinds 的该值。
        2. new video track 为一个新的 VideoTrack 对象。
        3. 生成一个唯一 ID,并将其赋值给 new video trackid 属性。
        4. video language 赋值给 new video tracklanguage 属性。
        5. video label 赋值给 new video tracklabel 属性。
        6. current video kind 赋值给 new video trackkind 属性。
        7. 如果该 SourceBuffer 对象的 videoTrackslength 等于 0,则运行以下步骤:

          1. new video trackselected 属性设为 true。
          2. active track flag 设为 true。
        8. new video track 添加到该 videoTracks 属性中, 位于此 SourceBuffer 对象上。
          注意

          这应当触发 VideoTrackList [HTML] 逻辑,排队一个任务触发一个事件,事件名为 addtrack, 并使用 TrackEvent, 其 track 属性初始化为 new video track,触发于由该 videoTracks 属性引用的 VideoTrackList 对象上,此对象位于该 SourceBuffer 上。

        9. 如果 父媒体源 是在 DedicatedWorkerGlobalScope 中构造的:
          [[port to main]] 发送一条内部 create track mirror 消息, 其在 Window 中的隐式处理器运行以下步骤:
          1. mirrored video track 为一个新的 VideoTrack 对象。
          2. 将与 new video track 确定的相同属性值赋予 mirrored video track
          3. mirrored video track 添加到 HTMLMediaElement 的 videoTracks 属性中。
          否则:
          new video track 添加到 HTMLMediaElement 的 videoTracks 属性中。
          注意

          这应当触发 VideoTrackList [HTML] 逻辑,排队一个任务触发一个事件,事件名为 addtrack, 并使用 TrackEvent, 其 track 属性初始化为 mirrored video tracknew video track,触发于由 HTMLMediaElement 的 videoTracks 属性引用的 VideoTrackList 对象上。

      7. 创建一个新的 轨道缓冲区,用于为该轨存储 编码帧
      8. 将该轨的 轨道描述 添加到该 轨道缓冲区 中。
    4. 对于该 初始化分段 中的每条文本轨,运行以下步骤:

      1. text byte stream track ID 为当前待处理文本轨的 Track ID
      2. text language 为该轨在 初始化分段 中指定的 BCP 47 语言标记;若无语言信息,则为空字符串。
      3. 如果 text language 等于 BCP 47 的 'und' 值,则将 text language 设为空字符串。
      4. text label 为该轨在 初始化分段 中指定的标签;若无标签信息,则为空字符串。
      5. text kinds 为该轨在 初始化分段 中指定的一组 kind 字符串;若未提供 kind 信息,则为仅包含一个空字符串的序列。
      6. 对于 text kinds 中的每个值,运行以下步骤:
        1. current text kind 等于本次循环中来自 text kinds 的该值。
        2. new text track 为一个新的 TextTrack 对象。
        3. 生成一个唯一 ID,并将其赋值给 new text trackid 属性。
        4. text language 赋值给 new text tracklanguage 属性。
        5. text label 赋值给 new text tracklabel 属性。
        6. current text kind 赋值给 new text trackkind 属性。
        7. 使用来自该 初始化分段 的适当信息填充 new text track 上的其余属性。
        8. 如果 new text trackmode 属性等于 "showing""hidden", 则将 active track flag 设为 true。
        9. new text track 添加到该 textTracks 属性中, 位于此 SourceBuffer 对象上。
          注意

          这应当触发 TextTrackList [HTML] 逻辑,排队一个任务触发一个事件,事件名为 addtrack, 并使用 TrackEvent, 其 track 属性初始化为 new text track,触发于由该 textTracks 属性引用的 TextTrackList 对象上,此对象位于该 SourceBuffer 上。

        10. 如果 父媒体源 是在 DedicatedWorkerGlobalScope 中构造的:
          [[port to main]] 发送一条内部 create track mirror 消息, 其在 Window 中的隐式处理器运行以下步骤:
          1. mirrored text track 为一个新的 TextTrack 对象。
          2. 将与 new text track 确定的相同属性值赋予 mirrored text track
          3. mirrored text track 添加到 HTMLMediaElement 的 textTracks 属性中。
          否则:
          new text track 添加到 HTMLMediaElement 的 textTracks 属性中。
          注意

          这应当触发 TextTrackList [HTML] 逻辑,排队一个任务触发一个事件,事件名为 addtrack, 并使用 TrackEvent, 其 track 属性初始化为 mirrored text tracknew text track,触发于由 HTMLMediaElement 的 textTracks 属性引用的 TextTrackList 对象上。

      7. 创建一个新的 轨道缓冲区,用于为该轨存储 编码帧
      8. 将该轨的 轨道描述 添加到该 轨道缓冲区 中。
    5. 如果 active track flag 等于 true,则运行以下步骤:
      1. 将该 SourceBuffer 添加到 activeSourceBuffers
      2. 排队一个任务触发一个事件, 事件名为 addsourcebuffer,触发于 activeSourceBuffers
    6. [[first initialization segment received flag]] 设为 true。
  6. [[pending initialization segment for changeType flag]] 设为 false。
  7. 如果 active track flag 等于 true,则运行以下步骤:
  8. 使用 父媒体源必要时镜像 算法在 Window 中运行以下步骤:
    1. 如果 HTMLMediaElementreadyState 属性 大于 HAVE_CURRENT_DATA, 则将 HTMLMediaElementreadyState 属性设为 HAVE_METADATA
      注意

      根据 HTMLMediaElement ready states [HTML] 逻辑,HTMLMediaElementreadyState 变化可能会在 HTMLMediaElement 上触发事件。

  9. 如果 sourceBuffers 中的每个对象(属于 父媒体源) 的 [[first initialization segment received flag]] 都为 true,则使用 父媒体源必要时镜像 算法在 Window 中运行以下步骤:
    1. 如果 HTMLMediaElementreadyState 属性为 HAVE_NOTHING, 则将 HTMLMediaElementreadyState 属性设为 HAVE_METADATA
      注意

      根据 HTMLMediaElement ready states [HTML] 逻辑,HTMLMediaElementreadyState 变化可能会在 HTMLMediaElement 上触发事件。若发生从 HAVE_NOTHING 转变为 HAVE_METADATA, 则应当触发 HTMLMediaElement 逻辑,排队一个任务触发一个事件, 事件名为 loadedmetadata, 触发于该媒体元素上。

5.5.8 编码帧处理

编码帧 已被 分段解析循环 完整解析后,执行以下步骤:

  1. 编码帧 在该 媒体分段 中的每一个,执行以下步骤:

    1. 循环顶部:
      如果 [[generate timestamps flag]] 等于 true:
      1. presentation timestamp 等于 0。
      2. decode timestamp 等于 0。
      否则:
      1. presentation timestamp 为该编码帧的 展示时间戳 的双精度浮点表示(单位为秒)。
        注意

        对于定时文本帧,可能需要特殊处理来确定展示与解码时间戳,因为这些信息可能在底层格式中未显式存在,或依赖于帧的顺序。一些元数据文本轨(如 MPEG2-TS PSI 数据)可能只有隐含时间戳。这些情况的格式特定规则 应当 位于 字节流格式规范 或单独的扩展规范中。

      2. decode timestamp 为该编码帧解码时间戳的双精度浮点表示(单位为秒)。
        注意

        实现不必在内部使用双精度浮点来存储时间戳。这种表示在此使用,是因为 HTML 规范中的时间戳即为这种表示。此处意在在不增加不必要复杂度的情况下阐明行为,而无需处理在底层字节流格式使用的时间戳表示中,添加 timestampOffset 可能导致时间戳回绕的问题。实现可使用任意内部时间戳表示,但添加 timestampOffset 应当 表现得与使用双精度浮点表示时相似。

    2. frame duration 为该编码帧时长的双精度浮点表示(单位为秒)。
    3. 如果 mode 等于 "sequence" 且 [[group start timestamp]] 已设置,则执行以下步骤:
      1. timestampOffset 设为 [[group start timestamp]] 减去 presentation timestamp
      2. [[group end timestamp]] 设为 [[group start timestamp]]
      3. 将所有 轨道缓冲区 上的 需要随机访问点标志 设为 true。
      4. 取消设置 [[group start timestamp]]
    4. 如果 timestampOffset 不为 0,则执行以下步骤:

      1. timestampOffset 加到 presentation timestamp 上。
      2. timestampOffset 加到 decode timestamp 上。
    5. track buffer 等于将把该编码帧添加到的 轨道缓冲区
    6. 如果 最后解码时间戳(属于 track buffer)已设置,且 decode timestamp 小于 最后解码时间戳
      如果 最后解码时间戳(属于 track buffer)已设置,且 decode timestamp最后解码时间戳 的差值大于 最后帧持续时间 的 2 倍:
      1. 如果 mode 等于 "segments":
        [[group end timestamp]] 设为 presentation timestamp
        如果 mode 等于 "sequence":
        [[group start timestamp]] 设为 [[group end timestamp]]
      2. 取消所有 轨道缓冲区 上的 最后解码时间戳
      3. 取消所有 轨道缓冲区 上的 最后帧持续时间
      4. 取消所有 轨道缓冲区 上的 最高结束时间戳
      5. 将所有 轨道缓冲区 上的 需要随机访问点标志 设为 true。
      6. 跳转至上方的 循环顶部 步骤,重新开始处理当前 编码帧
      否则:
      继续。
    7. frame end timestamp 等于 presentation timestampframe duration 之和。
    8. 如果 presentation timestamp 小于 appendWindowStart, 则将 需要随机访问点标志 设为 true,丢弃该编码帧,并跳转到循环顶部开始处理下一帧编码帧。
      注意

      一些实现 可以 选择收集这些 presentation timestamp 小于 appendWindowStart 的编码帧,并用它们在第一个 展示时间戳 大于等于 appendWindowStart 的编码帧处生成拼接,即使该帧并非 随机访问点。支持此能力需要多个解码器或快于实时的解码,因此目前不作为规范性要求。

    9. 如果 frame end timestamp 大于 appendWindowEnd, 则 将 需要随机访问点标志 设为 true,丢弃该编码帧,并跳转到循环顶部开始处理下一帧编码帧。
      注意

      一些实现 可以 选择收集 presentation timestamp 小于 appendWindowEndframe end timestamp 大于 appendWindowEnd 的编码帧,并利用它们在收集时位于追加窗口内的编码帧部分与稍后处理、仅部分重叠这些已收集编码帧末端的帧的起始部分之间生成跨越拼接。支持此能力需要多个解码器或快于实时的解码,因此目前不作为规范性要求。结合收集跨越 appendWindowStart 的编码帧,实现因此 可以 支持无缝音频拼接。

    10. 如果 track buffer 上的 需要随机访问点标志 等于 true,则执行以下步骤:
      1. 如果该编码帧不是一个 随机访问点,则丢弃该编码帧,并跳转到循环顶部开始处理下一帧编码帧。
      2. track buffer 上的 需要随机访问点标志 设为 false。
    11. spliced audio frame 为一个未设值的变量,用于保存音频拼接信息。
    12. spliced timed text frame 为一个未设值的变量,用于保存定时文本拼接信息。
    13. 如果 最后解码时间戳(属于 track buffer)未设置,且 presentation timestamp 落在 展示区间 内(此区间属于 编码帧) 且该编码帧位于 track buffer 中,则执行以下步骤:
      1. overlapped frametrack buffer 中满足上述条件的 编码帧
      2. 如果 track buffer 包含音频 编码帧
        运行 音频拼接帧 算法,若返回了拼接帧,则将其赋给 spliced audio frame
        如果 track buffer 包含视频 编码帧
        1. remove window timestamp 等于 overlapped frame展示时间戳 加 1 微秒。
        2. 如果 presentation timestamp 小于 remove window timestamp,则从 track buffer 移除 overlapped frame
          注意

          这是为了补偿在双精度浮点与有理数之间往返转换时可能出现的帧时间戳计算的微小误差。该容差允许在新帧的起始时间与已存在帧的起始时间相差不超过 1 微秒时,用新帧替换已存在帧。稍早于已存在帧到来的帧将由下方的移除步骤处理。

        如果 track buffer 包含定时文本 编码帧
        运行 文本拼接帧 算法,若返回了拼接帧,则将其赋给 spliced timed text frame
    14. 移除 track buffer 中已有的编码帧:
      如果 最高结束时间戳(属于 track buffer)未设置:
      track buffer 中移除所有 编码帧,这些帧的 展示时间戳 大于等于 presentation timestamp 且小于 frame end timestamp
      如果 最高结束时间戳(属于 track buffer)已设置且小于等于 presentation timestamp
      track buffer 中移除所有 编码帧,这些帧的 展示时间戳 大于等于 最高结束时间戳 且小于 frame end timestamp
    15. 通过移除在前两个步骤中被移除的 编码帧 的所有可能解码依赖来清理依赖关系:从 track buffer 中移除位于上述已移除帧与这些帧之后的下一个 随机访问点 之间的所有 编码帧
      注意

      一直移除到下一个 随机访问点 是对解码依赖的保守估计,因为这假设在被移除帧与下一个随机访问点之间的所有帧都依赖于已被移除的那些帧。

    16. 如果 spliced audio frame 已设置:
      spliced audio frame 添加到 track buffer
      如果 spliced timed text frame 已设置:
      spliced timed text frame 添加到 track buffer
      否则:
      将具有 presentation timestampdecode timestampframe duration编码帧 添加到 track buffer
    17. track buffer最后解码时间戳 设为 decode timestamp
    18. track buffer最后帧持续时间 设为 frame duration
    19. 如果 最高结束时间戳(属于 track buffer)未设置,或 frame end timestamp 大于 最高结束时间戳,则将 最高结束时间戳(属于 track buffer)设为 frame end timestamp
      注意

      需要“大于”检查,因为编码帧之间的双向预测可能导致 presentation timestamp 不是单调递增的,即使解码时间戳是单调递增的。

    20. 如果 frame end timestamp 大于 [[group end timestamp]], 则将 [[group end timestamp]] 设为 frame end timestamp
    21. 如果 [[generate timestamps flag]] 等于 true,则将 timestampOffset 设为 frame end timestamp
  2. 如果 HTMLMediaElementreadyState 属性为 HAVE_METADATA 且新的 编码帧 使 HTMLMediaElementbuffered 在当前播放位置拥有一个 TimeRanges, 则将 HTMLMediaElementreadyState 属性设为 HAVE_CURRENT_DATA

    注意

    根据 HTMLMediaElement ready states [HTML] 逻辑,HTMLMediaElementreadyState 变化可能在该元素上触发事件。

  3. 如果 HTMLMediaElementreadyState 属性为 HAVE_CURRENT_DATA 且新的 编码帧 使 HTMLMediaElementbuffered 拥有一个 TimeRanges, 该 TimeRanges 覆盖当前播放位置并延伸至当前播放位置之后一段时间,则将 HTMLMediaElementreadyState 属性设为 HAVE_FUTURE_DATA

    注意

    根据 HTMLMediaElement ready states [HTML] 逻辑,HTMLMediaElementreadyState 变化可能在该元素上触发事件。

  4. 如果 HTMLMediaElementreadyState 属性为 HAVE_FUTURE_DATA 且新的 编码帧 使 HTMLMediaElementbuffered 拥有一个 TimeRanges, 该 TimeRanges 覆盖当前播放位置并具有足够数据以确保不间断播放,则将 HTMLMediaElementreadyState 属性设为 HAVE_ENOUGH_DATA

    注意

    根据 HTMLMediaElement ready states [HTML] 逻辑,HTMLMediaElementreadyState 变化可能在该元素上触发事件。

  5. 如果该 媒体分段 包含超出当前 duration 的数据, 则运行 时长变更 算法,并将 new duration 设为当前时长与 [[group end timestamp]] 的最大值。

5.5.9 编码帧移除

当需要从 SourceBuffer 移除特定时间范围的 编码帧 时,请按照以下步骤操作:

  1. start 为移除范围的起始 展示时间戳
  2. end 为移除范围的结束 展示时间戳
  3. 对该 SourceBuffer 中的每个 轨道缓冲区,运行以下步骤:

    1. remove end timestamp 为当前 duration 的值。
    2. 如果此 轨道缓冲区 有一个大于等于 end随机访问点时间戳,则将 remove end timestamp 更新为该随机访问点时间戳。

      注意

      不同轨道的随机访问点时间戳可能不同,因为同一轨道内编码帧的依赖关系通常不同于另一轨道内的依赖关系。

    3. 从该 轨道缓冲区 中移除所有起始时间戳大于等于 start 且小于 remove end timestamp 的媒体数据。
      1. 对每个被移除的帧,如果该帧的 解码时间戳 等于该帧轨道的 最后解码时间戳,则运行以下步骤:

        如果 mode 等于 "segments":
        [[group end timestamp]] 设置为 展示时间戳
        如果 mode 等于 "sequence":
        [[group start timestamp]] 设置为 [[group end timestamp]] 的值。
      2. 取消所有 轨道缓冲区 上的 最后解码时间戳
      3. 取消所有 轨道缓冲区 上的 最后帧持续时间
      4. 取消所有 轨道缓冲区 上的 最高结束时间戳
      5. 将所有 轨道缓冲区 上的 需要随机访问点标志 设置为 true。
    4. 通过移除在上一步中被移除的 编码帧 的所有可能解码依赖来清理依赖关系:从该 轨道缓冲区 中移除位于上述已移除帧与其后的下一个 随机访问点 之间的所有 编码帧
      注意

      一直移除到下一个 随机访问点,是对解码依赖的保守估计,因为这假设在被移除帧与下一个随机访问点之间的所有帧都依赖于已被移除的那些帧。

    5. 如果此对象在 activeSourceBuffers 中, 当前播放位置大于等于 start 且小于 remove end timestamp, 并且 HTMLMediaElementreadyState 大于 HAVE_METADATA, 则将 HTMLMediaElementreadyState 属性设为 HAVE_METADATA, 并暂停播放。

      注意

      根据 HTMLMediaElement ready states [HTML] 逻辑,HTMLMediaElementreadyState 变化可能会在该元素上触发事件。

      注意

      此状态变化发生是因为当前播放位置的媒体数据被移除了。只有在为当前播放位置追加了媒体数据,或发生了 3.15.5 选中/启用轨道状态变化,播放才能继续。

  4. 如果 [[buffer full flag]] 等于 true 且此对象已准备好接受更多字节, 则将 [[buffer full flag]] 设为 false。

5.5.10 编码帧清除

当向此 SourceBuffer 追加新数据时,运行此算法以释放空间。

  1. new data 为即将追加到此 SourceBuffer 的数据。
    Issue 289: 编辑?编码帧清除算法需要注意“缓冲区满标志”可能会根据 |new data| 立即更新

    需要在此处认识到,实现 可以 决定在预测处理 [[input buffer]] 中现有字节和 new data 后 会超出 SourceBuffer 容量时,立即将 [[buffer full flag]] 设为 true。 此步骤使实现能更主动地在接收可能导致溢出的 new data 之前进行推回。例如,实际上至少已有一个实现采用此方法。

  2. 如果 [[buffer full flag]] 等于 false,则中止这些步骤。
  3. removal ranges 为可以从展示中清除以为 new data 腾出空间的展示时间范围列表。
    注意

    实现 可以 采用不同方法选择 removal ranges, 因此 Web 应用 不应 依赖特定行为。Web 应用可使用 buffered 属性观察缓冲数据是否已被清除。

  4. removal ranges 中的每个范围,运行 编码帧移除算法, 其中 startend 分别为移除范围的起止时间戳。

5.5.11 音频拼接帧

编码帧处理算法需要为两个重叠的音频 编码帧 生成拼接帧时,按照以下步骤操作:

  1. track buffer 为将包含拼接的 轨道缓冲区
  2. new coded frame 为即将被添加到 track buffer 的新 编码帧,它触发了拼接需求。
  3. presentation timestampnew coded frame展示时间戳
  4. decode timestampnew coded frame 的解码时间戳。
  5. frame durationnew coded frame编码帧时长
  6. overlapped frametrack buffer 中展示区间包含 presentation timestamp编码帧
  7. presentation timestampdecode timestamp 更新为基于 overlapped frame 音频采样率最近的音频采样时间戳。如果时间戳距离两个采样时间戳等距,则用较大的时间戳(例如, floor(x * sample_rate + 0.5) / sample_rate)。
    注意

    例如,给定如下值:

    • overlapped frame展示时间戳 等于 10。
    • overlapped frame 的采样率为 8000 Hz
    • presentation timestamp 等于 10.01255
    • decode timestamp 等于 10.01255

    presentation timestampdecode timestamp 被更新为 10.0125,因为 10.01255 更接近于 10 + 100/8000 (10.0125),而不是 10 + 101/8000 (10.012625)

  8. 如果用户代理不支持淡入淡出,则运行以下步骤:
    1. track buffer 移除 overlapped frame
    2. track buffer 添加一个静音帧,属性如下:
      注意

      一些实现 可以 对插入静音帧两侧的编码帧应用淡入淡出处理,使过渡不那么突兀。

    3. 无需返回拼接帧,直接返回给调用方。
      注意

      这样可使 new coded frame 能被添加到 track buffer,仿佛 overlapped frame 起初就不在 track buffer 中一样。

  9. frame end timestamppresentation timestampframe duration 之和。
  10. splice end timestamppresentation timestamp 与拼接时长(5 毫秒)之和。
  11. fade out coded framesoverlapped frame 以及 track buffer 中所有 展示时间戳 大于 presentation timestamp 且小于 splice end timestamp 的其他帧。
  12. track buffer 中移除 fade out coded frames 中的所有帧。
  13. 返回一个拼接帧,属性如下:
    • 展示时间戳 设为 overlapped frame展示时间戳
    • 解码时间戳 设为 overlapped frame解码时间戳
    • 编码帧时长 设为 frame end timestampoverlapped frame展示时间戳 的差值。
    • 淡出编码帧为 fade out coded frames
    • 淡入编码帧为 new coded frame
      注意

      如果 new coded frame 的时长小于 5 毫秒,则在 new coded frame 之后追加的编码帧将被用于正确渲染拼接。

    • 拼接时间戳为 presentation timestamp
    注意

    有关该拼接帧如何被渲染的详细信息,参见 音频拼接渲染 算法。

5.5.12 音频拼接渲染

当由 音频拼接帧算法生成的拼接帧需要由媒体元素进行渲染时,运行以下步骤:

  1. fade out coded frames 为拼接过程中被淡出的 编码帧
  2. fade in coded frames 为拼接过程中被淡入的 编码帧
  3. presentation timestampfade out coded frames 中第一个编码帧的 展示时间戳
  4. end timestampfade in coded frames 中最后一个帧的 展示时间戳 加上该帧的 编码帧时长
  5. splice timestamp 为拼接开始的 展示时间戳,即 fade in coded frames 的第一个帧的展示时间戳。
  6. splice end timestamp 等于 splice timestamp 加五毫秒。
  7. fade out samples 为解码 fade out coded frames 后生成的音频采样。
  8. 裁剪 fade out samples,使其只包含 presentation timestampsplice end timestamp 之间的采样。
  9. fade in samples 为解码 fade in coded frames 后生成的音频采样。
  10. 如果 fade out samplesfade in samples 没有共同的采样率和声道布局,则将其转换为统一的采样率和声道布局。
  11. output samples 为用于存放输出采样的缓冲区。
  12. fade out samplessplice timestampsplice end timestamp 的采样应用线性增益淡出,起始增益为 1,结束增益为 0。
  13. fade in samplessplice timestampsplice end timestamp 的采样应用线性增益淡入,起始增益为 0,结束增益为 1。
  14. fade out samplespresentation timestampsplice timestamp 之间的采样复制到 output samples
  15. splice timestampsplice end timestamp 之间的每个采样,计算 fade out samplesfade in samples 的相应采样之和,并存储到 output samples
  16. fade in samplessplice end timestampend timestamp 之间的采样复制到 output samples
  17. 渲染 output samples
注意

以下是该算法的图示。

音频拼接示意图

5.5.13 文本拼接帧

编码帧处理算法需要为两个重叠的定时文本 编码帧 生成拼接帧时,运行以下步骤:

  1. track buffer 为将包含拼接的 轨道缓冲区
  2. new coded frame 为即将被添加到 track buffer 的新 编码帧,它触发了拼接需求。
  3. presentation timestampnew coded frame展示时间戳
  4. decode timestampnew coded frame 的解码时间戳。
  5. frame durationnew coded frame编码帧时长
  6. frame end timestamppresentation timestampframe duration 之和。
  7. first overlapped frametrack buffer 中展示区间包含 presentation timestamp编码帧
  8. overlapped presentation timestampfirst overlapped frame展示时间戳
  9. overlapped framesfirst overlapped frame 以及 track buffer 中所有 展示时间戳 大于 presentation timestamp 且小于 frame end timestamp 的其他帧。
  10. track buffer 中移除 overlapped frames 中的所有帧。
  11. first overlapped frame编码帧时长 更新为 presentation timestamp 减去 overlapped presentation timestamp 的值。
  12. first overlapped frame 添加到 track buffer
  13. 无需返回拼接帧,直接返回给调用方。
    注意

    这样可使 new coded frame 能被添加到 track buffer,仿佛它起初就没有与 track buffer 中的任何帧重叠。

6. SourceBufferList 接口

SourceBufferList 是用于 SourceBuffer 对象的简单容器对象。它提供只读的数组访问方式,并在列表被修改时触发事件。

WebIDL[Exposed=(Window,DedicatedWorker)]
interface SourceBufferList : EventTarget {
  readonly attribute unsigned long length;

  attribute EventHandler onaddsourcebuffer;
  attribute EventHandler onremovesourcebuffer;

  getter SourceBuffer (unsigned long index);
};

6.1 属性

length 类型为 unsigned long, 只读

表示列表中 SourceBuffer 对象的数量。

onaddsourcebuffer 类型为 EventHandler

处理 addsourcebuffer 事件的事件处理器。

onremovesourcebuffer 类型为 EventHandler

处理 removesourcebuffer 事件的事件处理器。

6.2 方法

getter

允许通过数组操作符(即 [])访问列表中的 SourceBuffer 对象。

调用此方法时,用户代理必须运行以下步骤:

  1. 如果 index 大于等于 length 属性,则返回 undefined 并中止这些步骤。
  2. 返回列表中第 indexSourceBuffer 对象。

6.3 事件摘要

事件名 接口 何时分发...
addsourcebuffer Event SourceBuffer 被添加到列表时。
removesourcebuffer Event SourceBuffer 被从列表中移除时。

7. ManagedMediaSource 接口

ManagedMediaSource 是一个主动管理其内存内容的 MediaSource。与 MediaSource 不同,用户代理可以通过 内存清理算法,因任何原因从其 sourceBuffers (由 ManagedSourceBuffer 填充)清除内容。

注意: 清除原因
WebIDL[Exposed=(Window,DedicatedWorker)]
interface ManagedMediaSource : MediaSource {
  constructor();
  readonly attribute boolean streaming;
  attribute EventHandler onstartstreaming;
  attribute EventHandler onendstreaming;
};

7.1 属性

streaming

获取时:

  1. 返回该属性的当前值。

7.2 事件摘要

事件名 接口 何时分发...
startstreaming Event ManagedMediaSourcestreaming 属性从 false 变为 true 时。
endstreaming Event ManagedMediaSourcestreaming 属性从 true 变为 false 时。

7.3 算法

7.3.1 ManagedSourceBuffer 监控

以下步骤会周期性运行,每当 SourceBuffer 监控算法被调度执行时。

拥有 足够的受管数据以确保不间断播放,是一个由实现定义的条件,用户代理判断当前拥有足够数据以在展示过程中避免长时间卡顿。该条件会被持续评估,以决定何时切换 streaming 的值。这些切换表示用户代理认为数据缓冲充足或仍需更多数据。

能以高效方式检索和缓冲数据,是一个由实现定义的条件,用户代理判断其能以节能的方式抓取新数据,同时实现所需内存使用。

  1. 运行 MediaSourceSourceBuffer 监控算法。
  2. can play uninterrupted and efficiently 为一个标志,当下列条件同时满足时为 true: buffered 属性包含一个 TimeRanges,覆盖当前播放位置且拥有 足够的受管数据以确保不间断播放,并且 能以高效方式检索和缓冲数据
    如果 can play uninterrupted and efficiently 不等于 streaming在媒体元素上排队一个元素任务,其运行以下步骤:
    1. thisstreaming 属性设为 can play uninterrupted and efficiently
    2. 如果 can play uninterrupted and efficiently 为 false,触发事件 名为 startstreaming,于 ManagedMediaSource 处触发。
    3. 否则,触发事件 名为 endstreaming,于 ManagedMediaSource 处触发。

7.3.2 内存清理

  1. thissourceBuffers 中的每个 buffer
    1. 运行 buffer内存清理算法。

8. BufferedChangeEvent 接口

WebIDL[Exposed=(Window,DedicatedWorker)]
interface BufferedChangeEvent : Event {
  constructor(DOMString type, optional BufferedChangeEventInit eventInitDict = {});

  [SameObject] readonly attribute TimeRanges addedRanges;
  [SameObject] readonly attribute TimeRanges removedRanges;
};

dictionary BufferedChangeEventInit : EventInit {
  TimeRanges addedRanges;
  TimeRanges removedRanges;
};

8.1 属性

addedRanges
在上一次 updatestartupdateend 事件之间新增的时间范围(这通常发生在上一次运行 编码帧处理 算法期间)。
removedRanges
在上一次 updatestartupdateend 事件之间被移除的时间范围(这通常发生在上一次运行 编码帧移除编码帧清除 算法期间,或当用户代理因 内存清理 而驱逐内容时)。

9. ManagedSourceBuffer 接口

WebIDL[Exposed=(Window,DedicatedWorker)]
interface ManagedSourceBuffer : SourceBuffer {
  attribute EventHandler onbufferedchange;
};

9.1 属性

onbufferedchange

一个 事件处理程序 IDL 属性, 其 事件处理程序事件类型bufferedchange

9.2 事件摘要

事件名 接口 何时分发…
bufferedchange BufferedChangeEvent 在调用 appendBuffer()remove()endOfStream() 之后,或由于用户代理运行 内存清理 算法而导致 ManagedSourceBuffer 的缓冲范围发生变化时分发。

9.3 算法

9.3.1 缓冲变更

当对 ManagedSourceBuffer buffer 的所有会导致该 bufferbuffered 发生变化的操作完成后,运行以下步骤。即,一旦 appendBuffer()remove()内存清理 算法完成时。

  1. previous buffered ranges 等于变更发生之前 buffered 属性的值。
  2. new buffered ranges 等于新的 buffered TimeRanges
  3. added 等于用 new buffered ranges 减去 previous buffered ranges 的结果。
  4. removed 等于用 previous buffered ranges 减去 new buffered ranges 的结果。
  5. eventInitDict 为一个新的 BufferedChangeEventInit 字典,并用 added 初始化其 addedRanges,用 removed 初始化其 removedRanges
  6. 排队一个任务, 以 触发一个事件,事件名为 bufferedchange, 在 buffer 上使用 BufferedChangeEvent 接口, 并用 eventInitDict 初始化。

9.3.2 内存清理

  1. 如果 this 不在其 ManagedMediaSource 父对象的 activeSourceBuffers 中:
    1. 运行 编码帧移除 算法, 将 start 设为 0,end 设为正无穷大,然后中止这些步骤。
  2. removal ranges 为一组可从展示中被驱逐的展示时间范围, 以确保从 currentTime 起直到能够再次获取该展示内容期间的连续播放。
    注意

    不同实现可以采用不同策略选择 removal ranges, 因此 Web 应用不应依赖特定行为。Web 应用应监听 bufferedchange 事件以观察缓冲数据的部分是否被驱逐。

  3. 对于 removal ranges 中的每个范围,运行 编码帧移除 算法,其中 startend 分别等于该移除范围的起始与结束时间戳。

10. HTMLMediaElement 扩展

本节规定当一个 MediaSource 附加到元素时,现有 HTMLMediaElementseekableHTMLMediaElementbuffered 属性 必须 返回的内容,以及当其 srcObject 属性被设为 MediaSourceHandle 对象时, 现有 HTMLMediaElementsrcObject 属性 必须 执行的操作。

HTMLMediaElementseekable 属性返回一个新的静态 规范化 TimeRanges 对象,按以下步骤创建:

  1. 如果该 MediaSource 是在一个已终止或正在关闭的 DedicatedWorkerGlobalScope 中构造的,则返回一个空的 TimeRanges 对象并中止这些步骤。
    注意

    此情况旨在处理如下实现:当某个在 DedicatedWorkerGlobalScope 中构造的 MediaSource 因 terminate() 或用户代理执行 终止 worker 而结束时,实现可能不再保留任何关于该 MediaSource 的已缓冲或可寻址媒体的历史信息;例如,这也可能是 close() 执行的最终结果。

    Issue 277: MSE-in-Workers:在 MediaSource 的 worker 终止时,是否(最终)应让附加的元素进入错误状态/媒体元素应如何处理?mse-in-workers

    当所附加的 worker MediaSource 的上下文被销毁时,是否应当发生某种(最终的)媒体元素错误状态转换?Chromium 的实验性 worker MSE 实现会保持元素的 readyState、networkState 和 error 与销毁前一致,但 seekable 与 buffered 属性会各自返回一个空的 TimeRange。

  2. recent durationrecent live seekable range 分别为 duration[[live seekable range]] 的最新值,按如下方式确定:
    若该 MediaSourceWindow 中构造
    recent duration 设为 duration,并将 recent live seekable range 设为 [[live seekable range]]
    否则:
    recent durationrecent live seekable range 分别设为最近的取值, 这些取值由该 MediaSource 在每次 duration[[live seekable range]] 变化时, 通过向其 [[port to main]] 发送的隐式消息更新。
  3. recent duration 等于 NaN:
    返回一个空的 TimeRanges 对象。
    recent duration 等于正无穷大:
    1. 如果 recent live seekable range 非空:
      1. union rangesrecent live seekable rangeHTMLMediaElementbuffered 属性的并集。
      2. 返回一个单一范围,其起始时间等于 union ranges 中最早的起始时间, 结束时间等于 union ranges 中最高的结束时间,并中止这些步骤。
    2. 如果 HTMLMediaElementbuffered 属性返回一个空的 TimeRanges 对象, 则返回一个空的 TimeRanges 对象并中止这些步骤。
    3. 返回一个单一范围,其起始时间为 0,结束时间等于 HTMLMediaElementbuffered 所报告的最高结束时间。
    否则:
    返回一个单一范围,其起始时间为 0,结束时间等于 recent duration

HTMLMediaElementbuffered 属性返回一个静态的 规范化 TimeRanges 对象,按以下步骤计算。

  1. 如果该 MediaSource 是在一个已终止或正在关闭的 DedicatedWorkerGlobalScope 中构造的,则返回一个空的 TimeRanges 对象并中止这些步骤。
    注意

    此情况旨在处理如下实现:当一个在 DedicatedWorkerGlobalScope 中构造的 MediaSource 因 terminate() 或用户代理执行 终止 worker 而结束时,实现可能不再保留任何关于该 MediaSource 的已缓冲或可寻址媒体的历史信息;例如,这也可能是 close() 执行的最终结果。

    Issue 277: MSE-in-Workers:在 MediaSource 的 worker 终止时,是否(最终)应让附加的元素进入错误状态/媒体元素应如何处理?mse-in-workers

    当所附加的 worker MediaSource 的上下文被销毁时,是否应当发生某种(最终的)媒体元素错误状态转换?Chromium 的实验性 worker MSE 实现会保持元素的 readyState、networkState 和 error 与销毁前一致,但 seekable 与 buffered 属性会各自返回一个空的 TimeRange。

  2. 按如下方式确定 recent intersection ranges
    若该 MediaSourceWindow 中构造
    1. recent intersection ranges 为一个空的 TimeRanges 对象。
    2. activeSourceBuffers.length 不为 0,则运行以下步骤:
      1. active ranges 为对 buffered 的返回结果,针对 SourceBuffer 的每个对象, 这些对象位于 activeSourceBuffers 中。
      2. highest end timeactive ranges 中最大的范围结束时间。
      3. recent intersection ranges 为一个 TimeRanges 对象, 其包含从 0 到 highest end time 的单一范围。
      4. 对于 SourceBuffer 中的每个对象(位于 activeSourceBuffers 中),运行以下步骤:
        1. source ranges 等于当前 buffered 属性返回的范围集合。
        2. readyState 为 "ended", 则将 source ranges 中最后一个范围的结束时间设为 highest end time
        3. new intersection rangesrecent intersection rangessource ranges 的交集。
        4. new intersection ranges 替换 recent intersection ranges 中的范围。
    否则:
    recent intersection ranges 为在 Window 情况下步骤所得的 TimeRanges, 但使用位于其 DedicatedWorkerGlobalScope 中的该 MediaSource 及其 SourceBuffer 对象, 并在每次更新 activeSourceBuffersreadyState 或任何会改变各 buffered 属性取值的缓冲状态时, 通过 [[port to main]] 的隐式消息进行通信。
    注意

    如此频繁地重新计算并通信 recent intersection ranges 会带来额外开销, 这也是允许实现使用其他机制(如共享内存与锁)按需查询该信息的原因之一, 详见 跨上下文通信模型

  3. 如果本算法尚未设置该属性的当前值,或 recent intersection ranges 不包含与该属性当前值完全相同的范围信息, 则将该属性的当前值更新为 recent intersection ranges
  4. 返回该属性的当前值。

如果某个 HTMLMediaElementsrcObject 属性被赋值为一个 MediaSourceHandle,则在调用元素的加载算法之前, 作为扩展的 HTMLMediaElementsrcObject setter 的同步步骤的一部分,将该 MediaSourceHandle[[has ever been assigned as srcobject]] 设为 true。

注意

这可防止再次传输该 MediaSourceHandle 对象, 并在尝试时提供明确的同步异常。

Issue

需要将 MediaSourceHandle 添加到 HTMLMediaElement 的 MediaProvider IDL typedef 及其与媒体提供者对象相关的文本中。

11. AudioTrack 扩展

本节对 [HTML] 中 AudioTrack 的定义进行扩展。

WebIDL[Exposed=(Window,DedicatedWorker)]
partial interface AudioTrack {
  readonly attribute SourceBuffer? sourceBuffer;
};
Issue 280: MSE-in-Workers: {Audio,Video,Text}Track{,List} 在 HTML 中的 IDL 需要在 Exposed 中增加 DedicatedWorker mse-in-workers
[HTML] 中的 AudioTrack 需要同时对 Window 与 DedicatedWorker 暴露。

属性

sourceBuffer,类型为 SourceBuffer, 只读,且可为空

获取时,运行以下步骤:

如果此轨道是由与该轨道处于同一 realm 中创建的 SourceBuffer 所创建,且该 SourceBuffer 尚未从其 sourceBuffers 属性(属于其 父媒体源)中被移除:
返回创建此轨道的 SourceBuffer
否则:
返回 null。
注意
例如,若某个 DedicatedWorkerGlobalScopeSourceBuffer 通知其在 Window 中的内部 create track mirror 处理器创建了此轨道,则该轨道在 Window 中的副本对本属性将返回 null。

12. VideoTrack 扩展

本节对 [HTML] 中 VideoTrack 的定义进行扩展。

WebIDL[Exposed=(Window,DedicatedWorker)]
partial interface VideoTrack {
  readonly attribute SourceBuffer? sourceBuffer;
};
Issue 280: MSE-in-Workers: {Audio,Video,Text}Track{,List} 在 HTML 中的 IDL 需要在 Exposed 中增加 DedicatedWorker mse-in-workers
[HTML] 中的 VideoTrack 需要同时对 Window 与 DedicatedWorker 暴露。

属性

sourceBuffer,类型为 SourceBuffer, 只读,且可为空

获取时,运行以下步骤:

如果此轨道是由与该轨道处于同一 realm 中创建的 SourceBuffer 所创建,且该 SourceBuffer 尚未从其 sourceBuffers 属性(属于其 父媒体源)中被移除:
返回创建此轨道的 SourceBuffer
否则:
返回 null。
注意
例如,若某个 DedicatedWorkerGlobalScopeSourceBuffer 通知其在 Window 中的内部 create track mirror 处理器创建了此轨道,则该轨道在 Window 中的副本对本属性将返回 null。

13. TextTrack 扩展

本节对 [HTML] 中 TextTrack 的定义进行扩展。

WebIDL[Exposed=(Window,DedicatedWorker)]
partial interface TextTrack {
  readonly attribute SourceBuffer? sourceBuffer;
};
Issue 280: MSE-in-Workers: {Audio,Video,Text}Track{,List} 在 HTML 中的 IDL 需要在 Exposed 中增加 DedicatedWorker mse-in-workers
[HTML] 中的 TextTrack 需要同时对 Window 与 DedicatedWorker 暴露。

属性

sourceBuffer,类型为 SourceBuffer, 只读,且可为空

获取时,运行以下步骤:

如果此轨道是由与该轨道处于同一 realm 中创建的 SourceBuffer 所创建,且该 SourceBuffer 尚未从其 sourceBuffers 属性(属于其 父媒体源)中被移除:
返回创建此轨道的 SourceBuffer
否则:
返回 null。
注意
例如,若某个 DedicatedWorkerGlobalScopeSourceBuffer 通知其在 Window 中的内部 create track mirror 处理器创建了此轨道,则该轨道在 Window 中的副本对本属性将返回 null。

14. 字节流格式

通过 appendBuffer() 提供给 SourceBuffer 的字节构成一个 逻辑字节流。此类字节流的格式与语义由 字节流格式规范 定义。字节流格式 注册表 [MSE-REGISTRY] 提供了可传递给 addSourceBuffer()isTypeSupported()changeType() 的 MIME 类型与 SourceBuffer 在解析新追加数据时所期望的字节流格式之间的映射。 鼓励各实现为其支持的字节流格式注册映射,以促进互操作性。字节流格式注册表 [MSE-REGISTRY] 是这些 映射的权威来源。若某实现宣称支持注册表中列出的某个 MIME 类型,则其 SourceBuffer 实现 必须 遵循注册项中所列的 字节流格式规范

注意

注册表中的字节流格式规范并不意在定义新的存储格式。它们仅概述了本规范实现所接受的 既有存储格式结构的子集。

注意

字节流格式的解析与校验由 分段解析循环 算法实现。

本节为所有字节流格式规范提供通用要求:

字节流规范 必须 至少定义保证上述要求成立的约束。为简化实现等目的,可以 定义额外约束。

15. 一致性

除标记为非规范性的章节外,本规范中的所有编写指南、图示、示例与注记均为非规范性内容。除此之外的内容均为规范性内容。

本文中的关键词 MAYMUSTMUST NOTSHOULDSHOULD NOT 按照 BCP 14 [RFC2119] 与 [RFC8174] 的描述进行解释,且仅当这些词以大写形式出现时(如本段所示)方适用。

16. 示例

16.1 使用 Media Source Extensions

<video id="v" autoplay></video>
<script>
const video = document.getElementById("v");
const mediaSource = new MediaSource();
mediaSource.addEventListener("sourceopen", onSourceOpen);
video.src = window.URL.createObjectURL(mediaSource);

async function onSourceOpen(e) {
  const mediaSource = e.target;

  if (mediaSource.sourceBuffers.length > 0) return;

  const sourceBuffer = mediaSource.addSourceBuffer(
    'video/webm; codecs="vorbis,vp8"',
  );

  video.addEventListener("seeking", (e) => onSeeking(mediaSource, e.target));
  video.addEventListener("progress", () =>
    appendNextMediaSegment(mediaSource),
  );

  try {
    const initSegment = await getInitializationSegment();

    if (initSegment == null) {
      // 获取初始化分段出错。使用错误结束流。
      mediaSource.endOfStream("network");
      return;
    }

    // 追加初始化分段。
    sourceBuffer.addEventListener("updateend", function firstAppendHandler() {
      sourceBuffer.removeEventListener("updateend", firstAppendHandler);

      // 追加一些初始媒体数据。
      appendNextMediaSegment(mediaSource);
    });

    sourceBuffer.appendBuffer(initSegment);
  } catch (error) {
    // 处理在获取初始化分段期间可能发生的错误。
    console.error("获取初始化分段出错:", error);
    mediaSource.endOfStream("network");
  }
}

async function appendNextMediaSegment(mediaSource) {
  if (
    mediaSource.readyState === "closed" ||
    mediaSource.sourceBuffers[0].updating
  )
    return;

  // 若流数据已用尽,则结束流。
  if (!haveMoreMediaSegments()) {
    mediaSource.endOfStream();
    return;
  }

  try {
    const mediaSegment = await getNextMediaSegment();

    // 注意: 若 mediaSource.readyState == "ended",此 appendBuffer() 调用将
    // 使其状态转为 "open"。Web 应用应准备处理多个 "sourceopen" 事件。
    mediaSource.sourceBuffers[0].appendBuffer(mediaSegment);
  }
  catch (error) {
    // 处理在获取媒体分段期间可能发生的错误。
    console.error("获取媒体分段出错:", error);
    mediaSource.endOfStream("network");
  }
}

function onSeeking(mediaSource, video) {
  if (mediaSource.readyState === "open") {
    // 中止当前分段追加。
    mediaSource.sourceBuffers[0].abort();
  }

  // 通知媒体分段加载逻辑从新的播放位置开始抓取数据。
  seekToMediaSegmentAt(video.currentTime);

  // 从新的播放位置追加一个媒体分段。
  appendNextMediaSegment(mediaSource);
}

function onProgress(mediaSource, e) {
  appendNextMediaSegment(mediaSource);
}

// 获取初始化分段的异步函数示例
async function getInitializationSegment() {
  // 在此实现获取初始化分段的逻辑
  // 这里只是占位函数
}

// 检查是否还有更多媒体分段的示例函数
function haveMoreMediaSegments() {
  // 在此实现是否仍有更多媒体分段的判断逻辑
  // 这里只是占位函数
}

// 获取下一个媒体分段的示例函数
async function getNextMediaSegment() {
  // 在此实现获取下一个媒体分段的逻辑
  // 这里只是占位函数
}

// 跳转到特定媒体分段的示例函数
function seekToMediaSegmentAt(currentTime) {
  // 在此实现跳转逻辑
  // 这里只是占位函数
}
</script>

16.2 使用受管媒体源(Managed Media Source)

<script>
async function setUpVideoStream() {
  // 指定的视频格式与编解码器
  const mediaType = 'video/mp4; codecs="mp4a.40.2,avc1.4d4015"';

  // 检查该视频格式/编解码器是否受支持。
  if (!window.ManagedMediaSource?.isTypeSupported(mediaType)) {
    return; // 不受支持,采取其他方案。
  }

  // 设置视频与其受管源。
  const video = document.createElement("video");
  const source = new ManagedMediaSource();

  video.controls = true;

  await new Promise((resolve) => {
    video.src = URL.createObjectURL(source);
    source.addEventListener("sourceopen", resolve, { once: true });
    document.body.appendChild(video);
  });

  const sourceBuffer = source.addSourceBuffer(mediaType);

  // 设置事件处理程序
  sourceBuffer.onbufferedchange = (e) => {
    console.log("触发 onbufferedchange 事件。");
    console.log(`新增范围: ${timeRangesToString(e.addedRanges)}`);
    console.log(`移除范围: ${timeRangesToString(e.removedRanges)}`);
  };

  source.onstartstreaming = async () => {
    const response = await fetch("./videos/bipbop.mp4");
    const buffer = await response.arrayBuffer();
    await new Promise((resolve) => {
      sourceBuffer.addEventListener("updateend", resolve, { once: true });
      sourceBuffer.appendBuffer(buffer);
    });
  };

  source.onendstreaming = async () => {
    // 在此停止抓取新分段
  };
}

// 辅助函数...
function timeRangesToString(timeRanges) {
  const ranges = [];
  for (let i = 0; i < timeRanges.length; i++) {
    ranges.push([timeRanges.start(i), timeRanges.end(i)]);
  }
  return "[" + ranges.map(([start, end]) => `[${start}, ${end})` ) + "]";     
}
</script>
<body onload="setUpVideoStream()"></body>

17. 致谢

编辑们感谢 Alex Giladi、Bob Lund、Chris Needham、Chris Poole、Chris Wilson、Cyril Concolato、Dale Curtis、David Dorwin、David Singer、Duncan Rowden、François Daoust、Frank Galligan、Glenn Adams、Jer Noble、Joe Steele、John Simmons、Kagami Sascha Rosylight、Kevin Streeter、Marcos Cáceres、Mark Vickers、Matt Ward、Matthew Gregan、 Michael(tm) Smith、Michael Thornburgh、Mounir Lamouri、Paul Adenot、Philip Jägenstedt、 Philippe Le Hegaret、Pierre Lemieux、Ralph Giles、Steven Robertson 与 Tatsuya Igarashi 对本规范所作的贡献。

A. VideoPlaybackQuality

本节为非规范性内容。

在本规范先前修订版中描述的视频播放质量指标(例如 候选推荐标准 的第 5 与第 10 节) 现已作为 [MEDIA-PLAYBACK-QUALITY] 的一部分进行制定。某些实现可能已支持较早草案中的 VideoPlaybackQuality 对象及 HTMLVideoElement 扩展方法 getVideoPlaybackQuality(), 这些均见于之前的修订版中。

B. 问题摘要

C. 参考

C.1 规范性引用

[dom]
DOM Standard。Anne van Kesteren。WHATWG。 现行标准(Living Standard)。URL:https://dom.spec.whatwg.org/
[ECMASCRIPT]
ECMAScript Language Specification。 Ecma International。URL:https://tc39.es/ecma262/multipage/
[FILEAPI]
File API。Marijn Kruisselbrink。W3C。2024 年 12 月 4 日。W3C 工作草案。URL:https://www.w3.org/TR/FileAPI/
[HTML]
HTML Standard。Anne van Kesteren; Domenic Denicola;Dominic Farolino;Ian Hickson;Philip Jägenstedt;Simon Pieters。WHATWG。现行标准(Living Standard)。URL:https://html.spec.whatwg.org/multipage/
[infra]
Infra Standard。Anne van Kesteren;Domenic Denicola。WHATWG。现行标准(Living Standard)。URL:https://infra.spec.whatwg.org/
[MSE-REGISTRY]
Media Source Extensions™ Byte Stream Format Registry。Matthew Wolenetz;Jerry Smith;Aaron Colwell。W3C。URL: https://www.w3.org/TR/mse-byte-stream-format-registry/
[RFC2119]
Key words for use in RFCs to Indicate Requirement Levels。S. Bradner。IETF。1997 年 3 月。最佳当前实践。URL:https://www.rfc-editor.org/rfc/rfc2119
[RFC8174]
Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words。B. Leiba。IETF。2017 年 5 月。最佳当前实践。URL:https://www.rfc-editor.org/rfc/rfc8174
[WEBIDL]
Web IDL Standard。Edgar Chen;Timothy Gu。 WHATWG。现行标准(Living Standard)。URL:https://webidl.spec.whatwg.org/

C.2 非规范性引用

[INBANDTRACKS]
Sourcing In-band Media Resource Tracks from Media Containers into HTML。Silvia Pfeiffer;Bob Lund。W3C。 2015 年 4 月 26 日。非正式草案。URL:https://dev.w3.org/html5/html-sourcing-inband-tracks/
[MEDIA-PLAYBACK-QUALITY]
Media Playback Quality。 Mounir Lamouri;Chris Cunningham。W3C。编辑草案。URL:https://w3c.github.io/media-playback-quality/
[url]
URL Standard。Anne van Kesteren。WHATWG。 现行标准(Living Standard)。URL:https://url.spec.whatwg.org/