From b75f52734d182f546cfba074d6ee25ec4f8d5706 Mon Sep 17 00:00:00 2001 From: Bruno Ribeito Date: Mon, 8 Jan 2018 20:53:26 +0000 Subject: [PATCH] Use Web audio API --- src/Interfaces.ts | 3 +++ src/SoundManager.ts | 61 +++++++++++++++++++++++++++++++++++++++++++++ src/Terminal.ts | 38 +++++++--------------------- src/utils/Sounds.ts | 10 -------- 4 files changed, 73 insertions(+), 39 deletions(-) create mode 100644 src/SoundManager.ts delete mode 100644 src/utils/Sounds.ts diff --git a/src/Interfaces.ts b/src/Interfaces.ts index f90cb8c0..e9a347d8 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -352,3 +352,6 @@ export interface ITheme { brightCyan?: string; brightWhite?: string; } +export interface ISoundManager { + playBellSound(): void; +} diff --git a/src/SoundManager.ts b/src/SoundManager.ts new file mode 100644 index 00000000..b95a253e --- /dev/null +++ b/src/SoundManager.ts @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ITerminal, ISoundManager } from './Interfaces'; + +// Source: https://freesound.org/people/altemark/sounds/45759/ +// This sound is released under the Creative Commons Attribution 3.0 Unported +// (CC BY 3.0) license. It was created by 'altemark'. No modifications have been +// made, apart from the conversion to base64. +export const DefaultBellSound = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQBAADpAFgCwAMlBZoG/wdmCcoKRAypDQ8PbRDBEQQTOxRtFYcWlBePGIUZXhoiG88bcBz7HHIdzh0WHlMeZx51HmkeUx4WHs8dah0AHXwc3hs9G4saxRnyGBIYGBcQFv8U4RPAEoYRQBACD70NWwwHC6gJOwjWBloF7gOBAhABkf8b/qv8R/ve+Xf4Ife79W/0JfPZ8Z/wde9N7ijtE+wU6xvqM+lb6H7nw+YX5mrlxuQz5Mzje+Ma49fioeKD4nXiYeJy4pHitOL04j/jn+MN5IPkFOWs5U3mDefM55/ogOl36m7rdOyE7abuyu8D8Unyj/Pg9D/2qfcb+Yn6/vuK/Qj/lAAlAg=='; + +export class SoundManager implements ISoundManager { + + private _terminal: ITerminal; + private _audioContext: AudioContext; + + constructor(_terminal: ITerminal) { + this._terminal = _terminal; + } + + public playBellSound(): void { + if (!this._audioContext) { + this._audioContext = new (window.AudioContext || window.webkitAudioContext)(); + } + + if (this._audioContext) { + let bellAudioSource = this._audioContext.createBufferSource(); + let context = this._audioContext; + this._audioContext.decodeAudioData(this.base64ToArrayBuffer(this.removeMimeType(this._terminal.options.bellSound)), function (buffer) { + bellAudioSource.buffer = buffer; + bellAudioSource.connect(context.destination); + bellAudioSource.start(0); + }); + } + else { + console.warn('Sorry, but the Web Audio API is not supported by your browser. Please, consider upgrading to the latest version'); + } + } + + private base64ToArrayBuffer(base64: string): ArrayBuffer { + const binaryString = window.atob(base64); + const len = binaryString.length; + let bytes = new Uint8Array(len); + + for (let i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + + return bytes.buffer; + } + + private removeMimeType(dataURI: string): string { + // Split the input to get the mime-type and the data itself + const SplitURI = dataURI.split(','); + + // Return only the data + return SplitURI[1]; + } +} diff --git a/src/Terminal.ts b/src/Terminal.ts index f95cc0ea..f12f42b7 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -40,7 +40,7 @@ import { MouseHelper } from './utils/MouseHelper'; import { CHARSETS } from './Charsets'; import { CustomKeyEventHandler, Charset, LinkMatcherHandler, LinkMatcherValidationCallback, CharData, LineData } from './Types'; import { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal, ILinkMatcherOptions, IViewport, ICompositionHelper, ITheme, ILinkifier } from './Interfaces'; -import { BellSound } from './utils/Sounds'; +import { DefaultBellSound, SoundManager } from './SoundManager'; import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager'; import { IMouseZoneManager } from './input/Interfaces'; import { MouseZoneManager } from './input/MouseZoneManager'; @@ -70,7 +70,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = { termName: 'xterm', cursorBlink: false, cursorStyle: 'block', - bellSound: BellSound, + bellSound: DefaultBellSound, bellStyle: 'none', enableBold: true, fontFamily: 'courier-new, courier, monospace', @@ -105,7 +105,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT private helperContainer: HTMLElement; private compositionView: HTMLElement; private charSizeStyleElement: HTMLStyleElement; - private bellAudioElement: HTMLAudioElement; + private visualBellTimer: number; public browser: IBrowser = Browser; @@ -187,9 +187,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT private userScrolling: boolean; private inputHandler: InputHandler; + public soundManager: SoundManager; private parser: Parser; public renderer: IRenderer; - public selectionManager: SelectionManager; public linkifier: ILinkifier; public buffers: BufferSet; public buffer: Buffer; @@ -198,6 +198,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT public charMeasure: CharMeasure; private _mouseZoneManager: IMouseZoneManager; public mouseHelper: MouseHelper; + public selectionManager: SelectionManager; public cols: number; public rows: number; @@ -290,6 +291,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.selectionManager = this.selectionManager || null; this.linkifier = this.linkifier || new Linkifier(this); this._mouseZoneManager = this._mouseZoneManager || null; + this.soundManager = this.soundManager || new SoundManager(this); // Create the terminal's buffers and set the current buffer this.buffers = new BufferSet(this); @@ -424,8 +426,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.viewport.syncScrollArea(); break; case 'tabStopWidth': this.buffers.setupTabStops(); break; - case 'bellSound': - case 'bellStyle': this.syncBellSound(); break; } // Inform renderer of changes if (this.renderer) { @@ -622,9 +622,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.helperContainer.appendChild(this.charSizeStyleElement); this.charMeasure = new CharMeasure(document, this.helperContainer); - // Preload audio, this relied on helperContainer - this.syncBellSound(); - // Performance: Add viewport and helper elements from the fragment this.element.appendChild(fragment); @@ -1797,7 +1794,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT */ public bell(): void { this.emit('bell'); - if (this.soundBell()) this.bellAudioElement.play(); + if (this.soundBell()) { + this.soundManager.playBellSound(); + } if (this.visualBell()) { this.element.classList.add('visual-bell-active'); @@ -2117,25 +2116,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.options.bellStyle === 'both'; } - private syncBellSound(): void { - // Don't update anything if the terminal has not been opened yet - if (!this.element) { - return; - } - - if (this.soundBell() && this.bellAudioElement) { - this.bellAudioElement.setAttribute('src', this.options.bellSound); - } else if (this.soundBell()) { - this.bellAudioElement = document.createElement('audio'); - this.bellAudioElement.setAttribute('preload', 'auto'); - this.bellAudioElement.setAttribute('src', this.options.bellSound); - this.helperContainer.appendChild(this.bellAudioElement); - } else if (this.bellAudioElement) { - this.helperContainer.removeChild(this.bellAudioElement); - } - } -} - /** * Helpers */ diff --git a/src/utils/Sounds.ts b/src/utils/Sounds.ts deleted file mode 100644 index 03f36f42..00000000 --- a/src/utils/Sounds.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -// Source: https://freesound.org/people/altemark/sounds/45759/ -// This sound is released under the Creative Commons Attribution 3.0 Unported -// (CC BY 3.0) license. It was created by 'altemark'. No modifications have been -// made, apart from the conversion to base64. -export const BellSound = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQBAADpAFgCwAMlBZoG/wdmCcoKRAypDQ8PbRDBEQQTOxRtFYcWlBePGIUZXhoiG88bcBz7HHIdzh0WHlMeZx51HmkeUx4WHs8dah0AHXwc3hs9G4saxRnyGBIYGBcQFv8U4RPAEoYRQBACD70NWwwHC6gJOwjWBloF7gOBAhABkf8b/qv8R/ve+Xf4Ife79W/0JfPZ8Z/wde9N7ijtE+wU6xvqM+lb6H7nw+YX5mrlxuQz5Mzje+Ma49fioeKD4nXiYeJy4pHitOL04j/jn+MN5IPkFOWs5U3mDefM55/ogOl36m7rdOyE7abuyu8D8Unyj/Pg9D/2qfcb+Yn6/vuK/Qj/lAAlAg==';