feat: Add UI optional UI locking when microphone is hot (#2484)

This commit is contained in:
Dermot Duffy
2026-06-30 17:45:12 -07:00
committed by dermotduffy
parent 9a763db1f3
commit be7e7d79dc
45 changed files with 922 additions and 53 deletions
+5
View File
@@ -45,6 +45,11 @@ export interface ViewFactoryOptions {
// `live` view if the configured default view is not supported.
failSafe?: boolean;
// When force is true the view change bypasses internal gates such as the
// navigation lock. Reserved for internal callers that must override
// user-facing locks (e.g. an active call ending).
force?: boolean;
// Options for the query executor that control how a query is executed and the
// result selected.
queryExecutorOptions?: QueryExecutorOptions;
+24 -4
View File
@@ -102,7 +102,7 @@ export class ViewManager implements ViewManagerInterface {
viewFactoryFunc: (options?: ViewFactoryOptions) => View | null,
options?: ViewFactoryOptions,
): void {
if (!this._isAllowedToSetView()) {
if (!this._isAllowedToProposeView()) {
return;
}
@@ -125,6 +125,9 @@ export class ViewManager implements ViewManagerInterface {
}
this._api.getIssueManager().trigger('view_incompatible', { error: e });
}
if (view && !this._isAllowedToSetView(view, options)) {
return;
}
if (view) {
this._setView(view);
}
@@ -147,7 +150,10 @@ export class ViewManager implements ViewManagerInterface {
view.removeContextProperty('loading', 'query');
}
private _isAllowedToSetView(): boolean {
// Pre-computation gate: whether we should even attempt to build a candidate
// view. Skipped here for race conditions that would otherwise generate a
// spurious `view_incompatible` issue.
private _isAllowedToProposeView(): boolean {
// It is possible to have a race condition where the view is being set at
// the same time as the cameras being initialized. Test case: Open
// folder-based media in the media viewer carousel, then attempt to edit the
@@ -160,6 +166,20 @@ export class ViewManager implements ViewManagerInterface {
.isInitialized(InitializationAspect.CAMERAS);
}
// Post-computation gate: given a freshly proposed view, whether we should
// actually commit it. Respects the lock state by potentially rejecting
// changes that would disrupt the active session (camera, view name, or
// substream).
private _isAllowedToSetView(
proposedView: View,
options?: ViewFactoryOptions,
): boolean {
if (options?.force || !this._api.getLockManager().isLocked()) {
return true;
}
return !this.hasMajorMediaChange(this._view, proposedView);
}
private async _setViewThenModifyAsync(
viewFactoryFunc: (options?: ViewFactoryOptions) => View | null,
viewModifiersFunc: (
@@ -168,7 +188,7 @@ export class ViewManager implements ViewManagerInterface {
) => Promise<ViewModifier[] | null>,
options?: ViewFactoryOptions,
): Promise<void> {
if (!this._isAllowedToSetView()) {
if (!this._isAllowedToProposeView()) {
return;
}
@@ -195,7 +215,7 @@ export class ViewManager implements ViewManagerInterface {
this._api.getIssueManager().trigger('view_incompatible', { error: e });
}
if (!initialView) {
if (!initialView || !this._isAllowedToSetView(initialView, options)) {
return;
}