express+vue在线im实现【三】

往期内容

express+vue在线im实现【一】
express+vue在线im实现【二】

本期示例

在这里插入图片描述

在这里插入图片描述

本期总结

  • 支持各种类型的文件上传,常见文件类型图片,音频,视频等,上传时同步获取音频与视频的时长,以及使用上传文件的缓存路径来作为video播放地址,使用canvas生成视频的第一帧作为封面(本期的第一个亮点)
  • 使用腾讯播放器完成视频播放,支持自定义控件功能与样式,这儿示例了前进后退15s与设置里的配置(本期的第二个亮点)
  • 音频使用html5标签audio播放
  • 其他类型统一为下载

下期安排

  • 在线音频录制,发送
  • 在线语音

重点总结

上传部分

input chang方法

        // 有上传文件inputFileChange(e) {let file = e.target.files[0]if (!file) returnlet { type } = file// 图片if (type.indexOf('image') >= 0) {this.commonUploadImg(file, 'im').then(({ url }) => {// 发送消息this.pushInfo({msg_type: '2',content: url,})}).catch(() => {}).finally(() => {this.$refs.fileInput.value = ''})return}// mp4if (type.indexOf('video') >= 0) {if (type != 'video/mp4') {this.$message.warning('请上传mp4格式的视频')return}// 视频上传this.commonUploadFile(file, 'im', 500).then(({ url = '' }) => {// 发送消息this.pushInfo({msg_type: '4',content: url,})}).catch(() => {})this.$refs.fileInput.value = ''return}// audioif (type.indexOf('audio') >= 0) {if (type != 'audio/ogg') {this.$message.warning('请上传audio/ogg格式的音频')return}this.commonUploadFile(file, 'im', 500).then(({ url = '' }) => {// 发送消息this.pushInfo({msg_type: '5',content: url,})}).catch(() => {})this.$refs.fileInput.value = ''return}// 其他类型this.commonUploadFile(file, 'im', 500).then(({ url = '' }) => {// 发送消息this.pushInfo({msg_type: '3',content: url,})}).catch(() => {})this.$refs.fileInput.value = ''},

统一的上传方法

