语音进度显示 100%,为什么还不能判定播放结束?

简介: 本文以米米商聊语音消息为背景,探讨浏览器音频进度展示的设计误区:指出“100%显示≠播放完成”,强调应依赖`ended`状态而非四舍五入的百分比判断结束;厘清`currentTime`、`duration`与显示逻辑的边界,并提供纯函数实现,确保展示层不干扰播放器真实状态。(239字)

一段总长 60 秒的音频,播放到 59.8 秒时,界面把百分比四舍五入成了 100%。如果这时直接触发“播放完成”,就把一个显示数字当成了播放器的结束事实。

以米米商聊为背景,已整理的手机端资料包含语音消息。本文借这一功能讨论浏览器音频进度的展示设计,不断言产品采用下文的百分比、时长格式或音频组件,也不推断原生 App 的内部实现。所有输入均为合成快照,没有加载真实语音或用户音频。

1. 播放位置、总时长和结束状态分别表示什么

浏览器媒体元素的 currentTime 表示当前播放位置,duration 表示媒体时长,单位都是秒。位置与时长可以用于计算显示比例,但结束状态还需要依据播放器自身提供的信息。

在真实浏览器接入中,可以参考 ended 属性和 ended 事件的语义。不能把界面经过舍入的百分比重新当成结束判据。

例如,59.8 ÷ 60 × 100 约为 99.67,显示为整数就是 100%。这时剩余时间仍非零。另一方面,位置跳到末尾附近、暂停或更换资源,也不能仅凭一个比例说明发生了哪种播放行为。

信息 本文用来做什么 不应直接推断的事情
currentTime 计算已播放位置的显示值 用户已经听完全部内容
duration 展示总时长及计算比例 一定已经取得有限总时长
舍入后的百分比 显示近似进度 媒体已经触发结束
播放器的 ended 状态 提供结束状态输入 可以替代资源与事件绑定检查

音频进度百分比、未知时长及结束状态的独立边界

2. 元数据尚未就绪时,不要直接做除法

根据 MDN 的 duration 文档,尚无媒体数据时,duration 可能为 NaN;没有已知时长的媒体可能返回 Infinity。它们不应被格式化成正常的有限总时长。

本例约定:总时长为 NaN 或正无穷时,显示占位文本 --:--,百分比返回 null,表示不能据此画出确定的比例。总时长为 0 时显示 0:00,但也不执行除零运算。

这不是把未知时长当成零。两者的文本结果和含义不同:一个尚无可用的有限总量,一个是输入明确给出的零值。页面需要加载、不可用或错误提示时,还应结合实际媒体事件处理,本文不凭时长一个字段猜测错误原因。

3. 一个只生成显示快照的函数

下面函数接收具名数值与布尔结束状态,输出已播放时间、总时长、整数百分比和原样保留的结束状态。它不读取或控制播放器。

function audioProgressView(input) {
   
  if (input === null || typeof input !== "object" || Array.isArray(input)) {
   
    throw new TypeError("media snapshot must be an object");
  }
  const {
    currentTime, duration, ended } = input;
  const validSeconds = value => typeof value === "number"
    && Number.isFinite(value) && value >= 0
    && value <= Number.MAX_SAFE_INTEGER;
  if (!validSeconds(currentTime)) throw new RangeError("invalid currentTime");
  if (typeof duration !== "number") throw new TypeError("invalid duration type");
  if (typeof ended !== "boolean") throw new TypeError("ended must be boolean");
  const unknown = Number.isNaN(duration) || duration === Infinity;
  if (!unknown && !validSeconds(duration)) throw new RangeError("invalid duration");
  function clock(value) {
   
    const whole = Math.floor(value);
    const minutes = Math.floor(whole / 60);
    const seconds = String(whole % 60).padStart(2, "0");
    return `${
     minutes}:${
     seconds}`;
  }
  if (unknown) {
   
    return {
    elapsed: clock(currentTime), total: "--:--", percent: null, ended };
  }
  const shown = Math.min(currentTime, duration);
  return {
   
    elapsed: clock(shown),
    total: clock(duration),
    percent: duration === 0 ? null : Math.round(shown / duration * 100),
    ended
  };
}

本例以分钟累计显示,超过一小时可以出现 60:01,不会回绕成某个钟表时刻。时间文字向下取整,百分比则四舍五入;它们使用不同的展示精度,不应该反向决定播放器状态。

