mirror of
https://github.com/imputnet/cobalt.git
synced 2025-06-28 09:28:29 +00:00
to fix youtube 0 byte error
This commit is contained in:
parent
996a7426f6
commit
7369bc3687
@ -13,22 +13,45 @@ async function* readChunks(streamInfo, size) {
|
||||
throw new Error("controller aborted");
|
||||
}
|
||||
|
||||
const chunk = await request(streamInfo.url, {
|
||||
headers: {
|
||||
...getHeaders('youtube'),
|
||||
Range: `bytes=${read}-${read + CHUNK_SIZE}`
|
||||
},
|
||||
dispatcher: streamInfo.dispatcher,
|
||||
signal: streamInfo.controller.signal,
|
||||
maxRedirections: 4
|
||||
});
|
||||
let retries = 3;
|
||||
let chunk;
|
||||
|
||||
if (chunk.statusCode === 403 && chunksSinceTransplant >= 3 && streamInfo.transplant) {
|
||||
chunksSinceTransplant = 0;
|
||||
// Retry mechanism for failed chunks
|
||||
while (retries > 0) {
|
||||
try {
|
||||
await streamInfo.transplant(streamInfo.dispatcher);
|
||||
continue;
|
||||
} catch {}
|
||||
chunk = await request(streamInfo.url, {
|
||||
headers: {
|
||||
...getHeaders('youtube'),
|
||||
Range: `bytes=${read}-${read + CHUNK_SIZE}`
|
||||
},
|
||||
dispatcher: streamInfo.dispatcher,
|
||||
signal: streamInfo.controller.signal,
|
||||
maxRedirections: 4
|
||||
});
|
||||
|
||||
// Check for valid response before processing
|
||||
if (chunk.statusCode === 206 || chunk.statusCode === 200) {
|
||||
break; // Success, exit retry loop
|
||||
} else if (chunk.statusCode === 403 && chunksSinceTransplant >= 3 && streamInfo.transplant) {
|
||||
chunksSinceTransplant = 0;
|
||||
try {
|
||||
await streamInfo.transplant(streamInfo.dispatcher);
|
||||
continue; // Retry with transplanted connection
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// For other status codes, retry
|
||||
throw new Error(`HTTP ${chunk.statusCode} response`);
|
||||
|
||||
} catch (error) {
|
||||
retries--;
|
||||
if (retries === 0) {
|
||||
throw new Error(`Failed to fetch chunk after 3 attempts: ${error.message}`);
|
||||
}
|
||||
|
||||
// Wait before retry (exponential backoff)
|
||||
await new Promise(resolve => setTimeout(resolve, (4 - retries) * 1000));
|
||||
}
|
||||
}
|
||||
|
||||
chunksSinceTransplant++;
|
||||
@ -36,8 +59,9 @@ async function* readChunks(streamInfo, size) {
|
||||
const expected = min(CHUNK_SIZE, size - read);
|
||||
const received = BigInt(chunk.headers['content-length']);
|
||||
|
||||
if (received < expected / 2n) {
|
||||
closeRequest(streamInfo.controller);
|
||||
// Validate we received some data
|
||||
if (received === 0n && expected > 0n) {
|
||||
throw new Error("Received empty chunk when data was expected");
|
||||
}
|
||||
|
||||
for await (const data of chunk.body) {
|
||||
@ -50,7 +74,13 @@ async function* readChunks(streamInfo, size) {
|
||||
|
||||
async function handleYoutubeStream(streamInfo, res) {
|
||||
const { signal } = streamInfo.controller;
|
||||
const cleanup = () => (res.end(), closeRequest(streamInfo.controller));
|
||||
const cleanup = () => {
|
||||
// Only end response if headers haven't been sent to prevent 0-byte files
|
||||
if (!res.headersSent) {
|
||||
res.end();
|
||||
}
|
||||
closeRequest(streamInfo.controller);
|
||||
};
|
||||
|
||||
try {
|
||||
let req, attempts = 3;
|
||||
@ -78,6 +108,21 @@ async function handleYoutubeStream(streamInfo, res) {
|
||||
return cleanup();
|
||||
}
|
||||
|
||||
// Check if client sent a Range header for partial content request
|
||||
const rangeHeader = streamInfo.headers?.get('range');
|
||||
|
||||
if (rangeHeader && req.headers.get('accept-ranges') === 'bytes') {
|
||||
// Handle range request - delegate to handleGenericStream for proper range handling
|
||||
return await handleGenericStream(streamInfo, res);
|
||||
}
|
||||
|
||||
// Set headers before starting stream to ensure they're sent
|
||||
for (const headerName of ['content-type', 'content-length', 'accept-ranges']) {
|
||||
const headerValue = req.headers.get(headerName);
|
||||
if (headerValue) res.setHeader(headerName, headerValue);
|
||||
}
|
||||
|
||||
// Full file request - use chunked download approach
|
||||
const generator = readChunks(streamInfo, size);
|
||||
|
||||
const abortGenerator = () => {
|
||||
@ -89,13 +134,17 @@ async function handleYoutubeStream(streamInfo, res) {
|
||||
|
||||
const stream = Readable.from(generator);
|
||||
|
||||
for (const headerName of ['content-type', 'content-length']) {
|
||||
const headerValue = req.headers.get(headerName);
|
||||
if (headerValue) res.setHeader(headerName, headerValue);
|
||||
}
|
||||
// Add error handling to prevent 0-byte files
|
||||
stream.on('error', (error) => {
|
||||
console.error('YouTube stream error:', error);
|
||||
if (!res.headersSent) {
|
||||
res.status(500).end();
|
||||
}
|
||||
});
|
||||
|
||||
pipe(stream, res, cleanup);
|
||||
} catch {
|
||||
} catch (error) {
|
||||
console.error('YouTube stream handling error:', error);
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
|
@ -41,11 +41,43 @@ export function getHeaders(service) {
|
||||
}
|
||||
|
||||
export function pipe(from, to, done) {
|
||||
from.on('error', done)
|
||||
.on('close', done);
|
||||
let finished = false;
|
||||
let bytesWritten = 0;
|
||||
|
||||
to.on('error', done)
|
||||
.on('close', done);
|
||||
const cleanup = (error) => {
|
||||
if (finished) return;
|
||||
finished = true;
|
||||
|
||||
// Log if we got 0 bytes to help debug production issues
|
||||
if (bytesWritten === 0 && !error) {
|
||||
console.warn('Stream completed with 0 bytes written - potential issue');
|
||||
}
|
||||
|
||||
done(error);
|
||||
};
|
||||
|
||||
from.on('error', (error) => {
|
||||
console.error('Stream source error:', error);
|
||||
cleanup(error);
|
||||
})
|
||||
.on('close', () => {
|
||||
if (!finished) {
|
||||
cleanup();
|
||||
}
|
||||
})
|
||||
.on('data', (chunk) => {
|
||||
bytesWritten += chunk.length;
|
||||
});
|
||||
|
||||
to.on('error', (error) => {
|
||||
console.error('Stream destination error:', error);
|
||||
cleanup(error);
|
||||
})
|
||||
.on('close', () => {
|
||||
if (!finished) {
|
||||
cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
from.pipe(to);
|
||||
}
|
||||
|
@ -54,7 +54,14 @@
|
||||
<div class="notification" role="alert">
|
||||
<div class="notification-content">
|
||||
<span class="notification-icon">🎉</span>
|
||||
<span class="notification-text">最新功能=>【传输】: 你可以方便的在你的设备之间传输文件和文本。采用端到端加密技术,保护隐私性!</span> <button
|
||||
<span class="notification-text">
|
||||
通知:
|
||||
<br>
|
||||
1. 上线最新功能=>【传输】: 此功能方便在你的设备之间传输文件和文本。采用端到端加密技术,保护隐私性!
|
||||
<br>
|
||||
2. youtube下载遇到了些问题,正在修复中...
|
||||
</span>
|
||||
<button
|
||||
class="notification-close"
|
||||
aria-label="关闭通知"
|
||||
on:click={closeNotification}
|
||||
|
Loading…
Reference in New Issue
Block a user