mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-26 17:01:20 -08:00
fix(audio): [PKG-AUDIO-05] checkpoint zero-time resume and native buffering evidence
This commit is contained in:
1 parent
0218196df9
commit
d18c545675
25 files changed
+14373
-32
No files matched your search
@@ -0,0 +1,90 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { createHash } from 'node:crypto'
|
||||
import http from 'node:http'
|
||||
|
||||
// Serves real media bytes, withholding the tail until a test observes native starvation.
|
||||
export async function createMediaGate(bytes, contentType, limit, completeRanges = false) {
|
||||
assert(Number.isSafeInteger(limit) && limit > 0 && limit < bytes.length)
|
||||
const requests = []
|
||||
const pending = new Map()
|
||||
let released = false
|
||||
const etag = `"${createHash('sha256').update(bytes).digest('hex')}"`
|
||||
const server = http.createServer((req, res) => {
|
||||
if (!['GET', 'HEAD'].includes(req.method) || !req.url.startsWith('/media.')) {
|
||||
res.writeHead(404).end()
|
||||
return
|
||||
}
|
||||
let start = 0
|
||||
let end = bytes.length - 1
|
||||
if (req.headers.range) {
|
||||
const match = /^bytes=(\d*)-(\d*)$/.exec(req.headers.range)
|
||||
start = match?.[1] ? Number(match[1]) : Math.max(0, bytes.length - Number(match?.[2]))
|
||||
end = match?.[1] && match[2] ? Math.min(bytes.length - 1, Number(match[2])) : bytes.length - 1
|
||||
if (!match || (!match[1] && !match[2]) || !Number.isSafeInteger(start) || start > end || start >= bytes.length) {
|
||||
res.writeHead(416, { 'Content-Range': `bytes */${bytes.length}` }).end()
|
||||
return
|
||||
}
|
||||
res.statusCode = 206
|
||||
if (completeRanges && !released && start < limit)
|
||||
end = Math.min(end, limit - 1)
|
||||
res.setHeader('Content-Range', `bytes ${start}-${end}/${bytes.length}`)
|
||||
}
|
||||
res.setHeader('Content-Type', contentType)
|
||||
res.setHeader('Content-Length', end - start + 1)
|
||||
res.setHeader('Accept-Ranges', 'bytes')
|
||||
res.setHeader('Cache-Control', 'no-store')
|
||||
res.setHeader('ETag', etag)
|
||||
res.setHeader('Access-Control-Allow-Origin', '*')
|
||||
const record = { method: req.method, range: req.headers.range || null, start, end, sent: 0, wasHeld: false, closed: false }
|
||||
requests.push(record)
|
||||
res.on('close', () => {
|
||||
record.closed = true
|
||||
pending.delete(res)
|
||||
})
|
||||
res.on('error', (error) => {
|
||||
record.error = error.message
|
||||
})
|
||||
if (req.method === 'HEAD') {
|
||||
res.end()
|
||||
return
|
||||
}
|
||||
const availableEnd = released ? end + 1 : Math.min(end + 1, limit)
|
||||
const sentEnd = Math.max(start, availableEnd)
|
||||
if (sentEnd > start) {
|
||||
res.write(bytes.subarray(start, sentEnd))
|
||||
record.sent += sentEnd - start
|
||||
}
|
||||
if (sentEnd > end) {
|
||||
res.end()
|
||||
}
|
||||
else {
|
||||
record.wasHeld = true
|
||||
pending.set(res, { offset: sentEnd, end, record })
|
||||
res.flushHeaders()
|
||||
}
|
||||
})
|
||||
await new Promise((resolve, reject) => {
|
||||
server.once('error', reject)
|
||||
server.listen(0, '127.0.0.1', resolve)
|
||||
})
|
||||
const address = server.address()
|
||||
return {
|
||||
url: `http://127.0.0.1:${address.port}/media.${contentType === 'video/mp4' ? 'mp4' : 'm4a'}`,
|
||||
requests,
|
||||
get blocked() { return pending.size },
|
||||
release() {
|
||||
released = true
|
||||
for (const [res, { offset, end, record }] of pending) {
|
||||
if (!res.destroyed) {
|
||||
record.sent += end + 1 - offset
|
||||
res.end(bytes.subarray(offset, end + 1))
|
||||
}
|
||||
}
|
||||
pending.clear()
|
||||
},
|
||||
async close() {
|
||||
server.closeAllConnections()
|
||||
await new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve()))
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user