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 }