视频切片上传(uniapp、nodejs)

一:前言(思路及问题)

1.uniapp中无法使用formdata,与我个人而言,很有影响

2.formdata无法使用,就只能通过uploadFile上传,这个有个formdata的参数,可以把formdata类的数据给到这个参数

3.然后说选择视频文件,选择视频文件uniapp给出了uni.chooseVideo来专门选择手机内的视频文件,这个api是支持app端的,参数之类的就不说了,但是就得到的数据来说,有tempFilePath选取文件的临时路径(app端支持)和temFile选取的文件file(只支持h5),这样一来对于要实现的功能来说,就有很大的问题,因为拿不到文件的file值,那就只能通过得到的临时文件地址去获取文件的file值

4.想通过这个临时地址获取文件的file值有点麻烦,uniapp相关的api但凡是能得到file值的都是只支持h5或小程序的,app端行不通,这样的就通过h5的api来实现了

5.plus.io.resolveLocalFileSystemURL通过URL参数获取目录对象或文件对象,这样就可以得到选择视频文件的file了

6.得到文件file后,说上传问题,uni.uploadFile参数file(仅支持h5)、filePath(要上传文件资源的路径)和name(文件对应的 key , 开发者在服务器端通过这个 key 可以获取到文件二进制内容),其他的参数不说,就这三个参数而言,那能用的就filePath和name,视频文件切片后file数据中path都是原文件file的path值,路径是一样的,上传之后,都是整个的上传,不是切片后的文件

7.这样就iu意味着,如果文件切片后就这样去上传,实际的情况就是文件整个的上传了,没有达到切片上传的效果

8.我不知道有没有的其他的方式可以实现还是我的方法错了,反正我目前这样的方法是行不通的,所以我选择不用uni.uploadFile这个api了,这就又回到h5的api了

9.new一个plus.io.FileReader,然后read.readAsDataURL(file) ,这样是以URL编码格式读取文件数据内容,得到一个base64格式的file数据,将这样的file数据上传给后端后,一翻处理后,理论上效果是能实现了,但是....

10.nodejs接收到base64格式数据后,正常应该是将数据写入到文件,写入格式为binary,然后等所有请求处理后合并文件,最终达成效果

11. 但是实际的情况是,我将前端传来的base64格式数据转为buffer数据然后依次写入文件后,并接到合并请求后合并文件,最终的情况是合并文件打不开。(这个时候我的情况是:前端设定切片大小为2M,视频实际大小是1M,就不用切片了,这样的话nodejs接收base64格式的file数据处理后,最终的文件可以打开,但是文件只要大了,就会切片,最后的文件就打不开了)

12.这种情况,我有想是不是切片或者上传途中丢失了字符,不然怎么会文件小就行,文件大了就不行?但是我一直没有找到文件

13.这个时候我做了点小改变,之前的方法是接收切片的时候,每次接收都将切片数据先转buffer,然后写入文件,合并文件就是创建可写流和可读流来合并追加内容,这个时候我是在接收切片的时候将base64数据写入到txt文件,直接不转buffer,等到合并的时候,读取这几个切片文件得到里面的把base64,拼接这些字符,最后转buffer,写入,然后大功告成

 二:uniapp

1.选取视频文件

// 打开选择文件
await uni.chooseVideo({
					count:1,
					mediaType:['video'],
					sourceType:['album'],
					success: async(res) => {
						plus.io.resolveLocalFileSystemURL(res.tempFilePath,(entry) => {
							entry.file(async (ent) => {
								this.file = ent
                                // uploadVideo是我从其他的js文件导入的方法
								await uplaodVideo(ent)
							})
						})
					}
				})

2.开始切片

