当前位置: 首页 > Web前端 > HTML

富媒体即时传递在客服IM消息通信中的实践

时间:2023-03-28 19:08:17 HTML

介绍富媒体是指在即时通信过程中传输的图片、语音、视频、文件等媒体媒体的展示方式。一、背景一站式客服平台旨在为得物生态圈内的客服人员提供一站式服务办公平台。我们有多个业务线。客服在与用户聊天时,有很多场景需要发送富媒体。与普通的文本传输相比,富媒体可以直观地让用户了解消息的内容,但在传输过程中也面临着文件大、内存消耗大、传输过程长等问题。2、面临的挑战客服向用户发送大文件(视频、图片)等消息的一般流程如下:首先通过文件上传服务上传到CDN,返回对应的CDN地址链接在同一时间;其次,通过IM获取CDN地址链接,网关返回链接给UI进行渲染。在整个传输过程中,前端必须等待文件上传成功,拿到链接后才能渲染。如果传输的文件很大,客服需要等待很长时间,对客服接通效率影响很大。理想的方式是,当客服发送文件时,文件会立即在聊天窗口中呈现。这时候呈现的不是完整的文件,而是文件的画像,比如文件名,封面图,通过消息的上传状态。控制。以视频传输为例,如果将视频直接存入缓存,显示在客服聊天内容区,巨大的缓存会导致用户浏览器分分钟崩溃。比如一个大于70M的视频,如果网络和电脑硬件环境较好,从读取文件到获取第一帧图传的过程大约需要2~3s。如果网络正常,同一环境中有很多人。在发送视频文件或一般硬件设备的情况下,时间会更长。如何在不影响客服接线效率的情况下,让大文件传输如丝般顺畅?三、解决方案及结果1、使用fileReader.target.result作为视频的url来渲染页面。最开始的方法是在视频上传到CDN的时候抓取视频的第一帧,然后将抓取的视频的第一帧上传到CDN。然后通过一个长链(wss)发送给客户端,因为拦截第一帧是一个同步过程,需要拿到截图的url才能渲染到页面,导致客服看不到一点击发送就在聊天界面发送对于外发视频,如上视频所示,客服无法感知视频发送进度。通过FileReader读取文件信息:exportfunctiongetFileInfo(file:File):Promise{returnnewPromise((resolve,reject)=>{try{constreader=newFileReader()reader.readAsDataURL(file)reader.onload=(event:ProgressEvent)=>{resolve(event)}}catch(e){reject(e)}})}通过返回的文件信息设置属性:exportfunctiongetVideoInfo(file){returnnewPromise((resolve,reject)=>{getFileInfo(file).then(fileReader=>{consttarget=fileReader.target.resultif(/video/g.test(file.type)){constvideo=document.createElement('video')video.muted=truevideo.setAttribute('autoplay','autoplay')video.setAttribute('src',target)video.addEventListener('loadeddata',()=>{//...})video.onerror=e=>reject(e)}}).catch(e=>reject(e))})}如上代码video.setAttribute('src',target),如果使用target作为视频url在页面上渲染,页面会分分钟崩溃。可以看一下1M的视频文件,通过readAsDataURL(file)读取文件内容得到一个base64的data:url字符串,使用这个字符串渲染相当于给页面添加了一个1.4M的字符串内容,如图下图中,这样做的后果不堪设想。如果文件稍大,会有更明显的滞后。所以这个方案在开发之初就被否决了。2.使用的URL.createObjectURL(file)。在第一种方案获取URL被拒绝后,研究了URL.createObjectURL的实现。使用URL.createObjectURL(file)获取URL(此URL对象代表指定的File对象或Blob对象),然后放入聊天数据缓存中,以便快速发送到客服聊天窗口页面。其主要实现代码如下:if(/********/){//...//.blob用作预览视频的urlstate.previewVideoSrc=URL.createObjectURL(file)state.previewVideo=true状态。cachePreviewVideoFile=filenextTick(()=>{focus()})}else{//...}经过这样的改造,很明显视频发送后,可以快速显示在页面上,这样客服可以感知视频发送的状态和进度,相比第一种方案,发送视频的流程有了明显的提升。渲染后的代码效果如下图所示:但是!向客户端发送视频信息时,需要携带首帧和视频时长作为显示封面。历史的做法是:先由前端获取文件信息,通过canvas转成图片,再上传到CDN;在获取到第一帧和文件信息后,先上传到CDN,返回URL再通过长链发送给用户,更新页面的URL地址为CDN返回的真实地址。获取第一帧时,需要读取文件。由于是读取文件,所以还是需要一定的时间。如以下代码片段所示,这项耗时的工作也会影响客户服务体验。exportfunctiongetVideoInfo(file,msgid?:string){returnnewPromise((resolve,reject)=>{getFileInfo(file,msgid).then(fileReader=>{consttarget=fileReader.target.resultif(/video/g.test(file.type)){constvideo=document.createElement('video')video.muted=truevideo.setAttribute('autoplay','autoplay')//目标仅用作创建视频的url获取视频大小、播放时长等基本信息,不用于页面渲染canvas.width=video.videoWidthcanvas.height=video.videoHeightconstwidth=video.videoWidthconstheight=video.videoHeightcanvas.getContext('2d')!.drawImage(video,0,0,width,height)constsrc=canvas.toDataURL('image/jpg')constimgFile=dataURLtoFile(src,`video_${Math.random()}.png`)returngetImgInfo(imgFile,fileReader.msgid).then(({宽度:imgWidth,高度:imgHeight,文件:imgFile,大小:imgSize,src:imgSrc,msgid})=>{resolve({//...})})})video.onerror=e=>{//...reject(e)}}}).catch(e=>{reject(e)})})}上传视频时,file服务器提供了获取首帧的方法获取图片首帧,只需在链接地址上拼接相应的参数即可,如下图://图片首帧的拼接URL地址为获得exportconstthumbSuffix=`?x-oss-process=video/snapshot,****`exportfunctionaddOssImageParams(url,isThumb=false){constsuffix=isThumb?thumbSuffix:urlSuffixif(!url)return''//...returnurl}但在实际使用场景中,仅仅获取视频的第一帧信息是不够的。还需要获取视频的宽高、播放时长等信息,通过网络请求发送给网关,最后在客户端显示和读取文件。这个过程是不可避免的,而且很耗时。问题仍然需要解决。3、WebWorker异步读取文件信息。方案二虽然实现了文件的快速渲染,但是如果读取文件信息是在浏览器主线程中完成的话,时间长了还是会阻碍客服操作。如果这个过程可以异步实现就完美了。JS虽然是单线程的,但是浏览器提供了WebWorker的能力,使得JS也可以通过异步的方式与主线程通信。首先比较一下浏览器主线程执行和主子线程执行的区别,如下图:当浏览器主线程执行发送文件时,如果发送文件的任务是没有完成,其他任务会被阻塞,相当于发送过程中。客服也无能为力;浏览器主子线程执行发送文件时,通过子线程读取文件。在读取文件的过程中,主线程可以继续执行其他任务,等到子线程读取完文件后,通过postMessage发送相关信息通知主线程文件已经读取完毕,主线程再次开始渲染。整个过程对客户服务没有任何阻碍。WebWorker主子线程的实现过程如下:首先在线程订阅中心创建一个子线程任务,如下://子线程任务导出函数subWork(){self.onmessage=({data:{file}})=>{try{//读取文件信息//...//发送相应信息self.postMessage({fileReader:****})}catch(e){self.postMessage({fileReader:undefined})}}}然后在线程订阅中心初始化Worker如下:exportconstcreateWorker=(subWorker,file,resolve,reject)=>{constworker=newWorker(URL.createObjectURL(newBlob([`(${subWorker.toString()})()`])))//发送给子线程worker.postMessage({file})//监听子线程返回数据worker.onmessage=({data:{fileReader}})=>{resolve(fileReader)//获取结果后关闭线程worker.terminate()}//监听异常worker.onmessageerror=function(){worker.terminate()}}最后,在th中调用Workere主线程获取文件信息,如下://创建主线程任务exportconstgetFileInfoFromSubWorker=files=>{returnnewPromise((resolve,reject)=>{createWorker(subWork,files,resolve,reject)})}通过以上三步,基本上就可以获取到文件信息了。获取视频信息对象后,可以通过URL.createObjectURL(file)获取视频相关的属性信息,如下:exportfunctiongetVideoInfo(file,blob,msgid?:string){returnnewPromise((resolve,reject)=>{if(/video/g.test(file.type)){constvideo=document.createElement('video')video.muted=truevideo.setAttribute('autoplay','autoplay')//blob作为url:URL.createObjectURL(file)video.setAttribute('src',blob)video.addEventListener('loadeddata',()=>{constwidth=video.videoWidthconstheight=video.videoHeightresolve({videoWidth:width,videoHeight:height,videoDuration:video.duration*1000,videoFile:file,videoSize:file.size,videoSrc:blob,msgid})})video.onerror=e=>{reject(e)}}})}如上上面提到,获取文件对象信息后,通过blob方法直接获取视频的宽高作为图片第一帧的宽高。两者结合实现了如何在不影响客服运行的情况下发送视频。如丝般柔滑。通过WebWorker+URL.createObjectURL(file)的方式,解决了富媒体文件秒发的问题,不管发送成功与否,即先在聊天框显示视频信息,然后通过发送状态发送。标识当前发送进度。4.总结富媒体发送涉及到很多IM场景。用什么样的技术让客服和用户的沟通更方便,是本文关注的重点。通过在实际客服业务场景中的实践,本文的技术方案很好地解决了业务中的问题,并在实际线上运行较为稳定。发现业务中的问题,用技术手段解决问题,提高客服效率,给用户带来良好的体验是我们不变的目标。如果您在阅读本文后有更好的建议,可以给我们留言。另外客服领域的技术点远不止这些,脚踏实地,一步一个脚印,相信即时通讯在客服领域的沉淀会越来越好。五、知识拓展1、文件读取实现的区别URL.createObjectURL()和FileReader.readAsDataURL(file)都可以获取文件信息。为什么我们选择使用前者而不是后者?两者的主要区别是:通过FileReader.readAsDataURL(file)得到的是一个data:base64字符串,base64位的字符串比较大。通过URL.createObjectURL(blob)会创建一个DOMString,其中包含一个包含文件信息的URL(指定的FileObject或Blob对象)执行时机不同:createObjectURL是立即执行FileReader。readAsDataURL是异步执行的(一段时间后)对内存的使用是不同的:createObjectURL返回一个带哈希的url,它总是存储在内存中。当读取文档时触发Unload或者执行revokeObjectURL释放内存;FileReader.readAsDataURL返回的是base64字符串,比bloburl更耗内存,但是这个数据会通过垃圾回收机制自动清除。使用选项:使用createObjectURL可以节省性能并获得更快的速度;如果设备性能足够好,想获取图片的base64,可以使用FileReader.readAsDataURL。2、流媒体、富媒体、多媒体的概念流媒体、富媒体、多媒体有什么区别?流媒体:在使用的同时,后台下载一些以后可能用到的东西。富媒体:混合了文本、图片、视频和音频的页面内容。多媒体:图片、文字、音频、视频等素材。其中,流媒体是一种传输方式,富媒体是一种区别于纯文本的显示方式,多媒体是一种显示内容的手段。推荐阅读:得物客服IM消息通信SDK自研路客服领域微前端实践*文/君@德物技术公众号