音乐播放器保持播放的经典问题 - $at.vm 的开发问题讨论
在 VM 的设计定义中,是主要使用 new Worker() 的方式进行最小化隔离。但整个 $at() 是面向单页面单路由应用设计的,因此在这里以 Web 音乐播放器 为例子,进行的一个讨论。
常见方案:
- SPA 应用制作
- Iframe 标签阻止刷新
- 弹出窗口
- Web Storage
- BroadcastChannel
第一种是向 Client-Browser 返回的报文中固定 <audio> 标签
它们第一次的报文通常如下,然后依靠不同的 脚本 (Script) 对 #content 的内容进行修正。一般是访问服务器的某个API端口,靠数据修改。是属于早期经典的 SPA 应用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SPA Music Player</title>
</head>
<body>
<div id="content">
<!-- 页面内容将在这里动态加载 -->
</div>
<div id="player">
<audio id="audio" controls>
<source src="music.mp3" type="audio/mpeg">
</audio>
</div>
</body>
</html>
伴随着发展。它不再被认为在生产环境中是安全的。
第二个方案是 由两个 HTML 页面实现隔离
这种方案相对 SPA 来说是折中的;A 页面保持变化,也就是 GUI 层面,B 页面保持状态不变化。可以实现真正的状态缓存。
这种办法最便捷的就是使用 iframe 创建不同的页面管理通信。
亦或者是 window.open() 的方式打开新页面窗口,使用 postMessage() 的方式进行交换。 BroadcastChannel() 的频道建立。这些是基于早期的即时通信的论坛技术实现的。
一、二方案都是不清理缓存的方案,从而实现的持续化播放。
在一和二方案之间的伪装方案:Web Storage
这种方案开销最大,管控最麻烦,暴露度高。因而很快就被抛弃。大致就是依靠对象(或者 json) 存储,恢复播放状态。
但由于 Client-Browser 会销毁整个页面,因而创建的播放器会被摧毁造成播放中断。而且在网络波动大时,中断缺点会被无限制地放大。
理清思路,解决技术债
在任何工业生产需要的情况下,服务器报文都是在前几行声明完整的通信和核验信息,空几行(\n\n\n) 供浏览器分割解析 HTML 文本。 Server 本身并不提供 DOM 解析 和局部更新,缓存操作都是由浏览器自主完成完善的。
也就是说,真正提供资源整合服务的是你的浏览器。传统意义上的服务是由服务器提供,但现代意义上的服务是由浏览器提供。或者叫做 终端感知、边界部署 之类的。
核心总结为以下三条:
- DOM 变化的唯一执行者是浏览器,触发方式为「HTML 解析」或「JS 调用 DOM API」;
- 服务器的所有输出都是「纯文本」,对 DOM 无任何操作能力;
- 前端框架的 DOM 更新,本质是封装了原生 DOM API,最终仍由浏览器执行。
现代化方案:
现代化方案更关注 消费端生产,遵循 声明即配置 的一个重要原则。也就是说,万物皆可参与消费。而不是工业化流程。
因此,字面量逻辑被改写。更适合每一个人参与到这个生产流程中。
React,Vue - NodeJS
这是另外一种 SPA 的展示,触发器(trigger) 在生产链的变更中被重视起来。URL 变为了一种崭新的表达式。工业产物变为消费产物。
// React + React Router 示例
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
import { useRef, useState } from 'react';
function App() {
const audioRef = useRef(null);
const [isPlaying, setIsPlaying] = useState(false);
const [currentSong, setCurrentSong] = useState(null);
return (
<BrowserRouter>
{/* 全局播放器组件,固定在底部 */}
<div className="global-player">
<audio
ref={audioRef}
src={currentSong?.url}
onPlay={() => setIsPlaying(true)}
onPause={() => setIsPlaying(false)}
/>
<button onClick={() => audioRef.current[isPlaying ? 'pause' : 'play']()}>
{isPlaying ? '暂停' : '播放'}
</button>
</div>
{/* 路由内容区域 */}
<Routes>
<Route path="/" element={<Home setCurrentSong={setCurrentSong} />} />
<Route path="/album/:id" element={<Album setCurrentSong={setCurrentSong} />} />
<Route path="/artist/:id" element={<Artist setCurrentSong={setCurrentSong} />} />
</Routes>
</BrowserRouter>
);
}
不得不说这种方式确实是革命性的,虽然程序设计上的变化难以察觉,但打开并丰富了工程组装方式以便于均衡负载和风险平摊。走上了专业化。
消费端新革命向讨论 - worker -$at.vm()
这是我们一直在讨论的方式,我们希望通过 new worker() 的方式,在不同页面间共享一个音乐进程。以下是较为常见的 Worker 方式。
-
专用 Worker (Dedicated Worker)
- 页面刷新/跳转:会销毁 Worker
- 单页应用路由变化:不会销毁(因为页面没有重新加载)
- URL hash 变化:不会销毁
- history.pushState/replaceState:不会销毁
-
共享 Worker (Shared Worker)
- 页面跳转但其他页面仍在使用:不会销毁
- 所有连接页面都关闭:会销毁
- 单页应用路由变化:不会销毁
-
Service Worker
- 独立于页面生命周期,URL 变化通常不影响
- 需要新的 Service Worker 更新时才会替换旧的
这三种通常存在于 Client-Browser 中,将权限下放给了用户的浏览器设备。
基于 Notes 中的核心哲学,$at.vm 注册的过程必须符合以下条件构造 Client-Node:
// 用户浏览器中的情况:
// 1. 页面加载时
// ↓
// Service Worker 注册并安装(如果支持)
// ↓
// 存储在:浏览器内部 → Service Worker 注册表
// ↓
// 控制范围:所有同源页面
// 2. 用户打开新标签页
// ↓
// Shared Worker 被创建(如果使用)
// ↓
// 存储在:浏览器内部 → Worker 线程池
// ↓
// 多个标签页可连接到同一个 Shared Worker
// 3. 服务器视角:
// ↓
// 服务器完全不知道 Worker 的存在
// ↓
// 只看到普通的 HTTP 请求
我们在原型中期待简化的是(基于 view 和 compute 的分割,方便全平台移植):
// 浏览器内部架构示意
浏览器进程
├── 主进程
├── 渲染进程(每个标签页)
│ └── 页面DOM、JavaScript
│ └── AudioContext/HTMLAudioElement
└── 音频进程(独立)
└── 音频解码器
└── 音频输出设备
└── 播放队列
也就是说,$at.vm() 实现它的基础技术栈需要满足如下:
1. Service Worker (缓存 + 后台同步) ---待开发
2. Web Audio API (底层音频控制) ---待开发
3. Media Session API (系统级集成) ---待开发
4. IndexedDB (状态持久化) ---$at.vm() 系统级支持
5. BroadcastChannel/SharedWorker (跨页面通信)
在 ToDo 中的 Flow Design Support,我们预计对 IndexedDB 的支持深入。
基于原生 ES6/JavaScript 简化的流程,同时要不影响普通开发者参与的程序设计流程,设计方面可能需要平台开发者主动导入其他框架形成合作。
$at.vm() 的默认装载激活项:
- [等待 at.StructDataShare 原型机完善]
new Worker() 用于 view 和 compute 的实现
- [等待 at.StructDataShare 完善]
IndexedDB.* 用于对象存储
- [未开发完毕]
Python.PySeq 用于数据的清洗导入
- [等待 atline.min.js 原型机完善]
at.StructDataShare 用于框架的对外交流部分
音乐播放器保持播放的经典问题 - $at.vm 的开发问题讨论
在 VM 的设计定义中,是主要使用
new Worker()的方式进行最小化隔离。但整个$at()是面向单页面单路由应用设计的,因此在这里以 Web 音乐播放器 为例子,进行的一个讨论。常见方案:
第一种是向 Client-Browser 返回的报文中固定
<audio>标签它们第一次的报文通常如下,然后依靠不同的 脚本 (Script) 对
#content的内容进行修正。一般是访问服务器的某个API端口,靠数据修改。是属于早期经典的 SPA 应用。伴随着发展。它不再被认为在生产环境中是安全的。
第二个方案是 由两个 HTML 页面实现隔离
这种方案相对 SPA 来说是折中的;A 页面保持变化,也就是 GUI 层面,B 页面保持状态不变化。可以实现真正的状态缓存。
这种办法最便捷的就是使用 iframe 创建不同的页面管理通信。
亦或者是
window.open()的方式打开新页面窗口,使用postMessage()的方式进行交换。BroadcastChannel()的频道建立。这些是基于早期的即时通信的论坛技术实现的。一、二方案都是不清理缓存的方案,从而实现的持续化播放。
在一和二方案之间的伪装方案:Web Storage
这种方案开销最大,管控最麻烦,暴露度高。因而很快就被抛弃。大致就是依靠对象(或者 json) 存储,恢复播放状态。
但由于 Client-Browser 会销毁整个页面,因而创建的播放器会被摧毁造成播放中断。而且在网络波动大时,中断缺点会被无限制地放大。
理清思路,解决技术债
在任何工业生产需要的情况下,服务器报文都是在前几行声明完整的通信和核验信息,空几行(
\n\n\n) 供浏览器分割解析 HTML 文本。 Server 本身并不提供 DOM 解析 和局部更新,缓存操作都是由浏览器自主完成完善的。也就是说,真正提供资源整合服务的是你的浏览器。传统意义上的服务是由服务器提供,但现代意义上的服务是由浏览器提供。或者叫做 终端感知、边界部署 之类的。
核心总结为以下三条:
现代化方案:
现代化方案更关注 消费端生产,遵循 声明即配置 的一个重要原则。也就是说,万物皆可参与消费。而不是工业化流程。
因此,字面量逻辑被改写。更适合每一个人参与到这个生产流程中。
React,Vue - NodeJS
这是另外一种 SPA 的展示,触发器(trigger) 在生产链的变更中被重视起来。URL 变为了一种崭新的表达式。工业产物变为消费产物。
不得不说这种方式确实是革命性的,虽然程序设计上的变化难以察觉,但打开并丰富了工程组装方式以便于均衡负载和风险平摊。走上了专业化。
消费端新革命向讨论 - worker -$at.vm()
这是我们一直在讨论的方式,我们希望通过
new worker()的方式,在不同页面间共享一个音乐进程。以下是较为常见的 Worker 方式。专用 Worker (Dedicated Worker)
共享 Worker (Shared Worker)
Service Worker
这三种通常存在于 Client-Browser 中,将权限下放给了用户的浏览器设备。
基于 Notes 中的核心哲学,$at.vm 注册的过程必须符合以下条件构造 Client-Node:
我们在原型中期待简化的是(基于 view 和 compute 的分割,方便全平台移植):
也就是说,$at.vm() 实现它的基础技术栈需要满足如下:
在 ToDo 中的 Flow Design Support,我们预计对 IndexedDB 的支持深入。
基于原生 ES6/JavaScript 简化的流程,同时要不影响普通开发者参与的程序设计流程,设计方面可能需要平台开发者主动导入其他框架形成合作。
$at.vm() 的默认装载激活项:
new Worker()用于 view 和 compute 的实现IndexedDB.*用于对象存储Python.PySeq用于数据的清洗导入at.StructDataShare用于框架的对外交流部分