| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| 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>;
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| export async function hashBlob(blob: Blob): Promise<string> {
|
| const buffer = await blob.arrayBuffer();
|
|
|
|
|
|
|
| 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('');
|
| }
|
|
|
| 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;
|
|
|
|
|
|
|
|
|
|
|
| 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;
|
| }
|
|
|