UNPKG

@jxstjh/jhvideo

Version:

HTML5 jhvideo base on MPEG2-TS Stream Player

201 lines (184 loc) 7.59 kB
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) }