日本熟妇hd丰满老熟妇,中文字幕一区二区三区在线不卡 ,亚洲成片在线观看,免费女同在线一区二区

數字人實時互動openAPI

版本變更

版本

描述

時間

v1.1

SDK版本升級

2024-09-10

v1.0

新增websocket實時互動鏈路

2024-09-09

v0.3

啟動會話接口新增webSocketUrl

2024-08-22

v0.2

添加pop接口sdk的maven依賴

2024-08-19

v0.1

功能發布

2024-07-01

概覽

交互示例

image

接入準備

  1. 需要接入方提前準備阿里云賬號,并利用阿里云子賬號生成對應的AK/SK;

  2. 阿里云主賬號需要對生成AK/SK的子賬號進行RAM授權;

  3. 使用阿里云主賬號登錄平臺,簽署相關法務協議。

API詳情

1. 查詢數字人項目信息

入參 QueryAvatarProjectRequest

參數名

類型

是否必填

說明

projectId

String

Y

項目ID

出參 QueryAvatarProjectResponse

參數名

類型

說明

status

String

啟動結果:DEPLOYING - 發布中

DEPLOYED - 已發布

DEPLOY_FAIL - 發布失敗

projectName

String

名稱

agentId

String

智能體id

errorMsg

String

發布失敗原因

2. 啟動會話

入參 StartAvatarSessionRequest

參數名

類型

是否必填

說明

projectId

String

Y

項目ID

requestId

String

Y

請求id用于冪等

出參 StartAvatarSessionResponse

參數名

類型

說明

sessionId

String

會話id

channelToken

String

頻道信息

webSocketUrl

String

流式交互鏈接

channelToken 字段解析

{
  "channelId":"123",//頻道ID
  "token":"", // 令牌
  "expireTime":600,//過期時間(單位秒)
  "nonce":"",//隨機數
  "userId":"",//用戶ID
  "appId":""http://應用ID
}

3. 停止會話

入參 StopAvatarSessionRequest

參數名

類型

是否必填

說明

projectId

String

Y

項目ID

sessionId

String

Y

會話ID

出參 StopAvatarSessionResponse

參數名

類型

說明

status

String

停止結果:Stopped - 已停止

StoppedFail - 停止失敗

4. 有效資源查詢

入參 QueryAvatarResourceRequest

參數名

類型

是否必填

說明

出參 QueryAvatarResourceResponse

參數名

類型

說明

queryResourceInfoList

List<QueryResourceInfo>

資源信息

QueryResourceInfo

參數名

類型

說明

resourceId

String

資源id

type

String

資源類型:

STANDARD - 2D數字人實時互動【基礎版】

ADVANCED - 2D數字人實時互動【高級版】

validPeriodTime

String

有效期(時間戳) 例如:1719904342237

WebSocket實時互動

WebSocket對接整體流程

  1. 開啟會話,獲取websocket鏈接和RTC channel信息;

  2. 建立websocket連接;

  3. 循環發送"通道準備"數據包,直至收到"通道準備完成";

  4. 使用RTC SDK和channel信息拉取視頻流;

  5. (可選)發送查詢開場白數據包,進行開場白互動;

  6. (可選)發送文本驅動數據包進行文本驅動數字人;

  7. (可選)發送音頻驅動頭包+數據包+尾包進行音頻驅動數字人;

  8. 停止會話

  9. 關閉websocket連接;

建立websocket連接代碼示例:

@ClientEndpoint
public class WebSocketClient {

    private Session userSession = null;

