CEC命令开机后Tizen应用IP频道播放器卡顿报错问题排查
问题描述
开发了一款Tizen应用,可通过同一网络下的WebSocket控制电视操作并播放IP频道。当IP频道正在播放时,使用Tizen API将电视设为待机模式(关机),此时停止IP频道播放器;随后通过Java包经串口发送CEC命令开机,电视开机后Java包发送播放IP频道的命令,但播放器卡在播放时长0的状态,一段时间后监听器触发PLAYER_ERROR_STREAMING_PLAYER错误,无法确定问题根本原因。
环境与配置
- 三星Tizen电视(系统版本:6.0,型号:HG43AU800EUXEN)
- WebSocket服务器:通过Java包向Tizen应用发送命令
- Tizen应用:监听Java包发送的命令
- 用于控制IP频道的Tizen AVPlay API
Tizen应用代码
WebSocket连接代码(conn.ts)
// conn.ts (与WebSocket服务器建立连接的代码) let socket = new WebSocket("ws://ip:port/"); const websocketState = { CONNECTING: 0, OPEN: 1, CLOSING: 2, CLOSED: 3 }; const socketConnection = () => { socket.onopen = function (e) { console.log("Open Connection established"); // socket.send(JSON.stringify({ message: "Message From Tv" })); }; socket.onmessage = function (event) { console.log("event: ", event, typeof event.data); if (event.data) { console.log("Data:" + JSON.stringify(event.data)); updateFromSocket(event.data); } }; socket.onclose = function (event) { hardCodedAccessToken = "1c26d63ade4a43adc04e2f628a3676b2"; setTimeout(() => { console.log("Retrying to connect Web-Socket", { event }); socketConnection(); }, 1000); }; socket.onerror = function (error) { hardCodedAccessToken = "1c26d63ade4a43adc04e2f628a3676b2"; setTimeout(() => { console.log("Socket onerror -> ", { error }); socketConnection(); }, 1000); }; }; setInterval(() => { if (socket) { if ( socket.readyState == websocketState.CLOSED || socket.readyState == websocketState.CLOSING ) { socket = new WebSocket("ws://ip:port/"); socketConnection(); } } else { socket = new WebSocket("ws://ip:port/"); socketConnection(); } }, 2000);
播放IP频道与关机控制代码(controls.ts)
// controls.ts (播放IP频道及其他控制逻辑) // 播放器监听器,用于获取播放器当前状态 const listener = { onbufferingstart: function () { console.log("Buffering start."); }, onbufferingprogress: function (percent: any) { console.log("Buffering progress data : " + percent); }, onbufferingcomplete: function () { console.log("Buffering complete."); }, onstreamcompleted: function () { console.log("Stream Completed"); webapis.avplay.stop(); }, oncurrentplaytime: function (currentTime: any) { console.log("Current playtime: " + currentTime); }, onerror: function (eventType: any) { console.log("event type error : " + eventType); // 一段时间后触发**PLAYER_ERROR_STREAMING_PLAYER**错误 // tvController.launchApplication(); }, onevent: function (eventType: any, eventData: any) { console.log("event type: " + eventType + ", data: " + eventData); }, onsubtitlechange: function ( duration: any, text: any, data3: any, data4: any ) { console.log("subtitleText: " + text); }, ondrmevent: function (drmEvent: any, drmData: any) { console.log("DRM callback: " + drmEvent + ", data: " + drmData); }, }; const tvController = { playIPChannel: (url: string, currentIpIndex: number) => { // IP频道控制逻辑 try { tizen.tvwindow.hide(() => { console.log("Back to TV Mode"); }); } catch (err) { console.log(err); } const player = webapis.avplay; console.log("Player state: ", player.getState()); const successCallback = function () { console.log( "SUCCESS1: The media has finished preparing" + player.getState() ); player.play(); console.log("Playing: " + url); console.log("Player state 9: ", player.getState()); }; const errorCallback = function () { console.log("The media has FAILED to prepare : " + player.getState()); console.log("Player state 10: ", player.getState()); }; if ( player.getState() === String(AVPlayPlayerState.NONE) || player.getState() === String(AVPlayPlayerState.IDLE) ) { console.log("Player state 1: ", player.getState()); try { if (player.getState() !== AVPlayPlayerState.NONE) { player.stop(); player.close(); console.log("Player state 2: ", player.getState()); } player.open(url); player.setDisplayRect(0, 0, 1920, 1080); // 设置播放器显示区域 player.setListener(listener); const drmParam = { ForensicData: true, }; console.log({ drmParam }); console.log("Player state 3: ", player.getState()); if ( player.getState() === String(AVPlayPlayerState.IDLE) || player.getState() === String(AVPlayPlayerState.READY) ) { console.log("Player state 4 before prepare: ", player.getState()); const prepareAsyncResponse = player.prepareAsync( successCallback, errorCallback ); console.log({ prepareAsyncResponse }); } } catch (error) { console.log("ERROR -> player -> ", { error: JSON.stringify(error), }); // 调试用 return processError({ Error: error }); } } else if ( player.getState() === String(AVPlayPlayerState.PLAYING) || player.getState() === String(AVPlayPlayerState.PAUSED) || player.getState() === String(AVPlayPlayerState.READY) ) { console.log("Player state 5: ", player.getState()); try { player.changeURL(url, 0, "false"); console.log( "Player state 6: ", player.getState(), String(AVPlayPlayerState.PLAYING), player.getState() !== String(AVPlayPlayerState.PLAYING) ); if (player.getState() !== String(AVPlayPlayerState.PLAYING)) { console.log("HELL YEAHHHH"); try { player.play(); console.log("playing...", url); console.log("Player state 7: ", player.getState()); } catch (error) { if ( player.getState() === String(AVPlayPlayerState.IDLE) || player.getState() === String(AVPlayPlayerState.READY) ) { console.log("Player state 8: ", player.getState()); player.prepareAsync(successCallback, errorCallback); } console.log("ERROR on RUN after changeURL fn: ", { error, url, }); } } } catch (error) { console.log("ERROR in changeURLFN, ", { url }); return processError({ Error: error }); } } console.log("FINAL State: ", player.getState()); return processResponse(controlCommands.CHANNEL_CONTROLS, { currentIpIndex: currentIpIndex, }); }, setPowerOff: () => { // 电视关机逻辑 return new Promise((resolve, reject) => { try { const Success = function (val: any) { console.log("[setPowerOff] success : " + val); if ( webapis.avplay.getState() === AVPlayPlayerState.PLAYING || webapis.avplay.getState() === AVPlayPlayerState.PAUSED || webapis.avplay.getState() === AVPlayPlayerState.READY ) { webapis.avplay.stop(); } resolve(processResponse(controlCommands.POWER_OFF)); }; const Error = function (error: any) { console.log( "[setPowerOff] code :" + error.code + " error name: " + error.name + " message " + error.message ); reject(processError({ Error: error })); }; b2bapis.b2bcontrol.setPowerOff(Success, Error); } catch (err) { console.log("Failed to power off: ", err); reject(processError({ Error: err })); } }); }, }
Java包代码
串口连接代码
RXTXCommDriver rxtx = new RXTXCommDriver(); rxtx.initialize(); this.commPortIdentifier = CommPortIdentifier.getPortIdentifier(comPortName); this.commPort = this.commPortIdentifier.open("TV", 500); this.mySerialPort = (SerialPort) commPort; this.mySerialPort.setSerialPortParams(19200, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); inputStream = this.mySerialPort.getInputStream(); outputStream = this.mySerialPort.getOutputStream(); this.mySerialPort.setRTS(true);
CEC开机命令发送代码
String command = "E004" ; String commandWithCR = command.toUpperCase() + "\r\n"; dvLogger.debug("Message::Sending Command to TV","tvId::"+dvtvDriver.displayDeviceId, "Command::" + command.toUpperCase()); this.outputStream.write(commandWithCR.getBytes()); this.outputStream.flush();
问题排查与修复建议
- 彻底释放AVPlay资源:关机时仅调用
stop()无法完全释放播放器资源,电视开机后残留的异常状态会导致播放失败。修改setPowerOff的成功回调,添加close()调用:
const Success = function (val: any) { console.log("[setPowerOff] success : " + val); if ( webapis.avplay.getState() === AVPlayPlayerState.PLAYING || webapis.avplay.getState() === AVPlayPlayerState.PAUSED || webapis.avplay.getState() === AVPlayPlayerState.READY ) { webapis.avplay.stop(); webapis.avplay.close(); // 新增:彻底关闭播放器 } resolve(processResponse(controlCommands.POWER_OFF)); };
等待应用完全就绪后再发送播放命令:CEC开机后,Tizen应用可能仍处于初始化阶段,此时发送播放命令会导致播放器加载失败。可在Java端延迟3-5秒发送播放命令,或在Tizen应用中监听电视开机后的就绪事件(如
b2bapis.b2bcontrol的状态回调),确认应用完全启动后再执行播放逻辑。修正播放器状态判断逻辑:代码中存在枚举值转字符串后的类型不匹配问题,建议直接使用枚举值进行状态判断,避免类型错误:
// 修改状态判断逻辑 if ( player.getState() === AVPlayPlayerState.NONE || player.getState() === AVPlayPlayerState.IDLE ) { // ...后续逻辑 }
- 确认WebSocket连接状态:电视开机后WebSocket可能未重新建立,导致播放命令未被接收。Java端发送命令前需检查WebSocket连接状态,或在Tizen应用中添加命令缓存与重试机制,确保命令被正确处理。
内容的提问来源于stack exchange,提问作者user21744620
相关产品推荐
相关产品推荐

