Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use web audio api for bell sound #1200

Merged
merged 7 commits into from
Feb 21, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions src/SoundManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/

import { ITerminal, ISoundManager } from './Types';

// 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 DEFAULT_BELL_SOUND = '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 _audioContext: AudioContext;

constructor(
private _terminal: ITerminal
) {
}

public playBellSound(): void {
const audioContextCtor: typeof AudioContext = (<any>window).AudioContext || (<any>window).webkitAudioContext;
if (!this._audioContext && audioContextCtor) {
this._audioContext = new audioContextCtor();
}

if (this._audioContext) {
const bellAudioSource = this._audioContext.createBufferSource();
const context = this._audioContext;
this._audioContext.decodeAudioData(this.base64ToArrayBuffer(this.removeMimeType(this._terminal.options.bellSound)), (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;
const 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];
}
}
35 changes: 8 additions & 27 deletions src/Terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import * as Browser from './shared/utils/Browser';
import * as Strings from './Strings';
import { MouseHelper } from './utils/MouseHelper';
import { CHARSETS } from './Charsets';
import { BELL_SOUND } from './utils/Sounds';
import { DEFAULT_BELL_SOUND, SoundManager } from './SoundManager';
import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager';
import { MouseZoneManager } from './input/MouseZoneManager';
import { AccessibilityManager } from './AccessibilityManager';
Expand Down Expand Up @@ -100,7 +100,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = {
termName: 'xterm',
cursorBlink: false,
cursorStyle: 'block',
bellSound: BELL_SOUND,
bellSound: DEFAULT_BELL_SOUND,
bellStyle: 'none',
enableBold: true,
fontFamily: 'courier-new, courier, monospace',
Expand Down Expand Up @@ -142,7 +142,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 = <any>Browser;
Expand Down Expand Up @@ -224,6 +224,7 @@ 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;
Expand Down Expand Up @@ -328,6 +329,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);
Expand Down Expand Up @@ -493,8 +495,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
}
break;
case 'tabStopWidth': this.buffers.setupTabStops(); break;
case 'bellSound':
case 'bellStyle': this.syncBellSound(); break;
}
// Inform renderer of changes
if (this.renderer) {
Expand Down Expand Up @@ -693,9 +693,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);

Expand Down Expand Up @@ -1894,7 +1891,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
*/
public bell(): void {
this.emit('bell');
if (this.soundBell()) this.bellAudioElement.play();
if (this.soundBell()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be a good idea to move all the sound-related code (including base64ToArrayBuffer) into it's own file now that the DOM interactions are gone.

this.soundManager.playBellSound();
}

if (this.visualBell()) {
this.element.classList.add('visual-bell-active');
Expand Down Expand Up @@ -2215,24 +2214,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
// return this.options.bellStyle === 'sound' ||
// 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);
}
}
}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,3 +349,7 @@ export interface IBrowser {
isIphone: boolean;
isMSWindows: boolean;
}

export interface ISoundManager {
playBellSound(): void;
}
10 changes: 0 additions & 10 deletions src/utils/Sounds.ts

This file was deleted.