audio.libx.js
Version:
Comprehensive audio library with progressive streaming, recording capabilities, real-time processing, and intelligent caching for web applications
298 lines • 12.1 kB
JavaScript
import { PermissionError } from './types.js';
export class PermissionManager {
constructor() {
this._currentStream = null;
this._permissionState = {
status: 'unknown',
isSupported: false,
};
this._initialize();
}
static getInstance() {
if (!PermissionManager._instance) {
PermissionManager._instance = new PermissionManager();
}
return PermissionManager._instance;
}
_initialize() {
this._permissionState.isSupported = this._isGetUserMediaSupported();
if (this._permissionState.isSupported) {
this._checkInitialPermissionState();
}
}
_isGetUserMediaSupported() {
return !!(navigator.mediaDevices?.getUserMedia ||
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia);
}
async _checkInitialPermissionState() {
if ('permissions' in navigator && 'query' in navigator.permissions) {
try {
const permission = await navigator.permissions.query({ name: 'microphone' });
this._permissionState.status = permission.state;
permission.addEventListener('change', () => {
this._permissionState.status = permission.state;
});
}
catch (error) {
this._permissionState.status = 'prompt';
}
}
else {
this._permissionState.status = 'prompt';
}
}
async requestPermission(constraints = {}) {
if (!this._permissionState.isSupported) {
const error = new PermissionError('getUserMedia is not supported in this browser');
return {
granted: false,
state: { ...this._permissionState, error },
error,
};
}
try {
const mediaConstraints = this._buildMediaConstraints(constraints);
const stream = await this._getUserMedia(mediaConstraints);
this._permissionState.status = 'granted';
this._currentStream = stream;
return {
granted: true,
state: { ...this._permissionState },
stream,
};
}
catch (error) {
const permissionError = this._handlePermissionError(error);
this._permissionState.error = permissionError;
return {
granted: false,
state: { ...this._permissionState },
error: permissionError,
};
}
}
_buildMediaConstraints(options) {
const audioConstraints = {
echoCancellation: options.echoCancellation ?? true,
noiseSuppression: options.noiseSuppression ?? true,
autoGainControl: options.autoGainControl ?? true,
};
if (options.deviceId) {
audioConstraints.deviceId = options.deviceId;
}
if (options.sampleRate) {
audioConstraints.sampleRate = options.sampleRate;
}
if (options.channelCount) {
audioConstraints.channelCount = options.channelCount;
}
if (this._isSafari()) {
const safariConstraints = {
echoCancellation: audioConstraints.echoCancellation,
noiseSuppression: audioConstraints.noiseSuppression,
autoGainControl: audioConstraints.autoGainControl,
};
if (audioConstraints.deviceId) {
safariConstraints.deviceId = audioConstraints.deviceId;
}
return {
audio: safariConstraints,
};
}
return { audio: audioConstraints };
}
async _getUserMedia(constraints) {
if (navigator.mediaDevices?.getUserMedia) {
return await navigator.mediaDevices.getUserMedia(constraints);
}
const getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
if (!getUserMedia) {
throw new Error('getUserMedia is not supported');
}
return new Promise((resolve, reject) => {
getUserMedia.call(navigator, constraints, resolve, reject);
});
}
_handlePermissionError(error) {
let message = 'Unknown permission error';
let status = 'unknown';
if (error.name === 'NotAllowedError' || error.name === 'PermissionDeniedError') {
message = 'Microphone access was denied by the user. Please enable microphone permissions in your browser settings.';
status = 'denied';
}
else if (error.name === 'NotFoundError' || error.name === 'DevicesNotFoundError') {
message = 'No microphone device was found. Please check that a microphone is connected and try again.';
status = 'denied';
}
else if (error.name === 'NotSupportedError') {
message = 'Microphone access is not supported in this browser or context (HTTPS required).';
status = 'denied';
}
else if (error.name === 'NotReadableError' || error.name === 'TrackStartError') {
message = 'The microphone is already in use by another application. Please close other applications using the microphone and try again.';
status = 'denied';
}
else if (error.name === 'OverconstrainedError' || error.name === 'ConstraintNotSatisfiedError') {
message = 'The requested audio constraints could not be satisfied. Please try with different settings.';
status = 'denied';
}
else if (error.name === 'SecurityError') {
message = 'Microphone access blocked due to security restrictions. Please ensure you are using HTTPS and try again.';
status = 'denied';
}
else if (error.name === 'AbortError') {
message = 'Permission request was cancelled or interrupted.';
status = 'prompt';
}
this._permissionState.status = status;
return new PermissionError(message, error);
}
async checkPermissionState() {
if (!this._permissionState.isSupported) {
return { ...this._permissionState };
}
if ('permissions' in navigator && 'query' in navigator.permissions) {
try {
const permission = await navigator.permissions.query({ name: 'microphone' });
this._permissionState.status = permission.state;
}
catch (error) {
}
}
return { ...this._permissionState };
}
stopCurrentStream() {
if (this._currentStream) {
this._currentStream.getTracks().forEach((track) => {
track.stop();
});
this._currentStream = null;
}
}
getCurrentStream() {
return this._currentStream;
}
getPermissionErrorGuidance(error) {
const guidance = [];
if (error.message.includes('denied')) {
guidance.push("Click the microphone icon in your browser's address bar");
guidance.push('Select "Always allow" for microphone access');
guidance.push('Refresh the page and try again');
}
else if (error.message.includes('not found')) {
guidance.push('Check that your microphone is properly connected');
guidance.push('Try selecting a different microphone in your browser settings');
guidance.push('Restart your browser if the issue persists');
}
else if (error.message.includes('in use')) {
guidance.push('Close other applications that might be using your microphone');
guidance.push('Check for other browser tabs using the microphone');
guidance.push('Restart your browser if necessary');
}
else if (error.message.includes('HTTPS')) {
guidance.push('Microphone access requires a secure connection (HTTPS)');
guidance.push('Try accessing the site via HTTPS');
guidance.push('Contact the site administrator if the issue persists');
}
else {
guidance.push('Try refreshing the page');
guidance.push("Check your browser's microphone permissions");
guidance.push('Try using a different browser if the issue persists');
}
return guidance;
}
async testMicrophoneAccess(constraints = {}) {
const result = await this.requestPermission(constraints);
if (result.stream) {
result.stream.getTracks().forEach((track) => track.stop());
}
return {
...result,
stream: undefined,
};
}
async getAudioInputDevices() {
if (!navigator.mediaDevices?.enumerateDevices) {
throw new PermissionError('Device enumeration is not supported');
}
try {
const devices = await navigator.mediaDevices.enumerateDevices();
return devices.filter((device) => device.kind === 'audioinput');
}
catch (error) {
throw new PermissionError('Failed to enumerate audio devices', error);
}
}
_isSafari() {
const userAgent = navigator.userAgent.toLowerCase();
return userAgent.includes('safari') && !userAgent.includes('chrome');
}
getBrowserSpecificGuidance() {
const userAgent = navigator.userAgent.toLowerCase();
if (userAgent.includes('chrome')) {
return [
'Chrome: Click the microphone icon in the address bar',
'Select "Always allow" for this site',
'Check chrome://settings/content/microphone for global settings',
];
}
else if (userAgent.includes('firefox')) {
return [
'Firefox: Click the shield icon or microphone icon in the address bar',
'Select "Allow" and check "Remember this decision"',
'Check about:preferences#privacy for global settings',
];
}
else if (this._isSafari()) {
return [
'Safari: Check Safari > Preferences > Websites > Microphone',
'Set this website to "Allow"',
'Ensure microphone access is enabled in System Preferences > Security & Privacy',
];
}
else if (userAgent.includes('edge')) {
return [
'Edge: Click the microphone icon in the address bar',
'Select "Always allow on this site"',
'Check edge://settings/content/microphone for global settings',
];
}
return [
"Check your browser's microphone permissions for this site",
'Look for a microphone icon in the address bar',
'Ensure microphone access is enabled in your browser settings',
];
}
getCapabilities() {
return {
isSupported: this._permissionState.isSupported,
hasPermissionsAPI: 'permissions' in navigator && 'query' in navigator.permissions,
hasEnumerateDevices: !!navigator.mediaDevices?.enumerateDevices,
currentStatus: this._permissionState.status,
browser: this._getBrowserInfo(),
};
}
_getBrowserInfo() {
const userAgent = navigator.userAgent.toLowerCase();
if (userAgent.includes('chrome'))
return 'chrome';
if (userAgent.includes('firefox'))
return 'firefox';
if (this._isSafari())
return 'safari';
if (userAgent.includes('edge'))
return 'edge';
return 'unknown';
}
dispose() {
this.stopCurrentStream();
this._permissionState = {
status: 'unknown',
isSupported: false,
};
}
}
//# sourceMappingURL=PermissionManager.js.map