Optimize media stream subscriptions by distance and movement

This commit is contained in:
Jage9
2026-02-22 19:31:44 -05:00
parent c4494f2f99
commit 7ba67c3707
5 changed files with 132 additions and 28 deletions

View File

@@ -24,6 +24,8 @@ type EmitSpatialConfig = {
};
const ITEM_EMIT_BASE_GAIN = 0.3;
const SUBSCRIBE_PRELOAD_SQUARES = 5;
const UNSUBSCRIBE_HYSTERESIS_SQUARES = 8;
/** Maps a 0-100 speed control to playback-rate range used by emitted audio. */
function resolveEmitPlaybackRate(raw: unknown): number {
@@ -60,6 +62,7 @@ function resolveEmitRates(item: WorldItem): { playbackRate: number; preservePitc
export class ItemEmitRuntime {
private readonly outputs = new Map<string, EmitOutput>();
private layerEnabled = true;
private listenerPosition: { x: number; y: number } | null = null;
constructor(
private readonly audio: AudioEngine,
@@ -86,30 +89,39 @@ export class ItemEmitRuntime {
}
}
async setLayerEnabled(enabled: boolean, items: Iterable<WorldItem>): Promise<void> {
async setLayerEnabled(
enabled: boolean,
items: Iterable<WorldItem>,
listenerPosition: { x: number; y: number } | null = null,
): Promise<void> {
this.layerEnabled = enabled;
if (listenerPosition) {
this.listenerPosition = { ...listenerPosition };
}
if (!enabled) {
this.cleanupAll();
return;
}
await this.sync(items);
await this.sync(items, this.listenerPosition);
}
async sync(items: Iterable<WorldItem>): Promise<void> {
async sync(items: Iterable<WorldItem>, listenerPosition: { x: number; y: number } | null = null): Promise<void> {
if (!this.layerEnabled) {
this.cleanupAll();
return;
}
if (listenerPosition) {
this.listenerPosition = { ...listenerPosition };
}
const listener = this.listenerPosition;
const validIds = new Set<string>();
await this.audio.ensureContext();
const audioCtx = this.audio.context;
if (!audioCtx) return;
let audioCtx = this.audio.context;
for (const item of items) {
const emitSound = String(item.params.emitSound ?? item.emitSound ?? '').trim();
const enabled = item.params.enabled !== false;
const soundUrl = enabled ? this.resolveSoundUrl(emitSound) : '';
if (!soundUrl || item.carrierId) {
if (!soundUrl || item.carrierId || !this.shouldKeepRuntime(item, listener, this.outputs.has(item.id))) {
this.cleanup(item.id);
continue;
}
@@ -121,6 +133,13 @@ export class ItemEmitRuntime {
if (existing) {
this.cleanup(item.id);
}
if (!audioCtx) {
await this.audio.ensureContext();
audioCtx = this.audio.context;
}
if (!audioCtx) {
continue;
}
const element = new Audio(soundUrl);
element.loop = true;
element.preload = 'none';
@@ -208,4 +227,16 @@ export class ItemEmitRuntime {
}
}
}
private shouldKeepRuntime(
item: WorldItem,
listenerPosition: { x: number; y: number } | null,
currentlyActive: boolean,
): boolean {
if (!listenerPosition) return false;
const spatialConfig = this.getSpatialConfig(item);
const baseRange = Math.max(1, spatialConfig.range || HEARING_RADIUS);
const threshold = baseRange + (currentlyActive ? UNSUBSCRIBE_HYSTERESIS_SQUARES : SUBSCRIBE_PRELOAD_SQUARES);
return Math.hypot(item.x - listenerPosition.x, item.y - listenerPosition.y) <= threshold;
}
}