yimultiscreenserver-sdk-web
Version:
YiMultiScreenServer SDK for Web
597 lines • 27.2 kB
JavaScript
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