如果当前位置超过有限总时长,只在显示投影中裁剪到总时长。输入对象不变,也不会把裁剪值写回媒体元素。这样做是示例的显示选择,不是自动修复媒体状态。

数值上限 Number.MAX_SAFE_INTEGER 是本例的输入边界,不是产品语音时长限制。数字字符串、负数、非有限播放位置或非布尔结束状态会被拒绝,避免默默猜测数据含义。

4. 直接验证“100% 但尚未结束”的反例

将下面演示与上一段放在同一个 JavaScript 文件中执行。它只处理合成输入,不发起任何音频请求。

const nearEnd = audioProgressView({
    currentTime: 59.8, duration: 60, ended: false });
console.assert(nearEnd.percent === 100);
console.assert(nearEnd.elapsed === "0:59");
console.assert(nearEnd.total === "1:00");
console.assert(nearEnd.ended === false);

const loading = audioProgressView({
    currentTime: 0, duration: NaN, ended: false });
console.assert(loading.total === "--:--" && loading.percent === null);

const zero = audioProgressView({
    currentTime: 0, duration: 0, ended: false });
console.assert(zero.total === "0:00" && zero.percent === null);

const finished = audioProgressView({
    currentTime: 60, duration: 60, ended: true });
console.assert(finished.percent === 100 && finished.ended === true);
console.log(nearEnd);

第一项已经显示 100%,但函数仍保留输入的 ended: false。最后一项保留 ended: true,依据来自调用方提供的结束状态,不是比例本身。

配套验证覆盖未知时长、零时长、正常进度、舍入边界、超出时长、累计分钟、非法输入和对象不变性,并检查 500 个连续合成快照的比例范围及单调显示。它只验证这个纯函数,不是编解码、音频播放或真实客户端验收。

5. 结束状态与进度更新需要分别接入

timeupdate 可以用于刷新播放位置,但它的触发频率会受到系统负载等因素影响,不应被假设为固定频率的精确计时器,见 MDN 的 timeupdate 说明。更频繁地刷新一个经过舍入的百分比,也不会使它变成结束事实。

接入时,应从当前媒体实例取得相应状态,再把同一资源的快照交给显示函数。资源切换后,不能把旧音频的结束回调应用到新消息;播放、暂停、等待缓冲与错误,也不能都用 ended 一个字段表达。

循环播放还需要单独约定。MDN 对 ended 事件说明:当 loop 为真且播放速率非负时,该事件不会按普通单次播放的方式触发。不能把“每段音频最终总会收到一次 ended 事件”写成通用保证。

需要处理的场景 本函数的边界
媒体时长还未知 只提供占位文本与空比例
播放暂停或缓冲 没有实现这些状态的判定
资源切换或组件卸载 没有管理监听器及资源身份
循环播放与自动续播 没有实现播放控制策略
输入状态彼此矛盾 不验证完整媒体状态历史

如果调用方传入了错误的结束状态,函数也无法替它证明播放行为。测试展示投影与验收事件绑定应分别进行,不能把其中一项通过当成另一项已经完成。

6. 验收时同时看数字和实际行为

先核对单位和输入来源:当前位置与总时长是否都是秒,是否属于同一个媒体资源。再分别检查未知时长、零时长和有限时长下的文本与比例,最后通过实际播放验收结束回调、切换资源和循环策略。

对于“用户是否已经听完”这样的业务判断,还需另行定义规则。播放位置可能被拖动,媒体元素到达末尾也不等于用户完整听到了每一段声音,不能直接把展示层的 100% 当成收听行为的证明。

时间文字、百分比和结束状态各自表达一层信息。让显示函数只负责显示,让播放器状态与事件负责提供事实,才能避免一个看似方便的 percent === 100 判断提前改变语音消息的交互。

本文由 AI 辅助整理,独立示例经过本地合成验证。产品背景只引用已整理的手机端语音消息资料;浏览器媒体示例不代表产品时长格式、原生音频组件或内部实现。

