mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 11:24:00 +00:00
refactor(radio): separate directory and playback components
This commit is contained in:
1 parent
ba7fda74d7
commit
41b3cc1705
30 files changed
+4488
-2795
No files matched your search
@@ -0,0 +1,233 @@
|
||||
import {
|
||||
RADIO_TUNER_STATION_LIMIT,
|
||||
RADIO_TUNER_DIRECTORY_LIMIT,
|
||||
EMPTY_ACCEPTED_CATALOG_SNAPSHOT,
|
||||
} from './policy.js';
|
||||
|
||||
export function createTuning({ state: layerState, services, parts, source }) {
|
||||
/** Bound one already ordered filtered directory without injecting outside selections. */
|
||||
|
||||
function buildRadioTunerBand(
|
||||
rankedStations,
|
||||
selected,
|
||||
limit = RADIO_TUNER_STATION_LIMIT,
|
||||
) {
|
||||
const boundedLimit = Math.min(
|
||||
RADIO_TUNER_DIRECTORY_LIMIT,
|
||||
Math.max(1, Math.floor(Number(limit) || RADIO_TUNER_STATION_LIMIT)),
|
||||
);
|
||||
return (Array.isArray(rankedStations) ? rankedStations : []).slice(
|
||||
0,
|
||||
boundedLimit,
|
||||
);
|
||||
}
|
||||
|
||||
function captureRadioTuningResolutionSnapshot() {
|
||||
return layerState._acceptedCatalogSnapshot;
|
||||
}
|
||||
|
||||
/** Return the complete accepted filtered directory in stable catalog order. */
|
||||
|
||||
function getRadioTunerStations(limit = RADIO_TUNER_STATION_LIMIT) {
|
||||
if (
|
||||
!parts.interaction.radioPresentationAllowed() ||
|
||||
!layerState._acceptedCatalogSnapshot.stations.length
|
||||
)
|
||||
return [];
|
||||
return buildRadioTunerBand(
|
||||
parts.queries.visibleStations(),
|
||||
parts.queries.selectedStation(),
|
||||
limit,
|
||||
);
|
||||
}
|
||||
|
||||
/** Begin a direct-manipulation tuning gesture and pause the current stream. */
|
||||
|
||||
function beginRadioTuning() {
|
||||
if (
|
||||
!parts.interaction.radioPresentationAllowed() ||
|
||||
!parts.queries.visibleStations().length
|
||||
)
|
||||
return false;
|
||||
const snapshot = captureRadioTuningResolutionSnapshot();
|
||||
if (!Number.isSafeInteger(snapshot.generation) || !snapshot.stations.length)
|
||||
return false;
|
||||
layerState._tuningResolutionSnapshot = snapshot;
|
||||
layerState._tuningStationById = new Map(
|
||||
layerState._tuningResolutionSnapshot.stations.map((station) => [
|
||||
station.id,
|
||||
station,
|
||||
]),
|
||||
);
|
||||
layerState._tuningUnavailableStationId = null;
|
||||
layerState._tuningActive = true;
|
||||
layerState._tuningStatic = false;
|
||||
layerState._tuningAwaitingStationId = null;
|
||||
layerState._cancelledTuningPresentationStation = null;
|
||||
layerState._tuningStartStationId = layerState._selectedId;
|
||||
layerState._tuningPreviewId = layerState._selectedId;
|
||||
parts.tuningNoise.installTuningNoise();
|
||||
parts.playback.pauseRadioPlayback({ origin: 'user' });
|
||||
parts.tuningNoise.syncTuningNoiseGain();
|
||||
parts.presentation.emitState();
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Toggle low-volume synthesized static for tuner preview and stream handoff. */
|
||||
|
||||
function setRadioTuningStatic(active) {
|
||||
if (
|
||||
!parts.interaction.radioPresentationAllowed() ||
|
||||
!layerState._tuningActive
|
||||
)
|
||||
return false;
|
||||
const next = Boolean(active);
|
||||
if (next === layerState._tuningStatic) return true;
|
||||
layerState._tuningStatic = next;
|
||||
parts.tuningNoise.syncTuningNoiseGain();
|
||||
parts.presentation.emitState();
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Preview a tuner station bracket and camera orientation without starting audio. */
|
||||
|
||||
function previewRadioTuningStation(id, { rotate = true } = {}) {
|
||||
if (
|
||||
!parts.interaction.radioPresentationAllowed() ||
|
||||
!layerState._tuningActive
|
||||
)
|
||||
return false;
|
||||
const station = parts.model.tuningResolutionStation(id);
|
||||
const nextId = station?.id || null;
|
||||
const changed = nextId !== layerState._tuningPreviewId;
|
||||
layerState._tuningPreviewId = nextId;
|
||||
layerState._tuningStatic = !station;
|
||||
if (changed) {
|
||||
parts.rendering.updateSelectionEntity();
|
||||
if (station && rotate) {
|
||||
const cameraState = parts.navigation.radioCameraState();
|
||||
layerState._tuningCameraNavigation =
|
||||
parts.navigation.beginRadioCameraNavigation(cameraState);
|
||||
parts.navigation.rotateRadioStationIntoView(
|
||||
station,
|
||||
0.35,
|
||||
cameraState,
|
||||
layerState._tuningCameraNavigation,
|
||||
);
|
||||
} else if (rotate) {
|
||||
layerState._tuningCameraNavigation = null;
|
||||
parts.navigation.invalidateRadioCameraNavigation();
|
||||
}
|
||||
}
|
||||
parts.tuningNoise.syncTuningNoiseGain();
|
||||
parts.presentation.emitState();
|
||||
return Boolean(station);
|
||||
}
|
||||
|
||||
/** Finish a tuning gesture and release its synthesized noise source. */
|
||||
|
||||
function endRadioTuning() {
|
||||
if (
|
||||
!layerState._tuningActive &&
|
||||
!layerState._tuningStatic &&
|
||||
!layerState._tuningAwaitingStationId &&
|
||||
!layerState._tuningNoiseSource &&
|
||||
!layerState._cancelledTuningPresentationStation
|
||||
)
|
||||
return;
|
||||
parts.tuningNoise.clearRadioTuningNoise();
|
||||
}
|
||||
|
||||
/** Cancel a tuning gesture and restore its frozen start marker for presentation only. */
|
||||
|
||||
function cancelRadioTuning() {
|
||||
if (!layerState._tuningActive) return false;
|
||||
const restoredStation = parts.model.tuningResolutionStation(
|
||||
layerState._tuningStartStationId,
|
||||
);
|
||||
parts.tuningNoise.clearRadioTuningNoise({ restoredStation });
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Commit the exact frozen drag resolution or report that it is unavailable. */
|
||||
|
||||
function commitRadioTuningStation(id, { origin = 'programmatic' } = {}) {
|
||||
const frozenStation = parts.model.tuningResolutionStation(id);
|
||||
const currentStation = layerState._stationById.get(String(id)) || null;
|
||||
const generation = layerState._tuningResolutionSnapshot.generation;
|
||||
if (
|
||||
!parts.interaction.radioPresentationAllowed() ||
|
||||
!layerState._tuningActive ||
|
||||
!frozenStation
|
||||
) {
|
||||
endRadioTuning();
|
||||
return Object.freeze({
|
||||
ok: false,
|
||||
reason: 'not-tuning',
|
||||
stationId: String(id || ''),
|
||||
generation,
|
||||
});
|
||||
}
|
||||
if (
|
||||
!parts.model.radioStationResolutionMatches(frozenStation, currentStation)
|
||||
) {
|
||||
const stationId = frozenStation.id;
|
||||
// A failed exact release owns the visible outcome. Do not let the previous
|
||||
// selection—or changed metadata under the same ID—reappear between the
|
||||
// frozen preview and the unavailable state.
|
||||
layerState._selectedId = null;
|
||||
layerState._selectionGeneration += 1;
|
||||
parts.tuningNoise.clearRadioTuningNoise({ emit: false });
|
||||
layerState._tuningUnavailableStationId = stationId;
|
||||
parts.presentation.emitState();
|
||||
return Object.freeze({
|
||||
ok: false,
|
||||
reason: 'station-unavailable',
|
||||
stationId,
|
||||
generation,
|
||||
});
|
||||
}
|
||||
layerState._tuningActive = false;
|
||||
layerState._tuningStatic = true;
|
||||
layerState._tuningAwaitingStationId = frozenStation.id;
|
||||
layerState._tuningPreviewId = null;
|
||||
layerState._tuningStartStationId = null;
|
||||
layerState._tuningUnavailableStationId = null;
|
||||
const cameraNavigation = layerState._tuningCameraNavigation;
|
||||
layerState._tuningCameraNavigation = null;
|
||||
layerState._tuningResolutionSnapshot = EMPTY_ACCEPTED_CATALOG_SNAPSHOT;
|
||||
layerState._tuningStationById = new Map();
|
||||
// An exact tuner release owns its frozen target. A fallback armed by an
|
||||
// earlier non-playing selection must never retarget this gesture if the
|
||||
// broadcaster fails after release.
|
||||
layerState._playFallbackId = null;
|
||||
layerState._playFallbackFocus = null;
|
||||
layerState._playFallbackOrigin = 'programmatic';
|
||||
layerState._playFallbackAttemptId = null;
|
||||
parts.tuningNoise.syncTuningNoiseGain();
|
||||
parts.presentation.emitState();
|
||||
const selected = parts.selection.selectRadioStation(frozenStation.id, {
|
||||
autoplay: true,
|
||||
focus: false,
|
||||
origin,
|
||||
cameraNavigation,
|
||||
});
|
||||
return Object.freeze({
|
||||
ok: selected,
|
||||
reason: selected ? null : 'station-unavailable',
|
||||
stationId: frozenStation.id,
|
||||
generation,
|
||||
});
|
||||
}
|
||||
return {
|
||||
buildRadioTunerBand,
|
||||
captureRadioTuningResolutionSnapshot,
|
||||
getRadioTunerStations,
|
||||
beginRadioTuning,
|
||||
setRadioTuningStatic,
|
||||
previewRadioTuningStation,
|
||||
endRadioTuning,
|
||||
cancelRadioTuning,
|
||||
commitRadioTuningStation,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user