代码示例之原生js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>获取文件code</title>
<script src="./assets/mp4box.all.js"></script>
</head>
<body>
<div>
<input type="file" name="" id="uploadFile">
<script>
function readFileCode(fileobj = { mp4boxfile: null, objectToLoad: null }, progressbar) {
var fileSize = fileobj.objectToLoad.size;
var offset = 0;
var self = this; // we need a reference to the current object
var readBlock = null;
var startDate = new Date();
fileobj.mp4boxfile = MP4Box.createFile(false);
fileobj.mp4boxfile.onError = function (e) {
console.log("Failed to parse ISOBMFF data");
};
fileobj.mp4boxfile.onReady = function (info) {
console.log('onReady-info', info)
}
var onparsedbuffer = function (mp4boxfileobj, buffer) {
console.log("Appending buffer with offset " + offset);
buffer.fileStart = offset;
mp4boxfileobj.appendBuffer(buffer);
}
var onBlockRead = function (evt) {
if (evt.target.error == null) {
onparsedbuffer(fileobj.mp4boxfile, evt.target.result); // callback for handling read chunk
offset += evt.target.result.byteLength;
// progressbar.progressbar({ value: Math.ceil(100*offset/fileSize) });
} else {
console.log("Read error: " + evt.target.error);
return;
}
if (offset >= fileSize) {
// progressbar.progressbar({ value: 100 });
console.log("Done reading file (" + fileSize + " bytes) in " + (new Date() - startDate) + " ms");
fileobj.mp4boxfile.flush();
console.log(fileobj.mp4boxfile.getInfo())
return;
}
readBlock(offset, fileobj.objectToLoad);
}
readBlock = function (_offset, _file) {
// 分片大小 每片100M
let chunkSize = 100 * 1024 * 1024; // bytes
var r = new FileReader();
var blob = _file.slice(_offset, chunkSize + _offset);
r.onload = onBlockRead;
r.readAsArrayBuffer(blob);
}
readBlock(offset, fileobj.objectToLoad);
}
document.getElementById('uploadFile').onchange = function (e) {
console.log('fileInfo', e.target.files[0])
readFileCode({objectToLoad: e.target.files[0]})
}
</script>
</div>
</body>
</html>
代码示例之vue
插件安装
npm i mp4box
封装代码
import MP4Box from 'mp4box'
export function checkVideoCode (fileobj = { mp4boxfile: null, objectToLoad: null }) {
return new Promise((resolve, reject) => {
var fileSize = fileobj.objectToLoad.size
var offset = 0
// var self = this // we need a reference to the current object
var readBlock = null
// var startDate = new Date()
fileobj.mp4boxfile = MP4Box.createFile(false)
fileobj.mp4boxfile.onError = function (e) {
console.log('Failed to parse ISOBMFF data')
reject(e)
}
// 监听到(大文件会分片读取文件,实际是读取文件第一片,前100M)就返回
fileobj.mp4boxfile.onReady = function (info) {
// console.log('onReady', info)
resolve(info)
}
var onparsedbuffer = function (mp4boxfileobj, buffer) {
// console.log('Appending buffer with offset ' + offset)
buffer.fileStart = offset
mp4boxfileobj.appendBuffer(buffer)
}
var onBlockRead = function (evt) {
if (evt.target.error == null) {
onparsedbuffer(fileobj.mp4boxfile, evt.target.result) // callback for handling read chunk
offset += evt.target.result.byteLength
// progressbar.progressbar({ value: Math.ceil(100*offset/fileSize) });
} else {
console.log('Read error: ' + evt.target.error)
return
}
if (offset >= fileSize) {
// progressbar.progressbar({ value: 100 });
// console.log('Done reading file (' + fileSize + ' bytes) in ' + (new Date() - startDate) + ' ms')
fileobj.mp4boxfile.flush()
// console.log(fileobj.mp4boxfile.getInfo())
// 等分片读取完毕再返回
// resolve(fileobj.mp4boxfile.getInfo())
return
}
readBlock(offset, fileobj.objectToLoad)
}
readBlock = function (_offset, _file) {
// 分片大小 每片100M
let chunkSize = 100 * 1024 * 1024 // bytes
var r = new FileReader()
var blob = _file.slice(_offset, chunkSize + _offset)
r.onload = onBlockRead
r.readAsArrayBuffer(blob)
}
readBlock(offset, fileobj.objectToLoad)
})
}
应用封装代码
<template>
<div class='mat-video-wrapper' v-loading='loading'>
<!-- 编辑区域 -->
<section class='edit-body'>
<!-- 添加图文 -->
<div v-show="!videoFile" class="add-video">
<div class='add-icon'>添加视频</div>
<input
class="video-uploader"
type="file"
@change="subVideo"
ref="fileInput"
accept="video/mp4">
</div>
<!-- 图文预览区域 -->
<div
class='prev-box'
v-show="videoFile">
<div class='prev-main'
v-loading="loadingVideo"
element-loading-spinner="el-icon-loading"
element-loading-text="上传中"
element-loading-background="rgba(0, 0, 0, 0.8)"
:style="{backgroundImage:`url('${picUrl}')`}">
<video class="video-dom" ref='videoDOM' :src="fileSrc"></video>
<span class='prev-icon' @click='startPreview()'></span>
</div>
<div class='prev-title'>{{videoFile ? videoFile.name || '标题' : '标题'}}</div>
<div class="del" @click="delVideo()">删除</div>
</div>
</section>
<div class='video-tip'>
提示: 视频素材大小控制在{{wechatAccord ? '10M' : '5G'}}以内,支持MP4(H264编码格式)
</div>
<!-- 按钮区域 -->
<section class='edit-btn'>
<el-button size="small" type="primary" @click="saveEdit">保 存</el-button>
<el-button size="small" @click="cancelEdit">取 消</el-button>
</section>
<!-- 视频的预览 -->
<previewVideo
v-show="showVideoPreview"
:videoSrc="fileSrc"
@handleClose="closePreviewVideo()"/>
<!-- 图片上传 -->
<shardUploader ref='upload1' :showSuccMsg='false'/>
</div>
</template>
<script>
import shardUploader from '@/public/shardUploader'
import previewVideo from '@/public/preview/previewVideo'
import { checkVideoCode } from '@/utils/readFileCode'
export default {
name: 'matVideo',
props: {
mode: {
type: String,
default: 'create'
},
mat: {
type: Object,
default () {
return {}
}
},
// 是否按照威微信标准过滤素材列表
wechatAccord: {
type: Boolean,
default: false
}
},
components: {
shardUploader,
previewVideo
},
data () {
return {
loading: false,
videoFile: null,
fileSrc: '', // 视频本地播放地址
loadingVideo: false, // 获取封面的loading
picUrl: null, // 视频封面图
videoDuration: null, // 视频播放时长
videoDOM: null,
showVideoPreview: false
}
},
methods: {
delVideo () {
this.$refs.fileInput.value = ''
this.videoFile = null
this.picUrl = null
this.videoDuration = null
},
subVideo () {
let video = this.$refs.fileInput.files[0]
if (video) {
// 文件格式验证
if (!/^video\//.test(video.type) || !/\.mp4$/.test(video.name)) {
this.showWarning('视频文件只支持mp4格式')
this.$refs.fileInput.value = ''
return
}
// 大小验证, 用于微信消息则限制是10M,否则限制为5G
let maxSize = this.wechatAccord ? 1024 * 1024 * 10 : 1024 * 1024 * 1024 * 5
if (video.size > maxSize) {
this.showWarning(`视频文件大小不能超过${this.wechatAccord ? '10M' : '5G'}`)
this.$refs.fileInput.value = ''
return
}
// 正确的视频后缀会有mime信息
checkVideoCode({objectToLoad: video}).then(res => {
console.log('mime:', res.mime)
let valid = this.getCodecValid(res.mime)
if (!valid) {
this.showWarning('请上传正确的视频编码格式')
} else {
this.loading = true
this.fileSrc = URL.createObjectURL(video)
this.videoFile = video
// this.loadingVideo = true
// 截取视频的第一帧
if (!this.videoDOM) {
this.videoDOM = this.$refs.videoDOM
}
this.videoDOM.addEventListener('loadeddata', this.getVideoFirstImg(video))
}
}).catch(() => {
this.showWarning('获取视频编码失败,请检查视频文件')
})
}
},
// 获取视频第一帧
getVideoFirstImg (video) {
this.videoDOM.removeEventListener('loadeddata', this.getVideoFirstImg)
// 获取第一帧
setTimeout(() => {
// 获取视频时长
this.videoDuration = this.videoDOM.duration
if (!this.videoDuration) {
this.loading = false
return this.showWarning('视频格式或mp4编码格式不支持')
}
let canvas = document.createElement('canvas') // 创建一个画布
canvas.width = this.videoDOM.videoWidth
canvas.height = this.videoDOM.videoHeight
canvas.getContext('2d').drawImage(this.videoDOM, 0, 0, canvas.width, canvas.height) // getContext:设置画布环境;drawImage:画画
let imgDataUrl = canvas.toDataURL('image/png') // 获取图片的url
// console.log('imgDataUrl: ', imgDataUrl)
// 判断生成的base64是否符合格式
if (!/;base64/.test(imgDataUrl)) {
this.showWarning('视频格式或mp4编码格式不支持')
this.loading = false
} else {
// 转换为图片file文件,并上传到服务器
let imgFile = this.dataURLtoFile(imgDataUrl, new Date().getTime())
this.$refs.upload1.uploadFile(imgFile, result => {
this.loadingVideo = false
this.picUrl = result.url
this.loading = false
})
}
}, 1000)
},
startPreview () {
this.showVideoPreview = true
},
closePreviewVideo () {
this.showVideoPreview = false
},
saveEdit () {
// 验证视频的标题不能为空
if (!this.videoFile) {
this.loading = false
return this.$message({ message: '请先上传视频素材', type: 'warning' })
}
if (!this.videoDuration) {
this.loading = false
return this.showWarning('获取视频长度失败, 请检查视频格式')
}
if (this.loading) {
return false
}
// this.loading = true
// 首先上传图片到服务器,获取图片链接
this.$refs.upload1.uploadFile(this.videoFile, result => {
setTimeout(() => {
let formData = new FormData()
formData.append('fileUrl', result.url)
formData.append('fileName', result.name)
formData.append('fileSize', result.size)
formData.append('picUrl', this.picUrl)
formData.append('duration', parseInt(this.videoDuration * 1000))
let appid = sessionStorage.getItem('appid')
let url = `${this.SERVICE_WECHAT}/upgrade/media/${appid}/video/upload`
this.posts(url, formData).then(res => {
if (res.data.code === 200) {
this.showSucc(res.data.message)
let data = res.data.data
let info = {
picUrl: this.picUrl,
mediaName: this.videoFile.name
}
if (data) {
info.id = data
}
this.$emit('success', info)
}
this.loading = false
}).catch(e => {
this.loading = false
this.handleError(e)
})
}, 1000)
})
},
cancelEdit () {
this.$emit('cancel')
},
getCodecValid (str) {
let arr = str.split(';')
// 排除非video格式的
if (!arr[0].includes('video/mp4')) {
return false
}
// 特定的有问题视频
return !!(arr[1].includes('avc1') && !(arr[1].includes('avc1.640029') && arr[1].includes('mp4a.40.2')))
}
},
created () {
// console.log('wechatAccord: ', this.wechatAccord)
}
}
</script>
<style lang="stylus" scoped>
.mat-video-wrapper
min-height calc(100% - 70px)
padding-bottom 70px
position relative
.edit-body
padded_box(border-box,0 10px)
display flex
align-items center
.add-video
width 275px
height 160px
background #f2f2f6
position relative
.add-icon
position absolute
left 0
right 0
top 0
bottom 0
margin auto
width 64px
height: 21px;
font-size: 14px;
color: #5E5E66;
line-height: 21px;
padding-top 24px
text-align center
&:before, &:after
content ''
display block
width 20px
height 2px
background: #ACAEBC;
position absolute
left 0
right 0
top 0
margin auto
&:after
transform rotate(90deg)
.video-uploader
position absolute
z-index 1
left 0
top 0
width 100%
height 100%
opacity 0
cursor pointer
.video-box
width 0
height 0
opacity 0
.prev-box
width 275px
position relative
.del
line-height 18px
font-size 14px
color #4c84ff
cursor pointer
position absolute
right -44px
bottom 0px
.prev-main
position relative
width 100%
height 108px
background-color #F7F7F7;
background-position center
background-repeat no-repeat
background-size cover
cursor pointer
.video-dom
object-fit cover
width 100%
height 100%
position absolute
top 0
left 0
visibility hidden
.prev-icon
position absolute
left 0
right 0
top 0
bottom 0
background url('~assets/img/enterprise/video_ic_play@2x.png') no-repeat center / 30px
z-index 4
.prev-title
height 50px
line-height 50px
border 1px solid #ddd
padding 0 9px
font-size 14px
color #333
no-wrap()
.video-tip
padding 15px 10px
color #666
.edit-btn
position absolute
left 0
right 0
bottom 0
padding 20px 0
text-align center
</style>
在线获取文件编码的地址:https://gpac.github.io/mp4box.js/test/filereader.html