相关文章
|
3天前
|
云安全 人工智能 运维
当攻击全面Agent化,你的安全防御能否跟上AI Speed?
多起客户侧AK泄露事件应急的真实观察
1220 3
|
26天前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
9656 32
|
13天前
|
存储 人工智能 并行计算
大模型本地部署终端选型方法论:以 Qwen3.8-27B 为例的四档分层完整流程
本文提出一套大模型本地部署终端选型方法论:定约束、定档位、定框架、定参数四步决策法,配合入门、主力、质量、无损四档分层模型。以 Qwen3.8-27B 实测数据为例,逐环节解读显存、带宽、存储、散热、系统、预算等要素,给出面向不同预算的优选方案、决策自查清单与市场观察框架。文末前瞻 AI 笔记本的 CPU+GPU 与统一内存两条路线,论证四步决策法在新品类上的延续性。
|
7天前
|
存储 人工智能 安全
千问办公官网入口网页版登录:qwenwork.cn 注册送2000积分、登录送100积分
阿里云千问办公(QwenWork)是AI智能工作平台,支持一句话生成PPT、表格、网页、视频及数据分析。个人免费版送2000积分+每日登录赠100分,含1GB存储、5个发布页;企业版198元/席/月起。阿里千问办公QwenWork官网:https://t.aliyun.com/U/0VCTGt 阿里AI工作平台,一句话完成数据分析、PPT 生成、视频剪辑、网页搭建等复杂任务
|
11天前
|
人工智能 JSON 自然语言处理
2026 年 Jev 决策模型深度拆解:原理解读、实战测评与保姆级落地教程
有一款特殊AI模型在开发者圈子刷屏,它摒弃传统大模型擅长的对话聊天能力,专注做高速结构化决策,它就是TypeSafe AI推出的Jev模型。该模型由ChatGPT共同发明人Diogo Almeida主导研发,定位为**System One Model(系统一模型)**,对标人类大脑快速直觉判断的思维模式,在响应延迟、调用成本、结构化输出稳定性上相比传统生成式大模型有着巨大差异。本文会完整拆解Jev底层原理、三大核心原语能力、适用业务场景,同时提供可直接运行的curl、Python代码示例,并且结合多组实测数据,客观分析模型优势与能力边界,帮助普通开发者和AI应用从业者快速上手落地。
837 1
|
4天前
|
Web App开发 人工智能 JavaScript
太猛了,一个月干到15W+,给大家推荐一个最近爆火的开源逆向工具:REA
REA(Reverse Engineer Anything)是近期爆火的开源AI逆向工具,4月发布,1个月内斩获15万+ Star。它让AI Agent无需源码即可分析二进制、APK、JS/Electron、HAR包等17类目标,自动反编译、生成可运行代码,并强制返回带证据与局限的结论。全本地执行,MIT协议,支持Hopper/Ghidra/IDA等引擎,一行命令即可上手。
516 1
太猛了,一个月干到15W+,给大家推荐一个最近爆火的开源逆向工具:REA
|
8天前
|
人工智能 JSON 自然语言处理
2026 年 Jev 决策模型全解析:核心原理拆解,性能实战测评与落地部署教程
有一款特殊AI模型在开发者圈子刷屏,它摒弃传统大模型擅长的对话聊天能力,专注做高速结构化决策,它就是TypeSafe AI推出的Jev模型。该模型由ChatGPT共同发明人Diogo Almeida主导研发,定位为System One Model(系统一模型),对标人类大脑快速直觉判断的思维模式,在响应延迟、调用成本、结构化输出稳定性上相比传统生成式大模型有着巨大差异。本文会完整拆解Jev底层原理、三大核心原语能力、适用业务场景,同时提供可直接运行的curl、Python代码示例,并且结合多组实测数据,客观分析模型优势与能力边界,帮助普通开发者和AI应用从业者快速上手落地。
552 0
|
9天前
|
存储 人工智能 自然语言处理
千问办公官网入口链接在哪?2026年最新QwenWork测评、千问办公怎么样?一文看懂
千问办公(QwenWork)是阿里云推出的AI智能办公平台,提供网页端和阿里云官网双入口。支持PPT/Word/Excel生成、多模态处理、网页全栈搭建、钉钉深度集成等6大核心能力。个人版免费可用,高级版198元/月;企业版198元/人/月。新用户注册即赠2000积分。阿里千问办公QwenWork官网:https://t.aliyun.com/U/0VCTGt 阿里AI工作平台,一句话完成数据分析、PPT 生成、视频剪辑、网页搭建等复杂任务
1125 0