function sliceVideo (file,fileMd5) {
	return new Promise((res,rej) => {
		try {
			// 当前已切片数
			let sliceNum = 0;
			// 切片文件数组
			let sliceVideoArr = [];
			console.log('file',file);
            // new一个plus.io.FileReader
			let read = new plus.io.FileReader()
            // read.readAsDataURL(file)以URL编码格式读取文件数据内容
			read.readAsDataURL(file)
            // 文件读取操作完成时的回调函数 得到一个base64格式的数据
			read.onloadend = (e) => {
				// blob = e.target.result.replace(/^data:video\/\w+;base64,/, '');
                // 这里是因为现在得到的字符数据前面有些没有用的东西,切割一下
				let blob = read.result.split(',')[1]
				// 应切片总数
				sliceAllNum = Math.ceil(blob.length / sliceSize)
				let start = 0;
				let end = 0;
				let fileStr; 
                // 这里来一个递归函数,一次次切割,直到最后一次
				const recursition = () => {
					console.log('sliceNum',sliceNum);
					start = sliceNum * sliceSize
					end = Math.min(blob.length, start + sliceSize)
					fileStr = blob.slice(start,end)
					sliceVideoArr.push({index:sliceNum,file:fileStr,md5:fileMd5})
					sliceNum++
					if(sliceNum < sliceAllNum) {
						recursition()
					} else {
						res(sliceVideoArr)
					}
				}
				recursition()
			}
		} catch(err) {
			console.log('try-catch-err',err);
		}
	})
}

3.上传(uniapp全部代码)

import { request,url } from './request.js'
// 用来获取文件的唯一标识 yarn add spark-md5 --save
import sparkMd5 from 'spark-md5'
// 规定每片的大小  byte->kb->mb  限制每片大小为2mb
let sliceSize = 1024 * 1024 * 2;//1024 * 1024 * 2
// 应切片总数
let sliceAllNum = 0;
// 当前上传进度
let nowprogress = 0;
export const uplaodVideo = async(file) => {
	// 获取文件得唯一值
	let spark = new sparkMd5.ArrayBuffer()
	spark.append(file)
	let fileMd5 = spark.end()
	// 开始切片
	let sliceVideoArr = await sliceVideo(file,fileMd5)
	console.log('sliceVideoArr',sliceVideoArr);
	// 上传
	let upload = uploadVideo(sliceVideoArr)
	await Promise.all(upload)
	console.log('promise_all');
	const full = await request({url:`/issue/full_video?md5=${fileMd5}`,method:'get'})
	console.log('full',full);
}
function sliceVideo (file,fileMd5) {
	return new Promise((res,rej) => {
		try {
			// 当前已切片数
			let sliceNum = 0;
			// 切片文件数组
			let sliceVideoArr = [];
			console.log('file',file);
			let read = new plus.io.FileReader()
			read.readAsDataURL(file)
			read.onloadend = (e) => {
				// blob = e.target.result.replace(/^data:video\/\w+;base64,/, '');
				let blob = read.result.split(',')[1]
				// 应切片总数
				sliceAllNum = Math.ceil(blob.length / sliceSize)
				let start = 0;
				let end = 0;
				let fileStr; 
				const recursition = () => {
					console.log('sliceNum',sliceNum);
					start = sliceNum * sliceSize
					end = Math.min(blob.length, start + sliceSize)
					fileStr = blob.slice(start,end)
					sliceVideoArr.push({index:sliceNum,file:fileStr,md5:fileMd5})
					sliceNum++
					if(sliceNum < sliceAllNum) {
						recursition()
					} else {
						res(sliceVideoArr)
					}
				}
				recursition()
			}
		} catch(err) {
			console.log('try-catch-err',err);
		}
	})
}
function uploadVideo(sliceVideoArr) {
	return sliceVideoArr.map((val,ins) => {
		console.log('map-val',val);
		return new Promise((res,rej) => {
			uni.request({
				url:`${url}/issue/slice_video`,
				method:'post',
				header: {
					'Content-Type': 'application/x-www-form-urlencoded'
				},
				data:val,
				success: (req) => {
					res(req)
				},
				fail: (err) => {
					rej(err)
				}
			})
		})
	})
}

三:nodejs

1.准备工作

// 中间件处理
// 因为现在选择的实现方式不是直接上传file,而是base64数据,字符会很长,所以需要针对中间件进行处理,让其可接收的字符长度变得更大
app.use(bodyParse.json({limit:'300mb',extended: true}));

2.接收切片文件

// 接收切片的视频,写入指定文件
router.post('/slice_video',(req,res) => {
    try {
        console.log('req.body',req.body.index);
        let paths = path.join(__dirname,`../sliceVideo/${req.body.index}.txt`)
        // 创建可写流
        const writeStream = fs.createWriteStream(paths);
        // 将base64数据通过流写入文件
        writeStream.write(req.body.file, (err) => {
            if (err) {
                console.error('写入文件错误', err);
            } else {
                console.log('文件写入成功');
                // 关闭可写流
                // writeStream.end();
            }
        });
        res.send({code:200})
    } catch (err) {
        console.log('slice_video_err',err);
    }
})

