ffmpeg.js 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. const ERROR_IMPORT_FAILURE = 'Failed to import FFmpeg core';
  2. let ffmpeg;
  3. let arrayBufferPool = [];
  4. const load = async ({ coreURL, wasmURL }) => {
  5. const first = !ffmpeg;
  6. try {
  7. // when web worker type is `classic`.
  8. importScripts([coreURL]);
  9. } catch {
  10. // when web worker type is `module`.
  11. const module = await import(/* @vite-ignore */ coreURL);
  12. self.createFFmpegCore = module.default;
  13. if (!self.createFFmpegCore) {
  14. throw ERROR_IMPORT_FAILURE;
  15. }
  16. }
  17. try {
  18. ffmpeg = await self.createFFmpegCore({
  19. // Fix `Overload resolution failed.` when using multi-threaded ffmpeg-core.
  20. // Encoded wasmURL and workerURL in the URL as a hack to fix locateFile issue.
  21. mainScriptUrlOrBlob: `${coreURL}#${btoa(JSON.stringify({ wasmURL }))}`,
  22. });
  23. } catch(e) {
  24. console.error(e);
  25. throw ERROR_IMPORT_FAILURE;
  26. }
  27. return first;
  28. };
  29. const decode = async ({ codec, data }) => {
  30. return ffmpeg.processFrame(codec, data, (_) =>{});
  31. };
  32. const recycle = (array) => {
  33. ffmpeg.recycleFrame(array);
  34. }
  35. const close = () => {
  36. ffmpeg.close();
  37. ffmpeg = null;
  38. arrayBufferPool = [];
  39. }
  40. // message handler is synchronous
  41. self.onmessage = async ({ data: { id, type, data } }) => {
  42. try {
  43. if (type !== "LOAD" && !ffmpeg) {
  44. self.postMessage({
  45. id,
  46. type: "ERROR",
  47. data: "FFmpeg not loaded",
  48. });
  49. } else if (type === "LOAD") {
  50. const ret = await load(data);
  51. self.postMessage({ id, type, data: ret });
  52. } else if (type === "DECODE") {
  53. // accept moved buffer first
  54. if (data.arrayBuffer) {
  55. arrayBufferPool.push(data.arrayBuffer);
  56. if (arrayBufferPool.length > 8) {
  57. arrayBufferPool.shift();
  58. }
  59. }
  60. const ret = await decode(data);
  61. if (ret === 0) {
  62. var buffer = null;
  63. while(arrayBufferPool.length > 0) {
  64. var pop = arrayBufferPool.pop();
  65. if (pop.byteLength === ffmpeg.frameBuffer.data.length) {
  66. buffer = pop;
  67. break;
  68. }
  69. }
  70. if (!buffer) {
  71. buffer = new ArrayBuffer(ffmpeg.frameBuffer.data.length);
  72. console.log("worker create arrayBuffer");
  73. }
  74. let array = new Uint8Array(buffer);
  75. array.set(ffmpeg.frameBuffer.data);
  76. self.postMessage({ id, type, data: {data: {data: buffer, yuvFormat: ffmpeg.frameBuffer.yuvFormat}}}, [buffer]);
  77. recycle(ffmpeg.frameBuffer.data);
  78. } else {
  79. self.postMessage({
  80. id,
  81. type: "ERROR",
  82. data: {},
  83. });
  84. }
  85. } else if (type === "CLOSE") {
  86. close();
  87. self.postMessage({ id, type, data: {} });
  88. } else {
  89. self.postMessage({
  90. id,
  91. type: "ERROR",
  92. data: `Unknown command: ${type}`,
  93. });
  94. }
  95. } catch (e) {
  96. self.postMessage({
  97. id,
  98. type: "ERROR",
  99. data: e.toString(),
  100. });
  101. }
  102. };