UNPKG

sussudio

Version:

An unofficial VS Code Internal API

270 lines (269 loc) 11.4 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; import { raceTimeout } from "../../../base/common/async.mjs"; import { Disposable } from "../../../base/common/lifecycle.mjs"; import { FileAccess } from "../../../base/common/network.mjs"; import { IAccessibilityService } from "../../accessibility/common/accessibility.mjs"; import { IConfigurationService } from "../../configuration/common/configuration.mjs"; import { createDecorator } from "../../instantiation/common/instantiation.mjs"; import { Event } from "../../../base/common/event.mjs"; import { localize } from "../../../nls.mjs"; import { observableFromEvent, derived } from "../../../base/common/observable.mjs"; export const IAudioCueService = createDecorator('audioCue'); let AudioCueService = class AudioCueService extends Disposable { configurationService; accessibilityService; _serviceBrand; screenReaderAttached = observableFromEvent(this.accessibilityService.onDidChangeScreenReaderOptimized, () => /** @description accessibilityService.onDidChangeScreenReaderOptimized */ this.accessibilityService.isScreenReaderOptimized()); constructor(configurationService, accessibilityService) { super(); this.configurationService = configurationService; this.accessibilityService = accessibilityService; } async playAudioCue(cue, allowManyInParallel = false) { if (this.isEnabled(cue)) { await this.playSound(cue.sound, allowManyInParallel); } } async playAudioCues(cues) { // 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)); await Promise.all(Array.from(sounds).map(sound => this.playSound(sound))); } getVolumeInPercent() { const volume = this.configurationService.getValue('audioCues.volume'); if (typeof volume !== 'number') { return 50; } return Math.max(Math.min(volume, 100), 0); } playingSounds = new Set(); async playSound(sound, allowManyInParallel = false) { if (!allowManyInParallel && this.playingSounds.has(sound)) { return; } this.playingSounds.add(sound); const url = FileAccess.asBrowserUri(`vs/platform/audioCues/browser/media/${sound.fileName}`).toString(); const audio = new Audio(url); audio.volume = this.getVolumeInPercent() / 100; audio.addEventListener('ended', () => { this.playingSounds.delete(sound); }); try { try { // Don't play when loading takes more than 1s, due to loading, decoding or playing issues. // Delayed sounds are very confusing. await raceTimeout(audio.play(), 1000); } catch (e) { console.error('Error while playing sound', e); } } finally { audio.remove(); } } obsoleteAudioCuesEnabled = observableFromEvent(Event.filter(this.configurationService.onDidChangeConfiguration, (e) => e.affectsConfiguration('audioCues.enabled')), () => /** @description config: audioCues.enabled */ this.configurationService.getValue('audioCues.enabled')); isEnabledCache = new Cache((cue) => { const settingObservable = observableFromEvent(Event.filter(this.configurationService.onDidChangeConfiguration, (e) => e.affectsConfiguration(cue.settingsKey)), () => this.configurationService.getValue(cue.settingsKey)); return derived('audio cue enabled', reader => { const setting = settingObservable.read(reader); if (setting === 'on' || (setting === 'auto' && this.screenReaderAttached.read(reader))) { return true; } const obsoleteSetting = this.obsoleteAudioCuesEnabled.read(reader); if (obsoleteSetting === 'on' || (obsoleteSetting === 'auto' && this.screenReaderAttached.read(reader))) { return true; } return false; }); }); isEnabled(cue) { return this.isEnabledCache.get(cue).get(); } onEnabledChanged(cue) { return eventFromObservable(this.isEnabledCache.get(cue)); } }; AudioCueService = __decorate([ __param(0, IConfigurationService), __param(1, IAccessibilityService) ], AudioCueService); export { AudioCueService }; function eventFromObservable(observable) { return (listener) => { let count = 0; let didChange = false; const observer = { beginUpdate() { count++; }, endUpdate() { count--; if (count === 0 && didChange) { didChange = false; listener(); } }, handleChange() { if (count === 0) { listener(); } else { didChange = true; } } }; observable.addObserver(observer); return { dispose() { observable.removeObserver(observer); } }; }; } class Cache { getValue; map = new Map(); constructor(getValue) { this.getValue = getValue; } get(arg) { if (this.map.has(arg)) { return this.map.get(arg); } const value = this.getValue(arg); this.map.set(arg, value); return value; } } /** * Corresponds to the audio files in ./media. */ export class Sound { fileName; static register(options) { const sound = new Sound(options.fileName); return sound; } static error = Sound.register({ fileName: 'error.mp3' }); static warning = Sound.register({ fileName: 'warning.mp3' }); static foldedArea = Sound.register({ fileName: 'foldedAreas.mp3' }); static break = Sound.register({ fileName: 'break.mp3' }); static quickFixes = Sound.register({ fileName: 'quickFixes.mp3' }); static taskCompleted = Sound.register({ fileName: 'taskCompleted.mp3' }); static taskFailed = Sound.register({ fileName: 'taskFailed.mp3' }); static terminalBell = Sound.register({ fileName: 'terminalBell.mp3' }); static diffLineInserted = Sound.register({ fileName: 'diffLineInserted.mp3' }); static diffLineDeleted = Sound.register({ fileName: 'diffLineDeleted.mp3' }); constructor(fileName) { this.fileName = fileName; } } export class AudioCue { sound; name; settingsKey; static _audioCues = new Set(); static register(options) { const audioCue = new AudioCue(options.sound, options.name, options.settingsKey); AudioCue._audioCues.add(audioCue); return audioCue; } static get allAudioCues() { return [...this._audioCues]; } static error = AudioCue.register({ name: localize('audioCues.lineHasError.name', 'Error on Line'), sound: Sound.error, settingsKey: 'audioCues.lineHasError', }); static warning = AudioCue.register({ name: localize('audioCues.lineHasWarning.name', 'Warning on Line'), sound: Sound.warning, settingsKey: 'audioCues.lineHasWarning', }); static foldedArea = AudioCue.register({ name: localize('audioCues.lineHasFoldedArea.name', 'Folded Area on Line'), sound: Sound.foldedArea, settingsKey: 'audioCues.lineHasFoldedArea', }); static break = AudioCue.register({ name: localize('audioCues.lineHasBreakpoint.name', 'Breakpoint on Line'), sound: Sound.break, settingsKey: 'audioCues.lineHasBreakpoint', }); static inlineSuggestion = AudioCue.register({ name: localize('audioCues.lineHasInlineSuggestion.name', 'Inline Suggestion on Line'), sound: Sound.quickFixes, settingsKey: 'audioCues.lineHasInlineSuggestion', }); static terminalQuickFix = AudioCue.register({ name: localize('audioCues.terminalQuickFix.name', 'Terminal Quick Fix'), sound: Sound.quickFixes, settingsKey: 'audioCues.terminalQuickFix', }); static onDebugBreak = AudioCue.register({ name: localize('audioCues.onDebugBreak.name', 'Debugger Stopped on Breakpoint'), sound: Sound.break, settingsKey: 'audioCues.onDebugBreak', }); static noInlayHints = AudioCue.register({ name: localize('audioCues.noInlayHints', 'No Inlay Hints on Line'), sound: Sound.error, settingsKey: 'audioCues.noInlayHints' }); static taskCompleted = AudioCue.register({ name: localize('audioCues.taskCompleted', 'Task Completed'), sound: Sound.taskCompleted, settingsKey: 'audioCues.taskCompleted' }); static taskFailed = AudioCue.register({ name: localize('audioCues.taskFailed', 'Task Failed'), sound: Sound.taskFailed, settingsKey: 'audioCues.taskFailed' }); static terminalBell = AudioCue.register({ name: localize('audioCues.terminalBell', 'Terminal Bell'), sound: Sound.terminalBell, settingsKey: 'audioCues.terminalBell' }); static notebookCellCompleted = AudioCue.register({ name: localize('audioCues.notebookCellCompleted', 'Notebook Cell Completed'), sound: Sound.taskCompleted, settingsKey: 'audioCues.notebookCellCompleted' }); static notebookCellFailed = AudioCue.register({ name: localize('audioCues.notebookCellFailed', 'Notebook Cell Failed'), sound: Sound.taskFailed, settingsKey: 'audioCues.notebookCellFailed' }); static diffLineInserted = AudioCue.register({ name: localize('audioCues.diffLineInserted', 'Diff Line Inserted'), sound: Sound.diffLineInserted, settingsKey: 'audioCues.diffLineInserted' }); static diffLineDeleted = AudioCue.register({ name: localize('audioCues.diffLineDeleted', 'Diff Line Deleted'), sound: Sound.diffLineDeleted, settingsKey: 'audioCues.diffLineDeleted' }); constructor(sound, name, settingsKey) { this.sound = sound; this.name = name; this.settingsKey = settingsKey; } }