UNPKG

@100mslive/hms-video-store

Version:

@100mslive Core SDK which abstracts the complexities of webRTC while providing a reactive store for data management with a unidirectional data flow

359 lines (316 loc) • 10.8 kB
import { PublishAnalyticPayload, SubscribeAnalyticPayload } from './stats/interfaces'; import { AdditionalAnalyticsProperties } from './AdditionalAnalyticsProperties'; import AnalyticsEvent from './AnalyticsEvent'; import { AnalyticsEventLevel } from './AnalyticsEventLevel'; import { IAnalyticsPropertiesProvider } from './IAnalyticsPropertiesProvider'; import { HMSException } from '../error/HMSException'; import { DeviceMap, SelectedDevices } from '../interfaces'; import { HMSTrackSettings } from '../media/settings/HMSTrackSettings'; import { HMSRemoteVideoTrack } from '../media/tracks/HMSRemoteVideoTrack'; export default class AnalyticsEventFactory { private static KEY_REQUESTED_AT = 'requested_at'; private static KEY_RESPONDED_AT = 'responded_at'; static connect( error?: Error, additionalProperties?: AdditionalAnalyticsProperties, requestedAt: Date = new Date(), respondedAt: Date = new Date(), endpoint?: string, ) { const name = this.eventNameFor('connect', error === undefined); const level = error ? AnalyticsEventLevel.ERROR : AnalyticsEventLevel.INFO; const properties = this.getPropertiesWithError( { ...additionalProperties, [this.KEY_REQUESTED_AT]: requestedAt?.getTime(), [this.KEY_RESPONDED_AT]: respondedAt?.getTime(), endpoint, }, error, ); return new AnalyticsEvent({ name, level, properties }); } static disconnect(error?: Error, additionalProperties?: AdditionalAnalyticsProperties) { const name = 'disconnected'; const level = error ? AnalyticsEventLevel.ERROR : AnalyticsEventLevel.INFO; const properties = this.getPropertiesWithError(additionalProperties, error); return new AnalyticsEvent({ name, level, properties }); } static preview({ error, ...props }: { error?: Error; time?: number; init_response_time?: number; ws_connect_time?: number; on_policy_change_time?: number; local_audio_track_time?: number; local_video_track_time?: number; }) { const name = this.eventNameFor('preview', error === undefined); const level = error ? AnalyticsEventLevel.ERROR : AnalyticsEventLevel.INFO; const properties = this.getPropertiesWithError(props, error); return new AnalyticsEvent({ name, level, properties }); } static join({ error, ...props }: { error?: Error; is_preview_called?: boolean; start?: Date; end?: Date; time?: number; init_response_time?: number; ws_connect_time?: number; on_policy_change_time?: number; local_audio_track_time?: number; local_video_track_time?: number; retries_join?: number; }) { const name = this.eventNameFor('join', error === undefined); const level = error ? AnalyticsEventLevel.ERROR : AnalyticsEventLevel.INFO; const properties = this.getPropertiesWithError({ ...props, is_preview_called: !!props.is_preview_called }, error); return new AnalyticsEvent({ name, level, properties }); } static publish({ devices, settings, error }: { devices?: DeviceMap; settings?: HMSTrackSettings; error?: Error }) { const name = this.eventNameFor('publish', error === undefined); const level = error ? AnalyticsEventLevel.ERROR : AnalyticsEventLevel.INFO; const properties = this.getPropertiesWithError( { devices, audio: settings?.audio, video: settings?.video, }, error, ); return new AnalyticsEvent({ name, level, properties, }); } static hlsPlayerError(error: HMSException) { return new AnalyticsEvent({ name: 'hlsPlayerError', level: AnalyticsEventLevel.ERROR, properties: this.getErrorProperties(error), }); } static subscribeFail(error: Error) { const name = this.eventNameFor('subscribe', false); const level = AnalyticsEventLevel.ERROR; const properties = this.getErrorProperties(error); return new AnalyticsEvent({ name, level, properties }); } static leave() { return new AnalyticsEvent({ name: 'leave', level: AnalyticsEventLevel.INFO }); } static autoplayError() { return new AnalyticsEvent({ name: 'autoplayError', level: AnalyticsEventLevel.ERROR }); } static audioPlaybackError(error: HMSException) { return new AnalyticsEvent({ name: 'audioPlaybackError', level: AnalyticsEventLevel.ERROR, properties: this.getErrorProperties(error), }); } static audioRecovered(message: string) { return new AnalyticsEvent({ name: 'audioRecovered', level: AnalyticsEventLevel.INFO, properties: { message, }, }); } static permissionChange(type: 'audio' | 'video', status: PermissionState) { return new AnalyticsEvent({ name: 'permissionChanged', level: AnalyticsEventLevel.INFO, properties: { message: `Perrmission for ${type} changed to ${status}`, }, }); } static deviceChange({ isUserSelection, selection, type, devices, error, }: { isUserSelection?: boolean; selection: Partial<SelectedDevices>; type?: 'change' | 'list' | 'audioInput' | 'audioOutput' | 'video'; devices: DeviceMap; error?: Error; }) { const name = this.eventNameFor(error ? 'publish' : `device.${type}`, error === undefined); const level = error ? AnalyticsEventLevel.ERROR : AnalyticsEventLevel.INFO; const properties = this.getPropertiesWithError({ selection, devices, isUserSelection }, error); return new AnalyticsEvent({ name, level, properties, }); } static performance(stats: IAnalyticsPropertiesProvider) { const name = 'perf.stats'; const level = AnalyticsEventLevel.INFO; const properties = stats.toAnalyticsProperties(); return new AnalyticsEvent({ name, level, properties }); } static rtcStats(stats: IAnalyticsPropertiesProvider) { const name = 'rtc.stats'; const level = AnalyticsEventLevel.INFO; const properties = stats.toAnalyticsProperties(); return new AnalyticsEvent({ name, level, properties }); } static rtcStatsFailed(error: HMSException) { const name = 'rtc.stats.failed'; const level = AnalyticsEventLevel.ERROR; return new AnalyticsEvent({ name, level, properties: this.getErrorProperties(error) }); } /** * TODO: remove once everything is switched to server side degradation, this * event can be handled on server side as well. */ static degradationStats(track: HMSRemoteVideoTrack, isDegraded: boolean) { const name = 'video.degradation.stats'; const level = AnalyticsEventLevel.INFO; let properties: any = { degradedAt: track.degradedAt, trackId: track.trackId, }; if (!isDegraded && track.degradedAt instanceof Date) { // not degraded => restored const restoredAt = new Date(); const duration = restoredAt.valueOf() - track.degradedAt.valueOf(); properties = { ...properties, duration, restoredAt }; } return new AnalyticsEvent({ name, level, properties }); } static audioDetectionFail(error: Error, device?: MediaDeviceInfo): AnalyticsEvent { const properties = this.getPropertiesWithError({ device }, error); const level = AnalyticsEventLevel.ERROR; const name = 'audiopresence.failed'; return new AnalyticsEvent({ name, level, properties }); } static previewNetworkQuality(properties: { downLink?: string; score?: number; error?: string }) { return new AnalyticsEvent({ name: 'perf.networkquality.preview', level: properties.error ? AnalyticsEventLevel.ERROR : AnalyticsEventLevel.INFO, properties, }); } static publishStats(properties: PublishAnalyticPayload) { return new AnalyticsEvent({ name: 'publisher.stats', level: AnalyticsEventLevel.INFO, properties, }); } static subscribeStats(properties: SubscribeAnalyticPayload) { return new AnalyticsEvent({ name: 'subscriber.stats', level: AnalyticsEventLevel.INFO, properties, }); } static getKrispUsage(duration: number) { return new AnalyticsEvent({ name: 'krisp.usage', level: AnalyticsEventLevel.INFO, properties: { duration }, }); } static krispStart() { return new AnalyticsEvent({ name: 'krisp.start', level: AnalyticsEventLevel.INFO, }); } static krispStop() { return new AnalyticsEvent({ name: 'krisp.stop', level: AnalyticsEventLevel.INFO, }); } static interruption({ started, type, reason, deviceInfo, trackInfo, }: { started: boolean; type: string; reason: string; deviceInfo: Partial<MediaDeviceInfo>; trackInfo?: { label: string; enabled: boolean; muted: boolean; readyState: string; settings: MediaTrackSettings; }; }) { return new AnalyticsEvent({ name: `${started ? 'interruption.start' : 'interruption.stop'}`, level: AnalyticsEventLevel.INFO, properties: { reason, type, pageHidden: document.visibilityState === 'hidden', ...deviceInfo, trackInfo, }, }); } static mediaConstraints({ requestedConstraints, appliedConstraints, trackSettings, }: { requestedConstraints: MediaStreamConstraints; appliedConstraints: { video?: MediaTrackConstraints; audio?: MediaTrackConstraints }; trackSettings: { video?: MediaTrackSettings; audio?: MediaTrackSettings }; }) { return new AnalyticsEvent({ name: 'media.constraints', level: AnalyticsEventLevel.INFO, properties: { requested_constraints: requestedConstraints, applied_constraints: appliedConstraints, track_settings: trackSettings, webgpu_supported: typeof navigator !== 'undefined' && 'gpu' in navigator, media_stream_track_processor_supported: typeof window !== 'undefined' && 'MediaStreamTrackProcessor' in window, }, }); } private static eventNameFor(name: string, ok: boolean) { const suffix = ok ? 'success' : 'failed'; return `${name}.${suffix}`; } private static getPropertiesWithError(initialProperties: any, error?: Error) { const errorProperties = this.getErrorProperties(error); initialProperties = { ...errorProperties, ...initialProperties }; return initialProperties; } private static getErrorProperties(error?: Error): Record<string, any> { if (error) { return error instanceof HMSException ? error.toAnalyticsProperties() : { error_name: error.name, error_message: error.message, error_description: error.cause, }; } else { return {}; } } }