This commit is contained in:
zoujiandong 2024-01-22 08:55:30 +08:00
parent 22f3da6c33
commit 2bd2fd31ac
3460 changed files with 136738 additions and 0 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

47
TUICallKit/node_modules/.package-lock.json generated vendored Normal file
View File

@ -0,0 +1,47 @@
{
"name": "@tencentcloud/call-uikit-wechat",
"version": "2.1.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"node_modules/@tencentcloud/chat": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@tencentcloud/chat/-/chat-3.2.4.tgz",
"integrity": "sha512-EC1M5exzI3BIvAt8apAq0/sZbecCaXri6mxE4Wifvzsz7OvJldF9qetMgUcLVC/nyQMt54BIEKnB3LolSHcPTQ=="
},
"node_modules/@tencentcloud/tui-core": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@tencentcloud/tui-core/-/tui-core-2.0.3.tgz",
"integrity": "sha512-kmrNjUobs2cSwRKkOuc1N7hinAB4ka4PZV6VyfO/hPr5iVG8NCKRJvh20Oj10bpYUjeN/2uJR+PuC/s7uPgm4w==",
"dependencies": {
"@tencentcloud/chat": "latest",
"tim-profanity-filter-plugin": "latest",
"tim-upload-plugin": "latest"
}
},
"node_modules/tim-profanity-filter-plugin": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/tim-profanity-filter-plugin/-/tim-profanity-filter-plugin-1.1.0.tgz",
"integrity": "sha512-+49p+n5LJs18rSPBvJFyq/m6ib8wiXvPEx2/Sz3M7hhDXAz69WuZ8cB/painNrN0MKAETf6G5cWa6xtVZfAK7A=="
},
"node_modules/tim-upload-plugin": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/tim-upload-plugin/-/tim-upload-plugin-1.3.0.tgz",
"integrity": "sha512-meoGeB3vOs3FvtBh+ik7zlOj8C03tC6MZEkjc8BKYvgTrCzBWVDXZi9a3X8AM/YtoyuTaeLuEmaLQj1O/5luuw=="
},
"node_modules/trtc-wx-sdk": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/trtc-wx-sdk/-/trtc-wx-sdk-1.1.5.tgz",
"integrity": "sha512-DOXPTekracOtp0e1tlZJtiImkTwUP2PXExp+xW8OUUTvu9F/lw9RVNqj4zsoCYZjBNbWHB0FwN5YT/6dtXVWKw=="
},
"node_modules/tuicall-engine-wx": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/tuicall-engine-wx/-/tuicall-engine-wx-2.1.3.tgz",
"integrity": "sha512-0RwMZ05gONHLzLMYOxz2yqdCB1zdL/vNXMZ63tIXPjsKyFI25DjAk9A0mcyoEuI904dE3Y1qElgAtiOqIFXlxw==",
"dependencies": {
"@tencentcloud/chat": "latest",
"trtc-wx-sdk": "latest"
}
}
}
}

View File