    public WebSocketClient(URI endpointURI) {
        try {
            WebSocketContainer container = ContainerProvider.getWebSocketContainer();
            container.connectToServer(this, endpointURI);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @OnOpen
    public void onOpen(Session userSession) {
        this.userSession = userSession;
        System.out.println("Connected to server");
    }

    @OnClose
    public void onClose(Session userSession, CloseReason reason) {
        this.userSession = null;
        System.out.println("Disconnected from server: " + reason);
    }

    @OnMessage
    public void onMessage(byte[] message) {

        //1. 獲取第1字節幀號
        int frame = Byte.toUnsignedInt(message[0]);

        //2. 獲取第2字節數據類型
        int dataType = Byte.toUnsignedInt(message[1]);

        //3. 獲取第3字節業務類型
        int bizType = Byte.toUnsignedInt(message[2]);

        //4. 獲取第4-11字節序列號
        byte[] serialNumberBytes = Arrays.copyOfRange(message, 3, 11);
        String serialNumberString = new String(serialNumberBytes, StandardCharsets.UTF_8);

        //5. 獲取剩余內容數據,數據結構需要結合具體業務類型文檔
        byte[] contentJsonBytes = Arrays.copyOfRange(message, 11, message.length);
        String contentJsonString = new String(contentJsonBytes, StandardCharsets.UTF_8);

        //6. 執行相應業務邏輯......
    }

    public void sendMessage(byte[] message) {
        try {
            if (this.userSession != null) {
                this.userSession.getBasicRemote().sendBinary(ByteBuffer.wrap(message));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {

        // 1. 建立ws連接
        WebSocketClient client = new WebSocketClient(URI.create("ws://127.0.0.1:7005/v1/interaction?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXNzaW9uSWQiOiI4ZjM3YmVhOC1lNDYxLTRhNzktODczZS00Yzg2ZTI2OWU4YTAiLCJleHAiOjE3MjU1MTY3NTIsImFsaXl1bk1haW5JZCI6IjE1Mzk3MDQ3MDY0MTMyNzgifQ.PAvCT7gY1VWNGJQc1cmubVLd76INRVZnhHdoBRV9-Rc&sessionId=8f37bea8-e461-4a79-873e-4c86e269e8a0"));

        // 2. 通道準備業務
        //      2.1 組裝數據包
        byte frameId = (byte) 0;
        byte dataType = (byte) 1;
        byte bizType = (byte) 131;
        // 正常業務使用隨機生成8位序列號即可
        byte[] serialNumber = new byte[8];
        byte[] contentBytes = new byte[0];

        byte[] message = new byte[11 + contentBytes.length];
        ByteBuffer bf = ByteBuffer.wrap(message)
        .put(frameId)
        .put(dataType)
        .put(bizType)
        .put(serialNumber)
        .put(contentBytes);
        message = bf.array();

        // 3. 發送數據包
        client.sendMessage(message);

    }

}

WebSocket消息結構

1. 整體結構

整體數據包格式為: 11字節包頭+N字節包內容

前11字節包頭格式為:1字節幀類型+1字節數據類型+1字節業務類型+8字節序列號(調用端隨機生成,排查問題使用)

public class InteractionWebSocketProtocol {

    // 1字節幀類型
    private InteractionProtocolFrameEnum interactionProtocolFrameEnum;

    // 1字節數據類型
    private DataTypeEnum dataType;

    // 1字節業務類型
    private BusinessTypeEnum businessType;

    // 8字節序列號
    private String serialNumber;

    // N字節消息 二進制格式
    private byte[] messageBytes;

}

2. 幀類型

bit

描述

備注

0000 0000

控制幀

用于客戶端傳遞控制指令

0000 0001

數據幀

用于服務端業務數據返回

0000 0010

消息幀

用于服務端錯誤消息返回

0000 1110

心跳幀ping

客戶端心跳發起

0000 1111

心跳幀pong

服務端心跳返回

3. 數據類型

bit

描述

備注

0000 0000

binary

二進制音頻數據使用

0000 0001

json

4. 業務類型

業務類型:

bit

描述

備注

0000 0000

查詢開場白

查詢數字人開場白

0000 0010

打斷

打斷數字人視頻流

1000 0000

文本驅動

輸入文本來驅動數字人播報

1000 0010

音頻驅動【開始,結束】

發送音頻驅動開始,結束

1000 0001

音頻驅動【數據】

發送音頻驅動二進制數據

1000 0011

通道準備

推送RTC通道數據

1111 1111

通用業務類型

客戶端發送ping,服務端返回pong、msg統一采用通用業務類型

業務內容出入參格式:

4.1. 查詢開場白

請求:

字段名

類型

是否必填

備注

sessionId

String

會話id

返回:

字段名

類型

備注

success

Boolean

成功標志

sessionId

String

會話id

content

String

返回內容

finish

Boolean

流式返回結束標志

relatedImages

String[]

關聯的圖片url列表

relatedVideos

String[]

關聯的視頻url列表

messageId

String

消息ID

4.2. 打斷數字人播報

請求:

字段名

類型

是否必填

備注

sessionId

String

會話id

返回:暫無

4.3. 文本驅動數字人

請求:

字段名

類型

是否必填

備注

sessionId

String

會話id

text

String

驅動文本

askType

Integer

提問方式:

1 - 提問

2 - 播報

返回:

字段名

類型

備注

success

Boolean

成功標志

sessionId

String

會話id

content

String

返回內容

finish

Boolean

結束標志

relatedImages

String[]

關聯的圖片url列表

relatedVideos

String[]

關聯的視頻url列表

messageId

String

消息ID

4.4. 音頻驅動【開始】

請求:

字段名

類型

是否必填

備注

sessionId

String

會話id

type

String

固定值:startAsrAudio

askType

Integer

提問方式:

1 - 提問

2 - 播報

返回:

字段名

類型

備注

sessionId

String

會話id

success

Boolean

成功標志

4.5. 音頻驅動【數據】

請求:二進制數據,pcm格式

返回:提問模式的音頻識別結果返回見接口說明 第3點接收識別結果;播報模式暫無返回。

數字人播報的文本返回見4.3文本驅動數字人返回;

4.6. 音頻驅動【結束】

請求:

字段名

類型

是否必填

備注

sessionId

String

會話id

type

String

固定值:endAsrAudio

返回:暫無

4.7. 通道準備

請求:

字段名

類型

是否必填

備注

sessionId

String

會話id

返回:

字段名

類型

備注

sessionId

String

會話id

status

String

INIT - 準備中

FAIL - 異常

READY - 準備成功

4.8. 通用業務類型

消息格式:

字段

類型

描述

code

int

錯誤碼

message

string

錯誤描述

錯誤碼:

code

描述

10000

系統錯誤

10001

權限不足

WebSocket對接示例

1. 開場白

客戶端發送:

    public void sendOpenning() {
        byte frameId = (byte) 0; //幀類型:控制幀
        byte dataType = (byte) 1; //數據類型:json
        byte bizType = (byte) 0; //開場白業務類型:0
        byte[] serialNumber = new byte[8]; // 正常業務使用隨機生成8位序列號即可

        String content = "{\"sessionId\":\"xxx\"}"; //需替換sessionId
        byte[] contentBytes = content.getBytes(); 

        byte[] message = new byte[11 + contentBytes.length];
        ByteBuffer bf = ByteBuffer.wrap(message)
        .put(frameId)
        .put(dataType)
        .put(bizType)
        .put(serialNumber)
        .put(contentBytes);
        message = bf.array();

        // 3. 發送數據包
        client.sendMessage(message);
    }

客戶端接收:

    @OnMessage
    public void onMessage(byte[] message) {

        //1. 獲取第1字節幀號
        int frame = Byte.toUnsignedInt(message[0]);

        //2. 獲取第2字節數據類型
        int dataType = Byte.toUnsignedInt(message[1]);

        //3. 獲取第3字節業務類型
        int bizType = Byte.toUnsignedInt(message[2]);

        //4. 獲取第4-11字節序列號
        byte[] serialNumberBytes = Arrays.copyOfRange(message, 3, 11);
        String serialNumberString = new String(serialNumberBytes, StandardCharsets.UTF_8);

        //5. 獲取剩余內容數據,數據結構需要結合具體業務類型文檔
        // 如果是開場白業務
        if (bizType == 0) {
            byte[] contentJsonBytes = Arrays.copyOfRange(message, 11, message.length);
            String contentJsonString = new String(contentJsonBytes, StandardCharsets.UTF_8);
            //6. 執行相應業務邏輯......
        }
        
    }

2. 打斷

客戶端發送:

    public void sendOpenning() {
        byte frameId = (byte) 0; //幀類型:控制幀
        byte dataType = (byte) 1; //數據類型:json
        byte bizType = (byte) 2; //打斷業務類型:2
        byte[] serialNumber = new byte[8]; // 正常業務使用隨機生成8位序列號即可

        String content = "{\"sessionId\":\"xxx\"}"; //需替換sessionId
        byte[] contentBytes = content.getBytes(); 

        byte[] message = new byte[11 + contentBytes.length];
        ByteBuffer bf = ByteBuffer.wrap(message)
        .put(frameId)
        .put(dataType)
        .put(bizType)
        .put(serialNumber)
        .put(contentBytes);
        message = bf.array();

        // 3. 發送數據包
        client.sendMessage(message);
    }

客戶端接收:無需

3. 文本驅動

客戶端發送:

    public void sendText() {
        byte frameId = (byte) 0; //幀類型:控制幀
        byte dataType = (byte) 1; //數據類型:json
        byte bizType = (byte) 128; //文本驅動業務類型:128
        byte[] serialNumber = new byte[8]; // 正常業務使用隨機生成8位序列號即可

        String content = "{\n" +
                "  \"sessionId\":\"xxx\",\n" + //需替換sessionId
                "  \"text\":\"xxx\",\n" + //需替換text文本
                "  \"askType\":1 \n" + // 1:提問;2:播報
                "}"; 
        byte[] contentBytes = content.getBytes(); 

        byte[] message = new byte[11 + contentBytes.length];
        ByteBuffer bf = ByteBuffer.wrap(message)
        .put(frameId)
        .put(dataType)
        .put(bizType)
        .put(serialNumber)
        .put(contentBytes);
        message = bf.array();

        // 3. 發送數據包
        client.sendMessage(message);
    }

客戶端接收:

    @OnMessage
    public void onMessage(byte[] message) {

        //1. 獲取第1字節幀號
        int frame = Byte.toUnsignedInt(message[0]);

        //2. 獲取第2字節數據類型
        int dataType = Byte.toUnsignedInt(message[1]);

        //3. 獲取第3字節業務類型
        int bizType = Byte.toUnsignedInt(message[2]);

        //4. 獲取第4-11字節序列號
        byte[] serialNumberBytes = Arrays.copyOfRange(message, 3, 11);
        String serialNumberString = new String(serialNumberBytes, StandardCharsets.UTF_8);

        //5. 獲取剩余內容數據,數據結構需要結合具體業務類型文檔
        // 文本驅動業務
        if (bizType == 128) {
            byte[] contentJsonBytes = Arrays.copyOfRange(message, 11, message.length);
            String contentJsonString = new String(contentJsonBytes, StandardCharsets.UTF_8);
            //6. 執行相應業務邏輯......
        }
        
    }

4. 音頻驅動

客戶端發送:

    public void sendAudio() {

        // 1. 發送音頻驅動頭數據包
        byte frameId = (byte) 0; //幀類型:控制幀
        byte dataType = (byte) 1; //數據類型:json
        byte bizType = (byte) 130; //音頻驅動頭尾業務類型:130
        byte[] serialNumber = new byte[8]; // 正常業務使用隨機生成8位序列號即可
        String content = "{\n" +
                "\t\"sessionId\":\"xxx\",\n" +
                "\t\"type\": \"startAsrAudio\",\n" +
                "\t\"askType\":1 " +
                "}";
        byte[] contentBytes = content.getBytes(); 
        byte[] message = new byte[11 + contentBytes.length];
        ByteBuffer bf = ByteBuffer.wrap(message)
        .put(frameId)
        .put(dataType)
        .put(bizType)
        .put(serialNumber)
        .put(contentBytes);
        message = bf.array();
        client.sendMessage(message);

        // 2. 多次發送音頻二進制數據
        byte frameId = (byte) 0; //幀類型:控制幀
        byte dataType = (byte) 0; //數據類型:binary
        byte bizType = (byte) 129; //音頻驅動數據業務類型:129
        byte[] serialNumber = new byte[8]; // 正常業務使用隨機生成8位序列號即可
        byte[] audioData = new Byte[1024]; //需替換音頻數據
        byte[] contentBytes = audioData;
        byte[] message = new byte[11 + contentBytes.length];
        ByteBuffer bf = ByteBuffer.wrap(message)
        .put(frameId)
        .put(dataType)
        .put(bizType)
        .put(serialNumber)
        .put(contentBytes);
        message = bf.array();
        client.sendMessage(message);

        // 3. 發送音頻驅動尾包
        byte frameId = (byte) 0; //幀類型:控制幀
        byte dataType = (byte) 1; //數據類型:json
        byte bizType = (byte) 130; //音頻驅動頭尾業務類型:130
        byte[] serialNumber = new byte[8]; // 正常業務使用隨機生成8位序列號即可
        String content = "{\n" +
                "\t\"sessionId\":\"xxx\",\n" +
                "\t\"type\":\"endAsrAudio\",\n" +
                "\t\"askType\": 1 " +
                "}";
        byte[] contentBytes = content.getBytes(); 
        byte[] message = new byte[11 + contentBytes.length];
        ByteBuffer bf = ByteBuffer.wrap(message)
        .put(frameId)
        .put(dataType)
        .put(bizType)
        .put(serialNumber)
        .put(contentBytes);
        message = bf.array();
        client.sendMessage(message);
    }

客戶端接收:

    @OnMessage
    public void onMessage(byte[] message) {

        //1. 獲取第1字節幀號
        int frame = Byte.toUnsignedInt(message[0]);

        //2. 獲取第2字節數據類型
        int dataType = Byte.toUnsignedInt(message[1]);

        //3. 獲取第3字節業務類型
        int bizType = Byte.toUnsignedInt(message[2]);

        //4. 獲取第4-11字節序列號
        byte[] serialNumberBytes = Arrays.copyOfRange(message, 3, 11);
        String serialNumberString = new String(serialNumberBytes, StandardCharsets.UTF_8);

        //5. 獲取剩余內容數據,數據結構需要結合具體業務類型文檔
        // 獲取音頻驅動返回
        if (bizType == 129) {
            byte[] contentJsonBytes = Arrays.copyOfRange(message, 11, message.length);
            String contentJsonString = new String(contentJsonBytes, StandardCharsets.UTF_8);
            //6. 執行相應業務邏輯......
        }
        
    }

5. 通道準備

客戶端發送:

    public void channelReady() {
        byte frameId = (byte) 0; //幀類型:控制幀
        byte dataType = (byte) 1; //數據類型:json
        byte bizType = (byte) 131; //通道準備業務類型:131
        byte[] serialNumber = new byte[8]; // 正常業務使用隨機生成8位序列號即可

        String content = "{\"sessionId\":\"xxx\"}"; //需替換sessionId
        byte[] contentBytes = content.getBytes(); // 正常業務使用隨機生成8位序列號即可

        byte[] message = new byte[11 + contentBytes.length];
        ByteBuffer bf = ByteBuffer.wrap(message)
        .put(frameId)
        .put(dataType)
        .put(bizType)
        .put(serialNumber)
        .put(contentBytes);
        message = bf.array();

        // 3. 發送數據包
        client.sendMessage(message);
    }

客戶端接收:

    @OnMessage
    public void onMessage(byte[] message) {

        //1. 獲取第1字節幀號
        int frame = Byte.toUnsignedInt(message[0]);

        //2. 獲取第2字節數據類型
        int dataType = Byte.toUnsignedInt(message[1]);

        //3. 獲取第3字節業務類型
        int bizType = Byte.toUnsignedInt(message[2]);

        //4. 獲取第4-11字節序列號
        byte[] serialNumberBytes = Arrays.copyOfRange(message, 3, 11);
        String serialNumberString = new String(serialNumberBytes, StandardCharsets.UTF_8);

        //5. 獲取剩余內容數據,數據結構需要結合具體業務類型文檔
        // 通道準備
        if (bizType == 131) {
            byte[] contentJsonBytes = Arrays.copyOfRange(message, 11, message.length);
            String contentJsonString = new String(contentJsonBytes, StandardCharsets.UTF_8);
            //6. 執行相應業務邏輯......
        }
        
    }

6. 心跳

客戶端發送:

    public void sendPing() {
        byte frameId = (byte) 0; //幀類型:控制幀
        byte dataType = (byte) 1; //數據類型:json
        byte bizType = (byte) 255; //通用心跳業務類型:255
        byte[] serialNumber = new byte[8]; // 正常業務使用隨機生成8位序列號即可

        String content = "{}"; //無需
        byte[] contentBytes = content.getBytes(); 

        byte[] message = new byte[11 + contentBytes.length];
        ByteBuffer bf = ByteBuffer.wrap(message)
        .put(frameId)
        .put(dataType)
        .put(bizType)
        .put(serialNumber)
        .put(contentBytes);
        message = bf.array();

        // 3. 發送數據包
        client.sendMessage(message);
    }

客戶端接收:無需

對接詳情

PHP

對接示例

require 'vendor/autoload.php';
use AlibabaCloud\SDK\Imarketing\V20220704\Models\GetOssUploadSignatureRequest;
use AlibabaCloud\SDK\IntelligentCreation\V20240313\IntelligentCreation;
use Darabonba\OpenApi\Models\Config as AlibabaConfig;

$config = new AlibabaConfig();
$config->accessKeyId = '****';
$config->accessKeySecret = '****';
$config->endpoint = "intelligentcreation.cn-zhangjiakou.aliyuncs.com";

$intelligentCreationClient = new IntelligentCreation($config);

$request = new QueryAvatarProjectRequest();
$request->projectId = '111';
try {
  $response = $intelligentCreationClient->queryAvatarProject($request);
  var_dump($response->toMap());
} catch (TeaError $e) {
  Log::error($e);
}

2.4.0

u-17374653-852f-4536-9c8f-61f96b9890ef-composer-tea.zip

composer require alibabacloud/intelligentcreation-20240313 2.4.0

Java

對接示例

package com.aliyun.intelligentcreation20240313;

import com.aliyun.intelligentcreation20240313.models.*;
import com.aliyun.tea.TeaException;
import com.aliyun.teaopenapi.models.Config;
import com.google.gson.Gson;

import java.util.HashMap;
import java.util.Map;

public class TestAvatarTest {

    public TestAvatarTest() throws Exception {
    }

    public static void main(String[] args) throws Exception {
        TestAvatarTest avatarTest = new TestAvatarTest();

        try {
            String projectId = "780931376329506816";
            avatarTest.queryAvatarProjectTest(projectId);
            String sessionId = avatarTest.startAvatarSessionRequest(projectId);
            avatarTest.checkSessionRequest(projectId,sessionId);
            avatarTest.sendTextMsgRequest(projectId,sessionId);
            avatarTest.stopAvatarSessionRequest(projectId,sessionId);
            avatarTest.queryAvatarResourceRequest(projectId);
        } catch (TeaException e) {
            Gson gson = new Gson();
            System.out.println(e.getMessage());
            System.out.println(gson.toJson(e.getData()));
        }
        
    }

    String url = "intelligentcreation.cn-zhangjiakou.aliyuncs.com";
    //初始化配置
    String ak = "**";
    String sk = "**";
    Config config = new Config().setAccessKeyId(ak)
            .setAccessKeySecret(sk)
            .setEndpoint(url);
    // 創建客戶端
    Client client = new Client(config);

    void queryAvatarProjectTest(String projectId) throws Exception {
        Gson gson = new Gson();
        Map<String, Object> map = new HashMap<>();
        map.put("projectId",projectId);
        QueryAvatarProjectRequest request = QueryAvatarProjectRequest.build(map);
        // 請求接口
        QueryAvatarProjectResponse response = client.queryAvatarProject(request);

        System.out.println(gson.toJson(response));

        if (response.getStatusCode().equals(200)) {
            System.out.println("queryAvatarProjectTest 請求成功");
        }
    }

    String startAvatarSessionRequest(String projectId) throws Exception {
        Gson gson = new Gson();
        Map<String, Object> map = new HashMap<>();
        map.put("projectId",projectId);
        StartAvatarSessionRequest startAvatarSessionRequest = StartAvatarSessionRequest.build(map);
        StartAvatarSessionResponse response = client.startAvatarSession(startAvatarSessionRequest);

        System.out.println(gson.toJson(response));
        if (response.getStatusCode().equals(200)) {
            System.out.println("startAvatarSessionRequest 請求成功");
            return response.getBody().getSessionId();
        }
        return null;
    }

    void stopAvatarSessionRequest(String projectId, String sessionId) throws Exception {
        Gson gson = new Gson();
        Map<String, Object> map = new HashMap<>();
        map.put("projectId",projectId);
        map.put("sessionId",sessionId);
        StopAvatarSessionRequest request = StopAvatarSessionRequest.build(map);
        StopAvatarSessionResponse response = client.stopAvatarSession(request);

        System.out.println(gson.toJson(response));
        if (response.getStatusCode().equals(200)) {
            System.out.println("stopAvatarSessionRequest 請求成功");
        }
    }

    void queryAvatarResourceRequest(String projectId) throws Exception {
        Gson gson = new Gson();
        Map<String, Object> map = new HashMap<>();
        QueryAvatarResourceRequest request = QueryAvatarResourceRequest.build(map);
        QueryAvatarResourceResponse response = client.queryAvatarResource(request);

        System.out.println(gson.toJson(response));
        if (response.getStatusCode().equals(200)) {
            System.out.println("queryAvatarResourceRequest 請求成功");
        }
    }

}

2.4.0

u-17374653-852f-4536-9c8f-61f96b9890ef-java-tea.zip

<dependency>
  <groupId>com.aliyun</groupId>
  <artifactId>intelligentcreation20240313</artifactId>
  <version>2.4.0</version>
</dependency>

2.1.0

u-ce29b5c7-8a37-4590-98f4-71a02e5366f9-java-tea.zip

<dependency>
  <groupId>com.aliyun</groupId>
  <artifactId>intelligentcreation20240313</artifactId>
  <version>2.1.0</version>
</dependency>

安卓

實時音視頻SDK接入文檔: 實時音頻快速入門指南-阿里云幫助中心

※ mLocalSurfaceContainer 是 視頻流的展示容器

※ projectId 是項目Id

  1. 添加maven倉庫

maven(url = "https://maven.aliyun.com/nexus/content/repositories/releases")
  1. 添加推流SDK依賴

implementation("com.aliyun.aio:AliVCSDK_ARTC:6.8.7")
implementation("com.aliyun:intelligentcreation20240313:2.1.0")
implementation("com.aliyun:tea-openapi:0.3.4")
  1. 初始化拉流引擎

private fun createEngine() {
    mAliRtcEngine = AliRtcEngine.getInstance(applicationContext)
    mAliRtcEngine?.setRtcEngineEventListener(object : AliRtcEngineEventListener() {
        /* SDK與服務器的鏈接狀態通知,務必處理鏈接失敗的情況 */
        override fun onConnectionStatusChange(
            aliRtcConnectionStatus: AliRtcEngine.AliRtcConnectionStatus,
            aliRtcConnectionStatusChangeReason: AliRtcEngine.AliRtcConnectionStatusChangeReason
        ) {
            super.onConnectionStatusChange(
                aliRtcConnectionStatus,
                aliRtcConnectionStatusChangeReason
            )
            if (aliRtcConnectionStatus == AliRtcEngine.AliRtcConnectionStatus.AliRtcConnectionStatusFailed) {
                /* TODO: 務必處理;建議業務提示客戶,此時SDK內部已經嘗試了各種恢復策略已經無法繼續使用時才會上報 */
            } else {
                /* TODO: 可選處理;增加業務代碼,一般用于數據統計、UI變化 */
            }
        }

        /* SDK嘗試控制本地設備異常 */
        override fun OnLocalDeviceException(
            aliRtcEngineLocalDeviceType: AliRtcEngine.AliRtcEngineLocalDeviceType,
            aliRtcEngineLocalDeviceExceptionType: AliRtcEngine.AliRtcEngineLocalDeviceExceptionType,
            s: String
        ) {
            //TODO 發生該異常時App需要檢測權限、設備硬件是否正常。
        }

        override fun onJoinChannelResult(
            result: Int,
            channel: String,
            userId: String,
            elapsed: Int
        ) {
            super.onJoinChannelResult(result, channel, userId, elapsed)
            Log.i(TAG, "onJoinChannelResult result=$result,channel=$channel,userId=$userId,elapsed=$elapsed")
        }

        override fun onLeaveChannelResult(result: Int, stats: AliRtcEngine.AliRtcStats) {
            super.onLeaveChannelResult(result, stats)
            Log.i(TAG, "onLeaveChannelResult result=$result")
        }
    })

    mAliRtcEngine?.setRtcEngineNotify(object : AliRtcEngineNotify() {
        /* 鑒權距離過期還有30s時會回調,務必進行鑒權時間刷新 */
        override fun onAuthInfoWillExpire() {
            super.onAuthInfoWillExpire()
            /* TODO: 務必處理;業務觸發重新獲取當前channel,user的鑒權信息,然后設置refreshAuthInfo即可 */

        }

        /* 業務可能會觸發踢人的動作,所以這個地方也需要處理 */
        override fun onBye(code: Int) {
            super.onBye(code)
            /* TODO: 建議業務根據自己的場景,進行對應的處理 */
        }

        override fun onRemoteUserOnLineNotify(uid: String, elapsed: Int) {
            super.onRemoteUserOnLineNotify(uid, elapsed)
            Log.i(TAG, "onRemoteUserOnLineNotify uid=$uid,elapsed=$elapsed")
        }

        override fun onRemoteUserOffLineNotify(
            uid: String,
            aliRtcUserOfflineReason: AliRtcEngine.AliRtcUserOfflineReason
        ) {
            super.onRemoteUserOffLineNotify(uid, aliRtcUserOfflineReason)
            Log.i(TAG, "onRemoteUserOnLineNotify uid=$uid,aliRtcUserOfflineReason=$aliRtcUserOfflineReason")
        }

        override fun onRemoteTrackAvailableNotify(
            uid: String,
            aliRtcAudioTrack: AliRtcEngine.AliRtcAudioTrack,
            aliRtcVideoTrack: AliRtcEngine.AliRtcVideoTrack
        ) {
            super.onRemoteTrackAvailableNotify(uid, aliRtcAudioTrack, aliRtcVideoTrack)
            Log.i(TAG, "onRemoteUserOnLineNotify uid=$uid,aliRtcAudioTrack=$aliRtcAudioTrack,aliRtcVideoTrack=$aliRtcVideoTrack")
            mLocalSurfaceContainer.post{
                if (aliRtcVideoTrack == AliRtcEngine.AliRtcVideoTrack.AliRtcVideoTrackCamera
                    || aliRtcVideoTrack == AliRtcEngine.AliRtcVideoTrack.AliRtcVideoTrackBoth
                ) {
                    val remote_canvas = AliRtcVideoCanvas()
                    val remoteView = mAliRtcEngine?.createRenderSurfaceView(this@PreviewActivity)
                    if (remoteView != null) {
                        remoteView.setZOrderOnTop(true)
                        remoteView.setZOrderMediaOverlay(true)
                    }
                    remote_canvas.view = remoteView
                    mLocalSurfaceContainer.addView(
                        remote_canvas.view, FrameLayout.LayoutParams(
                            mLocalSurfaceContainer.width,
                            mLocalSurfaceContainer.height
                        )
                    )
                    mAliRtcEngine?.setRemoteViewConfig(
                        remote_canvas,
                        uid,
                        AliRtcEngine.AliRtcVideoTrack.AliRtcVideoTrackCamera
                    )
                } else {
                    mAliRtcEngine?.setRemoteViewConfig(
                        null,
                        uid,
                        AliRtcEngine.AliRtcVideoTrack.AliRtcVideoTrackCamera
                    )
                }
            }
        }
    })
}
  1. 入會前引擎參數配置

private fun initEngineBeforeJoin() {
    /* 可選:入會前的參數設置 */
    mAliRtcEngine?.setChannelProfile(AliRtcEngine.AliRTCSdkChannelProfile.AliRTCSdkInteractiveLive)
    mAliRtcEngine?.setClientRole(AliRtcEngine.AliRTCSdkClientRole.AliRTCSdkLive)
    /* 設置音頻的屬性 */
    mAliRtcEngine?.setAudioProfile(
        AliRtcEngine.AliRtcAudioProfile.AliRtcEngineStereoHighQualityMode,
        AliRtcEngine.AliRtcAudioScenario.AliRtcSceneMusicMode
    )

    /* 可選:攝像頭預覽,不設置也會進行推流 */
    val canvas = AliRtcVideoCanvas()
    canvas.view = mAliRtcEngine?.createRenderSurfaceView(this)
    mLocalSurfaceContainer.removeAllViews()
    if (canvas.view != null) {
        mLocalSurfaceContainer.addView(
            canvas.view,
            FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT
            )
        )
    }
    mAliRtcEngine?.setLocalViewConfig(
        canvas,
        AliRtcEngine.AliRtcVideoTrack.AliRtcVideoTrackCamera
    )
}
  1. 初始化pop接口SDK

private fun initPopApiClient(){
    val config = Config()
        .setAccessKeyId(KEY_ID)
        .setAccessKeySecret(KEY_SECRET)
        .setEndpoint(HOST)
    client = Client(config)
}
  1. 獲取項目信息

private suspend fun getProjectInfo() = withContext(Dispatchers.IO) {
    val map: Map<String , String> = mutableMapOf<String, String>().apply {
        put(PROJECT_ID , projectId?:"")
    }
    client?.queryAvatarProject(QueryAvatarProjectRequest.build(map))?.apply {
        Log.e(TAG , "statusCode:${statusCode}")
        if (statusCode == 200) {
            Log.e(TAG , "projectName:${body.projectName}")
            tvProjectName.post {
                tvProjectName.text = body.projectName
            }
        }
    }
}
  1. 獲取會議信息

private suspend fun getMeetingInfo() = withContext(Dispatchers.IO){
    val map: Map<String , String> = mutableMapOf<String, String>().apply {
        put(PROJECT_ID , projectId?:"")
    }
    val response: StartAvatarSessionResponse? = client?.startAvatarSession(StartAvatarSessionRequest.build(map))

    response?.apply {
        Log.e(TAG , "statusCode:${statusCode}")
        if (statusCode == 200) {
            Log.e(TAG , "sessionId:${body.sessionId},channelToken:${body.channelToken}")
            sessionId = body.sessionId
            val meetingJsonObj = JSONObject(body.channelToken)
            var channelId = ""
            var token = ""
            var nonce = ""
            var userId = ""
            var appId = ""
            var expireTime = 0L
            var gslbList:MutableList<String> = mutableListOf()
            if (meetingJsonObj.has(CHANNEL_ID)){
                channelId = meetingJsonObj.optString(CHANNEL_ID)
            }
            if (meetingJsonObj.has(TOKEN)){
                token = meetingJsonObj.optString(TOKEN)
            }
            if (meetingJsonObj.has(NONCE)){
                nonce = meetingJsonObj.optString(NONCE)
            }
            if (meetingJsonObj.has(USER_ID)){
                userId = meetingJsonObj.optString(USER_ID)
            }
            if (meetingJsonObj.has(APP_ID)){
                appId = meetingJsonObj.optString(APP_ID)
            }
            if (meetingJsonObj.has(EXPIRE_TIME)){
                expireTime = meetingJsonObj.optLong(EXPIRE_TIME)
            }
            if (meetingJsonObj.has(GSLB_LIST)) {
                val gslbListJson = meetingJsonObj.optJSONArray(GSLB_LIST)
                for (i in 0 until gslbListJson.length()) {
                    gslbList.add(gslbListJson.optString(i))
                }
            }
            join(appId , channelId , userId , nonce,expireTime , token , gslbList.toTypedArray())
        }
    }
}
  1. 入會

private fun join(appId:String,channelId:String , userId:String ,
                     nonce:String , expireTime:Long,token:String,gslbList:Array<String>){
    val userInfo = AliRtcAuthInfo()
    userInfo.setAppId(appId)
    userInfo.setChannelId(channelId)
    userInfo.setUserId(userId)
    userInfo.setNonce(nonce)
    userInfo.setTimestamp(expireTime)
    userInfo.setGslb(gslbList)
    userInfo.setToken(token)
    mAliRtcEngine?.joinChannel(userInfo, "testUserName")

    resetCountDown()
}
  1. 發送問題

private fun send(){
    val question = etInputQuestion.text.toString().trim()
    if (TextUtils.isEmpty(question)) {
        Toast.makeText(this , "question content cannot be null" , Toast.LENGTH_SHORT).show()
        return
    }
    GlobalScope.launch {
        sendText(question)
    }
}

private suspend fun sendText(text:String) = withContext(Dispatchers.IO){

    val map: Map<String , String> = mutableMapOf<String, String>().apply {
        put(PROJECT_ID , projectId?:"")
        put(SESSION_ID , sessionId)
        put(REQUEST_ID , (System.currentTimeMillis()/1000).toString())
        put(TEXT , text)
        put(TYPE , "1")
    }
    client?.sendTextMsg(SendTextMsgRequest.build(map))?.apply {
        onSendResponse(statusCode == 200 && body.status == "SUCCESS" , text)
    }
}

private fun onSendResponse(result:Boolean , text:String){
    tvExpireHint.post {
        if (result) {
            etInputQuestion.setText("")
            questionAdapter?.data?.add(0 , text)
            questionAdapter?.notifyItemInserted(0)
            rvQuestion.scrollToPosition(0)
            resetCountDown()
            Toast.makeText(this@PreviewActivity , "發送成功" , Toast.LENGTH_SHORT).show()
        }else {
            Toast.makeText(this@PreviewActivity , "發送失敗" , Toast.LENGTH_SHORT).show()
        }
    }
}
  1. 離會

private fun leave(){
    mAliRtcEngine?.leaveChannel()
    findViewById<LinearLayout>(R.id.llEnd).visibility = View.VISIBLE

    val map: Map<String , String> = mutableMapOf<String, String>().apply {
        put(PROJECT_ID , projectId?:"")
        put(SESSION_ID , sessionId)
    }

    GlobalScope.launch {
        async {
            client?.stopAvatarSession(StopAvatarSessionRequest.build(map))?.apply {
                Log.e(TAG , "statusCode = ${statusCode}, status = ${body.status}")
            }
        }
    }
}
  1. 資源回收

override fun onDestroy() {
    super.onDestroy()
    mAliRtcEngine?.destroy()
    mAliRtcEngine = null
    handler.removeCallbacksAndMessages(null)
}
  1. 10min無提問,自動離會

private var handler: Handler = Handler(Looper.getMainLooper())

private val mRunnable = Runnable {
    dealCountDown()
}

 private fun dealCountDown(){
    if (messageCount == 0) {
        //離會
        leave()
        return
    }
    //小于1min ,提示
    val timeStr = when {
        60 == messageCount-> "01:00"
        (messageCount in 1..9) -> "00:0${messageCount}"
        else -> "00:${messageCount}"
    }
    llExpireHint.visibility = View.VISIBLE
    tvExpireHint.text = "檢測到您無操作,預覽將于${timeStr}自動關閉,以避免占用路數"
    messageCount--
    handler.postDelayed(mRunnable , 1000)
}

//在入會和發送問題之后調用
private fun resetCountDown(){
    handler.removeCallbacksAndMessages(null)
    messageCount = 60
    handler.postDelayed(mRunnable , 1000 * 60 * 9)
}