diff --git a/packages/hms-video-store/src/device-manager/DeviceManager.ts b/packages/hms-video-store/src/device-manager/DeviceManager.ts index 41ef7972b7..fd7bfcf9f3 100644 --- a/packages/hms-video-store/src/device-manager/DeviceManager.ts +++ b/packages/hms-video-store/src/device-manager/DeviceManager.ts @@ -7,6 +7,7 @@ import { DeviceMap, HMSDeviceChangeEvent, SelectedDevices } from '../interfaces' import { getAudioDeviceCategory, HMSAudioDeviceCategory, isIOS } from '../internal'; import { HMSAudioTrackSettingsBuilder, HMSVideoTrackSettingsBuilder } from '../media/settings'; import { HMSLocalAudioTrack, HMSLocalTrack, HMSLocalVideoTrack } from '../media/tracks'; +import { HMSTrackExceptionTrackType } from '../media/tracks/HMSTrackExceptionTrackType'; import { Store } from '../sdk/store'; import HMSLogger from '../utils/logger'; import { debounce } from '../utils/timer-utils'; @@ -325,7 +326,7 @@ export class DeviceManager implements HMSDeviceManager { this.eventBus.analytics.publish( AnalyticsEventFactory.deviceChange({ selection: { audioInput: newSelection }, - error: ErrorFactory.TracksErrors.SelectedDeviceMissing('audio'), + error: ErrorFactory.TracksErrors.SelectedDeviceMissing(HMSTrackExceptionTrackType.AUDIO), devices: this.getDevices(), type: 'audioInput', }), @@ -383,7 +384,7 @@ export class DeviceManager implements HMSDeviceManager { this.eventBus.analytics.publish( AnalyticsEventFactory.deviceChange({ selection: { videoInput: newSelection }, - error: ErrorFactory.TracksErrors.SelectedDeviceMissing('video'), + error: ErrorFactory.TracksErrors.SelectedDeviceMissing(HMSTrackExceptionTrackType.VIDEO), devices: this.getDevices(), type: 'video', }), diff --git a/packages/hms-video-store/src/error/ErrorFactory.ts b/packages/hms-video-store/src/error/ErrorFactory.ts index 7345d26749..c4b258c4a8 100644 --- a/packages/hms-video-store/src/error/ErrorFactory.ts +++ b/packages/hms-video-store/src/error/ErrorFactory.ts @@ -8,6 +8,8 @@ import { ErrorCodes } from './ErrorCodes'; import { HMSAction } from './HMSAction'; import { HMSException } from './HMSException'; +import { HMSTrackException } from './HMSTrackException'; +import { HMSTrackExceptionTrackType } from '../media/tracks/HMSTrackExceptionTrackType'; import { HMSSignalMethod } from '../signal/jsonrpc/models'; const terminalActions: (HMSSignalMethod | HMSAction)[] = [ @@ -98,52 +100,56 @@ export const ErrorFactory = { TracksErrors: { GenericTrack(action: HMSAction, description = '') { - return new HMSException( + return new HMSTrackException( ErrorCodes.TracksErrors.GENERIC_TRACK, 'GenericTrack', action, `[TRACK]: ${description}`, `[TRACK]: ${description}`, + HMSTrackExceptionTrackType.AUDIO_VIDEO, ); }, - CantAccessCaptureDevice(action: HMSAction, deviceInfo: string, description = '') { - return new HMSException( + return new HMSTrackException( ErrorCodes.TracksErrors.CANT_ACCESS_CAPTURE_DEVICE, 'CantAccessCaptureDevice', action, `User denied permission to access capture device - ${deviceInfo}`, description, + deviceInfo as HMSTrackExceptionTrackType, ); }, DeviceNotAvailable(action: HMSAction, deviceInfo: string, description = '') { - return new HMSException( + return new HMSTrackException( ErrorCodes.TracksErrors.DEVICE_NOT_AVAILABLE, 'DeviceNotAvailable', action, `[TRACK]: Capture device is no longer available - ${deviceInfo}`, description, + deviceInfo as HMSTrackExceptionTrackType, ); }, DeviceInUse(action: HMSAction, deviceInfo: string, description = '') { - return new HMSException( + return new HMSTrackException( ErrorCodes.TracksErrors.DEVICE_IN_USE, 'DeviceInUse', action, `[TRACK]: Capture device is in use by another application - ${deviceInfo}`, description, + deviceInfo as HMSTrackExceptionTrackType, ); }, DeviceLostMidway(action: HMSAction, deviceInfo: string, description = '') { - return new HMSException( + return new HMSTrackException( ErrorCodes.TracksErrors.DEVICE_LOST_MIDWAY, 'DeviceLostMidway', action, `Lost access to capture device midway - ${deviceInfo}`, description, + deviceInfo as HMSTrackExceptionTrackType, ); }, @@ -152,113 +158,123 @@ export const ErrorFactory = { description = '', message = `There is no media to return. Please select either video or audio or both.`, ) { - return new HMSException( + return new HMSTrackException( ErrorCodes.TracksErrors.NOTHING_TO_RETURN, 'NothingToReturn', action, message, description, + HMSTrackExceptionTrackType.AUDIO_VIDEO, ); }, InvalidVideoSettings(action: HMSAction, description = '') { - return new HMSException( + return new HMSTrackException( ErrorCodes.TracksErrors.INVALID_VIDEO_SETTINGS, 'InvalidVideoSettings', action, `Cannot enable simulcast when no video settings are provided`, description, + HMSTrackExceptionTrackType.VIDEO, ); }, AutoplayBlocked(action: HMSAction, description = '') { - return new HMSException( + return new HMSTrackException( ErrorCodes.TracksErrors.AUTOPLAY_ERROR, 'AutoplayBlocked', action, "Autoplay blocked because the user didn't interact with the document first", description, + HMSTrackExceptionTrackType.AUDIO, ); }, CodecChangeNotPermitted(action: HMSAction, description = '') { - return new HMSException( + return new HMSTrackException( ErrorCodes.TracksErrors.CODEC_CHANGE_NOT_PERMITTED, 'CodecChangeNotPermitted', action, `Codec can't be changed mid call.`, description, + HMSTrackExceptionTrackType.AUDIO_VIDEO, ); }, OverConstrained(action: HMSAction, deviceInfo: string, description = '') { - return new HMSException( + return new HMSTrackException( ErrorCodes.TracksErrors.OVER_CONSTRAINED, 'OverConstrained', action, `[TRACK]: Requested constraints cannot be satisfied with the device hardware - ${deviceInfo}`, description, + deviceInfo as HMSTrackExceptionTrackType, ); }, NoAudioDetected(action: HMSAction, description = 'Please check the mic or use another audio input') { - return new HMSException( + return new HMSTrackException( ErrorCodes.TracksErrors.NO_AUDIO_DETECTED, 'NoAudioDetected', action, 'No audio input detected from microphone', description, + HMSTrackExceptionTrackType.AUDIO, ); }, SystemDeniedPermission(action: HMSAction, deviceInfo: string, description = '') { - return new HMSException( + return new HMSTrackException( ErrorCodes.TracksErrors.SYSTEM_DENIED_PERMISSION, 'SystemDeniedPermission', action, `Operating System denied permission to access capture device - ${deviceInfo}`, description, + deviceInfo as HMSTrackExceptionTrackType, ); }, CurrentTabNotShared() { - return new HMSException( + return new HMSTrackException( ErrorCodes.TracksErrors.CURRENT_TAB_NOT_SHARED, 'CurrentTabNotShared', HMSAction.TRACK, 'The app requires you to share the current tab', 'You must screen share the current tab in order to proceed', + HMSTrackExceptionTrackType.SCREEN, ); }, AudioPlaybackError(description: string) { - return new HMSException( + return new HMSTrackException( ErrorCodes.TracksErrors.AUDIO_PLAYBACK_ERROR, 'Audio playback error', HMSAction.TRACK, description, description, + HMSTrackExceptionTrackType.AUDIO, ); }, SelectedDeviceMissing(deviceType: string) { - return new HMSException( + return new HMSTrackException( ErrorCodes.TracksErrors.SELECTED_DEVICE_MISSING, 'SelectedDeviceMissing', HMSAction.TRACK, `Could not detect selected ${deviceType} device`, `Please check connection to the ${deviceType} device`, - false, + deviceType as HMSTrackExceptionTrackType, ); }, NoDataInTrack(description: string) { - return new HMSException( + return new HMSTrackException( ErrorCodes.TracksErrors.NO_DATA, 'Track does not have any data', HMSAction.TRACK, description, 'This could possibily due to another application taking priority over the access to camera or microphone or due to an incoming call', + HMSTrackExceptionTrackType.AUDIO_VIDEO, ); }, }, diff --git a/packages/hms-video-store/src/error/HMSException.ts b/packages/hms-video-store/src/error/HMSException.ts index 03c6afa687..88513d117d 100644 --- a/packages/hms-video-store/src/error/HMSException.ts +++ b/packages/hms-video-store/src/error/HMSException.ts @@ -38,13 +38,13 @@ export class HMSException extends Error implements IAnalyticsPropertiesProvider toString() { return `{ - code: ${this.code}; - name: ${this.name}; - action: ${this.action}; - message: ${this.message}; - description: ${this.description}; - isTerminal: ${this.isTerminal}; - nativeError: ${this.nativeError?.message}; - }`; + code: ${this.code}; + name: ${this.name}; + action: ${this.action}; + message: ${this.message}; + description: ${this.description}; + isTerminal: ${this.isTerminal}; + nativeError: ${this.nativeError?.message}; + }`; } } diff --git a/packages/hms-video-store/src/error/HMSTrackException.ts b/packages/hms-video-store/src/error/HMSTrackException.ts new file mode 100644 index 0000000000..1a8f9c666b --- /dev/null +++ b/packages/hms-video-store/src/error/HMSTrackException.ts @@ -0,0 +1,37 @@ +import { HMSAction } from './HMSAction'; +import { HMSException } from './HMSException'; +import { HMSTrackExceptionTrackType } from '../media/tracks/HMSTrackExceptionTrackType'; +import { HMSSignalMethod } from '../signal/jsonrpc/models'; + +export class HMSTrackException extends HMSException { + constructor( + public readonly code: number, + public name: string, + action: HMSAction | HMSSignalMethod, + public message: string, + public description: string, + public trackType: HMSTrackExceptionTrackType, + ) { + super(code, name, action, message, description, false); + } + + toAnalyticsProperties() { + return { + ...super.toAnalyticsProperties(), + track_type: this.trackType, + }; + } + + toString() { + return `{ + code: ${this.code}; + name: ${this.name}; + action: ${this.action}; + message: ${this.message}; + description: ${this.description}; + isTerminal: ${this.isTerminal}; + nativeError: ${this.nativeError?.message}; + trackType: ${this.trackType}; + }`; + } +} diff --git a/packages/hms-video-store/src/error/utils.ts b/packages/hms-video-store/src/error/utils.ts index 42dea262ce..36c5efbbbf 100644 --- a/packages/hms-video-store/src/error/utils.ts +++ b/packages/hms-video-store/src/error/utils.ts @@ -1,7 +1,7 @@ import adapter from 'webrtc-adapter'; import { ErrorFactory } from './ErrorFactory'; import { HMSAction } from './HMSAction'; -import { HMSException } from './HMSException'; +import { HMSTrackException } from './HMSTrackException'; export enum HMSGetMediaActions { UNKNOWN = 'unknown(video or audio)', @@ -13,13 +13,15 @@ export enum HMSGetMediaActions { function getDefaultError(error: string, deviceInfo: string) { const message = error.toLowerCase(); + let exception = ErrorFactory.TracksErrors.GenericTrack(HMSAction.TRACK, error); + if (message.includes('device not found')) { - return ErrorFactory.TracksErrors.DeviceNotAvailable(HMSAction.TRACK, deviceInfo, error); + exception = ErrorFactory.TracksErrors.DeviceNotAvailable(HMSAction.TRACK, deviceInfo, error); } else if (message.includes('permission denied')) { - return ErrorFactory.TracksErrors.CantAccessCaptureDevice(HMSAction.TRACK, deviceInfo, error); - } else { - return ErrorFactory.TracksErrors.GenericTrack(HMSAction.TRACK, error); + exception = ErrorFactory.TracksErrors.CantAccessCaptureDevice(HMSAction.TRACK, deviceInfo, error); } + + return exception; } /** @@ -31,7 +33,7 @@ function getDefaultError(error: string, deviceInfo: string) { * System blocked - NotAllowedError - Permission denied by system */ // eslint-disable-next-line complexity -function convertMediaErrorToHMSException(err: Error, deviceInfo = ''): HMSException { +function convertMediaErrorToHMSException(err: Error, deviceInfo = ''): HMSTrackException { /** * Note: Adapter detects all chromium browsers as 'chrome' */ @@ -70,7 +72,7 @@ function convertMediaErrorToHMSException(err: Error, deviceInfo = ''): HMSExcept } } -export function BuildGetMediaError(err: Error, deviceInfo: string): HMSException { +export function BuildGetMediaError(err: Error, deviceInfo: string): HMSTrackException { const exception = convertMediaErrorToHMSException(err, deviceInfo); exception.addNativeError(err); return exception; diff --git a/packages/hms-video-store/src/index.ts b/packages/hms-video-store/src/index.ts index e26beb60ef..8d44ae20d5 100644 --- a/packages/hms-video-store/src/index.ts +++ b/packages/hms-video-store/src/index.ts @@ -78,3 +78,5 @@ export type { } from './internal'; export * from './diagnostics'; export { DomainCategory } from './analytics/AnalyticsEventDomains'; + +export { HMSTrackExceptionTrackType } from './media/tracks/HMSTrackExceptionTrackType'; diff --git a/packages/hms-video-store/src/internal.ts b/packages/hms-video-store/src/internal.ts index 22ad33d6c9..dda7d2ba85 100644 --- a/packages/hms-video-store/src/internal.ts +++ b/packages/hms-video-store/src/internal.ts @@ -11,6 +11,7 @@ export * from './utils/media'; export * from './utils/device-error'; export * from './utils/support'; export * from './error/HMSException'; +export * from './error/HMSTrackException'; export * from './interfaces'; export * from './rtc-stats'; export * from './plugins'; diff --git a/packages/hms-video-store/src/media/tracks/HMSTrackExceptionTrackType.ts b/packages/hms-video-store/src/media/tracks/HMSTrackExceptionTrackType.ts new file mode 100644 index 0000000000..5bca9518b2 --- /dev/null +++ b/packages/hms-video-store/src/media/tracks/HMSTrackExceptionTrackType.ts @@ -0,0 +1,6 @@ +export enum HMSTrackExceptionTrackType { + AUDIO = 'audio', + VIDEO = 'video', + AUDIO_VIDEO = 'audio, video', + SCREEN = 'screen', +} diff --git a/packages/hms-video-store/src/reactive-store/adapter.ts b/packages/hms-video-store/src/reactive-store/adapter.ts index c765a4c8b3..b6c4ea7b29 100644 --- a/packages/hms-video-store/src/reactive-store/adapter.ts +++ b/packages/hms-video-store/src/reactive-store/adapter.ts @@ -24,6 +24,7 @@ import { HMSRoom, HMSScreenVideoTrack, HMSTrack, + HMSTrackException, HMSTrackFacingMode, HMSVideoTrack, } from '../schema'; @@ -203,8 +204,9 @@ export class SDKToHMS { }; } - static convertException(sdkException: sdkTypes.HMSException): HMSException { - return { + static convertException(sdkException: sdkTypes.HMSException): HMSException | HMSTrackException { + const isTrackException = 'trackType' in sdkException; + const exp = { code: sdkException.code, action: sdkException.action, name: sdkException.name, @@ -213,7 +215,12 @@ export class SDKToHMS { isTerminal: sdkException.isTerminal, nativeError: sdkException.nativeError, timestamp: new Date(), - }; + } as HMSException; + if (isTrackException) { + (exp as HMSTrackException).trackType = (sdkException as sdkTypes.HMSTrackException)?.trackType; + return exp as HMSTrackException; + } + return exp; } static convertDeviceChangeUpdate(sdkDeviceChangeEvent: sdkTypes.HMSDeviceChangeEvent): HMSDeviceChangeEvent { diff --git a/packages/hms-video-store/src/schema/error.ts b/packages/hms-video-store/src/schema/error.ts index 7a19a4a7a0..ca9dcb4572 100644 --- a/packages/hms-video-store/src/schema/error.ts +++ b/packages/hms-video-store/src/schema/error.ts @@ -1,3 +1,5 @@ +import { HMSTrackExceptionTrackType } from '../media/tracks/HMSTrackExceptionTrackType'; + /** * any mid call error notification will be in this format */ @@ -11,3 +13,7 @@ export interface HMSException { timestamp: Date; nativeError?: Error; } + +export interface HMSTrackException extends HMSException { + trackType: HMSTrackExceptionTrackType; +} diff --git a/packages/hms-video-store/src/sdk/LocalTrackManager.ts b/packages/hms-video-store/src/sdk/LocalTrackManager.ts index 64c0251a7e..70103137db 100644 --- a/packages/hms-video-store/src/sdk/LocalTrackManager.ts +++ b/packages/hms-video-store/src/sdk/LocalTrackManager.ts @@ -7,7 +7,7 @@ import { ErrorCodes } from '../error/ErrorCodes'; import { ErrorFactory } from '../error/ErrorFactory'; import { HMSAction } from '../error/HMSAction'; import { HMSException } from '../error/HMSException'; -import { BuildGetMediaError, HMSGetMediaActions } from '../error/utils'; +import { BuildGetMediaError } from '../error/utils'; import { EventBus } from '../events/EventBus'; import { HMSAudioCodec, @@ -27,6 +27,7 @@ import { HMSVideoTrackSettingsBuilder, } from '../media/settings'; import { HMSLocalStream } from '../media/streams/HMSLocalStream'; +import { HMSTrackExceptionTrackType } from '../media/tracks/HMSTrackExceptionTrackType'; import ITransportObserver from '../transport/ITransportObserver'; import HMSLogger from '../utils/logger'; import { HMSAudioContextHandler } from '../utils/media'; @@ -236,7 +237,7 @@ export class LocalTrackManager { } } catch (err) { HMSLogger.w(this.TAG, 'error in getting screenshare - ', err); - const error = BuildGetMediaError(err as Error, HMSGetMediaActions.SCREEN); + const error = BuildGetMediaError(err as Error, HMSTrackExceptionTrackType.SCREEN); this.eventBus.analytics.publish( AnalyticsEventFactory.publish({ error: error as Error, @@ -478,17 +479,17 @@ export class LocalTrackManager { } } - getErrorType(videoError: boolean, audioError: boolean): HMSGetMediaActions { + getErrorType(videoError: boolean, audioError: boolean): HMSTrackExceptionTrackType { if (videoError && audioError) { - return HMSGetMediaActions.AV; + return HMSTrackExceptionTrackType.AUDIO_VIDEO; } if (videoError) { - return HMSGetMediaActions.VIDEO; + return HMSTrackExceptionTrackType.VIDEO; } if (audioError) { - return HMSGetMediaActions.AUDIO; + return HMSTrackExceptionTrackType.AUDIO; } - return HMSGetMediaActions.UNKNOWN; + return HMSTrackExceptionTrackType.AUDIO_VIDEO; } private getEmptyTracks(fetchTrackOptions: IFetchAVTrackOptions) { diff --git a/packages/hms-video-store/src/utils/media.ts b/packages/hms-video-store/src/utils/media.ts index 025fc7a4bb..05d60e443f 100644 --- a/packages/hms-video-store/src/utils/media.ts +++ b/packages/hms-video-store/src/utils/media.ts @@ -1,12 +1,13 @@ import HMSLogger from './logger'; -import { BuildGetMediaError, HMSGetMediaActions } from '../error/utils'; +import { BuildGetMediaError } from '../error/utils'; +import { HMSTrackExceptionTrackType } from '../media/tracks/HMSTrackExceptionTrackType'; export async function getLocalStream(constraints: MediaStreamConstraints): Promise { try { const stream = await navigator.mediaDevices.getUserMedia(constraints); return stream; } catch (err) { - throw BuildGetMediaError(err as Error, HMSGetMediaActions.AV); + throw BuildGetMediaError(err as Error, HMSTrackExceptionTrackType.AUDIO_VIDEO); } } @@ -16,7 +17,7 @@ export async function getLocalScreen(constraints: MediaStreamConstraints['video' const stream = await navigator.mediaDevices.getDisplayMedia({ video: constraints, audio: false }); return stream; } catch (err) { - throw BuildGetMediaError(err as Error, HMSGetMediaActions.SCREEN); + throw BuildGetMediaError(err as Error, HMSTrackExceptionTrackType.SCREEN); } } @@ -37,7 +38,7 @@ export async function getLocalDevices(): Promise { devices.forEach(device => deviceGroups[device.kind].push(device)); return deviceGroups; } catch (err) { - throw BuildGetMediaError(err as Error, HMSGetMediaActions.AV); + throw BuildGetMediaError(err as Error, HMSTrackExceptionTrackType.AUDIO_VIDEO); } } diff --git a/packages/hms-video-store/src/utils/track.ts b/packages/hms-video-store/src/utils/track.ts index fea545dd08..6af65ce30f 100644 --- a/packages/hms-video-store/src/utils/track.ts +++ b/packages/hms-video-store/src/utils/track.ts @@ -1,5 +1,6 @@ -import { BuildGetMediaError, HMSGetMediaActions } from '../error/utils'; +import { BuildGetMediaError } from '../error/utils'; import { HMSAudioTrackSettings, HMSVideoTrackSettings } from '../media/settings'; +import { HMSTrackExceptionTrackType } from '../media/tracks/HMSTrackExceptionTrackType'; export async function getAudioTrack(settings: HMSAudioTrackSettings): Promise { try { @@ -8,7 +9,7 @@ export async function getAudioTrack(settings: HMSAudioTrackSettings): Promise { ) { return; } - const errorMessage = error?.message; - const hasAudio = errorMessage.includes('audio'); - const hasVideo = errorMessage.includes('video'); - const hasScreen = errorMessage.includes('screen'); - if (hasAudio && hasVideo) { + + const errorTrackExceptionType = (error as HMSTrackException)?.trackType; + const hasAudio = errorTrackExceptionType === HMSTrackExceptionTrackType.AUDIO; + const hasVideo = errorTrackExceptionType === HMSTrackExceptionTrackType.VIDEO; + const hasAudioVideo = errorTrackExceptionType === HMSTrackExceptionTrackType.AUDIO_VIDEO; + const hasScreen = errorTrackExceptionType === HMSTrackExceptionTrackType.SCREEN; + if (hasAudioVideo) { setDeviceType('camera and microphone'); } else if (hasAudio) { setDeviceType('microphone');