You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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();

问题排查与修复建议

  1. 彻底释放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));
};
  1. 等待应用完全就绪后再发送播放命令:CEC开机后,Tizen应用可能仍处于初始化阶段,此时发送播放命令会导致播放器加载失败。可在Java端延迟3-5秒发送播放命令,或在Tizen应用中监听电视开机后的就绪事件(如b2bapis.b2bcontrol的状态回调),确认应用完全启动后再执行播放逻辑。

  2. 修正播放器状态判断逻辑:代码中存在枚举值转字符串后的类型不匹配问题,建议直接使用枚举值进行状态判断,避免类型错误:

// 修改状态判断逻辑
if (
  player.getState() === AVPlayPlayerState.NONE ||
  player.getState() === AVPlayPlayerState.IDLE
) {
  // ...后续逻辑
}
  1. 确认WebSocket连接状态:电视开机后WebSocket可能未重新建立,导致播放命令未被接收。Java端发送命令前需检查WebSocket连接状态,或在Tizen应用中添加命令缓存与重试机制,确保命令被正确处理。

内容的提问来源于stack exchange,提问作者user21744620

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 14:57:04