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");
|
throw new Error("controller aborted");
|
||||||
}
|
}
|
||||||
|
|
||||||
const chunk = await request(streamInfo.url, {
|
let retries = 3;
|
||||||
headers: {
|
let chunk;
|
||||||
...getHeaders('youtube'),
|
|
||||||
Range: `bytes=${read}-${read + CHUNK_SIZE}`
|
|
||||||
},
|
|
||||||
dispatcher: streamInfo.dispatcher,
|
|
||||||
signal: streamInfo.controller.signal,
|
|
||||||
maxRedirections: 4
|
|
||||||
});
|
|
||||||
|
|
||||||
if (chunk.statusCode === 403 && chunksSinceTransplant >= 3 && streamInfo.transplant) {
|
// Retry mechanism for failed chunks
|
||||||
chunksSinceTransplant = 0;
|
while (retries > 0) {
|
||||||
try {
|
try {
|
||||||
await streamInfo.transplant(streamInfo.dispatcher);
|
chunk = await request(streamInfo.url, {
|
||||||
continue;
|
headers: {
|
||||||
} catch {}
|
...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++;
|
chunksSinceTransplant++;
|
||||||
@ -36,8 +59,9 @@ async function* readChunks(streamInfo, size) {
|
|||||||
const expected = min(CHUNK_SIZE, size - read);
|
const expected = min(CHUNK_SIZE, size - read);
|
||||||
const received = BigInt(chunk.headers['content-length']);
|
const received = BigInt(chunk.headers['content-length']);
|
||||||
|
|
||||||
if (received < expected / 2n) {
|
// Validate we received some data
|
||||||
closeRequest(streamInfo.controller);
|
if (received === 0n && expected > 0n) {
|
||||||
|
throw new Error("Received empty chunk when data was expected");
|
||||||
}
|
}
|
||||||
|
|
||||||
for await (const data of chunk.body) {
|
for await (const data of chunk.body) {
|
||||||
@ -50,7 +74,13 @@ async function* readChunks(streamInfo, size) {
|
|||||||
|
|
||||||
async function handleYoutubeStream(streamInfo, res) {
|
async function handleYoutubeStream(streamInfo, res) {
|
||||||
const { signal } = streamInfo.controller;
|
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 {
|
try {
|
||||||
let req, attempts = 3;
|
let req, attempts = 3;
|
||||||
@ -78,6 +108,21 @@ async function handleYoutubeStream(streamInfo, res) {
|
|||||||
return cleanup();
|
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 generator = readChunks(streamInfo, size);
|
||||||
|
|
||||||
const abortGenerator = () => {
|
const abortGenerator = () => {
|
||||||
@ -89,13 +134,17 @@ async function handleYoutubeStream(streamInfo, res) {
|
|||||||
|
|
||||||
const stream = Readable.from(generator);
|
const stream = Readable.from(generator);
|
||||||
|
|
||||||
for (const headerName of ['content-type', 'content-length']) {
|
// Add error handling to prevent 0-byte files
|
||||||
const headerValue = req.headers.get(headerName);
|
stream.on('error', (error) => {
|
||||||
if (headerValue) res.setHeader(headerName, headerValue);
|
console.error('YouTube stream error:', error);
|
||||||
}
|
if (!res.headersSent) {
|
||||||
|
res.status(500).end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
pipe(stream, res, cleanup);
|
pipe(stream, res, cleanup);
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
console.error('YouTube stream handling error:', error);
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,11 +41,43 @@ export function getHeaders(service) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function pipe(from, to, done) {
|
export function pipe(from, to, done) {
|
||||||
from.on('error', done)
|
let finished = false;
|
||||||
.on('close', done);
|
let bytesWritten = 0;
|
||||||
|
|
||||||
to.on('error', done)
|
const cleanup = (error) => {
|
||||||
.on('close', done);
|
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);
|
from.pipe(to);
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,14 @@
|
|||||||
<div class="notification" role="alert">
|
<div class="notification" role="alert">
|
||||||
<div class="notification-content">
|
<div class="notification-content">
|
||||||
<span class="notification-icon">🎉</span>
|
<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"
|
class="notification-close"
|
||||||
aria-label="关闭通知"
|
aria-label="关闭通知"
|
||||||
on:click={closeNotification}
|
on:click={closeNotification}
|
||||||
|
Loading…
Reference in New Issue
Block a user