HarmonyOS 「校园二手交易商城」App应用实战 49:视频播放控制与异常处理

发布时间:2026/9/1 19:25:46
HarmonyOS 「校园二手交易商城」App应用实战 49:视频播放控制与异常处理 49 视频播放控制与异常处理系列五直播与画中画 · 第 9 篇对应工程common/multishoppingbase/src/main/ets/utils/AvPlayerUtil.ets引言视频能放起来只是及格线能受控地停、干净地释放、异常地降级才是生产级。本篇聚焦AvPlayerUtil的三大韧性设计playerStateControl的播放/暂停总开关、onError等错误回调的兜底、以及release/closeRawFdSync的资源回收链。这些代码平时不显眼但正是它们在异常场景下保证应用不黑屏、不卡死、不泄漏。播放/暂停总开关playerStateControlplayerStateControl是 UI 与 PiP 控制面板共用的入口第 2 篇点视频、第 4 篇控制面板事件都调它逻辑只有四步playerStateControl():void{try{if(this.avPlayerundefined){Logger.error(AvPlayer is undefined);return;}if(this.avPlayer.statestopped){this.avPlayer.prepare();// 播完后再点重新准备从头再播return;}if(!this.playState){this.avPlayer.play();// 暂停态 → 继续播}else{this.avPlayer.pause();// 播放态 → 暂停}}catch(exception){Logger.error(Failed to playerStateControl. Code: JSON.stringify(exception));}}三个关键点stopped特判播放完成后状态机停在stopped此时play()非法必须先prepare()回到prepared才能播。这对应播完再点重新播放的交互。playState是唯一事实源内部布尔由playing/paused状态回调维护比反复读avPlayer.state更轻量、更可靠。try/catch 兜底AVPlayer 的方法返回 Promise 也可能同步抛错统一捕获记日志不让异常冒泡到 UI 层。配套的play()/pause()是有状态守卫的简化版只在状态允许时动作play():void{if(this.avPlayer!undefined!this.playState){this.avPlayer.play();}}pause():void{if(this.avPlayer!undefinedthis.playState){this.avPlayer.pause();}}错误回调onError 与 prepare 失败AVPlayer 的错误有两条通道AvPlayerUtil两条都堵住了。通道一on(error)注册的全局错误回调。播放器运行中任何内部错误解码失败、资源异常等都会触发privateonError:(err:BusinessError)void(err:BusinessError){Logger.error(Invoke avPlayer failed, code is${err.code}, message is${err.message});if(this.avPlayerundefined){Logger.error(AvPlayer is undefined);return;}this.avPlayer.reset().catch((){Logger.error(avPlayer reset error);});};核心动作是reset()把播放器从error状态拉回idle。reset()后状态机自动进入idleonStateChange的idle分支会重新拉取 rawfile 描述符并设置fdSrc——一次错误触发一次完整的重置 → 重新绑定数据源自愈流程这正是第 3 篇说的状态机自驱动。通道二异步方法的 Promise 失败回调。prepare()等方法的失败不会进onError而是走 Promise 的 rejectionthis.avPlayer.prepare().then((){Logger.info(AVPlayer prepare succeeded.);},(err:BusinessError){Logger.error(Invoke prepare failed, code is${err.code}, message is${err.message});this.avPlayer?.reset().catch((){Logger.error(avPlayer reset error);});});两通道殊途同归失败一律 reset让状态机回到 idle 重新来过。如果连续失败idle分支每次都会重新getRawFd形成有限重试。状态异常降级error 与 released 的收尾状态机里error与released两个终态都要做资源收尾——关闭文件描述符casereleased:try{uiContext.getHostContext()?.resourceManager.closeRawFdSync(AvPlayerUtil.liveVideoName);}catch(error){Logger.info(closeRawFdSync called.);}break;caseerror:Logger.error(AVPlayer state error called.);try{uiContext.getHostContext()?.resourceManager.closeRawFdSync(AvPlayerUtil.liveVideoName);}catch(error){Logger.info(closeRawFdSync called.);}break;closeRawFdSync是同步关闭 rawfile 文件描述符的 API第 10 篇详述。error状态下 fd 已不可靠主动关闭避免 fd 泄漏released表示播放器已销毁同样关闭。两处都包 try/catch——fd 可能已被系统回收重复关闭会抛异常吞掉即可。release 资源回收链release()是页面退出时调用的总回收口顺序有讲究——先注销回调、再释放实例release():void{if(this.avPlayer!undefinedthis.avPlayer.state!released){try{this.avPlayer.off(error);this.avPlayer.off(stateChange);this.avPlayer.release();}catch(exception){Logger.error(Failed to unregister the error and state callback. Code: JSON.stringify(exception));}}else{Logger.info(AvPlayer release failed);}}为什么必须先off再release因为release()后对象进入released终态若回调仍挂着后续任何状态变化或释放过程本身都可能触发回调访问已释放的对象。off(error) off(stateChange)把两个监听器摘干净再释放杜绝野回调。state ! released的守卫防止重复释放。createAvPlayer里还有个隐藏的释放点Surface 变更时先this.release()再置this.avPlayer undefined重建——复用同一个release()保证任何路径的销毁都走同一条回收链。注意事项与总结错误统一走 resetonError与 Promise rejection 两条通道最终都reset()回idle配合状态机的idle分支自动重建数据源实现自愈。先 off 后 release释放顺序反了会留下野回调release()后对象不可再用。closeRawFdSync 要 try/catchfd 可能已被关闭重复关闭抛异常属于预期吞掉并记日志。状态守卫防非法调用stopped不能直接play、released不能重复release每个公开方法都要先查状态。playState 与真实状态解耦控制逻辑依赖内部布尔而非频繁读avPlayer.state兼顾性能与正确性。播放控制与异常处理让播放器皮实了。最后一篇揭开数据源的秘密rawfile 与 fdSrc 的资源管理。