Spaces:
Paused
Paused
| /** | |
| * Copyright (c) Meta Platforms, Inc. and affiliates. | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| import Logger from '@/common/logger/Logger'; | |
| type Range = { | |
| start: number; | |
| end: number; | |
| }; | |
| type FileStreamPart = { | |
| data: Uint8Array; | |
| range: Range; | |
| contentLength: number; | |
| }; | |
| export type FileStream = AsyncGenerator<FileStreamPart, File | null, null>; | |
| /** | |
| * Asynchronously generates a SHA-256 hash for a Blob object. | |
| * | |
| * DO NOT USE this function casually. Computing the SHA-256 is expensive and can | |
| * take several 100 milliseconds to complete. | |
| * | |
| * @param blob - The Blob object to be hashed. | |
| * @returns A Promise that resolves to a string representing the SHA-256 hash of | |
| * the Blob. | |
| */ | |
| export async function hashBlob(blob: Blob): Promise<string> { | |
| const buffer = await blob.arrayBuffer(); | |
| // Crypto subtle is only availabe in secure contexts. For example, this will | |
| // be the case when running the project locally with http protocol. | |
| // https://developer.mozilla.org/en-US/docs/Web/API/Crypto/subtle | |
| if (crypto.subtle != null) { | |
| const hashBuffer = await crypto.subtle.digest('SHA-256', buffer); | |
| const hashArray = Array.from(new Uint8Array(hashBuffer)); | |
| return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); | |
| } | |
| // If not secure context, return random string | |
| return (Math.random() + 1).toString(36).substring(7); | |
| } | |
| export async function* streamFile(url: string, init?: RequestInit): FileStream { | |
| try { | |
| const response = await fetch(url, init); | |
| let blob: Blob; | |
| // Try to download the file with a stream reader. This has the benefit | |
| // of providing progress during the download. It requires the body and | |
| // Content-Length. As a fallback, it uses the blob function on the | |
| // response object. | |
| const contentLength = response.headers.get('Content-Length'); | |
| if (response.body != null && contentLength != null) { | |
| const totalLength = parseInt(contentLength); | |
| const chunks: Uint8Array[] = []; | |
| let start = 0; | |
| let end = 0; | |
| const reader = response.body.getReader(); | |
| try { | |
| while (true) { | |
| const {done, value} = await reader.read(); | |
| if (done) { | |
| break; | |
| } | |
| start = end; | |
| end += value.length; | |
| yield { | |
| data: value, | |
| range: {start, end}, | |
| contentLength: totalLength, | |
| }; | |
| } | |
| } finally { | |
| reader.releaseLock(); | |
| } | |
| blob = new Blob(chunks); | |
| } else { | |
| blob = await response.blob(); | |
| } | |
| const filename = await hashBlob(blob); | |
| return new File([blob], `${filename}.mp4`); | |
| } catch (error) { | |
| Logger.error('aborting download due to component unmount', error); | |
| } | |
| return null; | |
| } | |