TelegramPWA/src/util/progressiveLoader.ts
2025-01-21 18:29:39 +01:00

78 lines
2.0 KiB
TypeScript

import { ApiMediaFormat } from '../api/types';
import { callApi } from '../api/gramjs';
const MB = 1024 * 1024;
const DEFAULT_PART_SIZE = 0.25 * MB;
const MAX_END_TO_CACHE = 5 * MB - 1; // We only cache the first 2 MB of each file
const bufferCache = new Map<string, ArrayBuffer>();
const sizeCache = new Map<string, number>();
const pendingRequests = new Map<string, Promise<{ arrayBuffer?: ArrayBuffer; fullSize?: number } | undefined>>();
export async function* makeProgressiveLoader(
url: string,
start = 0,
chunkSize = DEFAULT_PART_SIZE,
): AsyncGenerator<ArrayBuffer, void, undefined> {
const match = url.match(/fileSize=(\d+)/);
let fileSize;
if (match) {
fileSize = match && Number(match[1]);
} else {
fileSize = sizeCache.get(url);
}
while (true) {
if (fileSize && start >= fileSize) return;
let end = start + chunkSize - 1;
if (fileSize && end > fileSize) {
end = fileSize - 1;
}
// Check if we have the chunk in memory
const cacheKey = `${url}:${start}-${end}`;
let arrayBuffer = bufferCache.get(cacheKey);
if (!arrayBuffer) {
let request = pendingRequests.get(cacheKey);
if (!request) {
request = callApi('downloadMedia', {
mediaFormat: ApiMediaFormat.Progressive,
url,
start,
end,
});
pendingRequests.set(cacheKey, request);
}
const result = await request.finally(() => {
pendingRequests.delete(cacheKey);
});
if (!result?.arrayBuffer) return;
// If fileSize is not yet defined, retrieve it from the first chunk's response
if (result.fullSize && !fileSize) {
fileSize = result.fullSize;
sizeCache.set(url, result.fullSize);
}
// Store the chunk in memory
arrayBuffer = result.arrayBuffer;
// Cache the first 2 MB of each file
if (end <= MAX_END_TO_CACHE) {
bufferCache.set(cacheKey, result.arrayBuffer);
}
}
// Yield the chunk data
yield arrayBuffer;
start = end + 1;
}
}