import { FFmpeg } from '@ffmpeg/ffmpeg'; import { fetchFile, toBlobURL } from '@ffmpeg/util'; import { showSaveFilePicker } from 'native-file-system-adapter'; import workerUrl from '../../../../node_modules/@ffmpeg/ffmpeg/dist/esm/worker.js?worker&url'; const ffmpeg = new FFmpeg(); (async () => { await ffmpeg.load({ coreURL: await toBlobURL( new URL('./core/package/pkg/esm/ffmpeg-core.js', import.meta.url), 'text/javascript' ), wasmURL: await toBlobURL( new URL('./core/package/pkg/esm/ffmpeg-core.wasm', import.meta.url), 'application/wasm' ), classWorkerURL: new URL(workerUrl, import.meta.url).toString(), }); })(); function formatDateTime(date) { function padZero(num) { return num < 10 ? '0' + num : num; } let year = date.getFullYear(); let month = padZero(date.getMonth() + 1); let day = padZero(date.getDate()); let hours = padZero(date.getHours()); let minutes = padZero(date.getMinutes()); let seconds = padZero(date.getSeconds()); let Milliseconds = date.getMilliseconds(); return `${year}${month}${day}${hours}${minutes}${seconds}${Milliseconds}`; } /** * * @param {Blob} blob * @param {String} fileName - 文件名 */ async function DownloadStreamSaver(blob, fileName, type) { const opts = { suggestedName: fileName, types: [{ 'image/png': ['png'] }], }; if (window.parent != window) { window.parent.postMessage( { type: 'native-file-system-adapter', imgOrVideo: type, opts, blob }, '*' ); } else { if (type === 'img') { // 创建一个新的Image对象 const img = new Image(); img.src = URL.createObjectURL(blob); // 等待图片加载完成 img.onload = () => { // 创建Canvas元素 const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); // 设置Canvas大小 canvas.width = img.width; canvas.height = img.height; if (ctx) { ctx.translate(img.width / 2, img.height / 2); ctx.rotate(Math.PI); ctx.translate(-img.width / 2, -img.height / 2); ctx.drawImage(img, 0, 0); } // 将旋转后的Canvas转换回Blob canvas.toBlob(async (rotatedBlob) => { const handle = await showSaveFilePicker(opts); const ws = await handle.createWritable(); ws.write(rotatedBlob); ws.close(); }); }; } else { const handle = await showSaveFilePicker(opts); const ws = await handle.createWritable(); ws.write(blob); ws.close(); } } } /** * * @param {String} url - 图片请求地址 * @param {String} fileName - 文件名 */ async function UrlStreamSaverDownload(url, fileName) { const opts = { suggestedName: fileName, types: [{ 'image/png': ['png'] }], }; const handle = await showSaveFilePicker(opts); const ws = await handle.createWritable(); fetch(url) .then((response) => { if (!response.ok) { throw new Error('Network response was not ok ' + response.statusText); } return response.blob(); }) .then((blob) => { ws.write(blob); ws.close(); }) .catch((error) => { console.error( 'There has been a problem with your fetch operation:', error ); }); } /** * * @param {String} url - wss连接地址 wss://origin/VideoShow/Common?UUID=uuid&DeviceID=deviceID&Token=token * @param {String} className - 选择器 .video-class | #video-class ... * @param {String} device - 设备ID * @param {Function} callback - 画面渲染完成时的回调 * @returns {Object} 包含两个Web Worker和一个关闭函数的对象 */ function useWorker( url, className, device, callback = () => {}, cropFullInfo = '' ) { let canvas = document.querySelector(className); canvas = canvas.transferControlToOffscreen(); const worker = new Worker(new URL('./worker.js', import.meta.url), { type: 'module', }); worker.addEventListener('message', (msg) => { if (msg.data.type === 'img') { // saveAs(msg.data.img,`${formatDateTime(new Date())}.png`) DownloadStreamSaver( msg.data.img, `${formatDateTime(new Date())}.png`, msg.data.type ); } else { callback(); } }); let DataType = 'Start'; worker.postMessage( { DataType, canvas, url, device, cropFullInfo, className }, [canvas] ); const WebSocketWork = new Worker( new URL('./GetVideoStreaming.js', import.meta.url).href, { type: 'module', } ); WebSocketWork.postMessage({ url }); WebSocketWork.addEventListener('message', async (message) => { if (message.data.DataType === 'Track') { let DataType = 'Track'; let codec = message.data.track.codec.startsWith('vp08') ? 'vp8' : message.data.track.codec; let codedHeight = message.data.track.video.height; let codedWidth = message.data.track.video.width; let description = message.data.tkinfo; worker.postMessage({ DataType, codec, codedHeight, codedWidth, description, }); } if (message.data.DataType === 'Samples') { let DataType = 'Samples'; let type = message.data.sample.is_sync ? 'key' : 'delta'; let timestamp = (1e6 * message.data.sample.cts) / message.data.sample.timescale; let duration = (1e6 * message.data.sample.duration) / message.data.sample.timescale; let data = message.data.sample.data; let showData = message.data.showData; worker.postMessage({ DataType, type, timestamp, duration, data, showData, }); } if (message.data.DataType === 'lx') { ffmpeg.on('log', ({ message: msg }) => { console.log(msg); }); await ffmpeg.writeFile('test.mp4', await fetchFile(message.data.Data)); ffmpeg.exec([ '-i', 'test.mp4', '-c', 'copy', '-metadata:s:v', 'rotate=90', 'out.mp4', ]); const data = await ffmpeg.readFile('out.mp4'); ffmpeg.deleteFile('test.mp4'); DownloadStreamSaver( new Blob([data.buffer], { type: 'video/mp4' }), `${formatDateTime(new Date())}.mp4`, message.data.DataType ); ffmpeg.deleteFile('out.mp4'); } }); return { worker, WebSocketWork, close: () => { WebSocketWork.terminate(); worker.terminate(); }, }; } window['OmnimatrixVideoPayer'] = useWorker; export { UrlStreamSaverDownload }; export default useWorker;