Skip to content

Commit

Permalink
get safari-friendly audioContext instance (#213)
Browse files Browse the repository at this point in the history
* get safari-friendly audioContext instance wherever AudioContext is created, add playResultAudio method to BaseAudioPlayer

* Pull getAudioContext into less public class, use getSampleAudio with test for webkit
  • Loading branch information
glharper authored Jul 23, 2020
1 parent cd44f89 commit e609680
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 19 deletions.
12 changes: 1 addition & 11 deletions src/common.browser/MicAudioSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,17 +302,7 @@ export class MicAudioSource implements IAudioSource {
return;
}

// https://developer.mozilla.org/en-US/docs/Web/API/AudioContext
if (typeof (AudioContext) === "undefined") {
throw new Error("Browser does not support Web Audio API (AudioContext is not available).");
}

// Browsers without sampleRate constraint support can't connect nodes with different sample rates
if (navigator.mediaDevices.getSupportedConstraints().sampleRate) {
this.privContext = new AudioContext({ sampleRate: MicAudioSource.AUDIOFORMAT.samplesPerSec });
} else {
this.privContext = new AudioContext();
}
this.privContext = AudioStreamFormatImpl.getAudioContext(MicAudioSource.AUDIOFORMAT.samplesPerSec);
}

private async destroyAudioContext(): Promise<void> {
Expand Down
25 changes: 25 additions & 0 deletions src/sdk/Audio/AudioStreamFormat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,31 @@ export class AudioStreamFormatImpl extends AudioStreamFormat {
return new AudioStreamFormatImpl();
}

/**
* Creates an audio context appropriate to current browser
* @member AudioStreamFormatImpl.getAudioContext
* @function
* @public
* @returns {AudioContext} An audio context instance
*/
public static getAudioContext(sampleRate?: number): AudioContext {
// Workaround for Speech SDK bug in Safari.
const AudioContext = (window as any).AudioContext // our preferred impl
|| (window as any).webkitAudioContext // fallback, mostly when on Safari
|| false; // could not find.

// https://developer.mozilla.org/en-US/docs/Web/API/AudioContext
if (!!AudioContext) {
if (sampleRate !== undefined && navigator.mediaDevices.getSupportedConstraints().sampleRate) {
return new AudioContext({ sampleRate });
} else {
return new AudioContext();
}
} else {
throw new Error("Browser does not support Web Audio API (AudioContext is not available).");
}
}

/**
* Closes the configuration object.
* @member AudioStreamFormatImpl.prototype.close
Expand Down
38 changes: 30 additions & 8 deletions src/sdk/Audio/BaseAudioPlayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@ export class BaseAudioPlayer {
/**
* Creates and initializes an instance of this class.
* @constructor
* @param {AudioStreamFormat} audioFormat audio stream format recognized by the player.
*/
public constructor(audioFormat: AudioStreamFormat) {
public constructor(audioFormat?: AudioStreamFormat) {
if (audioFormat === undefined) {
audioFormat = AudioStreamFormat.getDefaultInputFormat();
}
this.init(audioFormat);
}

Expand All @@ -33,13 +37,18 @@ export class BaseAudioPlayer {
* @param newAudioData audio data to be played.
*/
public playAudioSample(newAudioData: ArrayBuffer): void {
this.ensureInitializedContext();
const audioData = this.formatAudioData(newAudioData);
const newSamplesData = new Float32Array(this.samples.length + audioData.length);
newSamplesData.set(this.samples, 0);
newSamplesData.set(audioData, this.samples.length);
this.samples = newSamplesData;
if (!!(window as any).webkitAudioContext) {
this.playAudio(newAudioData);
} else {
this.ensureInitializedContext();
const audioData = this.formatAudioData(newAudioData);
const newSamplesData = new Float32Array(this.samples.length + audioData.length);
newSamplesData.set(this.samples, 0);
newSamplesData.set(audioData, this.samples.length);
this.samples = newSamplesData;
}
}

/**
* stops audio and clears the buffers
*/
Expand Down Expand Up @@ -77,7 +86,7 @@ export class BaseAudioPlayer {

private createAudioContext(): void {
// new ((window as any).AudioContext || (window as any).webkitAudioContext)();
this.audioContext = new AudioContext();
this.audioContext = AudioStreamFormatImpl.getAudioContext();

// TODO: Various examples shows this gain node, it does not seem to be needed unless we plan
// to control the volume, not likely
Expand Down Expand Up @@ -142,4 +151,17 @@ export class BaseAudioPlayer {
// Clear the samples for the next pushed data.
this.samples = new Float32Array();
}

private playAudio(audioData: ArrayBuffer): void {
if (this.audioContext === null) {
this.createAudioContext();
}
const source: AudioBufferSourceNode = this.audioContext.createBufferSource();
const destination: AudioDestinationNode = this.audioContext.destination;
this.audioContext.decodeAudioData(audioData, (newBuffer: AudioBuffer): void => {
source.buffer = newBuffer;
source.connect(destination);
source.start(0);
});
}
}

0 comments on commit e609680

Please sign in to comment.