@jxstjh/jhvideo
Version:
HTML5 jhvideo base on MPEG2-TS Stream Player
201 lines (184 loc) • 7.59 kB
text/typescript
import {EventEmitter} from 'events'
// 兼容性
const requestFileSystem = (window as any).requestFileSystem || (window as any).webkitRequestFileSystem
const bytesUint1G = Math.pow(1024, 3)
const storageSizeDefault = 2 * bytesUint1G
const recordBasePath = 'jsp'
class FileStorage {
private _bWaitMp4Index:boolean
_fileEntry = null
_fileWriter = null
_stashBuffer = null
_stashUsed = 0
_bStarted = false // 标记当前是否在录制状态,用于决定是否缓存塞入的数据,和判断是否结束录制
_writedBytes = 0
_bWaitingNewData = false // 上一轮写入结束后,_stashBuffer可能没有新的数据,标记等待新数据塞入后再进行写入
_stashSize = 1920 * 1080 * 30 * 10 / 8 // 缓存大小,默认值是以30帧1080P大小计算的
_transFlag = false
emitter: any
constructor( ) {
this.emitter = new EventEmitter()
}
on(event:any, listener:any) {
this.emitter.addListener(event, listener);
}
off(event:any, listener:any) {
this.emitter.removeListener(event, listener);
}
static queryUsageAndQuota() {
return new Promise((resolve) => {
const queryUsageAndQuotaSucCB = (usedBytes, grantedBytes) => {
resolve({ usedBytes, grantedBytes })
}
(navigator as any).webkitTemporaryStorage.queryUsageAndQuota(queryUsageAndQuotaSucCB, errCallback)
})
}
/**
* @param {String} fileName
* @param {JSON} extra { head, fileFormat, writeHead, ignoreAudio } fileFormat: 2-ps, 5-pm4, 100-mp3
* @returns
*/
async startRecord( fileName, extra:{ head:string,fileFormat:number}) {
this._bStarted = true
return new Promise(async (resolve, reject) => {
const fs:any = await Singleton.getFileSystem()
let getFileSucCb = (fileEntry) => {
this._fileEntry = fileEntry
fileEntry.createWriter(createWriterSucCb, errCallback)
}
let createWriterSucCb = (fileWriter) => {
this._fileWriter = fileWriter
fileWriter.onwriteend = this._onWriteEnd.bind(this)
// 检查缓存中是否有数据,无则标记等待,有则开始写入
if (this._stashUsed === 0) {
this._bWaitingNewData = true
} else {
this._write()
}
resolve(true)
}
let ext = extra.fileFormat === 100 ? 'mp3' : 'mp4'
fs.root.getFile(`${recordBasePath}/${fileName}.${ext}`, { create: true }, getFileSucCb, errCallback)
})
}
stopRecord() {
return new Promise((resolve, reject) => {
if (!this._fileEntry || !this._bStarted){
return resolve(true)
}
this.emitter.on('lastWriteEnd', () => {
downloadFile(this._fileEntry.toURL(), this._fileEntry.name)
resolve(true)
})
// 有时候在等新数据写入的间隙,无法触发lastWriteEnd
if (this._bWaitingNewData && !this._bWaitMp4Index) {
// Log.D('lastWriteEnd')
this.emitter.emit('lastWriteEnd')
}
})
}
async inputData(chunk) {
if (!this._bStarted) return
if (!this._transFlag) {
this._stash(chunk)
}
}
_write() {
if (!this._stashBuffer) return
let stashData = this._stashBuffer.slice(0, this._stashUsed)
let used = this._fileWriter.length
// 判断fileSystem空间是否足够写入本次数据
// let { usedBytes, grantedBytes } = await queryUsageAndQuota(),
// spareBytes = grantedBytes - usedBytes
// Log.D('write', used, stashData, spareBytes)
this._fileWriter.seek(used)
this._fileWriter.write(new Blob([stashData]))
this._writedBytes += stashData.byteLength
this._stashUsed = 0
}
async _onWriteEnd(evt) {
if (this._stashUsed > 0) {
// 从缓存中拿数据写入文件
this._write()
} else if (this._stashUsed === 0 && this._bStarted) {
// 等待缓存中有新数据塞入
this._bWaitingNewData = true
} else if (this._stashUsed === 0 && this._bWaitMp4Index) {
// mp4录制,已结束,但是未收到mp4索引
this._bWaitingNewData = true
} else {
// 结束,保存文件
// Log.D('lastWriteEnd')
// this.eventsCenter.emit('lastWriteEnd')
}
}
_stash(chunk) {
if (!this._stashBuffer) {
this._stashBuffer = new ArrayBuffer(this._stashSize)
}
let stashArr = new Uint8Array(this._stashBuffer, 0, this._stashSize)
stashArr.set(new Uint8Array(chunk, 0), this._stashUsed)
this._stashUsed += chunk.byteLength
// 检查是否在等新数据塞入
if (this._bWaitingNewData) {
this._bWaitingNewData = false
this._write()
}
}
destroy(){
this.emitter.removeAllListeners()
this.emitter = null;
}
}
export default FileStorage
// 文件系统-单例
const Singleton = (function () {
let fileSystem, dirEntry
return {
getFileSystem: function () {
return new Promise(async (resolve, reject) => {
if (fileSystem) return resolve(fileSystem)
let { usedBytes, grantedBytes }:any = await FileStorage.queryUsageAndQuota(),
spareBytes = grantedBytes - usedBytes
if (spareBytes < storageSizeDefault) {
return reject()
}
let requestFileSystemSucCB = (fs) => {
// 创建目录,用于清理缓存
fs.root.getDirectory(recordBasePath, { create: true }, getDirectorySucCB, errCallback)
fileSystem = fs
}
let getDirectorySucCB = (de) => {
dirEntry = de
// 清理之前的录像
de.createReader()
.readEntries(entries => {
if (entries.length === 0) {
// return Log.D('Directory /jsp clean.')
}
// Log.D('Remain fileEntries', entries)
entries.forEach(entry => {
entry.remove(() => {
// Log.D(entry.name, 'removed.')
})
})
})
// Log.D('FileSystem "jsp" directory created', fileSystem, dirEntry)
resolve(fileSystem)
}
requestFileSystem((Window as any).TEMPORARY, storageSizeDefault, requestFileSystemSucCB, errCallback)
})
}
}
})()
function errCallback(err) {
console.log(err)
}
function downloadFile(href, fileName) {
let mouseEvt = document.createEvent('MouseEvents')
mouseEvt.initEvent('click', false, false)
let aEl = document.createElement('a')
aEl.href = href
aEl.download = fileName
aEl.dispatchEvent(mouseEvt)
}