Files
modules/media-streaming/hyper-p2p-chunk-scheduler-media/index.js
T
Raven ScottandCursor 26ca05031a
Hyper-P2P Module Tests / unit-all (push) Failing after 1h20m44s
feat(media-streaming,pubsub): expand helper swarm and chunk scheduler APIs
- helper-swarm-coordinator: autoAssign, rebalance, resetUtilization
- chunk-scheduler-media: enqueueBatch, dropBelowPriority, clear
- qos-topic: queueDepths, clearQueues
- Update routing-advanced category README highlights

Co-authored-by: Cursor <[email protected]>
2026-05-21 02:21:32 -04:00

117 lines
3.1 KiB
JavaScript

require('bare-process/global')
const EventEmitter = require('bare-events')
const { CHUNK_TYPES, mediaStats } = require('../../_shared/media-streaming-base.js')
const PROTOCOL = 'chunk-scheduler-media/v1'
const PRIORITY = {
[CHUNK_TYPES.KEYFRAME]: 100,
[CHUNK_TYPES.BASE]: 80,
[CHUNK_TYPES.AUDIO]: 70,
[CHUNK_TYPES.ENHANCEMENT]: 50,
[CHUNK_TYPES.DELTA]: 40
}
class HyperP2PChunkSchedulerMedia extends EventEmitter {
constructor (opts = {}) {
super()
this._queue = []
this._stats = { enqueued: 0, scheduled: 0 }
}
enqueue (chunk, priority = null) {
if (!chunk || chunk.seq == null) throw new Error('chunk with seq required')
const p = priority != null ? priority : (PRIORITY[chunk.type] ?? 30)
const entry = { chunk, priority: p, enqueuedAt: Date.now() }
this._queue.push(entry)
this._stats.enqueued++
this._sort()
this.emit('enqueue', entry)
return entry
}
_sort () {
this._queue.sort((a, b) => b.priority - a.priority || a.chunk.seq - b.chunk.seq)
}
nextChunks (limit = 8) {
const out = this._queue.splice(0, Math.max(0, limit | 0))
this._stats.scheduled += out.length
if (out.length) this.emit('schedule', { count: out.length })
return out.map((e) => e.chunk)
}
prioritizeKeyframes () {
for (const e of this._queue) {
if (e.chunk.type === CHUNK_TYPES.KEYFRAME || e.chunk.keyframe) {
e.priority = PRIORITY[CHUNK_TYPES.KEYFRAME]
}
}
this._sort()
return this._queue.length
}
schedulePlan () {
const plan = { keyframes: 0, audio: 0, enhancement: 0, other: 0 }
for (const e of this._queue) {
if (e.chunk.type === CHUNK_TYPES.KEYFRAME) plan.keyframes++
else if (e.chunk.type === CHUNK_TYPES.AUDIO) plan.audio++
else if (e.chunk.type === CHUNK_TYPES.ENHANCEMENT) plan.enhancement++
else plan.other++
}
return plan
}
pendingCount () { return this._queue.length }
peek (n = 5) {
return this._queue.slice(0, Math.max(0, n | 0)).map((e) => e.chunk)
}
dequeueSeq (seq) {
const idx = this._queue.findIndex((e) => e.chunk.seq === seq)
if (idx < 0) return null
const [entry] = this._queue.splice(idx, 1)
this._stats.scheduled++
return entry.chunk
}
boostType (type, amount = 20) {
for (const e of this._queue) {
if (e.chunk.type === type) e.priority += amount
}
this._sort()
return this._queue.length
}
enqueueBatch (chunks) {
if (!Array.isArray(chunks)) throw new Error('chunks must be an array')
return chunks.map((c) => this.enqueue(c.chunk || c, c.priority))
}
dropBelowPriority (minPriority) {
const before = this._queue.length
this._queue = this._queue.filter((e) => e.priority >= minPriority)
return before - this._queue.length
}
clear () {
const n = this._queue.length
this._queue = []
return n
}
getStats () {
return mediaStats(this._stats, PROTOCOL, { pending: this._queue.length })
}
async ready () { return this }
async close () {
this._queue = []
this.emit('closed')
}
}
module.exports = { HyperP2PChunkSchedulerMedia, PROTOCOL }