index.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import { ERROR_RESPONSE_BODY_READER, ERROR_INCOMPLETED_DOWNLOAD, } from "./errors.js";
  2. import { HeaderContentLength } from "./const.js";
  3. const readFromBlobOrFile = (blob) => new Promise((resolve, reject) => {
  4. const fileReader = new FileReader();
  5. fileReader.onload = () => {
  6. const { result } = fileReader;
  7. if (result instanceof ArrayBuffer) {
  8. resolve(new Uint8Array(result));
  9. }
  10. else {
  11. resolve(new Uint8Array());
  12. }
  13. };
  14. fileReader.onerror = (event) => {
  15. reject(Error(`File could not be read! Code=${event?.target?.error?.code || -1}`));
  16. };
  17. fileReader.readAsArrayBuffer(blob);
  18. });
  19. /**
  20. * An util function to fetch data from url string, base64, URL, File or Blob format.
  21. *
  22. * Examples:
  23. * ```ts
  24. * // URL
  25. * await fetchFile("http://localhost:3000/video.mp4");
  26. * // base64
  27. * await fetchFile("data:<type>;base64,wL2dvYWwgbW9yZ...");
  28. * // URL
  29. * await fetchFile(new URL("video.mp4", import.meta.url));
  30. * // File
  31. * fileInput.addEventListener('change', (e) => {
  32. * await fetchFile(e.target.files[0]);
  33. * });
  34. * // Blob
  35. * const blob = new Blob(...);
  36. * await fetchFile(blob);
  37. * ```
  38. */
  39. export const fetchFile = async (file) => {
  40. let data;
  41. if (typeof file === "string") {
  42. /* From base64 format */
  43. if (/data:_data\/([a-zA-Z]*);base64,([^"]*)/.test(file)) {
  44. data = atob(file.split(",")[1])
  45. .split("")
  46. .map((c) => c.charCodeAt(0));
  47. /* From remote server/URL */
  48. }
  49. else {
  50. data = await (await fetch(file)).arrayBuffer();
  51. }
  52. }
  53. else if (file instanceof URL) {
  54. data = await (await fetch(file)).arrayBuffer();
  55. }
  56. else if (file instanceof File || file instanceof Blob) {
  57. data = await readFromBlobOrFile(file);
  58. }
  59. else {
  60. return new Uint8Array();
  61. }
  62. return new Uint8Array(data);
  63. };
  64. /**
  65. * importScript dynamically import a script, useful when you
  66. * want to use different versions of ffmpeg.wasm based on environment.
  67. *
  68. * Example:
  69. *
  70. * ```ts
  71. * await importScript("http://localhost:3000/ffmpeg.js");
  72. * ```
  73. */
  74. export const importScript = async (url) => new Promise((resolve) => {
  75. const script = document.createElement("script");
  76. const eventHandler = () => {
  77. script.removeEventListener("load", eventHandler);
  78. resolve();
  79. };
  80. script.src = url;
  81. script.type = "text/javascript";
  82. script.addEventListener("load", eventHandler);
  83. document.getElementsByTagName("head")[0].appendChild(script);
  84. });
  85. /**
  86. * Download content of a URL with progress.
  87. *
  88. * Progress only works when Content-Length is provided by the server.
  89. *
  90. */
  91. export const downloadWithProgress = async (url, cb) => {
  92. const resp = await fetch(url);
  93. let buf;
  94. try {
  95. // Set total to -1 to indicate that there is not Content-Type Header.
  96. const total = parseInt(resp.headers.get(HeaderContentLength) || "-1");
  97. const reader = resp.body?.getReader();
  98. if (!reader)
  99. throw ERROR_RESPONSE_BODY_READER;
  100. const chunks = [];
  101. let received = 0;
  102. for (;;) {
  103. const { done, value } = await reader.read();
  104. const delta = value ? value.length : 0;
  105. if (done) {
  106. if (total != -1 && total !== received)
  107. throw ERROR_INCOMPLETED_DOWNLOAD;
  108. cb && cb({ url, total, received, delta, done });
  109. break;
  110. }
  111. chunks.push(value);
  112. received += delta;
  113. cb && cb({ url, total, received, delta, done });
  114. }
  115. const data = new Uint8Array(received);
  116. let position = 0;
  117. for (const chunk of chunks) {
  118. data.set(chunk, position);
  119. position += chunk.length;
  120. }
  121. buf = data.buffer;
  122. }
  123. catch (e) {
  124. console.log(`failed to send download progress event: `, e);
  125. // Fetch arrayBuffer directly when it is not possible to get progress.
  126. buf = await resp.arrayBuffer();
  127. cb &&
  128. cb({
  129. url,
  130. total: buf.byteLength,
  131. received: buf.byteLength,
  132. delta: 0,
  133. done: true,
  134. });
  135. }
  136. return buf;
  137. };
  138. /**
  139. * toBlobURL fetches data from an URL and return a blob URL.
  140. *
  141. * Example:
  142. *
  143. * ```ts
  144. * await toBlobURL("http://localhost:3000/ffmpeg.js", "text/javascript");
  145. * ```
  146. */
  147. export const toBlobURL = async (url, mimeType, progress = false, cb) => {
  148. const buf = progress
  149. ? await downloadWithProgress(url, cb)
  150. : await (await fetch(url)).arrayBuffer();
  151. const blob = new Blob([buf], { type: mimeType });
  152. return URL.createObjectURL(blob);
  153. };