API目錄
- KY_registerSDKListener - 注冊(cè)IOTC回調(diào)
- KY_unRegisterSDKListener - 注銷IOTC回調(diào)
- KY_receiveBindInfo - Nebula綁定結(jié)果回調(diào)
- KY_DeviceStatus - 指定通道的連線狀態(tài)回調(diào)
- KY_UpdateDecodedImage - 視頻解碼畫面更新回調(diào)
- KY_DecodeVideoFramInfo - 視頻幀信息回調(diào)
- KY_ReceiveFrameData - 視頻幀數(shù)據(jù)回調(diào)
- KY_ReceiveAudioData - 音頻幀數(shù)據(jù)回調(diào)
- KY_DidReceiveIOCtrlWithUid - Command回復(fù)數(shù)據(jù)回調(diào)
- KY_DidReceiveNebulaCtrlWithUid - Nebula Command回復(fù)回調(diào)
- KY_DownloadUploadOutput - 文件上傳下載進(jìn)度回調(diào)
回調(diào)接口模塊介紹
回調(diào)接口是TUTK P2P SDK針對(duì)Android平臺(tái)提供的核心回調(diào)集,用于接收設(shè)備綁定狀態(tài)、連線狀態(tài)、音視頻數(shù)據(jù)、指令回復(fù)、文件傳輸進(jìn)度等關(guān)鍵事件通知,是APP與設(shè)備之間異步通信的核心方式。所有回調(diào)需先通過KY_registerSDKListener注冊(cè)監(jiān)聽后才能生效。
KY_registerSDKListener
功能描述:注冊(cè)IOTC回調(diào),是接收所有SDK回調(diào)事件的前提,需在使用其他回調(diào)接口前調(diào)用。
接口定義
public boolean KY_registerSDKListener(InterfaceCtrl.KY_SDKListener listener);
參數(shù)說明
| 參數(shù) | 類型 | 說明 |
|---|---|---|
| listener | InterfaceCtrl.KY_SDKListener | SDK回調(diào)監(jiān)聽器對(duì)象,需實(shí)現(xiàn)所有回調(diào)方法 |
回調(diào)說明
無額外回調(diào),注冊(cè)結(jié)果通過接口返回值直接返回
返回碼
- true: 注冊(cè)成功
- false: 注冊(cè)失敗
代碼示例
// 實(shí)現(xiàn)SDK回調(diào)監(jiān)聽器
InterfaceCtrl.KY_SDKListener sdkListener = new InterfaceCtrl.KY_SDKListener() {
@Override
public void KY_receiveBindInfo(String uid, String credential, int KYDeviceState, int errorCode) {
// 處理綁定結(jié)果回調(diào)
}
@Override
public void KY_DeviceStatus(String uid, int channel, int state) {
// 處理設(shè)備狀態(tài)回調(diào)
}
// 實(shí)現(xiàn)其他回調(diào)方法...
};
// 注冊(cè)SDK回調(diào)
boolean isRegistered = InterfaceCtrl.KY_registerSDKListener(sdkListener);
if (isRegistered) {
Log.d("TUTK", "SDK回調(diào)注冊(cè)成功");
} else {
Log.d("TUTK", "SDK回調(diào)注冊(cè)失敗");
}
KY_unRegisterSDKListener
功能描述:注銷IOTC回調(diào),停止接收所有SDK回調(diào)事件,建議在頁(yè)面銷毀或不再需要回調(diào)時(shí)調(diào)用。
接口定義
public boolean KY_unRegisterSDKListener(InterfaceCtrl.KY_SDKListener listener);
參數(shù)說明
| 參數(shù) | 類型 | 說明 |
|---|---|---|
| listener | InterfaceCtrl.KY_SDKListener | 已注冊(cè)的SDK回調(diào)監(jiān)聽器對(duì)象 |
回調(diào)說明
無額外回調(diào),注銷結(jié)果通過接口返回值直接返回
返回碼
- true: 注銷成功
- false: 注銷失敗
代碼示例
// 注銷SDK回調(diào)
boolean isUnregistered = InterfaceCtrl.KY_unRegisterSDKListener(sdkListener);
if (isUnregistered) {
Log.d("TUTK", "SDK回調(diào)注銷成功");
} else {
Log.d("TUTK", "SDK回調(diào)注銷失敗");
}
KY_receiveBindInfo
功能描述:Nebula綁定結(jié)果回調(diào)。當(dāng)調(diào)用 KY_nebulaStartBind 進(jìn)行設(shè)備綁定時(shí),此接口會(huì)回調(diào)綁定結(jié)果。
接口定義
void KY_receiveBindInfo(String uid, String credential, int KYDeviceState, int errorCode);
參數(shù)說明
| 參數(shù) | 類型 | 說明 |
|---|---|---|
| uid | string | 當(dāng)前設(shè)備的uid |
| credential | string | 綁定設(shè)備產(chǎn)生的credential |
| KYDeviceState | int | 綁定狀態(tài) |
| errorCode | int | P2P SDK錯(cuò)誤碼 |
回調(diào)說明
綁定操作完成后自動(dòng)觸發(fā)此回調(diào),無需主動(dòng)調(diào)用
返回碼
錯(cuò)誤碼參考TUTK P2P SDK官方錯(cuò)誤碼文檔,0表示綁定成功
代碼示例
@Override
public void KY_receiveBindInfo(String uid, String credential, int KYDeviceState, int errorCode) {
if (errorCode == 0) {
Log.d("TUTK", "設(shè)備綁定成功,UID: " + uid + ", credential: " + credential);
// 保存綁定憑證,用于后續(xù)設(shè)備連接
saveCredential(uid, credential);
} else {
Log.e("TUTK", "設(shè)備綁定失敗,錯(cuò)誤碼: " + errorCode);
// 處理綁定失敗邏輯
handleBindError(errorCode);
}
}
KY_DeviceStatus
功能描述:指定通道的連線狀態(tài)回調(diào)。當(dāng)調(diào)用 KY_Connect 進(jìn)行連線并出圖成功時(shí),此接口會(huì)回調(diào)設(shè)備連線狀態(tài)。
接口定義
void KY_DeviceStatus(String uid, int channel, int state);
狀態(tài)碼說明
KY_STATE_CONNECTING = 1: 連接中KY_STATE_CONNECTED = 2: 已連接KY_STATE_DISCONNECTED = 3: 已斷開KY_STATE_UNKNOWN_DEVICE = 4: 未知設(shè)備KY_STATE_WRONG_PASSWORD = 5: 密碼錯(cuò)誤KY_STATE_TIMEOUT = 6: 超時(shí)KY_STATE_UNSUPPORTED = 7: 不支持KY_STATE_CONNECT_FAILED = 8: 連接失敗KY_STATE_UNKNOWN_LICENSE = 9: 未知授權(quán)KY_STATE_SLEEP = 10: 設(shè)備休眠KY_STATE_DEVICE_MAX_SESSION = 11: 設(shè)備連接數(shù)已滿KY_STATE_POOR_NETWORK_SIGNAL = 12: 網(wǎng)絡(luò)信號(hào)差KY_STATE_WRONG_AUTH_KEY = 13: 認(rèn)證密鑰錯(cuò)誤KY_STATE_SELF_DISCONNECT = 14: 主動(dòng)斷開
參數(shù)說明
| 參數(shù) | 類型 | 說明 |
|---|---|---|
| uid | string | 當(dāng)前設(shè)備的uid |
| channel | int | 設(shè)備連線的channel,默認(rèn)為0 |
| state | int | 設(shè)備連線狀態(tài)碼,參考上方狀態(tài)碼說明 |
代碼示例
@Override
public void KY_DeviceStatus(String uid, int channel, int state) {
switch (state) {
case 1: // KY_STATE_CONNECTING
Log.d("TUTK", "設(shè)備" + uid + "正在連接中...");
break;
case 2: // KY_STATE_CONNECTED
Log.d("TUTK", "設(shè)備" + uid + "連接成功");
// 連接成功,開始出圖或其他操作
startPreview(uid);
break;
case 3: // KY_STATE_DISCONNECTED
Log.d("TUTK", "設(shè)備" + uid + "連接已斷開");
// 處理斷開連接邏輯
stopPreview(uid);
break;
default:
Log.e("TUTK", "設(shè)備" + uid + "連接失敗,狀態(tài)碼: " + state);
// 處理連接失敗邏輯
showConnectError(state);
break;
}
}
KY_UpdateDecodedImage
功能描述:視頻解碼畫面更新回調(diào)。當(dāng)視頻流解碼出一幀新圖像時(shí)觸發(fā)此回調(diào),用于同步視頻幀更新狀態(tài)。
接口定義
void KY_UpdateDecodedImage(String uid, int channel, long timestamp);
參數(shù)說明
| 參數(shù) | 類型 | 說明 |
|---|---|---|
| uid | string | 當(dāng)前設(shè)備的uid |
| channel | int | 設(shè)備連線的channel,默認(rèn)為0 |
| timestamp | long | 當(dāng)前數(shù)據(jù)的時(shí)間戳 |
回調(diào)說明
視頻解碼過程中實(shí)時(shí)觸發(fā),每解碼一幀圖像就會(huì)回調(diào)一次
代碼示例
@Override
public void KY_UpdateDecodedImage(String uid, int channel, long timestamp) {
Log.d("TUTK", "設(shè)備" + uid + "視頻幀更新,時(shí)間戳: " + timestamp);
// 更新視頻顯示畫面
updateVideoView(uid, channel);
// 統(tǒng)計(jì)幀率
calculateFps(uid, timestamp);
}
KY_DecodeVideoFramInfo
功能描述:視頻幀信息回調(diào)。提供當(dāng)前視頻流的詳細(xì)參數(shù),包括分辨率、幀率、碼率、解碼方式等關(guān)鍵信息。
接口定義
void KY_DecodeVideoFramInfo(String uid, int channel, int connectMode, int videoWidth, int videoHeight, int videoFPS, int bps, int onlineNm, int frameCount, int incompleteFrameCount, boolean isHwDecode, int sessionID, int avChannelIndex, int cmdNum, int cmdReturn);
參數(shù)說明
| 參數(shù) | 類型 | 說明 |
|---|---|---|
| uid | string | 當(dāng)前設(shè)備的uid |
| channel | int | 設(shè)備連線的channel,默認(rèn)為0 |
| connectMode | int | 連線模式 (-1:NONE, 0:P2P, 1:RELAY, 2:LAN) |
| videoWidth | int | 視頻寬 |
| videoHeight | int | 視頻高 |
| videoFPS | int | 幀率 (fps) |
| bps | int | 音視頻碼率 (bps) |
| onlineNm | int | 在線人數(shù) |
| frameCount | int | 接收幀數(shù) |
| incompleteFrameCount | int | 丟幀數(shù)或不完整幀數(shù) |
| isHwDecode | boolean | 是否是硬件解碼 |
| sessionID | int | 連線返回的session號(hào) |
| avChannelIndex | int | 開啟通道返回的av通道 |
| cmdNum | int | command指令類型值 |
| cmdReturn | int | command指令返回值 |
回調(diào)說明
視頻連接成功后周期性觸發(fā),用于監(jiān)控視頻流質(zhì)量和參數(shù)
代碼示例
@Override
public void KY_DecodeVideoFramInfo(String uid, int channel, int connectMode,
int videoWidth, int videoHeight, int videoFPS,
int bps, int onlineNm, int frameCount,
int incompleteFrameCount, boolean isHwDecode,
int sessionID, int avChannelIndex, int cmdNum, int cmdReturn) {
// 打印視頻流信息
Log.d("TUTK", String.format(
"設(shè)備%s視頻信息:分辨率%dx%d,幀率%dfps,碼率%dbps,解碼方式:%s",
uid, videoWidth, videoHeight, videoFPS, bps,
isHwDecode ? "硬件解碼" : "軟件解碼"
));
// 計(jì)算丟幀率
float lossRate = frameCount > 0 ? (float)incompleteFrameCount / frameCount * 100 : 0;
Log.d("TUTK", "丟幀率: " + String.format("%.2f", lossRate) + "%");
// 根據(jù)連線模式做特殊處理
String connectModeStr = getConnectModeString(connectMode);
Log.d("TUTK", "連線模式: " + connectModeStr);
}
// 轉(zhuǎn)換連線模式為字符串
private String getConnectModeString(int mode) {
switch (mode) {
case -1: return "NONE";
case 0: return "P2P";
case 1: return "RELAY";
case 2: return "LAN";
default: return "UNKNOWN";
}
}
KY_ReceiveFrameData
功能描述:獲取當(dāng)前設(shè)備指定通道解碼后的視頻幀數(shù)據(jù)回調(diào),可用于自定義視頻渲染或處理。
接口定義
void KY_ReceiveFrameData(String uid, int channel, AVFrame avFrame);
參數(shù)說明
| 參數(shù) | 類型 | 說明 |
|---|---|---|
| uid | string | 當(dāng)前設(shè)備的uid |
| channel | int | 設(shè)備連線的channel,默認(rèn)為0 |
| avFrame | AVFrame | 包含視頻數(shù)據(jù)(如YUV)和幀信息的對(duì)象 |
回調(diào)說明
視頻解碼后實(shí)時(shí)回調(diào)原始視頻幀數(shù)據(jù),適用于自定義渲染、截圖、錄制等場(chǎng)景
代碼示例
@Override
public void KY_ReceiveFrameData(String uid, int channel, AVFrame avFrame) {
if (avFrame == null) {
return;
}
// 獲取YUV數(shù)據(jù)
byte[] yData = avFrame.y;
byte[] uData = avFrame.u;
byte[] vData = avFrame.v;
// 獲取幀信息
int width = avFrame.width;
int height = avFrame.height;
long timestamp = avFrame.timestamp;
// 自定義渲染YUV數(shù)據(jù)到SurfaceView
renderYUVData(yData, uData, vData, width, height);
// 可選:保存視頻幀為圖片
if (needCaptureFrame) {
saveFrameToImage(avFrame, uid);
needCaptureFrame = false;
}
}
KY_ReceiveAudioData
功能描述:獲取當(dāng)前設(shè)備指定通道接收的音頻幀裸流數(shù)據(jù)及幀信息回調(diào)。當(dāng)調(diào)用 KY_StartListen 進(jìn)行監(jiān)聽時(shí),此接口會(huì)回調(diào)音頻數(shù)據(jù)。
接口定義
void KY_ReceiveAudioData(String uid, int channel, AVFrame avFrame);
參數(shù)說明
| 參數(shù) | 類型 | 說明 |
|---|---|---|
| uid | string | 當(dāng)前設(shè)備的uid |
| channel | int | 設(shè)備連線的channel,默認(rèn)為0 |
| avFrame | AVFrame | 包含音頻數(shù)據(jù)(如PCM)和幀信息的對(duì)象 |
回調(diào)說明
調(diào)用KY_StartListen開啟監(jiān)聽后實(shí)時(shí)回調(diào)音頻PCM數(shù)據(jù),需自行處理音頻播放
代碼示例
// 音頻播放器初始化
private AudioTrack audioTrack;
@Override
public void KY_ReceiveAudioData(String uid, int channel, AVFrame avFrame) {
if (avFrame == null || avFrame.data == null) {
return;
}
// 獲取PCM音頻數(shù)據(jù)
byte[] pcmData = avFrame.data;
int sampleRate = avFrame.sampleRate; // 采樣率,如8000, 16000等
int channels = avFrame.channels; // 聲道數(shù),1:單聲道,2:立體聲
int bitDepth = avFrame.bitDepth; // 位深,通常為16
// 初始化音頻播放器
if (audioTrack == null || !audioTrack.isPlaying()) {
initAudioTrack(sampleRate, channels, bitDepth);
}
// 播放PCM音頻數(shù)據(jù)
if (audioTrack != null && audioTrack.getState() == AudioTrack.STATE_INITIALIZED) {
audioTrack.write(pcmData, 0, pcmData.length);
}
}
// 初始化AudioTrack
private void initAudioTrack(int sampleRate, int channels, int bitDepth) {
int channelConfig = channels == 1 ? AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO;
int audioFormat = bitDepth == 16 ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
int bufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);
audioTrack = new AudioTrack(
AudioManager.STREAM_MUSIC,
sampleRate,
channelConfig,
audioFormat,
bufferSize * 2,
AudioTrack.MODE_STREAM
);
audioTrack.play();
}
KY_DidReceiveIOCtrlWithUid
功能描述:獲取設(shè)備回復(fù)Command的數(shù)據(jù)信息回調(diào)。當(dāng)調(diào)用 KY_SendIOCtrlToChannel 發(fā)送Command時(shí),此接口會(huì)回調(diào)設(shè)備的回復(fù)數(shù)據(jù)。
接口定義
void KY_DidReceiveIOCtrlWithUid(String uid, int channel, int type, byte[] data, int dataSize);
參數(shù)說明
| 參數(shù) | 類型 | 說明 |
|---|---|---|
| uid | string | 當(dāng)前接收Command的uid |
| channel | int | 設(shè)備連線的channel,默認(rèn)為0 |
| type | int | command指令值 |
| data | byte[] | command對(duì)應(yīng)指令的結(jié)構(gòu)體數(shù)據(jù) |
| dataSize | int | command對(duì)應(yīng)指令的結(jié)構(gòu)體數(shù)據(jù)長(zhǎng)度 |
回調(diào)說明
發(fā)送Command指令后,設(shè)備回復(fù)時(shí)觸發(fā)此回調(diào),需根據(jù)指令類型解析二進(jìn)制數(shù)據(jù)
代碼示例
@Override
public void KY_DidReceiveIOCtrlWithUid(String uid, int channel, int type, byte[] data, int dataSize) {
Log.d("TUTK", "收到設(shè)備" + uid + "的Command回復(fù),指令類型: " + type);
// 根據(jù)指令類型解析數(shù)據(jù)
switch (type) {
case CMD_GET_DEVICE_INFO: // 假設(shè)這是獲取設(shè)備信息的指令類型
// 解析設(shè)備信息結(jié)構(gòu)體
DeviceInfo info = parseDeviceInfo(data, dataSize);
Log.d("TUTK", "設(shè)備名稱: " + info.deviceName + ", 固件版本: " + info.firmwareVersion);
break;
case CMD_GET_SD_CARD_INFO: // 獲取SD卡信息
SDCardInfo sdInfo = parseSDCardInfo(data, dataSize);
Log.d("TUTK", "SD卡容量: " + sdInfo.totalSize + "MB, 可用: " + sdInfo.freeSize + "MB");
break;
default:
// 其他指令類型解析
Log.d("TUTK", "未處理的指令類型: " + type);
break;
}
}
// 示例:解析設(shè)備信息
private DeviceInfo parseDeviceInfo(byte[] data, int dataSize) {
DeviceInfo info = new DeviceInfo();
if (data != null && dataSize > 0) {
// 按結(jié)構(gòu)體格式解析二進(jìn)制數(shù)據(jù)
// 示例:假設(shè)前32字節(jié)是設(shè)備名稱,接下來4字節(jié)是固件版本號(hào)
ByteBuffer buffer = ByteBuffer.wrap(data);
buffer.order(ByteOrder.LITTLE_ENDIAN);
byte[] nameBytes = new byte[32];
buffer.get(nameBytes);
info.deviceName = new String(nameBytes).trim();
info.firmwareVersion = buffer.getInt();
}
return info;
}
KY_DidReceiveNebulaCtrlWithUid
功能描述:獲取設(shè)備回復(fù)Nebula Command的數(shù)據(jù)信息回調(diào)。當(dāng)調(diào)用 KY_nebulaSendData 發(fā)送Nebula Command時(shí),此接口會(huì)回調(diào)設(shè)備的回復(fù)數(shù)據(jù)。
接口定義
void KY_DidReceiveNebulaCtrlWithUid(String uid, int channel, String jsonRequest, String jsonResponse);
參數(shù)說明
| 參數(shù) | 類型 | 說明 |
|---|---|---|
| uid | string | 當(dāng)前接收Command的uid |
| channel | int | 設(shè)備連線的channel,默認(rèn)為0 |
| jsonRequest | string | 發(fā)送的nebula command指令 (JSON格式) |
| jsonResponse | string | 設(shè)備返回的nebula command響應(yīng) (JSON格式) |
回調(diào)說明
發(fā)送Nebula Command后,設(shè)備回復(fù)時(shí)觸發(fā)此回調(diào),JSON格式數(shù)據(jù)易于解析和處理
代碼示例
@Override
public void KY_DidReceiveNebulaCtrlWithUid(String uid, int channel, String jsonRequest, String jsonResponse) {
Log.d("TUTK", "設(shè)備" + uid + "Nebula指令回復(fù):");
Log.d("TUTK", "請(qǐng)求: " + jsonRequest);
Log.d("TUTK", "響應(yīng): " + jsonResponse);
try {
// 解析JSON響應(yīng)
JSONObject response = new JSONObject(jsonResponse);
// 獲取響應(yīng)狀態(tài)
int code = response.optInt("code", -1);
String msg = response.optString("msg", "");
if (code == 0) {
// 指令執(zhí)行成功
Log.d("TUTK", "Nebula指令執(zhí)行成功");
// 根據(jù)不同指令解析數(shù)據(jù)
JSONObject request = new JSONObject(jsonRequest);
String cmd = request.optString("cmd");
if ("device_info".equals(cmd)) {
// 解析設(shè)備信息
JSONObject data = response.optJSONObject("data");
String deviceModel = data.optString("model");
String firmwareVer = data.optString("firmware_version");
Log.d("TUTK", "設(shè)備型號(hào): " + deviceModel + ", 固件版本: " + firmwareVer);
} else if ("wifi_config".equals(cmd)) {
// 解析WiFi配置結(jié)果
boolean success = response.optBoolean("success");
Log.d("TUTK", "WiFi配置" + (success ? "成功" : "失敗"));
}
} else {
// 指令執(zhí)行失敗
Log.e("TUTK", "Nebula指令執(zhí)行失敗:" + msg + " (錯(cuò)誤碼: " + code + ")");
}
} catch (JSONException e) {
Log.e("TUTK", "JSON解析失敗", e);
}
}
KY_DownloadUploadOutput
功能描述:文件上傳、下載進(jìn)度回調(diào)。
- 連線成功之后,當(dāng)調(diào)用 KY_startDownload 開始文件下載后,回調(diào)下載進(jìn)度。
- 連線成功之后,當(dāng)調(diào)用 KY_startUpload 開始文件上傳后,回調(diào)上傳進(jìn)度。
接口定義
void KY_DownloadUploadOutput(Camera camera, int channel, String filePath, int progress, int loadType, int p2pCode);
參數(shù)說明
| 參數(shù) | 類型 | 說明 |
|---|---|---|
| camera | Camera | 綁定的camera對(duì)象 |
| channel | int | 通道號(hào) |
| filePath | string | 文件完整沙盒路徑 |
| progress | int | 上傳/下載進(jìn)度 (0-100) |
| loadType | int | 下載或上傳類型 (AVIOCTRLDEFs.FILETransferType_Download / AVIOCTRLDEFs.FILETransferType_Upload) |
| p2pCode | int | P2P SDK返回值 (0通常表示成功) |
回調(diào)說明
文件傳輸過程中周期性觸發(fā),進(jìn)度達(dá)到100表示傳輸完成;p2pCode非0表示傳輸失敗
代碼示例
// 進(jìn)度條控件
private ProgressDialog progressDialog;
@Override
public void KY_DownloadUploadOutput(Camera camera, int channel, String filePath, int progress, int loadType, int p2pCode) {
String fileName = new File(filePath).getName();
String transferType = loadType == AVIOCTRLDEFs.FILETransferType_Download ? "下載" : "上傳";
Log.d("TUTK", String.format("%s文件%s,進(jìn)度:%d%%,狀態(tài)碼:%d",
fileName, transferType, progress, p2pCode));
// 初始化進(jìn)度對(duì)話框
if (progressDialog == null) {
progressDialog = new ProgressDialog(context);
progressDialog.setTitle(transferType + "文件");
progressDialog.setMessage("正在" + transferType + ":" + fileName);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setCancelable(false);
progressDialog.show();
}
// 更新進(jìn)度
progressDialog.setProgress(progress);
// 傳輸完成或失敗
if (progress >= 100 || p2pCode != 0) {
progressDialog.dismiss();
progressDialog = null;
if (p2pCode == 0) {
Toast.makeText(context, fileName + transferType + "完成", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, fileName + transferType + "失敗,錯(cuò)誤碼:" + p2pCode, Toast.LENGTH_SHORT).show();
}
}
}
