Advance M8-M11 parity workflows
This commit is contained in:
86
web/protocol/sequencer-audio-session.ts
Normal file
86
web/protocol/sequencer-audio-session.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
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 };
|
||||
}
|
||||
Reference in New Issue
Block a user