Commit 686595d9 authored by ali's avatar ali

feat: 照片数字人接入 hwsdk

parent 491194bc
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
"version": "1.0.0", "version": "1.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"axios": "^1.6.2",
"electron-store": "^8.1.0", "electron-store": "^8.1.0",
"flv.js": "^1.6.2", "flv.js": "^1.6.2",
"pinia": "^2.1.7", "pinia": "^2.1.7",
...@@ -2619,8 +2620,7 @@ ...@@ -2619,8 +2620,7 @@
"node_modules/asynckit": { "node_modules/asynckit": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
"dev": true
}, },
"node_modules/at-least-node": { "node_modules/at-least-node": {
"version": "1.0.0", "version": "1.0.0",
...@@ -2651,6 +2651,16 @@ ...@@ -2651,6 +2651,16 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/axios": {
"version": "1.6.2",
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.6.2.tgz",
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
"dependencies": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/balanced-match": { "node_modules/balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
...@@ -3154,7 +3164,6 @@ ...@@ -3154,7 +3164,6 @@
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"dependencies": { "dependencies": {
"delayed-stream": "~1.0.0" "delayed-stream": "~1.0.0"
}, },
...@@ -3439,7 +3448,6 @@ ...@@ -3439,7 +3448,6 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"dev": true,
"engines": { "engines": {
"node": ">=0.4.0" "node": ">=0.4.0"
} }
...@@ -4923,6 +4931,19 @@ ...@@ -4923,6 +4931,19 @@
"webworkify-webpack": "^2.1.5" "webworkify-webpack": "^2.1.5"
} }
}, },
"node_modules/follow-redirects": {
"version": "1.15.3",
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.3.tgz",
"integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/for-each": { "node_modules/for-each": {
"version": "0.3.3", "version": "0.3.3",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
...@@ -4952,7 +4973,6 @@ ...@@ -4952,7 +4973,6 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dev": true,
"dependencies": { "dependencies": {
"asynckit": "^0.4.0", "asynckit": "^0.4.0",
"combined-stream": "^1.0.8", "combined-stream": "^1.0.8",
...@@ -6309,7 +6329,6 @@ ...@@ -6309,7 +6329,6 @@
"version": "1.52.0", "version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"dev": true,
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
} }
...@@ -6318,7 +6337,6 @@ ...@@ -6318,7 +6337,6 @@
"version": "2.1.35", "version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dev": true,
"dependencies": { "dependencies": {
"mime-db": "1.52.0" "mime-db": "1.52.0"
}, },
...@@ -7098,6 +7116,11 @@ ...@@ -7098,6 +7116,11 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/pump": { "node_modules/pump": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
......
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
"node": ">=18.0.0" "node": ">=18.0.0"
}, },
"dependencies": { "dependencies": {
"axios": "^1.6.2",
"electron-store": "^8.1.0", "electron-store": "^8.1.0",
"flv.js": "^1.6.2", "flv.js": "^1.6.2",
"pinia": "^2.1.7", "pinia": "^2.1.7",
......
import HWLLSSDK from '@/renderer/utils/HWLLS_SDK_Web_2.3.0/lib/HWLLSPlayer';
import EventEmitter from 'events';
const { createClient, setLogLevel } = HWLLSSDK;
/**
*
* @param { string } url:必选,string类型,拉流地址。具体地址格式请参见startPlay详情。
@param {Object} options:可选,StartPlayOptions类型。
@param {} options.elementId:必选,播放Dom标识ID。
@param {} options.objectFit:可选,string类型,默认值为cover。支持的枚举值如下:
@param {} options.contain:优先保证视频内容全部显示。视频尺寸等比缩放,直至视频窗口的一边与视窗边框对齐。如果视频尺寸与显示视窗尺寸不一致,在保持长宽比的前提下,将视频进行缩放后填满视窗,缩放后的视频四周会有一圈黑边。
@param {} options.cover:优先保证视窗被填满。视频尺寸等比缩放,直至整个视窗被视频填满。如果视频长宽与显示窗口不同,则视频流会按照显示视窗的比例进行周边裁剪或图像拉伸后填满视窗。19_超低时延直播用户指南19_超低时延直播用户指南 7 低时延直播 SDK文档版本 01 (2022-12-02) 版权所有 © 华为技术有限公司 19
@param {} options.fill:视频内容完全填充视窗。如果视频的宽高比与视窗不相匹配,那么视频将被拉伸以适应视窗。
@param {} options.muted:可选,boolean类型,true表示静音,false表示不静音。默认值为 false。
@param {} options.sessionId:可选,string类型,一次完整会话的统一标识。
@param {} options.showLoading:可选,boolean类型,true表示开启loading的展示效果,默认值为false。当该参数设置为true时,起播loading效果同步开启,播放过程中发生缓冲时loading的效果,需根据setParameter接口中的LOADING_CONFIG进行设置。
@param {} options.autoPlay: 可选,boolean类型,true表示开启自动起播功能,false表示非自动起播,需要人为触发播放,默认为true。
@param {} options.poster: 可选,对象定义如下:
{
url:可选,string类型。设置播放封面图片完整地址,图片格式限 JPG/PNG 和静态GIF格式,大小不超过1MB,尺寸不超过1920 x 1080, 文件名不得含有中文字符。
mode:可选,string类型。默认值为cover。支持的枚举值如下:{ ○ fiÃÃ:视频内容完全填充视窗,如果视频的宽高比与视窗不相匹配,那么视频将被拉伸以适应视窗。
crop:播放封面原始尺寸大小展示,如果海报超出播放区域,则会对超出部分进行裁剪,否则在播放窗口居中展示。
}
@param {} startEnable:可选,boolean类型。启动播放时是否展示播放封面,true 表示展示,false表示不展示播放封面,默认值false。该参数只在设置非 自动播放场景下生效。
@param {} pauseEnable:可选,boolean类型。触发暂停操作时,是否在播放页面展示播放封面,true表示展示播放封面,false表示不展示,默认值false。
webrtcConfig 可选,webrtcConfig类型。指定媒体类型进行拉流的配置参数,webrtcConfig 定义如下:
{
receiveVideo:可选,boolean类型。设置是否拉取视频进行播放,true表示拉取视频进行播放,false表示不拉取视频播放,默认值true。该属性值和receiveAudio不能同时设置为false。
receiveAudio:可选,boolean类型。设置是否拉取音频进行播放,true 表示拉取音频进行播放,false表示不拉取音频播放,默认值true。该属性值和receiveVideo不能同时设置为false`
}
*
*
*/
export type StartPlayOptions = {
objectFit?: 'contain' | 'cover' | 'fill';
muted?: boolean;
sessionId?: string;
showLoading?: boolean;
autoPlay?: boolean;
poster?: {
url?: string;
mode?: 'fill' | 'crop';
startEnable?: boolean;
pauseEnable: boolean;
};
};
// 自定义事件类型
export type HwEventType = 'videoStart' | 'audioStart' | 'audioBroken' | 'videoBroken' | 'error'; // 场景页切换
export type HwEventTypeData<T extends HwEventType> = {
videoStart: [];
audioStart: [];
audioBroken: [];
videoBroken: [];
error: [{ code: number; message: string }];
}[T];
export type HwEventTypeFn<T extends HwEventType> = {
// eslint-disable-next-line no-unused-vars
[K in T]: (...args: HwEventTypeData<T>) => void;
}[T];
export class HwWebRTC extends EventEmitter {
elementId = '';
startPlayOptions: StartPlayOptions | null = null;
client: any = null;
constructor(id: string, log: 'none' | 'error' | 'warn' | 'info' | 'debug' = 'none') {
super();
this.elementId = id;
setLogLevel(log);
}
/**
* 发送事件
* @param event 事件名称
* @param args 事件参数
* @returns 是否成功
*/
emit<T extends HwEventType>(event: T, ...args: HwEventTypeData<T>): boolean {
return super.emit(event, ...args);
}
/**
* 绑定事件
* @param event 事件名称
* @param fn 事件回调
* @returns this
*/
on<T extends HwEventType>(event: T, fn: HwEventTypeFn<T>): this {
// fn 可能确实只有一个参数, 只能使用as
return super.on(event, fn as (...args: any[]) => void);
}
/**
* 预处理:获取浏览器的版本号、检查兼容性
*/
static async isBrowserSupport() {
let check = false;
check = await HWLLSSDK.checkSystemRequirements();
return check;
}
/**
* 拉流播放:拉流播放请求。
* @param url 拉流链接
* @param options 配置项
*/
async startPlay(
url: string,
options: StartPlayOptions = {
objectFit: 'contain'
}
) {
if (this.client) this.destroyed();
this.startPlayOptions = options;
this.client = createClient('webrtc');
await this.client.startPlay(url, {
elementId: this.elementId,
...this.startPlayOptions
});
this.client.enableStreamStateDetection(true, 2);
this._bindEvents();
}
private _bindEvents() {
this.client.on('video-start', () => {
this.emit('videoStart');
});
this.client.on('audio-start', () => {
this.emit('audioStart');
});
this.client.on('audio-broken', () => {
this.emit('audioBroken');
});
this.client.on('video-broken', () => {
this.emit('videoBroken');
});
// this.client.on('audio-recovery', () => {
// logManage.log('----------------> audio-recovery', 1);
// });
// this.client.on('video-recovery', () => {
// logManage.log('----------------> video-recovery', 1);
// });
this.client.on('Error', (error: any) => this.emit('error', error));
}
/**
* 停止播放:停止播放请求
*/
stopPlay() {
this.client && this.client.stopPlay();
}
/**
* 后处理:销毁客户端等。
*/
destroyed() {
this.client?.offAllEvents();
this.client?.destoryClient();
}
}
/* eslint-disable camelcase */
import http from '@/renderer/utils/http';
import { HwWebRTC } from './HwWebRTC';
import { guid } from '@/renderer/utils/index';
import EventEmitter from 'events';
const HOST = 'http://122.51.32.12:9000';
type LiveOptions = {
speaker?: string;
languageCode?: string;
text?: string;
audioUrl?: string | null;
taskId: string;
imgUrl: string;
speed?: number;
};
export class PhotoAnswer {
question = '';
answer = '';
/** 将答案分割,一段一段合成数字人直播流 */
_sliceAnswer = '';
/** 保存答案数组,用于实现打字输出效果 */
_typingAnswer: string[] = [];
/** 与 answer 内容一致,外部实现打字输出效果 */
asyncAnswer = '';
/** 数字人播放状态 */
playState: 'playing' | 'pause' = 'pause';
/** 答案是否接收完毕 */
answerEnd = false;
play() {
}
stop() {
}
pause() {
}
destroy() {
}
}
// 自定义事件类型
export type PhotoEventType =
| 'videoStart'
| 'audioStart'
| 'audioBroken'
| 'videoBroken'
| 'liveStatusTrace'
| 'asyncAnswer';
export type PhotoEventTypeData<T extends PhotoEventType> = {
videoStart: [];
audioStart: [];
audioBroken: [];
videoBroken: [];
liveStatusTrace: ['init' | 'ready' | 'wait' | 'closing' | 'pushing'];
asyncAnswer: [PhotoAnswer];
}[T];
export type PhotoEventTypeFn<T extends PhotoEventType> = {
// eslint-disable-next-line no-unused-vars
[K in T]: (...args: PhotoEventTypeData<T>) => void;
}[T];
export class PhotoRole extends EventEmitter {
readonly view: HTMLCanvasElement;
readonly ctx: CanvasRenderingContext2D;
private _webRTCContainer: HTMLDivElement = document.createElement('div');
private _rtc: HwWebRTC | null = null;
private _image: HTMLImageElement = new Image();
private _rtcVideo: HTMLVideoElement | null = null;
private _rtcVideoInfo: {
center: {
x: number;
y: number;
};
width: number;
height: number;
r_w: number;
r_h: number;
} | null = null;
private _liveStatus: 'init' | 'ready' | 'wait' | 'closing' | 'pushing' = 'closing';
private _pollTimeout = -1;
private _answerArgs: PhotoAnswer | null = null;
readonly url: string;
readonly sessionId = guid();
constructor(url: string, view: HTMLCanvasElement) {
super();
this.url = url;
this.view = view;
this.ctx = view.getContext('2d') as CanvasRenderingContext2D;
// this._dialogSession = new Dialog({
// type: 'photo',
// callbacks: {
// chatConnect: (ans) => {
// this._answerArgs = ans;
// console.time('chat');
// },
// chatEnd: (ans) => {
// this._enQueue(this.sessionId, ans._sliceAnswer);
// ans._sliceAnswer = '';
// ans.answerEnd = true;
// console.timeEnd('chat');
// console.log('----------------> answer.length: ', ans.answer.length);
// },
// chatMessage: (message, ans) => {
// ans._typingAnswer.push(message);
// ans._sliceAnswer += message;
// if (/[。,?!;,.?!;]/.test(message) && ans._sliceAnswer.length >= 40) {
// this._enQueue(this.sessionId, ans._sliceAnswer);
// ans._sliceAnswer = '';
// }
// }
// }
// });
}
private _liveTaskQueue: LiveOptions[] = [];
private _isLiveTaskRunning = false;
enQueue(taskId: string, text: string) {
if (text.length < 1) return;
this._liveTaskQueue.push({
imgUrl: this.url,
taskId,
speaker: 'speaker',
languageCode: 'zh-CN',
text,
speed: 10
});
this._runTask();
}
private async _runTask() {
if (this._isLiveTaskRunning) return;
this._isLiveTaskRunning = true;
this.off('liveStatusTrace', this._liveStatusTrace);
this.on('liveStatusTrace', this._liveStatusTrace);
try {
while (this._liveTaskQueue.length) {
const task = this._liveTaskQueue.shift() as LiveOptions;
console.time(task.text);
if (this._liveStatus === 'closing') await this._initLive();
await this._createLive(task);
console.timeEnd(task.text);
}
} catch (error) {
console.error(error);
}
this._isLiveTaskRunning = false;
}
private async _liveStatusTrace() {
if (!this._answerArgs) return;
if (this._liveStatus === 'pushing') {
this._answerArgs.playState = 'playing';
this._typingOutAnswer();
return;
}
if (this._liveStatus === 'wait') {
this._answerArgs.playState = 'pause';
this.emit('asyncAnswer', this._answerArgs)
}
}
private _typingRunner = false;
private async _typingOutAnswer() {
if (!this._answerArgs || this._typingRunner) return;
this._typingRunner = true;
// 加延迟是为了 playing 状态时,能跟声音保持相对同步
await new Promise((resolve) => setTimeout(resolve, 2000));
while (this._answerArgs._typingAnswer.length) {
this._answerArgs.asyncAnswer += this._answerArgs._typingAnswer.shift();
this.emit('asyncAnswer', this._answerArgs)
await new Promise((resolve) => setTimeout(resolve, 100));
}
this._typingRunner = false;
}
private _play(url: string) {
return new Promise<void>((resolve) => {
if (!this._rtc) return;
let isPlaying = false;
let timeout = -1;
this._rtc.once('videoStart', () => {
isPlaying = true;
clearTimeout(timeout);
resolve();
});
const keepCalling = async () => {
if (isPlaying) return;
try {
await this._rtc?.startPlay(url);
if (!isPlaying) {
timeout = setTimeout(keepCalling, 400) as unknown as number;
}
} catch (error) {
timeout = setTimeout(keepCalling, 200) as unknown as number;
}
};
keepCalling();
});
}
draw() {
this.ctx.clearRect(0, 0, this._image.naturalWidth, this._image.naturalHeight);
this.ctx.drawImage(this._image, 0, 0, this._image.naturalWidth, this._image.naturalHeight);
if (this._rtcVideo && this._rtcVideoInfo) {
const { center, r_w, r_h } = this._rtcVideoInfo;
this.ctx.drawImage(this._rtcVideo, center.x - r_w / 2, center.y - r_h / 2, r_w, r_h);
}
}
private async _createLive(options: LiveOptions) {
const resp = (await http({
method: 'POST',
url: `${HOST}/create`,
data: { ...options, isSdk: 1 } // ip: 'http://116.63.168.14:9000'
})) as {
code: number;
taskId: string;
pullUrl: string;
imgInfo: {
center: { x: number; y: number };
r_w: number;
r_h: number;
width: number;
height: number;
};
msg?: string;
};
if (resp.code && resp.code !== 200) {
throw new Error(resp.msg);
}
if (!resp.pullUrl || !resp.taskId) {
throw new Error(`Field is empty: taskId: ${resp.taskId}, pullUrl: ${resp.pullUrl}`);
}
return resp;
}
private async _pushLive(taskId: string) {
const resp = (await http({
method: 'POST',
url: `${HOST}/push`,
data: { taskId }
})) as {
code: number;
taskId: string;
msg?: string;
};
if (resp.code && resp.code !== 200) {
throw new Error(resp.msg);
}
return resp;
}
private async _getLiveStatus(taskId: string) {
const resp = (await http({
method: 'GET',
url: `${HOST}/status`,
params: { taskId }
})) as {
code: number;
status: 'init' | 'ready' | 'wait' | 'closing' | 'pushing';
msg?: string;
};
if (resp.code && resp.code !== 200) {
throw new Error(resp.msg);
}
return resp;
}
private _checkStatus(
taskId: string,
checkStatus: 'init' | 'ready' | 'wait' | 'closing' | 'pushing'
) {
return new Promise<boolean>((resolve) => {
let count = 0;
const keepCalling = async () => {
if (count >= 10) return resolve(false);
count++;
const { status } = await this._getLiveStatus(taskId);
if (status === checkStatus) return resolve(true);
setTimeout(keepCalling, 1000) as unknown as number;
};
keepCalling();
});
}
private _pollStatus(taskId: string, callbacks: () => void) {
const keepCalling = async () => {
try {
const { status } = await this._getLiveStatus(taskId);
this._liveStatus = status;
} catch (error) {
this._liveStatus = 'closing';
}
this._pollTimeout = setTimeout(keepCalling, 200) as unknown as number;
callbacks();
};
clearTimeout(this._pollTimeout);
keepCalling();
}
private async _initLive() {
console.time('init');
console.time('init-_createLive');
const { pullUrl, taskId, imgInfo } = await this._createLive({
imgUrl: this.url,
taskId: this.sessionId,
speaker: 'speaker',
languageCode: 'zh-CN',
audioUrl: ''
});
console.timeEnd('init-_createLive');
this._rtcVideoInfo = imgInfo;
console.time('play');
await this._checkStatus(taskId, 'ready');
await this._play(pullUrl);
await this._pushLive(taskId);
console.timeEnd('play');
this._rtcVideo = this._webRTCContainer.querySelector('video');
// 加延迟是为了 playing 状态时,能跟声音保持相对同步
const initTime = new Date().getTime();
this._pollStatus(this.sessionId, () => {
if (new Date().getTime() - initTime < 1000) return;
console.log('---------------->', this._liveStatus);
this._liveStatus === 'closing' && clearTimeout(this._pollTimeout);
this.emit('liveStatusTrace', this._liveStatus);
});
console.timeEnd('init');
}
private _bindEvents() {
this._rtc?.on('videoStart', () => {
this.emit('videoStart');
});
this._rtc?.on('audioStart', () => {
this.emit('audioStart');
});
this._rtc?.on('audioBroken', () => {
this.emit('audioBroken');
});
this._rtc?.on('videoBroken', () => {
this.emit('videoBroken');
});
}
/**
* 发送事件
* @param event 事件名称
* @param args 事件参数
* @returns 是否成功
*/
emit<T extends PhotoEventType>(event: T, ...args: PhotoEventTypeData<T>): boolean {
return super.emit(event, ...args);
}
/**
* 绑定事件
* @param event 事件名称
* @param fn 事件回调
* @returns this
*/
on<T extends PhotoEventType>(event: T, fn: PhotoEventTypeFn<T>): this {
// fn 可能确实只有一个参数, 只能使用as
return super.on(event, fn as (...args: any[]) => void);
}
get id(): string {
return this.sessionId;
}
async init() {
if (!(await HwWebRTC.isBrowserSupport())) {
throw new Error('WebRTC is not supported');
}
this._webRTCContainer.style.height = '256px';
this._webRTCContainer.style.width = '256px';
document.body.appendChild(this._webRTCContainer);
this._image.src = this.url;
await new Promise((resolve, reject) => {
this._image.onload = resolve;
this._image.onerror = reject;
});
this.ctx.canvas.width = this._image.naturalWidth;
this.ctx.canvas.height = this._image.naturalHeight;
this._webRTCContainer.id = 'webRTCContainer';
this._webRTCContainer.style.display = 'none';
this._rtc = new HwWebRTC(this._webRTCContainer.id);
this._bindEvents();
await this._initLive();
// this._pollStatus(this.sessionId);
}
destroy() {
this._webRTCContainer && document.body.removeChild(this._webRTCContainer);
this._rtc?.stopPlay();
this._rtc?.destroyed();
clearTimeout(this._pollTimeout);
return {
code: 1
};
}
}
...@@ -9,6 +9,18 @@ const usePhotoStore = defineStore('photo', { ...@@ -9,6 +9,18 @@ const usePhotoStore = defineStore('photo', {
state: () => state: () =>
({ ({
list: [ list: [
{
url: new URL('/images/photo/1.png', import.meta.url).href
},
{
url: new URL('/images/photo/2.png', import.meta.url).href
},
{
url: new URL('/images/photo/3.png', import.meta.url).href
},
{
url: new URL('/images/photo/4.png', import.meta.url).href
},
{ {
url: new URL('/2023-11-2/93ffb6a7-ae93-4918-944e-877016ba266b.png', import.meta.url).href url: new URL('/2023-11-2/93ffb6a7-ae93-4918-944e-877016ba266b.png', import.meta.url).href
}, },
......
declare const _default: {
getVersion: any;
checkSystemRequirements: any;
setParameter: any;
createClient: any;
saveLog: any;
setLogLevel: any;
uploadLog: any;
};
export { _default as default };
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "HWLLSPlayer",
"version": "2.3.0",
"main": "lib/HWLLSPlayer.js",
"browser": "lib/HWLLSPlayer.js",
"types": "lib/HWLLSPlayer.d.ts"
}
/* eslint-disable no-unused-vars */
/**
* 错误码(参考 http 响应状态码:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status/200):
* 0 表示成功
* -x 表示失败
* 4xx 未授权/禁止
* 5xx 内部错误
*/
export enum CHAT_ERROR_CODE {
SUCCESS = 0, // 成功
UNKNOWN_ERROR = -1, // 未知错误
INVALID_BEARER_TOKEN = -2, // 无效token
INVALID_APPKEY = -3, // 无效appkey
INVALID_ACTION_PARAMTER = -4, // 无效动作参数
CREATE_PHOTO_ROLE_ERROR = -5, // 创建数字人失败
BUSTLING = -6, // 对话正忙
CREATE_PHOTO_ROLE_FAILED = -7, // 照片数字人创建失败
START_AUDIO_INPUT_ERROR = -8, // 开始语音失败
CHAT_FAILED = -9, // 对话接口返回错误
GET_ROLES_FAILED = -10, // 获取角色列表失败
GET_LANGS_FAILED = -11, // 获取语言列表失败
GET_VOICES_FAILED = -12, // 获取音色列表失败
RESET_LANGS_FAILED = -13, // 重置语言列表失败
SET_SPEED_FAILED = -14, // 设置语速不在有效区间
ACTION_ID_UNDEFINED = -15, // 动作id不存在
TEXT_EMPTY = -16, // 文字为空
FREQUENT_REQUEST = -17, // 请求过于频繁
WORD_EXCEEDING_THE_LIMIT = -18 // 字数超过限制
}
export enum ANSWER_ERROR_CODE {
SUCCESS = 0, // 成功
QUESTION_EMPTY = -1, // 问题为空
ANSWER_EMPTY = -2 // 答案为空
}
import axios from 'axios';
import type { AxiosRequestConfig } from 'axios';
export const axiosInstance = axios.create();
export interface ApiResult<T = unknown> {
error?: boolean;
code?: number;
message?: string;
msg?: string;
data?: T;
[k: string]: any;
}
export default async function http<T>(input: AxiosRequestConfig): Promise<ApiResult<T>> {
try {
const response = await axiosInstance(input);
if (response.status === 200) {
return response.data;
}
return { error: true };
} catch (error) {
return {
code: (error as any).response?.code || (error as any).code,
data: (error as any)?.response?.data
};
}
}
...@@ -6,6 +6,13 @@ export default class Utils { ...@@ -6,6 +6,13 @@ export default class Utils {
static async openExternal(url: string): Promise<void> { static async openExternal(url: string): Promise<void> {
await window.mainApi.send('msgOpenExternalLink', url) await window.mainApi.send('msgOpenExternalLink', url)
} }
static guid() {
function S4() {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
}
return S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4();
}
} }
export const { getCurrentLocale, openExternal } = Utils export const { getCurrentLocale, openExternal, guid} = Utils
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment