87 lines
4.1 KiB
TypeScript
87 lines
4.1 KiB
TypeScript
import type { ErrorCode } from "./error";
|
|
|
|
export const SEQUENCER_AUDIO_SESSION_SCHEMA = 1 as const;
|
|
export type SequencerAudioContextState = "UNAVAILABLE" | "SUSPENDED" | "RUNNING" | "CLOSED";
|
|
export type SequencerAudioOutputState = "BLOCKED" | "SILENT" | "ENABLED";
|
|
export type SequencerAudioSessionIssueCode = Extract<
|
|
ErrorCode,
|
|
| "SEQUENCER_AUDIO_DEVICE_UNAVAILABLE"
|
|
| "SEQUENCER_AUDIO_RESUME_FAILED"
|
|
| "SEQUENCER_AUDIO_SUSPEND_FAILED"
|
|
>;
|
|
|
|
export interface SequencerAudioSessionReportIR {
|
|
schemaVersion: typeof SEQUENCER_AUDIO_SESSION_SCHEMA;
|
|
revision: number;
|
|
contextState: SequencerAudioContextState;
|
|
outputState: SequencerAudioOutputState;
|
|
muted: boolean;
|
|
outputGain: number;
|
|
issueCode: SequencerAudioSessionIssueCode | null;
|
|
}
|
|
|
|
export class SequencerAudioSessionValidationError extends Error {
|
|
readonly code: ErrorCode;
|
|
|
|
constructor(message: string) {
|
|
super(`SEQUENCER_AUDIO_CONTEXT_INVALID: ${message}`);
|
|
this.name = "SequencerAudioSessionValidationError";
|
|
this.code = "SEQUENCER_AUDIO_CONTEXT_INVALID";
|
|
}
|
|
}
|
|
|
|
const REPORT_KEYS = new Set([
|
|
"schemaVersion", "revision", "contextState", "outputState", "muted", "outputGain", "issueCode",
|
|
]);
|
|
const CONTEXT_STATES = new Set<SequencerAudioContextState>(["UNAVAILABLE", "SUSPENDED", "RUNNING", "CLOSED"]);
|
|
const OUTPUT_STATES = new Set<SequencerAudioOutputState>(["BLOCKED", "SILENT", "ENABLED"]);
|
|
const ISSUE_CODES = new Set<SequencerAudioSessionIssueCode>([
|
|
"SEQUENCER_AUDIO_DEVICE_UNAVAILABLE",
|
|
"SEQUENCER_AUDIO_RESUME_FAILED",
|
|
"SEQUENCER_AUDIO_SUSPEND_FAILED",
|
|
]);
|
|
|
|
function record(value: unknown): value is Record<string, unknown> {
|
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
}
|
|
|
|
export function parseSequencerAudioSessionReport(value: unknown): SequencerAudioSessionReportIR {
|
|
if (!record(value) || value.schemaVersion !== SEQUENCER_AUDIO_SESSION_SCHEMA ||
|
|
Object.keys(value).length !== REPORT_KEYS.size || Object.keys(value).some((key) => !REPORT_KEYS.has(key))) {
|
|
throw new SequencerAudioSessionValidationError("audio session report fields are invalid");
|
|
}
|
|
if (!Number.isSafeInteger(value.revision) || (value.revision as number) < 0 ||
|
|
!CONTEXT_STATES.has(value.contextState as SequencerAudioContextState) ||
|
|
!OUTPUT_STATES.has(value.outputState as SequencerAudioOutputState) ||
|
|
typeof value.muted !== "boolean" || typeof value.outputGain !== "number" ||
|
|
!Number.isFinite(value.outputGain) || value.outputGain < 0 || value.outputGain > 1 ||
|
|
(value.issueCode !== null && !ISSUE_CODES.has(value.issueCode as SequencerAudioSessionIssueCode))) {
|
|
throw new SequencerAudioSessionValidationError("audio session report values are invalid");
|
|
}
|
|
|
|
const report = value as unknown as SequencerAudioSessionReportIR;
|
|
if (report.contextState === "UNAVAILABLE" &&
|
|
(report.outputState !== "BLOCKED" || report.outputGain !== 0 ||
|
|
report.issueCode !== "SEQUENCER_AUDIO_DEVICE_UNAVAILABLE")) {
|
|
throw new SequencerAudioSessionValidationError("unavailable audio context must be blocked");
|
|
}
|
|
if (report.contextState === "CLOSED" &&
|
|
(report.outputState !== "SILENT" || report.outputGain !== 0 || report.issueCode !== null)) {
|
|
throw new SequencerAudioSessionValidationError("closed audio context must release output");
|
|
}
|
|
if ((report.contextState === "RUNNING" || report.contextState === "SUSPENDED") && report.outputState === "BLOCKED") {
|
|
throw new SequencerAudioSessionValidationError("an allocated audio context cannot report blocked output");
|
|
}
|
|
if (report.contextState === "SUSPENDED" && report.outputState !== "SILENT") {
|
|
throw new SequencerAudioSessionValidationError("a suspended audio context must be silent");
|
|
}
|
|
if (report.outputState === "ENABLED" &&
|
|
(report.contextState !== "RUNNING" || report.muted || report.outputGain <= 0 || report.issueCode !== null)) {
|
|
throw new SequencerAudioSessionValidationError("enabled audio output invariants are invalid");
|
|
}
|
|
if (report.muted && report.outputGain !== 0) {
|
|
throw new SequencerAudioSessionValidationError("muted audio output gain must be zero");
|
|
}
|
|
return { ...report };
|
|
}
|