乐于分享
好东西不私藏

纯 H5 实现共享屏幕小项目:PDF.js+WebSocket

纯 H5 实现共享屏幕小项目:PDF.js+WebSocket

 Web 开发中经常会遇到共享屏幕阅读类的需求,然而受项目预算有限、规模较小、团队技术能力不足等现实条件制约,无法采用复杂的技术实现方案。本文结合真实项目场景,详细介绍了如何通过 PDF.js+WebSocket 实现 PDF 文档共享同步的完整落地方案。

01

核心技术与工具作用
层面
技术/工具
作用
后端
FastAPI + WebSocket + Python
管理 WebSocket 连接、维护会议状态、处理 PDF 上传、广播同步指令
前端
PDF.js
解析 PDF URL 并渲染到 Canvas,支持翻页、缩放等核心操作
通信
WebSocket(自定义重连/心跳)+ 防抖/节流
保障长连接稳定性,避免频繁指令传输导致的性能问题

02

功能流程

实现方案

1. 共享场景初始化
    发起端和查看端通过共享标识如:shareid进入对应共享房间,后端推送 INITSTATE 消息,包括当前共享状态、在线人数等。若已开启共享模式,查看端立即加载发起端同步的 PDF URL 并渲染。
2. 发起端上传 PDF
    发起端选择本地PDF,上传至后端生成URL并自身加载预览,若已开启共享模式,自动将 PDF URL 和初始状态同步给所有查看端。
3. 共享模式运行
    发起端操作 PDF(翻页、缩放、滚动),进行防抖、节流后发送 STATE_UPDATE 指令至后端,后端更新共享状态并广播至所有查看端。查看端接收指令后,进行同步页码、缩放、滚动位置,实现发起端操作即查看端展示。
4. 共享模式关闭 / 连接断开
    发起端关闭共享模式,后端广播 SYNCMODECHANGED,查看端解锁交互、显示“等待共享”,客户端断开连接,后端清理连接,广播 USER_LEAVE 更新在线人数,若断开的是发起端则自动关闭共享模式。

03

实现流程
该方案实现流程图如下:

完整流程拆解

  1. 阶段 1:分享端点击”开启同屏”
