【数字人开发】结合nextHuman平台进行数字人网页端开发

发布于:2025-07-01 ⋅ 阅读:(18) ⋅ 点赞:(0)

零、实现效果

结合nextHuman进行数字人网页端开发

一、实现过程

1、创建应用获取AccessKey和AccessSecret

官方网址:https://nexthuman.cn/developer/#/app-list
在这里插入图片描述

2、环境准备

(1)新建项目

在这里插入图片描述

(2)安装依赖的库

安装nextHuman WebGL端的SDK

npm i @nextcas/sdk@latest -s

安装鉴权方法所依赖的库

npm install jose

3、鉴权方法

官方鉴权方法(基于JAVA):

public String createAccessToken(String accessKey,String accessSecret,String visitId,String visitName){
      SecretKey secretKey = Keys.hmacShaKeyFor(accessSecret.getBytes(StandardCharsets.UTF_8));
      JwtBuilder builder = Jwts.builder();
      builder.claim("role","role.visit");
      builder.claim("visitId",StringUtil.trimToEmpty(visitId));//业务方userId(不同用户切记不可重复,否则会话记忆可能穿插)
      builder.claim("visitName",StringUtil.trimToEmpty(visitName));
      builder.claim("timestamp",System.currentTimeMillis());//务必传系统当前时间,会用于系统时间钟校验
      builder.claim("noncestr",MD5Helper.getRandomMD5());//建议加上随机字符串,用于控制Token结果的随机性
      builder.setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000));//建议设置为24小时以内的有效期
      return accessKey + "@" + builder.signWith(secretKey).compact();
}

修改完善后(基于JavaScript):

/**
 * 生成一个随机的十六进制字符串(替代 MD5)
 */
function getRandomHex(len = 16) {
  const bytes = new Uint8Array(len);
  crypto.getRandomValues(bytes);
  return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
}
/**
 * 创建 JWT Token(浏览器版本)
 */
async function getToken(accessKey, accessSecret, visitId, visitName) {
  const encoder = new TextEncoder();
  const secretKey = encoder.encode(accessSecret);
  const payload = {
    role: 'role.visit',
    visitId: (visitId || '').trim(),
    visitName: (visitName || '').trim(),
    timestamp: Date.now(),
    noncestr: getRandomHex(16),
  };

  const token = await new SignJWT(payload)
    .setProtectedHeader({ alg: 'HS256' })
    .setExpirationTime('24h')
    .sign(secretKey);

  return `${accessKey}@${token}`;
}

4、相关方法使用

(1)初始化项目,并使用官方样例

const  token = await getToken(
    "AccessKey",
    "AccessSecret",
    "userId01",
    "username");
  console.log("token:", token);

  cas = new NextCas(container, {
    avatarId: "avatar_592456",
    actorId: "641811add41a3f2f91247af5",
    token,
    templateName: "introduce"
  });

(2)主要方法

//切换模型方法
cas.setAvatar("模型对应id");
//切换问答智能体
cas.setActor(“智能体对应id”)
//语音播报
cas.speak("播报的内容");
//获取当前装扮信息
cas.getBundles();
//切换装扮
cas.addBundle("具体装扮信息")

二、完整代码

main.js

import './style.css'
import NextCas from "@nextcas/sdk";
import { SignJWT } from 'jose';

let cas = null;

const container = document.getElementById("container");
const statusText = document.getElementById("status");
const initBtn = document.getElementById("initBtn");


/**
 * 生成一个随机的十六进制字符串(替代 MD5)
 */
function getRandomHex(len = 16) {
  const bytes = new Uint8Array(len);
  crypto.getRandomValues(bytes);
  return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
}
/**
 * 创建 JWT Token(浏览器版本)
 */
async function getToken(accessKey, accessSecret, visitId, visitName) {
  const encoder = new TextEncoder();
  const secretKey = encoder.encode(accessSecret);
  const payload = {
    role: 'role.visit',
    visitId: (visitId || '').trim(),
    visitName: (visitName || '').trim(),
    timestamp: Date.now(),
    noncestr: getRandomHex(16),
  };

  const token = await new SignJWT(payload)
    .setProtectedHeader({ alg: 'HS256' })
    .setExpirationTime('24h')
    .sign(secretKey);

  return `${accessKey}@${token}`;
}