3.合并文件

// 合并切片
router.get('/full_video',async (req,res) => {
    try {
        // , { encoding: 'binary' }
        let slicePath = path.join(__dirname,'../sliceVideo')
        let fullPath = path.join(__dirname,'../fullVideo/all.mp4')
        fs.readdir(slicePath,async(err,files) => {
            if(err) {
                console.log('fs.readdir--err',err);
                return;
            }
            let arr = files
            arr.sort((a,b) => {
                return a.slice('.')[0]-b.slice('.')[0]
            });
            console.log('arr--------------',arr);
            let str = '';
            // 创建可写流
            for(var i = 0;i < arr.length;i++) {
                console.log('index',i);
                let data = await readFile(path.join(slicePath,arr[i]))
                str+=data
            }
            const binaryData = Buffer.from(str, 'base64')
            const writeStream = fs.createWriteStream(fullPath);
            writeStream.write(binaryData, 'binary', (err) => {
                if (err) {
                    console.error('写入文件错误', err);
                } else {
                    console.log('文件写入成功');
                    // 关闭可写流
                    writeStream.end();
                }
            });
            function readFile(videopath){
                return new Promise((res,rej) => {
                    fs.readFile(videopath,'utf8',(readerr,readdata) => {
                        if(readerr) {
                            console.log('fs.readFile---readerr',readerr);
                            return;
                        }
                        console.log('readdata',readdata);
                        res(readdata)
                    })
                })
            }
        })
    } catch(err) {
        console.log('err----errr',err);
    }
})

至此,这个功能是已经实现了

只是还欠些需要完善的地方,比如在sliceVideo文件中,需要以文件名再建一个文件,不同的切片文件保存到各自的文件夹中

然后在合并中,合并完成后,把这个合并的切片文件和文件夹删除掉

断点续传,每一次上传切片做好记录,这个就以自己的方式做些数据来记录,当前第一次上传被中断后,第二次上传请求发起后,nodejs就查询之前自己留的数据看是已经上传到那了,把查到的数据返给前端,前端就在那个地方继续上传(然后因为我这个方法获取的文件数据一开始是文件的临时路径,我不清楚这个文件的临时路径是否会变?第二次不用api选取直接通过之前的临时文件地址是否能行?这些都不是问题,试一下然后折中处理就ok了)

记录一下,接收数据小视频的其他方法

方法一:multer中间件
const express = require('express')
const router = express.Router()
const path = require('path')
const fs = require('fs');
const multer = require('multer')
let storage = multer.diskStorage({
    destination: function(req, file, cb) {
        // console.log('req',req.body);
        cb(null, 'sliceVideo/'); 
    },
    filename: function(req, file, cb) {
        console.log('multer-req',req.body,req.params,req.query);
        console.log('multer-file',file);
        let filename = file.originalname + '.' + `${req.body.index}` + '.mp4'
        cb(null, filename)
    }
})
// 记得diskStorage用完后,放到multer里面来
let upload = multer({ storage: storage });
// upload.single是针对只上串一个视频文件  upload.array是多个
// 后面的这个video是前端在上传文件时使用formData append命名,如formdata.append('video',file),前端和后端的这个命名要一致
// 到这里前端上传的视频已经是保存到指定文件中了
router.post('/slice_video',upload.single('video'),(req,res) => {
    // 这里现在就做些其他的处理就行
}) 


方法二:formidable(这个就不用像multer一样需要设置video之类的命名了,但是有一个问题,用这个的话,请求数据req就是空的了,需要通过form.parse才能得到req的数据)
router.post('/slice_video',(req,res) => {
    
    console.log('body',req.body);
    let form = new formidable.IncomingForm()
    form.keepExtensions = true
    form.uploadDir = path.join(__dirname,'../videoUpload')
    form.parse(req,(err,fields,files) => {
    console.log('body',req.body);                                  fs.rename(path.join(__dirname,`../videoUpload/${files.videofile[0].newFilename}`),path.join(__dirname,`../videoUpload/${files.videofile[0].newFilename + '.mp4'}`),(err) => {
        console.log('fs-renma',err);
    })
})
}) 

  • 39
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值