mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-26 13:19:42 +00:00
Fixes #167027
This commit is contained in:
committed by
Henning Dieterichs
parent
4df2340a3c
commit
e8b1e2bea8
@@ -142,9 +142,9 @@ class ViewModel extends Disposable {
|
||||
/** @description play audio-cue for diff */
|
||||
const currentViewItem = this.currentElement.read(reader);
|
||||
if (currentViewItem?.type === LineType.Deleted) {
|
||||
this._audioCueService.playAudioCue(AudioCue.diffLineDeleted);
|
||||
this._audioCueService.playAudioCue(AudioCue.diffLineDeleted, { source: 'accessibleDiffViewer.currentElementChanged' });
|
||||
} else if (currentViewItem?.type === LineType.Added) {
|
||||
this._audioCueService.playAudioCue(AudioCue.diffLineInserted);
|
||||
this._audioCueService.playAudioCue(AudioCue.diffLineInserted, { source: 'accessibleDiffViewer.currentElementChanged' });
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
@@ -255,11 +255,11 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
|
||||
if (e?.reason === CursorChangeReason.Explicit) {
|
||||
const diff = this._diffModel.get()?.diff.get()?.mappings.find(m => m.lineRangeMapping.modifiedRange.contains(e.position.lineNumber));
|
||||
if (diff?.lineRangeMapping.modifiedRange.isEmpty) {
|
||||
this._audioCueService.playAudioCue(AudioCue.diffLineDeleted);
|
||||
this._audioCueService.playAudioCue(AudioCue.diffLineDeleted, { source: 'diffEditor.cursorPositionChanged' });
|
||||
} else if (diff?.lineRangeMapping.originalRange.isEmpty) {
|
||||
this._audioCueService.playAudioCue(AudioCue.diffLineInserted);
|
||||
this._audioCueService.playAudioCue(AudioCue.diffLineInserted, { source: 'diffEditor.cursorPositionChanged' });
|
||||
} else if (diff) {
|
||||
this._audioCueService.playAudioCue(AudioCue.diffLineModified);
|
||||
this._audioCueService.playAudioCue(AudioCue.diffLineModified, { source: 'diffEditor.cursorPositionChanged' });
|
||||
}
|
||||
}
|
||||
}));
|
||||
@@ -460,11 +460,11 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
|
||||
this._goTo(diff);
|
||||
|
||||
if (diff.lineRangeMapping.modifiedRange.isEmpty) {
|
||||
this._audioCueService.playAudioCue(AudioCue.diffLineDeleted);
|
||||
this._audioCueService.playAudioCue(AudioCue.diffLineDeleted, { source: 'diffEditor.goToDiff' });
|
||||
} else if (diff.lineRangeMapping.originalRange.isEmpty) {
|
||||
this._audioCueService.playAudioCue(AudioCue.diffLineInserted);
|
||||
this._audioCueService.playAudioCue(AudioCue.diffLineInserted, { source: 'diffEditor.goToDiff' });
|
||||
} else if (diff) {
|
||||
this._audioCueService.playAudioCue(AudioCue.diffLineModified);
|
||||
this._audioCueService.playAudioCue(AudioCue.diffLineModified, { source: 'diffEditor.goToDiff' });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -224,11 +224,11 @@ export class DiffNavigator extends Disposable implements IDiffNavigator {
|
||||
}
|
||||
const insertedOrModified = modifiedEditor.getLineDecorations(lineNumber).find(l => l.options.className === 'line-insert');
|
||||
if (insertedOrModified) {
|
||||
this._audioCueService.playAudioCue(AudioCue.diffLineModified, true);
|
||||
this._audioCueService.playAudioCue(AudioCue.diffLineModified, { allowManyInParallel: true });
|
||||
} else if (jumpToChange) {
|
||||
// The modified editor does not include deleted lines, but when
|
||||
// we are moved to the area where lines were deleted, play this cue
|
||||
this._audioCueService.playAudioCue(AudioCue.diffLineDeleted, true);
|
||||
this._audioCueService.playAudioCue(AudioCue.diffLineDeleted, { allowManyInParallel: true });
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -325,9 +325,9 @@ export class DiffReview extends Disposable {
|
||||
}
|
||||
const element = !type ? current : type === 'next' ? current?.nextElementSibling : current?.previousElementSibling;
|
||||
if (element?.classList.contains(DiffEditorLineClasses.Insert)) {
|
||||
this._audioCueService.playAudioCue(AudioCue.diffLineInserted, true);
|
||||
this._audioCueService.playAudioCue(AudioCue.diffLineInserted, { allowManyInParallel: true });
|
||||
} else if (element?.classList.contains(DiffEditorLineClasses.Delete)) {
|
||||
this._audioCueService.playAudioCue(AudioCue.diffLineDeleted, true);
|
||||
this._audioCueService.playAudioCue(AudioCue.diffLineDeleted, { allowManyInParallel: true });
|
||||
}
|
||||
this.scrollbar.scanDomNode();
|
||||
}
|
||||
|
||||
@@ -1039,7 +1039,7 @@ class StandaloneContextMenuService extends ContextMenuService {
|
||||
|
||||
class StandaloneAudioService implements IAudioCueService {
|
||||
_serviceBrand: undefined;
|
||||
async playAudioCue(cue: AudioCue, allowManyInParallel?: boolean | undefined): Promise<void> {
|
||||
async playAudioCue(cue: AudioCue, options: {}): Promise<void> {
|
||||
}
|
||||
|
||||
async playAudioCues(cues: AudioCue[]): Promise<void> {
|
||||
|
||||
@@ -11,13 +11,14 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { localize } from 'vs/nls';
|
||||
import { observableFromEvent, derived } from 'vs/base/common/observable';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
export const IAudioCueService = createDecorator<IAudioCueService>('audioCue');
|
||||
|
||||
export interface IAudioCueService {
|
||||
readonly _serviceBrand: undefined;
|
||||
playAudioCue(cue: AudioCue, allowManyInParallel?: boolean): Promise<void>;
|
||||
playAudioCues(cues: AudioCue[]): Promise<void>;
|
||||
playAudioCue(cue: AudioCue, options?: IAudioCueOptions): Promise<void>;
|
||||
playAudioCues(cues: (AudioCue | { cue: AudioCue; source: string })[]): Promise<void>;
|
||||
isEnabled(cue: AudioCue): boolean;
|
||||
onEnabledChanged(cue: AudioCue): Event<void>;
|
||||
|
||||
@@ -25,33 +26,72 @@ export interface IAudioCueService {
|
||||
playAudioCueLoop(cue: AudioCue, milliseconds: number): IDisposable;
|
||||
}
|
||||
|
||||
export interface IAudioCueOptions {
|
||||
allowManyInParallel?: boolean;
|
||||
source?: string;
|
||||
}
|
||||
|
||||
export class AudioCueService extends Disposable implements IAudioCueService {
|
||||
readonly _serviceBrand: undefined;
|
||||
sounds: Map<string, HTMLAudioElement> = new Map();
|
||||
private readonly sounds: Map<string, HTMLAudioElement> = new Map();
|
||||
private readonly screenReaderAttached = observableFromEvent(
|
||||
this.accessibilityService.onDidChangeScreenReaderOptimized,
|
||||
() => /** @description accessibilityService.onDidChangeScreenReaderOptimized */ this.accessibilityService.isScreenReaderOptimized()
|
||||
);
|
||||
private readonly sentTelemetry = new Set<string>();
|
||||
|
||||
constructor(
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IAccessibilityService private readonly accessibilityService: IAccessibilityService
|
||||
@IAccessibilityService private readonly accessibilityService: IAccessibilityService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public async playAudioCue(cue: AudioCue, allowManyInParallel = false): Promise<void> {
|
||||
public async playAudioCue(cue: AudioCue, options: IAudioCueOptions = {}): Promise<void> {
|
||||
if (this.isEnabled(cue)) {
|
||||
await this.playSound(cue.sound.getSound(), allowManyInParallel);
|
||||
this.sendAudioCueTelemetry(cue, options.source);
|
||||
await this.playSound(cue.sound.getSound(), options.allowManyInParallel);
|
||||
}
|
||||
}
|
||||
|
||||
public async playAudioCues(cues: AudioCue[]): Promise<void> {
|
||||
public async playAudioCues(cues: (AudioCue | { cue: AudioCue; source: string })[]): Promise<void> {
|
||||
for (const cue of cues) {
|
||||
this.sendAudioCueTelemetry('cue' in cue ? cue.cue : cue, 'source' in cue ? cue.source : undefined);
|
||||
}
|
||||
|
||||
// Some audio cues might reuse sounds. Don't play the same sound twice.
|
||||
const sounds = new Set(cues.filter(cue => this.isEnabled(cue)).map(cue => cue.sound.getSound()));
|
||||
const sounds = new Set(cues.map(c => 'cue' in c ? c.cue : c).filter(cue => this.isEnabled(cue)).map(cue => cue.sound.getSound()));
|
||||
await Promise.all(Array.from(sounds).map(sound => this.playSound(sound, true)));
|
||||
}
|
||||
|
||||
private sendAudioCueTelemetry(cue: AudioCue, source: string | undefined): void {
|
||||
const isScreenReaderOptimized = this.accessibilityService.isScreenReaderOptimized();
|
||||
const key = cue.name + (source ? `::${source}` : '') + (isScreenReaderOptimized ? '{screenReaderOptimized}' : '');
|
||||
// Only send once per user session
|
||||
if (this.sentTelemetry.has(key) || this.getVolumeInPercent() === 0) {
|
||||
return;
|
||||
}
|
||||
this.sentTelemetry.add(key);
|
||||
|
||||
this.telemetryService.publicLog2<{
|
||||
audioCue: string;
|
||||
source: string;
|
||||
isScreenReaderOptimized: boolean;
|
||||
}, {
|
||||
owner: 'hediet';
|
||||
|
||||
audioCue: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The audio cue that was played.' };
|
||||
source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The source that triggered the audio cue (e.g. "diffEditorNavigation").' };
|
||||
isScreenReaderOptimized: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user is using a screen reader' };
|
||||
|
||||
comment: 'This data is collected to understand how audio cues are used and if more audio cues should be added.';
|
||||
}>('audioCue.played', {
|
||||
audioCue: cue.name,
|
||||
source: source ?? '',
|
||||
isScreenReaderOptimized,
|
||||
});
|
||||
}
|
||||
|
||||
private getVolumeInPercent(): number {
|
||||
const volume = this.configurationService.getValue<number>('audioCues.volume');
|
||||
@@ -92,7 +132,7 @@ export class AudioCueService extends Disposable implements IAudioCueService {
|
||||
let playing = true;
|
||||
const playSound = () => {
|
||||
if (playing) {
|
||||
this.playAudioCue(cue, true).finally(() => {
|
||||
this.playAudioCue(cue, { allowManyInParallel: true }).finally(() => {
|
||||
setTimeout(() => {
|
||||
if (playing) {
|
||||
playSound();
|
||||
|
||||
@@ -30,7 +30,7 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi
|
||||
}, CHAT_RESPONSE_PENDING_ALLOWANCE_MS));
|
||||
}
|
||||
acceptRequest(): void {
|
||||
this._audioCueService.playAudioCue(AudioCue.chatRequestSent, true);
|
||||
this._audioCueService.playAudioCue(AudioCue.chatRequestSent, { allowManyInParallel: true });
|
||||
this._runOnceScheduler.schedule();
|
||||
}
|
||||
acceptResponse(response?: IChatResponseViewModel | string): void {
|
||||
@@ -42,7 +42,7 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi
|
||||
if (this._lastResponse === responseContent) {
|
||||
return;
|
||||
}
|
||||
this._audioCueService.playAudioCue(AudioCue.chatResponseReceived, true);
|
||||
this._audioCueService.playAudioCue(AudioCue.chatResponseReceived, { allowManyInParallel: true });
|
||||
this._hasReceivedRequest = false;
|
||||
if (!response) {
|
||||
return;
|
||||
|
||||
@@ -662,13 +662,13 @@ async function playAudioCueForChange(change: IChange, audioCueService: IAudioCue
|
||||
const changeType = getChangeType(change);
|
||||
switch (changeType) {
|
||||
case ChangeType.Add:
|
||||
audioCueService.playAudioCue(AudioCue.diffLineInserted, true);
|
||||
audioCueService.playAudioCue(AudioCue.diffLineInserted, { allowManyInParallel: true, source: 'dirtyDiffDecoration' });
|
||||
break;
|
||||
case ChangeType.Delete:
|
||||
audioCueService.playAudioCue(AudioCue.diffLineDeleted, true);
|
||||
audioCueService.playAudioCue(AudioCue.diffLineDeleted, { allowManyInParallel: true, source: 'dirtyDiffDecoration' });
|
||||
break;
|
||||
case ChangeType.Modify:
|
||||
audioCueService.playAudioCue(AudioCue.diffLineModified, true);
|
||||
audioCueService.playAudioCue(AudioCue.diffLineModified, { allowManyInParallel: true, source: 'dirtyDiffDecoration' });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ export class AccessibleBufferWidget extends TerminalAccessibleWidget {
|
||||
return;
|
||||
}
|
||||
if (activeItem.exitCode) {
|
||||
this._audioCueService.playAudioCue(AudioCue.error, true);
|
||||
this._audioCueService.playAudioCue(AudioCue.error, { allowManyInParallel: true, source: 'accessibleBufferWidget' });
|
||||
}
|
||||
this.editorWidget.revealLine(activeItem.lineNumber, 0);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user