UNPKG

yimultiscreenserver-sdk-web

Version:

YiMultiScreenServer SDK for Web

597 lines 27.2 kB
import { GlobalCache } from "../core/GlobalCache"; import { HttpRequestExecutor } from "../core/http/HttpRequestExecutor"; import { Code } from "../core/http/response/Code"; import { Result } from "../core/http/response/Result"; import { JsonParser } from "../core/json/JsonParser"; 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 { CastProtocol } from "../vo/cast/CastProtocol"; import { CastSessionVO } from "../vo/cast/CastSessionVO"; import { CastType } from "../vo/cast/CastType"; import { DevicePair } from "../vo/device/DevicePair"; import { P2PKeyVO } from "../vo/p2p/P2PKeyVO"; import { DisplayReportVO } from "../vo/report/DisplayReportVO"; import { SessionDestroyReason } from '../core/mqtt/message/SessionDestroyReason'; export class ControllerManager { constructor() { this.TAG = "ControllerManager"; this.isValidMyDataType = (data) => { return (typeof data === "object" && data !== null && typeof data.main === "number" && typeof data.sub === "number"); }; this.p2pKeyFromPeer = new P2PKeyVO(); this.cache = new GlobalCache(); this.httpRequestExecutor = new HttpRequestExecutor(); this.jsonParser = new JsonParser(); this.guid = "fake-guid"; } setGuid(guid) { this.guid = guid; return this; } setCallback(callback) { this.callback = callback; return this; } setHttpRequester(requester) { this.httpRequestExecutor.setHttpRequester(requester); return this; } setHttpConfig(config) { this.httpRequestExecutor.setHTTPConfig(config); return this; } setMQTTLib(mqtt) { this.mqttLogic = mqtt; return this; } createControllerDevice(controllerDevice) { this.cache.addControllerDevice(controllerDevice.guid, controllerDevice); return this; } updateControllerType(type) { let cd = this.cache.getControllerDevice(this.guid); cd.controllerDeviceType = type; this.cache.addControllerDevice(this.guid, cd); return this; } updateNetwork(ip, gateway, mask) { 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) { let result = 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; } subscribeAccountTopic(user) { let mqtt = this.mqttLogic; mqtt.subscribeAccountTopic(user.account.accountId); } unsubscribeAccountTopic(user) { let mqtt = this.mqttLogic; mqtt.unsubscribeAccountTopic(user.account.accountId); } /** * 阻塞方法,控制终端通过手机号和短信验证码登录账号,如果账号不存在则自动注册 * 返回值为空意味着登录失败,请检查错误信息 * @param phoneNumber 手机号 * @param verifyCode 短信验证码 * @returns LoginUserVO | null 登录成功后返回用户信息,失败则返回null */ async login(phoneNumber, verifyCode) { let temp = this.cache.getControllerDevice(this.guid); let result = 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 = result.data.user; this.subscribeAccountTopic(user); return user; } /** * 阻塞方法,控制终端登录账号后,将access token存储下来,每次启动如果本地有access token,则调用此方法 * 此方法如果未正常返回,则说明需要通过UI引导用户重新登录,刷新access token * 返回值为空意味着刷新失败,请检查错误信息 * @param accessToken: 控制端登录账号成功后拿到的唯一令牌 * @returns LoginUserVO | null 登录成功后返回用户信息,失败则返回null */ async refresh(accessToken) { let result = 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 = result.data.user; this.subscribeAccountTopic(user); return user; } /** * 阻塞方法,控制终端登录账号后,退出登录此账号 * @param user: 已登录账号的用户信息 * @returns boolean 退出成功返回true,否则返回false */ async logout(user) { let result = 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) { let result = 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) { let result = 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, alias, accessToken) { let result = 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, alias, accessToken) { let result = 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, accessToken) { let result = 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) { let temp = this.cache.getControllerDevice(this.guid); let result = 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.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) { let result = 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, protocol, accessToken) { 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 = 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 = 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) { let session = 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, mediaPayload) { let session = 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) { let session = this.cache.getCastSession(sessionId); if (null === session) { SDKLog.e(this.TAG, "stopPush: session not found"); return; } this.sendCastSessionMessage(session, SessionAction.STOP, ""); } generateMediaPayload(session, mediaPayload) { let s = ""; 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; } sendCastSessionMessage(session, action, mediaPayload) { 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 = 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, reason) { let result = 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 = this.cache.getCastSession(sessionId); this.cache.removeCastSession(sessionId); return s; } async destroyAllCastSessions() { let all = this.cache.castSessionValues(); for (let session of all) { await this.destroyCastSession(session.castSessionId); } ; } /** * 阻塞方法,控制终端主动告知显示端,当前投屏会话出现异常需要处理(停推、停收、销毁) * @param sessionId 异常投屏会话id * @param error 异常原因 */ castSessionError(sessionId, error) { let session = 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) { 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 = new DataMessage(); message.data = data; this.mqttLogic?.publish(r.displayDevice.displayCode, message); } getP2PKey() { // 目前的网页端无法使用供应商的p2p sdk,所以网页发起的所有投屏方式都不能走p2p return null; } onServerDied() { let result = new Result(); 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() { 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) { this.callback?.onMySelfDisconnected(cause); } onDisplayReport(report) { 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, action, mediaPayload, reason) { 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 = 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; } } onUserHasBeenKicked(user) { SDKLog.i(this.TAG, "onUserHasBeenKicked: " + user); this.callback?.onUserHasBeenKicked(user); } onAccountBindDevice(accountBindDevice, action) { 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); } } } //# sourceMappingURL=ControllerManager.js.map