@ -0,0 +1,53 @@
# TUICallkit 组件接入说明
TUICallkit 是小程序音视频通话 UI 组件,通过编写几行代码,就可以为您的小程序应用添加音视频通话功能。
**低于 v3.0.4 版本,参考的 [readme](https://www.npmjs.com/package/@tencentcloud/call-uikit-wechat/v/1.4.4)。**
## 环境准备
- 微信 App iOS 最低版本要求8.0.40
- 微信 App Android 最低版本要求8.0.40
- 小程序基础库最低版本要求2.10.0
- 由于小程序测试号不具备 live-pusher 和 live-player 的使用权限,请使用企业小程序账号申请相关权限进行开发
- 由于微信开发者工具不支持原生组件(即 live-pusher 和 live-player 标签),需要在真机上进行运行体验
## 特性
- ⚡️ 功能全面 —— 支持单人/多人/音频/视频通话、支持视频转音频通话、支持自由选择通话设备
- 🎨 灵活样式 —— 组件开源,可复用逻辑,自定义 UI 样式
- 🛠 优秀生态 —— 与 [TUIKit](https://cloud.tencent.com/document/product/269/79737) 协同使用,可以在 [TIM](https://cloud.tencent.com/document/product/269) 会话中直接发起音视频通话
- 🌍 跨平台 —— 支持 Android、iOS、Web、小程序、Flutter、UniApp 等 [多个开发平台](https://cloud.tencent.com/document/product/647/78742)
- ☁️ 低延迟 —— 腾讯云全球链路资源储备,保证国际链路端到端平均时延 < 300ms
- 🤙🏻 低卡顿 —— 抗丢包率超过 80%、抗网络抖动超过 1000ms弱网环境仍顺畅稳定
- 🌈 高品质 —— 支持 720P、1080P 高清画质70% 丢包率仍可正常视频
## 目录结构
```
TUICallkit
├─ debug // 用来本地测试时生成 userSig
├─ src // UI 组件源码
├─ Components // 呼叫中 UI 组件
└─ TUICallService // UI 组件的逻辑
└─ adapter-vue.ts // 用来适配 vue2、vue3 的适配层
└─ index.ts // UI 组件逻辑的入口
├─ types
├─ package.json
├─ README.md
├─ tuicall-uikit-vue.es.js
├─ tuicall-uikit-vue.umd.js
```
## 使用指引
为方便您的使用,本组件配套多篇使用指引:
- 如果您想要了解 TUICallKit请阅读 [组件介绍 TUICallKit](https://cloud.tencent.com/document/product/647/78742)。
- 如果您想把我们的功能直接嵌入到您的项目中,请阅读 [快速接入 TUICallKit](https://cloud.tencent.com/document/product/647/78912)。
- 如果您想要了解详细 API请阅读 [API 概览](https://cloud.tencent.com/document/product/647/78759)。
## 附录
- 如果你遇到了困难,可以先参阅 [常见问题](https://cloud.tencent.com/document/product/647/78733)。
- 如果发现了示例代码的 bug欢迎提交 issue。
- 欢迎加入 QQ 群:**646165204**,进行技术交流和反馈~
-

View File

@ -0,0 +1,196 @@
import { TUIStore, StoreName, NAME } from "../index";
const {
CALL_STATUS,
CALL_ROLE,
CALL_MEDIA_TYPE,
LOCAL_USER_INFO,
REMOTE_USER_INFO_LIST,
CALLER_USER_INFO,
IS_GROUP,
CALL_DURATION,
PUSHER,
PLAYER,
BIG_SCREEN_USER_ID,
IS_EAR_PHONE,
LOCAL_VIDEO,
MYSELF,
TOAST_INFO,
} = NAME;
Component({
properties: {},
data: {
callStatus: TUIStore.getData(StoreName.CALL, CALL_STATUS), // 通话状态
isGroupCall: TUIStore.getData(StoreName.CALL, IS_GROUP), // 是否群通话
callMediaType: TUIStore.getData(StoreName.CALL, CALL_MEDIA_TYPE), // 通话类型
callDuration: TUIStore.getData(StoreName.CALL, CALL_DURATION), // 通话时长
callRole: TUIStore.getData(StoreName.CALL, CALL_ROLE), // 通话角色
isEarPhone: TUIStore.getData(StoreName.CALL, IS_EAR_PHONE), // 声音模式 听筒/扬声器
bigScreenUserId: TUIStore.getData(StoreName.CALL, BIG_SCREEN_USER_ID), // 大屏显示的用户
localUserInfo: TUIStore.getData(StoreName.CALL, LOCAL_USER_INFO), // 本地用户信息
callerUserInfo: TUIStore.getData(StoreName.CALL, CALLER_USER_INFO),// 邀请者用户信息
remoteUserInfoList: TUIStore.getData(StoreName.CALL, REMOTE_USER_INFO_LIST), // 远端用户信息
pusher: TUIStore.getData(StoreName.CALL, PUSHER), // TRTC 本地流
playerList: TUIStore.getData(StoreName.CALL, PLAYER), // TRTC 远端流
playerProcess: {}, // 经过处理的的远端流(多人通话)
},
methods: {
// 监听通话状态变更回调
handleCallStatusChange(value) {
this.setData({
callStatus: value,
});
},
// 监听是否群组通话变更回调
handleIsGroupChange(value) {
this.setData({
isGroupCall: value,
});
},
// 监听通话类型变更回调
handleCallMediaTypeChange(value) {
this.setData({
callMediaType: value,
});
},
// 监听通话角色变更回调
handleCallRoleChange(value) {
this.setData({
callRole: value,
});
},
// 监听通话时长变更回调
handleCallDurationChange(value) {
this.setData({
callDuration: value,
});
},
// 监听声音模式变更回调
handleEarPhoneChange(value) {
this.setData({
isEarPhone: value,
});
},
// 监听大屏显示的用户变更回调
handleScreenChange(value) {
if (value === LOCAL_VIDEO) {
this.setData({
bigScreenUserId: true,
});
} else {
this.setData({
bigScreenUserId: false,
});
}
},
// 监听本地用户信息变更回调
handleLocalUserInfoChange(value) {
this.setData({
localUserInfo: value,
});
},
// 监听到邀请者信息变更回调
handleCallerUserInfoChange(value) {
this.setData({
callerUserInfo: value,
});
},
// 监听远端用户信息变更回调
handleRemoteUserInfoListChange(value) {
this.setData({
remoteUserInfoList: value,
});
},
// 监听 TRTC 本地流变更回调
handlePusherChange(value) {
this.setData({
pusher: value,
});
},
// 监听 TRTC 远端流变更回调
handlePlayerListChange(value) {
if (this.data.isGroupCall) {
const convertToPlayer = this.convertToObj(value);
this.setData({
playerProcess: convertToPlayer,
});
} else {
this.setData({
playerList: value,
});
}
},
// 监听到弹窗信息变更回调
handleToastInfoChange(value) {
if (value.text) {
this.showToast(value.text, value.type || "info");
}
},
showToast(value, type) {
switch (type) {
case "info":
wx.showToast({
title: value,
icon: "none",
});
break;
default:
break;
}
},
convertToObj(arr = []) {
const tempObject = {};
for (let i = 0; i < arr.length; i++) {
tempObject[arr[i].userID] = arr[i];
}
return tempObject;
},
},
// 生命周期方法
lifetimes: {
attached() {
let that = this;
TUIStore.watch(
StoreName.CALL,
{
[CALL_STATUS]: this.handleCallStatusChange.bind(that),
[IS_GROUP]: this.handleIsGroupChange.bind(that),
[CALL_MEDIA_TYPE]: this.handleCallMediaTypeChange.bind(that),
[CALL_DURATION]: this.handleCallDurationChange.bind(that),
[CALL_ROLE]: this.handleCallRoleChange.bind(that),
[IS_EAR_PHONE]: this.handleEarPhoneChange.bind(that),
[BIG_SCREEN_USER_ID]: this.handleScreenChange.bind(that),
[LOCAL_USER_INFO]: this.handleLocalUserInfoChange.bind(that),
[CALLER_USER_INFO]: this.handleCallerUserInfoChange.bind(that),
[REMOTE_USER_INFO_LIST]: this.handleRemoteUserInfoListChange.bind(that),
[TOAST_INFO]: this.handleToastInfoChange.bind(that),
[PUSHER]: this.handlePusherChange.bind(that),
[PLAYER]: this.handlePlayerListChange.bind(that),
},
{
notifyRangeWhenWatch: MYSELF,
}
);
},
async detached() {
let that = this;
TUIStore.unwatch(StoreName.CALL, {
[CALL_STATUS]: this.handleCallStatusChange.bind(that),
[IS_GROUP]: this.handleIsGroupChange.bind(that),
[CALL_MEDIA_TYPE]: this.handleCallMediaTypeChange.bind(that),
[CALL_DURATION]: this.handleCallDurationChange.bind(that),
[CALL_ROLE]: this.handleCallRoleChange.bind(that),
[IS_EAR_PHONE]: this.handleEarPhoneChange.bind(that),
[BIG_SCREEN_USER_ID]: this.handleScreenChange.bind(that),
[LOCAL_USER_INFO]: this.handleLocalUserInfoChange.bind(that),
[CALLER_USER_INFO]: this.handleCallerUserInfoChange.bind(that),
[REMOTE_USER_INFO_LIST]: this.handleRemoteUserInfoListChange.bind(that),
[TOAST_INFO]: this.handleToastInfoChange.bind(that),
[PUSHER]: this.handlePusherChange.bind(that),
[PLAYER]: this.handlePlayerListChange.bind(that),
});
},
},
});

View File

@ -0,0 +1,9 @@
{
"component": true,
"usingComponents": {
"SingleCall":"./component/SingleCall/SingleCall",
"GroupCall":"./component/GroupCall/GroupCall"
},
"navigationStyle": "custom",
"disableScroll": true
}

View File

@ -0,0 +1,29 @@
<view wx:if="{{callStatus !== 'idle'}}" class="TUICall-container">
<SingleCall
wx:if="{{!isGroupCall}}"
callRole="{{callRole}}"
callStatus ='{{callStatus}}'
pusher="{{pusher}}"
bigScreenUserId="{{bigScreenUserId}}"
playerList="{{playerList}}"
callDuration="{{callDuration}}"
callMediaType="{{callMediaType}}"
localUserInfo="{{localUserInfo}}"
remoteUserInfoList="{{remoteUserInfoList}}"
isEarPhone="{{isEarPhone}}"
></SingleCall>
<GroupCall
wx:if="{{isGroupCall}}"
callRole="{{callRole}}"
callStatus ='{{callStatus}}'
callMediaType="{{callMediaType}}"
callDuration="{{callDuration}}"
pusher="{{pusher}}"
playerList="{{playerList}}"
localUserInfo="{{localUserInfo}}"
callerUserInfo="{{callerUserInfo}}"
remoteUserInfoList="{{remoteUserInfoList}}"
playerProcess="{{playerProcess}}"
isEarPhone="{{isEarPhone}}"
></GroupCall>
</view>

View File

@ -0,0 +1,10 @@
.TUICall-container {
width: 100vw;
height: 100vh;
overflow: hidden;
position: absolute;
top: 0;
left: 0;
z-index: 10;
margin: 0;
}

View File

@ -0,0 +1,116 @@
import { TUICallKitServer } from "../../../TUICallService/index";
const PATH = '../../../static';
Component({
properties: {
callRole: {
type: String,
},
callStatus: {
type: String,
},
callMediaType: {
type: Number,
},
callDuration: {
type: String,
},
pusher: {
type: Object,
},
playerList: {
type: Array,
},
localUserInfo: {
type: Object,
},
callerUserInfo: {
type: Object
},
remoteUserInfoList: {
type: Array,
},
playerProcess: {
type: Object,
},
isEarPhone: {
type: Boolean,
},
},
data: {
renderStreamList: [],
IMG_DEFAULT_AVATAR:`${PATH}/default_avatar.png`,
IMG_LOADING:`${PATH}/loading.png`,
IMG_HANGUP:`${PATH}/hangup.png`,
IMG_ACCEPT: `${PATH}/dialing.png`,
IMG_SPEAKER_FALSE:`${PATH}/speaker-false.png`,
IMG_SPEAKER_TRUE:`${PATH}/speaker-true.png`,
IMG_AUDIO_TRUE:`${PATH}/audio-true.png`,
IMG_AUDIO_FALSE:`${PATH}/audio-false.png`,
IMG_CAMERA_TRUE:`${PATH}/camera-true.png`,
IMG_CAMERA_FALSE:`${PATH}/camera-false.png`,
IMG_TRANS:`${PATH}/trans.png`,
IMG_SWITCH_CAMERA:`${PATH}/switch_camera.png`,
},
observers: {
"localUserInfo, remoteUserInfoList": function (
localUserInfo,
remoteUserInfoList
) {
this.setData({
renderStreamList: [localUserInfo, ...remoteUserInfoList],
});
},
},
methods: {
async accept() {
await TUICallKitServer.accept();
},
async hangup() {
await TUICallKitServer.hangup();
},
async reject() {
await TUICallKitServer.reject();
},
async switchCamera() {
await TUICallKitServer.switchCamera();
},
async microPhoneHandler() {
if (this.data.localUserInfo.isAudioAvailable) {
await TUICallKitServer.closeMicrophone();
} else {
await TUICallKitServer.openMicrophone();
}
},
async cameraHandler() {
if (this.data.localUserInfo.isVideoAvailable) {
await TUICallKitServer.closeCamera();
} else {
await TUICallKitServer.openCamera();
}
},
async toggleSoundMode() {
await TUICallKitServer.setSoundMode();
},
pusherStateChangeHandler(e) {
TUICallKitServer._tuiCallEngine._pusherStateChangeHandler(e);
},
pusherNetStatus(e) {
TUICallKitServer._tuiCallEngine._pusherNetStatus(e);
},
pusherErrorHandler(e) {
TUICallKitServer._tuiCallEngine._pusherNetStatus(e);
},
pusherAudioVolumeNotify(e) {
TUICallKitServer._tuiCallEngine._pusherAudioVolumeNotify(e);
},
playerStateChange(e) {
TUICallKitServer._tuiCallEngine._playerStateChange(e);
},
playNetStatus(e) {
TUICallKitServer._tuiCallEngine._playNetStatus(e);
},
playerAudioVolumeNotify(e) {
TUICallKitServer._tuiCallEngine._playerAudioVolumeNotify(e);
},
},
});

View File

@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@ -0,0 +1,285 @@
<!-- 语音通话 -->
<view class="incoming-call audio-call transition-animation" wx:if="{{callMediaType === 1}}">
<!-- 主叫方呼叫状态 -->
<swiper class="swiper" wx:if="{{callRole === 'caller' && callStatus === 'calling' }}" indicator-dots="{{renderStreamList.length/4 > 1}}" indicator-color="white" indicator-active-color="black">
<block wx:for="{{(renderStreamList.length)/4}}" wx:key="*this" wx:for-index="pos">
<swiper-item class="invite-calling-list">
<view wx:for="{{renderStreamList}}" wx:key="userID" class="invite-calling-item" wx:if="{{index >= pos*4 && index < pos*4+4}}">
<view id="{{item.userId}}" class="invite-calling-item-message" wx:if="{{item.userId !== localUserInfo.userId}}">
<view class="invite-calling-item-loadimg">
<image src="{{IMG_LOADING}}"></image>
</view>
<view class="invite-calling-item-id">{{item.displayUserInfo || item.nick || item.userId}}</view>
</view>
<image class="avatar" src="{{item.avatar || IMG_DEFAULT_AVATAR}}" binderror="handleErrorImage" />
<view class="invite-calling-item-id">{{item.displayUserInfo || item.nick || item.userId}}</view>
</view>
</swiper-item>
</block>
</swiper>
<!-- 被叫方呼叫状态 -->
<view wx:if="{{callRole !== 'caller' && callStatus === 'calling' }}">
<view class="invite-calling-single">
<image class="avatar" src="{{callerUserInfo.avatar || IMG_DEFAULT_AVATAR}}" id="{{renderStreamList[0].userId}}" binderror="handleErrorImage" />
<view class="tips">{{callerUserInfo.nick || callerUserInfo.userId}}</view>
<view class="invite-txt">邀请你参加多人通话</view>
</view>
<view class="invite-other-txt">参与通话的有:</view>
<view class="invite-other-list">
<view class="invite-other-item" wx:for="{{renderStreamList}}" wx:key="item">
<image class="avatar" src="{{item.avatar || IMG_DEFAULT_AVATAR}}" binderror="handleErrorImage" />
<view class="invite-other-item-name">{{item.displayUserInfo || item.nick || item.userId}}</view>
</view>
</view>
</view>
<!-- 主叫/被叫方通话状态 -->
<view wx:if="{{callStatus === 'connected' }}">
<view>
<swiper class="swiper" indicator-dots="{{renderStreamList.length/4 > 1}}" indicator-color="white" indicator-active-color="black">
<block wx:for="{{(renderStreamList.length)/4}}" wx:key="*this" wx:for-index="pos">
<swiper-item class="invite-calling-list">
<view wx:for="{{renderStreamList}}" wx:key="userID" class="invite-calling-item" wx:if="{{index >= pos*4 && index < pos*4+4}}">
<view id="{{item.userId}}" class="invite-calling-item-message" wx:if="{{!item.isEnter}}">
<view class="invite-calling-item-loadimg">
<image src="{{IMG_LOADING}}"></image>
</view>
<view class="invite-calling-item-id">
{{item.displayUserInfo || item.nick || item.userId}}
</view>
</view>
<image class="avatar" src="{{item.avatar || IMG_DEFAULT_AVATAR}}" binderror="handleErrorImage" />
<view class="player-control">
<image src="{{item.avatar || IMG_DEFAULT_AVATAR}}"></image>
<view class="name">{{item.displayUserInfo || item.nick || item.userId}}</view>
</view>
</view>
</swiper-item>
</block>
</swiper>
</view>
<view class="pusher-audio">
<swiper class="swiper" indicator-dots="{{renderStreamList.length/4 > 1}}" indicator-color="white" indicator-active-color="black">
<block wx:for="{{(renderStreamList.length)/4}}" wx:key="*this" wx:for-index="pos">
<swiper-item class="invite-calling-list">
<view wx:for="{{renderStreamList}}" wx:key="userID" class="invite-calling-item" wx:if="{{index >= pos*4 && index < pos*4+4}}">
<view id="{{item.userId}}" class="invite-calling-item-message" wx:if="{{!item.isEnter}}">
<view class="invite-calling-item-loadimg">
<image src="{{IMG_LOADING}}"></image>
</view>
<image class="avatar" src="{{item.avatar || IMG_DEFAULT_AVATAR}}" binderror="handleErrorImage" />
<view class="invite-calling-item-id">
{{item.displayUserInfo || item.nick || item.userId}}
</view>
</view>
<view wx:else>
<!-- 本地流 -->
<view wx:if="{{item.userId===localUserInfo.userId}}" class="pusher-audio" data-screen="pusher" catch:tap="toggleViewSize">
<live-pusher class="pusher-audio" url="{{pusher.url}}" mode="{{pusher.mode}}" autopush="{{true}}" enable-camera="{{pusher.enableCamera}}" enable-mic="{{true}}" muted="{{!pusher.enableMic}}" enable-agc="{{true}}" enable-ans="{{true}}" enable-ear-monitor="{{pusher.enableEarMonitor}}" auto-focus="{{pusher.enableAutoFocus}}" zoom="{{pusher.enableZoom}}" min-bitrate="{{pusher.minBitrate}}" max-bitrate="{{pusher.maxBitrate}}" video-width="{{pusher.videoWidth}}" video-height="{{pusher.videoHeight}}" beauty="{{pusher.beautyLevel}}" whiteness="{{pusher.whitenessLevel}}" orientation="{{pusher.videoOrientation}}" aspect="{{pusher.videoAspect}}" device-position="{{pusher.frontCamera}}" remote-mirror="{{pusher.enableRemoteMirror}}" local-mirror="{{pusher.localMirror}}" background-mute="{{pusher.enableBackgroundMute}}" audio-quality="{{pusher.audioQuality}}" audio-volume-type="{{pusher.audioVolumeType}}" audio-reverb-type="{{pusher.audioReverbType}}" waiting-image="{{pusher.waitingImage}}" beauty-style="{{pusher.beautyStyle}}" filter="{{pusher.filter}}" bindstatechange="pusherStateChangeHandler" bindnetstatus="pusherNetStatus" binderror="pusherErrorHandler" bindaudiovolumenotify="pusherAudioVolumeNotify" />
<view class="player-control">
<image src="{{item.avatar || IMG_DEFAULT_AVATAR}}"></image>
<view class="name">我</view>
</view>
</view>
<!-- 远端流 -->
<view class="pusher-audio" wx:else>
<live-player wx:if="{{playerProcess[item.userId]}}" wx:if="{{ playerProcess[item.userId].hasAudio || playerProcess[item.userId].hasVideo }}" class="{{callMediaType === 1 ? 'pusher-audio' : 'pusher-ownvideo'}}" id="{{playerProcess[item.userId].id}}" data-userid="{{playerProcess[item.userId].userID}}" data-streamid="{{playerProcess[item.userId].streamID}}" data-streamtype="{{playerProcess[item.userId].streamType}}" src="{{playerProcess[item.userId].src}}" mode="RTC" autoplay="{{playerProcess[item.userId].autoplay}}" mute-audio="{{playerProcess[item.userId].muteAudio}}" mute-video="{{playerProcess[item.userId].muteVideo}}" orientation="{{playerProcess[item.userId].orientation}}" object-fit="{{playerProcess[item.userId].objectFit}}" background-mute="{{playerProcess[item.userId].enableBackgroundMute}}" min-cache="{{playerProcess[item.userId].minCache}}" max-cache="{{playerProcess[item.userId].maxCache}}" sound-mode="{{isEarPhone?'ear':'speaker'}}" enable-recv-message="{{playerProcess[item.userId].enableRecvMessage}}" auto-pause-if-navigate="{{playerProcess[item.userId].autoPauseIfNavigate}}" auto-pause-if-open-native="{{playerProcess[item.userId].autoPauseIfOpenNative}}" bindstatechange="playerStateChange" bindfullscreenchange="playerFullscreenChange" bindnetstatus="playNetStatus" bindaudiovolumenotify="playerAudioVolumeNotify" />
</view>
</view>
</view>
</swiper-item>
</block>
</swiper>
</view>
</view>
<!-- 呼叫阶段按钮 -->
<view wx:if="{{callStatus === 'calling'}}" class="footer">
<view wx:if="{{callRole !== 'caller'}}" class="btn-operate">
<view class="button-container">
<view class="call-operate" style="background-color: red" catch:tap="reject">
<image src="{{IMG_HANGUP}}" />
</view>
<view style="margin-top:10px;color: #666666">挂断</view>
</view>
<view class="button-container">
<view class="call-operate" catch:tap="accept">
<image src="{{IMG_ACCEPT}}" />
</view>
<view style="margin-top:10px;color: #666666">接听</view>
</view>
</view>
<view wx:if="{{callRole === 'caller'}}" class="btn-operate">
<view class="button-container">
<view class="call-operate" style="background-color: red" catch:tap="hangup">
<image src="{{IMG_HANGUP}}" />
</view>
<view style="margin-top:10px;color: #666666">挂断</view>
</view>
</view>
</view>
<!-- 通话阶段按钮 -->
<view wx:if="{{callStatus === 'connected'}}" class="handle-btns">
<view class="other-view black">
<text>{{callDuration}}</text>
</view>
<view class="btn-list">
<view class="button-container">
<view class="btn-normal" bindtap="microPhoneHandler">
<image class="btn-image" src="{{localUserInfo.isAudioAvailable? IMG_AUDIO_TRUE: IMG_AUDIO_FALSE}} "></image>
</view>
<view>麦克风</view>
</view>
<view class="button-container">
<view class="btn-hangup" bindtap="hangup">
<image class="btn-image" src="{{IMG_HANGUP}}"></image>
</view>
<view>挂断</view>
</view>
<view class="button-container">
<view class="btn-normal" bindtap="toggleSoundMode">
<image class="btn-image" src="{{isEarPhone ? IMG_SPEAKER_FALSE: IMG_SPEAKER_TRUE}} "></image>
</view>
<text>扬声器</text>
</view>
</view>
</view>
</view>
<!-- 视频通话 -->
<view class="invite-call audio-call transition-animation" wx:if="{{callMediaType === 2}}">
<!-- 通话状态 -->
<view wx:if="{{callStatus === 'connected' }}">
<swiper class="swiper" indicator-dots="{{renderStreamList.length/4 > 1}}" indicator-color="white" indicator-active-color="black">
<block wx:for="{{(renderStreamList.length)/4}}" wx:key="*this" wx:for-index="pos">
<swiper-item class="invite-calling-list">
<view wx:for="{{renderStreamList}}" wx:key="userID" class="invite-calling-item" wx:if="{{index >= pos*4 && index < pos*4+4}}">
<view id="{{item.userId}}" class="invite-calling-item-message" wx:if="{{!item.isEnter}}">
<view class="invite-calling-item-loadimg">
<image src="{{IMG_LOADING}}"></image>
</view>
<image class="avatar" src="{{item.avatar || IMG_DEFAULT_AVATAR}}" binderror="handleErrorImage" />
<view class="invite-calling-item-id">{{item.displayUserInfo || item.nick || item.userId}}</view>
</view>
<view wx:else>
<!-- 本地流 -->
<view wx:if="{{item.userId === localUserInfo.userId}}" class="play-item" data-screen="pusher">
<live-pusher class="pusher-ownvideo" url="{{pusher.url}}" mode="{{pusher.mode}}" autopush="{{true}}" enable-camera="{{pusher.enableCamera}}" enable-mic="{{true}}" muted="{{!pusher.enableMic}}" enable-agc="{{true}}" enable-ans="{{true}}" enable-ear-monitor="{{pusher.enableEarMonitor}}" auto-focus="{{pusher.enableAutoFocus}}" zoom="{{pusher.enableZoom}}" min-bitrate="{{pusher.minBitrate}}" max-bitrate="{{pusher.maxBitrate}}" video-width="{{pusher.videoWidth}}" video-height="{{pusher.videoHeight}}" beauty="{{pusher.beautyLevel}}" whiteness="{{pusher.whitenessLevel}}" orientation="{{pusher.videoOrientation}}" aspect="{{pusher.videoAspect}}" device-position="{{pusher.frontCamera}}" remote-mirror="{{pusher.enableRemoteMirror}}" local-mirror="{{pusher.localMirror}}" background-mute="{{pusher.enableBackgroundMute}}" audio-quality="{{pusher.audioQuality}}" audio-volume-type="{{pusher.audioVolumeType}}" audio-reverb-type="{{pusher.audioReverbType}}" waiting-image="{{pusher.waitingImage}}" beauty-style="{{pusher.beautyStyle}}" filter="{{pusher.filter}}" bindstatechange="pusherStateChangeHandler" bindnetstatus="pusherNetStatus" binderror="pusherErrorHandler" bindaudiovolumenotify="pusherAudioVolumeNotify" />
<view class="player-control">
<image src="{{item.avatar || IMG_DEFAULT_AVATAR}}"></image>
<view class="name">我</view>
</view>
</view>
<!-- 远端流 -->
<view class="play-item" wx:else>
<live-player class="pusher-ownvideo" wx:if="{{playerProcess[item.userId]}}" wx:if="{{ playerProcess[item.userId].hasAudio || playerProcess[item.userId].hasVideo }}" id="{{playerProcess[item.userId].id}}" data-userid="{{playerProcess[item.userId].userID}}" data-streamid="{{playerProcess[item.userId].streamID}}" data-streamtype="{{playerProcess[item.userId].streamType}}" src="{{playerProcess[item.userId].src}}" mode="RTC" autoplay="{{playerProcess[item.userId].autoplay}}" mute-audio="{{playerProcess[item.userId].muteAudio}}" mute-video="{{playerProcess[item.userId].muteVideo}}" orientation="{{playerProcess[item.userId].orientation}}" object-fit="{{playerProcess[item.userId].objectFit}}" background-mute="{{playerProcess[item.userId].enableBackgroundMute}}" min-cache="{{playerProcess[item.userId].minCache}}" max-cache="{{playerProcess[item.userId].maxCache}}" sound-mode="{{isEarPhone?'ear':'speaker'}}" enable-recv-message="{{playerProcess[item.userId].enableRecvMessage}}" auto-pause-if-navigate="{{playerProcess[item.userId].autoPauseIfNavigate}}" auto-pause-if-open-native="{{playerProcess[item.userId].autoPauseIfOpenNative}}" bindstatechange="playerStateChange" bindfullscreenchange="playerFullscreenChange" bindnetstatus="playNetStatus" bindaudiovolumenotify="playerAudioVolumeNotify" />
<view class="player-control">
<image src="{{item.avatar || IMG_DEFAULT_AVATAR}}"></image>
<view class="name">{{item.displayUserInfo || item.nick || item.userId}}</view>
</view>
</view>
</view>
</view>
</swiper-item>
</block>
</swiper>
</view>
<!-- 邀请信息 -->
<view class="invite-calling">
<!-- 主叫方 -->
<swiper class="swiper" wx:if="{{callRole === 'caller' && callStatus === 'calling' }}" indicator-dots="{{renderStreamList.length/4 > 1}}" indicator-color="white" indicator-active-color="black">
<block wx:for="{{(renderStreamList.length)/4}}" wx:key="*this" wx:for-index="pos">
<swiper-item class="invite-calling-list">
<view wx:for="{{renderStreamList}}" wx:key="userID" class="invite-calling-item" wx:if="{{index >= pos*4 && index < pos*4+4}}">
<view id="{{item.userId}}" class="invite-calling-item-message" wx:if="{{item.userId !== localUserInfo.userId}}">
<view class="invite-calling-item-loadimg">
<image src="{{IMG_LOADING}}"></image>
</view>
<view class="invite-calling-item-id">{{item.displayUserInfo || item.nick || item.userId}}</view>
</view>
<image class="avatar" src="{{item.avatar || IMG_DEFAULT_AVATAR}}" binderror="handleErrorImage" />
<view class="invite-calling-item-id">{{item.displayUserInfo || item.nick || item.userId}}</view>
</view>
</swiper-item>
</block>
</swiper>
<!-- 被叫方 -->
<view wx:if="{{callRole !== 'caller' && callStatus === 'calling' }}">
<view class="invite-calling-single">
<image class="avatar" src="{{callerUserInfo.avatar || IMG_DEFAULT_AVATAR}}" id="{{renderStreamList[0].userId}}" binderror="handleErrorImage" />
<view class="tips">{{ callerUserInfo.nick || callerUserInfo.userId }}</view>
<view class="invite-txt">邀请你参加多人通话</view>
</view>
<view class="invite-other-txt">参与通话的有:</view>
<view class="invite-other-list">
<view class="invite-other-item" wx:for="{{renderStreamList}}" wx:key="item">
<image class="avatar" src="{{item.avatar || IMG_DEFAULT_AVATAR}}" binderror="handleErrorImage" />
<view class="invite-other-item-name">{{ item.displayUserInfo || item.nick || item.userId}}</view>
</view>
</view>
</view>
<!-- 呼叫阶段按钮 -->
<view wx:if="{{callStatus === 'calling' }}" class="footer">
<view class="btn-operate" wx:if="{{callRole === 'caller'}}">
<view class="btn-operate-item">
<view class="btn-container">
<view class="call-operate" catch:tap="hangup">
<image src="{{IMG_HANGUP}}" />
</view>
</view>
<text style="color: #666666">挂断</text>
</view>
</view>
<view class="btn-operate" wx:if="{{callRole !== 'caller'}}">
<view class="btn-operate-item">
<view class="call-operate" style="background-color: red" catch:tap="reject">
<image src="{{IMG_HANGUP}}" />
</view>
<text style="color: #666666">挂断</text>
</view>
<view class="btn-operate-item">
<view class="call-operate" catch:tap="accept">
<image src="{{IMG_ACCEPT}}" />
</view>
<text style="color: #666666">接听</text>
</view>
</view>
</view>
<!-- 通话阶段按钮 -->
<view wx:if="{{callStatus === 'connected' }}" class="handle-btns">
<view class="other-view white">
<text>{{callDuration}}</text>
</view>
<view class="btn-list">
<view class="button-container">
<view class="btn-normal" bindtap="microPhoneHandler">
<image class="btn-image" src="{{localUserInfo.isAudioAvailable ? IMG_AUDIO_TRUE: IMG_AUDIO_FALSE}} "></image>
</view>
<view class="white">麦克风</view>
</view>
<view class="button-container">
<view class="btn-normal" bindtap="toggleSoundMode">
<image class="btn-image" src="{{isEarPhone ? IMG_SPEAKER_FALSE: IMG_SPEAKER_TRUE}} "></image>
</view>
<text class="white">扬声器</text>
</view>
<view class="button-container">
<view class="btn-normal" bindtap="cameraHandler">
<image class="btn-image" src="{{localUserInfo.isVideoAvailable? IMG_CAMERA_TRUE: IMG_CAMERA_FALSE}} "></image>
</view>
<text class="white">摄像头</text>
</view>
</view>
<view class="btn-list">
<view class="btn-list-item other-view">
<view class="btn-container">
<view class="btn-hangup" bindtap="hangup">
<image class="btn-image" src="{{IMG_HANGUP}}"></image>
</view>
<view wx:if="{{pusher.enableCamera}}" class="invite-calling-header-left">
<image src="{{IMG_SWITCH_CAMERA}}" data-device="{{pusher.frontCamera}}" catch:tap="toggleSwitchCamera" />
</view>
</view>
<text class="white">挂断</text>
</view>
</view>
</view>
</view>
</view>

View File

@ -0,0 +1,754 @@
.transition-animation {
transform: translateY(-100%);
animation: slideInDown 0.5s ease forwards;
}
@keyframes slideInDown {
from {
transform: translateY(-100%);
}
to {
transform: translateY(0);
}
}
.footer {
position: absolute;
bottom: 5vh;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.button-container {
display: flex;
flex-direction: column;
text-align: center;
}
.btn-operate {
display: flex;
justify-content: space-between;
}
.btn-operate-item {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 20px;
}
.btn-operate-item text {
font-size: 14px;
color: #f0e9e9;
padding: 5px;
letter-spacing: 0;
font-weight: 400;
}
.call-switch text {
padding: 5px;
color: #f0e9e9;
font-size: 14px;
}
.call-operate {
width: 8vh;
height: 8vh;
border-radius: 8vh;
margin: 0 15vw;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
}
.call-switch .call-operate {
width: 4vh;
height: 3vh;
}
.call-operate image {
width: 100%;
height: 100%;
background: none;
}
.tips {
font-size: 20px;
color: #ffffff;
letter-spacing: 0;
margin: 0 auto;
font-weight: 600;
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tips-subtitle {
font-family: PingFangSC-Regular;
font-size: 14px;
color: #ffffff;
letter-spacing: 0;
text-align: right;
font-weight: 400;
}
.invite-call {
position: absolute;
top: 0;
z-index: 100;
width: 100%;
height: 187px;
}
.invite-call .local-video {
width: 100px;
height: 187px;
}
.invite-call .invite-calling {
position: absolute;
top: 0;
z-index: 101;
width: 100vw;
height: 100vh;
}
.invite-calling-header {
margin-top: 107px;
display: flex;
justify-content: flex-end;
padding: 0 16px;
}
.btn-container {
display: flex;
align-items: center;
position: relative;
}
.invite-calling-header-left {
position: absolute;
right: 0;
}
.invite-calling-header-left image {
width: 32px;
height: 32px;
}
.invite-calling-header-right {
display: flex;
align-items: center;
}
.invite-calling-header-message {
display: flex;
flex-direction: column;
padding: 0 16px;
}
.invite-calling-header-right image {
width: 100px;
height: 100px;
border-radius: 12px;
}
.invite-calling .footer {
position: absolute;
bottom: 5vh;
width: 100%;
}
.invite-calling .btn-operate {
display: flex;
justify-content: center;
align-items: center;
}
.hidden {
display: none;
}
.trtc-calling {
width: 100vw;
height: 100vh;
overflow: hidden;
margin: 0;
z-index: 99;
}
.audio-call {
width: 100vw;
height: 100vh;
position: absolute;
top: 0;
z-index: 100;
background: #ffffff;
}
.audio-call > .btn-operate {
display: flex;
justify-content: center;
}
.audio-call > image {
width: 40vw;
height: 40vw;
display: block;
margin: 20vw 30vw;
margin-top: 40vw;
}
.invite-calling-single > image {
width: 120px;
height: 120px;
border-radius: 12px;
display: block;
margin: 120px auto 15px;
}
.invite-calling-single .tips {
width: 100%;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 20px;
color: #333333;
letter-spacing: 0;
font-weight: 500;
}
.invite-calling-single .tips-subtitle {
height: 20px;
font-family: PingFangSC-Regular;
font-size: 14px;
color: #97989c;
letter-spacing: 0;
font-weight: 400;
text-align: center;
}
.swiper {
margin-top: 107px;
min-height: 374px;
}
.invite-calling-list {
display: flex;
flex-wrap: wrap;
width: 100%;
justify-content: flex-start;
}
.invite-calling-item {
flex: 0.5;
min-width: 50%;
height: 187px;
position: relative;
}
.invite-calling-item image {
width: 100%;
height: 100%;
}
.invite-calling-item-message {
position: absolute;
top: 0;
left: 0;
float: left;
background: rgba(0, 0, 0, 0.6);
width: 100%;
height: 100%;
z-index: 2;
}
.invite-calling-item-loadimg {
position: absolute;
left: calc(50% - 20px);
top: calc(50% - 20px);
width: 40px;
height: 40px;
-webkit-transform: rotate(360deg);
animation: rotation 2s linear infinite;
-moz-animation: rotation 2s linear infinite;
-webkit-animation: rotation 2s linear infinite;
-o-animation: rotation 2s linear infinite;
}
@-webkit-keyframes rotation {
from {
-webkit-transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
}
}
.invite-calling-item-loadimg image {
width: 100%;
height: 100%;
}
.invite-calling-item-id {
position: absolute;
left: 2%;
bottom: 2%;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 12px;
color: #ffffff;
}
.avatar {
background-color: black;
}
/* 被叫者 */
.invite-txt {
width: 126px;
height: 20px;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 14px;
color: #333333;
letter-spacing: 0;
margin: 16px auto 60px auto;
}
.invite-other-txt {
width: 112px;
height: 20px;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 14px;
color: #333333;
letter-spacing: 0;
margin: 0 auto 24px auto;
}
.invite-other-list {
position: absolute;
left: 14vw;
margin-top: 0 auto;
display: flex;
flex-wrap: wrap;
width: 272px;
justify-content: center;
flex-wrap: wrap;
}
.invite-other-item {
flex: 0.25;
text-align: center;
max-width: 64px;
margin: 2px;
}
.invite-other-item image {
border-radius: 10%;
max-width: 64px;
height: 64px;
}
.invite-other-item-name {
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 12px;
color: #666666;
letter-spacing: 0;
line-height: 18px;
}
/* 全屏设置 */
.TUICalling-connected-layout {
width: 100%;
height: 100%;
}
.TUICalling-connected-video {
width: 100%;
height: 180%;
}
/* 本地音频 */
.stream-box {
float: left;
width: 187px;
height: 187px;
position: absolute;
top: 13vh;
}
/* 远端音频列表 */
.swiper {
margin-top: 107px;
min-height: 374px;
}
.invite-calling-list {
display: flex;
flex-wrap: wrap;
width: 100%;
justify-content: flex-start;
}
.invite-calling-item {
flex: 0.5;
min-width: 50%;
height: 187px;
position: relative;
}
.invite-calling-item image {
width: 100%;
height: 100%;
}
/* 本地视频 */
.play-item {
width: 100%;
height: 187px;
position: relative;
background-color: #000000;
}
.pusher-ownvideo {
width: 100%;
height: 100%;
}
/* 远端视频列表 */
.swiper-list {
min-height: 189px;
}
.player-list {
display: flex;
flex-wrap: wrap;
width: 100%;
justify-content: center;
}
.player-item {
flex: 0.5;
min-width: 50%;
min-height: 187px;
}
/* 音量图标 */
.player-control {
background-color: rgba(0, 0, 0, 0.4);
border-radius: 0 6px 6px 0;
color: #fff;
z-index: 999;
position: absolute;
bottom: 0px;
left: 0px;
display: flex;
align-items: center;
height: 32px;
max-width: 50%;
z-index: 99;
}
.player-control image {
width: 32px;
height: 32px;
}
.player-control .name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-left: 10px;
margin-right: 10px;
flex: 1;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 12px;
color: #ffffff;
}
.control-buttons {
display: flex;
}
.icon-button {
margin-right: 10px;
}
/* 菜单 */
.handle-btns {
position: absolute;
bottom: 44px;
width: 100vw;
z-index: 3;
display: flex;
flex-direction: column;
}
.handle-btns .btn-list {
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
}
.button-container {
display: flex;
flex-direction: column;
text-align: center;
}
.btn-normal {
width: 8vh;
height: 8vh;
box-sizing: border-box;
display: flex;
flex-direction: column;
/* background: white; */
justify-content: center;
align-items: center;
border-radius: 50%;
}
.btn-image {
width: 100%;
height: 100%;
background: none;
}
.btn-hangup {
width: 8vh;
height: 8vh;
/*background: #f75c45;*/
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
}
.btn-hangup > .btn-image {
width: 100%;
height: 100%;
background: none;
}
.TRTCCalling-call-audio {
width: 100%;
height: 100%;
}
.btn-footer {
position: relative;
}
.btn-footer .multi-camera {
width: 32px;
height: 32px;
}
.btn-footer .camera {
width: 64px;
height: 64px;
position: fixed;
left: 16px;
top: 107px;
display: flex;
justify-content: center;
align-items: center;
background: rgba(255, 255, 255, 0.7);
}
.btn-footer .camera .camera-image {
width: 32px;
height: 32px;
}
.audio {
padding-top: 15vh;
background: #ffffff;
}
.pusher-audio {
width: 0;
height: 0;
}
.player-audio {
width: 0;
height: 0;
}
.other-view {
display: flex;
flex-direction: column;
align-items: center;
font-size: 18px;
letter-spacing: 0;
font-weight: 400;
padding: 16px;
}
.white {
font-weight: 400;
font-size: 14px;
color: #666666;
padding: 5px;
}
.black {
color: #000000;
padding: 5px;
}
.TRTCCalling-call-audio-box {
margin-top: 100px;
display: flex;
flex-wrap: wrap;
width: 100%;
justify-content: center;
}
.TRTCCalling-calling-list {
flex: 0.5;
/*设置最小宽度,才会让元素排不下,导致换行排列*/
min-width: 50%;
min-height: 187px;
position: relative;
}
.TRTCCalling-calling-list image {
width: 100%;
height: 100%;
}
.TRTCCalling-calling-item-id {
position: absolute;
left: 2%;
bottom: 2%;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 12px;
color: #ffffff;
}
.btn-list-item {
flex: 1;
display: flex;
justify-content: center;
padding: 16px 0;
}
.btn-image-small {
transform: scale(0.7);
}
.avatar {
background: #dddddd;
}
.btn-container {
display: flex;
align-items: center;
position: relative;
}
.invite-calling-header-left {
position: absolute;
right: -88px;
}
.invite-calling-header-left image {
width: 32px;
height: 32px;
}
.call-switch .call-operate {
width: 4vh;
height: 3vh;
}
.call-operate image {
width: 100%;
height: 100%;
background: none;
}
.call-switch text {
padding: 0;
font-size: 14px;
}
.btn-operate-item {
display: flex;
flex-direction: column;
align-items: center;
}
.btn-operate-item text {
padding: 8px 0;
font-size: 18px;
color: #666666;
letter-spacing: 0;
font-weight: 400;
font-size: 14px;
}
.invite-calling-item-message {
position: absolute;
top: 0;
left: 0;
float: left;
background: rgba(0, 0, 0, 0.6);
width: 100%;
height: 100%;
z-index: 2;
}
.invite-calling-item-loadimg {
position: absolute;
left: calc(50% - 20px);
top: calc(50% - 20px);
width: 40px;
height: 40px;
-webkit-transform: rotate(360deg);
animation: rotation 2s linear infinite;
-moz-animation: rotation 2s linear infinite;
-webkit-animation: rotation 2s linear infinite;
-o-animation: rotation 2s linear infinite;
}
@-webkit-keyframes rotation {
from {
-webkit-transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
}
}
.invite-calling-item-loadimg image {
width: 100%;
height: 100%;
}
.invite-calling-item-id {
position: absolute;
left: 2%;
bottom: 2%;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 12px;
color: #ffffff;
}

View File

@ -0,0 +1,107 @@
import { TUICallKitServer } from "../../../TUICallService/index";
const PATH = '../../../static';
Component({
properties: {
callRole: {
type: String,
},
callStatus: {
type: String,
},
callMediaType: {
type: Number,
},
callDuration: {
type: String,
},
pusher: {
type: Object,
},
playerList: {
type: Array,
},
localUserInfo: {
type:Object
},
remoteUserInfoList: {
type: Array,
},
isEarPhone: {
type: Boolean,
},
bigScreenUserId: {
type: Boolean
}
},
data:{
IMG_DEFAULT_AVATAR:`${PATH}/default_avatar.png`,
IMG_HANGUP:`${PATH}/hangup.png`,
IMG_ACCEPT: `${PATH}/dialing.png`,
IMG_SPEAKER_FALSE:`${PATH}/speaker-false.png`,
IMG_SPEAKER_TRUE:`${PATH}/speaker-true.png`,
IMG_AUDIO_TRUE:`${PATH}/audio-true.png`,
IMG_AUDIO_FALSE:`${PATH}/audio-false.png`,
IMG_CAMERA_TRUE:`${PATH}/camera-true.png`,
IMG_CAMERA_FALSE:`${PATH}/camera-false.png`,
IMG_TRANS:`${PATH}/trans.png`,
IMG_SWITCH_CAMERA:`${PATH}/switch_camera.png`,
},
methods: {
async accept() {
await TUICallKitServer.accept();
},
async hangup() {
await TUICallKitServer.hangup();
},
async reject() {
await TUICallKitServer.reject();
},
async switchCamera() {
await TUICallKitServer.switchCamera();
},
async switchCallMediaType() {
await TUICallKitServer.switchCallMediaType();
},
async microPhoneHandler() {
if (this.data.localUserInfo.isAudioAvailable) {
await TUICallKitServer.closeMicrophone();
} else {
await TUICallKitServer.openMicrophone();
}
},
async cameraHandler() {
if (this.data.localUserInfo.isVideoAvailable) {
await TUICallKitServer.closeCamera();
} else {
await TUICallKitServer.openCamera();
}
},
async toggleSoundMode() {
await TUICallKitServer.setSoundMode();
},
toggleViewSize() {
TUICallKitServer.switchScreen(this.data.bigScreenUserId ? 'player':'localVideo')
},
pusherStateChangeHandler(e) {
TUICallKitServer._tuiCallEngine._pusherStateChangeHandler(e);
},
pusherNetStatus(e) {
TUICallKitServer._tuiCallEngine._pusherNetStatus(e);
},
pusherErrorHandler(e) {
TUICallKitServer._tuiCallEngine._pusherNetStatus(e);
},
pusherAudioVolumeNotify(e) {
TUICallKitServer._tuiCallEngine._pusherAudioVolumeNotify(e);
},
playerStateChange(e) {
TUICallKitServer._tuiCallEngine._playerStateChange(e);
},
playNetStatus(e) {
TUICallKitServer._tuiCallEngine._playNetStatus(e);
},
playerAudioVolumeNotify(e) {
TUICallKitServer._tuiCallEngine._playerAudioVolumeNotify(e);
},
},
});

View File

@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@ -0,0 +1,172 @@
<!-- 音频通话 -->
<view class="{{callMediaType === 1?'audio-call':'invite-call' }} transition-animation">
<!-- 本地流 -->
<view class="{{callMediaType === 1 ? 'pusher-audio' : (bigScreenUserId || callStatus === 'calling' ? 'pusher-video' : 'player')}}" data-screen="pusher" catch:tap="toggleViewSize">
<live-pusher class="{{callMediaType === 1?'pusher-audio':'live'}}" url="{{pusher.url}}" mode="{{pusher.mode}}" autopush="{{true}}" enable-camera="{{pusher.enableCamera}}" enable-mic="{{true}}" muted="{{!pusher.enableMic}}" enable-agc="{{true}}" enable-ans="{{true}}" enable-ear-monitor="{{pusher.enableEarMonitor}}" auto-focus="{{pusher.enableAutoFocus}}" zoom="{{pusher.enableZoom}}" min-bitrate="{{pusher.minBitrate}}" max-bitrate="{{pusher.maxBitrate}}" video-width="{{pusher.videoWidth}}" video-height="{{pusher.videoHeight}}" beauty="{{pusher.beautyLevel}}" whiteness="{{pusher.whitenessLevel}}" orientation="{{pusher.videoOrientation}}" aspect="{{pusher.videoAspect}}" device-position="{{pusher.frontCamera}}" remote-mirror="{{pusher.enableRemoteMirror}}" local-mirror="{{pusher.localMirror}}" background-mute="{{pusher.enableBackgroundMute}}" audio-quality="{{pusher.audioQuality}}" audio-volume-type="{{pusher.audioVolumeType}}" audio-reverb-type="{{pusher.audioReverbType}}" waiting-image="{{pusher.waitingImage}}" beauty-style="{{pusher.beautyStyle}}" filter="{{pusher.filter}}" bindstatechange="pusherStateChangeHandler" bindnetstatus="pusherNetStatus" binderror="pusherErrorHandler" bindaudiovolumenotify="pusherAudioVolumeNotify" />
</view>
<!-- 远端流 -->
<view wx:for="{{playerList}}" wx:key="streamID" class="view-container player-container" catch:tap="toggleViewSize">
<view class="{{callMediaType === 1 ? 'player-audio' : (!bigScreenUserId ? 'pusher-video' : 'player')}}" data-screen="player">
<live-player class="live" wx:if="{{ item.hasAudio || item.hasVideo }}" id="{{item.id}}" data-userid="{{item.userID}}" data-streamid="{{item.streamID}}" data-streamtype="{{item.streamType}}" src="{{item.src}}" mode="RTC" autoplay="{{item.autoplay}}" mute-audio="{{item.muteAudio}}" mute-video="{{item.muteVideo}}" orientation="{{item.orientation}}" object-fit="{{item.objectFit}}" background-mute="{{item.enableBackgroundMute}}" min-cache="{{item.minCache}}" max-cache="{{item.maxCache}}" sound-mode="{{isEarPhone?'ear':'speaker'}}" enable-recv-message="{{item.enableRecvMessage}}" auto-pause-if-navigate="{{item.autoPauseIfNavigate}}" auto-pause-if-open-native="{{item.autoPauseIfOpenNative}}" bindstatechange="playerStateChange" bindfullscreenchange="playerFullscreenChange" bindnetstatus="playNetStatus" bindaudiovolumenotify="playerAudioVolumeNotify" />
</view>
</view>
<!-- 语言通话邀请信息 -->
<view wx:if="{{callMediaType === 1}}" class="invite-calling-single">
<image class="avatar" src="{{remoteUserInfoList[0].avatar || IMG_DEFAULT_AVATAR}}" id="{{remoteUserInfoList[0].userId}}" binderror="handleErrorImage" />
<view class="tips">{{remoteUserInfoList[0].displayUserInfo}}</view>
<view wx:if="{{callRole === 'caller' && callStatus === 'calling' }}" class="tips-subtitle">等待对方接受</view>
</view>
<!-- 视频通话邀请信息 -->
<view wx:if="{{callStatus === 'calling' && callMediaType === 2}}" class="invite-calling">
<view class="invite-calling-header">
<view class="invite-calling-header-right">
<view class="invite-calling-header-message">
<label class="tips">
{{remoteUserInfoList[0].displayUserInfo}}
</label>
<text class="tips-subtitle" wx:if="{{callRole !== 'caller'}}">邀请你视频通话</text>
<text class="tips-subtitle" wx:else>等待对方接受</text>
</view>
<image class="avatar" src="{{remoteUserInfoList[0].avatar || IMG_DEFAULT_AVATAR}}" id="{{remoteUserInfoList[0].userId}}" binderror="handleErrorImage" />
</view>
</view>
</view>
<!-- 语音通话呼叫状态按钮 -->
<view wx:if="{{callStatus === 'calling' && callMediaType === 1}}" class="footer">
<view wx:if="{{callRole !== 'caller'}}" class="btn-operate">
<view class="button-container">
<view class="call-operate" style="background-color: red" catch:tap="reject">
<image src="{{IMG_HANGUP}}" />
</view>
<view style="margin-top:10px">挂断</view>
</view>
<view class="button-container">
<view class="call-operate" catch:tap="accept">
<image src="{{IMG_ACCEPT}}" />
</view>
<view style="margin-top:10px">接听</view>
</view>
</view>
<view wx:if="{{callRole === 'caller'}}" class="btn-operate">
<view class="button-container">
<view class="call-operate" style="background-color: red" catch:tap="hangup">
<image src="{{IMG_HANGUP}}" />
</view>
<view style="margin-top:10px">挂断</view>
</view>
</view>
</view>
<!-- 视频通话呼叫状态按钮 -->
<view wx:if="{{callStatus === 'calling' && callMediaType === 2}}" class="invite-calling">
<view class="footer">
<view class="btn-operate" wx:if="{{callRole === 'caller'}}">
<view class="btn-operate-item call-switch" catch:tap="switchCallMediaType">
<view class="call-operate">
<image src="{{IMG_TRANS}}" />
</view>
<text>切到语音通话</text>
</view>
</view>
<view class="btn-operate" wx:if="{{callRole === 'caller'}}">
<view class="btn-operate-item">
<view class="btn-container">
<view class="call-operate" catch:tap="hangup">
<image src="{{IMG_HANGUP}}" />
</view>
<view class="invite-calling-header-left">
<image src="{{IMG_SWITCH_CAMERA}}" data-device="{{pusher.frontCamera}}" catch:tap="switchCamera" />
</view>
</view>
<text>挂断</text>
</view>
</view>
<view class="btn-operate" wx:if="{{callRole !== 'caller'}}">
<view class="btn-operate-item">
<view class="call-operate" style="background-color: red" catch:tap="reject">
<image src="{{IMG_HANGUP}}" />
</view>
<text>挂断</text>
</view>
<view class="btn-operate-item">
<view class="call-operate" catch:tap="accept">
<image src="{{IMG_ACCEPT}}" />
</view>
<text>接听</text>
</view>
</view>
</view>
</view>
<!-- 语音通话接通状态按钮 -->
<view wx:if="{{callStatus === 'connected' && callMediaType === 1}}" class="handle-btns">
<view class="other-view black">
<text>{{callDuration}}</text>
</view>
<view class="btn-list">
<view class="button-container">
<view class="btn-normal" bindtap="microPhoneHandler">
<image class="btn-image" src="{{localUserInfo.isAudioAvailable? IMG_AUDIO_TRUE: IMG_AUDIO_FALSE}} "></image>
</view>
<view>麦克风</view>
</view>
<view class="button-container">
<view class="btn-hangup" bindtap="hangup">
<image class="btn-image" src="{{IMG_HANGUP}}"></image>
</view>
<view>挂断</view>
</view>
<view class="button-container">
<view class="btn-normal" bindtap="toggleSoundMode">
<image class="btn-image" src="{{isEarPhone ? IMG_SPEAKER_FALSE: IMG_SPEAKER_TRUE}} "></image>
</view>
<text>扬声器</text>
</view>
</view>
</view>
<!-- 视频通话接听状态按钮 -->
<view wx:if="{{callStatus === 'connected' && callMediaType === 2}}" class="invite-calling">
<view class="handle-btns">
<view class="other-view white">
<text>{{callDuration}}</text>
</view>
<view class="btn-operate-item call-switch" catch:tap="switchCallMediaType">
<view class="call-operate">
<image src="{{IMG_TRANS}}" />
</view>
<text>切到语音通话</text>
</view>
<view class="btn-list">
<view class="button-container" bindtap="microPhoneHandler">
<view class="btn-normal">
<image class="btn-image" src="{{localUserInfo.isAudioAvailable? IMG_AUDIO_TRUE: IMG_AUDIO_FALSE}} "></image>
</view>
<view class="white">麦克风</view>
</view>
<view class="button-container" bindtap="toggleSoundMode">
<view class="btn-normal">
<image class="btn-image" src="{{isEarPhone ? IMG_SPEAKER_FALSE: IMG_SPEAKER_TRUE}} "></image>
</view>
<text class="white">扬声器</text>
</view>
<view class="button-container" bindtap="cameraHandler">
<view class="btn-normal">
<image class="btn-image" src="{{localUserInfo.isVideoAvailable ? IMG_CAMERA_TRUE: IMG_CAMERA_FALSE}} "></image>
</view>
<text class="white">摄像头</text>
</view>
</view>
<view class="btn-list">
<view class="btn-list-item other-view">
<view class="btn-container">
<view class="btn-hangup" bindtap="hangup">
<image class="btn-image" src="{{IMG_HANGUP}}"></image>
</view>
<view wx:if="{{localUserInfo.isVideoAvailable}}" class="invite-calling-connected">
<image src="{{IMG_SWITCH_CAMERA}}" data-device="{{pusher.frontCamera}}" catch:tap="switchCamera" />
</view>
</view>
<text class="white">挂断</text>
</view>
</view>
</view>
</view>
</view>

View File

@ -0,0 +1,508 @@
.footer {
position: absolute;
bottom: 5vh;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.button-container {
display: flex;
flex-direction: column;
text-align: center;
}
.btn-operate {
display: flex;
justify-content: space-between;
z-index: 9999;
}
.btn-operate-item {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 20px;
}
.btn-operate-item text {
font-size: 14px;
color: #f0e9e9;
padding: 5px;
letter-spacing: 0;
font-weight: 400;
}
.call-switch text {
padding: 5px;
color: #f0e9e9;
font-size: 14px;
}
.call-operate {
width: 8vh;
height: 8vh;
border-radius: 8vh;
margin: 0 15vw;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
}
.call-switch .call-operate {
width: 4vh;
height: 3vh;
}
.call-operate image {
width: 100%;
height: 100%;
background: none;
}
.tips {
font-size: 20px;
color: #ffffff;
letter-spacing: 0;
margin: 0 auto;
/* text-shadow: 0 1px 2px rgba(0,0,0,0.40); */
font-weight: 600;
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tips-subtitle {
font-family: PingFangSC-Regular;
font-size: 14px;
color: #ffffff;
letter-spacing: 0;
text-align: right;
/* text-shadow: 0 1px 2px rgba(0,0,0,0.30); */
font-weight: 400;
}
.invite-call {
position: absolute;
top: 0;
z-index: 100;
width: 100vw;
height: 100vh;
background-color: black;
}
.transition-animation {
transform: translateY(-100%);
animation: slideInDown 0.5s ease forwards;
}
@keyframes slideInDown {
from {
transform: translateY(-100%);
}
to {
transform: translateY(0);
}
}
.invite-call .local-video {
width: 100vw;
height: 100vh;
}
.invite-call .invite-calling {
position: absolute;
top: 0;
z-index: 101;
width: 100vw;
height: 100vh;
}
.invite-calling-header {
margin-top: 107px;
display: flex;
justify-content: flex-end;
padding: 0 16px;
}
.btn-container {
display: flex;
align-items: center;
position: relative;
}
.invite-calling-header-left {
position: absolute;
right: 0;
}
.invite-calling-header-left image {
width: 32px;
height: 32px;
}
.invite-calling-header-right {
display: flex;
align-items: center;
}
.invite-calling-header-message {
display: flex;
flex-direction: column;
padding: 0 16px;
}
.invite-calling-header-right image {
width: 100px;
height: 100px;
border-radius: 12px;
}
.invite-calling .footer {
position: absolute;
bottom: 5vh;
width: 100%;
}
.invite-calling .btn-operate {
display: flex;
justify-content: center;
align-items: center;
}
.hidden {
display: none;
}
.trtc-calling {
width: 100vw;
height: 100vh;
overflow: hidden;
margin: 0;
z-index: 99;
}
.audio-call {
width: 100vw;
height: 100vh;
position: absolute;
top: 0;
z-index: 100;
background: #ffffff;
}
.audio-call > .btn-operate {
display: flex;
justify-content: center;
}
.audio-call > image {
width: 40vw;
height: 40vw;
display: block;
margin: 20vw 30vw;
margin-top: 40vw;
}
.invite-calling-single > image {
width: 120px;
height: 120px;
border-radius: 12px;
display: block;
margin: 140px auto 15px;
/* margin: 20vw 30vw; */
}
.invite-calling-single .tips {
width: 100%;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 20px;
color: #333333;
letter-spacing: 0;
font-weight: 500;
}
.invite-calling-single .tips-subtitle {
height: 20px;
font-family: PingFangSC-Regular;
font-size: 14px;
color: #97989c;
letter-spacing: 0;
font-weight: 400;
text-align: center;
}
.invite-calling-list {
justify-content: flex-start;
}
.invite-calling-item {
position: relative;
margin: 0 12px;
}
.invite-calling-item image {
width: 100px;
height: 100px;
border-radius: 12px;
}
.invite-calling-item-message {
position: absolute;
background: rgba(0, 0, 0, 0.5);
width: 100px;
height: 100px;
top: 0;
left: 0;
z-index: 2;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.avatar {
background: #dddddd;
}
.player {
position: absolute;
right: 16px;
top: 107px;
width: 100px;
height: 178px;
padding: 16px;
z-index: 999;
}
.pusher-video {
position: absolute;
width: 100%;
height: 100%;
z-index: 1;
}
.stream-box {
position: relative;
float: left;
width: 50vw;
height: 260px;
/* background-color: #f75c45; */
z-index: 3;
}
.handle-btns {
position: absolute;
bottom: 44px;
width: 100vw;
z-index: 3;
display: flex;
flex-direction: column;
}
.handle-btns .btn-list {
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
}
.button-container {
display: flex;
flex-direction: column;
text-align: center;
}
.btn-normal {
width: 8vh;
height: 8vh;
box-sizing: border-box;
display: flex;
flex-direction: column;
/* background: white; */
justify-content: center;
align-items: center;
border-radius: 50%;
}
.btn-image {
width: 100%;
height: 100%;
background: none;
}
.btn-hangup {
width: 8vh;
height: 8vh;
/*background: #f75c45;*/
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
}
.btn-hangup > .btn-image {
width: 100%;
height: 100%;
background: none;
}
.TRTCCalling-call-audio {
width: 100%;
height: 100%;
}
.btn-footer {
position: relative;
}
.btn-footer .multi-camera {
width: 32px;
height: 32px;
}
.btn-footer .camera {
width: 64px;
height: 64px;
position: fixed;
left: 16px;
top: 107px;
display: flex;
justify-content: center;
align-items: center;
background: rgba(255, 255, 255, 0.7);
}
.btn-footer .camera .camera-image {
width: 32px;
height: 32px;
}
.TUICalling-connected-layout {
width: 100%;
height: 100%;
}
.audio {
padding-top: 15vh;
background: #ffffff;
}
.pusher-audio {
width: 0;
height: 0;
}
.player-audio {
width: 0;
height: 0;
}
.live {
width: 100%;
height: 100%;
}
.other-view {
display: flex;
flex-direction: column;
align-items: center;
font-size: 18px;
letter-spacing: 0;
font-weight: 400;
padding: 16px;
}
.white {
color: #f0e9e9;
padding: 5px;
font-size: 14px;
}
.black {
color: #000000;
padding: 5px;
}
.TRTCCalling-call-audio-box {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.mutil-img {
justify-content: flex-start !important;
}
.TRTCCalling-call-audio-img {
display: flex;
flex-direction: column;
align-items: center;
}
.TRTCCalling-call-audio-img > image {
width: 25vw;
height: 25vw;
margin: 0 4vw;
border-radius: 4vw;
position: relative;
}
.TRTCCalling-call-audio-img text {
font-size: 20px;
color: #333333;
letter-spacing: 0;
font-weight: 500;
}
.btn-list-item {
flex: 1;
display: flex;
justify-content: center;
padding: 16px 0;
}
.btn-image-small {
transform: scale(0.7);
}
.avatar {
background: #dddddd;
}
.btn-container {
display: flex;
align-items: center;
position: relative;
}
.invite-calling-connected {
position: absolute;
right: -88px;
}
.invite-calling-connected image {
width: 32px;
height: 32px;
}
.call-switch .call-operate {
width: 4vh;
height: 3vh;
}
.call-operate image {
width: 100%;
height: 100%;
background: none;
}
.call-switch text {
padding: 0;
font-size: 14px;
}
.btn-operate-item {
display: flex;
flex-direction: column;
align-items: center;
}
.btn-operate-item text {
padding: 8px 0;
font-size: 18px;
color: #ffffff;
letter-spacing: 0;
font-weight: 400;
font-size: 14px;
}

View File

@ -0,0 +1,15 @@
import { IBellParams } from '../interface/index';
export declare class BellContext {
private _bellContext;
private _isMuteBell;
private _calleeBellFilePath;
private _callRole;
private _callStatus;
constructor();
setBellSrc(): void;
setBellProperties(bellParams: IBellParams): void;
play(): Promise<void>;
stop(): Promise<void>;
setBellMute(enable: boolean): Promise<void>;
destroy(): void;
}

View File

@ -0,0 +1,106 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BellContext = void 0;
const index_1 = require("../const/index");
const common_utils_1 = require("../utils/common-utils");
const DEFAULT_CALLER_BELL_FILEPATH = '/TUICallKit/static/phone_dialing.mp3';
const DEFAULT_CALLEE_BELL_FILEPATH = '/TUICallKit/static/phone_ringing.mp3';
class BellContext {
constructor() {
this._bellContext = null;
this._isMuteBell = false;
this._calleeBellFilePath = DEFAULT_CALLEE_BELL_FILEPATH;
this._callRole = index_1.CallRole.UNKNOWN;
this._callStatus = index_1.CallStatus.IDLE;
// @ts-ignore
this._bellContext = wx.createInnerAudioContext();
this._bellContext.loop = true;
}
setBellSrc() {
// @ts-ignore
const fs = wx.getFileSystemManager();
try {
let playBellFilePath = DEFAULT_CALLER_BELL_FILEPATH;
if (this._callRole === index_1.CallRole.CALLEE) {
playBellFilePath = this._calleeBellFilePath || DEFAULT_CALLEE_BELL_FILEPATH;
}
fs.readFileSync(playBellFilePath, 'utf8', 0);
this._bellContext.src = playBellFilePath;
}
catch (error) {
console.warn(`${index_1.NAME.PREFIX}Failed to setBellSrc, ${error}`);
}
}
setBellProperties(bellParams) {
this._callRole = bellParams.callRole || this._callRole;
this._callStatus = bellParams.callStatus || this._callStatus;
this._calleeBellFilePath = bellParams.calleeBellFilePath || this._calleeBellFilePath;
// undefined/false || isMuteBell => isMuteBell (不符合预期)
this._isMuteBell = (0, common_utils_1.isUndefined)(bellParams.isMuteBell) ? this._isMuteBell : bellParams.isMuteBell;
}
play() {
return __awaiter(this, void 0, void 0, function* () {
try {
if (this._callStatus !== index_1.CallStatus.CALLING) {
return;
}
this.setBellSrc();
if (this._callRole === index_1.CallRole.CALLEE && !this._isMuteBell) {
yield this._bellContext.play();
}
if (this._callRole === index_1.CallRole.CALLER) {
yield this._bellContext.play();
}
}
catch (error) {
console.warn(`${index_1.NAME.PREFIX}Failed to play audio file, ${error}`);
}
});
}
stop() {
return __awaiter(this, void 0, void 0, function* () {
try {
this._bellContext.stop();
}
catch (error) {
console.warn(`${index_1.NAME.PREFIX}Failed to stop audio file, ${error}`);
}
});
}
setBellMute(enable) {
return __awaiter(this, void 0, void 0, function* () {
if (this._callStatus !== index_1.CallStatus.CALLING && this._callRole !== index_1.CallRole.CALLEE) {
return;
}
if (enable) {
yield this.stop();
}
else {
yield this.play();
}
});
}
destroy() {
try {
this._isMuteBell = false;
this._calleeBellFilePath = '';
this._callRole = index_1.CallRole.UNKNOWN;
this._callStatus = index_1.CallStatus.IDLE;
this._bellContext.destroy();
this._bellContext = null;
}
catch (error) {
console.warn(`${index_1.NAME.PREFIX}Failed to destroy, ${error}`);
}
}
}
exports.BellContext = BellContext;

View File

@ -0,0 +1,97 @@
import { ITUICallService, ICallParams, IGroupCallParams, ICallbackParam, ISelfInfoParams, IInviteUserParams, IJoinInGroupCallParams, IInitParams } from '../interface/ICallService';
import { LanguageType, LOG_LEVEL, VideoDisplayMode, VideoResolution } from '../const/index';
import { ITUIGlobal } from '../interface/ITUIGlobal';
import { ITUIStore } from '../interface/ITUIStore';
declare const TUIGlobal: ITUIGlobal;
declare const TUIStore: ITUIStore;
export { TUIGlobal, TUIStore };
export default class TUICallService implements ITUICallService {
static instance: TUICallService;
_tuiCallEngine: any;
private _tim;
private _TUICore;
private _timerId;
private _bellContext;
constructor();
static getInstance(): TUICallService;
init(params: IInitParams): Promise<void>;
destroyed(): Promise<void>;
call(callParams: ICallParams): Promise<void>;
groupCall(groupCallParams: IGroupCallParams): Promise<void>;
inviteUser(params: IInviteUserParams): Promise<void>;
joinInGroupCall(params: IJoinInGroupCallParams): Promise<void>;
getTUICallEngineInstance(): any;
setLogLevel(level: LOG_LEVEL): void;
setLanguage(language: LanguageType): void;
enableFloatWindow(enable: boolean): void;
setSelfInfo(params: ISelfInfoParams): Promise<void>;
setCallingBell(filePath?: string): Promise<void>;
enableMuteMode(enable: boolean): Promise<void>;
accept(): Promise<void>;
hangup(): Promise<void>;
reject(): Promise<void>;
openCamera(videoViewDomID: string): Promise<void>;
closeCamera(): Promise<void>;
openMicrophone(): Promise<void>;
closeMicrophone(): Promise<void>;
switchScreen(userId: string): void;
switchCallMediaType(): Promise<void>;
switchCamera(): Promise<void>;
setSoundMode(type?: string): void;
getTim(): any;
private _addListenTuiCallEngineEvent;
private _removeListenTuiCallEngineEvent;
private _handleError;
private _handleNewInvitationReceived;
private _handleUserAccept;
private _handleUserEnter;
private _callerChangeToConnected;
private _handleUserLeave;
private _unNormalEventsManager;
private _handleInviteeReject;
private _handleNoResponse;
private _handleLineBusy;
private _handleCallingCancel;
private _handleCallingEnd;
private _handleSDKReady;
private _handleKickedOut;
private _handleCallTypeChange;
private _messageSentByMe;
private _handleUserUpdate;
private _handleCallError;
beforeCalling: ((...args: any[]) => void) | undefined;
afterCalling: ((...args: any[]) => void) | undefined;
onMinimized: ((...args: any[]) => void) | undefined;
onMessageSentByMe: ((...args: any[]) => void) | undefined;
kickedOut: ((...args: any[]) => void) | undefined;
statusChanged: ((...args: any[]) => void) | undefined;
setCallback(params: ICallbackParam): void;
toggleMinimize(): void;
private _executeExternalBeforeCalling;
private _executeExternalAfterCalling;
setVideoDisplayMode(displayMode: VideoDisplayMode): void;
setVideoResolution(resolution: VideoResolution): Promise<void>;
private _handleExceptionExit;
private _setLocalUserInfoAudioVideoAvailable;
private _updateCallStoreBeforeCall;
private _updateCallStoreAfterCall;
private _resetCurrentDevice;
private _resetCallStore;
private _noDevicePermissionToast;
private _startTimer;
private _updateCallDuration;
private _stopTimer;
private _deleteRemoteUser;
private _analyzeEventData;
getGroupMemberList(count: number, offset: number): Promise<any>;
getGroupProfile(): Promise<any>;
private _handleCallStatusChange;
private _watchTUIStore;
private _unwatchTUIStore;
bindTUICore(TUICore: any): void;
private _callTUIService;
onNotifyEvent(eventName: string, subKey: string): Promise<void>;
onCall(method: String, params: any): Promise<void>;
private _handleTUICoreOnClick;
onGetExtension(extensionID: string, params: any): any[];
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
import { CallMediaType, CallStatus } from '../const/index';
export declare function initialUI(): void;
export declare function checkRunPlatform(): void;
export declare function initAndCheckRunEnv(): void;
export declare function beforeCall(type: CallMediaType, that: any): Promise<CallStatus.IDLE | CallStatus.CALLING>;
export declare function handlePackageError(error: any): void;

View File

@ -0,0 +1,76 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.handlePackageError = exports.beforeCall = exports.initAndCheckRunEnv = exports.checkRunPlatform = exports.initialUI = void 0;
const index_1 = require("../const/index");
function initialUI() {
// 收起键盘
// @ts-ignore
wx.hideKeyboard({
complete: () => { },
});
}
exports.initialUI = initialUI;
;
// 检测运行时环境, 当是微信开发者工具时, 提示用户需要手机调试
function checkRunPlatform() {
// @ts-ignore
const systemInfo = wx.getSystemInfoSync();
if (systemInfo.platform === 'devtools') {
// 当前运行在微信开发者工具里
// @ts-ignore
wx.showModal({
icon: 'none',
title: '运行环境提醒',
content: '微信开发者工具不支持原生推拉流组件(即 <live-pusher> 和 <live-player> 标签),请使用真机调试或者扫码预览。',
showCancel: false,
});
}
}
exports.checkRunPlatform = checkRunPlatform;
;
function initAndCheckRunEnv() {
initialUI(); // miniProgram 收起键盘, 隐藏 tabBar
checkRunPlatform(); // miniProgram 检测运行时环境
}
exports.initAndCheckRunEnv = initAndCheckRunEnv;
function beforeCall(type, that) {
return __awaiter(this, void 0, void 0, function* () {
try {
initAndCheckRunEnv();
// 检查设备权限
const deviceMap = {
microphone: true,
camera: type === index_1.CallMediaType.VIDEO,
};
const hasDevicePermission = yield that._tuiCallEngine.deviceCheck(deviceMap); // miniProgram 检查设备权限
return hasDevicePermission ? index_1.CallStatus.CALLING : index_1.CallStatus.IDLE;
}
catch (error) {
console.debug(error);
return index_1.CallStatus.IDLE;
}
});
}
exports.beforeCall = beforeCall;
// 套餐问题提示, 小程序最低需要群组通话版, 1v1 通话版本使用 TRTC 就会报错
function handlePackageError(error) {
if ((error === null || error === void 0 ? void 0 : error.code) === -1002) {
// @ts-ignore
wx.showModal({
icon: 'none',
title: 'error',
content: (error === null || error === void 0 ? void 0 : error.message) || '',
showCancel: false,
});
}
}
exports.handlePackageError = handlePackageError;

View File

@ -0,0 +1,9 @@
import { IUserInfo } from '../interface/ICallService';
import { ITUIStore } from '../interface/ITUIStore';
export declare function setDefaultUserInfo(userId: string, domId?: string): IUserInfo;
export declare function getMyProfile(myselfUserId: string, tim: any, TUIStore: any): Promise<IUserInfo>;
export declare function getRemoteUserProfile(userIdList: Array<string>, tim: any, TUIStore: any): Promise<any>;
export declare function generateText(TUIStore: ITUIStore, key: string, prefix?: string, suffix?: string): string;
export declare function generateStatusChangeText(TUIStore: ITUIStore): string;
export declare function getGroupMemberList(groupID: string, tim: any, count: any, offset: any): Promise<any>;
export declare function getGroupProfile(groupID: string, tim: any): Promise<any>;

View File

@ -0,0 +1,167 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getGroupProfile = exports.getGroupMemberList = exports.generateStatusChangeText = exports.generateText = exports.getRemoteUserProfile = exports.getMyProfile = exports.setDefaultUserInfo = void 0;
const index_1 = require("../const/index");
const index_2 = require("../locales/index");
// 设置默认的 UserInfo 信息
function setDefaultUserInfo(userId, domId) {
const userInfo = {
userId,
nick: '',
avatar: '',
remark: '',
displayUserInfo: '',
isAudioAvailable: false,
isVideoAvailable: false,
isEnter: false,
domId: domId || userId,
};
return domId ? userInfo : Object.assign(Object.assign({}, userInfo), { isEnter: false }); // localUserInfo 没有 isEnter, remoteUserInfoList 有 isEnter
}
exports.setDefaultUserInfo = setDefaultUserInfo;
// 获取个人用户信息
function getMyProfile(myselfUserId, tim, TUIStore) {
var _a, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
let localUserInfo = setDefaultUserInfo(myselfUserId, index_1.NAME.LOCAL_VIDEO);
try {
if (!tim)
return localUserInfo;
const res = yield tim.getMyProfile();
const currentLocalUserInfo = TUIStore === null || TUIStore === void 0 ? void 0 : TUIStore.getData(index_1.StoreName.CALL, index_1.NAME.LOCAL_USER_INFO); // localUserInfo may have been updated
if ((res === null || res === void 0 ? void 0 : res.code) === 0) {
localUserInfo = Object.assign(Object.assign(Object.assign({}, localUserInfo), currentLocalUserInfo), { userId: (_a = res === null || res === void 0 ? void 0 : res.data) === null || _a === void 0 ? void 0 : _a.userID, nick: (_b = res === null || res === void 0 ? void 0 : res.data) === null || _b === void 0 ? void 0 : _b.nick, avatar: (_c = res === null || res === void 0 ? void 0 : res.data) === null || _c === void 0 ? void 0 : _c.avatar });
}
return localUserInfo;
}
catch (error) {
console.error(`${index_1.NAME.PREFIX}getMyProfile failed, error: ${error}.`);
return localUserInfo;
}
});
}
exports.getMyProfile = getMyProfile;
// 获取远端用户列表信息
function getRemoteUserProfile(userIdList, tim, TUIStore) {
return __awaiter(this, void 0, void 0, function* () {
let remoteUserInfoList = userIdList.map((userId) => setDefaultUserInfo(userId));
try {
if (!tim)
return remoteUserInfoList;
const res = yield tim.getFriendProfile({ userIDList: userIdList });
if (res.code === 0) {
const { friendList = [], failureUserIDList = [] } = res.data;
let unFriendList = failureUserIDList.map((obj) => obj.userID);
if (failureUserIDList.length > 0) {
const res = yield tim.getUserProfile({ userIDList: failureUserIDList.map((obj) => obj.userID) });
if ((res === null || res === void 0 ? void 0 : res.code) === 0) {
unFriendList = (res === null || res === void 0 ? void 0 : res.data) || [];
}
}
const currentRemoteUserInfoList = TUIStore === null || TUIStore === void 0 ? void 0 : TUIStore.getData(index_1.StoreName.CALL, index_1.NAME.REMOTE_USER_INFO_LIST); // remoteUserInfoList may have been updated
const tempFriendIdList = friendList.map((obj) => obj.userID);
const tempUnFriendIdList = unFriendList.map((obj) => obj.userID);
remoteUserInfoList = userIdList.map((userId) => {
var _a, _b, _c, _d, _e, _f, _g;
const defaultUserInfo = setDefaultUserInfo(userId);
const friendListIndex = tempFriendIdList.indexOf(userId);
const unFriendListIndex = tempUnFriendIdList.indexOf(userId);
let remark = '';
let nick = '';
let displayUserInfo = '';
let avatar = '';
if (friendListIndex !== -1) {
remark = ((_a = friendList[friendListIndex]) === null || _a === void 0 ? void 0 : _a.remark) || '';
nick = ((_c = (_b = friendList[friendListIndex]) === null || _b === void 0 ? void 0 : _b.profile) === null || _c === void 0 ? void 0 : _c.nick) || '';
displayUserInfo = remark || nick || defaultUserInfo.userId || '';
avatar = ((_e = (_d = friendList[friendListIndex]) === null || _d === void 0 ? void 0 : _d.profile) === null || _e === void 0 ? void 0 : _e.avatar) || '';
}
if (unFriendListIndex !== -1) {
nick = ((_f = unFriendList[unFriendListIndex]) === null || _f === void 0 ? void 0 : _f.nick) || '';
displayUserInfo = nick || defaultUserInfo.userId || '';
avatar = ((_g = unFriendList[unFriendListIndex]) === null || _g === void 0 ? void 0 : _g.avatar) || '';
}
const userInfo = currentRemoteUserInfoList.find(subObj => subObj.userId === userId) || {};
return Object.assign(Object.assign(Object.assign({}, defaultUserInfo), userInfo), { remark, nick, displayUserInfo, avatar });
});
}
return remoteUserInfoList;
}
catch (error) {
console.error(`${index_1.NAME.PREFIX}getRemoteUserProfile failed, error: ${error}.`);
return remoteUserInfoList;
}
});
}
exports.getRemoteUserProfile = getRemoteUserProfile;
// 生成弹框提示文案
function generateText(TUIStore, key, prefix, suffix) {
const isGroup = TUIStore.getData(index_1.StoreName.CALL, index_1.NAME.IS_GROUP);
let callTips = `${(0, index_2.t)(key)}`;
if (isGroup) {
callTips = prefix ? `${prefix} ${callTips}` : callTips;
callTips = suffix ? `${callTips} ${suffix}` : callTips;
}
return callTips;
}
exports.generateText = generateText;
// 生成 statusChange 抛出的字符串
function generateStatusChangeText(TUIStore) {
const callStatus = TUIStore.getData(index_1.StoreName.CALL, index_1.NAME.CALL_STATUS);
if (callStatus === index_1.CallStatus.IDLE) {
return index_1.StatusChange.IDLE;
}
const isGroup = TUIStore.getData(index_1.StoreName.CALL, index_1.NAME.IS_GROUP);
if (callStatus === index_1.CallStatus.CALLING) {
return isGroup ? index_1.StatusChange.DIALING_GROUP : index_1.StatusChange.DIALING_C2C;
}
const callMediaType = TUIStore.getData(index_1.StoreName.CALL, index_1.NAME.CALL_MEDIA_TYPE);
if (isGroup) {
return callMediaType === index_1.CallMediaType.AUDIO ? index_1.StatusChange.CALLING_GROUP_AUDIO : index_1.StatusChange.CALLING_GROUP_VIDEO;
}
return callMediaType === index_1.CallMediaType.AUDIO ? index_1.StatusChange.CALLING_C2C_AUDIO : index_1.StatusChange.CALLING_C2C_VIDEO;
}
exports.generateStatusChangeText = generateStatusChangeText;
// 获取群组[offset, count + offset]区间成员
function getGroupMemberList(groupID, tim, count, offset) {
return __awaiter(this, void 0, void 0, function* () {
let groupMemberList = [];
try {
const res = yield tim.getGroupMemberList({ groupID, count, offset });
if (res.code === 0) {
return res.data.memberList || groupMemberList;
}
}
catch (error) {
console.error(`${index_1.NAME.PREFIX}getGroupMember failed, error: ${error}.`);
return groupMemberList;
}
});
}
exports.getGroupMemberList = getGroupMemberList;
// 获取 IM 群信息
function getGroupProfile(groupID, tim) {
return __awaiter(this, void 0, void 0, function* () {
let groupProfile = {};
try {
const res = yield tim.getGroupProfile({ groupID });
if (res.code === 0) {
return res.data.group || groupProfile;
}
}
catch (error) {
console.error(`${index_1.NAME.PREFIX}getGroupProfile failed, error: ${error}.`);
return groupProfile;
}
});
}
exports.getGroupProfile = getGroupProfile;

View File

@ -0,0 +1,21 @@
import { ITUIGlobal } from '../interface/ITUIGlobal';
export default class TUIGlobal implements ITUIGlobal {
static instance: TUIGlobal;
global: any;
isPC: boolean;
isH5: boolean;
isWeChat: boolean;
isApp: boolean;
isUniPlatform: boolean;
isOfficial: boolean;
isWIN: boolean;
isMAC: boolean;
constructor();
/**
* TUIGlobal
* @returns {TUIGlobal}
*/
static getInstance(): TUIGlobal;
initEnv(): void;
initOfficial(SDKAppID: number): void;
}

View File

@ -0,0 +1,40 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const env_1 = require("../utils/env");
class TUIGlobal {
constructor() {
this.global = env_1.APP_NAMESPACE;
this.isPC = false;
this.isH5 = false;
this.isWeChat = false;
this.isApp = false;
this.isUniPlatform = false;
this.isOfficial = false;
this.isWIN = false;
this.isMAC = false;
this.initEnv();
}
/**
* 获取 TUIGlobal 实例
* @returns {TUIGlobal}
*/
static getInstance() {
if (!TUIGlobal.instance) {
TUIGlobal.instance = new TUIGlobal();
}
return TUIGlobal.instance;
}
initEnv() {
this.isPC = env_1.IS_PC;
this.isH5 = env_1.IS_H5;
this.isWeChat = env_1.IN_WX_MINI_APP;
this.isApp = env_1.IN_UNI_NATIVE_APP && !env_1.IN_WX_MINI_APP; // uniApp 打包小程序时 IN_UNI_NATIVE_APP 为 true所以此处需要增加条件
this.isUniPlatform = env_1.IN_UNI_APP;
this.isWIN = env_1.IS_WIN;
this.isMAC = env_1.IS_MAC;
}
initOfficial(SDKAppID) {
this.isOfficial = (SDKAppID === 1400187352 || SDKAppID === 1400188366);
}
}
exports.default = TUIGlobal;

View File

@ -0,0 +1,8 @@
import { ICallStore } from '../interface/ICallStore';
export default class CallStore {
defaultStore: ICallStore;
store: ICallStore;
update(key: keyof ICallStore, data: any): void;
getData(key: string | undefined): any;
reset(keyList?: Array<string>): void;
}

View File

@ -0,0 +1,62 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const index_1 = require("../const/index");
const common_utils_1 = require("../utils/common-utils");
class CallStore {
constructor() {
this.defaultStore = {
callStatus: index_1.CallStatus.IDLE,
callRole: index_1.CallRole.UNKNOWN,
callMediaType: index_1.CallMediaType.UNKNOWN,
localUserInfo: { userId: '' },
remoteUserInfoList: [],
callerUserInfo: { userId: '' },
isGroup: false,
callDuration: '00:00:00',
callTips: '',
toastInfo: { text: '' },
isMinimized: false,
enableFloatWindow: false,
bigScreenUserId: '',
language: (0, common_utils_1.getLanguage)(),
isClickable: false,
deviceList: { cameraList: [], microphoneList: [], currentCamera: {}, currentMicrophone: {} },
showPermissionTip: false,
groupID: '',
roomID: 0,
// TUICallKit 组件上的属性
displayMode: index_1.VideoDisplayMode.COVER,
videoResolution: index_1.VideoResolution.RESOLUTION_480P,
showSelectUser: false,
// 小程序相关属性
pusher: {},
player: [],
isEarPhone: false, // 是否是听筒, 默认: false
};
this.store = Object.assign({}, this.defaultStore);
}
;
update(key, data) {
switch (key) {
// case 'callTips':
// break;
default:
// resolve "Type 'any' is not assignable to type 'never'.ts", ref: https://github.com/microsoft/TypeScript/issues/31663
this.store[key] = data;
}
}
getData(key) {
if (!key)
return this.store;
return this.store[key];
}
// reset call store
reset(keyList = []) {
if (keyList.length === 0) {
keyList = Object.keys(this.store);
}
const resetToDefault = keyList.reduce((acc, key) => (Object.assign(Object.assign({}, acc), { [key]: this.defaultStore[key] })), {});
this.store = Object.assign(Object.assign(Object.assign({}, this.defaultStore), this.store), resetToDefault);
}
}
exports.default = CallStore;

View File

@ -0,0 +1,50 @@
import { ITUIStore, IOptions, Task } from '../interface/ITUIStore';
import { StoreName } from '../const/index';
export default class TUIStore implements ITUIStore {
static instance: TUIStore;
task: Task;
private storeMap;
private timerId;
constructor();
/**
* TUIStore
* @returns {TUIStore}
*/
static getInstance(): TUIStore;
/**
* UI
* @param {StoreName} storeName store
* @param {IOptions} options
* @param {Object} params
* @param {String} params.notifyRangeWhenWatch , 'all' - key ; 'myself' - key ;
*/
watch(storeName: StoreName, options: IOptions, params?: any): void;
/**
* UI
* @param {StoreName} storeName store
* @param {IOptions} options ,
*/
unwatch(storeName: StoreName, options: IOptions): void;
/**
* store messageList
* @param {StoreName} storeName store
* @param {string} key key
* @param {unknown} data
*/
update(storeName: StoreName, key: string, data: unknown): void;
/**
* Store
* @param {StoreName} storeName store
* @param {string} key key
* @returns {Any}
*/
getData(storeName: StoreName, key: string): any;
/**
* UI
* @param {StoreName} storeName store
* @param {string} key key
*/
private notify;
reset(storeName: StoreName, keyList?: Array<string>, isNotificationNeeded?: boolean): void;
updateStore(params: any, name?: StoreName): void;
}

View File

@ -0,0 +1,155 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const index_1 = require("../const/index");
const callStore_1 = __importDefault(require("./callStore"));
const common_utils_1 = require("../utils/common-utils");
class TUIStore {
constructor() {
this.timerId = -1;
this.storeMap = {
[index_1.StoreName.CALL]: new callStore_1.default(),
};
// todo 此处后续优化结构后调整
this.task = {}; // 保存监听回调列表
}
/**
* 获取 TUIStore 实例
* @returns {TUIStore}
*/
static getInstance() {
if (!TUIStore.instance) {
TUIStore.instance = new TUIStore();
}
return TUIStore.instance;
}
/**
* UI 组件注册监听回调
* @param {StoreName} storeName store 名称
* @param {IOptions} options 监听信息
* @param {Object} params 扩展参数
* @param {String} params.notifyRangeWhenWatch 注册时监听时的通知范围, 'all' - 通知所有注册该 key 的监听; 'myself' - 通知本次注册该 key 的监听; 默认不通知
*/
watch(storeName, options, params) {
if (!this.task[storeName]) {
this.task[storeName] = {};
}
const watcher = this.task[storeName];
Object.keys(options).forEach((key) => {
const callback = options[key];
if (!watcher[key]) {
watcher[key] = new Map();
}
watcher[key].set(callback, 1);
const { notifyRangeWhenWatch } = params || {};
// 注册监听后, 通知所有注册该 key 的监听,使用 'all' 时要特别注意是否对其他地方的监听产生影响
if (notifyRangeWhenWatch === index_1.NAME.ALL) {
this.notify(storeName, key);
}
// 注册监听后, 仅通知自己本次监听该 key 的数据
if (notifyRangeWhenWatch === index_1.NAME.MYSELF) {
const data = this.getData(storeName, key);
callback.call(this, data);
}
});
}
/**
* UI 取消组件监听回调
* @param {StoreName} storeName store 名称
* @param {IOptions} options 监听信息,包含需要取消的回掉等
*/
unwatch(storeName, options) {
// todo 该接口暂未支持unwatch掉同一类如仅传入store注销掉该store下的所有callback同样options仅传入key注销掉该key下的所有callback
// options的callback function为必填参数后续修改
if (!this.task[storeName]) {
return;
}
;
// if (isString(options)) {
// // 移除所有的监听
// if (options === '*') {
// const watcher = this.task[storeName];
// Object.keys(watcher).forEach(key => {
// watcher[key].clear();
// });
// } else {
// console.warn(`${NAME.PREFIX}unwatch warning: options is ${options}`);
// }
// return;
// }
const watcher = this.task[storeName];
Object.keys(options).forEach((key) => {
watcher[key].delete(options[key]);
});
}
/**
* 通用 store 数据更新messageList 的变更需要单独处理
* @param {StoreName} storeName store 名称
* @param {string} key 变更的 key
* @param {unknown} data 变更的数据
*/
update(storeName, key, data) {
var _a;
// 基本数据类型时, 如果相等, 就不进行更新, 减少不必要的 notify
if ((0, common_utils_1.isString)(data) || (0, common_utils_1.isNumber)(data) || (0, common_utils_1.isBoolean)(data)) {
const currentData = this.storeMap[storeName]['store'][key]; // eslint-disable-line
if (currentData === data)
return;
}
(_a = this.storeMap[storeName]) === null || _a === void 0 ? void 0 : _a.update(key, data);
this.notify(storeName, key);
}
/**
* 获取 Store 数据
* @param {StoreName} storeName store 名称
* @param {string} key 待获取的 key
* @returns {Any}
*/
getData(storeName, key) {
var _a;
return (_a = this.storeMap[storeName]) === null || _a === void 0 ? void 0 : _a.getData(key);
}
/**
* UI 组件注册监听回调
* @param {StoreName} storeName store 名称
* @param {string} key 变更的 key
*/
notify(storeName, key) {
if (!this.task[storeName]) {
return;
}
const watcher = this.task[storeName];
if (watcher[key]) {
const callbackMap = watcher[key];
const data = this.getData(storeName, key);
for (const [callback] of callbackMap.entries()) {
callback.call(this, data);
}
}
}
reset(storeName, keyList = [], isNotificationNeeded = false) {
if (storeName in this.storeMap) {
const store = this.storeMap[storeName];
// reset all
if (keyList.length === 0) {
keyList = Object.keys(store === null || store === void 0 ? void 0 : store.store);
}
store.reset(keyList);
if (isNotificationNeeded) {
keyList.forEach((key) => {
this.notify(storeName, key);
});
}
}
}
// 批量修改多个 key-value
updateStore(params, name) {
const storeName = name ? name : index_1.StoreName.CALL;
Object.keys(params).forEach((key) => {
this.update(storeName, key, params[key]);
});
}
}
exports.default = TUIStore;

View File

@ -0,0 +1,93 @@
/**
* @property {String} call 1v1 +
* @property {String} CUSTOM Store
*/
export declare enum StoreName {
CALL = "call",
CUSTOM = "custom"
}
/**
* @property {String} idle
* @property {String} connecting
* @property {String} connected
*/
export declare enum CallMediaType {
UNKNOWN = 0,
AUDIO = 1,
VIDEO = 2
}
/**
* @property {String} caller
* @property {String} callee
*/
export declare enum CallRole {
UNKNOWN = "unknown",
CALLEE = "callee",
CALLER = "caller"
}
/**
* @property {String} idle
* @property {String} calling
* @property {String} connected
*/
export declare enum CallStatus {
IDLE = "idle",
CALLING = "calling",
CONNECTED = "connected"
}
/**
*
* 使 cover ; 使 contain
* @property {String} contain
* @property {String} cover
* @property {String} fill ( v4.12.1 )
*/
export declare enum VideoDisplayMode {
CONTAIN = "contain",
COVER = "cover",
FILL = "fill"
}
/**
*
* @property {String} 480p
* @property {String} 720p
* @property {String} 1080p
*/
export declare enum VideoResolution {
RESOLUTION_480P = "480p",
RESOLUTION_720P = "720p",
RESOLUTION_1080P = "1080p"
}
export declare enum LanguageType {
EN = "en",
'ZH-CN' = "zh-cn",
JA_JP = "ja_JP"
}
export type TDeviceList = {
cameraList: any[];
microphoneList: any[];
currentCamera: any;
currentMicrophone: any;
};
export declare const StatusChange: {
readonly IDLE: "idle";
readonly BE_INVITED: "be-invited";
readonly DIALING_C2C: "dialing-c2c";
readonly DIALING_GROUP: "dialing-group";
readonly CALLING_C2C_AUDIO: "calling-c2c-audio";
readonly CALLING_C2C_VIDEO: "calling-c2c-video";
readonly CALLING_GROUP_AUDIO: "calling-group-audio";
readonly CALLING_GROUP_VIDEO: "calling-group-video";
};
/**
* @property {String} ear
* @property {String} speaker
*/
export declare enum AudioPlayBackDevice {
EAR = "ear",
SPEAKER = "speaker"
}
export declare enum DeviceType {
MICROPHONE = "microphone",
CAMERA = "camera"
}

View File

@ -0,0 +1,104 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DeviceType = exports.AudioPlayBackDevice = exports.StatusChange = exports.LanguageType = exports.VideoResolution = exports.VideoDisplayMode = exports.CallStatus = exports.CallRole = exports.CallMediaType = exports.StoreName = void 0;
/**
* @property {String} call 1v1 通话 + 群组通话
* @property {String} CUSTOM 自定义 Store
*/
var StoreName;
(function (StoreName) {
StoreName["CALL"] = "call";
StoreName["CUSTOM"] = "custom";
})(StoreName = exports.StoreName || (exports.StoreName = {}));
/**
* @property {String} idle 空闲
* @property {String} connecting 呼叫等待中
* @property {String} connected 通话中
*/
var CallMediaType;
(function (CallMediaType) {
CallMediaType[CallMediaType["UNKNOWN"] = 0] = "UNKNOWN";
CallMediaType[CallMediaType["AUDIO"] = 1] = "AUDIO";
CallMediaType[CallMediaType["VIDEO"] = 2] = "VIDEO";
})(CallMediaType = exports.CallMediaType || (exports.CallMediaType = {}));
/**
* @property {String} caller 主叫
* @property {String} callee 被叫
*/
var CallRole;
(function (CallRole) {
CallRole["UNKNOWN"] = "unknown";
CallRole["CALLEE"] = "callee";
CallRole["CALLER"] = "caller";
})(CallRole = exports.CallRole || (exports.CallRole = {}));
/**
* @property {String} idle 空闲
* @property {String} calling 呼叫等待中
* @property {String} connected 通话中
*/
var CallStatus;
(function (CallStatus) {
CallStatus["IDLE"] = "idle";
CallStatus["CALLING"] = "calling";
CallStatus["CONNECTED"] = "connected";
})(CallStatus = exports.CallStatus || (exports.CallStatus = {}));
/**
* 视频画面显示模式
* 播放视频流默认使用 cover 模式; 播放屏幕共享流默认使用 contain 模式
* @property {String} contain 优先保证视频内容全部显示视频尺寸等比缩放直至视频窗口的一边与视窗边框对齐如果视频尺寸与显示视窗尺寸不一致在保持长宽比的前提下将视频进行缩放后填满视窗缩放后的视频四周会有一圈黑边
* @property {String} cover 优先保证视窗被填满视频尺寸等比缩放直至整个视窗被视频填满如果视频长宽与显示窗口不同则视频流会按照显示视窗的比例进行周边裁剪或图像拉伸后填满视窗
* @property {String} fill 保证视窗被填满的同时保证视频内容全部显示但是不保证视频尺寸比例不变视频的宽高会被拉伸至和视窗尺寸一致(该选项值自 v4.12.1 开始支持)
*/
var VideoDisplayMode;
(function (VideoDisplayMode) {
VideoDisplayMode["CONTAIN"] = "contain";
VideoDisplayMode["COVER"] = "cover";
VideoDisplayMode["FILL"] = "fill";
})(VideoDisplayMode = exports.VideoDisplayMode || (exports.VideoDisplayMode = {}));
/**
* 视频分辨率
* @property {String} 480p
* @property {String} 720p
* @property {String} 1080p
*/
var VideoResolution;
(function (VideoResolution) {
VideoResolution["RESOLUTION_480P"] = "480p";
VideoResolution["RESOLUTION_720P"] = "720p";
VideoResolution["RESOLUTION_1080P"] = "1080p";
})(VideoResolution = exports.VideoResolution || (exports.VideoResolution = {}));
// 支持的语言
var LanguageType;
(function (LanguageType) {
LanguageType["EN"] = "en";
LanguageType["ZH-CN"] = "zh-cn";
LanguageType["JA_JP"] = "ja_JP";
})(LanguageType = exports.LanguageType || (exports.LanguageType = {}));
/* === 【原来 TUICallKit 对外暴露】=== */
// 原来 web callKit 定义通知外部状态变更的变量, 对外暴露
exports.StatusChange = {
IDLE: "idle",
BE_INVITED: "be-invited",
DIALING_C2C: "dialing-c2c",
DIALING_GROUP: "dialing-group",
CALLING_C2C_AUDIO: "calling-c2c-audio",
CALLING_C2C_VIDEO: "calling-c2c-video",
CALLING_GROUP_AUDIO: "calling-group-audio",
CALLING_GROUP_VIDEO: "calling-group-video",
};
/* === 【小程序使用】=== */
/**
* @property {String} ear 听筒
* @property {String} speaker 免提
*/
var AudioPlayBackDevice;
(function (AudioPlayBackDevice) {
AudioPlayBackDevice["EAR"] = "ear";
AudioPlayBackDevice["SPEAKER"] = "speaker";
})(AudioPlayBackDevice = exports.AudioPlayBackDevice || (exports.AudioPlayBackDevice = {}));
;
var DeviceType;
(function (DeviceType) {
DeviceType["MICROPHONE"] = "microphone";
DeviceType["CAMERA"] = "camera";
})(DeviceType = exports.DeviceType || (exports.DeviceType = {}));

View File

@ -0,0 +1,2 @@
export declare const ErrorCode: any;
export declare const ErrorMessage: any;

View File

@ -0,0 +1,22 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ErrorMessage = exports.ErrorCode = void 0;
// 错误码
exports.ErrorCode = {
SWITCH_TO_AUDIO_CALL_FAILED: 60001,
SWITCH_TO_VIDEO_CALL_FAILED: 60002,
MICROPHONE_UNAVAILABLE: 60003,
CAMERA_UNAVAILABLE: 60004,
BAN_DEVICE: 60005,
NOT_SUPPORTED_WEBRTC: 60006,
ERROR_BLACKLIST: 20007,
};
exports.ErrorMessage = {
SWITCH_TO_AUDIO_CALL_FAILED: 'switchToAudioCall-call-failed',
SWITCH_TO_VIDEO_CALL_FAILED: 'switchToVideoCall-call-failed',
MICROPHONE_UNAVAILABLE: 'microphone-unavailable',
CAMERA_UNAVAILABLE: 'camera-unavailable',
BAN_DEVICE: 'ban-device',
NOT_SUPPORTED_WEBRTC: 'not-supported-webrtc',
ERROR_BLACKLIST: 'blacklist-user-tips'
};

View File

@ -0,0 +1,12 @@
export * from './call';
export * from './error';
export * from './log';
export declare const CALL_DATA_KEY: any;
export declare const NAME: any;
export declare const AudioCallIcon = "https://web.sdk.qcloud.com/component/TUIKit/assets/call.png";
export declare const VideoCallIcon = "https://web.sdk.qcloud.com/component/TUIKit/assets/call-video-reverse.svg";
export declare const MAX_NUMBER_ROOM_ID = 2147483647;
export declare enum PLATFORM {
MAC = "mac",
WIN = "win"
}

View File

@ -0,0 +1,60 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PLATFORM = exports.MAX_NUMBER_ROOM_ID = exports.VideoCallIcon = exports.AudioCallIcon = exports.NAME = exports.CALL_DATA_KEY = void 0;
__exportStar(require("./call"), exports);
__exportStar(require("./error"), exports);
__exportStar(require("./log"), exports);
// import { keys } from 'ts-transformer-keys';
// import { ICallStore } from '../interface/store';
// console.warn('--> ', keys<ICallStore>())
exports.CALL_DATA_KEY = {
CALL_STATUS: 'callStatus',
CALL_ROLE: 'callRole',
CALL_MEDIA_TYPE: 'callMediaType',
LOCAL_USER_INFO: 'localUserInfo',
REMOTE_USER_INFO_LIST: 'remoteUserInfoList',
CALLER_USER_INFO: 'callerUserInfo',
IS_GROUP: 'isGroup',
CALL_DURATION: 'callDuration',
CALL_TIPS: 'callTips',
TOAST_INFO: 'toastInfo',
IS_MINIMIZED: 'isMinimized',
ENABLE_FLOAT_WINDOW: 'enableFloatWindow',
BIG_SCREEN_USER_ID: 'bigScreenUserId',
LANGUAGE: 'language',
IS_CLICKABLE: 'isClickable',
DISPLAY_MODE: 'displayMode',
VIDEO_RESOLUTION: 'videoResolution',
PUSHER: 'pusher',
PLAYER: 'player',
IS_EAR_PHONE: 'isEarPhone',
SHOW_PERMISSION_TIP: 'SHOW_PERMISSION_TIP',
GROUP_ID: 'groupID',
ROOM_ID: 'roomID',
SHOW_SELECT_USER: 'showSelectUser',
};
exports.NAME = Object.assign({ PREFIX: '【CallService】', AUDIO: 'audio', VIDEO: 'video', LOCAL_VIDEO: 'localVideo', ERROR: 'error', TIMEOUT: 'timeout', RAF: 'raf', INTERVAL: 'interval', DEFAULT: 'default', BOOLEAN: 'boolean', STRING: 'string', NUMBER: 'number', OBJECT: 'object', ARRAY: 'array', FUNCTION: 'function', UNDEFINED: "undefined", ALL: 'all', MYSELF: 'myself', DEVICE_LIST: 'deviceList' }, exports.CALL_DATA_KEY);
exports.AudioCallIcon = 'https://web.sdk.qcloud.com/component/TUIKit/assets/call.png';
exports.VideoCallIcon = 'https://web.sdk.qcloud.com/component/TUIKit/assets/call-video-reverse.svg';
exports.MAX_NUMBER_ROOM_ID = 2147483647;
var PLATFORM;
(function (PLATFORM) {
// eslint-disable-next-line no-unused-vars
PLATFORM["MAC"] = "mac";
// eslint-disable-next-line no-unused-vars
PLATFORM["WIN"] = "win";
})(PLATFORM = exports.PLATFORM || (exports.PLATFORM = {}));

View File

@ -0,0 +1,7 @@
export declare enum LOG_LEVEL {
NORMAL = 0,
RELEASE = 1,
WARNING = 2,
ERROR = 3,
NONE = 4
}

View File

@ -0,0 +1,13 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LOG_LEVEL = void 0;
/* eslint-disable */
// 唯一一个变量格式有问题的, 但是为了和原来 TUICallKit 对外暴露的保持一致
var LOG_LEVEL;
(function (LOG_LEVEL) {
LOG_LEVEL[LOG_LEVEL["NORMAL"] = 0] = "NORMAL";
LOG_LEVEL[LOG_LEVEL["RELEASE"] = 1] = "RELEASE";
LOG_LEVEL[LOG_LEVEL["WARNING"] = 2] = "WARNING";
LOG_LEVEL[LOG_LEVEL["ERROR"] = 3] = "ERROR";
LOG_LEVEL[LOG_LEVEL["NONE"] = 4] = "NONE";
})(LOG_LEVEL = exports.LOG_LEVEL || (exports.LOG_LEVEL = {}));

View File

@ -0,0 +1,5 @@
import TUICallService, { TUIGlobal, TUIStore } from './CallService/index';
import { StoreName, NAME, CallRole, CallMediaType, CallStatus, StatusChange, VideoResolution, VideoDisplayMode, AudioPlayBackDevice } from './const/index';
import { t } from './locales/index';
declare const TUICallKitServer: TUICallService;
export { TUIGlobal, TUIStore, StoreName, TUICallKitServer, NAME, CallStatus, CallRole, CallMediaType, StatusChange, VideoResolution, VideoDisplayMode, AudioPlayBackDevice, t, };

View File

@ -0,0 +1,44 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.t = exports.AudioPlayBackDevice = exports.VideoDisplayMode = exports.VideoResolution = exports.StatusChange = exports.CallMediaType = exports.CallRole = exports.CallStatus = exports.NAME = exports.TUICallKitServer = exports.StoreName = exports.TUIStore = exports.TUIGlobal = void 0;
const index_1 = __importStar(require("./CallService/index"));
Object.defineProperty(exports, "TUIGlobal", { enumerable: true, get: function () { return index_1.TUIGlobal; } });
Object.defineProperty(exports, "TUIStore", { enumerable: true, get: function () { return index_1.TUIStore; } });
const index_2 = require("./const/index");
Object.defineProperty(exports, "StoreName", { enumerable: true, get: function () { return index_2.StoreName; } });
Object.defineProperty(exports, "NAME", { enumerable: true, get: function () { return index_2.NAME; } });
Object.defineProperty(exports, "CallRole", { enumerable: true, get: function () { return index_2.CallRole; } });
Object.defineProperty(exports, "CallMediaType", { enumerable: true, get: function () { return index_2.CallMediaType; } });
Object.defineProperty(exports, "CallStatus", { enumerable: true, get: function () { return index_2.CallStatus; } });
Object.defineProperty(exports, "StatusChange", { enumerable: true, get: function () { return index_2.StatusChange; } });
Object.defineProperty(exports, "VideoResolution", { enumerable: true, get: function () { return index_2.VideoResolution; } });
Object.defineProperty(exports, "VideoDisplayMode", { enumerable: true, get: function () { return index_2.VideoDisplayMode; } });
Object.defineProperty(exports, "AudioPlayBackDevice", { enumerable: true, get: function () { return index_2.AudioPlayBackDevice; } });
const index_3 = require("./locales/index");
Object.defineProperty(exports, "t", { enumerable: true, get: function () { return index_3.t; } });
// 实例化
const TUICallKitServer = index_1.default.getInstance();
exports.TUICallKitServer = TUICallKitServer;

View File

@ -0,0 +1,93 @@
import { CallStatus, CallRole } from '../const/index';
/**
* @interface ITUICallService
*/
export interface ITUICallService {
/**
* Service
* @function
* @private
*/
init(params: any): void;
/**
* 1v1
* @function
* @param {SwitchUserStatusParams} options
*/
call(callParams: ICallParams): void;
}
type SDKAppID = {
SDKAppID: number;
} | {
sdkAppID: number;
};
export interface IInitParamsBase {
userID: string;
userSig: string;
tim?: any;
isFromChat?: boolean;
}
export type IInitParams = IInitParamsBase & SDKAppID;
export interface ICallParams {
userID: string;
type: number;
roomID?: number;
userData?: string;
timeout?: number;
offlinePushInfo?: IOfflinePushInfo;
}
export interface IGroupCallParams {
userIDList: Array<string>;
type: number;
groupID: string;
roomID?: number;
userData?: string;
timeout?: number;
offlinePushInfo?: IOfflinePushInfo;
}
export interface IUserInfo {
userId: string;
nick?: string;
avatar?: string;
remark?: string;
displayUserInfo?: string;
isAudioAvailable?: boolean;
isVideoAvailable?: boolean;
volume?: number;
isEnter?: boolean;
domId?: string;
}
export interface IOfflinePushInfo {
title?: string;
description?: string;
androidOPPOChannelID?: string;
extension: String;
}
export interface ICallbackParam {
beforeCalling?: (...args: any[]) => void;
afterCalling?: (...args: any[]) => void;
onMinimized?: (...args: any[]) => void;
onMessageSentByMe?: (...args: any[]) => void;
kickedOut?: (...args: any[]) => void;
statusChanged?: (...args: any[]) => void;
}
export interface ISelfInfoParams {
nickName: string;
avatar: string;
}
export interface IBellParams {
callRole?: CallRole;
callStatus?: CallStatus;
isMuteBell?: boolean;
calleeBellFilePath?: string;
}
export interface IInviteUserParams {
userIDList: string[];
offlinePushInfo?: IOfflinePushInfo;
}
export interface IJoinInGroupCallParams {
type: number;
groupID: string;
roomID: number;
}
export {};

View File

@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View File

@ -0,0 +1,33 @@
import { CallStatus, CallRole, CallMediaType, VideoDisplayMode, VideoResolution, TDeviceList } from '../const/index';
import { IUserInfo } from './index';
export interface IToastInfo {
text: string;
type?: string;
}
export interface ICallStore {
callStatus: CallStatus;
callRole: CallRole;
callMediaType: CallMediaType;
localUserInfo: IUserInfo;
remoteUserInfoList: Array<IUserInfo>;
callerUserInfo: IUserInfo;
isGroup: boolean;
callDuration: string;
callTips: string;
toastInfo: IToastInfo;
isMinimized: boolean;
enableFloatWindow: boolean;
bigScreenUserId: string;
language: string;
isClickable: boolean;
showPermissionTip: boolean;
deviceList: TDeviceList;
groupID: string;
roomID: number;
displayMode: VideoDisplayMode;
videoResolution: VideoResolution;
pusher: any;
player: any[];
isEarPhone: boolean;
showSelectUser: boolean;
}

View File

@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View File

@ -0,0 +1,36 @@
/**
* @interface TUIGlobal
* @property {Object} global wxuniwindow
* @property {Boolean} isPC true pc
* @property {Boolean} isH5 true H5
* @property {Boolean} isWeChat true
* @property {Boolean} isApp true uniapp native app
* @property {Boolean} isUniPlatform true uniapp
* @property {Boolean} isOfficial true Demo
* @property {Boolean} isWIN true window系统pc
* @property {Boolean} isMAC true mac os系统pc
*/
export interface ITUIGlobal {
global: any;
isPC: boolean;
isH5: boolean;
isWeChat: boolean;
isApp: boolean;
isUniPlatform: boolean;
isOfficial: boolean;
isWIN: boolean;
isMAC: boolean;
/**
* TUIGlobal
* @function
* @private
*/
initEnv(): void;
/**
* isOfficial
* @function
* @param {number} SDKAppID SDKAppID
* @private
*/
initOfficial(SDKAppID: number): void;
}

View File

@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View File

@ -0,0 +1,75 @@
import { StoreName } from '../const/index';
export type Task = Record<StoreName, Record<string, Map<(data?: unknown) => void, 1>>>;
export interface IOptions {
[key: string]: (newData?: any) => void;
}
/**
* @class TUIStore
* @property {ICustomStore} customStore store API
*/
export interface ITUIStore {
task: Task;
/**
* UI
* @function
* @param {StoreName} storeName store
* @param {IOptions} options UI
* @param {Object} params
* @param {String} params.notifyRangeWhenWatch
* @example
* // UI 层监听会话列表更新通知
* let onConversationListUpdated = function(conversationList) {
* console.warn(conversationList);
* }
* TUIStore.watch(StoreName.CONV, {
* conversationList: onConversationListUpdated,
* })
*/
watch(storeName: StoreName, options: IOptions, params?: any): void;
/**
* UI
* @function
* @param {StoreName} storeName store
* @param {IOptions} options
* @example
* // UI 层取消监听会话列表更新通知
* TUIStore.unwatch(StoreName.CONV, {
* conversationList: onConversationListUpdated,
* })
*/
unwatch(storeName: StoreName, options: IOptions | string): void;
/**
* store
* @function
* @param {StoreName} storeName store
* @param {String} key key
* @private
*/
getData(storeName: StoreName, key: string): any;
/**
* store
* - 使 store API
* @function
* @param {StoreName} storeName store
* @param {String} key key
* @example
* // UI 层更新自定义 Store 数据
* TUIStore.update(StoreName.CUSTOM, 'customKey', 'customData')
*/
update(storeName: StoreName, key: string, data: unknown): void;
/**
* store
* @function
* @param {StoreName} storeName store
* @param {Array<string>} keyList reset keyList
* @param {boolean} isNotificationNeeded
* @private
*/
reset: (storeName: StoreName, keyList?: Array<string>, isNotificationNeeded?: boolean) => void;
/**
* key-value
* @param {Object} params key-value object
* @param {StoreName} storeName store
*/
updateStore: (params: any, name?: StoreName) => void;
}

View File

@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View File

@ -0,0 +1,4 @@
export * from './ICallService';
export * from './ICallStore';
export * from './ITUIGlobal';
export * from './ITUIStore';

View File

@ -0,0 +1,20 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./ICallService"), exports);
__exportStar(require("./ICallStore"), exports);
__exportStar(require("./ITUIGlobal"), exports);
__exportStar(require("./ITUIStore"), exports);

View File

@ -0,0 +1,90 @@
export declare const en: {
hangup: string;
reject: string;
'other side reject call': string;
'reject call': string;
accept: string;
cancel: string;
'other side line busy': string;
'in busy': string;
'call timeout': string;
'no response from the other side': string;
'end call': string;
timeout: string;
'kick out': string;
'caller calling message': string;
'callee calling video message': string;
'callee calling audio message': string;
'no microphone access': string;
'no camera access': string;
'invite member': string;
speaker: string;
'Invited group call': string;
'Those involved': string;
call: string;
'video-call': string;
'audio-call': string;
search: string;
'search-result': string;
'no-user': string;
'member-not-added': string;
'input-phone-userID': string;
'not-login': string;
'login-status-expire': string;
'experience-multi-call': string;
'not-support-multi-call': string;
userID: string;
'already-enter': string;
waiting: string;
'camera-opened': string;
'camera-closed': string;
'microphone-opened': string;
'microphone-closed': string;
camera: string;
microphone: string;
'image-resolution': string;
'default-image-resolution': string;
'invited-person': string;
'video-to-audio': string;
me: string;
'be-rejected': string;
'be-no-response': string;
'be-line-busy': string;
'be-canceled': string;
'voice-call-end': string;
'video-call-end': string;
'method-call-failed': string;
'failed-to-obtain-permission': string;
'environment-detection-failed': string;
'switchToAudioCall-call-failed': string;
'switchToVideoCall-call-failed': string;
'microphone-unavailable': string;
'camera-unavailable': string;
'ban-device': string;
'not-supported-webrtc': string;
'blacklist-user-tips': string;
'is-already-calling': string;
'need-init': string;
"can't call yourself": string;
'Use-phone-and-computer': string;
'Wechat scan right QR code': string;
'Scan the QR code above': string;
'accept-error': string;
'accept-device-error': string;
'call-error': string;
'browser-authorization': string;
'mac-privacy': string;
'win-privacy': string;
'mac-preferences': string;
'win-preferences': string;
'open camera': string;
'close camera': string;
'open microphone': string;
'close microphone': string;
'Please enter userID': string;
'View more': string;
'people selected': string;
'Select all': string;
Cancel: string;
Done: string;
};

View File

@ -0,0 +1,93 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.en = void 0;
exports.en = {
'hangup': 'Hang up',
'reject': 'Decline',
'other side reject call': 'other side reject call',
'reject call': 'Reject Call',
'accept': 'Accept',
'cancel': 'Cancel Call',
'other side line busy': 'other side line busy',
'in busy': 'in busy',
'call timeout': 'call timeout',
'no response from the other side': 'no response from the other side',
'end call': 'end call',
'timeout': 'timeout',
'kick out': 'kick out',
'caller calling message': 'Waiting for the callee to accept the invitation...',
'callee calling video message': 'You are invited to a video call...',
'callee calling audio message': 'You are invited to a audio call...',
'no microphone access': 'no microphone access',
'no camera access': 'no camera access',
'invite member': 'Invite Member',
'speaker': 'speaker',
'Invited group call': 'Invited you to a group call',
'Those involved': 'Those involved in the call are',
'call': 'call',
'video-call': 'video call',
'audio-call': 'audio call',
'search': 'search',
'search-result': 'search result',
'no-user': 'user not found',
'member-not-added': 'member not added',
'input-phone-userID': 'phone number or userID',
'not-login': 'not logged in',
'login-status-expire': 'login status is invalid, please refresh the page and try again',
'experience-multi-call': 'experience multi-person calls, please download the full-featured demo: ',
'not-support-multi-call': 'multi-person call interface is not open',
'userID': 'userID',
'already-enter': 'entered the call',
'waiting': 'Calling...',
'camera-opened': 'Camera on',
'camera-closed': 'Camera off',
'microphone-opened': 'Mic on',
'microphone-closed': 'Mic off',
'camera': 'Camera',
'microphone': 'Microphone',
'image-resolution': 'Resolution',
'default-image-resolution': 'Default',
'invited-person': 'Invite',
'video-to-audio': 'Switch to audio',
'me': '(me)',
'be-rejected': 'Call declined, ',
'be-no-response': 'No response, ',
'be-line-busy': 'Line busy, ',
'be-canceled': 'The call is canceled, ',
'voice-call-end': 'Voice call ended',
'video-call-end': 'Video call ended',
'method-call-failed': 'Failed to sync the operation',
'failed-to-obtain-permission': 'Failed to obtain permissions',
'environment-detection-failed': 'Failed to check the environment',
'switchToAudioCall-call-failed': 'switch to audio call method failed',
'switchToVideoCall-call-failed': 'switch to video call method failed',
'microphone-unavailable': 'No mic found',
'camera-unavailable': 'No camera found',
'ban-device': 'Device access denied',
'not-supported-webrtc': 'Your current environment does not support WebRTC',
'blacklist-user-tips': 'The identifier is in blacklist. Failed to send this message!',
'is-already-calling': 'TUICallKit is already on a call',
'need-init': 'Before initiating a call with TUICallKit, ensure that the TUICallKitServer.init() method has executed successfully. ',
"can't call yourself": "Can't call yourself",
'Use-phone-and-computer': 'Use your mobile phone and computer to experience video calls',
'Wechat scan right QR code': 'Wechat scan right QR code',
'Scan the QR code above': 'Scan the QR code above',
'accept-error': 'Accept failed',
'accept-device-error': 'Accept failed, unable to auth calling device',
'call-error': 'Start call failed',
'browser-authorization': 'Browser authorization',
'mac-privacy': 'System Preferences -> Security and Privacy -> Privacy',
'win-privacy': 'Setting -> Privacy and Security -> App permissions',
'mac-preferences': 'Open System Preferences',
'win-preferences': 'Open Setting',
'open camera': 'Open Camera',
'close camera': 'Close Camera',
'open microphone': 'Open Microphone',
'close microphone': 'Close Microphone',
'Please enter userID': 'Please enter userID',
'View more': 'View more',
'people selected': 'people selected',
'Select all': 'Select all',
'Cancel': 'Cancel',
'Done': 'Done',
};

View File

@ -0,0 +1,10 @@
export declare const CallTips: any;
export declare const languageData: languageDataType;
export declare function t(key: any): string;
interface languageItemType {
[key: string]: string;
}
interface languageDataType {
[key: string]: languageItemType;
}
export {};

View File

@ -0,0 +1,51 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.t = exports.languageData = exports.CallTips = void 0;
const index_1 = require("../CallService/index");
const index_2 = require("../const/index");
const en_1 = require("./en");
const zh_cn_1 = require("./zh-cn");
const ja_JP_1 = require("./ja_JP");
exports.CallTips = {
OTHER_SIDE: 'other side',
CANCEL: 'cancel',
OTHER_SIDE_REJECT_CALL: 'other side reject call',
REJECT_CALL: 'reject call',
OTHER_SIDE_LINE_BUSY: 'other side line busy',
IN_BUSY: 'in busy',
CALL_TIMEOUT: 'call timeout',
END_CALL: 'end call',
TIMEOUT: 'timeout',
KICK_OUT: 'kick out',
CALLER_CALLING_MSG: 'caller calling message',
CALLEE_CALLING_VIDEO_MSG: 'callee calling video message',
CALLEE_CALLING_AUDIO_MSG: 'callee calling audio message',
NO_MICROPHONE_DEVICE_PERMISSION: 'no microphone access',
NO_CAMERA_DEVICE_PERMISSION: 'no camera access',
};
exports.languageData = {
en: en_1.en,
'zh-cn': zh_cn_1.zh,
ja_JP: ja_JP_1.ja_JP,
};
// language translate
function t(key) {
var _a;
const language = index_1.TUIStore.getData(index_2.StoreName.CALL, index_2.NAME.LANGUAGE);
// eslint-disable-next-line
for (const langKey in exports.languageData) {
if (langKey === language) {
const currentLanguage = exports.languageData[langKey];
// eslint-disable-next-line
for (const sentenceKey in currentLanguage) {
if (sentenceKey === key) {
return currentLanguage[sentenceKey];
}
}
}
}
const enString = (_a = key['en']) === null || _a === void 0 ? void 0 : _a.key; // eslint-disable-line
console.error(`${index_2.NAME.PREFIX}translation is not found: ${key}.`);
return enString;
}
exports.t = t;

View File

@ -0,0 +1,85 @@
export declare const ja_JP: {
hangup: string;
reject: string;
'other side reject call': string;
'reject call': string;
accept: string;
cancel: string;
'other side line busy': string;
'in busy': string;
'call timeout': string;
'end call': string;
timeout: string;
'kick out': string;
'caller calling message': string;
'callee calling video message': string;
'callee calling audio message': string;
'no microphone access': string;
'no camera access': string;
'invite member': string;
speaker: string;
'Invited group call': string;
'Those involved': string;
call: string;
'video-call': string;
'audio-call': string;
search: string;
'search-result': string;
'Wechat scan right QR code': string;
'Use-phone-and-computer': string;
'Scan the QR code above': string;
'no-user': string;
'member-not-added': string;
'not-login': string;
'login-status-expire': string;
'experience-multi-call': string;
'not-support-multi-call': string;
'input-phone-userID': string;
userID: string;
'already-enter': string;
waiting: string;
'camera-opened': string;
'camera-closed': string;
'microphone-opened': string;
'microphone-closed': string;
camera: string;
microphone: string;
'image-resolution': string;
'default-image-resolution': string;
'invited-person': string;
'video-to-audio': string;
me: string;
'be-rejected': string;
'be-no-response': string;
'be-line-busy': string;
'be-canceled': string;
'voice-call-end': string;
'video-call-end': string;
'method-call-failed': string;
'failed-to-obtain-permission': string;
'environment-detection-failed': string;
'switchToAudioCall-call-failed': string;
'switchToVideoCall-call-failed': string;
'microphone-unavailable': string;
'camera-unavailable': string;
'ban-device': string;
'not-supported-webrtc': string;
'blacklist-user-tips': string;
'is-already-calling': string;
'need-init': string;
"can't call yourself": string;
'accept-error': string;
'accept-device-error': string;
'call-error': string;
'browser-authorization': string;
'mac-privacy': string;
'win-privacy': string;
'mac-preferences': string;
'win-preferences': string;
'Please enter userID': string;
'View more': string;
'people selected': string;
'Select all': string;
Cancel: string;
Done: string;
};

View File

@ -0,0 +1,88 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ja_JP = void 0;
exports.ja_JP = {
'hangup': '通話終了',
'reject': '拒否',
'other side reject call': '通話が拒否されました',
'reject call': '通話拒否',
'accept': '応答',
'cancel': '通話をキャンセル',
'other side line busy': '相手が通話中です',
'in busy': '通話中',
'call timeout': '呼び出しタイムアウト',
'end call': '通話終了',
'timeout': 'タイムアウト',
'kick out': 'キックアウトされました',
'caller calling message': '相手が招待を承諾するのを待っています。',
'callee calling video message': 'ビデオ通話に招待されました。',
'callee calling audio message': '音声通話に招待されました。',
'no microphone access': 'マイクにアクセスできません',
'no camera access': 'カメラにアクセスできません',
'invite member': 'メンバーを招待する',
'speaker': 'スピーカー',
'Invited group call': 'グループ通話に招待されました。',
'Those involved': '参加者:',
'call': '通話',
'video-call': 'ビデオ通話',
'audio-call': '音声通話',
'search': '検索',
'search-result': '検索結果',
'Wechat scan right QR code': 'WeChatで右側にあるQRコードを読み取ります。',
'Use-phone-and-computer': '携帯電話とコンピュータを使用してビデオ通話を体験してください',
'Scan the QR code above': '上のQRコードを読み取ります。',
'no-user': 'ユーザーが見つかりませんでした',
'member-not-added': 'メンバーが追加されていません',
'not-login': 'ログインしていません',
'login-status-expire': 'ログインの有効期限が過ぎています。ページを更新してもう一度お試しください',
'experience-multi-call': '複数人で同時に音声通話できるグループ通話機能を体験するには、全機能のデモをダウンロードしてください',
'not-support-multi-call': 'グループ通話インターフェイスが開いていません',
'input-phone-userID': '携帯電話番号/ユーザーIDを入力してください',
'userID': 'ユーザーID',
'already-enter': 'すでに通話に参加しています',
'waiting': '応答を待っています...',
'camera-opened': 'カメラがオンになっています',
'camera-closed': 'カメラがオフになっています',
'microphone-opened': 'マイクがオンになっています',
'microphone-closed': 'マイクがオフになっています',
'camera': 'カメラ',
'microphone': 'マイク',
'image-resolution': '解像度',
'default-image-resolution': 'デフォルト解像度',
'invited-person': 'メンバーを招待',
'video-to-audio': '音声通話に切り替えます',
'me': '(自分)',
'be-rejected': '通話が拒否されました, ',
'be-no-response': '応答なし, ',
'be-line-busy': '相手が通話中です, ',
'be-canceled': '相手が通話をキャンセルしました',
'voice-call-end': '音声通話が終了しました',
'video-call-end': 'ビデオ通話が終了しました',
'method-call-failed': '操作の同期に失敗しました',
'failed-to-obtain-permission': '権限の取得に失敗しました',
'environment-detection-failed': '環境の検出に失敗しました',
'switchToAudioCall-call-failed': '音声通話に切り替えることはできません',
'switchToVideoCall-call-failed': 'ビデオ通話に切り替えることはできません',
'microphone-unavailable': '使用できるマイクがありません',
'camera-unavailable': '使用できるカメラがありません',
'ban-device': 'デバイスへのアクセスが拒否されました',
'not-supported-webrtc': '現在の環境はWebRTCをサポートしていません',
'blacklist-user-tips': 'ユーザーはブラックリストに登録され、通話が開始できませんでした',
'is-already-calling': 'TUICallKit はすでに通話中です',
'need-init': 'TUICallKitで通話を開始する前に、TUICallKitServer.init() メソッドが正常に実行されたことを確認してください。',
"can't call yourself": '自分に電話をかけることができません',
'accept-error': '接続できませんでした',
'accept-device-error': '接続できませんでした。発信側デバイスを認証できません',
'call-error': '通話が開始できませんでした',
'browser-authorization': 'ブラウザ認証',
'mac-privacy': 'システム環境設定 -> セキュリティとプライバシー ->プライバシー',
'win-privacy': '設定 -> セキュリティとプライバシー ->アプリのアクセス許可',
'mac-preferences': 'システム環境設定を開く',
'win-preferences': 'システム設定を開く',
'Please enter userID': 'ユーザーIDを入力してください',
'View more': 'もっと見る',
'people selected': '人が選択されました',
'Select all': 'すべて選択',
'Cancel': 'キャンセル',
'Done': '完了',
};

View File

@ -0,0 +1,89 @@
export declare const zh: {
hangup: string;
reject: string;
'other side reject call': string;
'reject call': string;
accept: string;
cancel: string;
'other side line busy': string;
'in busy': string;
'call timeout': string;
'end call': string;
timeout: string;
'kick out': string;
'caller calling message': string;
'callee calling video message': string;
'callee calling audio message': string;
'no microphone access': string;
'no camera access': string;
'invite member': string;
speaker: string;
'Invited group call': string;
'Those involved': string;
call: string;
'video-call': string;
'audio-call': string;
search: string;
'search-result': string;
'Wechat scan right QR code': string;
'Use-phone-and-computer': string;
'Scan the QR code above': string;
'no-user': string;
'member-not-added': string;
'not-login': string;
'login-status-expire': string;
'experience-multi-call': string;
'not-support-multi-call': string;
'input-phone-userID': string;
userID: string;
'already-enter': string;
waiting: string;
'camera-opened': string;
'camera-closed': string;
'microphone-opened': string;
'microphone-closed': string;
camera: string;
microphone: string;
'image-resolution': string;
'default-image-resolution': string;
'invited-person': string;
'video-to-audio': string;
me: string;
'be-rejected': string;
'be-no-response': string;
'be-line-busy': string;
'be-canceled': string;
'voice-call-end': string;
'video-call-end': string;
'method-call-failed': string;
'failed-to-obtain-permission': string;
'environment-detection-failed': string;
'switchToAudioCall-call-failed': string;
'switchToVideoCall-call-failed': string;
'microphone-unavailable': string;
'camera-unavailable': string;
'ban-device': string;
'not-supported-webrtc': string;
'blacklist-user-tips': string;
'is-already-calling': string;
'need-init': string;
"can't call yourself": string;
'accept-error': string;
'accept-device-error': string;
'call-error': string;
'browser-authorization': string;
'mac-privacy': string;
'win-privacy': string;
'mac-preferences': string;
'win-preferences': string;
'open camera': string;
'close camera': string;
'open microphone': string;
'close microphone': string;
'Please enter userID': string;
'View more': string;
'people selected': string;
'Select all': string;
Cancel: string;
Done: string;
};

View File

@ -0,0 +1,92 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.zh = void 0;
exports.zh = {
'hangup': '挂断',
'reject': '拒绝',
'other side reject call': '对方已拒绝',
'reject call': '拒绝通话',
'accept': '接受',
'cancel': '取消通话',
'other side line busy': '对方忙线',
'in busy': '正在忙',
'call timeout': '呼叫超时',
'end call': '结束通话',
'timeout': '超时',
'kick out': '被踢',
'caller calling message': '正在等待对方接受邀请…',
'callee calling video message': '邀请您进行视频通话…',
'callee calling audio message': '邀请您进行语音通话…',
'no microphone access': '没有麦克风权限',
'no camera access': '没有摄像头权限',
'invite member': '邀请成员',
'speaker': '扬声器',
'Invited group call': '邀请你参加多人通话',
'Those involved': '参与通话的有:',
'call': '通话',
'video-call': '视频通话',
'audio-call': '音频通话',
'search': '搜索',
'search-result': '搜索结果',
'Wechat scan right QR code': '微信扫右二维码',
'Use-phone-and-computer': '用手机与电脑互打体验视频通话',
'Scan the QR code above': '扫描上方二维码',
'no-user': '未搜索到用户',
'member-not-added': '未添加成员',
'not-login': '未登录',
'login-status-expire': '登录状态已失效,请刷新网页重试',
'experience-multi-call': '体验多人通话请下载全功能demo:',
'not-support-multi-call': '多人通话接口未开放',
'input-phone-userID': '请输入手机号/用户ID',
'userID': '用户ID',
'already-enter': '已经进入当前通话',
'waiting': '等待接听...',
'camera-opened': '摄像头已开',
'camera-closed': '摄像头已关',
'microphone-opened': '麦克风已开',
'microphone-closed': '麦克风已关',
'camera': '摄像头',
'microphone': '麦克风',
'image-resolution': '分辨率',
'default-image-resolution': '默认分辨率',
'invited-person': '添加成员',
'video-to-audio': '切到语音通话',
'me': '(我)',
'be-rejected': '对方已拒绝,',
'be-no-response': '对方无应答,',
'be-line-busy': '对方忙线中,',
'be-canceled': '对方已取消',
'voice-call-end': '语音通话结束',
'video-call-end': '视频通话结束',
'method-call-failed': '同步操作失败',
'failed-to-obtain-permission': '权限获取失败',
'environment-detection-failed': '环境检测失败',
'switchToAudioCall-call-failed': '切语音调用失败',
'switchToVideoCall-call-failed': '切视频调用失败',
'microphone-unavailable': '没有可用的麦克风设备',
'camera-unavailable': '没有可用的摄像头设备',
'ban-device': '用户禁止使用设备',
'not-supported-webrtc': '当前环境不支持 WebRTC',
'blacklist-user-tips': '发起通话失败,被对方拉入黑名单,禁止发起!',
'is-already-calling': 'TUICallKit 已在通话状态',
'need-init': 'TUICallKit 发起通话前需保证 TUICallKitServer.init() 方法执行成功',
"can't call yourself": '不能呼叫自己',
'accept-error': '接通失败',
'accept-device-error': '接通失败,通话设备获取失败',
'call-error': '发起通话失败',
'browser-authorization': '浏览器授权',
'mac-privacy': '系统偏好设置 -> 安全与隐私 -> 隐私',
'win-privacy': '设置 -> 隐私和安全性 -> 应用权限',
'mac-preferences': '打开系统偏好设置',
'win-preferences': '打开系统设置',
'open camera': '打开摄像头',
'close camera': '关闭摄像头',
'open microphone': '打开麦克风',
'close microphone': '关闭麦克风',
'Please enter userID': '请输入 userID',
'View more': '查看更多',
'people selected': '人已选中',
'Select all': '全选',
'Cancel': '取消',
'Done': '完成',
};

View File

@ -0,0 +1,11 @@
export declare class CallManager {
private _globalCallPagePath;
private _isPageRedirected;
init(params: any): Promise<void>;
private _watchTUIStore;
private _unwatchTUIStore;
private _handleCallStatusChange;
private _handleCallStatusToCalling;
private _handleCallStatusToIdle;
destroyed(): Promise<void>;
}

View File

@ -0,0 +1,133 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CallManager = void 0;
const index_1 = require("../../index");
const index_2 = require("../const/index");
const index_3 = require("../utils/validate/index");
/**
* @param {Number} sdkAppID 用户的sdkAppID 必传
* @param {String} userID 用户的userID 必传
* @param {String} userSig 用户的userSig 必传
* @param {String} globalCallPagePath 跳转的路径 必传
* @param {ChatSDK} tim tim实例 非必传
*/
const PREFIX = 'callManager';
class CallManager {
constructor() {
this._globalCallPagePath = '';
this._isPageRedirected = false;
this._handleCallStatusChange = (value) => __awaiter(this, void 0, void 0, function* () {
switch (value) {
case index_2.CallStatus.CALLING:
case index_2.CallStatus.CONNECTED:
this._handleCallStatusToCalling();
break;
case index_2.CallStatus.IDLE:
this._handleCallStatusToIdle();
break;
}
});
}
init(params) {
return __awaiter(this, void 0, void 0, function* () {
const { sdkAppID, userID, userSig, globalCallPagePath, tim } = params;
if (!globalCallPagePath) {
console.error(`${PREFIX} globalCallPagePath Can not be empty!`);
return;
}
;
this._globalCallPagePath = globalCallPagePath;
try {
yield index_1.TUICallKitServer.init({
sdkAppID,
userID,
userSig,
tim,
});
this._watchTUIStore();
// 全局监听下,关闭悬浮窗
index_1.TUICallKitServer.enableFloatWindow(false);
console.log(`${PREFIX} init Ready!`);
}
catch (error) {
console.error(`${PREFIX} init fail!`);
}
});
}
// =========================【监听 TUIStore 中的状态】=========================
_watchTUIStore() {
index_1.TUIStore === null || index_1.TUIStore === void 0 ? void 0 : index_1.TUIStore.watch(index_1.StoreName.CALL, {
[index_1.NAME.CALL_STATUS]: this._handleCallStatusChange,
}, {
notifyRangeWhenWatch: index_1.NAME.MYSELF,
});
}
_unwatchTUIStore() {
index_1.TUIStore === null || index_1.TUIStore === void 0 ? void 0 : index_1.TUIStore.unwatch(index_1.StoreName.CALL, {
[index_1.NAME.CALL_STATUS]: this._handleCallStatusChange,
});
}
_handleCallStatusToCalling() {
if (this._isPageRedirected)
return;
// @ts-ignore
wx.navigateTo({
url: `/${this._globalCallPagePath}`,
success: () => {
this._isPageRedirected = true;
},
fail: () => {
console.error(`${PREFIX} navigateTo fail!`);
},
complete: () => { },
});
}
_handleCallStatusToIdle() {
if (!this._isPageRedirected)
return;
// @ts-ignore
wx.navigateBack({
success: () => {
this._isPageRedirected = false;
},
fail: () => {
console.error(`${PREFIX} navigateBack fail!`);
},
complete: () => { },
});
}
// 卸载 callManger
destroyed() {
return __awaiter(this, void 0, void 0, function* () {
this._globalCallPagePath = '';
this._isPageRedirected = false;
this._unwatchTUIStore();
yield index_1.TUICallKitServer.destroyed();
});
}
}
__decorate([
(0, index_3.avoidRepeatedCall)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], CallManager.prototype, "init", null);
exports.CallManager = CallManager;

View File

@ -0,0 +1,53 @@
export declare const isUndefined: (input: any) => boolean;
export declare const isPlainObject: (input: any) => boolean;
export declare const isArray: (input: any) => boolean;
export declare const isPrivateKey: (key: string) => boolean;
export declare const isUrl: (url: string) => boolean;
/**
* input类型是否为string
* @param {*} input
* @returns {Boolean} true->string / false->not a string
*/
export declare const isString: (input: any) => boolean;
export declare const isBoolean: (input: any) => boolean;
export declare const isNumber: (input: any) => boolean;
export declare function formatTime(secondTime: number): string;
export declare function formatTimeInverse(stringTime: string): number;
export declare function isJSON(str: string): boolean;
export declare const JSONToObject: (str: string) => any;
/**
* , catch
* @param {Promise} promise
* @param {number} num
* @param {number} time s
* @returns {Promise<any>} im response
*/
export declare const retryPromise: (promise: Promise<any>, num?: number, time?: number) => Promise<any>;
/**
* web call engine , TUICallKit
* @param {any} error
* @returns {Boolean}
*/
export declare function handleRepeatedCallError(error: any): boolean;
/**
*
* @param {any} error
* @returns {Boolean}
*/
export declare function handleNoDevicePermissionError(error: any): boolean;
export declare function performanceNow(): number;
/**
* input类型是否为function
* @param {*} input
* @returns {Boolean} true->input is a function
*/
export declare const isFunction: (input: any) => boolean;
export declare const getLanguage: () => string;
export declare function noop(e: any): void;
/**
* Get the object type string
* @param {*} input
* @returns {String} the object type string
*/
export declare const getType: (input: any) => any;
export declare function modifyObjectKey(obj: any, oldKey: any, newKey: any): any;

View File

@ -0,0 +1,249 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.modifyObjectKey = exports.getType = exports.noop = exports.getLanguage = exports.isFunction = exports.performanceNow = exports.handleNoDevicePermissionError = exports.handleRepeatedCallError = exports.retryPromise = exports.JSONToObject = exports.isJSON = exports.formatTimeInverse = exports.formatTime = exports.isNumber = exports.isBoolean = exports.isString = exports.isUrl = exports.isPrivateKey = exports.isArray = exports.isPlainObject = exports.isUndefined = void 0;
const index_1 = require("../const/index");
const tuiGlobal_1 = __importDefault(require("../TUIGlobal/tuiGlobal"));
const isUndefined = function (input) {
return typeof input === index_1.NAME.UNDEFINED;
};
exports.isUndefined = isUndefined;
const isPlainObject = function (input) {
// 注意不能使用以下方式判断因为IE9/IE10下对象的__proto__是 undefined
// return isObject(input) && input.__proto__ === Object.prototype;
if (typeof input !== index_1.NAME.OBJECT || input === null) {
return false;
}
const proto = Object.getPrototypeOf(input);
if (proto === null) { // edge case Object.create(null)
return true;
}
let baseProto = proto;
while (Object.getPrototypeOf(baseProto) !== null) {
baseProto = Object.getPrototypeOf(baseProto);
}
// 原型链第一个和最后一个比较
return proto === baseProto;
};
exports.isPlainObject = isPlainObject;
const isArray = function (input) {
if (typeof Array.isArray === index_1.NAME.FUNCTION) {
return Array.isArray(input);
}
return Object.prototype.toString.call(input).match(/^\[object (.*)\]$/)[1].toLowerCase() === index_1.NAME.ARRAY;
};
exports.isArray = isArray;
const isPrivateKey = function (key) {
return key.startsWith('_');
};
exports.isPrivateKey = isPrivateKey;
const isUrl = function (url) {
return /^(https?:\/\/(([a-zA-Z0-9]+-?)+[a-zA-Z0-9]+\.)+[a-zA-Z]+)(:\d+)?(\/.*)?(\?.*)?(#.*)?$/.test(url);
};
exports.isUrl = isUrl;
/**
* 检测input类型是否为string
* @param {*} input 任意类型的输入
* @returns {Boolean} true->string / false->not a string
*/
const isString = function (input) {
return typeof input === index_1.NAME.STRING;
};
exports.isString = isString;
const isBoolean = function (input) {
return typeof input === index_1.NAME.BOOLEAN;
};
exports.isBoolean = isBoolean;
const isNumber = function (input) {
return (
// eslint-disable-next-line
input !== null &&
((typeof input === index_1.NAME.NUMBER && !isNaN(input - 0)) || (typeof input === index_1.NAME.OBJECT && input.constructor === Number)));
};
exports.isNumber = isNumber;
function formatTime(secondTime) {
const hours = Math.floor(secondTime / 3600);
const minutes = Math.floor((secondTime % 3600) / 60);
const seconds = Math.floor(secondTime % 60);
let callDurationStr = hours > 9 ? `${hours}` : `0${hours}`;
callDurationStr += minutes > 9 ? `:${minutes}` : `:0${minutes}`;
callDurationStr += seconds > 9 ? `:${seconds}` : `:0${seconds}`;
return callDurationStr;
}
exports.formatTime = formatTime;
function formatTimeInverse(stringTime) {
const list = stringTime.split(':');
return parseInt(list[0]) * 3600 + parseInt(list[1]) * 60 + parseInt(list[2]); // eslint-disable-line
}
exports.formatTimeInverse = formatTimeInverse;
// Determine if it is a JSON string
function isJSON(str) {
if (typeof str === index_1.NAME.STRING) {
try {
const data = JSON.parse(str);
if (data) {
return true;
}
return false;
}
catch (error) {
console.debug(error);
return false;
}
}
return false;
}
exports.isJSON = isJSON;
// Determine if it is a JSON string
const JSONToObject = function (str) {
if (!str || !isJSON(str)) {
return str;
}
return JSON.parse(str);
};
exports.JSONToObject = JSONToObject;
/**
* 重试函数, catch 重试
* @param {Promise} promise 需重试的函数
* @param {number} num 需要重试的次数
* @param {number} time 间隔时间s
* @returns {Promise<any>} im 接口的 response 原样返回
*/
const retryPromise = (promise, num = 6, time = 0.5) => {
let n = num;
const func = () => promise;
return func()
.catch((error) => {
if (n === 0) {
throw error;
}
const timer = setTimeout(() => {
func();
clearTimeout(timer);
n = n - 1;
}, time * 1000);
});
};
exports.retryPromise = retryPromise;
// /**
// * 节流函数(目前 TUICallKit 增加防重调用装饰器,该方法可删除)
// * @param {Function} func 传入的函数
// * @param {wait} time 间隔时间ms
// */
// export const throttle = (func: Function, wait: number) => {
// let previousTime = 0;
// return function () {
// const now = Date.now();
// const args = [...arguments];
// if (now - previousTime > wait) {
// func.apply(this, args);
// previousTime = now;
// }
// };
// }
/**
* web call engine 重复调用时的错误, 这种错误在 TUICallKit 应该忽略
* @param {any} error 错误信息
* @returns {Boolean}
*/
function handleRepeatedCallError(error) {
if ((error === null || error === void 0 ? void 0 : error.message.indexOf('is ongoing, please avoid repeated calls')) !== -1) {
return true;
}
return false;
}
exports.handleRepeatedCallError = handleRepeatedCallError;
/**
* 设备无权限时的错误处理
* @param {any} error 错误信息
* @returns {Boolean}
*/
function handleNoDevicePermissionError(error) {
const { message } = error;
if (message.indexOf('NotAllowedError: Permission denied') !== -1) {
return true;
}
return false;
}
exports.handleNoDevicePermissionError = handleNoDevicePermissionError;
/*
* 获取向下取整的 performance.now()
* @export
* @return {Number}
*/
function performanceNow() {
// 在不支持 performance.now 的浏览器中,使用 Date.now()
// 例如 ie 9ie 10避免加载 sdk 时报错
if (!performance || !performance.now) {
return Date.now();
}
return Math.floor(performance.now());
}
exports.performanceNow = performanceNow;
/**
* 检测input类型是否为function
* @param {*} input 任意类型的输入
* @returns {Boolean} true->input is a function
*/
const isFunction = function (input) {
return typeof input === index_1.NAME.FUNCTION;
};
exports.isFunction = isFunction;
/*
* 获取浏览器语言
* @export
* @return {zh-cn | en}
*/
const getLanguage = () => {
if (tuiGlobal_1.default.getInstance().isWeChat) {
return 'zh-cn';
}
// @ts-ignore
const lang = ((navigator === null || navigator === void 0 ? void 0 : navigator.language) || (navigator === null || navigator === void 0 ? void 0 : navigator.userLanguage) || '').substr(0, 2);
let language = 'en';
switch (lang) {
case 'zh':
language = 'zh-cn';
break;
case 'ja':
language = 'ja_JP';
break;
default:
language = 'en';
}
return language;
};
exports.getLanguage = getLanguage;
function noop(e) { }
exports.noop = noop;
/**
* Get the object type string
* @param {*} input 任意类型的输入
* @returns {String} the object type string
*/
const getType = function (input) {
return Object.prototype.toString
.call(input)
.match(/^\[object (.*)\]$/)[1]
.toLowerCase();
};
exports.getType = getType;
// 修改对象键名
function modifyObjectKey(obj, oldKey, newKey) {
if (!obj.hasOwnProperty(oldKey)) {
return obj;
}
const newObj = {};
Object.keys(obj).forEach(key => {
if (key === oldKey) {
newObj[newKey] = obj[key];
}
else {
newObj[key] = obj[key];
}
});
return newObj;
}
exports.modifyObjectKey = modifyObjectKey;

View File

@ -0,0 +1,10 @@
export declare const IN_WX_MINI_APP: boolean;
export declare const IN_UNI_NATIVE_APP: boolean;
export declare const IN_MINI_APP: boolean;
export declare const IN_UNI_APP: boolean;
export declare const IN_BROWSER: boolean;
export declare const APP_NAMESPACE: any;
export declare const IS_H5: boolean;
export declare const IS_PC: boolean;
export declare const IS_WIN: any;
export declare const IS_MAC: any;

View File

@ -0,0 +1,39 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.IS_MAC = exports.IS_WIN = exports.IS_PC = exports.IS_H5 = exports.APP_NAMESPACE = exports.IN_BROWSER = exports.IN_UNI_APP = exports.IN_MINI_APP = exports.IN_UNI_NATIVE_APP = exports.IN_WX_MINI_APP = void 0;
// 在 uniApp 框架下,打包 H5、ios app、android app 时存在 wx/qq/tt/swan/my 等变量会导致引入 web sdk 环境判断失效
// 小程序 getSystemInfoSync 返回的 fontSizeSetting 在 H5 和 app 中为 undefined所以通过 fontSizeSetting 增强小程序环境判断
// wx 小程序
exports.IN_WX_MINI_APP = (typeof wx !== 'undefined' && typeof wx.getSystemInfoSync === 'function' && Boolean(wx.getSystemInfoSync().fontSizeSetting));
// 用 uni-app 打包 native app此时运行于 js core无 window 等对象,此时调用 api 都得 uni.xxx由于风格跟小程序类似就归为 IN_MINI_APP 的一种
exports.IN_UNI_NATIVE_APP = (typeof uni !== 'undefined' && typeof uni === 'undefined');
exports.IN_MINI_APP = exports.IN_WX_MINI_APP || exports.IN_UNI_NATIVE_APP;
exports.IN_UNI_APP = (typeof uni !== 'undefined');
// 在 uniApp 框架下,由于客户打包 ios app、android app 时 window 不一定存在,所以通过 !IN_MINI_APP 进行判断
// 非 uniApp 框架下,仍然通过 window 结合 IN_MINI_APP 进行判断,可兼容 Taro3.0+ 暴露 window 对象引起的 IN_BROWSER 判断失效问题
exports.IN_BROWSER = (function () {
if (typeof uni !== 'undefined') {
return !exports.IN_MINI_APP;
}
return (typeof window !== 'undefined') && !exports.IN_MINI_APP;
}());
// 命名空间
exports.APP_NAMESPACE = (function () {
if (exports.IN_WX_MINI_APP) {
return wx;
}
if (exports.IN_UNI_APP) {
return uni;
}
return window;
}());
// eslint-disable-next-line no-mixed-operators
const USER_AGENT = exports.IN_BROWSER && window && window.navigator && window.navigator.userAgent || '';
const IS_ANDROID = /Android/i.test(USER_AGENT);
const IS_WIN_PHONE = /(?:Windows Phone)/.test(USER_AGENT);
const IS_SYMBIAN = /(?:SymbianOS)/.test(USER_AGENT);
const IS_IOS = /iPad/i.test(USER_AGENT) || /iPhone/i.test(USER_AGENT) || /iPod/i.test(USER_AGENT);
exports.IS_H5 = IS_ANDROID || IS_WIN_PHONE || IS_SYMBIAN || IS_IOS;
exports.IS_PC = exports.IN_BROWSER && !exports.IS_H5;
exports.IS_WIN = exports.IS_PC && USER_AGENT.includes('Windows NT');
exports.IS_MAC = exports.IS_PC && USER_AGENT.includes('Mac');

View File

@ -0,0 +1 @@
export declare function checkLocalMP3FileExists(src: string): Promise<boolean>;

View File

@ -0,0 +1,33 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkLocalMP3FileExists = void 0;
function checkLocalMP3FileExists(src) {
return __awaiter(this, void 0, void 0, function* () {
if (!src)
return false;
try {
const response = yield new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('HEAD', src, true);
xhr.onload = () => resolve(xhr);
xhr.onerror = () => reject(xhr);
xhr.send();
});
return response.status === 200 && response.getResponseHeader('Content-Type') === 'audio/mpeg';
}
catch (error) {
console.warn(error);
return false;
}
});
}
exports.checkLocalMP3FileExists = checkLocalMP3FileExists;

View File

@ -0,0 +1,2 @@
declare const isEmpty: (input: any) => boolean;
export default isEmpty;

View File

@ -0,0 +1,38 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const common_utils_1 = require("./common-utils");
const isEmpty = function (input) {
// Null and Undefined...
if (input === null || typeof (input) === 'undefined')
return true;
// Booleans...
if (typeof input === 'boolean')
return false;
// Numbers...
if (typeof input === 'number')
return input === 0;
// Strings...
if (typeof input === 'string')
return input.length === 0;
// Functions...
if (typeof input === 'function')
return input.length === 0;
// Arrays...
if (Array.isArray(input))
return input.length === 0;
// Errors...
if (input instanceof Error)
return input.message === '';
// plain object
if ((0, common_utils_1.isPlainObject)(input)) {
// eslint-disable-next-line
for (const key in input) {
if (Object.prototype.hasOwnProperty.call(input, key)) {
return false;
}
}
return true;
}
return false;
};
exports.default = isEmpty;

View File

@ -0,0 +1,64 @@
/// <reference types="node" />
/**
*
* 1. [1,n]
* @example
* // 默认嵌套执行count=0 无限次
* timer.run(callback, {delay: 2000});
* // count=1 等同于 原始setTimeout
* timer.run(callback, {delay: 2000, count:0});
* 2. RAF audio音量获取等任务退 setTimeout 1s
* @example
* // 默认60fps可以根据单位时长换算默认开启后台执行
* timer.run('raf', callback, {fps: 60});
* // 设置执行次数
* timer.run('raf', callback, {fps: 60, count: 300, backgroundTask: false});
* 3. , requestIdleCallback storage
* @example
* // 支持原生setInterval 但不推荐使用,定时任务推荐用 timeout
* timer.run('interval', callback, {delay:2000, count:10})
*/
declare class Timer {
static taskMap: Map<any, any>;
static currentTaskID: number;
static generateTaskID(): number;
/**
*
* @param {string} taskName 'interval' 'timeout'
* @param {function} callback
* @param {object} options include:
* @param {number} options.delay millisecond
* @param {number} options.count 0 or n次
* @param {boolean} options.backgroundTask
*/
static run(taskName: any, callback: any, options: any): any;
/**
*
*
*
* @param {object} taskItem
* @param {function} callback
* @param {*} delay
* @param {*} count
* @returns ID
*/
static interval(taskItem: any): NodeJS.Timer;
/**
*
* count = 0,
* count = n, n次
* @param {object} taskItem
*
*/
static timeout(taskItem: any): NodeJS.Timeout;
static hasTask(taskID: any): boolean;
static clearTask(taskID: any): boolean;
/**
* 1. 退
* 2. 退
* @param {object} taskItem
* @returns
*/
static isBreakLoop(taskItem: any): boolean;
}
export default Timer;

View File

@ -0,0 +1,154 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable */
const common_utils_1 = require("./common-utils");
const index_1 = require("../const/index");
/**
* 定时器功能
* 1. 支持定时执行回调 [1,n] 用于常规延迟定时操作
* @example
* // 默认嵌套执行count=0 无限次
* timer.run(callback, {delay: 2000});
* // count=1 等同于 原始setTimeout
* timer.run(callback, {delay: 2000, count:0});
* 2. 支持 RAF 执行回调用于小流渲染audio音量获取等任务需要渲染频率稳定支持页面退后台后 setTimeout 接管最短 1s 执行一次
* @example
* // 默认60fps可以根据单位时长换算默认开启后台执行
* timer.run('raf', callback, {fps: 60});
* // 设置执行次数
* timer.run('raf', callback, {fps: 60, count: 300, backgroundTask: false});
* 3. 支持空闲任务执行回调, requestIdleCallback 在帧渲染的空闲时间执行任务可以用于 storage 写入等低优先级的任务
* @example
* // 支持原生setInterval 但不推荐使用,定时任务推荐用 timeout
* timer.run('interval', callback, {delay:2000, count:10})
*/
class Timer {
static generateTaskID() {
return this.currentTaskID++;
}
/**
*
* @param {string} taskName 'interval' 'timeout'
* @param {function} callback
* @param {object} options include:
* @param {number} options.delay millisecond
* @param {number} options.count 定时器回调执行次数0 无限次 or n次
* @param {boolean} options.backgroundTask 在页面静默后是否继续执行定时器
*/
static run(taskName = index_1.NAME.TIMEOUT, callback, options) {
// default options
if (taskName === index_1.NAME.INTERVAL) {
options = Object.assign({ delay: 2000, count: 0, backgroundTask: true }, options);
}
else {
options = Object.assign({ delay: 2000, count: 0, backgroundTask: true }, options);
}
// call run(function, {...})
if ((0, common_utils_1.isPlainObject)(callback)) {
options = Object.assign(Object.assign({}, options), callback);
}
if ((0, common_utils_1.isFunction)(taskName)) {
callback = taskName;
taskName = index_1.NAME.TIMEOUT;
}
// 1. 创建 taskID作为 timer task 的唯一标识,在本函数执行完后返回,用于在调用的地方实现互斥逻辑
// 2. 根据 taskName 执行相应的函数
const taskItem = Object.assign({ taskID: this.generateTaskID(), loopCount: 0, intervalID: null, timeoutID: null, taskName,
callback }, options);
this.taskMap.set(taskItem.taskID, taskItem);
// console.log(`timer run task:${taskItem.taskName}, task queue size: ${this.taskMap.size}`);
if (taskName === index_1.NAME.INTERNAL) {
this.interval(taskItem);
}
else {
this.timeout(taskItem);
}
return taskItem.taskID;
}
/**
* 定时循环执行回调函数
* 可以指定循环的时间间隔
* 可以指定循环次数
* @param {object} taskItem
* @param {function} callback
* @param {*} delay
* @param {*} count
* @returns ID
*/
static interval(taskItem) {
// setInterval 缺点,浏览器退后台会降频,循环执行间隔时间不可靠
// 创建进入定时器循环的任务函数,函数内1. 判断是否满足执行条件2.执行 callback
// 通过 setInterval 执行任务函数
// 将 intervalID 记录到 taskMap 对应的 item
const task = () => {
taskItem.callback();
taskItem.loopCount += 1;
if (this.isBreakLoop(taskItem)) {
return;
}
};
return taskItem.intervalID = setInterval(task, taskItem.delay);
}
/**
* 延迟执行回调
* count = 0,循环
* count = n, 执行n次
* @param {object} taskItem
*
*/
static timeout(taskItem) {
// setTimeout 浏览器退后台延迟变为至少1s
const task = () => {
// 执行回调
taskItem.callback();
taskItem.loopCount += 1;
if (this.isBreakLoop(taskItem)) {
return;
}
// 不修正延迟每次callback间隔平均
return taskItem.timeoutID = setTimeout(task, taskItem.delay);
};
return taskItem.timeoutID = setTimeout(task, taskItem.delay);
}
static hasTask(taskID) {
return this.taskMap.has(taskID);
}
static clearTask(taskID) {
// console.log('timer clearTask start', `| taskID:${taskID} | size:${this.taskMap.size}`);
if (!this.taskMap.has(taskID)) {
return true;
}
const { intervalID, timeoutID, onVisibilitychange } = this.taskMap.get(taskID);
if (intervalID) {
clearInterval(intervalID);
}
if (timeoutID) {
clearTimeout(timeoutID);
}
if (onVisibilitychange) {
document.removeEventListener('visibilitychange', onVisibilitychange);
}
this.taskMap.delete(taskID);
// console.log('timer clearTask end ', `| size:${this.taskMap.size}`);
return true;
}
/**
* 1. 如果已移除出定时队列退出当前任务
* 2. 如果当前任务已满足次数限制则退出当前任务
* @param {object} taskItem
* @returns
*/
static isBreakLoop(taskItem) {
if (!this.taskMap.has(taskItem.taskID)) {
return true;
}
if (taskItem.count !== 0 && taskItem.loopCount >= taskItem.count) {
this.clearTask(taskItem.taskID);
return true;
}
return false;
}
}
Timer.taskMap = new Map();
Timer.currentTaskID = 1;
exports.default = Timer;

View File

@ -0,0 +1,10 @@
/**
*
* @export
* @param {Object} options
* @param {Function} options.fn
* @param {Object} options.context
* @param {String} options.name
* @returns {Function}
*/
export declare function avoidRepeatedCall(): (target: any, name: string, descriptor: any) => any;

View File

@ -0,0 +1,49 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.avoidRepeatedCall = void 0;
const index_1 = require("../../const/index");
/**
* 装饰器阻止函数重复调用
* @export
* @param {Object} options 入参
* @param {Function} options.fn 函数
* @param {Object} options.context 上下文对象
* @param {String} options.name 函数名
* @returns {Function} 封装后的函数
*/
function avoidRepeatedCall() {
return function (target, name, descriptor) {
const oldFn = descriptor.value;
const isCallingSet = new Set();
descriptor.value = function (...args) {
return __awaiter(this, void 0, void 0, function* () {
if (isCallingSet.has(this)) {
console.warn((`${index_1.NAME.PREFIX}previous ${name}() is ongoing, please avoid repeated calls`));
// throw new Error(`previous ${name}() is ongoing, please avoid repeated calls`);
return;
}
try {
isCallingSet.add(this);
const result = yield oldFn.apply(this, args);
isCallingSet.delete(this);
return result;
}
catch (error) {
isCallingSet.delete(this);
throw error;
}
});
};
return descriptor;
};
}
exports.avoidRepeatedCall = avoidRepeatedCall;

View File

@ -0,0 +1,4 @@
import { avoidRepeatedCall } from './avoidRepeatedCall';
import { paramValidate } from './validateParams';
import { VALIDATE_PARAMS } from './validateConfig';
export { VALIDATE_PARAMS, paramValidate, avoidRepeatedCall, };

View File

@ -0,0 +1,9 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.avoidRepeatedCall = exports.paramValidate = exports.VALIDATE_PARAMS = void 0;
const avoidRepeatedCall_1 = require("./avoidRepeatedCall");
Object.defineProperty(exports, "avoidRepeatedCall", { enumerable: true, get: function () { return avoidRepeatedCall_1.avoidRepeatedCall; } });
const validateParams_1 = require("./validateParams");
Object.defineProperty(exports, "paramValidate", { enumerable: true, get: function () { return validateParams_1.paramValidate; } });
const validateConfig_1 = require("./validateConfig");
Object.defineProperty(exports, "VALIDATE_PARAMS", { enumerable: true, get: function () { return validateConfig_1.VALIDATE_PARAMS; } });

View File

@ -0,0 +1,173 @@
import { VideoResolution, VideoDisplayMode } from "../../const/index";
export declare const VALIDATE_PARAMS: {
init: {
SDKAppID: {
required: boolean;
rules: any[];
allowEmpty: boolean;
};
userID: {
required: boolean;
rules: any[];
allowEmpty: boolean;
};
userSig: {
required: boolean;
rules: any[];
allowEmpty: boolean;
};
tim: {
required: boolean;
rules: any[];
};
};
call: {
userID: {
required: boolean;
rules: any[];
allowEmpty: boolean;
};
type: {
required: boolean;
rules: any[];
range: number[];
allowEmpty: boolean;
};
roomID: {
required: boolean;
rules: any[];
range: string;
allowEmpty: boolean;
};
userData: {
required: boolean;
rules: any[];
allowEmpty: boolean;
};
timeout: {
required: boolean;
rules: any[];
allowEmpty: boolean;
};
};
groupCall: {
userIDList: {
required: boolean;
rules: any[];
allowEmpty: boolean;
};
type: {
required: boolean;
rules: any[];
range: number[];
allowEmpty: boolean;
};
groupID: {
required: boolean;
rules: any[];
allowEmpty: boolean;
};
roomID: {
required: boolean;
rules: any[];
range: string;
allowEmpty: boolean;
};
timeout: {
required: boolean;
rules: any[];
allowEmpty: boolean;
};
userData: {
required: boolean;
rules: any[];
allowEmpty: boolean;
};
offlinePushInfo: {
required: boolean;
rules: any[];
allowEmpty: boolean;
};
};
joinInGroupCall: {
type: {
required: boolean;
rules: any[];
range: number[];
allowEmpty: boolean;
};
groupID: {
required: boolean;
rules: any[];
allowEmpty: boolean;
};
roomID: {
required: boolean;
rules: any[];
allowEmpty: boolean;
};
};
inviteUser: {
userIDList: {
required: boolean;
rules: any[];
allowEmpty: boolean;
};
};
setSelfInfo: {
nickName: {
required: boolean;
rules: any[];
allowEmpty: boolean;
};
avatar: {
required: boolean;
rules: any[];
allowEmpty: boolean;
};
};
enableFloatWindow: {
key: string;
required: boolean;
rules: any[];
allowEmpty: boolean;
}[];
enableAIVoice: {
key: string;
required: boolean;
rules: any[];
allowEmpty: boolean;
}[];
enableMuteMode: {
key: string;
required: boolean;
rules: any[];
allowEmpty: boolean;
}[];
setCallingBell: {
key: string;
required: boolean;
rules: any[];
allowEmpty: boolean;
}[];
setLanguage: {
key: string;
required: boolean;
rules: any[];
allowEmpty: boolean;
}[];
setVideoDisplayMode: {
key: string;
required: boolean;
rules: any[];
range: VideoDisplayMode[];
allowEmpty: boolean;
}[];
setVideoResolution: {
key: string;
required: boolean;
rules: any[];
range: VideoResolution[];
allowEmpty: boolean;
}[];
};

View File

@ -0,0 +1,190 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VALIDATE_PARAMS = void 0;
const index_1 = require("../../const/index");
exports.VALIDATE_PARAMS = {
init: {
SDKAppID: {
required: true,
rules: [index_1.NAME.NUMBER],
allowEmpty: false,
},
userID: {
required: true,
rules: [index_1.NAME.STRING],
allowEmpty: false,
},
userSig: {
required: true,
rules: [index_1.NAME.STRING],
allowEmpty: false,
},
tim: {
required: false,
rules: [index_1.NAME.OBJECT],
},
},
call: {
userID: {
required: true,
rules: [index_1.NAME.STRING],
allowEmpty: false
},
type: {
required: true,
rules: [index_1.NAME.NUMBER],
range: [1, 2],
allowEmpty: false
},
roomID: {
required: false,
rules: [index_1.NAME.NUMBER],
range: `1~${index_1.MAX_NUMBER_ROOM_ID}`,
allowEmpty: false,
},
userData: {
required: false,
rules: [index_1.NAME.STRING],
allowEmpty: false,
},
timeout: {
required: false,
rules: [index_1.NAME.NUMBER],
allowEmpty: false
}
},
groupCall: {
userIDList: {
required: true,
rules: [index_1.NAME.ARRAY],
allowEmpty: false
},
type: {
required: true,
rules: [index_1.NAME.NUMBER],
range: [1, 2],
allowEmpty: false
},
groupID: {
required: true,
rules: [index_1.NAME.STRING],
allowEmpty: false
},
roomID: {
required: false,
rules: [index_1.NAME.NUMBER],
range: `1~${index_1.MAX_NUMBER_ROOM_ID}`,
allowEmpty: false
},
timeout: {
required: false,
rules: [index_1.NAME.NUMBER],
allowEmpty: false
},
userData: {
required: false,
rules: [index_1.NAME.STRING],
allowEmpty: false,
},
offlinePushInfo: {
required: false,
rules: [index_1.NAME.OBJECT],
allowEmpty: false,
},
},
joinInGroupCall: {
type: {
required: true,
rules: [index_1.NAME.NUMBER],
range: [1, 2],
allowEmpty: false
},
groupID: {
required: true,
rules: [index_1.NAME.STRING],
allowEmpty: false
},
roomID: {
required: true,
rules: [index_1.NAME.NUMBER],
allowEmpty: false,
},
},
inviteUser: {
userIDList: {
required: true,
rules: [index_1.NAME.ARRAY],
allowEmpty: false
},
},
setSelfInfo: {
nickName: {
required: false,
rules: [index_1.NAME.STRING],
allowEmpty: false,
},
avatar: {
required: false,
rules: [index_1.NAME.STRING],
allowEmpty: false,
}
},
enableFloatWindow: [
{
key: "enable",
required: false,
rules: [index_1.NAME.BOOLEAN],
allowEmpty: false,
}
],
enableAIVoice: [
{
key: "enable",
required: true,
rules: [index_1.NAME.BOOLEAN],
allowEmpty: false,
}
],
enableMuteMode: [
{
key: "enable",
required: true,
rules: [index_1.NAME.BOOLEAN],
allowEmpty: false,
}
],
setCallingBell: [
{
key: "filePath",
required: false,
rules: [index_1.NAME.STRING],
allowEmpty: true,
}
],
setLanguage: [
{
key: "language",
required: true,
rules: [index_1.NAME.STRING],
allowEmpty: false
}
],
setVideoDisplayMode: [
{
key: "displayMode",
required: true,
rules: [index_1.NAME.STRING],
range: [index_1.VideoDisplayMode.CONTAIN, index_1.VideoDisplayMode.COVER, index_1.VideoDisplayMode.FILL],
allowEmpty: false
}
],
setVideoResolution: [
{
key: "resolution",
required: true,
rules: [index_1.NAME.STRING],
range: [index_1.VideoResolution.RESOLUTION_1080P, index_1.VideoResolution.RESOLUTION_480P, index_1.VideoResolution.RESOLUTION_720P],
allowEmpty: false
}
]
};

View File

@ -0,0 +1 @@
export declare function paramValidate(config: any): (target: any, propertyName: string, descriptor: PropertyDescriptor) => PropertyDescriptor;

View File

@ -0,0 +1,86 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.paramValidate = void 0;
const common_utils_1 = require("../common-utils");
const index_1 = require("../../const/index");
const PREFIX = index_1.NAME.PREFIX + "API";
function paramValidate(config) {
return function (target, propertyName, descriptor) {
let method = descriptor.value;
descriptor.value = function (...args) {
doValidate.call(this, config, args, propertyName);
return method.apply(this, args);
};
return descriptor;
};
}
exports.paramValidate = paramValidate;
function doValidate(config, args, name) {
try {
// 兼容 init 方法中: SDKAppID sdkAppID 两种写法的参数校验判断
if (!args[0].SDKAppID) {
config = (0, common_utils_1.modifyObjectKey)(config, "SDKAppID", "sdkAppID");
}
if ((0, common_utils_1.isArray)(config)) {
for (let i = 0; i < config.length; i++) {
check.call(this, Object.assign(Object.assign({}, config[i]), { value: args[i], name }));
}
}
else {
for (const key in config) {
if (config.hasOwnProperty(key)) {
check.call(this, Object.assign(Object.assign({}, config[key]), { value: args[0][key], name,
key }));
}
}
}
}
catch (error) {
console.error(error);
throw error;
}
}
function check({ required, rules, range, value, allowEmpty, name, key }) {
// 用户没传指定参数
if ((0, common_utils_1.isUndefined)(value)) {
// 检查必填参数, 若配置是必填则报错
if (required) {
throw new Error(`${PREFIX}<${name}>: ${key} is required.`);
}
else {
return;
}
}
// 判断参数类型是否正确
const result = rules.some((item) => item === (0, common_utils_1.getType)(value));
let type = '';
if (!result) {
for (let i = 0; i < rules.length; i++) {
let str = rules[i];
str = str.replace(str[0], str[0].toUpperCase());
type += `${str}/`;
}
type = type.substring(0, type.length - 1);
throw new Error(`${PREFIX}<${name}>: ${key} must be ${type}, current ${key} is ${typeof value}.`);
}
// 不允许传空值, 例如: '', ' '
if (allowEmpty === false) {
const isEmptyString = (0, common_utils_1.isString)(value) && value.trim() === '';
if (isEmptyString) {
throw new Error(`${PREFIX}<${name}>: ${key} is blank.`);
}
}
// 判断是否符合限制条件
if ((0, common_utils_1.isArray)(range)) {
if (range && range.indexOf(value) === -1) {
throw new Error(`${PREFIX}<${name}>: ${key} error, only be ${range}, current ${key} is ${value}.`);
}
}
// 取值范围, 前闭后闭
if ((0, common_utils_1.isString)(range) && range.indexOf('~') !== -1) {
const valueList = range.split('~');
if (value < +valueList[0] || value > +valueList[1] || ((0, common_utils_1.isNumber)(value) && Number.isNaN(value))) {
throw new Error(`${PREFIX}<${name}>: ${key} error, only be ${range}, current ${key} is ${value}.`);
}
}
}

View File

@ -0,0 +1,2 @@
export * from '@vue/composition-api';
export * from 'vue';

View File

@ -0,0 +1,23 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
// #ifndef VUE3
// @ts-ignore
__exportStar(require("@vue/composition-api"), exports);
// #endif
// #ifdef VUE3
__exportStar(require("vue"), exports);
// #endif

View File

@ -0,0 +1,63 @@
import LibGenerateTestUserSig from './lib-generate-test-usersig-es.min.js';
/**
* 腾讯云 SDKAppId需要替换为您自己账号下的 SDKAppId
*
* 进入腾讯云实时音视频[控制台](https://console.cloud.tencent.com/rav ) 创建应用,即可看到 SDKAppId
* 它是腾讯云用于区分客户的唯一标识
*/
let SDKAPPID = 0;
/**
* 签名过期时间建议不要设置的过短
* <p>
* 时间单位
* 默认时间7 x 24 x 60 x 60 = 604800 = 7
*/
const EXPIRETIME = 604800;
/**
* 计算签名用的加密密钥获取步骤如下
*
* step1. 进入腾讯云实时音视频[控制台](https://console.cloud.tencent.com/rav ),如果还没有应用就创建一个,
* step2. 单击应用配置进入基础配置页面并进一步找到帐号体系集成部分
* step3. 点击查看密钥按钮就可以看到计算 UserSig 使用的加密的密钥了请将其拷贝并复制到如下的变量中
*
* 注意该方案仅适用于调试Demo正式上线前请将 UserSig 计算代码和密钥迁移到您的后台服务器上以避免加密密钥泄露导致的流量盗用
* 文档https://cloud.tencent.com/document/product/647/17275#Server
*/
let SECRETKEY = '';
/*
* Module: GenerateTestUserSig
*
* Function: 用于生成测试用的 UserSigUserSig 是腾讯云为其云服务设计的一种安全保护签名
* 其计算方法是对 SDKAppIDUserID EXPIRETIME 进行加密加密算法为 HMAC-SHA256
*
* Attention: 请不要将如下代码发布到您的线上正式版本的 App 原因如下
*
* 本文件中的代码虽然能够正确计算出 UserSig但仅适合快速调通 SDK 的基本功能不适合线上产品
* 这是因为客户端代码中的 SECRETKEY 很容易被反编译逆向破解尤其是 Web 端的代码被破解的难度几乎为零
* 一旦您的密钥泄露攻击者就可以计算出正确的 UserSig 来盗用您的腾讯云流量
*
* 正确的做法是将 UserSig 的计算代码和加密密钥放在您的业务服务器上然后由 App 按需向您的服务器获取实时算出的 UserSig
* 由于破解服务器的成本要高于破解客户端 App所以服务器计算的方案能够更好地保护您的加密密钥
*
* Referencehttps://cloud.tencent.com/document/product/647/17275#Server
*/
export function genTestUserSig({ userID, SDKAppID, SecretKey }) {
if (SDKAppID) SDKAPPID = SDKAppID;
if (SecretKey) SECRETKEY = SecretKey;
const generator = new LibGenerateTestUserSig(SDKAPPID, SECRETKEY, EXPIRETIME);
const userSig = generator.genTestUserSig(userID);
return {
SDKAppID: SDKAPPID,
userSig,
};
}
export default genTestUserSig;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,3 @@
import { TUIGlobal, TUIStore, StoreName, TUICallKitServer, NAME, StatusChange as STATUS, CallRole, CallMediaType, VideoResolution, VideoDisplayMode, t } from './TUICallService/index';
declare const Version = "2.1.1";
export { TUIGlobal, TUIStore, StoreName, TUICallKitServer, NAME, STATUS, CallRole, CallMediaType, VideoResolution, VideoDisplayMode, Version, t, };

View File

@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.t = exports.Version = exports.VideoDisplayMode = exports.VideoResolution = exports.CallMediaType = exports.CallRole = exports.STATUS = exports.NAME = exports.TUICallKitServer = exports.StoreName = exports.TUIStore = exports.TUIGlobal = void 0;
const index_1 = require("./TUICallService/index");
Object.defineProperty(exports, "TUIGlobal", { enumerable: true, get: function () { return index_1.TUIGlobal; } });
Object.defineProperty(exports, "TUIStore", { enumerable: true, get: function () { return index_1.TUIStore; } });
Object.defineProperty(exports, "StoreName", { enumerable: true, get: function () { return index_1.StoreName; } });
Object.defineProperty(exports, "TUICallKitServer", { enumerable: true, get: function () { return index_1.TUICallKitServer; } });
Object.defineProperty(exports, "NAME", { enumerable: true, get: function () { return index_1.NAME; } });
Object.defineProperty(exports, "STATUS", { enumerable: true, get: function () { return index_1.StatusChange; } });
Object.defineProperty(exports, "CallRole", { enumerable: true, get: function () { return index_1.CallRole; } });
Object.defineProperty(exports, "CallMediaType", { enumerable: true, get: function () { return index_1.CallMediaType; } });
Object.defineProperty(exports, "VideoResolution", { enumerable: true, get: function () { return index_1.VideoResolution; } });
Object.defineProperty(exports, "VideoDisplayMode", { enumerable: true, get: function () { return index_1.VideoDisplayMode; } });
Object.defineProperty(exports, "t", { enumerable: true, get: function () { return index_1.t; } });
const Version = '2.1.1'; // basic-demo 原来上报使用
exports.Version = Version;

View File

@ -0,0 +1,40 @@
{
"name": "@tencentcloud/call-uikit-wechat",
"version": "2.1.1",
"main": "./tuicall-uikit-vue.umd.js",
"module": "./tuicall-uikit-vue.es.js",
"types": "./types/index.d.ts",
"description": "An Open-source Voice & Video Calling UI Component Based on Tencent Cloud Service.",
"homepage": "https://cloud.tencent.com/document/product/647/78912",
"license": "ISC",
"repository": {
"type": "git",
"url": "https://github.com/tencentyun/TUICallKit/tree/main/uni-app/TUICallKit-Miniprogram",
"directory": "Web"
},
"dependencies": {
"@tencentcloud/tui-core": "latest",
"tim-upload-plugin": "^1.3.0",
"tuicall-engine-wx": "latest"
},
"bugs": {
"url": "https://github.com/tencentyun/TUICallKit/issues"
},
"keywords": [
"uikit",
"call",
"vue3",
"tencent",
"chat",
"vue",
"vue2",
"video",
"audio",
"voice",
"callkit",
"语音",
"视频",
"电话",
"通话"
]
}

View File

@ -0,0 +1,4 @@
Page({
data: {},
onShow() {},
});

View File

@ -0,0 +1,7 @@
{
"usingComponents": {
"TUICallKit": "../../TUICallKit/TUICallKit"
},
"navigationStyle": "custom",
"disableScroll": true
}

View File

@ -0,0 +1,3 @@
<view>
<TUICallKit></TUICallKit>
</view>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1017 B

Some files were not shown because too many files have changed in this diff Show More