initBtn.onclick = async () => {
  const  token = await getToken(
    "next685e15c1959a9c02aca4b577",
    "Y_PCViGvZJM27wXa29x2jejyfgqfQcCEvjwRYgyqMI",
    "userId01",
    "username");
  console.log("token:", token);

  cas = new NextCas(container, {
    avatarId: "avatar_257",
    actorId: "641811add41a3f2f91247af5",
    token,
    templateName: "introduce"
  });

  cas.setBackground("");
  cas.on("initProgress", (cent) => {
    statusText.textContent = `加载中 ${cent}%`;
  });

  cas.on("ready", () => {
    statusText.textContent = "初始化完成";
  });
};

document.getElementById("switchGirl").onclick = async () => {
  if (cas) {
    await cas.setAvatar("avatar_257");
    await cas.setActor("641811add41a3f2f91247af5");
  }
};

document.getElementById("switchBoy").onclick = async () => {
  if (cas) {
    await cas.setAvatar("avatar_1078");
    await cas.setActor("actor_100230");
  }
}

const inputText = document.getElementById("inputText");
const speakBtn = document.getElementById("speakBtn");

speakBtn.onclick = () => {
  if (cas && inputText.value.trim() !== "") {
    cas.speak(inputText.value.trim());
  }
};


document.getElementById("changeHairBtn").onclick = async () => {
  if (cas) {
    await cas.addBundle("hair_1001");  // 这里的 bundle 名称根据你具体资源填写
    console.log("头发换装成功");
  }
};

document.getElementById("changeShoesBtn").onclick = async () => {
  if (cas) {
    await cas.addBundle("shoes_1002"); // 换鞋子的bundle名称,假设是 shoes_1001
    console.log("鞋子换装成功");
  }
};

document.getElementById("changeClothesBtn").onclick = async () => {
  if (cas) {
    await cas.addBundle("clothes_1001"); // 换套装的bundle名称,假设是 clothes_1001
    console.log("套装换装成功");
  }
};

const showBundleBtn = document.getElementById("showBundleBtn");
const bundleInfo = document.getElementById("bundleInfo");


showBundleBtn.onclick = async () => {
  if (!cas) return;
  const bundles = await cas.getBundles();
  console.log("已加载的bundle列表:", bundles);
};

index.html

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>NextHuman 控制面板</title>
    <style>
      body {
        display: flex;
        margin: 0;
        font-family: sans-serif;
      }
      #container {
        width: 375px;
        height: 800px;
        border: 1px solid red;
        flex-shrink: 0;
        ;
      }
      .panel {
        padding: 20px;
        max-width: 500px;
      }
      button {
        margin: 8px;
      }
    </style>
  </head>
  <body>
    <div id="container" style="background-color: black;"></div>
    <div class="panel">
        <div>
          <strong>初始化状态:</strong>
          <span id="status">未初始化</span>
        </div>

        <div style="margin-top: 10px">
          <button id="initBtn">初始化数字人</button>
        </div>

        <!-- ✅ 你需要添加的代码是这一段 -->
        <div style="margin-top: 20px;">
          <strong>角色切换:</strong><br />
          <button id="switchGirl">切换为女</button>
          <button id="switchBoy">切换为男</button>
        </div>

        <div style="margin-top: 20px;">
          <strong>朗读文本:</strong><br />
          <textarea id="inputText" rows="3" cols="40" placeholder="请输入你想让数字人朗读的内容"></textarea><br />
          <button id="speakBtn">朗读</button>
        </div>

        <div style="margin-top: 20px;">
          <strong>换装:</strong><br />
          <button id="changeHairBtn">换头发</button>
          <button id="changeShoesBtn">换鞋子</button>
          <button id="changeClothesBtn">换套装</button>      
        </div>

        <div style="margin-top: 20px;">
          <button id="showBundleBtn">显示当前装扮信息</button>
          <div id="bundleInfo" style="margin-top: 10px; white-space: pre-wrap; border: 1px solid #ccc; padding: 8px; max-width: 400px; min-height: 40px;">
            <!-- 装扮信息显示区域 -->
          </div>
        </div>

    </div>

    <script type="module" src="/src/main.js"></script>
  </body>
</html>

三、存在问题

1、模型角色只有两个模型可用,存在限制

2、装扮无法获取完整的id信息,无法进行切换


网站公告

今日签到

点亮在社区的每一天
去签到