yimultiscreenserver-sdk-web
Version:
YiMultiScreenServer SDK for Web
695 lines (634 loc) • 32 kB
text/typescript
import { HTTPConfig } from "../config/HTTPConfig";
import { GlobalCache } from "../core/GlobalCache";
import { HttpRequestExecutor } from "../core/http/HttpRequestExecutor";
import { IHttpRequester } from "../core/http/IHttpRequester";
import { AccountBindDeviceResponse } from "../core/http/response/AccountBindDeviceResponse";
import { CastSessionResponse } from "../core/http/response/CastSessionResponse";
import { Code } from "../core/http/response/Code";
import { ConnectDisplayResponse } from "../core/http/response/ConnectDisplayResponse";
import { EmptyResponse } from "../core/http/response/EmptyResponse";
import { LoginUserResponse } from "../core/http/response/LoginUserResponse";
import { Result } from "../core/http/response/Result";
import { VerifyCodeResponse } from "../core/http/response/VerifyCodeResponse";
import { JsonParser } from "../core/json/JsonParser";
import { MQTTLogic } from "../core/mqtt/MQTTLogic";
import { ControllerMQTTImpl } from "../core/mqtt/impl/ControllerMQTTImpl";
import { IControllerMQTTCallback } from "../core/mqtt/impl/IControllerMQTTCallback";
import { BindDeviceAction } from "../core/mqtt/message/BindDeviceAction";
import { CastSessionMessage } from "../core/mqtt/message/CastSessionMessage";
import { DataMessage } from "../core/mqtt/message/DataMessage";
import { MsgClientType } from "../core/mqtt/message/MsgClientType";
import { SessionAction } from "../core/mqtt/message/SessionAction";
import { CastError } from "../util/CastError";
import { SDKLog } from "../util/SDKLog";
import { AccountBindDeviceVO } from "../vo/account/AccountBindDeviceVO";
import { LoginUserVO } from "../vo/account/LoginUserVO";
import { CastProtocol } from "../vo/cast/CastProtocol";
import { CastSessionVO } from "../vo/cast/CastSessionVO";
import { CastType } from "../vo/cast/CastType";
import { ControllerDeviceType } from "../vo/device/ControllerDeviceType";
import { ControllerDeviceVO } from "../vo/device/ControllerDeviceVO";
import { DevicePair } from "../vo/device/DevicePair";
import { DisplayDeviceVO } from "../vo/device/DisplayDeviceVO";
import { P2PKeyVO } from "../vo/p2p/P2PKeyVO";
import { DisplayReportVO } from "../vo/report/DisplayReportVO";
import { IControllerCallback } from "./IControllerCallback";
import { SessionDestroyReason } from '../core/mqtt/message/SessionDestroyReason';
export class ControllerManager implements IControllerMQTTCallback {
private readonly TAG: string = "ControllerManager";
private readonly jsonParser: JsonParser;
private readonly p2pKeyFromPeer: P2PKeyVO;
private readonly httpRequestExecutor: HttpRequestExecutor;
private readonly cache: GlobalCache;
private guid: string;
private mqttLogic: MQTTLogic | undefined;
private callback: IControllerCallback | undefined;
constructor() {
this.p2pKeyFromPeer = new P2PKeyVO();
this.cache = new GlobalCache();
this.httpRequestExecutor = new HttpRequestExecutor();
this.jsonParser = new JsonParser();
this.guid = "fake-guid";
}
setGuid(guid: string): this {
this.guid = guid;
return this;
}
setCallback(callback: IControllerCallback): this {
this.callback = callback;
return this;
}
setHttpRequester(requester: IHttpRequester): this {
this.httpRequestExecutor.setHttpRequester(requester);
return this;
}
setHttpConfig(config: HTTPConfig): this {
this.httpRequestExecutor.setHTTPConfig(config);
return this;
}
setMQTTLib(mqtt: MQTTLogic): this {
this.mqttLogic = mqtt;
return this;
}
createControllerDevice(controllerDevice: ControllerDeviceVO): this {
this.cache.addControllerDevice(controllerDevice.guid, controllerDevice);
return this;
}
updateControllerType(type: ControllerDeviceType): this {
let cd = this.cache.getControllerDevice(this.guid);
cd!.controllerDeviceType = type;
this.cache.addControllerDevice(this.guid, cd!);
return this;
}
updateNetwork(ip: string, gateway: string, mask: string): this {
let cd = this.cache.getControllerDevice(this.guid);
cd!.ip = ip;
cd!.gateway = gateway;
cd!.mask = mask;
this.cache.addControllerDevice(this.guid, cd!);
return this;
}
/**
* 阻塞方法,控制终端给指定手机号发送注册/登录验证码
* 请使用Task调用,不需要在Task中进行await,因为函数结果是通过回调触发
* @param phoneNumber 手机号
*/
async requestVerifyCode(phoneNumber: string): Promise<boolean> {
let result: Result<VerifyCodeResponse> = await this.httpRequestExecutor.requireVerifyCode(phoneNumber);
if (Code.OK !== result.code) {
switch (result.code) {
case Code.ERR_SMS_REACH_TODAY_COUNT:
this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL,
CastError.SUB_SEND_SMS_REACH_TODAY_COUNT_FAIL));
break;
case Code.ERR_SMS_INVALID_PHONE_NUMBER | Code.ERR_SMS_OTHER_API_EXCEPTION:
this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL,
CastError.SUB_SEND_SMS_FAIL));
break;
default:
break;
}
return false;
}
SDKLog.e(this.TAG, "requestVerifyCode: " + result);
return result.data!.isSMSSuccessSend;
}
private subscribeAccountTopic(user: LoginUserVO): void {
let mqtt: ControllerMQTTImpl = this.mqttLogic as ControllerMQTTImpl;
mqtt.subscribeAccountTopic(user.account.accountId);
}
private unsubscribeAccountTopic(user: LoginUserVO): void {
let mqtt: ControllerMQTTImpl = this.mqttLogic as ControllerMQTTImpl;
mqtt.unsubscribeAccountTopic(user.account.accountId);
}
/**
* 阻塞方法,控制终端通过手机号和短信验证码登录账号,如果账号不存在则自动注册
* 返回值为空意味着登录失败,请检查错误信息
* @param phoneNumber 手机号
* @param verifyCode 短信验证码
* @returns LoginUserVO | null 登录成功后返回用户信息,失败则返回null
*/
async login(phoneNumber: string, verifyCode: string): Promise<LoginUserVO | null> {
let temp = this.cache.getControllerDevice(this.guid);
let result: Result<LoginUserResponse> = await this.httpRequestExecutor.login(phoneNumber,
verifyCode, temp!.controllerDeviceType, this.guid, "verifyCode"/*目前先写死走手机验证码*/);
if (Code.OK !== result.code) {
switch (result.code) {
case Code.ERR_AUTH_INVALID_VERIFY_CODE :
this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL,
CastError.SUB_VERIFY_CODE_INVALID_FAIL));
break;
default:
break;
}
return null;
}
SDKLog.e(this.TAG, "login: " + result);
let user: LoginUserVO = result.data!.user;
this.subscribeAccountTopic(user);
return user;
}
/**
* 阻塞方法,控制终端登录账号后,将access token存储下来,每次启动如果本地有access token,则调用此方法
* 此方法如果未正常返回,则说明需要通过UI引导用户重新登录,刷新access token
* 返回值为空意味着刷新失败,请检查错误信息
* @param accessToken: 控制端登录账号成功后拿到的唯一令牌
* @returns LoginUserVO | null 登录成功后返回用户信息,失败则返回null
*/
async refresh(accessToken: string): Promise<LoginUserVO | null> {
let result: Result<LoginUserResponse> = await this.httpRequestExecutor.refresh(this.guid,
accessToken);
if (Code.OK !== result.code) {
switch (result.code) {
case Code.ERR_AUTH_INVALID_TOKEN :
this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL,
CastError.SUB_USER_TOKEN_INVALID_FAIL));
break;
case Code.ERR_USER_LOGIN_ACCOUNT_NOT_EXIST :
this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL,
CastError.SUB_ACCOUNT_NOT_EXIST_FAIL));
break;
default:
break;
}
return null;
}
SDKLog.e(this.TAG, "refresh: " + result);
let user: LoginUserVO = result.data!.user;
this.subscribeAccountTopic(user);
return user;
}
/**
* 阻塞方法,控制终端登录账号后,退出登录此账号
* @param user: 已登录账号的用户信息
* @returns boolean 退出成功返回true,否则返回false
*/
async logout(user: LoginUserVO): Promise<boolean> {
let result: Result<EmptyResponse> = await this.httpRequestExecutor.logout(user.accessToken);
if (Code.OK !== result.code) {
switch (result.code) {
case Code.ERR_AUTH_INVALID_TOKEN :
this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL,
CastError.SUB_USER_TOKEN_INVALID_FAIL));
break;
case Code.ERR_USER_LOGIN_ACCOUNT_NOT_EXIST :
this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL,
CastError.SUB_ACCOUNT_NOT_EXIST_FAIL));
break;
default:
break;
}
return false;
}
SDKLog.e(this.TAG, "logout: " + result);
this.unsubscribeAccountTopic(user);
return true;
}
/**
* 阻塞方法,控制终端注销指定账号(已登录的账号)
* @param user: 已登录账号的用户信息
* @returns boolean 注销成功返回true,否则返回false
*/
async unRegister(user: LoginUserVO): Promise<boolean> {
let result: Result<EmptyResponse> = await this.httpRequestExecutor.unRegister(user.accessToken);
if (Code.OK !== result.code) {
switch (result.code) {
case Code.ERR_AUTH_INVALID_TOKEN :
this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL,
CastError.SUB_USER_TOKEN_INVALID_FAIL));
break;
case Code.ERR_USER_LOGIN_ACCOUNT_NOT_EXIST :
this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL,
CastError.SUB_ACCOUNT_NOT_EXIST_FAIL));
break;
default:
break;
}
return false;
}
SDKLog.e(this.TAG, "unRegister: " + result);
this.unsubscribeAccountTopic(user);
return true;
}
/**
* 阻塞方法,控制终端登录账号后,获取此账号下所有已绑定的显示设备
* @param accessToken: 控制端登录账号成功后拿到的唯一令牌
* @returns AccountBindDeviceVO[] | null 已绑定的显示设备列表,失败则返回null
*/
async getAllBoundDevices(accessToken: string): Promise<AccountBindDeviceVO[] | null> {
let result: Result<AccountBindDeviceResponse> = await this.httpRequestExecutor.getAllBoundDevices(accessToken);
if (Code.OK !== result.code) {
switch (result.code) {
case Code.ERR_AUTH_INVALID_TOKEN :
this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL,
CastError.SUB_USER_TOKEN_INVALID_FAIL));
break;
default:
break;
}
return null;
}
SDKLog.e(this.TAG, "getAllBoundDevices: " + result);
return result.data!.boundDisplayDevices;
}
/**
* 阻塞方法,控制终端登录账号后,对指定显示终端与本账号建立绑定关系
* @param accessToken: 控制端登录账号成功后拿到的唯一令牌
* @param displayDeviceId 显示终端设备ID
* @param alias 显示终端设备别名
* @returns AccountBindDeviceVO | null 绑定的显示设备,失败则返回null
*/
async bindDisplay(displayDeviceId: string, alias: string, accessToken: string): Promise<AccountBindDeviceVO | null> {
let result: Result<AccountBindDeviceResponse> = await this.httpRequestExecutor.bindDisplay(displayDeviceId, alias, accessToken);
if (Code.OK !== result.code) {
switch (result.code) {
case Code.ERR_AUTH_INVALID_TOKEN :
this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL,
CastError.SUB_USER_TOKEN_INVALID_FAIL));
break;
case Code.ERR_DEVICE_NOT_EXIST :
this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL,
CastError.SUB_BIND_DISPLAY_NOT_EXIST_FAIL));
break;
case Code.ERR_DEVICE_ALREADY_BOUND :
this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL,
CastError.SUB_BIND_DISPLAY_ALREADY_BOUND_FAIL));
break;
default:
break;
}
return null;
}
SDKLog.e(this.TAG, "bindDisplay: " + result);
for (let device of result.data!.boundDisplayDevices) {
if (device.displayDevice.displayDeviceId === displayDeviceId) {
SDKLog.e(this.TAG, "bindDisplay, deivce: " + device);
return device
}
}
return null;
}
/**
* 阻塞方法,控制终端登录账号后,对指定的已绑定的显示设备进行更新,更新其别名(UI展示名称)
* @param accessToken: 控制端登录账号成功后拿到的唯一令牌
* @param alias 显示终端设备别名
* @param displayDeviceId 显示终端设备ID
* @returns AccountBindDeviceVO | null 绑定的显示设备,失败则返回null
*/
async updateBindDisplayAlias(displayDeviceId: string, alias: string, accessToken: string): Promise<AccountBindDeviceVO | null> {
let result: Result<AccountBindDeviceResponse> = await this.httpRequestExecutor.updateBindDisplayAlias(displayDeviceId, alias, accessToken);
if (Code.OK !== result.code) {
switch (result.code) {
case Code.ERR_AUTH_INVALID_TOKEN :
this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL,
CastError.SUB_USER_TOKEN_INVALID_FAIL));
break;
case Code.ERR_DEVICE_NOT_EXIST :
this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL,
CastError.SUB_BIND_DISPLAY_NOT_EXIST_FAIL));
break;
case Code.ERR_DEVICE_NOT_BOUND :
this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL,
CastError.SUB_BIND_DISPLAY_NOT_BOUND_FAIL));
break;
default:
break;
}
return null;
}
SDKLog.e(this.TAG, "updateBindDisplayAlias: " + result);
for (let device of result.data!.boundDisplayDevices) {
if (device.displayDevice.displayDeviceId === displayDeviceId) {
SDKLog.e(this.TAG, "updateBindDisplayAlias, deivce: " + device);
return device;
}
}
return null;
}
/**
* 阻塞方法,控制终端登录账号后,解除指定显示终端与此账号之间的绑定关系
* @param accessToken: 控制端登录账号成功后拿到的唯一令牌
* @param displayDeviceId 显示终端设备ID
* @returns 解除绑定成功返回true,失败则返回false
*/
async unBindDisplay(displayDeviceId: string, accessToken: string): Promise<boolean> {
let result: Result<EmptyResponse> = await this.httpRequestExecutor.unBindDisplay(displayDeviceId, accessToken);
if (Code.OK !== result.code) {
switch (result.code) {
case Code.ERR_AUTH_INVALID_TOKEN :
this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL,
CastError.SUB_USER_TOKEN_INVALID_FAIL));
break;
case Code.ERR_DEVICE_NOT_BOUND :
this.callback?.onError(CastError.create(CastError.MAIN_ACCOUNT_FAIL,
CastError.SUB_BIND_DISPLAY_NOT_EXIST_FAIL));
break;
default:
break;
}
return false;
}
SDKLog.e(this.TAG, "unBindDisplay: " + result);
return true;
}
async connectDisplay(displayCode: number): Promise<DevicePair | null> {
let temp = this.cache.getControllerDevice(this.guid);
let result: Result<ConnectDisplayResponse> = await this.httpRequestExecutor.connectDisplay(displayCode, temp!);
if (Code.OK !== result.code) {
this.callback?.onError(result.msg);
this.callback?.onError(CastError.create(CastError.CONNECT_FAIL, CastError.WAN_CONNECT_FAIL));
return null;
}
let report: DisplayReportVO = DisplayReportVO.copy(result.data!.displayReport);
this.cache.addConnectedDisplay(this.guid, report.displayDevice.displayCode, report);
temp!.outIp = result.data!.outIp;
this.cache.addControllerDevice(this.guid, temp!);
this.mqttLogic?.connect(report.displayDevice.displayCode);
this.callback?.onDisplayConnected(report.displayDevice, this.cache.getControllerDevice(this.guid)!);
//正式服不支持Retained消息,因此SDK在connect后单独触发一下
this.callback?.onDisplayReport(report);
return new DevicePair(report, this.cache.getControllerDevice(this.guid)!);
}
async disconnectDisplay(displayCode: number): Promise<DisplayDeviceVO | null> {
let result: Result<EmptyResponse> = await this.httpRequestExecutor.disconnectDisplay(displayCode,
this.cache.getControllerDevice(this.guid)!);
if (Code.OK !== result.code) {
this.callback?.onError(result.msg);
this.callback?.onError(CastError.create(CastError.DISCONNECT_FAIL, CastError.DISCONNECT_FAIL));
return null;
}
await this.mqttLogic?.disconnect(displayCode);
let r = this.cache.getConnectedDisplayByID(this.guid);
if (null === r) {
SDKLog.e(this.TAG, "disconnectDisplay error: " + displayCode + " not in cache");
} else {
this.cache.removeConnectedDisplay(this.guid, displayCode);
}
this.callback?.onDisplayDisconnected(r!.displayDevice);
return r!.displayDevice;
}
async createCastSession(castType: CastType, protocol: CastProtocol, accessToken?: string): Promise<CastSessionVO | null> {
let r = this.cache.getConnectedDisplayByID(this.guid);
if (null === r) {
SDKLog.e(this.TAG, "createCastSession error: controller not connect to any display");
return null;
}
let result: Result<CastSessionResponse> = await this.httpRequestExecutor.createCastSession(
r.displayDevice.displayCode, this.guid, castType, protocol, accessToken
);
if (Code.OK !== result.code) {
this.callback?.onError(result.msg);
this.callback?.onError(CastError.create(CastError.TRANSMIT_FAIL, CastError.WAN_TRANSMIT_SESSION_CREATE_FAIL));
return null;
}
let s: CastSessionVO = new CastSessionVO();
s.castSessionId = result.data!.castSessionId;;
s.castType = castType;
s.castProtocol = protocol;
s.guid = this.guid;
s.displayCode = r!.displayDevice.displayCode;
this.cache.addCastSession(s.castSessionId, s);
return s;
}
/**
* 投屏会话接收端通知发送端自己已经做好准备,发送端可以准备开始往指定的投屏会话推流
* 会触发发送端的 onReadyToPush 函数
* @param sessionId 要推流的投屏会话id
*/
readyToPush(sessionId: number): void {
let session: CastSessionVO | null = this.cache.getCastSession(sessionId);
if (null === session) {
SDKLog.e(this.TAG, "readyToPush: session not found");
return;
}
this.sendCastSessionMessage(session, SessionAction.CONFIG, this.generateMediaPayload(session, ""));
}
startPush(sessionId: number, mediaPayload: string): void {
let session: CastSessionVO | null = this.cache.getCastSession(sessionId);
if (null === session) {
SDKLog.e(this.TAG, "startPush: session not found");
return;
}
this.sendCastSessionMessage(session, SessionAction.START, this.generateMediaPayload(session, mediaPayload));
}
stopPush(sessionId: number): void {
let session: CastSessionVO | null = this.cache.getCastSession(sessionId);
if (null === session) {
SDKLog.e(this.TAG, "stopPush: session not found");
return;
}
this.sendCastSessionMessage(session, SessionAction.STOP, "");
}
private generateMediaPayload(session: CastSessionVO, mediaPayload: string): string {
let s: string = "";
if (CastProtocol.P2P === session.castProtocol) {
s = this.jsonParser.toJson(this.p2pKeyFromPeer);
} else if (CastProtocol.NRTC === session.castProtocol) {
s = session.castSessionId + "";
} else if (CastProtocol.HTTP === session.castProtocol) {
s = mediaPayload;
}
return s;
}
private sendCastSessionMessage(session: CastSessionVO, action: SessionAction, mediaPayload: string): void {
let r = this.cache.getConnectedDisplayByID(this.guid);
if (null === r) {
SDKLog.e(this.TAG, "sendCastSessionMessage error: we have not connect display in cache");
return;
}
let message: CastSessionMessage = new CastSessionMessage(MsgClientType.CONTROLLER, MsgClientType.DISPLAY);
session.controllerDevice = this.cache.getControllerDevice(this.guid!)!;
message.castSession = session;
message.action = action;
message.mediaPayload = mediaPayload;
this.mqttLogic?.publish(r!.displayDevice.displayCode, message);
}
/**
* 主动销毁投屏会话,携带销毁原因
* @param sessionId 试图销毁的投屏会话id
* @param reason 销毁原因,可选
* @returns 如果会话成功被销毁则返回会话数据类,否则返回null
*/
async destroyCastSession(sessionId: number, reason?: SessionDestroyReason): Promise<CastSessionVO | null> {
let result: Result<EmptyResponse> = await this.httpRequestExecutor.destroyCastSession(sessionId, reason?? SessionDestroyReason.CONTROLLER_NORMAL_FINISH);
if (Code.OK !== result.code) {
this.callback?.onError(result.msg);
this.callback?.onError(CastError.create(CastError.TRANSMIT_FAIL, CastError.WAN_TRANSMIT_SESSION_DESTROY_FAIL));
return null;
}
let s: CastSessionVO | null = this.cache.getCastSession(sessionId);
this.cache.removeCastSession(sessionId);
return s;
}
async destroyAllCastSessions(): Promise<void> {
let all: IterableIterator<CastSessionVO> = this.cache.castSessionValues();
for (let session of all) {
await this.destroyCastSession(session.castSessionId);
};
}
/**
* 阻塞方法,控制终端主动告知显示端,当前投屏会话出现异常需要处理(停推、停收、销毁)
* @param sessionId 异常投屏会话id
* @param error 异常原因
*/
castSessionError(sessionId: number, error: string | CastError): void {
let session: CastSessionVO | null = this.cache.getCastSession(sessionId);
if (null == session) {
SDKLog.e(this.TAG, "castSessionError: session not found");
return;
}
if (typeof error === 'string') {
this.sendCastSessionMessage(session, SessionAction.ERROR, error);
} else {
this.sendCastSessionMessage(session, SessionAction.ERROR, this.jsonParser.toJson(error));
}
}
sendPrivateData(data: string): void {
let r = this.cache.getConnectedDisplayByID(this.guid);
if (null === r) {
SDKLog.e(this.TAG, "sendPrivateData error: we have not connect display in cache");
return;
}
let message: DataMessage = new DataMessage();
message.data = data;
this.mqttLogic?.publish(r!.displayDevice.displayCode, message);
}
getP2PKey(): P2PKeyVO | null {
// 目前的网页端无法使用供应商的p2p sdk,所以网页发起的所有投屏方式都不能走p2p
return null;
}
onServerDied(): void {
let result: Result<EmptyResponse> = new Result<EmptyResponse>();
result.code = Code.ERR;
result.msg = "server died!!";
this.callback?.onError(result.msg);
this.callback?.onError(CastError.create(CastError.CONNECT_PEER_OFFLINE, CastError.WAN_CONNECT_SERVER_OFFLINE));
}
onDisplayDied(): void {
SDKLog.i(this.TAG, "onDisplayDied");
let r = this.cache.getConnectedDisplayByID(this.guid);
if (null !== r) {
this.cache.removeOnlineDisplay(r!.displayDevice.displayDeviceId, r!.displayDevice.displayCode);
this.cache.removeConnectedDisplay(this.guid, r!.displayDevice.displayCode);
this.callback?.onDisplayOffline(r!.displayDevice);
} else {
SDKLog.e(this.TAG, "onDisplayDied error: we have not connected to this display");
}
}
onMySelfDisconnected(cause: string): void {
this.callback?.onMySelfDisconnected(cause);
}
onDisplayReport(report: DisplayReportVO): void {
SDKLog.i(this.TAG, "onDisplayReport: " + report);
let r = this.cache.getOnlineDisplayByID(report.displayDevice.displayDeviceId);
if (null !== r) {
let prevIsDisplayOnline = r.isDisplayDeviceOnline;
r = DisplayReportVO.copy(report);
this.cache.addOnlineDisplay(r.displayDevice.displayDeviceId, r.displayDevice.displayCode, r);
this.callback?.onDisplayReport(report);
if (prevIsDisplayOnline && !r.isDisplayDeviceOnline) {
// 从在线到离线,一定是display主动调用了displayOffline,因此主动触发onDisplayOffline
SDKLog.d(this.TAG, "onDisplayReport, display device call displayOffline: " + report);
this.callback?.onDisplayOffline(r.displayDevice);
}
} else {
SDKLog.e(this.TAG, "onDisplayDied error: we have not connected to this display");
}
}
onCastSession(session: CastSessionVO, action: SessionAction, mediaPayload: string, reason?: SessionDestroyReason): void {
if (this.guid != session.guid) {
SDKLog.e(this.TAG, "onCastSession error: guid not equal, except: " + this.guid + ", actual: " + session.guid);
return;
}
if (null === this.cache.getConnectedDisplayByID(this.guid)) {
SDKLog.e(this.TAG, "onCastSession error: we have not connected to any display");
return;
}
if (this.cache.getConnectedDisplayByID(this.guid)!.displayDevice.displayCode
!= session.displayCode) {
SDKLog.e(this.TAG, "onCastSession error: session display not equal connected display");
return;
}
switch (action) {
case SessionAction.CREATE:
this.cache.addCastSession(session.castSessionId, session);
this.callback?.onCastSessionCreate(session);
break;
case SessionAction.CONFIG:
// 目前的网页端无法使用供应商的p2p sdk,所以网页发起的所有投屏方式都不能走p2p
// 也就不需要做任何P2PKEY解析处理
switch (session.castType) {
case CastType.MIX_CAST:
case CastType.DOCUMENT:
this.callback?.onReadyToPush(session, mediaPayload);
break;
}
break;
case SessionAction.START:
// 目前的网页端无法使用供应商的p2p sdk,所以网页发起的所有投屏方式都不能走p2p
// 也就不需要做任何P2PKEY解析处理
this.callback?.onCastSessionPushStart(session, mediaPayload);
break;
case SessionAction.STOP:
this.callback?.onCastSessionPushEnd(session);
break;
case SessionAction.DESTROY:
this.cache.removeCastSession(session.castSessionId);
this.callback?.onCastSessionDestroy(session, reason?? SessionDestroyReason.NONE);
break;
case SessionAction.ERROR:
try {
let tmp: any = this.jsonParser.fromJson(mediaPayload);
if (this.isValidMyDataType(tmp)) {
this.callback?.onError(CastError.create(tmp.main, tmp.sub));
} else {
this.callback?.onError(mediaPayload);
}
} catch (error) {
SDKLog.e(this.TAG, `Failed to parse JSON: ${error}`);
this.callback?.onError(mediaPayload);
}
break;
}
}
private readonly isValidMyDataType = (data: any): data is CastError => {
return (
typeof data === "object" &&
data !== null &&
typeof data.main === "number" &&
typeof data.sub === "number"
);
};
onUserHasBeenKicked(user: LoginUserVO): void {
SDKLog.i(this.TAG, "onUserHasBeenKicked: " + user);
this.callback?.onUserHasBeenKicked(user);
}
onAccountBindDevice(accountBindDevice: AccountBindDeviceVO, action: BindDeviceAction): void {
SDKLog.i(this.TAG, "onAccountBindDevice: " + accountBindDevice + ", action: " + action);
if (BindDeviceAction.DEVICE_ONLINE == action) {
this.callback?.onAccountBindDeviceOnline(accountBindDevice);
} else if (BindDeviceAction.DEVICE_OFFLINE == action) {
this.callback?.onAccountBindDeviceOffline(accountBindDevice);
}
}
}