/*** 公共上传图片方法(相比下面的上传文件方法,多了压缩与获取图片宽高)* @param {*} oldFile  文件信息* @param {*} type     服务器的存储位置* @param {*} minSize  最小产生loading的文件大小* @returns*/
export function commonUploadImg(oldFile, type, minSize = 500) {return new Promise(async (resolve, reject) => {let { size } = oldFile// 对于大于200k的图片添加一个loadingconst currentSize = size / 1024let loading = nullif (currentSize > minSize) {loading = this.$klLoading()}try {let { file: miniFile, newWidth, newHeight } = await compressImg(oldFile)const formData = new FormData()formData.append('file', miniFile)const devicePixelRatioa = window.devicePixelRatio || 1// 上传图片,同时需要上传图片的宽高upload_imgs_im(formData, {type,devicePixelRatioa,width: Math.floor(newWidth / devicePixelRatioa),height: Math.floor(newHeight / devicePixelRatioa),}).then((res) => {resolve({ url: `/${type}/` + res.data[0]?.filename })})} catch (err) {this.$message.warning('请重新上传')reject()}loading && loading.close()})
}/*** 公共上传通用文件的方法* @param {*} oldFile  文件信息* @param {*} type     服务器的存储位置* @param {*} minSize  最小产生loading的文件大小* @param {*} needPoster   对于视频需要上传封面图,这个用于获取封面图* @returns* */
export function commonUploadFile(oldFile, type, minSize = 500, needPoster = false) {return new Promise(async (resolve, reject) => {let { size = 0 } = oldFile// 对于大于minSize的图片添加一个loadinglet loading = nullif (size / 1024 > minSize) {loading = this.$klLoading()}const formData = new FormData()formData.append('file', oldFile)// 封面图对象let preImg = {}if (needPoster) {try {// 获取文件的缓存地址const file_path = getObjectURL(oldFile)// 获取视频首帧的图片宽高及babs64图片const { width, height, pre_img, duration } = await getVideoCover(file_path)// 封面图toFilelet file = this.base64ToFile(pre_img, createId())// 上传封面图let res = await commonUploadImg(file, 'im', 5000).catch(() => {return {}})const poster = res.url || ''Object.assign(preImg, {video_width: width,video_height: height,poster,time: duration,})} catch (err) {console.log('err', err)this.$message.error('获取封面失败,请重试~')reject(err)loading && loading.close()return}}let res = await upload_imgs_im(formData, {type,}).catch((err) => {console.log('err', err)return {}})resolve({ url: `/${type}/` + res.data[0]?.filename, ...preImg })loading && loading.close()})
}// 富文本给图片补充完整路径
export function parseHtmlUrl(htmlString) {function removeTrailingSlash(str) {if (str.endsWith('/')) {return str.slice(0, -1) // 使用slice方法从字符串的开头到倒数第二个字符(不包括结尾的/)}return str // 如果字符串不以/结尾,则直接返回原字符串}const { origin } = locationconst baseurl = removeTrailingSlash(baseURL)const parser = new DOMParser()const doc = parser.parseFromString(htmlString, 'text/html')const imgs = doc.body.querySelectorAll('img')for (let index = 0; index < imgs.length; index++) {const element = imgs[index]const { src } = elementif (src && src.startsWith('/') && !src.startsWith('//')) {element.src = baseurl + element.src}}const serializer = new XMLSerializer()const modifiedHtml = serializer.serializeToString(doc.body)// 移除外层的bodylet div = document.createElement('div')div.innerHTML = modifiedHtmlreturn div.innerHTML
}// 截取视频的封面图
export function getVideoCover(url) {if (!url) returnreturn new Promise((resolve, reject) => {let dataURL = ''let video = document.createElement('video')video.setAttribute('crossOrigin', 'anonymous') //处理跨域video.setAttribute('src', url)video.setAttribute('autoplay', 'true')video.setAttribute('muted', 'true')video.setAttribute('playsinline', 'true')video.setAttribute('webkit-playsinline', 'true')video.setAttribute('x5-video-player-type', 'h5')// 设置时间为第一秒video.currentTime = 1// 播放错误监听video.addEventListener('error', (err) => {video.remove()reject(err)})// 兼容ios的上传,改成了延时获取let timer = setTimeout(() => {// 获取宽高let { videoWidth, videoHeight } = video// 创建canvas 取视频的第一帧作为封面图let canvas = document.createElement('canvas')canvas.width = videoWidthcanvas.height = videoHeightlet ctx = canvas.getContext('2d')ctx.drawImage(video, 0, 0, videoWidth, videoHeight)dataURL = canvas.toDataURL('image/jpeg')// 获取成功后清除节点video.remove()timer = nullclearTimeout(timer)resolve({width: videoWidth || 0,height: videoHeight || 0,pre_img: dataURL,duration: video.duration || 0,})}, 1000)})
}// 获取视频的本地地址
export function getObjectURL(file) {var url = null// 下面函数执行的效果是一样的,只是需要针对不同的浏览器执行不同的 js 函数而已if (window.createObjectURL !== undefined) {// basicurl = window.createObjectURL(file)} else if (window.URL !== undefined) {// mozilla(firefox)url = window.URL.createObjectURL(file)} else if (window.webkitURL !== undefined) {// webkit or chromeurl = window.webkitURL.createObjectURL(file)}return url
}

视频播放组件

核心播放组件

<template><div class="demo"><videoid="player-container-id"preload="auto"playsinlinewebkit-playsinlineclass="tx-video":style="getStyle"></video></div>
</template><script>
import { createControl } from './index.js'
const plugins = [{isAppendHead: true,css: 'https://web.sdk.qcloud.com/player/tcplayer/release/v5.0.1/tcplayer.min.css',},{js: 'https://web.sdk.qcloud.com/player/tcplayer/release/v5.0.1/tcplayer.v5.0.1.min.js',},
]
export default {props: {videoUrl: {type: String,default: '',},width:{type: String,default: '600px',},height:{type: String,default:'400px',}},data() {return {list: [1111],}},computed: {player() {let { videoStore } = this.$store.statereturn videoStore.player || {}},getStyle(){return {width:this.width,height:this.height,}}},mounted() {this.getIndexDBJS(plugins).finally(() => {this.init()})},beforeDestroy() {this.player.dispose()this.delPageScript(plugins)},methods: {async init() {this.$store.commit('videoStore/SET_PLAYER', null)let { player } = thisif (player && this.getType(player.dispose) === 'function') {// 先销毁this.player.dispose()await this.$nextTick()}player = TCPlayer('player-container-id', {sources: [{src: this.videoUrl,},],licenseUrl: this.videoUrl,})player.src(this.videoUrl)player.on('loadedmetadata', () => {// 视频加载完成-设置控件createControl(this)})this.$store.commit('videoStore/SET_PLAYER', player)},},
}
</script><style scoped>
/deep/ .tcp-skin .vjs-custom-control-spacer {display: flex;justify-content: space-between;align-items: center;
}
</style>

控件index.js入口

// 前进后退控件
import fast_forward from './components/fast_forward.vue'// 系统控制控件
import sys from './components/sys.vue'export function createControl(that) {// 处理前进后退的播放控件const Ctor = Vue.extend(fast_forward)// create 可以传入props值const comp = new Ctor({propsData: {preImg: '//image.zlketang.com/public/news/others/imgs/web_pc/0283cad753b8be5df7a764d78f66dd31.png',nextImg:'//image.zlketang.com/public/news/others/imgs/web_pc/5510ac8bad62f39b6675a12574347598.png',},})comp.$mount()let controlBox = document.querySelector('.vjs-custom-control-spacer')if (!controlBox) return// 清空controlBox下的数据controlBox.innerHTML = ''controlBox.appendChild(comp.$el)// 监听组件的emit事件comp.$on('pre-fun', (data) => {console.log('pre-fun', data, that.list)})comp.$on('next-fun', (data) => {console.log('next-fun', data, that.list)})const sysCtor = Vue.extend(sys)// create 可以传入props值const sysComp = new sysCtor({propsData: {},})sysComp.$mount()controlBox.appendChild(sysComp.$el)
}

具体实现fast_forward.vue示例

<template><!-- 前进后退15s控件  --><div class="tx-video-control-fast-forward flex-center-wrap"><img@click="pre"class="backward-box-img":src="preImg"/><img@click="next"class="forward-box-img":src="nextImg"/></div>
</template><script>
export default {name: 'tx-video-control-fast-forward',props:{preImg:{type:String,default:''},nextImg:{type:String,default:''}},data() {return {}},methods: {pre() {this.$emit('pre-fun')},next() {this.$emit('next-fun')},},
}
</script><style scoped>
.backward-box-img {cursor: pointer;width: 20px;height: 20px;
}
.forward-box-img {cursor: pointer;width: 20px;height: 20px;margin-left: 24px;
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/32072.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WDF驱动开发-DMA(一)

在 Windows 7 及更早版本上&#xff0c;Kernel-Mode Driver Framework (KMDF) 仅支持 (DMA) 设备的总线-主直接内存访问。 此类设备包含其自己的 DMA 控制器。 在片上系统 (SoC) 上运行Windows 8及更高版本的平台上&#xff0c;该框架还支持系统模式 DMA&#xff0c;其中多个设…

视频讲解|基于模型预测算法的含储能微网双层能量管理模型【mpc】

1 主要内容 该讲解视频对应的免费程序链接为【防骗贴】基于模型预测算法的含储能微网双层能量管理模型&#xff0c;主要做的是一个微网双层优化调度模型&#xff0c;微网聚合单元包括风电、光伏、储能以及超级电容器&#xff0c;在微网的运行成本层面考虑了电池的退化成本&…

快捷方式(lnk)--加载HTA-CS上线

免责声明:本文仅做技术交流与学习... 目录 CS: HTA文档 文件托管 借助mshta.exe突破 本地生成lnk快捷方式: 非系统图标路径不同问题: 关于lnk的上线问题: CS: HTA文档 配置监听器 有效载荷---->HTA文档--->选择监听器--->选择powershell模式----> 默认生成一…

政务大厅引导系统:AR、VR技术革新引领政务服务体验升级

一、传统政务大厅面临的普遍痛点 随着城市的发展和政务服务需求的增长&#xff0c;传统的政务大厅面临着诸多挑战和痛点&#xff1a; 信息不对称&#xff1a;政务大厅内各部门信息分散&#xff0c;群众难以快速获取全面准确的服务信息&#xff0c;导致办事效率低下。 办事流…

计算机视觉 | 基于图像处理和边缘检测算法的黄豆计数实验

目录 一、实验原理二、实验步骤1. 图像读取与预处理2. 边缘检测3. 轮廓检测4. 标记轮廓序号 三、实验结果 Hi&#xff0c;大家好&#xff0c;我是半亩花海。 本实验旨在利用 Python 和 OpenCV 库&#xff0c;通过图像处理和边缘检测算法实现黄豆图像的自动识别和计数&#xff0…

JetBrains GoLand 2024 mac/win版:高效开发,Go无止境

JetBrains GoLand 2024是一款专为Go语言开发者设计的集成开发环境(IDE)&#xff0c;为开发者带来了更加高效、智能和便捷的编程体验。 GoLand 2024 mac/win版获取 在代码编辑方面&#xff0c;GoLand 2024提供了全行代码补全功能&#xff0c;通过利用先进的深度学习模型&#x…

力扣85.最大矩形

力扣85.最大矩形 遍历所有行作为底边 做求矩形面积&#xff08;84. class Solution {public:int maximalRectangle(vector<vector<char>>& matrix) {if (matrix.empty()) return 0;int n matrix.size(),m matrix[0].size();int res0;vector<int> li…

适耳贴合的气传导耳机,带来智能生活体验,塞那Z50耳夹耳机上手

现在大家几乎每天都会用到各种AI产品&#xff0c;蓝牙耳机也是我们必不可少的装备&#xff0c;最近我发现一款很好用的分体式气传导蓝牙耳机&#xff0c;它还带有一个具备AI功能的APP端&#xff0c;大大方便了我们日常的使用。这款sanag塞那Z50耳夹耳机我用过一段时间以后&…

开发指南033-数据库兼容

元芳&#xff0c;你怎么看&#xff1f; 单一数据库自身就有一些不同处理之处&#xff0c;如果一个平台要兼容所有数据库&#xff0c;就是难上加难&#xff0c;像isnull函数各数据库就不同。 对于这类问题&#xff0c;平台采用统一自定义函数解决&#xff0c;例如上面的round函…

模式分解的概念(下)-无损连接分解的与保持函数依赖分解的定义和判断、损失分解

一、无损连接分解 1、定义 2、检验一个分解是否是无损连接分解的算法 输入与输出 输入&#xff1a; 关系模式R&#xff08;U&#xff0c;F&#xff09;&#xff0c;F是最小函数依赖集 R上的一个分解 输出&#xff1a; 判断分解是否为无损连接分解 &#xff08;1&#x…

JAVA同城服务场馆门店预约系统支持H5小程序APP源码

&#x1f4f1;一键预约&#xff0c;畅享无忧体验&#x1f3e2; &#x1f680;一、开启预约新纪元 在繁忙的都市生活中&#xff0c;我们常常因为时间紧张而错过心仪的门店或场馆服务。然而&#xff0c;有了“门店场馆预约小程序”&#xff0c;这些问题都将迎刃而解。这款小程序…

群辉NAS中文件下载的三种方案

目录 一、迅雷套件 1、添加套件来源 2、安装套件 3、手机安装迅雷 二、qBittorrent套件 1、添加套件来源 2、改手工安装 3、更新后的问题 4、最后放弃DSM6 (1)上传文件手工安装 (2)添加套件来源 5、解决登陆报错 6、添加tracker 7、修改下载默认位置 8、手机…

c++之说_15|成员函数的const尾缀修饰 ( const const)

我记得我刚接触c的时候 遇到成员函数 右边尾部 写了个const 我当时就很蒙 不过慢慢的也从大佬口中获得一二经验了 class kj{public:void get(){printf("无修饰\n");}void get()const{printf("const 修饰\n");}}; 大概就是这个样子 当时我抓耳挠腮的看…

【yolov8语义分割】跑通:下载yolov8+预测图片+预测视频

1、下载yolov8到autodl上 git clone https://github.com/ultralytics/ultralytics 下载到Yolov8文件夹下面 另外&#xff1a;现在yolov8支持像包一样导入&#xff0c;pip install就可以 2、yolov8 语义分割文档 看官方文档&#xff1a;主页 -Ultralytics YOLO 文档 还能切…

图扑助力铝型材挤压:数字孪生引领智慧管理

通过图扑数字孪生技术&#xff0c;为铝型材挤压车间提供实时监控和优化管理方案。高精度三维建模和数据可视化提升了生产效率和管理透明度&#xff0c;推动智能制造和资源优化配置。

leetcode 二分查找·系统掌握 寻找旋转排序数组中的最小值II

题目&#xff1a; 题解&#xff1a; 本题比普通的寻找旋转排序数组中的最小值多了一个数组中的元素可以重复这一点。 这会时原来的思路出现一个漏洞&#xff08;大家感兴趣可以看看我做普通版寻找旋转排序数组最小值的思路&#xff09;&#xff0c;就是旋转后的数组中的第二个…

cas客户端流程详解(源码解析)--单点登录

博主之前一直使用了cas客户端进行用户的单点登录操作&#xff0c;决定进行源码分析来看cas的整个流程&#xff0c;以便以后出现了问题还不知道是什么原因导致的 cas主要的形式就是通过过滤器的形式来实现的&#xff0c;来&#xff0c;贴上示例配置&#xff1a; 1 <list…

Spring-bean

Spring 网站&#xff1a;spring.io 两个方面&#xff1a; 简化开发&#xff1a; IoCAOP 框架整合&#xff1a; MyBatis SpringFrameWork系统架构&#xff08;上层依赖下层&#xff09; 解决问题&#xff08;代码耦合度高——模块与模块之间的依赖程度&#xff09; 目标&am…

Pikachu靶场--越权漏洞

参考借鉴 pikachu之越权漏洞_pikachu越权漏洞-CSDN博客 水平越权 需要输入username和password进行登录 查看提示&#xff0c;获取username和password 输入其中一组账号信息进行登录 可以查看到个人信息 在URL中更改username的值-->回车 成功越权&#xff0c;登录到其他账号…

【文献及模型、制图分享】1985-2015年美国坦帕湾流域土地开发利用强度时空变化分析

公众号新功能 目前公众号新增以下等功能 1、处理GIS出图、Python制图、区位图、土地利用现状图、土地利用动态度和重心迁移图等等 2、核密度分析、网络od分析、地形分析、空间分析等等 3、地理加权回归、地理探测器、生态环境质量指数、地理加权回归模型影响因素分析、计算…