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

01
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
02

实现方案

03


完整流程拆解

-
阶段 1:分享端点击”开启同屏”
functiontoggleSyncMode() {const newState = !isSyncMode; // 切换状态wsManager.send({type: 'TOGGLE_SYNC',enabled: newState});// 如果开启同屏,立即同步当前状态if (newState && pdfManager.pdfDoc) {syncState(); // 马上把当前的PDF状态发给参会端}}
elif msg_type == "TOGGLE_SYNC":# 切换同屏模式await manager.toggle_sync_mode(meeting_id, client_id, data.get("enabled", False))
async def toggle_sync_mode(self, meeting_id: str, client_id: str, enabled: bool):"""切换同屏模式"""# 权限检查:只有会控端能操作if self.controllers.get(meeting_id) != client_id:return Falsestate = 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
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);}});
nextPage():async function nextPage() {await pdfManager.nextPage(); // PDF管理器翻页updatePageDisplay(pdfManager.currentPage, pdfManager.totalPages);if (isSyncMode) 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 等});// 更新本地UIupdatePageDisplay(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, // 滚动位置Xscroll_y: this.container.scrollTop // 滚动位置Y};}
if msg_type == "STATE_UPDATE":#会控端更新状态await manager.update_state(meeting_id, client_id, data.get("state", {}))
async def update_state(self, meeting_id: str, client_id: str, data: dict):"""更新会议状态(仅会控人员可操作)"""if self.controllers.get(meeting_id) != client_id:return Falsestate = self.states.get(meeting_id)if not state:return Falsefor 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
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);});
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;}}}

实现效果



总结

STATE_UPDATE指令发送操作数据,后端校验共享模式后广播至所有查看端,查看端调用applyState同步画面。方案仅传输指令与 URL,带宽消耗极低,角色权限清晰,WebSocket 长连接保障实时性,在纯 H5 技术栈下即可实现稳定、高效的同屏体验,无需复杂流媒体架构。
夜雨聆风
