集成网易云信SDK,进行即时通信-Web(语音通信)

2023-10-30 07:59

本文主要是介绍集成网易云信SDK,进行即时通信-Web(语音通信),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、进行网易云信账号登录注册

登录网易云信控制台
1.登录后可在控制台创建uikit,获取自己appKey
2.查看自己所开通的服务
在这里插入图片描述
在这里插入图片描述
这边是没有语音通信权限的,可以申请免费试用

二、进行文档查看

开发文档
可以看到一些demo,各个平台和环境下API
在这里插入图片描述
选择自己开发的环境,选择下面的不含UI集成(根据自己需求选择适合自己的)
在这里插入图片描述

三、进行SDK集成

方式1: npm 集成(推荐)

npm install @yxim/nim-web-sdk@latest

方式2:script 集成

1.前往云信 SDK 下载中心下载最新版 SDK
2.解压 SDK。SDK 解压后可得到以下三个文件(配图仅以 v9.8.0 为例)
在这里插入图片描述
3. 三个文件说明

├── NIM_Web_Chatroom.js       提供聊天室功能,浏览器适配版(UMD 格式)
├── NIM_Web_NIM.js       提供 IM 功能,包括单聊、会话和群聊等,但不包含聊天室。浏览器适配版(UMD 格式)
├── NIM_Web_SDK.js       提供 IM 功能和聊天室功能的集成包,浏览器适配版(UMD 格式)

4.将所需的 SDK 文件,传入script标签的src中即可。在下文中使用 window 对象属性即可获取对 SDK 的引用。

