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

fix: audio playback error #3419

Closed
wants to merge 10 commits into from
43 changes: 31 additions & 12 deletions packages/hms-video-store/src/audio-sink-manager/AudioSinkManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { HMSRemoteAudioTrack } from '../media/tracks';
import { HMSRemotePeer } from '../sdk/models/peer';
import { Store } from '../sdk/store';
import HMSLogger from '../utils/logger';
import { sleep } from '../utils/timer-utils';

/**
* Following are the errors thrown when autoplay is blocked in different browsers
Expand Down Expand Up @@ -72,23 +71,27 @@ export class AudioSinkManager {
*/
async unblockAutoplay() {
if (this.autoPausedTracks.size > 0) {
this.unpauseAudioTracks();
HMSLogger.e(this.TAG, 'Unpausing audio tracks');
await this.unpauseAudioTracks();
} else {
HMSLogger.e(this.TAG, 'No audio tracks to unpause');
}
}

init(elementId?: string) {
if (this.state.initialized || this.audioSink) {
HMSLogger.e(this.TAG, 'Audio sink already initialized');
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's change this to debug instead of error?

return;
}
this.state.initialized = true;
const audioSink = document.createElement('div');
audioSink.id = `HMS-SDK-audio-sink-${uuid()}`;
const userElement = elementId && document.getElementById(elementId);
const audioSinkParent = userElement || document.body;
audioSinkParent.append(audioSink);

this.audioSink = audioSink;
HMSLogger.d(this.TAG, 'audio sink created', this.audioSink);
this.state.initialized = true;
HMSLogger.e(this.TAG, 'audio sink created', this.audioSink);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This can also be a debug log

}

cleanup() {
Expand All @@ -104,7 +107,7 @@ export class AudioSinkManager {
this.state = { ...INITIAL_STATE };
}

private handleAudioPaused = async (event: any) => {
private handleAudioPaused = (event: any) => {
// this means the audio paused because of external factors(headset removal, incoming phone call)
HMSLogger.d(this.TAG, 'Audio Paused', event.target.id);
const audioTrack = this.store.getTrackById(event.target.id);
Expand Down Expand Up @@ -134,37 +137,46 @@ export class AudioSinkManager {
audioEl.onerror = async () => {
HMSLogger.e(this.TAG, 'error on audio element', audioEl.error);
const ex = ErrorFactory.TracksErrors.AudioPlaybackError(
`Audio playback error for track - ${track.trackId} code - ${audioEl?.error?.code}`,
`Audio playback error for track - ${track.trackId} code - ${audioEl?.error?.code} ${audioEl.error} ${track}`,
);
this.eventBus.analytics.publish(AnalyticsEventFactory.audioPlaybackError(ex));
if (audioEl?.error?.code === MediaError.MEDIA_ERR_DECODE) {
// try to wait for main execution to complete first
this.removeAudioElement(audioEl, track);
await sleep(500);
await this.handleTrackAdd({ track, peer, callListener: false });
if (!this.state.autoplayFailed) {
this.eventBus.analytics.publish(
AnalyticsEventFactory.audioRecovered('Audio recovered after media decode error'),
);
}
} else {
HMSLogger.e(this.TAG, 'Audio playback error', audioEl.error);
}
};
track.setAudioElement(audioEl);
await track.setVolume(this.volume);
HMSLogger.d(this.TAG, 'Audio track added', `${track}`);
this.init(); // call to create sink element if not already created
this.audioSink?.append(audioEl);
this.outputDevice && (await track.setOutputDevice(this.outputDevice));
if (this.outputDevice) {
await track.setOutputDevice(this.outputDevice);
} else {
HMSLogger.e(this.TAG, 'No output device found', `${track}`);
}
audioEl.srcObject = new MediaStream([track.nativeTrack]);
callListener && this.listener?.onTrackUpdate(HMSTrackUpdate.TRACK_ADDED, track, peer);
if (callListener) {
this.listener?.onTrackUpdate(HMSTrackUpdate.TRACK_ADDED, track, peer);
} else {
HMSLogger.e(this.TAG, 'No listener found', `${track}`);
}
await this.handleAutoplayError(track);
};

private handleAutoplayError = async (track: HMSRemoteAudioTrack) => {
/**
* if it's not known whether autoplay will succeed, wait for it to be known
*/
if (this.state.autoplayFailed === undefined) {
if (!this.state.autoplayFailed) {
if (!this.state.autoplayCheckPromise) {
// it's the first track, try to play it, that'll tell us whether autoplay is allowed
this.state.autoplayCheckPromise = new Promise<void>(resolve => {
Expand All @@ -173,23 +185,28 @@ export class AudioSinkManager {
}
// and wait for the result to be known
await this.state.autoplayCheckPromise;
} else {
HMSLogger.e(this.TAG, 'Autoplay check promise already known', `${track}`);
}
/**
* Don't play the track if autoplay failed, add to paused list
*/
if (this.state.autoplayFailed) {
this.autoPausedTracks.add(track);
HMSLogger.e(this.TAG, 'Autoplay failed', `${track}`);
return;
} else {
HMSLogger.e(this.TAG, 'Autoplay succeeded', `${track}`);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This can be removed

}
await this.playAudioFor(track);
};

private handleAudioDeviceChange = (event: HMSDeviceChangeEvent) => {
private handleAudioDeviceChange = async (event: HMSDeviceChangeEvent) => {
// if there is no selection that means this is an init request. No need to do anything
if (event.isUserSelection || event.error || !event.selection || event.type === 'video') {
return;
}
this.unpauseAudioTracks();
await this.unpauseAudioTracks();
};

/**
Expand Down Expand Up @@ -253,6 +270,8 @@ export class AudioSinkManager {
audioEl.srcObject = null;
audioEl.remove();
track.setAudioElement(null);
} else {
HMSLogger.e(this.TAG, 'Audio element not found', `${track}`);
}
};
}
Loading