分享端前端
用户点击”开启同屏”按钮 → 触发 toggleSyncMode方法:
functiontoggleSyncMode() {    const newState = !isSyncMode;  // 切换状态    wsManager.send({        type'TOGGLE_SYNC',        enabled: newState    });    // 如果开启同屏,立即同步当前状态    if (newState && pdfManager.pdfDoc) {        syncState();  // 马上把当前的PDF状态发给参会端    }}
后端处理(app.py)
WebSocket 端点收到 TOGGLE_SYNC 消息:
elif msg_type == "TOGGLE_SYNC":    # 切换同屏模式    await manager.toggle_sync_mode(meeting_id, client_id, data.get("enabled"False))
toggle_sync_mode 方法:
async def toggle_sync_mode(self, meeting_id: str, client_id: str, enabled: bool):    """切换同屏模式"""    # 权限检查:只有会控端能操作    if self.controllers.get(meeting_id) != client_id:        return False    state = self.states.get(meeting_id)    if state:        state.is_sync_mode = enabled  # 设置同屏标志        state.timestamp = datetime.now().timestamp()        # 广播给所有人(包括参会端)        await self.broadcast(meeting_id, {            "type""SYNC_MODE_CHANGED",            "is_sync_mode": enabled,            "state": asdict(state)  # 带上当前完整状态        })    return True
查看端响应
查看端收到 SYNC_MODE_CHANGED 消息:
wsManager.on('SYNC_MODE_CHANGED', async (data) => {    isSyncMode = data.is_sync_mode;    updateSyncUI(isSyncMode);  // 更新UI(显示"同屏中")    if (isSyncMode && data.state) {        // 如果开启同屏,立即加载会控端的当前状态        if (data.state.document_url && data.state.document_url !== currentDocumentUrl) {            await loadDocument(data.state.document_url);  // 加载PDF        }        await pdfManager.applyState(data.state);  // 应用页码、缩放等        updatePageDisplay(data.state.page_number, data.state.total_pages);        updateScaleDisplay(data.state.scale);    }});
阶段 2:同屏模式下,分享端每次操作
分享端作示例:翻页,户点”下一页” → nextPage()
async function nextPage() {    await pdfManager.nextPage();  // PDF管理器翻页    updatePageDisplay(pdfManager.currentPage, pdfManager.totalPages);    if (isSyncMode) syncState();  // 如果同屏开启,同步状态}
syncState() 把状态发给后端:
functionsyncState() {    if (!wsManager || !pdfManager.pdfDoc) return;    const state = pdfManager.getState();  // 获取当前状态    state.document_url = currentDocumentUrl;    wsManager.send({        type'STATE_UPDATE',        state: state  // 包含:page_number, scale, scroll_x, scroll_y 等    });    // 更新本地UI    updatePageDisplay(state.page_number, state.total_pages);    updateScaleDisplay(state.scale);}
pdfManager.getState() 返回的内容:
getState() {    return {        page_number: this.currentPage,      // 当前页码        total_pages: this.totalPages,       // 总页数        scale: this.scale,                  // 缩放比例        scroll_x: this.container.scrollLeft, // 滚动位置X        scroll_y: this.container.scrollTop   // 滚动位置Y    };}
后端判断是否广播WebSocket 端点收到 STATE_UPDATE:
if msg_type == "STATE_UPDATE":#会控端更新状态  await manager.update_state(meeting_id, client_id, data.get("state", {}))
update_state 方法:
async def update_state(self, meeting_id: str, client_id: str, data: dict):    """更新会议状态(仅会控人员可操作)"""    if self.controllers.get(meeting_id) != client_id:        return False    state = self.states.get(meeting_id)    if not state:        return False    for key, value in data.items():        if hasattr(state, key):            setattr(state, key, value)    state.timestamp = datetime.now().timestamp()    if state.is_sync_mode:        await self.broadcast(meeting_id, {            "type""STATE_UPDATE",            "state": asdict(state)        }, exclude=client_id)  # 不发给会控端自己    return True
查看端应用状态参会端收到 STATE_UPDATE 消息:
wsManager.on('STATE_UPDATE'async (data) => {    if (!isSyncMode) return;  // 如果不是同屏模式,忽略    const state = data.state;    // 如果文档变化,重新加载    if (state.document_url && state.document_url !== currentDocumentUrl) {        await loadDocument(state.document_url);    }    // 应用状态:翻页、缩放、滚动    await pdfManager.applyState(state);    updatePageDisplay(state.page_number, state.total_pages);    updateScaleDisplay(state.scale);});
pdfManager.applyState() 实现:
async applyState(state) {    // 更新缩放    if (state.scale && state.scale !== this.scale) {        this.scale = state.scale;    }    // 跳转到指定页    if (state.page_number && state.page_number !== this.currentPage) {        await this.renderPage(state.page_number);    } else if (state.scale) {        await this.renderPage(this.currentPage);    }    // 同步滚动位置    if (this.container) {        if (state.scroll_x !== undefined) {            this.container.scrollLeft = state.scroll_x;        }        if (state.scroll_y !== undefined) {            this.container.scrollTop = state.scroll_y;        }    }}

实现效果

项目实现效果如下,左侧为分享端,右侧为观看端:

总结

    本方案基于 WebSocket 实现轻量级 PDF 共享同步,采用 “分享端主导、查看端被动同步” 模式,适配小范围文档演示场景。分享端开启共享后,后端广播状态,查看端自动加载同步的 PDF 并进入只读模式;分享端翻页、缩放、滚动时,通过STATE_UPDATE指令发送操作数据,后端校验共享模式后广播至所有查看端,查看端调用applyState同步画面。方案仅传输指令与 URL,带宽消耗极低,角色权限清晰,WebSocket 长连接保障实时性,在纯 H5 技术栈下即可实现稳定、高效的同屏体验,无需复杂流媒体架构。
本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 纯 H5 实现共享屏幕小项目:PDF.js+WebSocket

评论 抢沙发

9 + 8 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