omnimatrix-video-player.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. import { FFmpeg } from '@ffmpeg/ffmpeg';
  2. import { fetchFile, toBlobURL } from '@ffmpeg/util';
  3. import { showSaveFilePicker } from 'native-file-system-adapter';
  4. import workerUrl from '../../../../node_modules/@ffmpeg/ffmpeg/dist/esm/worker.js?worker&url';
  5. const ffmpeg = new FFmpeg();
  6. (async () => {
  7. await ffmpeg.load({
  8. coreURL: await toBlobURL(
  9. new URL('./core/package/pkg/esm/ffmpeg-core.js', import.meta.url),
  10. 'text/javascript'
  11. ),
  12. wasmURL: await toBlobURL(
  13. new URL('./core/package/pkg/esm/ffmpeg-core.wasm', import.meta.url),
  14. 'application/wasm'
  15. ),
  16. classWorkerURL: new URL(workerUrl, import.meta.url).toString(),
  17. });
  18. })();
  19. function formatDateTime(date) {
  20. function padZero(num) {
  21. return num < 10 ? '0' + num : num;
  22. }
  23. let year = date.getFullYear();
  24. let month = padZero(date.getMonth() + 1);
  25. let day = padZero(date.getDate());
  26. let hours = padZero(date.getHours());
  27. let minutes = padZero(date.getMinutes());
  28. let seconds = padZero(date.getSeconds());
  29. let Milliseconds = date.getMilliseconds();
  30. return `${year}${month}${day}${hours}${minutes}${seconds}${Milliseconds}`;
  31. }
  32. /**
  33. *
  34. * @param {Blob} blob
  35. * @param {String} fileName - 文件名
  36. */
  37. async function DownloadStreamSaver(blob, fileName, type) {
  38. const opts = {
  39. suggestedName: fileName,
  40. types: [{ 'image/png': ['png'] }],
  41. };
  42. if (window.parent != window) {
  43. window.parent.postMessage(
  44. { type: 'native-file-system-adapter', imgOrVideo: type, opts, blob },
  45. '*'
  46. );
  47. } else {
  48. if (type === 'img') {
  49. // 创建一个新的Image对象
  50. const img = new Image();
  51. img.src = URL.createObjectURL(blob);
  52. // 等待图片加载完成
  53. img.onload = () => {
  54. // 创建Canvas元素
  55. const canvas = document.createElement('canvas');
  56. const ctx = canvas.getContext('2d');
  57. // 设置Canvas大小
  58. canvas.width = img.width;
  59. canvas.height = img.height;
  60. if (ctx) {
  61. ctx.translate(img.width / 2, img.height / 2);
  62. ctx.rotate(Math.PI);
  63. ctx.translate(-img.width / 2, -img.height / 2);
  64. ctx.drawImage(img, 0, 0);
  65. }
  66. // 将旋转后的Canvas转换回Blob
  67. canvas.toBlob(async (rotatedBlob) => {
  68. const handle = await showSaveFilePicker(opts);
  69. const ws = await handle.createWritable();
  70. ws.write(rotatedBlob);
  71. ws.close();
  72. });
  73. };
  74. } else {
  75. const handle = await showSaveFilePicker(opts);
  76. const ws = await handle.createWritable();
  77. ws.write(blob);
  78. ws.close();
  79. }
  80. }
  81. }
  82. /**
  83. *
  84. * @param {String} url - 图片请求地址
  85. * @param {String} fileName - 文件名
  86. */
  87. async function UrlStreamSaverDownload(url, fileName) {
  88. const opts = {
  89. suggestedName: fileName,
  90. types: [{ 'image/png': ['png'] }],
  91. };
  92. const handle = await showSaveFilePicker(opts);
  93. const ws = await handle.createWritable();
  94. fetch(url)
  95. .then((response) => {
  96. if (!response.ok) {
  97. throw new Error('Network response was not ok ' + response.statusText);
  98. }
  99. return response.blob();
  100. })
  101. .then((blob) => {
  102. ws.write(blob);
  103. ws.close();
  104. })
  105. .catch((error) => {
  106. console.error(
  107. 'There has been a problem with your fetch operation:',
  108. error
  109. );
  110. });
  111. }
  112. /**
  113. *
  114. * @param {String} url - wss连接地址 wss://origin/VideoShow/Common?UUID=uuid&DeviceID=deviceID&Token=token
  115. * @param {String} className - 选择器 .video-class | #video-class ...
  116. * @param {String} device - 设备ID
  117. * @param {Function} callback - 画面渲染完成时的回调
  118. * @returns {Object} 包含两个Web Worker和一个关闭函数的对象
  119. */
  120. function useWorker(
  121. url,
  122. className,
  123. device,
  124. callback = () => {},
  125. cropFullInfo = ''
  126. ) {
  127. let canvas = document.querySelector(className);
  128. canvas = canvas.transferControlToOffscreen();
  129. const worker = new Worker(new URL('./worker.js', import.meta.url), {
  130. type: 'module',
  131. });
  132. worker.addEventListener('message', (msg) => {
  133. if (msg.data.type === 'img') {
  134. // saveAs(msg.data.img,`${formatDateTime(new Date())}.png`)
  135. DownloadStreamSaver(
  136. msg.data.img,
  137. `${formatDateTime(new Date())}.png`,
  138. msg.data.type
  139. );
  140. } else {
  141. callback();
  142. }
  143. });
  144. let DataType = 'Start';
  145. worker.postMessage(
  146. { DataType, canvas, url, device, cropFullInfo, className },
  147. [canvas]
  148. );
  149. const WebSocketWork = new Worker(
  150. new URL('./GetVideoStreaming.js', import.meta.url).href,
  151. {
  152. type: 'module',
  153. }
  154. );
  155. WebSocketWork.postMessage({ url });
  156. WebSocketWork.addEventListener('message', async (message) => {
  157. if (message.data.DataType === 'Track') {
  158. let DataType = 'Track';
  159. let codec = message.data.track.codec.startsWith('vp08')
  160. ? 'vp8'
  161. : message.data.track.codec;
  162. let codedHeight = message.data.track.video.height;
  163. let codedWidth = message.data.track.video.width;
  164. let description = message.data.tkinfo;
  165. worker.postMessage({
  166. DataType,
  167. codec,
  168. codedHeight,
  169. codedWidth,
  170. description,
  171. });
  172. }
  173. if (message.data.DataType === 'Samples') {
  174. let DataType = 'Samples';
  175. let type = message.data.sample.is_sync ? 'key' : 'delta';
  176. let timestamp =
  177. (1e6 * message.data.sample.cts) / message.data.sample.timescale;
  178. let duration =
  179. (1e6 * message.data.sample.duration) / message.data.sample.timescale;
  180. let data = message.data.sample.data;
  181. let showData = message.data.showData;
  182. worker.postMessage({
  183. DataType,
  184. type,
  185. timestamp,
  186. duration,
  187. data,
  188. showData,
  189. });
  190. }
  191. if (message.data.DataType === 'lx') {
  192. ffmpeg.on('log', ({ message: msg }) => {
  193. console.log(msg);
  194. });
  195. await ffmpeg.writeFile('test.mp4', await fetchFile(message.data.Data));
  196. ffmpeg.exec([
  197. '-i',
  198. 'test.mp4',
  199. '-c',
  200. 'copy',
  201. '-metadata:s:v',
  202. 'rotate=90',
  203. 'out.mp4',
  204. ]);
  205. const data = await ffmpeg.readFile('out.mp4');
  206. ffmpeg.deleteFile('test.mp4');
  207. DownloadStreamSaver(
  208. new Blob([data.buffer], { type: 'video/mp4' }),
  209. `${formatDateTime(new Date())}.mp4`,
  210. message.data.DataType
  211. );
  212. ffmpeg.deleteFile('out.mp4');
  213. }
  214. });
  215. return {
  216. worker,
  217. WebSocketWork,
  218. close: () => {
  219. WebSocketWork.terminate();
  220. worker.terminate();
  221. },
  222. };
  223. }
  224. window['OmnimatrixVideoPayer'] = useWorker;
  225. export { UrlStreamSaverDownload };
  226. export default useWorker;