Merge pull request 'feat(lotus): report the frame's audio outputs to the host (cinny #310)' (#49) from fix/audio-output-list into lotus
This commit was merged in pull request #49.
This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2026 Lotus Guild
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
|
import { audioOutputList } from "./lotusAudioOutput";
|
||||||
|
import { type AudioOutputDeviceLabel } from "../state/MediaDevices";
|
||||||
|
|
||||||
|
test("audioOutputList labels every kind of output (cinny #310)", () => {
|
||||||
|
const m = new Map<string, AudioOutputDeviceLabel>([
|
||||||
|
["", { type: "default", name: "Speakers" }],
|
||||||
|
["a1", { type: "name", name: "Headset" }],
|
||||||
|
["a2", { type: "number", number: 3 }],
|
||||||
|
]);
|
||||||
|
expect(audioOutputList(m)).toEqual([
|
||||||
|
{ id: "", label: "Default (Speakers)" },
|
||||||
|
{ id: "a1", label: "Headset" },
|
||||||
|
{ id: "a2", label: "Output 3" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
@@ -8,7 +8,12 @@ Please see LICENSE in the repository root for full details.
|
|||||||
import { type IWidgetApiRequest } from "matrix-widget-api";
|
import { type IWidgetApiRequest } from "matrix-widget-api";
|
||||||
import { logger } from "matrix-js-sdk/lib/logger";
|
import { logger } from "matrix-js-sdk/lib/logger";
|
||||||
|
|
||||||
import { type MediaDevices } from "../state/MediaDevices";
|
import { combineLatest } from "rxjs";
|
||||||
|
|
||||||
|
import {
|
||||||
|
type AudioOutputDeviceLabel,
|
||||||
|
type MediaDevices,
|
||||||
|
} from "../state/MediaDevices";
|
||||||
import { widget } from "../widget";
|
import { widget } from "../widget";
|
||||||
import { LotusWidgetActions } from "./lotusActions";
|
import { LotusWidgetActions } from "./lotusActions";
|
||||||
import { lotusSendToHost } from "./lotusWidget";
|
import { lotusSendToHost } from "./lotusWidget";
|
||||||
@@ -19,6 +24,10 @@ import { lotusSendToHost } from "./lotusWidget";
|
|||||||
* selects the output; the fork answers each change (and the initial state)
|
* selects the output; the fork answers each change (and the initial state)
|
||||||
* with `io.lotus.audio_output_state { deviceId, available: [{id,label}] }`.
|
* with `io.lotus.audio_output_state { deviceId, available: [{id,label}] }`.
|
||||||
* No effect unless the host sends the action. Returns a teardown function.
|
* No effect unless the host sends the action. Returns a teardown function.
|
||||||
|
*
|
||||||
|
* [cinny #310] Browsers hash deviceIds per frame/origin, so the host can't
|
||||||
|
* use ids from its own enumerateDevices(); it builds its menu from
|
||||||
|
* `available` here, whose ids are valid in this frame.
|
||||||
*/
|
*/
|
||||||
export function startLotusAudioOutput(mediaDevices: MediaDevices): () => void {
|
export function startLotusAudioOutput(mediaDevices: MediaDevices): () => void {
|
||||||
const w = widget;
|
const w = widget;
|
||||||
@@ -33,9 +42,13 @@ export function startLotusAudioOutput(mediaDevices: MediaDevices): () => void {
|
|||||||
};
|
};
|
||||||
w.lazyActions.on(LotusWidgetActions.SetAudioOutput, handler);
|
w.lazyActions.on(LotusWidgetActions.SetAudioOutput, handler);
|
||||||
|
|
||||||
const sub = mediaDevices.audioOutput.selected$.subscribe((selected) => {
|
const sub = combineLatest([
|
||||||
|
mediaDevices.audioOutput.available$,
|
||||||
|
mediaDevices.audioOutput.selected$,
|
||||||
|
]).subscribe(([available, selected]) => {
|
||||||
lotusSendToHost(LotusWidgetActions.AudioOutputState, {
|
lotusSendToHost(LotusWidgetActions.AudioOutputState, {
|
||||||
deviceId: selected?.id ?? null,
|
deviceId: selected?.id ?? null,
|
||||||
|
available: audioOutputList(available),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -44,3 +57,29 @@ export function startLotusAudioOutput(mediaDevices: MediaDevices): () => void {
|
|||||||
w.lazyActions.off(LotusWidgetActions.SetAudioOutput, handler);
|
w.lazyActions.off(LotusWidgetActions.SetAudioOutput, handler);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The output list as `{id, label}` for the host's menu (#310). */
|
||||||
|
export function audioOutputList(
|
||||||
|
available: Map<string, AudioOutputDeviceLabel>,
|
||||||
|
): { id: string; label: string }[] {
|
||||||
|
return [...available].map(([id, l]) => {
|
||||||
|
let label: string;
|
||||||
|
switch (l.type) {
|
||||||
|
case "name":
|
||||||
|
label = l.name;
|
||||||
|
break;
|
||||||
|
case "number":
|
||||||
|
label = `Output ${l.number}`;
|
||||||
|
break;
|
||||||
|
case "default":
|
||||||
|
label = l.name ? `Default (${l.name})` : "Default";
|
||||||
|
break;
|
||||||
|
case "speaker":
|
||||||
|
label = "Speaker";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
label = "Earpiece";
|
||||||
|
}
|
||||||
|
return { id, label };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user