<!-- 例如 -->
<script src="NIM_Web_SDK_vx.x.x.js">
<script>var nim = SDK.NIM.getInstance({// ...})
</script>

更详细的可去往网易云信官方文档

这边自己是通过方式一进行添加,然后封装在工具中的ts文件中
utils文件夹中的Nim.ts中

/** @Description: 即使通信* @Author: 黑猫* @Date: 2023-06-30 10:53:33* @LastEditTime: 2023-07-15 10:26:36* @FilePath: \pc-exam-invigilate\src\utils\Nim.ts*/
import { NIM } from '@yxim/nim-web-sdk'
// import { NimSdk, NimScene } from '@/constants'
import type {NIMSendTextOptions,NIMGetInstanceOptions,NIMSession,INimInstance
} from '@/types/NimTypes'type CallbackOptions =| 'onconnect'| 'ondisconnect'| 'onerror'| 'onmsg'| 'onwillreconnect'| 'onsessions'| 'onupdatesessions'| 'onofflinemsgs'
type Options = {account: stringtoken: stringdebug?: boolean
} & Pick<NIMGetInstanceOptions, CallbackOptions>class Nim {nimconstructor(options: Options) {this.nim = this.initNim(options)}private initNim({ account, token, debug = true, ...args }: Options): INimInstance {console.log(import.meta.env.VITE_APP_KEY)return NIM.getInstance({debug,appKey: import.meta.env.VITE_APP_KEY,account,token,privateConf: {isDataReportEnable: true,isMixStoreEnable: true}, // 私有化部署方案所需的配置...args})}// 发送文本消息sendText(options: NIMSendTextOptions) {this.nim.sendText({cc: true,...options})}// 重置会话未读数resetSessionUnread(sessionId: string,done: (err: Error | null, failedSessionId: string) => void) {this.nim.resetSessionUnread(sessionId, done)}// 获取历史记录getHistoryMsgs(options: any) {this.nim.getHistoryMsgs(options)}sendCustomMsg(options: any) {this.nim.sendCustomMsg(options)}getTeam(options: any) {this.nim.getTeam(options)}getTeams(options: any) {this.nim.getTeams(options)}sendFile(options: any) {this.nim.sendFile(options)}// 合并会话列表mergeSessions(olds: any[], news: any[]) {if (!olds) {olds = []}if (!news) {return olds}if (!NIM.util.isArray(news)) {news = [news]}if (!news.length) {return olds}const options = {sortPath: 'time',desc: true}return NIM.util.mergeObjArray([], olds, news, options)}destory(done?: any) {this.nim.destroy(done)}disconnect(done?: any) {this.nim.disconnect(done)}
}export default Nim

types中的NimTypes.ts

/** @Description: 功能* @Author: 黑猫* @Date: 2023-06-30 11:01:57* @LastEditTime: 2023-06-30 14:18:22* @FilePath: \pc-exam-invigilate\src\types\NimTypes.ts*/
import { NIM } from '@yxim/nim-web-sdk'
import type { NIMCommonError, NIMStrAnyObj } from '@yxim/nim-web-sdk/dist/types/types'
export type {NIMSendTextOptions,NIMMessage
} from '@yxim/nim-web-sdk/dist/types/nim/MessageInterface'
export type {NIMTeam,NIMTeamMember,TeamInterface
} from '@yxim/nim-web-sdk/dist/types/nim/TeamInterface'
export type { NIMGetInstanceOptions } from '@yxim/nim-web-sdk/dist/types/nim/types'
export type { NIMSession } from '@yxim/nim-web-sdk/dist/types/nim/SessionInterface'
export type NIMError = NIMCommonError | Error | NIMStrAnyObj | null
export type INimInstance = InstanceType<typeof NIM>

1.现在进行初始化

// 获取历史消息
const getHistoryMessages = (teamId: string) => {const today = new Date()const threeDaysAgo = new Date(today.getTime() - 2 * 24 * 60 * 60 * 1000) // 两天前的日期today.setHours(0, 0, 0, 0)threeDaysAgo.setHours(0, 0, 0, 0)const beginTime = threeDaysAgo.getTime()const endTime = Date.now()try {inspectorNimRef.value?.getHistoryMsgs({scene: NimScene.GroupChat,to: teamId,beginTime: beginTime,endTime: endTime,reverse: true, // 是否按照逆序获取历史消息done: getHistoryMsgsDone})} catch (error) {console.log('获取历史消息错误', error)}
}// 处理历史消息数据
const getHistoryMsgsDone = (error, obj: any) => {if (!error) {if (obj.msgs.length > 0) {obj.msgs.forEach((item) => {if (item?.file) {audioMessageList.value.push({duration: Math.ceil(item?.file?.size / (6600 * 2)),stream: item?.file?.url,wink: false,username: item.fromNick,flow: item.flow,time: formatDateTime(item.time)})}})}}
}
// 接收消息
const receiveMessage = (message: NIMMessage) => {if (message && message.type === 'audio' && message?.to) {// 处理接收到的语音消息const duration = Math.ceil(message.file.size / (6600 * 2))if (duration <= 1) {return}const dataTime = formatDateTime(message.time)audioMessageList.value.push({duration,stream: message.file.url,wink: false,username: message.fromNick,flow: message.flow,time: dataTime})openChatting(true, checkedObj.value)// 在此处触发弹窗或其他操作来展示语音消息}
}import { refreshImToken } from '@/api/modules/examRoom'
import type { NIMMessage } from '@/types/NimTypes'
import { NimScene } from '@/constants'
import { ref, onMounted, computed } from 'vue'
import Nim from '@/utils/Nim'
const inspectorNimRef = ref<InstanceType<typeof Nim>>()
// 创建群聊 获取群ID(可以用来语音消息的收发、获取聊天记录)
const createTeam = (checkedObj) => {monitorCreateTeam({scheduleId: checkedObj.scheduleId // 根据自身需求来调用接口获取群id,这边是日程id}).then((data) => {teamId.value = data as stringconfigStore.setConfigState('teamId', teamId.value)localStorage.setItem('teamId', teamId.value)getHistoryMessages(teamId.value)})
}
// 初始化
const initNim = (im_token) => {accid.value = userStore.userId ? `yss_${userStore.userId}` : 'yss_'const nim = new Nim({account: accid.value, // 用户idtoken: im_token, // token,是通过用户Id,调用接口获取tokendebug: false,onconnect() {console.log('连接成功')},onupdatesessions: '', // 您需要根据实际需求指定一个回调函数或设为 null 或 undefinedonsessions: null, // 根据需要指定或设为 null 或 undefinedonmsg: receiveMessage,ondisconnect() {// 处理断开连接逻辑},onerror(err) {// 处理错误逻辑console.log('err --->>>', err)},onwillreconnect(obj) {// 处理将要重新连接逻辑},onofflinemsgs(obj) {// 处理离线消息逻辑}})inspectorNimRef.value = nim
}
// iM_token刷新,获取accid和im_token
const getImToken = () => {const acc_id = userStore.userId ? `yss_${userStore.userId}` : 'yss_'refreshImToken({accId: acc_id}).then((data: { info?: { token?: string } }) => {if (data && data.info && data.info.token) {const im_token: string = data.info.tokeninitNim(im_token)}})
}
onMounted(() => {getImToken()
})

contant文件

/** @Author: heimao* @Date: 2023-07-10 15:33:14* @LastEditors: Do not edit* @LastEditTime: 2023-07-07 14:12:23* @FilePath: \pc-exam-invigilate\src\constants\index.ts* @Description:*/export const enum Source {Examinee = 1, // 学生Teacher = 2 // 老师
}export const enum EmptyStatus {NoSession = 0, // 暂无会话TransferSuccess = 1 // 转接成功
}// 通用
export const enum CommonData {PollingTime = 2000, // 轮询时间SliceLength = 10, // 截取长度PageSize = 100 // 一页长度
}// 云信im sdk
export const enum NimSdk {AppKey = 'cbf015717598350b86ecc0fc7ed51136'
}/*** p2p:单聊消息* team:群聊消息* superTeam:超大群消息**/
export const enum NimScene {SingleChat = 'p2p',GroupChat = 'team',SuperGroupChat = 'superTeam'
}export const enum FileType {Image = 'image',Audio = 'audio',Video = 'video',File = 'file'
}
  1. 进行消息的发送,这里语音消息需要处理再调用网易云信api
    一般都是通过浏览器自身的navigator.mediaDevices.getUserMedia去获取浏览器的语音授权
    以下是聊天框以及获取语音发送授权,封装成组件
<script lang="ts" setup>
import { ref, reactive, onMounted, nextTick, watch, inject, computed } from 'vue'
import { message } from 'ant-design-vue'
import { formatDateTime } from '@/utils/utils'
const chunks = ref<Array<any>>([])
const chunkList: any = ref<Array<{idClient: stringduration: numberstream: stringusername: stringflow: stringtime: string}>
>([]) // 显式指定类型
const audioDiv = ref<any>()
const btnText = ref('按住说话')
let recorder: any = reactive({})
// 父组件中的语音消息数组(包括历史消息和收发消息)
const props = defineProps({audioMessageList: {type: Array,default: () => []}
})
const isDataLoaded = ref(false) // 添加标志位
// 定义父组件中发送语音的消息
const emits = defineEmits(['sendFileVoice', 'createTeam'])
const requestAudioAccess = () => {navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {recorder = new window.MediaRecorder(stream)bindEvents()},(error) => {message.info('出错,请确保已允许浏览器获取录音权限')})
}
const onMousedown = () => {onStart()btnText.value = '松开结束'
}
const onMouseup = () => {onStop()btnText.value = '按住说话'
}
const onStart = () => {recorder.start()
}
const onStop = () => {recorder.stop()
}
const onPlay = (index: any) => {chunkList.value.forEach((item: any) => {item.wink = false})const item: any = chunkList.value[index]audioDiv.value.src = item.streamaudioDiv.value.play()bindAudioEvent(index)
}const bindAudioEvent = (index: any) => {const item: any = chunkList.value[index]audioDiv.value.onplaying = () => {item.wink = true}audioDiv.value.onended = () => {item.wink = false}
}const bindEvents = () => {recorder.ondataavailable = getRecordingDatarecorder.onstop = saveRecordingData
}const getRecordingData = (value) => {chunks.value.push(value.data)
}
// 将语音消息进行保存和转化
const saveRecordingData = () => {const blob = new Blob(chunks.value, { type: 'audio/ogg; codecs=opus' })// 将语音发送到网易云信集成的IMemits('sendFileVoice', blob)const audioStream = URL.createObjectURL(blob)let duration = Math.ceil(blob.size / (6600 * 2))if (duration <= 1) {message.info('说话时间太短')return}if (duration > 60) {duration = 60}const currentTimestamp = formatDateTime(Date.now())chunkList.value.push({ duration, stream: audioStream, time: currentTimestamp })chunks.value = []nextTick(() => {let msgList = document.getElementById('msgList')// 滚动到最底部if (msgList) {msgList.scrollTop = msgList.scrollHeight}})
}
watch(() => props.audioMessageList.length,(newValue) => {console.log('newValue --->>>', newValue)if (props.audioMessageList && props.audioMessageList.length > 0) {chunkList.value = [...props.audioMessageList]}},{ deep: true, immediate: true }
)
const shouldShowTime = (item) => {const currentIndex = chunkList.value.findIndex((el) => el.idClient === item.idClient)if (currentIndex === 0) {return true // 第一项总是显示时间}console.log(item, 'item.time')const currentTime = Date.parse(item.time)const previousTime = Date.parse(chunkList.value[currentIndex - 1].time)const timeDiffMinutes = (currentTime - previousTime) / (1000 * 60)return timeDiffMinutes >= 2 // 返回是否大于或等于两分钟
}
const handleScroll = () => {const container = document.getElementById('msgList')if (container) {// 判断是否滚动到顶部if (container.scrollTop === 0 && !isDataLoaded.value) {console.log('滚动到顶部加载')emits('createTeam')isDataLoaded.value = true}}
}
onMounted(() => {if (!navigator.mediaDevices) {message.info('您的浏览器不支持获取用户设备')return}if (!window.MediaRecorder) {message.info('您的浏览器不支持录音')return}requestAudioAccess()
})
</script>
<template><div class="recorder-wrapper"><div class="phone"><div class="phone-body"><div class="phone-content"><transition-group tag="ul" id="msgList" @scroll="handleScroll" class="msg-list"><li v-for="(item, index) in chunkList" :key="index" class="msg"><div class="msg-time" v-if="shouldShowTime(item)">{{ item.time }}</div><divclass="msg-content":class="item.flow === 'in' ? 'from-user' : 'from-other'"@click="onPlay(index)"@touchend.prevent="onPlay(index)"><div class="msg-body" v-if="item.flow === 'in'"><div class="username">{{ item.username }}</div><div class="avatar"></div><div:class="[item.flow === 'in' ? 'audio-left' : 'audio-right',{ wink: item.wink }]"v-cloak:style="{ width: 20 * item.duration + 'px' }"><span>(</span><span>(</span><span>(</span></div><div class="duration">{{ item.duration }}"</div></div><div class="msg-body" v-else><div class="duration">{{ item.duration }}"</div><div:class="[item.flow === 'in' ? 'audio-left' : 'audio-right',{ wink: item.wink }]"v-cloak:style="{ width: 20 * item.duration + 'px' }"><span>(</span><span>(</span><span>(</span></div><div class="avatar"></div><div class="username">{{ item.username }}</div></div></div></li></transition-group></div></div></div><audio ref="audioDiv"></audio><divclass="phone-operate"@mousedown="onMousedown"@touchstart.prevent="onMousedown"@mouseup="onMouseup"@touchend.prevent="onMouseup">{{ btnText }}</div></div>
</template>
<style lang="less" scoped>
.phone {margin: 0 auto;font-size: 12px;border-radius: 35px;background-image: #e1e1e1;box-sizing: border-box;user-select: none;
}
.phone-body {height: 100%;background-color: #fff;
}
.phone-head {height: 30px;line-height: 30px;color: #fff;font-weight: bold;background-color: #000;
}
.phone-head span {display: inline-block;
}
.phone-head span:nth-child(2) {width: 100px;text-align: center;
}
.phone-head span:nth-child(3) {float: right;margin-right: 10px;
}
.phone-content {height: 282px;background-color: #f1eded;
}
.phone-operate {position: relative;line-height: 28px;text-align: center;cursor: pointer;font-weight: bold;box-shadow: 0 -1px 1px rgba(0, 0, 0, 0.1);
}
.phone-operate:active {background-color: #95a5a6;
}
.phone-operate:active:before {position: absolute;left: 50%;transform: translate(-50%, 0);top: -2px;content: '';width: 0%;height: 2px;background-color: #7bed9f;animation: loading 1s ease-in-out infinite backwards;
}
.msg-list {margin: 0;padding: 0;height: 100%;overflow-y: auto;-webkit-overflow-scrolling: touch;
}
.msg-list::-webkit-scrollbar {display: none;
}
.msg-list .msg {list-style: none;padding: 0 8px;margin: 10px 0;overflow: hidden;cursor: pointer;
}
.from-user {float: left;
}
.from-other {float: right;
}
.msg-time {width: 100%;text-align: center;height: 30px;line-height: 30px;
}
.msg-list .msg .avatar,
.msg-list .msg .audio-right,
.msg-list .msg .duration,
.msg-list .msg .username {float: right;
}
.load-history {width: 100%;height: 30px;text-align: center;
}
.msg-list .msg .avatar {width: 24px;height: 24px;line-height: 24px;text-align: center;background-color: #000;background: url('@/assets/images/avatar.png') 0 0;background-size: 100%;margin-right: 5px;
}
.msg-list .msg .audio-right {position: relative;margin-right: 6px;max-width: 116px;min-width: 30px;height: 24px;line-height: 24px;padding: 0 4px 0 10px;border-radius: 2px;color: #000;text-align: right;background-color: rgba(107, 197, 107, 0.85);
}
.msg-list .msg .audio-left {position: relative;margin-right: 6px;max-width: 116px;min-width: 30px;height: 24px;line-height: 24px;padding: 0 10px 0 4px;border-radius: 2px;color: #000;text-align: left;direction: rtl;background-color: rgba(107, 197, 107, 0.85);
}
.msg-list .msg.eg {cursor: default;
}
.msg-list .msg.eg .audio {text-align: left;
}
.msg-list .msg .audio-right:before {position: absolute;right: -8px;top: 8px;content: '';display: inline-block;width: 0;height: 0;border-style: solid;border-width: 4px;border-color: transparent transparent transparent rgba(107, 197, 107, 0.85);
}
.msg-list .msg .audio-left:before {position: absolute;left: -8px;top: 8px;content: '';display: inline-block;width: 0;height: 0;border-style: solid;border-width: 4px;border-color: transparent rgba(107, 197, 107, 0.85) transparent transparent;
}
.msg-list .msg .audio-left span {color: rgba(255, 255, 255, 0.8);display: inline-block;transform-origin: center;
}
.msg-list .msg .audio-left span:nth-child(1) {font-weight: 400;
}
.msg-list .msg .audio-left span:nth-child(2) {transform: scale(0.8);font-weight: 500;
}
.msg-list .msg .audio-left span:nth-child(3) {transform: scale(0.5);font-weight: 700;
}
.msg-list .msg .audio-right span {color: rgba(255, 255, 255, 0.8);display: inline-block;transform-origin: center;
}
.msg-list .msg .audio-right span:nth-child(1) {font-weight: 400;
}
.msg-list .msg .audio-right span:nth-child(2) {transform: scale(0.8);font-weight: 500;
}
.msg-list .msg .audio-right span:nth-child(3) {transform: scale(0.5);font-weight: 700;
}
.msg-list .msg .audio-left.wink span {animation: wink 1s ease infinite;
}
.msg-list .msg .audio-right.wink span {animation: wink 1s ease infinite;
}
.msg-list .msg .duration {margin: 3px 2px;
}
.msg-list .msg .msg-content {display: flex;flex-direction: column;
}.msg-list .msg .msg-content .msg-time {margin-bottom: 5px;
}.msg-list .msg .msg-content .msg-body {display: flex;flex-direction: row;align-items: center;
}.msg-list .msg .msg-content .msg-body .username {padding: 0 5px 0 0;
}.msg-list .msg .msg-content .msg-body .audio {margin-right: 10px;
}.msg-list .msg .msg-content .msg-body .duration {font-style: italic;
}
.fade-enter-active,
.fade-leave-active {transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-to {opacity: 0;
}
@keyframes wink {from {color: rgba(255, 255, 255, 0.8);}to {color: rgba(255, 255, 255, 0.1);}
}
@keyframes loading {from {width: 0%;}to {width: 100%;}
}
</style>

3.将语音消息转化然后调用Nim中的发送语音消息API

<!--* @Description: 功能* @Author: 黑猫* @Date: 2023-06-30 15:31:32* @LastEditTime: 2023-07-17 11:04:05* @FilePath: \pc-exam-invigilate\src\components\chatting\index.vue
-->
<script lang="ts" setup>
import { ref, onMounted, computed, reactive } from 'vue'
import Nim from '@/utils/Nim'
import Header from '@/components/Header.vue'
import ChatPanel from './components/ChatPanel.vue'
import { NimScene } from '@/constants'
import { useConfigStore } from '@/stores/modules/config'
import { formatDateTime } from '@/utils/utils'
const visible = ref(false)
setTimeout(() => {visible.value = true
}, 100)
const emit = defineEmits(['closeChatting', 'openChatting', 'createTeam'])
const props = defineProps({checkedObj: {type: Object,default: () => ({})},inspectorNimRef: {type: Object,default: () => ({})},audioMessageList: {type: Array,default: () => []}
})
const audioMessageList = computed(() => {return props.audioMessageList
})
const username = ref('巡考员')
const teamId = ref<string>('')
const configStore = useConfigStore()
const chatPanelRef = ref<InstanceType<typeof ChatPanel> | null>(null)
// 发送语音
const sendFileVoice = (audio) => {// 创建一个 file 类型的 input 节点let fileInput = document.createElement('input')fileInput.type = 'file'// 创建一个 Blob 对象,代表你的音频文件 audioBloblet audioBlob = new Blob([audio], { type: 'audio/ogg' })// 创建一个 File 对象,代表音频文件let file = new File([audioBlob], 'audio.ogg', { type: 'audio/ogg' })// 创建一个自定义的 FileList 对象let fileList = new DataTransfer()fileList.items.add(file)// 将自定义的 FileList 设置到 fileInput.files 属性中Object.defineProperty(fileInput, 'files', {value: fileList.files,writable: false})teamId.value = (configStore.teamId || localStorage.getItem('teamId')) as stringtry {props.inspectorNimRef.sendFile({scene: NimScene.GroupChat,to: teamId.value,type: 'audio',fileInput: fileInput,beginupload: function (upload) {// - 如果开发者传入 fileInput, 在此回调之前不能修改 fileInput// - 在此回调之后可以取消图片上传, 此回调会接收一个参数 `upload`, 调用 `upload.abort();` 来取消文件上传},uploadprogress: function (obj) {// console.log('文件总大小: ' + obj.total + 'bytes')// console.log('已经上传的大小: ' + obj.loaded + 'bytes')// console.log('上传进度: ' + obj.percentage)// console.log('上传进度文本: ' + obj.percentageText)},uploaddone: function (error, file) {// console.log(error)// console.log(file)console.log('上传' + (!error ? '成功' : '失败'))},beforesend: function (msg) {// console.log('正在发送team 语音消息, id=' + msg.idClient)pushMsg(msg) // pushMsg需要用户自己实现,将消息压入到自己的数据中},done: sendMsgDone})} catch (error) {console.log(error)}
}
// 将消息存入数组
const pushMsg = (msg: any) => {// 将消息压入到自己的数据中// 例如,将消息存储在一个数组中const dataTime = formatDateTime(msg.time)const duration = Math.ceil(msg.file.size / (6600 * 2))if (duration <= 1) {return}audioMessageList.value.push({duration,stream: msg.file.url,wink: false,username: msg.fromNick,flow: msg.flow,time: dataTime})
}
// 发送语音是否成功或失败
const sendMsgDone = (error: any, file: any) => {if (!error) {console.log('消息发送成功')} else {console.error('消息发送失败')}
}
// 关闭聊天弹窗
const handleClose = () => {emit('closeChatting', false)
}
const createTeam = () => {emit('createTeam')
}
</script>
<template><a-modal v-model:open="visible" @cancel="handleClose" :footer="null" width="90%"><a-layout><a-layout-header class="header" style="background: #fff"><Header :username="username" /></a-layout-header><a-layout style="margin: 10px"><a-layout-content><div style="flex: 1"><ChatPanelref="chatPanelRef":audioMessageList="audioMessageList"@sendFileVoice="sendFileVoice"@createTeam="createTeam"/></div></a-layout-content></a-layout></a-layout></a-modal>
</template>
<style lang="less" scoped>
.chatting-layout {display: flex;flex-direction: column;flex: 1;background-color: #eef1f6;.header,.chat-list-container,.chat-dialog-container {display: flex;flex-direction: column;background: #fff;text-align: center;}.header {height: auto;margin-bottom: 15px;padding: 0;:deep(.header-wrapper) {padding: 0 166px 0 72px;box-shadow: none;}}.chat-list-container {padding-bottom: 10px;@media (max-width: 1440px) {width: 270px;}border-radius: 0px 20px 0px 0px;:deep(.teacher-info) {flex-direction: row;margin-top: none;padding: 20px 0;align-items: center;justify-content: center;img {margin-right: 17px;}span {margin-top: 0;}}}.chat-dialog-container {margin: 0 15px;padding: 0;border-radius: 20px;.send-chat {height: 218px;}}.examinee-info-container {@media (max-width: 1440px) {width: 270px;}display: flex;flex-direction: column;overflow: hidden;}
}
</style>

以上就是简单的进行语音收发和获取历史消息记录
如果需求复杂或者自己感兴趣可以在api文档中查看更多功能
在这里插入图片描述

这篇关于集成网易云信SDK,进行即时通信-Web(语音通信)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/306555

相关文章

大语言模型(LLMs)能够进行推理和规划吗?

大语言模型(LLMs),基本上是经过强化训练的 n-gram 模型,它们在网络规模的语言语料库(实际上,可以说是我们文明的知识库)上进行了训练,展现出了一种超乎预期的语言行为,引发了我们的广泛关注。从训练和操作的角度来看,LLMs 可以被认为是一种巨大的、非真实的记忆库,相当于为我们所有人提供了一个外部的系统 1(见图 1)。然而,它们表面上的多功能性让许多研究者好奇,这些模型是否也能在通常需要系

通信系统网络架构_2.广域网网络架构

1.概述          通俗来讲,广域网是将分布于相比局域网络更广区域的计算机设备联接起来的网络。广域网由通信子网于资源子网组成。通信子网可以利用公用分组交换网、卫星通信网和无线分组交换网构建,将分布在不同地区的局域网或计算机系统互连起来,实现资源子网的共享。 2.网络组成          广域网属于多级网络,通常由骨干网、分布网、接入网组成。在网络规模较小时,可仅由骨干网和接入网组成

SpringBoot集成Netty,Handler中@Autowired注解为空

最近建了个技术交流群,然后好多小伙伴都问关于Netty的问题,尤其今天的问题最特殊,功能大概是要在Netty接收消息时把数据写入数据库,那个小伙伴用的是 Spring Boot + MyBatis + Netty,所以就碰到了Handler中@Autowired注解为空的问题 参考了一些大神的博文,Spring Boot非controller使用@Autowired注解注入为null的问题,得到

ROS话题通信流程自定义数据格式

ROS话题通信流程自定义数据格式 需求流程实现步骤定义msg文件编辑配置文件编译 在 ROS 通信协议中,数据载体是一个较为重要组成部分,ROS 中通过 std_msgs 封装了一些原生的数据类型,比如:String、Int32、Int64、Char、Bool、Empty… 但是,这些数据一般只包含一个 data 字段,结构的单一意味着功能上的局限性,当传输一些复杂的数据,比如:

vue项目集成CanvasEditor实现Word在线编辑器

CanvasEditor实现Word在线编辑器 官网文档:https://hufe.club/canvas-editor-docs/guide/schema.html 源码地址:https://github.com/Hufe921/canvas-editor 前提声明: 由于CanvasEditor目前不支持vue、react 等框架开箱即用版,所以需要我们去Git下载源码,拿到其中两个主

Python应用开发——30天学习Streamlit Python包进行APP的构建(9)

st.area_chart 显示区域图。 这是围绕 st.altair_chart 的语法糖。主要区别在于该命令使用数据自身的列和指数来计算图表的 Altair 规格。因此,在许多 "只需绘制此图 "的情况下,该命令更易于使用,但可定制性较差。 如果 st.area_chart 无法正确猜测数据规格,请尝试使用 st.altair_chart 指定所需的图表。 Function signa

人工和AI大语言模型成本对比 ai语音模型

这里既有AI,又有生活大道理,无数渺小的思考填满了一生。 上一专题搭建了一套GMM-HMM系统,来识别连续0123456789的英文语音。 但若不是仅针对数字,而是所有普通词汇,可能达到十几万个词,解码过程将非常复杂,识别结果组合太多,识别结果不会理想。因此只有声学模型是完全不够的,需要引入语言模型来约束识别结果。让“今天天气很好”的概率高于“今天天汽很好”的概率,得到声学模型概率高,又符合表达

JavaWeb系列二十: jQuery的DOM操作 下

jQuery的DOM操作 CSS-DOM操作多选框案例页面加载完毕触发方法作业布置jQuery获取选中复选框的值jQuery控制checkbox被选中jQuery控制(全选/全不选/反选)jQuery动态添加删除用户 CSS-DOM操作 获取和设置元素的样式属性: css()获取和设置元素透明度: opacity属性获取和设置元素高度, 宽度: height(), widt

气象站的种类和应用范围可以根据不同的分类标准进行详细的划分和描述

气象站的种类和应用范围可以根据不同的分类标准进行详细的划分和描述。以下是从不同角度对气象站的种类和应用范围的介绍: 一、气象站的种类 根据用途和安装环境分类: 农业气象站:专为农业生产服务,监测土壤温度、湿度等参数,为农业生产提供科学依据。交通气象站:用于公路、铁路、机场等交通场所的气象监测,提供实时气象数据以支持交通运营和调度。林业气象站:监测林区风速、湿度、温度等气象要素,为林区保护和

企业如何进行员工的网络安全意识培训?

企业网络安全意识培训的重要性         企业网络安全意识培训是提升员工网络安全素质的关键环节。随着网络技术的快速发展,企业面临的网络安全威胁日益增多,员工的网络安全意识和技能水平直接关系到企业的信息安全和业务连续性。因此,企业需要通过系统的网络安全意识培训,提高员工对网络安全的认识和防范能力,从而降低企业在面对潜在安全风险时的损失和影响。 企业网络安全意识培训的方法         企