@techsee/techsee-media-service 7.0.0 → 7.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/MediaPublisher.d.ts.map +1 -1
- package/lib/MediaPublisher.js.map +1 -1
- package/lib/MediaServiceBase.d.ts.map +1 -1
- package/lib/MediaServiceBase.js.map +1 -1
- package/lib/MediaSession/SessionOpentok.d.ts.map +1 -1
- package/lib/MediaSession/SessionTurn.d.ts.map +1 -1
- package/lib/MediaSubscriber.d.ts.map +1 -1
- package/lib/MediaSubscriber.js.map +1 -1
- package/lib/MultiParty/DetectWebRtcService.d.ts.map +1 -1
- package/lib/MultiParty/MediaCapabilitiesService.d.ts.map +1 -1
- package/lib/MultiParty/OpentokMultiPartyService.d.ts.map +1 -1
- package/lib/TechseeMediaStream.d.ts.map +1 -1
- package/lib/TechseeMediaStream.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/MediaSession/SessionOpentok.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAC,MAAM,yBAAyB,CAAC;AAEjD,OAAO,EACH,aAAa,EACb,sBAAsB,EACtB,yBAAyB,EACzB,kBAAkB,EAClB,gBAAgB,EACnB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAC,iBAAiB,EAAoB,MAAM,mBAAmB,CAAC;AAEvE,OAAO,EAAC,iBAAiB,EAAE,gBAAgB,EAAkB,MAAM,oBAAoB,CAAC;AAKxF,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,MAAM;QACZ,EAAE,EAAE,GAAG,CAAC;KACX;CACJ;AAED,UAAU,YAAY;IAClB,YAAY,EAAE,MAAM,CAAC;CACxB;AAED,aAAK,WAAW,GAAG,GAAG,CAAC;AAEvB,aAAK,YAAY,GAAG,GAAG,CAAC;AA8BxB,UAAU,cAAc;IACpB,WAAW,EAAE,GAAG,CAAC;IACjB,WAAW,EAAE,GAAG,CAAC;CACpB;AAED,UAAU,gBAAiB,SAAQ,iBAAiB;IAChD,WAAW,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC;IACtC,UAAU,EAAE,YAAY,CAAC;IACzB,SAAS,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;IACjC,UAAU,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC;CACtC;AAGD,qBAAa,cAAe,SAAQ,gBAAgB,CAAC,gBAAgB,CAAE,YAAW,aAAa;IAC3F,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAM;IAEnC,OAAO,CAAC,iBAAiB,CAAa;IAEtC,OAAO,CAAC,QAAQ,CAA8B;IAE9C,IAAI,EAAE,IAAI,MAAM,CAEf;IAED,OAAO,KAAK,SAAS,GAEpB;gBAEW,aAAa,EAAE,kBAAkB,GAAG,yBAAyB,EAAE,aAAa,EAAE,sBAAsB;IAkBhH,iBAAiB,IAAI,IAAI;IAMzB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAkDxB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IA2B3B,sBAAsB,CAAC,UAAU,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAYpE,oBAAoB,CAAC,UAAU,EAAE,iBAAiB,EAAE,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAK5F,mBAAmB,CAAC,WAAW,EAAE,QAAQ,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAItE,mBAAmB,CAAC,UAAU,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA0B5E,OAAO,CAAC,wBAAwB;IAoFhC,OAAO,CAAC,0BAA0B;IAalC,OAAO,CAAC,oBAAoB;IAyB5B,OAAO,CAAC,sBAAsB;IAyB9B,OAAO,CAAC,qBAAqB;IAc7B,OAAO,CAAC,yBAAyB;IAmCjC,OAAO,CAAC,oBAAoB;IAmC5B,OAAO,CAAC,gCAAgC;IAexC,OAAO,CAAC,qBAAqB;IA+C7B,OAAO,CAAC,iCAAiC;IAsBzC,OAAO,CAAC,kBAAkB;CAK7B","file":"SessionOpentok.d.ts","sourcesContent":["import {Nullable} from '@techsee/techsee-common';\n\nimport {\n IMediaSession,\n ISessionStreamsManager,\n MediaSessionConfiguration,\n MediaSessionParams,\n RemoteTrackStats\n} from '../MediaContracts';\n\nimport {SessionClientRole, SessionClientType} from '../MediaConstants';\nimport {throwableGuard} from '@techsee/techsee-common/lib/core/guards';\nimport {IMediaSessionPeer, MediaSessionBase, recordingEvents} from './MediaSessionBase';\nimport {getMediaTracer} from '../MediaUtils/MediaTracer';\n\nconst trace = getMediaTracer('OpentokSession');\n\ndeclare global {\n interface Window {\n OT: any;\n }\n}\n\ninterface OTConnection {\n connectionId: string;\n}\n\ntype OTPublisher = any;\n\ntype OTSubscriber = any;\n\ninterface IOTSession {\n sessionId: string;\n\n connection: Nullable<OTConnection>;\n\n isConnected(): boolean;\n\n connect(token: string, cb: any): void;\n\n disconnect(): void;\n\n publish(publisher: OTPublisher, cb: any): void;\n\n unpublish(publisher: OTPublisher): void;\n\n subscribe(stream: any, container: any, uiProps: any, doneCb: any): OTSubscriber;\n\n unsubscribe(subscriber: OTSubscriber): void;\n\n getSubscribersForStream(stream: any): OTSubscriber[];\n\n off(): void;\n\n on(param: string, cb: any): void;\n\n once(param: string, cb: any): void;\n}\n\ninterface OTStreamSource {\n videoSource: any;\n audioSource: any;\n}\n\ninterface OTPeerConnection extends IMediaSessionPeer {\n localStream: Nullable<OTStreamSource>;\n connection: OTConnection;\n publisher: Nullable<OTPublisher>;\n subscriber: Nullable<OTSubscriber>;\n}\n\n//Currently this version supports subscribing only. Publishing will be implemented as needed.\nexport class OpentokSession extends MediaSessionBase<OTPeerConnection> implements IMediaSession {\n private readonly _credentials: any;\n\n private _lastStreamEvents: any[] = [];\n\n private _session: Nullable<IOTSession> = null;\n\n get id(): string {\n return this._session && this._session.sessionId ? this._session.sessionId : '';\n }\n\n private get ownPeerId(): string {\n return this._session && this._session.connection ? this._session.connection.connectionId : '';\n }\n\n constructor(sessionParams: MediaSessionParams & MediaSessionConfiguration, streamFactory: ISessionStreamsManager) {\n super(sessionParams, streamFactory);\n\n this._credentials = sessionParams.credentials;\n\n this.connect = this.connect.bind(this);\n this.disconnect = this.disconnect.bind(this);\n this.onMediaStreamDestroyed = this.onMediaStreamDestroyed.bind(this);\n this.onMediaStreamRenewed = this.onMediaStreamRenewed.bind(this);\n\n this.createStreamForPublishing = this.createStreamForPublishing.bind(this);\n this.createMediaPublisher = this.createMediaPublisher.bind(this);\n this.connectionCreatedHandler = this.connectionCreatedHandler.bind(this);\n this.connectionDestroyedHandler = this.connectionDestroyedHandler.bind(this);\n this.streamCreatedHandler = this.streamCreatedHandler.bind(this);\n this.streamDestroyedHandler = this.streamDestroyedHandler.bind(this);\n }\n\n sessionDisconnect(): void {\n if (this._session) {\n this._session.disconnect();\n }\n }\n\n connect(): Promise<void> {\n trace.info('Connect to session', this._credentials);\n\n if (this._session) {\n trace.error('Session already exists');\n\n return Promise.reject('Cannot connect to session while another session is active');\n }\n\n return new Promise((resolve, reject) => {\n const handleError = (error: any): void => {\n this._session && this._session!.off();\n this._session = null;\n trace.error('handleConnect error:', error);\n reject(error);\n };\n\n const {apiKey, sessionId} = this._sessionParams.credentials;\n\n this._session = (window as any).OTSESSION = window.OT.initSession(apiKey, sessionId);\n this._lastStreamEvents = [];\n\n this._session!.on('connectionCreated', this.connectionCreatedHandler);\n this._session!.on('connectionDestroyed', this.connectionDestroyedHandler);\n this._session!.on('streamCreated', this.streamCreatedHandler);\n this._session!.on('streamDestroyed', this.streamDestroyedHandler);\n this._session!.on('archiveStarted', (event: any) => {\n trace.info('archiveStarted event', event);\n this.emitEvent(recordingEvents.RECORD_STARTED, event);\n });\n\n this._session!.on('archiveStopped', (event: any) => {\n trace.info('archiveStopped event', event);\n this.emitEvent(recordingEvents.RECORD_STOPPED, event);\n });\n\n const connectHandler = (error: any): void => {\n if (error) {\n handleError(error);\n\n return;\n }\n\n resolve();\n };\n\n this._session!.connect(this._credentials.token, connectHandler);\n });\n }\n\n disconnect(): Promise<void> {\n return new Promise((resolve) => {\n trace.info('Disconnecting from session');\n const currentSession = this._session;\n\n this._session = null;\n\n this._peerConnections.forEach((peerConnection) => {\n this.destroyPeerConnection(peerConnection);\n });\n\n if (!currentSession) {\n trace.info('No active session - resolving');\n resolve();\n\n return;\n }\n currentSession.off();\n currentSession.disconnect();\n this._lastStreamEvents = [];\n\n trace.info('Disconnection from session completed');\n\n resolve();\n });\n }\n\n onMediaStreamDestroyed(clientRole: SessionClientRole): Promise<void> {\n const promises: Promise<void>[] = [];\n\n this._peerConnections.forEach((peerConnection) => {\n if (peerConnection.peerRole === clientRole) {\n promises.push(this.destroyPublisherOnPeerConnection(peerConnection));\n }\n });\n\n return Promise.all(promises).then(() => undefined);\n }\n\n onMediaStreamRenewed(clientRole: SessionClientRole, mediaStream: MediaStream): Promise<void> {\n //TODO - Alex: need to implement adding new tracks to peer connection and forcing negotiation (in OT API)\n return Promise.resolve();\n }\n\n replaceStreamTracks(mediaStream: Nullable<MediaStream>): Promise<void> {\n return Promise.reject(new Error('Not implemented'));\n }\n\n getRemoteTrackStats(mediaTrack: MediaStreamTrack): Promise<RemoteTrackStats> {\n return new Promise((resolve: any, reject: any) => {\n const peerConnectionOfTrack = this.getPeerConnectionByMediaTrack(mediaTrack);\n\n if (!peerConnectionOfTrack || !peerConnectionOfTrack.subscriber) {\n reject(new Error('Cannot find peer or subscriber for getting stats'));\n\n return;\n }\n\n //https://tokbox.com/developer/sdks/js/reference/Subscriber.html#getStats\n peerConnectionOfTrack!.subscriber.getStats((error: any, stats: any) => {\n if (error) {\n trace.warn('Error getting stats', error);\n resolve({trackId: mediaTrack.id, trackStats: {}});\n\n return;\n }\n\n resolve({trackId: mediaTrack.id, trackStats: stats});\n });\n });\n }\n\n //#region OpenTok Events Handlers\n\n private connectionCreatedHandler(event: any): void {\n trace.info('connectionCreatedHandler', event);\n const {connection} = event;\n\n if (!this.sessionExistsGuard(false) || !connection) {\n return;\n }\n\n const destinationRole = JSON.parse(connection.data).clientRole;\n\n this.createStreamForPublishing(destinationRole, connection.connectionId === this.ownPeerId)\n .then((streams: OTStreamSource) => {\n const peerConnection: OTPeerConnection = {\n peerId: connection.connectionId,\n peerRole: destinationRole,\n peerType:\n this._sessionParams.clientType === SessionClientType.INITIATOR\n ? SessionClientType.GUEST\n : SessionClientType.INITIATOR,\n connection: connection,\n publisher: null,\n subscriber: null,\n remoteMediaTracks: [],\n localStream: streams,\n connectionTimoutPrt: null\n };\n\n if (connection.connectionId !== this.ownPeerId || (!streams.videoSource && !streams.audioSource)) {\n trace.info('Peer connection without publisher was created', peerConnection);\n\n return peerConnection;\n }\n\n return this.createMediaPublisher(streams).then((publisher: OTPublisher) => {\n peerConnection.publisher = publisher;\n trace.info('Peer connection with publisher was created', peerConnection);\n\n return peerConnection;\n });\n })\n .then((peerConnection: OTPeerConnection) => {\n this._peerConnections.set(peerConnection.peerId, peerConnection);\n\n if (connection.connectionId === this.ownPeerId) {\n trace.info('Before subscribe the event streams');\n\n /*\n When publisher connect after others participants,\n make sure the publisher will not loose their stream to subscribe\n */\n return this._lastStreamEvents\n .reduce(\n (promiseChain: any, streamEvent: any) =>\n promiseChain.then(() => this.streamCreatedHandler(streamEvent)),\n Promise.resolve()\n )\n .then(() => {\n if (peerConnection.publisher) {\n return new Promise<void>((resolve, reject) => {\n trace.info('Starting publish to peerConnection.publisher');\n this._session!.publish(peerConnection.publisher, (error: any) => {\n if (error) {\n trace.error(`Publish to peerConnection error ${error}`);\n\n return reject(error);\n }\n\n trace.info('Publish to peerConnection success');\n\n return resolve();\n });\n });\n }\n\n return Promise.resolve();\n });\n }\n })\n .catch((error: any) => {\n //Need to handle fail cases during reliability\n trace.error(`Error handling new peer connection: ${error}`);\n });\n }\n\n private connectionDestroyedHandler(event: any): void {\n trace.info('connectionDestroyedHandler', event);\n const {connection} = event;\n\n if (!this.sessionExistsGuard(false) || !connection) {\n return;\n }\n\n if (this._peerConnections.has(connection.connectionId)) {\n this.destroyPeerConnection(this._peerConnections.get(connection.connectionId)!);\n }\n }\n\n private streamCreatedHandler(event: any): void {\n trace.info('streamCreatedHandler', event.stream);\n const {stream} = event;\n const {connection} = stream || {connection: null};\n\n if (!this.sessionExistsGuard(false) || !stream || connection.connectionId === this.ownPeerId) {\n return;\n }\n\n if (!this._peerConnections.has(connection.connectionId)) {\n trace.warn('Unexpected case, connection should exist on this stage');\n this._lastStreamEvents.push(event);\n\n return;\n }\n\n const peerConnection = this._peerConnections.get(connection.connectionId)!;\n\n trace.info('Subscribing the stream');\n\n this.createMediaSubscriber(stream).then((subscriber: OTSubscriber) => {\n peerConnection.subscriber = subscriber;\n });\n }\n\n private streamDestroyedHandler(event: any): void {\n trace.info('streamDestroyedHandler', event);\n const {stream} = event;\n\n if (!this.sessionExistsGuard(false) || !stream) {\n return;\n }\n\n const peerConnection = this._peerConnections.get(stream.connection.connectionId);\n\n if (!peerConnection) {\n trace.warn('Peer connection for remote stream not found');\n\n return;\n }\n\n this.destroySubscriberOnPeerConnection(peerConnection).catch(() =>\n trace.warn('Error destroying subscriber on stream destroy')\n );\n }\n\n //#endregion\n\n //#region PeerConnection\n\n private destroyPeerConnection(peerConnection: OTPeerConnection): void {\n this._peerConnections.delete(peerConnection.peerId);\n this.destroyPublisherOnPeerConnection(peerConnection).catch(() =>\n trace.warn('Error destroying publisher on peer destroy')\n );\n this.destroySubscriberOnPeerConnection(peerConnection).catch(() =>\n trace.warn('Error destroying subscriber on peer destroy')\n );\n }\n\n //#endregion\n\n //#region Publisher\n\n private createStreamForPublishing(\n destinationRole: SessionClientRole,\n shouldCreateStream: boolean\n ): Promise<OTStreamSource> {\n trace.info('createStreamForPublishing');\n const stream: OTStreamSource = {videoSource: null, audioSource: null};\n\n if (!shouldCreateStream) {\n trace.info(\n `createStreamForPublishing - destinationRole: ${destinationRole} not supposed to create a stream`\n );\n\n return Promise.resolve(stream);\n }\n\n return this._sessionStreamsManager\n .getMediaStreamForRole(destinationRole)\n .then((mediaStream: Nullable<MediaStream>) => {\n if (mediaStream) {\n trace.info('Media stream found for publishing', mediaStream);\n mediaStream.getTracks().forEach((track) => {\n if (track.kind === 'video') {\n stream.videoSource = track;\n } else if (track.kind === 'audio') {\n stream.audioSource = track;\n }\n });\n } else if (this._sessionParams.clientRole === SessionClientRole.USER) {\n trace.warn('No local media was found for publishing to destinationRole:', destinationRole);\n }\n\n return stream;\n });\n }\n\n private createMediaPublisher(stream: OTStreamSource): Promise<OTPublisher> {\n const otParams = {\n videoSource: stream.videoSource ? stream.videoSource : false,\n audioSource: stream.audioSource ? stream.audioSource : false,\n publishAudio: !!stream.audioSource,\n publishVideo: !!stream.videoSource,\n showControls: false,\n insertDefaultUI: false\n };\n\n return new Promise((resolve, reject) => {\n const otPublisher = window.OT.initPublisher(undefined, otParams, (error: any) => {\n if (error) {\n reject(error);\n\n return;\n }\n otPublisher.on('streamDestroyed', (event: any) => {\n trace.info('Publisher stream destroyed', event);\n //We preventing the default, in order to prevent stop the stream by OT.\n //We managing streams by ourselves and if we allow OT to close the stream, it cannot be reused.\n event.preventDefault();\n });\n otPublisher.on('videoElementCreated', (event: any) => {\n event.element.muted = true;\n });\n otPublisher.on('streamCreated', (event: any) => {\n trace.info('publisher stream created', event, otPublisher.stream.hasAudio);\n });\n\n resolve(otPublisher);\n });\n });\n }\n\n private destroyPublisherOnPeerConnection(peerConnection: OTPeerConnection): Promise<void> {\n if (this.sessionExistsGuard(false) && peerConnection.publisher) {\n this._session!.unpublish(peerConnection.publisher);\n }\n\n peerConnection.publisher = null;\n peerConnection.localStream = null;\n\n return Promise.resolve();\n }\n\n //#endregion\n\n //#region Subscriber\n\n private createMediaSubscriber(remoteStream: any): Promise<OTSubscriber> {\n return new Promise((resolve: any, reject: any) => {\n if (!this.sessionExistsGuard(false)) {\n reject('Session not exists');\n\n return;\n }\n\n const subscriber: OTSubscriber = this._session!.subscribe(\n remoteStream,\n null,\n {insertDefaultUI: false},\n (err: any) => {\n if (err) {\n trace.warn('Error creating subscriber', err);\n reject(err);\n\n return;\n }\n\n subscriber.on('videoElementCreated', (event: any) => {\n const {element} = event;\n const peerConnection = this._peerConnections.get(remoteStream.connection.connectionId)!;\n const otMediaStream: MediaStream = element.srcObject;\n\n peerConnection.subscriber = subscriber;\n\n if (!otMediaStream) {\n trace.warn('MediaStream not found in OT video element');\n\n return;\n }\n\n trace.info('Registering remote media tracks', peerConnection.subscriber, otMediaStream);\n otMediaStream.getTracks().forEach((mediaTrack: MediaStreamTrack) => {\n this.addRemoteTrackToPeer(peerConnection, mediaTrack);\n });\n\n this.registerPeersRemoteTracks(peerConnection);\n });\n\n resolve(subscriber);\n }\n );\n });\n }\n\n private destroySubscriberOnPeerConnection(peerConnection: OTPeerConnection): Promise<void> {\n if (this.sessionExistsGuard(false) && peerConnection.subscriber) {\n this._session!.unsubscribe(peerConnection.subscriber);\n }\n\n peerConnection.subscriber = null;\n\n return Promise.resolve();\n\n /**\n REMARK- opentok provide the same media track id for all participates.\n when we unregister the media track we check by the id which is the same for all.\n **/\n // return this.unregisterPeersRemoteMediaTracks(peerConnection)\n // .catch(() => trace.warn('Error removing remote streams from peerConnection'))\n // .then(() => {\n // peerConnection.subscriber = null;\n // });\n }\n\n //#endregion\n\n private sessionExistsGuard(shouldThrow = true): boolean {\n const connected = !!(this._session && this._session.connection && this._session.connection.connectionId);\n\n return throwableGuard(connected, 'There no OpenTok session exists', shouldThrow);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/MediaSession/SessionOpentok.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAC,MAAM,yBAAyB,CAAC;AAEjD,OAAO,EACH,aAAa,EACb,sBAAsB,EACtB,yBAAyB,EACzB,kBAAkB,EAClB,gBAAgB,EACnB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAC,iBAAiB,EAAoB,MAAM,mBAAmB,CAAC;AAEvE,OAAO,EAAC,iBAAiB,EAAE,gBAAgB,EAAkB,MAAM,oBAAoB,CAAC;AAKxF,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,MAAM;QACZ,EAAE,EAAE,GAAG,CAAC;KACX;CACJ;AAED,UAAU,YAAY;IAClB,YAAY,EAAE,MAAM,CAAC;CACxB;AAED,aAAK,WAAW,GAAG,GAAG,CAAC;AAEvB,aAAK,YAAY,GAAG,GAAG,CAAC;AA8BxB,UAAU,cAAc;IACpB,WAAW,EAAE,GAAG,CAAC;IACjB,WAAW,EAAE,GAAG,CAAC;CACpB;AAED,UAAU,gBAAiB,SAAQ,iBAAiB;IAChD,WAAW,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC;IACtC,UAAU,EAAE,YAAY,CAAC;IACzB,SAAS,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;IACjC,UAAU,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC;CACtC;AAGD,qBAAa,cAAe,SAAQ,gBAAgB,CAAC,gBAAgB,CAAE,YAAW,aAAa;IAC3F,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAM;IACnC,OAAO,CAAC,iBAAiB,CAAa;IACtC,OAAO,CAAC,QAAQ,CAA8B;IAE9C,IAAI,EAAE,IAAI,MAAM,CAEf;IAED,OAAO,KAAK,SAAS,GAEpB;gBAEW,aAAa,EAAE,kBAAkB,GAAG,yBAAyB,EAAE,aAAa,EAAE,sBAAsB;IAkBhH,iBAAiB,IAAI,IAAI;IAMzB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAkDxB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IA2B3B,sBAAsB,CAAC,UAAU,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAYpE,oBAAoB,CAAC,UAAU,EAAE,iBAAiB,EAAE,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAK5F,mBAAmB,CAAC,WAAW,EAAE,QAAQ,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAItE,mBAAmB,CAAC,UAAU,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA0B5E,OAAO,CAAC,wBAAwB;IAoFhC,OAAO,CAAC,0BAA0B;IAalC,OAAO,CAAC,oBAAoB;IAyB5B,OAAO,CAAC,sBAAsB;IAyB9B,OAAO,CAAC,qBAAqB;IAc7B,OAAO,CAAC,yBAAyB;IAmCjC,OAAO,CAAC,oBAAoB;IAmC5B,OAAO,CAAC,gCAAgC;IAexC,OAAO,CAAC,qBAAqB;IA+C7B,OAAO,CAAC,iCAAiC;IAsBzC,OAAO,CAAC,kBAAkB;CAK7B","file":"SessionOpentok.d.ts","sourcesContent":["import {Nullable} from '@techsee/techsee-common';\n\nimport {\n IMediaSession,\n ISessionStreamsManager,\n MediaSessionConfiguration,\n MediaSessionParams,\n RemoteTrackStats\n} from '../MediaContracts';\n\nimport {SessionClientRole, SessionClientType} from '../MediaConstants';\nimport {throwableGuard} from '@techsee/techsee-common/lib/core/guards';\nimport {IMediaSessionPeer, MediaSessionBase, recordingEvents} from './MediaSessionBase';\nimport {getMediaTracer} from '../MediaUtils/MediaTracer';\n\nconst trace = getMediaTracer('OpentokSession');\n\ndeclare global {\n interface Window {\n OT: any;\n }\n}\n\ninterface OTConnection {\n connectionId: string;\n}\n\ntype OTPublisher = any;\n\ntype OTSubscriber = any;\n\ninterface IOTSession {\n sessionId: string;\n\n connection: Nullable<OTConnection>;\n\n isConnected(): boolean;\n\n connect(token: string, cb: any): void;\n\n disconnect(): void;\n\n publish(publisher: OTPublisher, cb: any): void;\n\n unpublish(publisher: OTPublisher): void;\n\n subscribe(stream: any, container: any, uiProps: any, doneCb: any): OTSubscriber;\n\n unsubscribe(subscriber: OTSubscriber): void;\n\n getSubscribersForStream(stream: any): OTSubscriber[];\n\n off(): void;\n\n on(param: string, cb: any): void;\n\n once(param: string, cb: any): void;\n}\n\ninterface OTStreamSource {\n videoSource: any;\n audioSource: any;\n}\n\ninterface OTPeerConnection extends IMediaSessionPeer {\n localStream: Nullable<OTStreamSource>;\n connection: OTConnection;\n publisher: Nullable<OTPublisher>;\n subscriber: Nullable<OTSubscriber>;\n}\n\n//Currently this version supports subscribing only. Publishing will be implemented as needed.\nexport class OpentokSession extends MediaSessionBase<OTPeerConnection> implements IMediaSession {\n private readonly _credentials: any;\n private _lastStreamEvents: any[] = [];\n private _session: Nullable<IOTSession> = null;\n\n get id(): string {\n return this._session && this._session.sessionId ? this._session.sessionId : '';\n }\n\n private get ownPeerId(): string {\n return this._session && this._session.connection ? this._session.connection.connectionId : '';\n }\n\n constructor(sessionParams: MediaSessionParams & MediaSessionConfiguration, streamFactory: ISessionStreamsManager) {\n super(sessionParams, streamFactory);\n\n this._credentials = sessionParams.credentials;\n\n this.connect = this.connect.bind(this);\n this.disconnect = this.disconnect.bind(this);\n this.onMediaStreamDestroyed = this.onMediaStreamDestroyed.bind(this);\n this.onMediaStreamRenewed = this.onMediaStreamRenewed.bind(this);\n\n this.createStreamForPublishing = this.createStreamForPublishing.bind(this);\n this.createMediaPublisher = this.createMediaPublisher.bind(this);\n this.connectionCreatedHandler = this.connectionCreatedHandler.bind(this);\n this.connectionDestroyedHandler = this.connectionDestroyedHandler.bind(this);\n this.streamCreatedHandler = this.streamCreatedHandler.bind(this);\n this.streamDestroyedHandler = this.streamDestroyedHandler.bind(this);\n }\n\n sessionDisconnect(): void {\n if (this._session) {\n this._session.disconnect();\n }\n }\n\n connect(): Promise<void> {\n trace.info('Connect to session', this._credentials);\n\n if (this._session) {\n trace.error('Session already exists');\n\n return Promise.reject('Cannot connect to session while another session is active');\n }\n\n return new Promise((resolve, reject) => {\n const handleError = (error: any): void => {\n this._session && this._session!.off();\n this._session = null;\n trace.error('handleConnect error:', error);\n reject(error);\n };\n\n const {apiKey, sessionId} = this._sessionParams.credentials;\n\n this._session = (window as any).OTSESSION = window.OT.initSession(apiKey, sessionId);\n this._lastStreamEvents = [];\n\n this._session!.on('connectionCreated', this.connectionCreatedHandler);\n this._session!.on('connectionDestroyed', this.connectionDestroyedHandler);\n this._session!.on('streamCreated', this.streamCreatedHandler);\n this._session!.on('streamDestroyed', this.streamDestroyedHandler);\n this._session!.on('archiveStarted', (event: any) => {\n trace.info('archiveStarted event', event);\n this.emitEvent(recordingEvents.RECORD_STARTED, event);\n });\n\n this._session!.on('archiveStopped', (event: any) => {\n trace.info('archiveStopped event', event);\n this.emitEvent(recordingEvents.RECORD_STOPPED, event);\n });\n\n const connectHandler = (error: any): void => {\n if (error) {\n handleError(error);\n\n return;\n }\n\n resolve();\n };\n\n this._session!.connect(this._credentials.token, connectHandler);\n });\n }\n\n disconnect(): Promise<void> {\n return new Promise((resolve) => {\n trace.info('Disconnecting from session');\n const currentSession = this._session;\n\n this._session = null;\n\n this._peerConnections.forEach((peerConnection) => {\n this.destroyPeerConnection(peerConnection);\n });\n\n if (!currentSession) {\n trace.info('No active session - resolving');\n resolve();\n\n return;\n }\n currentSession.off();\n currentSession.disconnect();\n this._lastStreamEvents = [];\n\n trace.info('Disconnection from session completed');\n\n resolve();\n });\n }\n\n onMediaStreamDestroyed(clientRole: SessionClientRole): Promise<void> {\n const promises: Promise<void>[] = [];\n\n this._peerConnections.forEach((peerConnection) => {\n if (peerConnection.peerRole === clientRole) {\n promises.push(this.destroyPublisherOnPeerConnection(peerConnection));\n }\n });\n\n return Promise.all(promises).then(() => undefined);\n }\n\n onMediaStreamRenewed(clientRole: SessionClientRole, mediaStream: MediaStream): Promise<void> {\n //TODO - Alex: need to implement adding new tracks to peer connection and forcing negotiation (in OT API)\n return Promise.resolve();\n }\n\n replaceStreamTracks(mediaStream: Nullable<MediaStream>): Promise<void> {\n return Promise.reject(new Error('Not implemented'));\n }\n\n getRemoteTrackStats(mediaTrack: MediaStreamTrack): Promise<RemoteTrackStats> {\n return new Promise((resolve: any, reject: any) => {\n const peerConnectionOfTrack = this.getPeerConnectionByMediaTrack(mediaTrack);\n\n if (!peerConnectionOfTrack || !peerConnectionOfTrack.subscriber) {\n reject(new Error('Cannot find peer or subscriber for getting stats'));\n\n return;\n }\n\n //https://tokbox.com/developer/sdks/js/reference/Subscriber.html#getStats\n peerConnectionOfTrack!.subscriber.getStats((error: any, stats: any) => {\n if (error) {\n trace.warn('Error getting stats', error);\n resolve({trackId: mediaTrack.id, trackStats: {}});\n\n return;\n }\n\n resolve({trackId: mediaTrack.id, trackStats: stats});\n });\n });\n }\n\n //#region OpenTok Events Handlers\n\n private connectionCreatedHandler(event: any): void {\n trace.info('connectionCreatedHandler', event);\n const {connection} = event;\n\n if (!this.sessionExistsGuard(false) || !connection) {\n return;\n }\n\n const destinationRole = JSON.parse(connection.data).clientRole;\n\n this.createStreamForPublishing(destinationRole, connection.connectionId === this.ownPeerId)\n .then((streams: OTStreamSource) => {\n const peerConnection: OTPeerConnection = {\n peerId: connection.connectionId,\n peerRole: destinationRole,\n peerType:\n this._sessionParams.clientType === SessionClientType.INITIATOR\n ? SessionClientType.GUEST\n : SessionClientType.INITIATOR,\n connection: connection,\n publisher: null,\n subscriber: null,\n remoteMediaTracks: [],\n localStream: streams,\n connectionTimoutPrt: null\n };\n\n if (connection.connectionId !== this.ownPeerId || (!streams.videoSource && !streams.audioSource)) {\n trace.info('Peer connection without publisher was created', peerConnection);\n\n return peerConnection;\n }\n\n return this.createMediaPublisher(streams).then((publisher: OTPublisher) => {\n peerConnection.publisher = publisher;\n trace.info('Peer connection with publisher was created', peerConnection);\n\n return peerConnection;\n });\n })\n .then((peerConnection: OTPeerConnection) => {\n this._peerConnections.set(peerConnection.peerId, peerConnection);\n\n if (connection.connectionId === this.ownPeerId) {\n trace.info('Before subscribe the event streams');\n\n /*\n When publisher connect after others participants,\n make sure the publisher will not loose their stream to subscribe\n */\n return this._lastStreamEvents\n .reduce(\n (promiseChain: any, streamEvent: any) =>\n promiseChain.then(() => this.streamCreatedHandler(streamEvent)),\n Promise.resolve()\n )\n .then(() => {\n if (peerConnection.publisher) {\n return new Promise<void>((resolve, reject) => {\n trace.info('Starting publish to peerConnection.publisher');\n this._session!.publish(peerConnection.publisher, (error: any) => {\n if (error) {\n trace.error(`Publish to peerConnection error ${error}`);\n\n return reject(error);\n }\n\n trace.info('Publish to peerConnection success');\n\n return resolve();\n });\n });\n }\n\n return Promise.resolve();\n });\n }\n })\n .catch((error: any) => {\n //Need to handle fail cases during reliability\n trace.error(`Error handling new peer connection: ${error}`);\n });\n }\n\n private connectionDestroyedHandler(event: any): void {\n trace.info('connectionDestroyedHandler', event);\n const {connection} = event;\n\n if (!this.sessionExistsGuard(false) || !connection) {\n return;\n }\n\n if (this._peerConnections.has(connection.connectionId)) {\n this.destroyPeerConnection(this._peerConnections.get(connection.connectionId)!);\n }\n }\n\n private streamCreatedHandler(event: any): void {\n trace.info('streamCreatedHandler', event.stream);\n const {stream} = event;\n const {connection} = stream || {connection: null};\n\n if (!this.sessionExistsGuard(false) || !stream || connection.connectionId === this.ownPeerId) {\n return;\n }\n\n if (!this._peerConnections.has(connection.connectionId)) {\n trace.warn('Unexpected case, connection should exist on this stage');\n this._lastStreamEvents.push(event);\n\n return;\n }\n\n const peerConnection = this._peerConnections.get(connection.connectionId)!;\n\n trace.info('Subscribing the stream');\n\n this.createMediaSubscriber(stream).then((subscriber: OTSubscriber) => {\n peerConnection.subscriber = subscriber;\n });\n }\n\n private streamDestroyedHandler(event: any): void {\n trace.info('streamDestroyedHandler', event);\n const {stream} = event;\n\n if (!this.sessionExistsGuard(false) || !stream) {\n return;\n }\n\n const peerConnection = this._peerConnections.get(stream.connection.connectionId);\n\n if (!peerConnection) {\n trace.warn('Peer connection for remote stream not found');\n\n return;\n }\n\n this.destroySubscriberOnPeerConnection(peerConnection).catch(() =>\n trace.warn('Error destroying subscriber on stream destroy')\n );\n }\n\n //#endregion\n\n //#region PeerConnection\n\n private destroyPeerConnection(peerConnection: OTPeerConnection): void {\n this._peerConnections.delete(peerConnection.peerId);\n this.destroyPublisherOnPeerConnection(peerConnection).catch(() =>\n trace.warn('Error destroying publisher on peer destroy')\n );\n this.destroySubscriberOnPeerConnection(peerConnection).catch(() =>\n trace.warn('Error destroying subscriber on peer destroy')\n );\n }\n\n //#endregion\n\n //#region Publisher\n\n private createStreamForPublishing(\n destinationRole: SessionClientRole,\n shouldCreateStream: boolean\n ): Promise<OTStreamSource> {\n trace.info('createStreamForPublishing');\n const stream: OTStreamSource = {videoSource: null, audioSource: null};\n\n if (!shouldCreateStream) {\n trace.info(\n `createStreamForPublishing - destinationRole: ${destinationRole} not supposed to create a stream`\n );\n\n return Promise.resolve(stream);\n }\n\n return this._sessionStreamsManager\n .getMediaStreamForRole(destinationRole)\n .then((mediaStream: Nullable<MediaStream>) => {\n if (mediaStream) {\n trace.info('Media stream found for publishing', mediaStream);\n mediaStream.getTracks().forEach((track) => {\n if (track.kind === 'video') {\n stream.videoSource = track;\n } else if (track.kind === 'audio') {\n stream.audioSource = track;\n }\n });\n } else if (this._sessionParams.clientRole === SessionClientRole.USER) {\n trace.warn('No local media was found for publishing to destinationRole:', destinationRole);\n }\n\n return stream;\n });\n }\n\n private createMediaPublisher(stream: OTStreamSource): Promise<OTPublisher> {\n const otParams = {\n videoSource: stream.videoSource ? stream.videoSource : false,\n audioSource: stream.audioSource ? stream.audioSource : false,\n publishAudio: !!stream.audioSource,\n publishVideo: !!stream.videoSource,\n showControls: false,\n insertDefaultUI: false\n };\n\n return new Promise((resolve, reject) => {\n const otPublisher = window.OT.initPublisher(undefined, otParams, (error: any) => {\n if (error) {\n reject(error);\n\n return;\n }\n otPublisher.on('streamDestroyed', (event: any) => {\n trace.info('Publisher stream destroyed', event);\n //We preventing the default, in order to prevent stop the stream by OT.\n //We managing streams by ourselves and if we allow OT to close the stream, it cannot be reused.\n event.preventDefault();\n });\n otPublisher.on('videoElementCreated', (event: any) => {\n event.element.muted = true;\n });\n otPublisher.on('streamCreated', (event: any) => {\n trace.info('publisher stream created', event, otPublisher.stream.hasAudio);\n });\n\n resolve(otPublisher);\n });\n });\n }\n\n private destroyPublisherOnPeerConnection(peerConnection: OTPeerConnection): Promise<void> {\n if (this.sessionExistsGuard(false) && peerConnection.publisher) {\n this._session!.unpublish(peerConnection.publisher);\n }\n\n peerConnection.publisher = null;\n peerConnection.localStream = null;\n\n return Promise.resolve();\n }\n\n //#endregion\n\n //#region Subscriber\n\n private createMediaSubscriber(remoteStream: any): Promise<OTSubscriber> {\n return new Promise((resolve: any, reject: any) => {\n if (!this.sessionExistsGuard(false)) {\n reject('Session not exists');\n\n return;\n }\n\n const subscriber: OTSubscriber = this._session!.subscribe(\n remoteStream,\n null,\n {insertDefaultUI: false},\n (err: any) => {\n if (err) {\n trace.warn('Error creating subscriber', err);\n reject(err);\n\n return;\n }\n\n subscriber.on('videoElementCreated', (event: any) => {\n const {element} = event;\n const peerConnection = this._peerConnections.get(remoteStream.connection.connectionId)!;\n const otMediaStream: MediaStream = element.srcObject;\n\n peerConnection.subscriber = subscriber;\n\n if (!otMediaStream) {\n trace.warn('MediaStream not found in OT video element');\n\n return;\n }\n\n trace.info('Registering remote media tracks', peerConnection.subscriber, otMediaStream);\n otMediaStream.getTracks().forEach((mediaTrack: MediaStreamTrack) => {\n this.addRemoteTrackToPeer(peerConnection, mediaTrack);\n });\n\n this.registerPeersRemoteTracks(peerConnection);\n });\n\n resolve(subscriber);\n }\n );\n });\n }\n\n private destroySubscriberOnPeerConnection(peerConnection: OTPeerConnection): Promise<void> {\n if (this.sessionExistsGuard(false) && peerConnection.subscriber) {\n this._session!.unsubscribe(peerConnection.subscriber);\n }\n\n peerConnection.subscriber = null;\n\n return Promise.resolve();\n\n /**\n REMARK- opentok provide the same media track id for all participates.\n when we unregister the media track we check by the id which is the same for all.\n **/\n // return this.unregisterPeersRemoteMediaTracks(peerConnection)\n // .catch(() => trace.warn('Error removing remote streams from peerConnection'))\n // .then(() => {\n // peerConnection.subscriber = null;\n // });\n }\n\n //#endregion\n\n private sessionExistsGuard(shouldThrow = true): boolean {\n const connected = !!(this._session && this._session.connection && this._session.connection.connectionId);\n\n return throwableGuard(connected, 'There no OpenTok session exists', shouldThrow);\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/MediaSession/SessionTurn.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,QAAQ,EAAC,MAAM,yBAAyB,CAAC;AAEjD,OAAO,EACH,aAAa,EACb,sBAAsB,EACtB,yBAAyB,EACzB,oBAAoB,EACpB,kBAAkB,EAElB,gBAAgB,EACnB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAiD,iBAAiB,EAAoB,MAAM,mBAAmB,CAAC;AAWvH,OAAO,EAAC,gBAAgB,EAAC,MAAM,oBAAoB,CAAC;AAKpD,qBAAa,iBAAkB,SAAQ,gBAAgB,CAAC,iBAAiB,CAAE,YAAW,aAAa;IAC/F,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAuB;IAExD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAM;IAEvC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAM;IAErC,OAAO,CAAC,OAAO,CAAM;IAErB,IAAI,EAAE,IAAI,MAAM,CAEf;gBAGG,aAAa,EAAE,kBAAkB,GAAG,yBAAyB,EAC7D,eAAe,EAAE,oBAAoB,EACrC,aAAa,EAAE,sBAAsB;IA6BzC,OAAO,CAAC,WAAW,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB3C,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAiChE,4BAA4B,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAiBlD,mBAAmB,CAAC,WAAW,EAAE,QAAQ,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAoCtE,iBAAiB,CAAC,kBAAkB,EAAE,GAAG,GAAG,IAAI;IAuDhD,oBAAoB,CAAC,gBAAgB,EAAE,GAAG,GAAG,IAAI;IAyBjD,wBAAwB,CAAC,qBAAqB,EAAE,GAAG,GAAG,IAAI;IAgB1D,gBAAgB,CAAC,aAAa,EAAE,GAAG,GAAG,IAAI;IAoD1C,8BAA8B,IAAI,IAAI;IAgBtC,sBAAsB,CAAC,UAAU,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAapE,oBAAoB,CAAC,UAAU,EAAE,iBAAiB,EAAE,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAK5F,mBAAmB,CAAC,UAAU,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAY5E,OAAO,CAAC,mBAAmB;IAe3B,OAAO,CAAC,qBAAqB;IAoE7B,OAAO,CAAC,yBAAyB;IAQjC,OAAO,CAAC,4BAA4B;IAYpC,OAAO,CAAC,sBAAsB;IAe9B,OAAO,CAAC,mBAAmB;IAY3B,4BAA4B,CAAC,gBAAgB,EAAE,GAAG,GAAG,IAAI;IAuBzD,sBAAsB,CAAC,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAYvG,2BAA2B,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAejD,kBAAkB,CAAC,YAAY,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAQvE,qBAAqB,CAAC,cAAc,EAAE,iBAAiB,GAAG,IAAI;IAS9D,iBAAiB,IAAI,IAAI;CAM5B","file":"SessionTurn.d.ts","sourcesContent":["import * as io from 'socket.io-client';\nimport once from 'lodash/once';\nimport {Nullable} from '@techsee/techsee-common';\n\nimport {\n IMediaSession,\n ISessionStreamsManager,\n MediaSessionConfiguration,\n MediaSessionHandlers,\n MediaSessionParams,\n RemoteMediaTrack,\n RemoteTrackStats\n} from '../MediaContracts';\n\nimport {KnownMediaStream, MediaSessionDisconnectReason, SessionClientRole, SessionClientType} from '../MediaConstants';\n\nimport {\n SOCKET_EVENTS,\n SOCKET_PATH,\n SOCKET_RECONNECTION_ATTEMPTS,\n SOCKET_RECONNECTION_DELAY,\n SOCKET_RECONNECTION_DELAY_MAX\n} from './TurnConstants';\n\nimport {getMediaTracer} from '../MediaUtils/MediaTracer';\nimport {MediaSessionBase} from './MediaSessionBase';\n\nconst trace = getMediaTracer('TurnWebRtcSession');\n\n//Currently this version supports subscribing only. Publishing will be implemented as needed.\nexport class TurnWebRtcSession extends MediaSessionBase<RTCPeerConnection> implements IMediaSession {\n private readonly _sessionHandlers: MediaSessionHandlers;\n\n private readonly _pendingPeersIce: any;\n\n private readonly _configuration: any;\n\n private _socket: any;\n\n get id(): string {\n return this._sessionParams.sessionId;\n }\n\n constructor(\n sessionParams: MediaSessionParams & MediaSessionConfiguration,\n sessionHandlers: MediaSessionHandlers,\n streamFactory: ISessionStreamsManager\n ) {\n super(sessionParams, streamFactory);\n this._sessionHandlers = sessionHandlers;\n this._socket = null;\n this._pendingPeersIce = {};\n\n const iceServers = sessionParams.credentials.ice.map((item: any) => ({\n urls: item.url,\n username: item.login,\n credential: item.password\n }));\n\n this._configuration = {\n iceServers,\n iceTransportPolicy: 'relay'\n };\n\n this._joinResponseHandler = this._joinResponseHandler.bind(this);\n this._iceCandidateReceivedHandler = this._iceCandidateReceivedHandler.bind(this);\n this._sendOfferHandler = this._sendOfferHandler.bind(this);\n this._sdpOfferHandler = this._sdpOfferHandler.bind(this);\n this._initiatorPeerReconnectHandler = this._initiatorPeerReconnectHandler.bind(this);\n this._peerDisconnectedHandler = this._peerDisconnectedHandler.bind(this);\n this._onSocketDisconnectedHandler = this._onSocketDisconnectedHandler.bind(this);\n }\n\n //#region Socket Connectivity\n\n connect(connectOnly = false): Promise<void> {\n trace.info('Connect to session');\n\n return this._connectSocket(this._sessionParams.credentials.url, connectOnly)\n .then(() => {\n trace.info('Joining SessionId: ' + this._sessionParams.sessionId);\n\n this._socket.emit(SOCKET_EVENTS.OUTGOING.JOIN_ROOM, {\n clientType: this._sessionParams.clientType,\n sessionId: this._sessionParams.sessionId\n });\n })\n .catch((error) => {\n throw error;\n });\n }\n\n disconnect(): Promise<void> {\n trace.info('disconnect - call disconnect from session', MediaSessionDisconnectReason.ForcedByConsumer);\n\n return this._disconnectInternal(MediaSessionDisconnectReason.ForcedByConsumer);\n }\n\n _connectSocket(url: string, connectOnly: boolean): Promise<void> {\n return new Promise((resolve, reject) => {\n const socket = (this._socket = io.connect(url, {\n forceNew: true,\n reconnectionDelay: SOCKET_RECONNECTION_DELAY,\n reconnectionDelayMax: SOCKET_RECONNECTION_DELAY_MAX,\n reconnectionAttempts: SOCKET_RECONNECTION_ATTEMPTS,\n path: SOCKET_PATH\n }));\n\n const done = once((err?: any) => {\n err ? reject(err) : resolve();\n });\n\n socket.once('connect', () => done());\n socket.on(SOCKET_EVENTS.INCOMING.ERROR, done);\n socket.on('connect_error', done);\n socket.on('connect_timeout', done);\n socket.on('disconnect', this._onSocketDisconnectedHandler);\n\n if (!connectOnly) {\n trace.info('Subscribing socket events');\n\n socket.on(SOCKET_EVENTS.INCOMING.SEND_OFFER, this._sendOfferHandler);\n socket.on(SOCKET_EVENTS.INCOMING.JOIN_RESPONSE, this._joinResponseHandler);\n socket.on(SOCKET_EVENTS.INCOMING.PEER_DISCONNECTED, this._peerDisconnectedHandler);\n socket.on(SOCKET_EVENTS.INCOMING.SDP_OFFER, this._sdpOfferHandler);\n socket.on(SOCKET_EVENTS.INCOMING.PUBLISHER_RECONNECT, this._initiatorPeerReconnectHandler);\n socket.on(SOCKET_EVENTS.INCOMING.ICE_CANDIDATE, this._iceCandidateReceivedHandler);\n }\n });\n }\n\n _onSocketDisconnectedHandler(reason: string): void {\n if (this._socket) {\n // The initial example of differentiating between \"forced\" disconnect and \"network\" disconnect is taken from:\n // https://socket.io/docs/client-api/#Event-%E2%80%98disconnect%E2%80%99\n if (reason !== 'io server disconnect' && reason !== 'io client disconnect') {\n trace.info(\n '_onSocketDisconnectedHandler disconnect from session',\n MediaSessionDisconnectReason.SignalingChannelDisconnect\n );\n // the disconnection was initiated by the server or by client, you need to call (connect) manually\n this._disconnectInternal(MediaSessionDisconnectReason.SignalingChannelDisconnect).catch(\n () => undefined\n );\n }\n }\n }\n\n replaceStreamTracks(mediaStream: Nullable<MediaStream>): Promise<void> {\n const promises: any[] = [];\n\n if (mediaStream && this._peerConnections) {\n trace.info('replaceStreamTracks - Replace local tracks to peer connection');\n\n this._peerConnections.forEach((peerConnection: RTCPeerConnection) => {\n mediaStream.getTracks().forEach((track) => {\n const sender = peerConnection.getSenders().find((s: any) => s.track.kind === track.kind);\n\n if (sender) {\n promises.push(\n sender\n .replaceTrack(track)\n .then(() =>\n trace.info(\n `replaceStreamTracks - Local ${track.kind} ${track.id} track replace to peer connection`\n )\n )\n );\n }\n });\n });\n }\n\n return Promise.all(promises).then(() => {\n window.mediaStreamAlreadyReplaced = true;\n trace.info('replaceStreamTracks: replace stream tracks - Success');\n });\n }\n\n //#endregion\n\n //#region WebRTC Initiator - session Initiator (User on mobile device)\n\n // Received by Initiator, causing him to emit SDP_OFFER\n _sendOfferHandler(sendOfferEventData: any): void {\n const {uid, peerId} = sendOfferEventData;\n\n trace.info('_sendOfferHandler');\n\n //Currently we not support of passing role through signaling, so we assume every connection is \"AGENT\" like\n const peerConnection = this._createPeerConnection(peerId, SessionClientType.GUEST, SessionClientRole.AGENT);\n const offerParams = {offerToReceiveAudio: true, offerToReceiveVideo: true};\n\n //TODO - Alex: Currently we not sending \"role\" through signaling, and by default we assume that the one who asks fot offer, is agent.\n //On later stages we can send or the client \"role\" or the list of \"streams\" that peer is interested in.\n this._sessionStreamsManager\n .getMediaStreamForRole(SessionClientRole.AGENT)\n .then((mediaStream: Nullable<MediaStream>) => {\n trace.info('_sendOfferHandler stream and peer created');\n\n if (mediaStream) {\n trace.info('Adding local tracks to peer connection for sending offer');\n mediaStream.getTracks().forEach((track) => {\n peerConnection.addTrack(track, mediaStream);\n trace.info(`Local ${track.kind} ${track.id} track added to peer connection`);\n });\n } else {\n trace.info('No media stream was found for publishing');\n }\n })\n .then(() => peerConnection.createOffer(offerParams))\n .then((offerDesc) => {\n trace.info('Initiator has created sdp offer', {offerDesc});\n\n this._socket.emit(SOCKET_EVENTS.OUTGOING.SDP_OFFER, {\n uid,\n peerId,\n sessionId: this._sessionParams.sessionId,\n sdpOffer: offerDesc.sdp\n });\n\n return peerConnection.setLocalDescription(offerDesc).then(() => {\n trace.info('Local description was set on peerConnection', peerConnection);\n this._setPeerConnectionMonitor(peerConnection);\n\n // make sure using the tracks of the last stream that used\n if (window.latestUserMediaStream && window.mediaStreamAlreadyReplaced) {\n this.replaceStreamTracks(window.latestUserMediaStream.mediaStream).catch((e) =>\n trace.warn('_sendOfferHandler - Failed to replaceStreamTrace', e)\n );\n }\n });\n })\n .catch((e) => {\n trace.warn('Error', e);\n });\n }\n\n // Received by the Initiator: Indicates that publish should be started\n _joinResponseHandler(joinResponseData: any): void {\n const {sdpAnswer, uid, peerId} = joinResponseData;\n\n trace.info('joinResponse Received', {sdpAnswer});\n\n this._withPeerConnection(peerId, (peerConnection: RTCPeerConnection) => {\n const answer = new RTCSessionDescription({\n type: 'answer',\n sdp: sdpAnswer\n });\n\n peerConnection\n .setRemoteDescription(answer)\n .then(() => {\n trace.info('Remote description was set', answer);\n\n this._processQueuedIceCandidates(peerId);\n })\n .catch((e) => {\n trace.warn('Error', e);\n });\n });\n }\n\n // Received by Initiator after a guests socket has been disconnected\n _peerDisconnectedHandler(peerDisconnectedEvent: any): void {\n const {peerId} = peerDisconnectedEvent;\n\n trace.info('_peerDisconnectedHandler');\n\n this._withPeerConnection(peerId, (peerConnection: any) => {\n this._destroyPeerConnection(peerConnection);\n //this._disconnectInternal(MediaSessionDisconnectReason.PeerDisconnected).catch(() => undefined);\n });\n }\n\n //#endregion WebRTC Initiator - session Initiator (User on mobile device)\n\n //#region WebRTC Guest - session Guest (Agent on dashboard platform)\n\n // Received by Guest, causing him to emit SDP_ANSWER\n _sdpOfferHandler(sdpOfferEvent: any): void {\n const {sdpOffer, peerId, uid} = sdpOfferEvent;\n\n trace.info('sdpOffer Received', {sdpOffer});\n\n const peerConnection = this._createPeerConnection(peerId, SessionClientType.INITIATOR, SessionClientRole.USER);\n\n this._sessionStreamsManager\n .getMediaStreamForRole(SessionClientRole.USER)\n .then((mediaStream: Nullable<MediaStream>) => {\n if (mediaStream) {\n trace.info('Adding local tracks to peer connection offer handler');\n mediaStream.getTracks().forEach((track) => {\n peerConnection.addTrack(track, mediaStream);\n trace.info(`Local ${track.kind} track added to peer connection`);\n });\n } else {\n trace.info('No media stream was found for publishing');\n }\n })\n .then(() => {\n const remoteDesc = new RTCSessionDescription({type: 'offer', sdp: sdpOffer});\n\n return peerConnection.setRemoteDescription(remoteDesc).then(() => {\n trace.info('Remote description was set', remoteDesc);\n\n return peerConnection.createAnswer().then((sdpAnswer) => {\n trace.info('_sdpOfferHandler answer created', sdpAnswer);\n\n this._socket.emit(SOCKET_EVENTS.OUTGOING.SDP_ANSWER, {\n uid,\n peerId,\n sessionId: this._sessionParams.sessionId,\n sdpAnswer: sdpAnswer.sdp\n });\n\n return peerConnection.setLocalDescription(sdpAnswer).then(() => {\n trace.info('_sdpOfferHandler local desc was set');\n\n this._processQueuedIceCandidates(peerId);\n this._setPeerConnectionMonitor(peerConnection);\n });\n });\n });\n })\n .catch((e) => {\n trace.error('Error in _sdpOfferHandler', e);\n throw e;\n });\n }\n\n // Received by Guest after an Initiator reconnects to room\n _initiatorPeerReconnectHandler(): void {\n trace.info(\n '_initiatorPeerReconnectHandler disconnect from session',\n MediaSessionDisconnectReason.InitiatorPeerReconnected\n );\n this._disconnectInternal(MediaSessionDisconnectReason.InitiatorPeerReconnected).catch((error: any) => {\n trace.warn('Error while disconnecting during userPeerReconnectHandler.', error);\n });\n }\n\n //#endregion WebRTC Guest - session Guest (Agent on dashboard platform)\n\n //#region Peer Connection\n\n //#region Streams Handling\n\n onMediaStreamDestroyed(clientRole: SessionClientRole): Promise<void> {\n //TODO - Alex: need to implement adding new tracks to peer connection and forcing negotiation\n trace.info(\n 'onMediaStreamDestroyed disconnect all peer connections from session',\n MediaSessionDisconnectReason.PublishedStreamDestroyed\n );\n this._peerConnections.forEach((peerConnection: RTCPeerConnection) => {\n this._disconnectInternal(MediaSessionDisconnectReason.PublishedStreamDestroyed).catch(() => undefined);\n });\n\n return Promise.resolve();\n }\n\n onMediaStreamRenewed(clientRole: SessionClientRole, mediaStream: MediaStream): Promise<void> {\n //TODO - Alex: need to implement adding new tracks to peer connection and forcing negotiation\n return Promise.resolve();\n }\n\n getRemoteTrackStats(mediaTrack: MediaStreamTrack): Promise<RemoteTrackStats> {\n const peerConnectionOfTrack = this.getPeerConnectionByMediaTrack(mediaTrack);\n\n if (peerConnectionOfTrack !== null) {\n return (peerConnectionOfTrack as any).getStats(mediaTrack);\n }\n\n return Promise.reject(new Error('Provided track is not remote track'));\n }\n\n //#endregion\n\n private _disconnectInternal(reason: MediaSessionDisconnectReason): Promise<void> {\n this._peerConnections.forEach((peerConnection: RTCPeerConnection) => {\n this._destroyPeerConnection(peerConnection);\n });\n\n if (this._socket) {\n trace.info('Disconnecting from session', reason);\n this.sessionDisconnect();\n this._socket = null;\n setTimeout(() => this._sessionHandlers.onDisconnectedHandler(reason));\n }\n\n return Promise.resolve();\n }\n\n private _createPeerConnection(\n peerId: string,\n peerConnectionType: SessionClientType,\n peerConnectionRole: SessionClientRole\n ): RTCPeerConnection {\n if (this._peerConnections.get(peerId)) {\n throw new Error(`${this._sessionParams.clientType} peer connection for ${peerId} already exists`);\n }\n\n const peerConnection: any = new RTCPeerConnection(this._configuration);\n\n peerConnection.peerId = peerId;\n peerConnection.peerType = peerConnectionType;\n peerConnection.remoteMediaTracks = [];\n peerConnection.peerRole = peerConnectionRole;\n peerConnection.connectionTimoutPrt = null;\n\n peerConnection.onconnectionstatechange = () => {\n trace.info('onconnectionstatechange', peerConnection);\n };\n\n peerConnection.oniceconnectionstatechange = (state: any) => {\n trace.info(\n `ICE Connection state for peer ${peerId} was changed to: ${state.currentTarget.iceConnectionState}`,\n state\n );\n const cnState = state.currentTarget.iceConnectionState;\n\n clearTimeout(peerConnection.connectionTimoutPrt);\n\n if (['closed', 'disconnected', 'failed'].indexOf(cnState) > -1) {\n this._iceStateDisconnected(peerConnection);\n } else if (cnState === 'connected') {\n this.registerPeersRemoteTracks(peerConnection);\n }\n };\n\n peerConnection.onicecandidate = (event: any) => {\n if (event.candidate) {\n trace.info(`${this._sessionParams.clientType} peerConn.onicecandidate`, event.candidate);\n this._socket.emit(SOCKET_EVENTS.OUTGOING.ICE_CANDIDATE, {\n sessionId: this._sessionParams.sessionId,\n peerId: peerId,\n candidate: event.candidate,\n clientType: this._sessionParams.clientType\n });\n } else {\n trace.info('ICE Candidate is null in peerConnection', event);\n }\n };\n\n peerConnection.ontrack = (trackEvent: any) => {\n const track: MediaStreamTrack = trackEvent.track;\n\n trace.info(`MediaStreamTrack arrived from peerConnection: ${track.kind}`, track);\n this.addRemoteTrackToPeer(peerConnection, track);\n if (peerConnection.connectionState === 'connected') {\n this.registerPeersRemoteTracks(peerConnection);\n }\n };\n\n this._peerConnections.set(peerId, peerConnection);\n\n trace.info(`${peerConnectionType} peerConnection created`);\n\n return peerConnection;\n }\n\n private _setPeerConnectionMonitor(peerConnection: RTCPeerConnection) {\n clearTimeout(peerConnection.connectionTimoutPrt);\n peerConnection.connectionTimoutPrt = setTimeout(() => {\n trace.info('PeerConnection ICE state timeout', peerConnection);\n this._handlePeerConnectionTimeout(peerConnection);\n }, this._sessionParams.peerConnectivityTimeoutSeconds * 1000);\n }\n\n private _handlePeerConnectionTimeout(peerConnection: RTCPeerConnection) {\n trace.info(\n '_handlePeerConnectionTimeout disconnect from session',\n MediaSessionDisconnectReason.PeerConnectionStateChangeTimeout\n );\n if (peerConnection.peerRole === SessionClientRole.USER) {\n this._disconnectInternal(MediaSessionDisconnectReason.PeerConnectionStateChangeTimeout).catch(\n () => undefined\n );\n }\n }\n\n private _destroyPeerConnection(peerConnection: RTCPeerConnection): void {\n trace.info('Destroying PeerConnection', peerConnection.peerType);\n peerConnection.ontrack = null;\n peerConnection.onicecandidate = null;\n peerConnection.onconnectionstatechange = null;\n peerConnection.oniceconnectionstatechange = null;\n peerConnection.onicegatheringstatechange = null;\n\n clearTimeout(peerConnection.connectionTimoutPrt);\n this.unregisterPeersRemoteMediaTracks(peerConnection).catch(() => undefined);\n\n this._peerConnections.delete(peerConnection.peerId);\n peerConnection.close();\n }\n\n private _withPeerConnection(peerId: any, callback: any): void | undefined {\n if (this._peerConnections.get(peerId)) {\n return callback(this._peerConnections.get(peerId));\n }\n\n return undefined;\n }\n\n //#endregion\n\n //#region ICE Candidates\n\n _iceCandidateReceivedHandler(iceCandidateData: any): void {\n const {candidate, peerId} = iceCandidateData;\n\n trace.info('_iceCandidateReceivedHandler', candidate);\n if (!candidate) {\n trace.info('Unexpected case, iceCandidate message arrived without candidate');\n\n return;\n }\n\n const peerConnection = this._peerConnections.get(peerId);\n\n if (!peerConnection || !peerConnection.remoteDescription) {\n trace.info('Queueing iceCandidate', candidate);\n this._queueIceCandidate(candidate, peerId);\n\n return;\n }\n\n trace.info('Going to add iceCandidate', candidate);\n this._addIceCandidateToPeer(candidate, peerConnection).catch(() => undefined);\n }\n\n _addIceCandidateToPeer(iceCandidate: RTCIceCandidate, peerConnection: RTCPeerConnection): Promise<void> {\n return peerConnection\n .addIceCandidate(iceCandidate)\n .then(() => {\n trace.info('ICE Candidate add to peer connection');\n })\n .catch((e) => {\n trace.warn('Error adding ICE Candidate to peer connection', e);\n throw e;\n });\n }\n\n _processQueuedIceCandidates(peerId: string): void {\n this._withPeerConnection(peerId, (peerConnection: RTCPeerConnection) => {\n const pendingIces = this._pendingPeersIce[peerId];\n\n if (pendingIces) {\n pendingIces.forEach((pendingIce: RTCIceCandidate) => {\n trace.info('Adding pending ICE to peer connections');\n this._addIceCandidateToPeer(pendingIce, peerConnection).catch(() => undefined);\n });\n } else {\n trace.info('No pending ICE found for peer', peerId);\n }\n });\n }\n\n _queueIceCandidate(iceCandidate: RTCIceCandidate, peerId: string): void {\n if (!this._pendingPeersIce[peerId]) {\n this._pendingPeersIce[peerId] = [];\n }\n\n this._pendingPeersIce[peerId].push(iceCandidate);\n }\n\n _iceStateDisconnected(peerConnection: RTCPeerConnection): void {\n trace.info(\n '_iceStateDisconnected disconnect from session',\n MediaSessionDisconnectReason.PeerConnectionInterrupted\n );\n this._disconnectInternal(MediaSessionDisconnectReason.PeerConnectionInterrupted).catch(() => undefined);\n }\n\n //#endregion\n sessionDisconnect(): void {\n if (this._socket) {\n this._socket.off();\n this._socket.disconnect();\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/MediaSession/SessionTurn.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,QAAQ,EAAC,MAAM,yBAAyB,CAAC;AAEjD,OAAO,EACH,aAAa,EACb,sBAAsB,EACtB,yBAAyB,EACzB,oBAAoB,EACpB,kBAAkB,EAElB,gBAAgB,EACnB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAiD,iBAAiB,EAAoB,MAAM,mBAAmB,CAAC;AAWvH,OAAO,EAAC,gBAAgB,EAAC,MAAM,oBAAoB,CAAC;AAKpD,qBAAa,iBAAkB,SAAQ,gBAAgB,CAAC,iBAAiB,CAAE,YAAW,aAAa;IAC/F,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAuB;IACxD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAM;IACvC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAM;IACrC,OAAO,CAAC,OAAO,CAAM;IAErB,IAAI,EAAE,IAAI,MAAM,CAEf;gBAGG,aAAa,EAAE,kBAAkB,GAAG,yBAAyB,EAC7D,eAAe,EAAE,oBAAoB,EACrC,aAAa,EAAE,sBAAsB;IA6BzC,OAAO,CAAC,WAAW,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB3C,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAiChE,4BAA4B,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAiBlD,mBAAmB,CAAC,WAAW,EAAE,QAAQ,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAoCtE,iBAAiB,CAAC,kBAAkB,EAAE,GAAG,GAAG,IAAI;IAuDhD,oBAAoB,CAAC,gBAAgB,EAAE,GAAG,GAAG,IAAI;IAyBjD,wBAAwB,CAAC,qBAAqB,EAAE,GAAG,GAAG,IAAI;IAgB1D,gBAAgB,CAAC,aAAa,EAAE,GAAG,GAAG,IAAI;IAoD1C,8BAA8B,IAAI,IAAI;IAgBtC,sBAAsB,CAAC,UAAU,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAapE,oBAAoB,CAAC,UAAU,EAAE,iBAAiB,EAAE,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAK5F,mBAAmB,CAAC,UAAU,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAY5E,OAAO,CAAC,mBAAmB;IAe3B,OAAO,CAAC,qBAAqB;IAoE7B,OAAO,CAAC,yBAAyB;IAQjC,OAAO,CAAC,4BAA4B;IAYpC,OAAO,CAAC,sBAAsB;IAe9B,OAAO,CAAC,mBAAmB;IAY3B,4BAA4B,CAAC,gBAAgB,EAAE,GAAG,GAAG,IAAI;IAuBzD,sBAAsB,CAAC,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAYvG,2BAA2B,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAejD,kBAAkB,CAAC,YAAY,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAQvE,qBAAqB,CAAC,cAAc,EAAE,iBAAiB,GAAG,IAAI;IAS9D,iBAAiB,IAAI,IAAI;CAM5B","file":"SessionTurn.d.ts","sourcesContent":["import * as io from 'socket.io-client';\nimport once from 'lodash/once';\nimport {Nullable} from '@techsee/techsee-common';\n\nimport {\n IMediaSession,\n ISessionStreamsManager,\n MediaSessionConfiguration,\n MediaSessionHandlers,\n MediaSessionParams,\n RemoteMediaTrack,\n RemoteTrackStats\n} from '../MediaContracts';\n\nimport {KnownMediaStream, MediaSessionDisconnectReason, SessionClientRole, SessionClientType} from '../MediaConstants';\n\nimport {\n SOCKET_EVENTS,\n SOCKET_PATH,\n SOCKET_RECONNECTION_ATTEMPTS,\n SOCKET_RECONNECTION_DELAY,\n SOCKET_RECONNECTION_DELAY_MAX\n} from './TurnConstants';\n\nimport {getMediaTracer} from '../MediaUtils/MediaTracer';\nimport {MediaSessionBase} from './MediaSessionBase';\n\nconst trace = getMediaTracer('TurnWebRtcSession');\n\n//Currently this version supports subscribing only. Publishing will be implemented as needed.\nexport class TurnWebRtcSession extends MediaSessionBase<RTCPeerConnection> implements IMediaSession {\n private readonly _sessionHandlers: MediaSessionHandlers;\n private readonly _pendingPeersIce: any;\n private readonly _configuration: any;\n private _socket: any;\n\n get id(): string {\n return this._sessionParams.sessionId;\n }\n\n constructor(\n sessionParams: MediaSessionParams & MediaSessionConfiguration,\n sessionHandlers: MediaSessionHandlers,\n streamFactory: ISessionStreamsManager\n ) {\n super(sessionParams, streamFactory);\n this._sessionHandlers = sessionHandlers;\n this._socket = null;\n this._pendingPeersIce = {};\n\n const iceServers = sessionParams.credentials.ice.map((item: any) => ({\n urls: item.url,\n username: item.login,\n credential: item.password\n }));\n\n this._configuration = {\n iceServers,\n iceTransportPolicy: 'relay'\n };\n\n this._joinResponseHandler = this._joinResponseHandler.bind(this);\n this._iceCandidateReceivedHandler = this._iceCandidateReceivedHandler.bind(this);\n this._sendOfferHandler = this._sendOfferHandler.bind(this);\n this._sdpOfferHandler = this._sdpOfferHandler.bind(this);\n this._initiatorPeerReconnectHandler = this._initiatorPeerReconnectHandler.bind(this);\n this._peerDisconnectedHandler = this._peerDisconnectedHandler.bind(this);\n this._onSocketDisconnectedHandler = this._onSocketDisconnectedHandler.bind(this);\n }\n\n //#region Socket Connectivity\n\n connect(connectOnly = false): Promise<void> {\n trace.info('Connect to session');\n\n return this._connectSocket(this._sessionParams.credentials.url, connectOnly)\n .then(() => {\n trace.info('Joining SessionId: ' + this._sessionParams.sessionId);\n\n this._socket.emit(SOCKET_EVENTS.OUTGOING.JOIN_ROOM, {\n clientType: this._sessionParams.clientType,\n sessionId: this._sessionParams.sessionId\n });\n })\n .catch((error) => {\n throw error;\n });\n }\n\n disconnect(): Promise<void> {\n trace.info('disconnect - call disconnect from session', MediaSessionDisconnectReason.ForcedByConsumer);\n\n return this._disconnectInternal(MediaSessionDisconnectReason.ForcedByConsumer);\n }\n\n _connectSocket(url: string, connectOnly: boolean): Promise<void> {\n return new Promise((resolve, reject) => {\n const socket = (this._socket = io.connect(url, {\n forceNew: true,\n reconnectionDelay: SOCKET_RECONNECTION_DELAY,\n reconnectionDelayMax: SOCKET_RECONNECTION_DELAY_MAX,\n reconnectionAttempts: SOCKET_RECONNECTION_ATTEMPTS,\n path: SOCKET_PATH\n }));\n\n const done = once((err?: any) => {\n err ? reject(err) : resolve();\n });\n\n socket.once('connect', () => done());\n socket.on(SOCKET_EVENTS.INCOMING.ERROR, done);\n socket.on('connect_error', done);\n socket.on('connect_timeout', done);\n socket.on('disconnect', this._onSocketDisconnectedHandler);\n\n if (!connectOnly) {\n trace.info('Subscribing socket events');\n\n socket.on(SOCKET_EVENTS.INCOMING.SEND_OFFER, this._sendOfferHandler);\n socket.on(SOCKET_EVENTS.INCOMING.JOIN_RESPONSE, this._joinResponseHandler);\n socket.on(SOCKET_EVENTS.INCOMING.PEER_DISCONNECTED, this._peerDisconnectedHandler);\n socket.on(SOCKET_EVENTS.INCOMING.SDP_OFFER, this._sdpOfferHandler);\n socket.on(SOCKET_EVENTS.INCOMING.PUBLISHER_RECONNECT, this._initiatorPeerReconnectHandler);\n socket.on(SOCKET_EVENTS.INCOMING.ICE_CANDIDATE, this._iceCandidateReceivedHandler);\n }\n });\n }\n\n _onSocketDisconnectedHandler(reason: string): void {\n if (this._socket) {\n // The initial example of differentiating between \"forced\" disconnect and \"network\" disconnect is taken from:\n // https://socket.io/docs/client-api/#Event-%E2%80%98disconnect%E2%80%99\n if (reason !== 'io server disconnect' && reason !== 'io client disconnect') {\n trace.info(\n '_onSocketDisconnectedHandler disconnect from session',\n MediaSessionDisconnectReason.SignalingChannelDisconnect\n );\n // the disconnection was initiated by the server or by client, you need to call (connect) manually\n this._disconnectInternal(MediaSessionDisconnectReason.SignalingChannelDisconnect).catch(\n () => undefined\n );\n }\n }\n }\n\n replaceStreamTracks(mediaStream: Nullable<MediaStream>): Promise<void> {\n const promises: any[] = [];\n\n if (mediaStream && this._peerConnections) {\n trace.info('replaceStreamTracks - Replace local tracks to peer connection');\n\n this._peerConnections.forEach((peerConnection: RTCPeerConnection) => {\n mediaStream.getTracks().forEach((track) => {\n const sender = peerConnection.getSenders().find((s: any) => s.track.kind === track.kind);\n\n if (sender) {\n promises.push(\n sender\n .replaceTrack(track)\n .then(() =>\n trace.info(\n `replaceStreamTracks - Local ${track.kind} ${track.id} track replace to peer connection`\n )\n )\n );\n }\n });\n });\n }\n\n return Promise.all(promises).then(() => {\n window.mediaStreamAlreadyReplaced = true;\n trace.info('replaceStreamTracks: replace stream tracks - Success');\n });\n }\n\n //#endregion\n\n //#region WebRTC Initiator - session Initiator (User on mobile device)\n\n // Received by Initiator, causing him to emit SDP_OFFER\n _sendOfferHandler(sendOfferEventData: any): void {\n const {uid, peerId} = sendOfferEventData;\n\n trace.info('_sendOfferHandler');\n\n //Currently we not support of passing role through signaling, so we assume every connection is \"AGENT\" like\n const peerConnection = this._createPeerConnection(peerId, SessionClientType.GUEST, SessionClientRole.AGENT);\n const offerParams = {offerToReceiveAudio: true, offerToReceiveVideo: true};\n\n //TODO - Alex: Currently we not sending \"role\" through signaling, and by default we assume that the one who asks fot offer, is agent.\n //On later stages we can send or the client \"role\" or the list of \"streams\" that peer is interested in.\n this._sessionStreamsManager\n .getMediaStreamForRole(SessionClientRole.AGENT)\n .then((mediaStream: Nullable<MediaStream>) => {\n trace.info('_sendOfferHandler stream and peer created');\n\n if (mediaStream) {\n trace.info('Adding local tracks to peer connection for sending offer');\n mediaStream.getTracks().forEach((track) => {\n peerConnection.addTrack(track, mediaStream);\n trace.info(`Local ${track.kind} ${track.id} track added to peer connection`);\n });\n } else {\n trace.info('No media stream was found for publishing');\n }\n })\n .then(() => peerConnection.createOffer(offerParams))\n .then((offerDesc) => {\n trace.info('Initiator has created sdp offer', {offerDesc});\n\n this._socket.emit(SOCKET_EVENTS.OUTGOING.SDP_OFFER, {\n uid,\n peerId,\n sessionId: this._sessionParams.sessionId,\n sdpOffer: offerDesc.sdp\n });\n\n return peerConnection.setLocalDescription(offerDesc).then(() => {\n trace.info('Local description was set on peerConnection', peerConnection);\n this._setPeerConnectionMonitor(peerConnection);\n\n // make sure using the tracks of the last stream that used\n if (window.latestUserMediaStream && window.mediaStreamAlreadyReplaced) {\n this.replaceStreamTracks(window.latestUserMediaStream.mediaStream).catch((e) =>\n trace.warn('_sendOfferHandler - Failed to replaceStreamTrace', e)\n );\n }\n });\n })\n .catch((e) => {\n trace.warn('Error', e);\n });\n }\n\n // Received by the Initiator: Indicates that publish should be started\n _joinResponseHandler(joinResponseData: any): void {\n const {sdpAnswer, uid, peerId} = joinResponseData;\n\n trace.info('joinResponse Received', {sdpAnswer});\n\n this._withPeerConnection(peerId, (peerConnection: RTCPeerConnection) => {\n const answer = new RTCSessionDescription({\n type: 'answer',\n sdp: sdpAnswer\n });\n\n peerConnection\n .setRemoteDescription(answer)\n .then(() => {\n trace.info('Remote description was set', answer);\n\n this._processQueuedIceCandidates(peerId);\n })\n .catch((e) => {\n trace.warn('Error', e);\n });\n });\n }\n\n // Received by Initiator after a guests socket has been disconnected\n _peerDisconnectedHandler(peerDisconnectedEvent: any): void {\n const {peerId} = peerDisconnectedEvent;\n\n trace.info('_peerDisconnectedHandler');\n\n this._withPeerConnection(peerId, (peerConnection: any) => {\n this._destroyPeerConnection(peerConnection);\n //this._disconnectInternal(MediaSessionDisconnectReason.PeerDisconnected).catch(() => undefined);\n });\n }\n\n //#endregion WebRTC Initiator - session Initiator (User on mobile device)\n\n //#region WebRTC Guest - session Guest (Agent on dashboard platform)\n\n // Received by Guest, causing him to emit SDP_ANSWER\n _sdpOfferHandler(sdpOfferEvent: any): void {\n const {sdpOffer, peerId, uid} = sdpOfferEvent;\n\n trace.info('sdpOffer Received', {sdpOffer});\n\n const peerConnection = this._createPeerConnection(peerId, SessionClientType.INITIATOR, SessionClientRole.USER);\n\n this._sessionStreamsManager\n .getMediaStreamForRole(SessionClientRole.USER)\n .then((mediaStream: Nullable<MediaStream>) => {\n if (mediaStream) {\n trace.info('Adding local tracks to peer connection offer handler');\n mediaStream.getTracks().forEach((track) => {\n peerConnection.addTrack(track, mediaStream);\n trace.info(`Local ${track.kind} track added to peer connection`);\n });\n } else {\n trace.info('No media stream was found for publishing');\n }\n })\n .then(() => {\n const remoteDesc = new RTCSessionDescription({type: 'offer', sdp: sdpOffer});\n\n return peerConnection.setRemoteDescription(remoteDesc).then(() => {\n trace.info('Remote description was set', remoteDesc);\n\n return peerConnection.createAnswer().then((sdpAnswer) => {\n trace.info('_sdpOfferHandler answer created', sdpAnswer);\n\n this._socket.emit(SOCKET_EVENTS.OUTGOING.SDP_ANSWER, {\n uid,\n peerId,\n sessionId: this._sessionParams.sessionId,\n sdpAnswer: sdpAnswer.sdp\n });\n\n return peerConnection.setLocalDescription(sdpAnswer).then(() => {\n trace.info('_sdpOfferHandler local desc was set');\n\n this._processQueuedIceCandidates(peerId);\n this._setPeerConnectionMonitor(peerConnection);\n });\n });\n });\n })\n .catch((e) => {\n trace.error('Error in _sdpOfferHandler', e);\n throw e;\n });\n }\n\n // Received by Guest after an Initiator reconnects to room\n _initiatorPeerReconnectHandler(): void {\n trace.info(\n '_initiatorPeerReconnectHandler disconnect from session',\n MediaSessionDisconnectReason.InitiatorPeerReconnected\n );\n this._disconnectInternal(MediaSessionDisconnectReason.InitiatorPeerReconnected).catch((error: any) => {\n trace.warn('Error while disconnecting during userPeerReconnectHandler.', error);\n });\n }\n\n //#endregion WebRTC Guest - session Guest (Agent on dashboard platform)\n\n //#region Peer Connection\n\n //#region Streams Handling\n\n onMediaStreamDestroyed(clientRole: SessionClientRole): Promise<void> {\n //TODO - Alex: need to implement adding new tracks to peer connection and forcing negotiation\n trace.info(\n 'onMediaStreamDestroyed disconnect all peer connections from session',\n MediaSessionDisconnectReason.PublishedStreamDestroyed\n );\n this._peerConnections.forEach((peerConnection: RTCPeerConnection) => {\n this._disconnectInternal(MediaSessionDisconnectReason.PublishedStreamDestroyed).catch(() => undefined);\n });\n\n return Promise.resolve();\n }\n\n onMediaStreamRenewed(clientRole: SessionClientRole, mediaStream: MediaStream): Promise<void> {\n //TODO - Alex: need to implement adding new tracks to peer connection and forcing negotiation\n return Promise.resolve();\n }\n\n getRemoteTrackStats(mediaTrack: MediaStreamTrack): Promise<RemoteTrackStats> {\n const peerConnectionOfTrack = this.getPeerConnectionByMediaTrack(mediaTrack);\n\n if (peerConnectionOfTrack !== null) {\n return (peerConnectionOfTrack as any).getStats(mediaTrack);\n }\n\n return Promise.reject(new Error('Provided track is not remote track'));\n }\n\n //#endregion\n\n private _disconnectInternal(reason: MediaSessionDisconnectReason): Promise<void> {\n this._peerConnections.forEach((peerConnection: RTCPeerConnection) => {\n this._destroyPeerConnection(peerConnection);\n });\n\n if (this._socket) {\n trace.info('Disconnecting from session', reason);\n this.sessionDisconnect();\n this._socket = null;\n setTimeout(() => this._sessionHandlers.onDisconnectedHandler(reason));\n }\n\n return Promise.resolve();\n }\n\n private _createPeerConnection(\n peerId: string,\n peerConnectionType: SessionClientType,\n peerConnectionRole: SessionClientRole\n ): RTCPeerConnection {\n if (this._peerConnections.get(peerId)) {\n throw new Error(`${this._sessionParams.clientType} peer connection for ${peerId} already exists`);\n }\n\n const peerConnection: any = new RTCPeerConnection(this._configuration);\n\n peerConnection.peerId = peerId;\n peerConnection.peerType = peerConnectionType;\n peerConnection.remoteMediaTracks = [];\n peerConnection.peerRole = peerConnectionRole;\n peerConnection.connectionTimoutPrt = null;\n\n peerConnection.onconnectionstatechange = () => {\n trace.info('onconnectionstatechange', peerConnection);\n };\n\n peerConnection.oniceconnectionstatechange = (state: any) => {\n trace.info(\n `ICE Connection state for peer ${peerId} was changed to: ${state.currentTarget.iceConnectionState}`,\n state\n );\n const cnState = state.currentTarget.iceConnectionState;\n\n clearTimeout(peerConnection.connectionTimoutPrt);\n\n if (['closed', 'disconnected', 'failed'].indexOf(cnState) > -1) {\n this._iceStateDisconnected(peerConnection);\n } else if (cnState === 'connected') {\n this.registerPeersRemoteTracks(peerConnection);\n }\n };\n\n peerConnection.onicecandidate = (event: any) => {\n if (event.candidate) {\n trace.info(`${this._sessionParams.clientType} peerConn.onicecandidate`, event.candidate);\n this._socket.emit(SOCKET_EVENTS.OUTGOING.ICE_CANDIDATE, {\n sessionId: this._sessionParams.sessionId,\n peerId: peerId,\n candidate: event.candidate,\n clientType: this._sessionParams.clientType\n });\n } else {\n trace.info('ICE Candidate is null in peerConnection', event);\n }\n };\n\n peerConnection.ontrack = (trackEvent: any) => {\n const track: MediaStreamTrack = trackEvent.track;\n\n trace.info(`MediaStreamTrack arrived from peerConnection: ${track.kind}`, track);\n this.addRemoteTrackToPeer(peerConnection, track);\n if (peerConnection.connectionState === 'connected') {\n this.registerPeersRemoteTracks(peerConnection);\n }\n };\n\n this._peerConnections.set(peerId, peerConnection);\n\n trace.info(`${peerConnectionType} peerConnection created`);\n\n return peerConnection;\n }\n\n private _setPeerConnectionMonitor(peerConnection: RTCPeerConnection) {\n clearTimeout(peerConnection.connectionTimoutPrt);\n peerConnection.connectionTimoutPrt = setTimeout(() => {\n trace.info('PeerConnection ICE state timeout', peerConnection);\n this._handlePeerConnectionTimeout(peerConnection);\n }, this._sessionParams.peerConnectivityTimeoutSeconds * 1000);\n }\n\n private _handlePeerConnectionTimeout(peerConnection: RTCPeerConnection) {\n trace.info(\n '_handlePeerConnectionTimeout disconnect from session',\n MediaSessionDisconnectReason.PeerConnectionStateChangeTimeout\n );\n if (peerConnection.peerRole === SessionClientRole.USER) {\n this._disconnectInternal(MediaSessionDisconnectReason.PeerConnectionStateChangeTimeout).catch(\n () => undefined\n );\n }\n }\n\n private _destroyPeerConnection(peerConnection: RTCPeerConnection): void {\n trace.info('Destroying PeerConnection', peerConnection.peerType);\n peerConnection.ontrack = null;\n peerConnection.onicecandidate = null;\n peerConnection.onconnectionstatechange = null;\n peerConnection.oniceconnectionstatechange = null;\n peerConnection.onicegatheringstatechange = null;\n\n clearTimeout(peerConnection.connectionTimoutPrt);\n this.unregisterPeersRemoteMediaTracks(peerConnection).catch(() => undefined);\n\n this._peerConnections.delete(peerConnection.peerId);\n peerConnection.close();\n }\n\n private _withPeerConnection(peerId: any, callback: any): void | undefined {\n if (this._peerConnections.get(peerId)) {\n return callback(this._peerConnections.get(peerId));\n }\n\n return undefined;\n }\n\n //#endregion\n\n //#region ICE Candidates\n\n _iceCandidateReceivedHandler(iceCandidateData: any): void {\n const {candidate, peerId} = iceCandidateData;\n\n trace.info('_iceCandidateReceivedHandler', candidate);\n if (!candidate) {\n trace.info('Unexpected case, iceCandidate message arrived without candidate');\n\n return;\n }\n\n const peerConnection = this._peerConnections.get(peerId);\n\n if (!peerConnection || !peerConnection.remoteDescription) {\n trace.info('Queueing iceCandidate', candidate);\n this._queueIceCandidate(candidate, peerId);\n\n return;\n }\n\n trace.info('Going to add iceCandidate', candidate);\n this._addIceCandidateToPeer(candidate, peerConnection).catch(() => undefined);\n }\n\n _addIceCandidateToPeer(iceCandidate: RTCIceCandidate, peerConnection: RTCPeerConnection): Promise<void> {\n return peerConnection\n .addIceCandidate(iceCandidate)\n .then(() => {\n trace.info('ICE Candidate add to peer connection');\n })\n .catch((e) => {\n trace.warn('Error adding ICE Candidate to peer connection', e);\n throw e;\n });\n }\n\n _processQueuedIceCandidates(peerId: string): void {\n this._withPeerConnection(peerId, (peerConnection: RTCPeerConnection) => {\n const pendingIces = this._pendingPeersIce[peerId];\n\n if (pendingIces) {\n pendingIces.forEach((pendingIce: RTCIceCandidate) => {\n trace.info('Adding pending ICE to peer connections');\n this._addIceCandidateToPeer(pendingIce, peerConnection).catch(() => undefined);\n });\n } else {\n trace.info('No pending ICE found for peer', peerId);\n }\n });\n }\n\n _queueIceCandidate(iceCandidate: RTCIceCandidate, peerId: string): void {\n if (!this._pendingPeersIce[peerId]) {\n this._pendingPeersIce[peerId] = [];\n }\n\n this._pendingPeersIce[peerId].push(iceCandidate);\n }\n\n _iceStateDisconnected(peerConnection: RTCPeerConnection): void {\n trace.info(\n '_iceStateDisconnected disconnect from session',\n MediaSessionDisconnectReason.PeerConnectionInterrupted\n );\n this._disconnectInternal(MediaSessionDisconnectReason.PeerConnectionInterrupted).catch(() => undefined);\n }\n\n //#endregion\n sessionDisconnect(): void {\n if (this._socket) {\n this._socket.off();\n this._socket.disconnect();\n }\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/MediaSubscriber.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,QAAQ,EAAC,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAC,gBAAgB,EAAE,qBAAqB,EAAC,MAAM,kBAAkB,CAAC;AACzE,OAAO,EAAC,kBAAkB,EAAC,MAAM,sBAAsB,CAAC;AAExD,OAAO,EAAC,gBAAgB,EAAuB,MAAM,kBAAkB,CAAC;AAYxE,qBAAa,sBAAuB,YAAW,gBAAgB;IAC3D,OAAO,CAAC,QAAQ,CAAe;
|
|
1
|
+
{"version":3,"sources":["../src/MediaSubscriber.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,QAAQ,EAAC,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAC,gBAAgB,EAAE,qBAAqB,EAAC,MAAM,kBAAkB,CAAC;AACzE,OAAO,EAAC,kBAAkB,EAAC,MAAM,sBAAsB,CAAC;AAExD,OAAO,EAAC,gBAAgB,EAAuB,MAAM,kBAAkB,CAAC;AAYxE,qBAAa,sBAAuB,YAAW,gBAAgB;IAC3D,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,aAAa,CAAoC;IACzD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAwB;gBAE9C,gBAAgB,EAAE,qBAAqB;IAWnD,IAAI,UAAU,IAAI,gBAAgB,CAEjC;IAED,IAAI,SAAS,IAAI,cAAc,CAE9B;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,IAAI,YAAY,IAAI,OAAO,CAM1B;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,IAAI,YAAY,IAAI,QAAQ,CAAC,gBAAgB,CAAC,CAE7C;IAED,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IASjC,cAAc,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAK1C,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IASrC,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI;IAmB9C,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B,OAAO,IAAI,IAAI;IAef,OAAO,CAAC,eAAe;IA4BvB,OAAO,CAAC,kBAAkB;IAK1B,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,eAAe;CAK1B","file":"MediaSubscriber.d.ts","sourcesContent":["import {EventEmitter} from 'events';\nimport {Nullable} from '@techsee/techsee-common';\nimport {IMediaSubscriber, MediaSubscriberParams} from './MediaContracts';\nimport {TechseeMediaStream} from './TechseeMediaStream';\nimport {removeVideoElementFromDom, renderMediaStreamToVideoEl} from './MediaUtils/MediaDomUtils';\nimport {KnownMediaStream, KnownMediaStreamKind} from './MediaConstants';\n\nimport {getMediaTracer} from './MediaUtils/MediaTracer';\nimport {onMediaElementResize} from './MediaUtils/Compatibility';\n\nconst trace = getMediaTracer('MediaSubscriber');\n\nenum privateEvents {\n STREAM_STATE_CHANGE = 'STREAM_STATE_CHANGE',\n SUBSCRIBER_DISPOSE = 'SUBSCRIBER_DISPOSE'\n}\n\nexport class TechseeMediaSubscriber implements IMediaSubscriber {\n private _emitter: EventEmitter;\n private _isDisposed = false;\n private _isPlaying = false;\n private _hasVideo = false;\n private _hasAudio = false;\n private _mediaElement: Nullable<HTMLVideoElement> = null;\n private readonly _subscriberParams: MediaSubscriberParams;\n\n constructor(subscriberParams: MediaSubscriberParams) {\n trace.info('Subscriber created');\n\n this.videoResizeHandler = this.videoResizeHandler.bind(this);\n\n this._emitter = new EventEmitter();\n this._subscriberParams = subscriberParams;\n }\n\n //#region IMediaSubscriber\n\n get streamType(): KnownMediaStream {\n return this._subscriberParams.streamType;\n }\n\n get container(): HTMLDivElement {\n return this._subscriberParams.container;\n }\n\n get isPlaying(): boolean {\n return this._isPlaying;\n }\n\n get isSoundMuted(): boolean {\n if (!this._mediaElement) {\n return true;\n }\n\n return this._mediaElement.muted;\n }\n\n get hasAudio(): boolean {\n return this._hasAudio;\n }\n\n get hasVideo(): boolean {\n return this._hasVideo;\n }\n\n get mediaElement(): Nullable<HTMLVideoElement> {\n return this._mediaElement;\n }\n\n get videoWidth(): number {\n return this.mediaElement ? this.mediaElement.videoWidth : 0;\n }\n\n get videoHeight(): number {\n return this.mediaElement ? this.mediaElement.videoHeight : 0;\n }\n\n get renderWidth(): number {\n return this.container.offsetWidth;\n }\n\n get renderHeight(): number {\n return this.container.offsetHeight;\n }\n\n muteSound(isMuted: boolean): void {\n this.throwIfDisposed();\n trace.info('Subscriber mute change - isMuted =', isMuted);\n if (this._mediaElement && this._mediaElement.muted !== isMuted) {\n this._mediaElement.muted = isMuted;\n this.emitEvent(privateEvents.STREAM_STATE_CHANGE);\n }\n }\n\n onStateChanged(callback: () => void): void {\n this.throwIfDisposed();\n this._emitter.on(privateEvents.STREAM_STATE_CHANGE, callback);\n }\n\n onDispose(callback: () => void): void {\n this.throwIfDisposed();\n this._emitter.on(privateEvents.SUBSCRIBER_DISPOSE, callback);\n }\n\n //#endregion\n\n //#region Non Interface Methods\n\n renderStream(stream: TechseeMediaStream): void {\n this.throwIfDisposed();\n this.stopRenderInternal()\n .then(() => renderMediaStreamToVideoEl(this.container, stream.mediaStream, stream.streamType))\n .then((videoEl) => {\n this._hasAudio = stream.mediaStream.getAudioTracks().length > 0;\n this._hasVideo = stream.mediaStream.getVideoTracks().length > 0;\n\n this.setPlayingState(videoEl, stream.streamKind);\n\n if (this._hasAudio) {\n this.muteSound(false);\n }\n })\n .catch((error) => {\n trace.warn('Error while rendering stream on video element', error);\n });\n }\n\n stopRendering(): Promise<void> {\n return this.stopRenderInternal().then(() => this.setPlayingState());\n }\n\n dispose(): void {\n if (this._isDisposed) {\n return;\n }\n\n this.stopRendering().catch(() => undefined);\n\n this._isDisposed = true;\n this._emitter.removeAllListeners();\n trace.info('Subscriber disposed');\n this.emitEvent(privateEvents.SUBSCRIBER_DISPOSE);\n }\n\n //#endregion\n\n private setPlayingState(mediaElement?: HTMLVideoElement, streamKind?: KnownMediaStreamKind): void {\n this._isPlaying = !!mediaElement;\n this._mediaElement = mediaElement || null;\n\n const finalize = (): void => {\n this.emitEvent(privateEvents.STREAM_STATE_CHANGE);\n trace.info(`Video element ${streamKind || ''} ${this.isPlaying ? 'play' : 'stop'} success`);\n };\n\n if (this._mediaElement && streamKind === KnownMediaStreamKind.Video) {\n if (this._mediaElement.videoHeight === 0) {\n let disposeResizeHandler: any = null;\n const resizeHandler = (): void => {\n disposeResizeHandler && disposeResizeHandler();\n onMediaElementResize(this._mediaElement, this.videoResizeHandler);\n finalize();\n };\n\n disposeResizeHandler = onMediaElementResize(this._mediaElement, resizeHandler);\n } else {\n onMediaElementResize(this._mediaElement, this.videoResizeHandler);\n finalize();\n }\n } else {\n finalize();\n }\n }\n\n private videoResizeHandler(): void {\n trace.info(`Video resize, new size = w: ${this.videoWidth} h:${this.videoHeight}`);\n this.emitEvent(privateEvents.STREAM_STATE_CHANGE);\n }\n\n private stopRenderInternal() {\n if (this._mediaElement && document.body.contains(this._mediaElement)) {\n return removeVideoElementFromDom(this._mediaElement);\n }\n\n return Promise.resolve();\n }\n\n private emitEvent(event: string, eventArgs?: any): void {\n setTimeout(() => {\n this._emitter.emit(event, eventArgs);\n });\n }\n\n private throwIfDisposed(): void {\n if (this._isDisposed) {\n throw new Error('Subscriber is already disposed');\n }\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["MediaSubscriber.js","../src/MediaSubscriber.ts"],"names":["Object","defineProperty","exports","value","TechseeMediaSubscriber","events_1","require","MediaDomUtils_1","MediaConstants_1","MediaTracer_1","Compatibility_1","trace","getMediaTracer","privateEvents","subscriberParams","_isDisposed","_isPlaying","_hasVideo","_hasAudio","_mediaElement","info","videoResizeHandler","bind","_emitter","EventEmitter","_subscriberParams","prototype","get","streamType","enumerable","configurable","container","muted","mediaElement","videoWidth","videoHeight","offsetWidth","offsetHeight","muteSound","isMuted","throwIfDisposed","emitEvent","STREAM_STATE_CHANGE","onStateChanged","callback","on","onDispose","SUBSCRIBER_DISPOSE","renderStream","stream","_this","stopRenderInternal","then","renderMediaStreamToVideoEl","mediaStream","videoEl","getAudioTracks","length","getVideoTracks","setPlayingState","streamKind","error","warn","stopRendering","dispose","undefined","removeAllListeners","finalize","isPlaying","KnownMediaStreamKind","Video","disposeResizeHandler_1","resizeHandler","onMediaElementResize","document","body","contains","removeVideoElementFromDom","Promise","resolve","event","eventArgs","setTimeout","emit","Error"],"mappings":"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","file":"MediaSubscriber.js","sourcesContent":["\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.TechseeMediaSubscriber = void 0;\nvar events_1 = require(\"events\");\nvar MediaDomUtils_1 = require(\"./MediaUtils/MediaDomUtils\");\nvar MediaConstants_1 = require(\"./MediaConstants\");\nvar MediaTracer_1 = require(\"./MediaUtils/MediaTracer\");\nvar Compatibility_1 = require(\"./MediaUtils/Compatibility\");\nvar trace = MediaTracer_1.getMediaTracer('MediaSubscriber');\nvar privateEvents;\n(function (privateEvents) {\n privateEvents[\"STREAM_STATE_CHANGE\"] = \"STREAM_STATE_CHANGE\";\n privateEvents[\"SUBSCRIBER_DISPOSE\"] = \"SUBSCRIBER_DISPOSE\";\n})(privateEvents || (privateEvents = {}));\nvar TechseeMediaSubscriber = /** @class */ (function () {\n function TechseeMediaSubscriber(subscriberParams) {\n this._isDisposed = false;\n this._isPlaying = false;\n this._hasVideo = false;\n this._hasAudio = false;\n this._mediaElement = null;\n trace.info('Subscriber created');\n this.videoResizeHandler = this.videoResizeHandler.bind(this);\n this._emitter = new events_1.EventEmitter();\n this._subscriberParams = subscriberParams;\n }\n Object.defineProperty(TechseeMediaSubscriber.prototype, \"streamType\", {\n //#region IMediaSubscriber\n get: function () {\n return this._subscriberParams.streamType;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(TechseeMediaSubscriber.prototype, \"container\", {\n get: function () {\n return this._subscriberParams.container;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(TechseeMediaSubscriber.prototype, \"isPlaying\", {\n get: function () {\n return this._isPlaying;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(TechseeMediaSubscriber.prototype, \"isSoundMuted\", {\n get: function () {\n if (!this._mediaElement) {\n return true;\n }\n return this._mediaElement.muted;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(TechseeMediaSubscriber.prototype, \"hasAudio\", {\n get: function () {\n return this._hasAudio;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(TechseeMediaSubscriber.prototype, \"hasVideo\", {\n get: function () {\n return this._hasVideo;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(TechseeMediaSubscriber.prototype, \"mediaElement\", {\n get: function () {\n return this._mediaElement;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(TechseeMediaSubscriber.prototype, \"videoWidth\", {\n get: function () {\n return this.mediaElement ? this.mediaElement.videoWidth : 0;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(TechseeMediaSubscriber.prototype, \"videoHeight\", {\n get: function () {\n return this.mediaElement ? this.mediaElement.videoHeight : 0;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(TechseeMediaSubscriber.prototype, \"renderWidth\", {\n get: function () {\n return this.container.offsetWidth;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(TechseeMediaSubscriber.prototype, \"renderHeight\", {\n get: function () {\n return this.container.offsetHeight;\n },\n enumerable: false,\n configurable: true\n });\n TechseeMediaSubscriber.prototype.muteSound = function (isMuted) {\n this.throwIfDisposed();\n trace.info('Subscriber mute change - isMuted =', isMuted);\n if (this._mediaElement && this._mediaElement.muted !== isMuted) {\n this._mediaElement.muted = isMuted;\n this.emitEvent(privateEvents.STREAM_STATE_CHANGE);\n }\n };\n TechseeMediaSubscriber.prototype.onStateChanged = function (callback) {\n this.throwIfDisposed();\n this._emitter.on(privateEvents.STREAM_STATE_CHANGE, callback);\n };\n TechseeMediaSubscriber.prototype.onDispose = function (callback) {\n this.throwIfDisposed();\n this._emitter.on(privateEvents.SUBSCRIBER_DISPOSE, callback);\n };\n //#endregion\n //#region Non Interface Methods\n TechseeMediaSubscriber.prototype.renderStream = function (stream) {\n var _this = this;\n this.throwIfDisposed();\n this.stopRenderInternal()\n .then(function () { return MediaDomUtils_1.renderMediaStreamToVideoEl(_this.container, stream.mediaStream, stream.streamType); })\n .then(function (videoEl) {\n _this._hasAudio = stream.mediaStream.getAudioTracks().length > 0;\n _this._hasVideo = stream.mediaStream.getVideoTracks().length > 0;\n _this.setPlayingState(videoEl, stream.streamKind);\n if (_this._hasAudio) {\n _this.muteSound(false);\n }\n })\n .catch(function (error) {\n trace.warn('Error while rendering stream on video element', error);\n });\n };\n TechseeMediaSubscriber.prototype.stopRendering = function () {\n var _this = this;\n return this.stopRenderInternal().then(function () { return _this.setPlayingState(); });\n };\n TechseeMediaSubscriber.prototype.dispose = function () {\n if (this._isDisposed) {\n return;\n }\n this.stopRendering().catch(function () { return undefined; });\n this._isDisposed = true;\n this._emitter.removeAllListeners();\n trace.info('Subscriber disposed');\n this.emitEvent(privateEvents.SUBSCRIBER_DISPOSE);\n };\n //#endregion\n TechseeMediaSubscriber.prototype.setPlayingState = function (mediaElement, streamKind) {\n var _this = this;\n this._isPlaying = !!mediaElement;\n this._mediaElement = mediaElement || null;\n var finalize = function () {\n _this.emitEvent(privateEvents.STREAM_STATE_CHANGE);\n trace.info(\"Video element \" + (streamKind || '') + \" \" + (_this.isPlaying ? 'play' : 'stop') + \" success\");\n };\n if (this._mediaElement && streamKind === MediaConstants_1.KnownMediaStreamKind.Video) {\n if (this._mediaElement.videoHeight === 0) {\n var disposeResizeHandler_1 = null;\n var resizeHandler = function () {\n disposeResizeHandler_1 && disposeResizeHandler_1();\n Compatibility_1.onMediaElementResize(_this._mediaElement, _this.videoResizeHandler);\n finalize();\n };\n disposeResizeHandler_1 = Compatibility_1.onMediaElementResize(this._mediaElement, resizeHandler);\n }\n else {\n Compatibility_1.onMediaElementResize(this._mediaElement, this.videoResizeHandler);\n finalize();\n }\n }\n else {\n finalize();\n }\n };\n TechseeMediaSubscriber.prototype.videoResizeHandler = function () {\n trace.info(\"Video resize, new size = w: \" + this.videoWidth + \" h:\" + this.videoHeight);\n this.emitEvent(privateEvents.STREAM_STATE_CHANGE);\n };\n TechseeMediaSubscriber.prototype.stopRenderInternal = function () {\n if (this._mediaElement && document.body.contains(this._mediaElement)) {\n return MediaDomUtils_1.removeVideoElementFromDom(this._mediaElement);\n }\n return Promise.resolve();\n };\n TechseeMediaSubscriber.prototype.emitEvent = function (event, eventArgs) {\n var _this = this;\n setTimeout(function () {\n _this._emitter.emit(event, eventArgs);\n });\n };\n TechseeMediaSubscriber.prototype.throwIfDisposed = function () {\n if (this._isDisposed) {\n throw new Error('Subscriber is already disposed');\n }\n };\n return TechseeMediaSubscriber;\n}());\nexports.TechseeMediaSubscriber = TechseeMediaSubscriber;\n\n//# sourceMappingURL=MediaSubscriber.js.map\n","import {EventEmitter} from 'events';\nimport {Nullable} from '@techsee/techsee-common';\nimport {IMediaSubscriber, MediaSubscriberParams} from './MediaContracts';\nimport {TechseeMediaStream} from './TechseeMediaStream';\nimport {removeVideoElementFromDom, renderMediaStreamToVideoEl} from './MediaUtils/MediaDomUtils';\nimport {KnownMediaStream, KnownMediaStreamKind} from './MediaConstants';\n\nimport {getMediaTracer} from './MediaUtils/MediaTracer';\nimport {onMediaElementResize} from './MediaUtils/Compatibility';\n\nconst trace = getMediaTracer('MediaSubscriber');\n\nenum privateEvents {\n STREAM_STATE_CHANGE = 'STREAM_STATE_CHANGE',\n SUBSCRIBER_DISPOSE = 'SUBSCRIBER_DISPOSE'\n}\n\nexport class TechseeMediaSubscriber implements IMediaSubscriber {\n private _emitter: EventEmitter;\n\n private _isDisposed = false;\n\n private _isPlaying = false;\n\n private _hasVideo = false;\n\n private _hasAudio = false;\n\n private _mediaElement: Nullable<HTMLVideoElement> = null;\n\n private readonly _subscriberParams: MediaSubscriberParams;\n\n constructor(subscriberParams: MediaSubscriberParams) {\n trace.info('Subscriber created');\n\n this.videoResizeHandler = this.videoResizeHandler.bind(this);\n\n this._emitter = new EventEmitter();\n this._subscriberParams = subscriberParams;\n }\n\n //#region IMediaSubscriber\n\n get streamType(): KnownMediaStream {\n return this._subscriberParams.streamType;\n }\n\n get container(): HTMLDivElement {\n return this._subscriberParams.container;\n }\n\n get isPlaying(): boolean {\n return this._isPlaying;\n }\n\n get isSoundMuted(): boolean {\n if (!this._mediaElement) {\n return true;\n }\n\n return this._mediaElement.muted;\n }\n\n get hasAudio(): boolean {\n return this._hasAudio;\n }\n\n get hasVideo(): boolean {\n return this._hasVideo;\n }\n\n get mediaElement(): Nullable<HTMLVideoElement> {\n return this._mediaElement;\n }\n\n get videoWidth(): number {\n return this.mediaElement ? this.mediaElement.videoWidth : 0;\n }\n\n get videoHeight(): number {\n return this.mediaElement ? this.mediaElement.videoHeight : 0;\n }\n\n get renderWidth(): number {\n return this.container.offsetWidth;\n }\n\n get renderHeight(): number {\n return this.container.offsetHeight;\n }\n\n muteSound(isMuted: boolean): void {\n this.throwIfDisposed();\n trace.info('Subscriber mute change - isMuted =', isMuted);\n if (this._mediaElement && this._mediaElement.muted !== isMuted) {\n this._mediaElement.muted = isMuted;\n this.emitEvent(privateEvents.STREAM_STATE_CHANGE);\n }\n }\n\n onStateChanged(callback: () => void): void {\n this.throwIfDisposed();\n this._emitter.on(privateEvents.STREAM_STATE_CHANGE, callback);\n }\n\n onDispose(callback: () => void): void {\n this.throwIfDisposed();\n this._emitter.on(privateEvents.SUBSCRIBER_DISPOSE, callback);\n }\n\n //#endregion\n\n //#region Non Interface Methods\n\n renderStream(stream: TechseeMediaStream): void {\n this.throwIfDisposed();\n this.stopRenderInternal()\n .then(() => renderMediaStreamToVideoEl(this.container, stream.mediaStream, stream.streamType))\n .then((videoEl) => {\n this._hasAudio = stream.mediaStream.getAudioTracks().length > 0;\n this._hasVideo = stream.mediaStream.getVideoTracks().length > 0;\n\n this.setPlayingState(videoEl, stream.streamKind);\n\n if (this._hasAudio) {\n this.muteSound(false);\n }\n })\n .catch((error) => {\n trace.warn('Error while rendering stream on video element', error);\n });\n }\n\n stopRendering(): Promise<void> {\n return this.stopRenderInternal().then(() => this.setPlayingState());\n }\n\n dispose(): void {\n if (this._isDisposed) {\n return;\n }\n\n this.stopRendering().catch(() => undefined);\n\n this._isDisposed = true;\n this._emitter.removeAllListeners();\n trace.info('Subscriber disposed');\n this.emitEvent(privateEvents.SUBSCRIBER_DISPOSE);\n }\n\n //#endregion\n\n private setPlayingState(mediaElement?: HTMLVideoElement, streamKind?: KnownMediaStreamKind): void {\n this._isPlaying = !!mediaElement;\n this._mediaElement = mediaElement || null;\n\n const finalize = (): void => {\n this.emitEvent(privateEvents.STREAM_STATE_CHANGE);\n trace.info(`Video element ${streamKind || ''} ${this.isPlaying ? 'play' : 'stop'} success`);\n };\n\n if (this._mediaElement && streamKind === KnownMediaStreamKind.Video) {\n if (this._mediaElement.videoHeight === 0) {\n let disposeResizeHandler: any = null;\n const resizeHandler = (): void => {\n disposeResizeHandler && disposeResizeHandler();\n onMediaElementResize(this._mediaElement, this.videoResizeHandler);\n finalize();\n };\n\n disposeResizeHandler = onMediaElementResize(this._mediaElement, resizeHandler);\n } else {\n onMediaElementResize(this._mediaElement, this.videoResizeHandler);\n finalize();\n }\n } else {\n finalize();\n }\n }\n\n private videoResizeHandler(): void {\n trace.info(`Video resize, new size = w: ${this.videoWidth} h:${this.videoHeight}`);\n this.emitEvent(privateEvents.STREAM_STATE_CHANGE);\n }\n\n private stopRenderInternal() {\n if (this._mediaElement && document.body.contains(this._mediaElement)) {\n return removeVideoElementFromDom(this._mediaElement);\n }\n\n return Promise.resolve();\n }\n\n private emitEvent(event: string, eventArgs?: any): void {\n setTimeout(() => {\n this._emitter.emit(event, eventArgs);\n });\n }\n\n private throwIfDisposed(): void {\n if (this._isDisposed) {\n throw new Error('Subscriber is already disposed');\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["MediaSubscriber.js","../src/MediaSubscriber.ts"],"names":["Object","defineProperty","exports","value","TechseeMediaSubscriber","events_1","require","MediaDomUtils_1","MediaConstants_1","MediaTracer_1","Compatibility_1","trace","getMediaTracer","privateEvents","subscriberParams","_isDisposed","_isPlaying","_hasVideo","_hasAudio","_mediaElement","info","videoResizeHandler","bind","_emitter","EventEmitter","_subscriberParams","prototype","get","streamType","enumerable","configurable","container","muted","mediaElement","videoWidth","videoHeight","offsetWidth","offsetHeight","muteSound","isMuted","throwIfDisposed","emitEvent","STREAM_STATE_CHANGE","onStateChanged","callback","on","onDispose","SUBSCRIBER_DISPOSE","renderStream","stream","_this","stopRenderInternal","then","renderMediaStreamToVideoEl","mediaStream","videoEl","getAudioTracks","length","getVideoTracks","setPlayingState","streamKind","error","warn","stopRendering","dispose","undefined","removeAllListeners","finalize","isPlaying","KnownMediaStreamKind","Video","disposeResizeHandler_1","resizeHandler","onMediaElementResize","document","body","contains","removeVideoElementFromDom","Promise","resolve","event","eventArgs","setTimeout","emit","Error"],"mappings":"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","file":"MediaSubscriber.js","sourcesContent":["\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.TechseeMediaSubscriber = void 0;\nvar events_1 = require(\"events\");\nvar MediaDomUtils_1 = require(\"./MediaUtils/MediaDomUtils\");\nvar MediaConstants_1 = require(\"./MediaConstants\");\nvar MediaTracer_1 = require(\"./MediaUtils/MediaTracer\");\nvar Compatibility_1 = require(\"./MediaUtils/Compatibility\");\nvar trace = MediaTracer_1.getMediaTracer('MediaSubscriber');\nvar privateEvents;\n(function (privateEvents) {\n privateEvents[\"STREAM_STATE_CHANGE\"] = \"STREAM_STATE_CHANGE\";\n privateEvents[\"SUBSCRIBER_DISPOSE\"] = \"SUBSCRIBER_DISPOSE\";\n})(privateEvents || (privateEvents = {}));\nvar TechseeMediaSubscriber = /** @class */ (function () {\n function TechseeMediaSubscriber(subscriberParams) {\n this._isDisposed = false;\n this._isPlaying = false;\n this._hasVideo = false;\n this._hasAudio = false;\n this._mediaElement = null;\n trace.info('Subscriber created');\n this.videoResizeHandler = this.videoResizeHandler.bind(this);\n this._emitter = new events_1.EventEmitter();\n this._subscriberParams = subscriberParams;\n }\n Object.defineProperty(TechseeMediaSubscriber.prototype, \"streamType\", {\n //#region IMediaSubscriber\n get: function () {\n return this._subscriberParams.streamType;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(TechseeMediaSubscriber.prototype, \"container\", {\n get: function () {\n return this._subscriberParams.container;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(TechseeMediaSubscriber.prototype, \"isPlaying\", {\n get: function () {\n return this._isPlaying;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(TechseeMediaSubscriber.prototype, \"isSoundMuted\", {\n get: function () {\n if (!this._mediaElement) {\n return true;\n }\n return this._mediaElement.muted;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(TechseeMediaSubscriber.prototype, \"hasAudio\", {\n get: function () {\n return this._hasAudio;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(TechseeMediaSubscriber.prototype, \"hasVideo\", {\n get: function () {\n return this._hasVideo;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(TechseeMediaSubscriber.prototype, \"mediaElement\", {\n get: function () {\n return this._mediaElement;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(TechseeMediaSubscriber.prototype, \"videoWidth\", {\n get: function () {\n return this.mediaElement ? this.mediaElement.videoWidth : 0;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(TechseeMediaSubscriber.prototype, \"videoHeight\", {\n get: function () {\n return this.mediaElement ? this.mediaElement.videoHeight : 0;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(TechseeMediaSubscriber.prototype, \"renderWidth\", {\n get: function () {\n return this.container.offsetWidth;\n },\n enumerable: false,\n configurable: true\n });\n Object.defineProperty(TechseeMediaSubscriber.prototype, \"renderHeight\", {\n get: function () {\n return this.container.offsetHeight;\n },\n enumerable: false,\n configurable: true\n });\n TechseeMediaSubscriber.prototype.muteSound = function (isMuted) {\n this.throwIfDisposed();\n trace.info('Subscriber mute change - isMuted =', isMuted);\n if (this._mediaElement && this._mediaElement.muted !== isMuted) {\n this._mediaElement.muted = isMuted;\n this.emitEvent(privateEvents.STREAM_STATE_CHANGE);\n }\n };\n TechseeMediaSubscriber.prototype.onStateChanged = function (callback) {\n this.throwIfDisposed();\n this._emitter.on(privateEvents.STREAM_STATE_CHANGE, callback);\n };\n TechseeMediaSubscriber.prototype.onDispose = function (callback) {\n this.throwIfDisposed();\n this._emitter.on(privateEvents.SUBSCRIBER_DISPOSE, callback);\n };\n //#endregion\n //#region Non Interface Methods\n TechseeMediaSubscriber.prototype.renderStream = function (stream) {\n var _this = this;\n this.throwIfDisposed();\n this.stopRenderInternal()\n .then(function () { return MediaDomUtils_1.renderMediaStreamToVideoEl(_this.container, stream.mediaStream, stream.streamType); })\n .then(function (videoEl) {\n _this._hasAudio = stream.mediaStream.getAudioTracks().length > 0;\n _this._hasVideo = stream.mediaStream.getVideoTracks().length > 0;\n _this.setPlayingState(videoEl, stream.streamKind);\n if (_this._hasAudio) {\n _this.muteSound(false);\n }\n })\n .catch(function (error) {\n trace.warn('Error while rendering stream on video element', error);\n });\n };\n TechseeMediaSubscriber.prototype.stopRendering = function () {\n var _this = this;\n return this.stopRenderInternal().then(function () { return _this.setPlayingState(); });\n };\n TechseeMediaSubscriber.prototype.dispose = function () {\n if (this._isDisposed) {\n return;\n }\n this.stopRendering().catch(function () { return undefined; });\n this._isDisposed = true;\n this._emitter.removeAllListeners();\n trace.info('Subscriber disposed');\n this.emitEvent(privateEvents.SUBSCRIBER_DISPOSE);\n };\n //#endregion\n TechseeMediaSubscriber.prototype.setPlayingState = function (mediaElement, streamKind) {\n var _this = this;\n this._isPlaying = !!mediaElement;\n this._mediaElement = mediaElement || null;\n var finalize = function () {\n _this.emitEvent(privateEvents.STREAM_STATE_CHANGE);\n trace.info(\"Video element \" + (streamKind || '') + \" \" + (_this.isPlaying ? 'play' : 'stop') + \" success\");\n };\n if (this._mediaElement && streamKind === MediaConstants_1.KnownMediaStreamKind.Video) {\n if (this._mediaElement.videoHeight === 0) {\n var disposeResizeHandler_1 = null;\n var resizeHandler = function () {\n disposeResizeHandler_1 && disposeResizeHandler_1();\n Compatibility_1.onMediaElementResize(_this._mediaElement, _this.videoResizeHandler);\n finalize();\n };\n disposeResizeHandler_1 = Compatibility_1.onMediaElementResize(this._mediaElement, resizeHandler);\n }\n else {\n Compatibility_1.onMediaElementResize(this._mediaElement, this.videoResizeHandler);\n finalize();\n }\n }\n else {\n finalize();\n }\n };\n TechseeMediaSubscriber.prototype.videoResizeHandler = function () {\n trace.info(\"Video resize, new size = w: \" + this.videoWidth + \" h:\" + this.videoHeight);\n this.emitEvent(privateEvents.STREAM_STATE_CHANGE);\n };\n TechseeMediaSubscriber.prototype.stopRenderInternal = function () {\n if (this._mediaElement && document.body.contains(this._mediaElement)) {\n return MediaDomUtils_1.removeVideoElementFromDom(this._mediaElement);\n }\n return Promise.resolve();\n };\n TechseeMediaSubscriber.prototype.emitEvent = function (event, eventArgs) {\n var _this = this;\n setTimeout(function () {\n _this._emitter.emit(event, eventArgs);\n });\n };\n TechseeMediaSubscriber.prototype.throwIfDisposed = function () {\n if (this._isDisposed) {\n throw new Error('Subscriber is already disposed');\n }\n };\n return TechseeMediaSubscriber;\n}());\nexports.TechseeMediaSubscriber = TechseeMediaSubscriber;\n\n//# sourceMappingURL=MediaSubscriber.js.map\n","import {EventEmitter} from 'events';\nimport {Nullable} from '@techsee/techsee-common';\nimport {IMediaSubscriber, MediaSubscriberParams} from './MediaContracts';\nimport {TechseeMediaStream} from './TechseeMediaStream';\nimport {removeVideoElementFromDom, renderMediaStreamToVideoEl} from './MediaUtils/MediaDomUtils';\nimport {KnownMediaStream, KnownMediaStreamKind} from './MediaConstants';\n\nimport {getMediaTracer} from './MediaUtils/MediaTracer';\nimport {onMediaElementResize} from './MediaUtils/Compatibility';\n\nconst trace = getMediaTracer('MediaSubscriber');\n\nenum privateEvents {\n STREAM_STATE_CHANGE = 'STREAM_STATE_CHANGE',\n SUBSCRIBER_DISPOSE = 'SUBSCRIBER_DISPOSE'\n}\n\nexport class TechseeMediaSubscriber implements IMediaSubscriber {\n private _emitter: EventEmitter;\n private _isDisposed = false;\n private _isPlaying = false;\n private _hasVideo = false;\n private _hasAudio = false;\n private _mediaElement: Nullable<HTMLVideoElement> = null;\n private readonly _subscriberParams: MediaSubscriberParams;\n\n constructor(subscriberParams: MediaSubscriberParams) {\n trace.info('Subscriber created');\n\n this.videoResizeHandler = this.videoResizeHandler.bind(this);\n\n this._emitter = new EventEmitter();\n this._subscriberParams = subscriberParams;\n }\n\n //#region IMediaSubscriber\n\n get streamType(): KnownMediaStream {\n return this._subscriberParams.streamType;\n }\n\n get container(): HTMLDivElement {\n return this._subscriberParams.container;\n }\n\n get isPlaying(): boolean {\n return this._isPlaying;\n }\n\n get isSoundMuted(): boolean {\n if (!this._mediaElement) {\n return true;\n }\n\n return this._mediaElement.muted;\n }\n\n get hasAudio(): boolean {\n return this._hasAudio;\n }\n\n get hasVideo(): boolean {\n return this._hasVideo;\n }\n\n get mediaElement(): Nullable<HTMLVideoElement> {\n return this._mediaElement;\n }\n\n get videoWidth(): number {\n return this.mediaElement ? this.mediaElement.videoWidth : 0;\n }\n\n get videoHeight(): number {\n return this.mediaElement ? this.mediaElement.videoHeight : 0;\n }\n\n get renderWidth(): number {\n return this.container.offsetWidth;\n }\n\n get renderHeight(): number {\n return this.container.offsetHeight;\n }\n\n muteSound(isMuted: boolean): void {\n this.throwIfDisposed();\n trace.info('Subscriber mute change - isMuted =', isMuted);\n if (this._mediaElement && this._mediaElement.muted !== isMuted) {\n this._mediaElement.muted = isMuted;\n this.emitEvent(privateEvents.STREAM_STATE_CHANGE);\n }\n }\n\n onStateChanged(callback: () => void): void {\n this.throwIfDisposed();\n this._emitter.on(privateEvents.STREAM_STATE_CHANGE, callback);\n }\n\n onDispose(callback: () => void): void {\n this.throwIfDisposed();\n this._emitter.on(privateEvents.SUBSCRIBER_DISPOSE, callback);\n }\n\n //#endregion\n\n //#region Non Interface Methods\n\n renderStream(stream: TechseeMediaStream): void {\n this.throwIfDisposed();\n this.stopRenderInternal()\n .then(() => renderMediaStreamToVideoEl(this.container, stream.mediaStream, stream.streamType))\n .then((videoEl) => {\n this._hasAudio = stream.mediaStream.getAudioTracks().length > 0;\n this._hasVideo = stream.mediaStream.getVideoTracks().length > 0;\n\n this.setPlayingState(videoEl, stream.streamKind);\n\n if (this._hasAudio) {\n this.muteSound(false);\n }\n })\n .catch((error) => {\n trace.warn('Error while rendering stream on video element', error);\n });\n }\n\n stopRendering(): Promise<void> {\n return this.stopRenderInternal().then(() => this.setPlayingState());\n }\n\n dispose(): void {\n if (this._isDisposed) {\n return;\n }\n\n this.stopRendering().catch(() => undefined);\n\n this._isDisposed = true;\n this._emitter.removeAllListeners();\n trace.info('Subscriber disposed');\n this.emitEvent(privateEvents.SUBSCRIBER_DISPOSE);\n }\n\n //#endregion\n\n private setPlayingState(mediaElement?: HTMLVideoElement, streamKind?: KnownMediaStreamKind): void {\n this._isPlaying = !!mediaElement;\n this._mediaElement = mediaElement || null;\n\n const finalize = (): void => {\n this.emitEvent(privateEvents.STREAM_STATE_CHANGE);\n trace.info(`Video element ${streamKind || ''} ${this.isPlaying ? 'play' : 'stop'} success`);\n };\n\n if (this._mediaElement && streamKind === KnownMediaStreamKind.Video) {\n if (this._mediaElement.videoHeight === 0) {\n let disposeResizeHandler: any = null;\n const resizeHandler = (): void => {\n disposeResizeHandler && disposeResizeHandler();\n onMediaElementResize(this._mediaElement, this.videoResizeHandler);\n finalize();\n };\n\n disposeResizeHandler = onMediaElementResize(this._mediaElement, resizeHandler);\n } else {\n onMediaElementResize(this._mediaElement, this.videoResizeHandler);\n finalize();\n }\n } else {\n finalize();\n }\n }\n\n private videoResizeHandler(): void {\n trace.info(`Video resize, new size = w: ${this.videoWidth} h:${this.videoHeight}`);\n this.emitEvent(privateEvents.STREAM_STATE_CHANGE);\n }\n\n private stopRenderInternal() {\n if (this._mediaElement && document.body.contains(this._mediaElement)) {\n return removeVideoElementFromDom(this._mediaElement);\n }\n\n return Promise.resolve();\n }\n\n private emitEvent(event: string, eventArgs?: any): void {\n setTimeout(() => {\n this._emitter.emit(event, eventArgs);\n });\n }\n\n private throwIfDisposed(): void {\n if (this._isDisposed) {\n throw new Error('Subscriber is already disposed');\n }\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/MultiParty/DetectWebRtcService.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,gBAAgB,EAAC,MAAM,uDAAuD,CAAC;AAKvF,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAE7C,MAAM,WAAW,mBAAoB,SAAQ,gBAAgB;IAEzD,IAAI,CAAC,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,eAAe;IAC5B,aAAa,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAAC;CAC9C;AAED,qBAAa,mBAAoB,YAAW,eAAe;IACvD,OAAO,CAAC,aAAa,CAA6B;
|
|
1
|
+
{"version":3,"sources":["../src/MultiParty/DetectWebRtcService.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,gBAAgB,EAAC,MAAM,uDAAuD,CAAC;AAKvF,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAE7C,MAAM,WAAW,mBAAoB,SAAQ,gBAAgB;IAEzD,IAAI,CAAC,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,eAAe;IAC5B,aAAa,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAAC;CAC9C;AAED,qBAAa,mBAAoB,YAAW,eAAe;IACvD,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAgC;IAC/D,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;gBAElC,mBAAmB,EAAE,MAAM;IAMvC,aAAa,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAsC1C,OAAO,CAAC,MAAM,CAAC,gBAAgB;CASlC","file":"DetectWebRtcService.d.ts","sourcesContent":["import {Nullable} from '@techsee/techsee-common';\nimport {ClientWebRtcInfo} from '@techsee/techsee-common/lib/data-contracts/ClientInfo';\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-ignore\nimport detectRTC from '@techsee/techsee-common/lib/detectrtc.js';\n\nexport const DETECT_RTC_ADVISED_TIMEOUT = 20;\n\nexport interface WebRtcNativeService extends ClientWebRtcInfo {\n // eslint-disable-next-line @typescript-eslint/ban-types\n load(doneCb: Function): void;\n}\n\nexport interface IWebRtcDetector {\n getWebRtcInfo(): Promise<ClientWebRtcInfo>;\n}\n\nexport class DetectWebRtcService implements IWebRtcDetector {\n private _cachedResult: Nullable<ClientWebRtcInfo>;\n private readonly _nativeService: Nullable<WebRtcNativeService>;\n private readonly _detectionTimeoutSec: number;\n\n constructor(detectionTimeoutSec: number) {\n this._cachedResult = null;\n this._nativeService = detectRTC;\n this._detectionTimeoutSec = detectionTimeoutSec * 1000;\n }\n\n getWebRtcInfo(): Promise<ClientWebRtcInfo> {\n if (this._cachedResult) {\n return Promise.resolve(this._cachedResult);\n }\n\n if (!this._nativeService) {\n this._cachedResult = DetectWebRtcService.getDefaultResult();\n\n return Promise.resolve(this._cachedResult);\n }\n\n return new Promise((resolve) => {\n let timeoutPtr: any = setTimeout(() => {\n timeoutPtr = null;\n this._cachedResult = DetectWebRtcService.getDefaultResult();\n resolve(this._cachedResult);\n }, this._detectionTimeoutSec);\n\n this._nativeService!.load(() => {\n if (timeoutPtr === null) {\n return;\n }\n\n clearTimeout(timeoutPtr);\n\n this._cachedResult = {\n isWebRTCSupported: this._nativeService!.isWebRTCSupported,\n isWebsiteHasWebcamPermissions: this._nativeService!.isWebsiteHasWebcamPermissions,\n isWebsiteHasMicrophonePermissions: this._nativeService!.isWebsiteHasMicrophonePermissions,\n isGetUserMediaSupported: this._nativeService!.isGetUserMediaSupported,\n isApplyConstraintsSupported: this._nativeService!.isApplyConstraintsSupported\n };\n\n resolve(this._cachedResult);\n });\n });\n }\n\n private static getDefaultResult() {\n return {\n isWebRTCSupported: false,\n isWebsiteHasWebcamPermissions: false,\n isWebsiteHasMicrophonePermissions: false,\n isGetUserMediaSupported: false,\n isApplyConstraintsSupported: false\n };\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/MultiParty/MediaCapabilitiesService.ts"],"names":[],"mappings":"AA6BA,qBAAa,wBAAwB;IACjC,OAAO,CAAC,MAAM,CAAC,mBAAmB,CAAsB;
|
|
1
|
+
{"version":3,"sources":["../src/MultiParty/MediaCapabilitiesService.ts"],"names":[],"mappings":"AA6BA,qBAAa,wBAAwB;IACjC,OAAO,CAAC,MAAM,CAAC,mBAAmB,CAAsB;IACxD,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAA+B;IAC/D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA2B;IAClD,OAAO,CAAC,MAAM,CAAC,WAAW,CAAuD;IACjF,OAAO,CAAC,MAAM,CAAC,cAAc,CAA8C;IAC3E,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAU;IAC7C,OAAO,CAAC,MAAM,CAAC,mBAAmB,CAAS;IAC3C,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAS;IAC5C,OAAO,CAAC,MAAM,CAAC,YAAY,CAAoC;IAC/D,OAAO,CAAC,kBAAkB,CAA6B;WAEnC,WAAW,IAAI,OAAO,CAAC,wBAAwB,CAAC;mBAoB/C,IAAI;IAmBlB,SAAS,IAAI,OAAO;IAIpB,aAAa,IAAI,OAAO;IAMxB,SAAS,IAAI,OAAO;IAIpB,wBAAwB,IAAI,OAAO;IAI1C,OAAO,CAAC,MAAM,CAAC,iBAAiB;WAaZ,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAWnD,IAAW,mBAAmB,YAE7B;IAED,IAAW,kBAAkB,WAE5B;IAED,IAAW,mBAAmB,WAE7B;CACJ","file":"MediaCapabilitiesService.d.ts","sourcesContent":["// eslint-disable-next-line @typescript-eslint/no-var-requires\nimport {getMediaTracer} from '../MediaUtils/MediaTracer';\nimport {DETECT_RTC_ADVISED_TIMEOUT, DetectWebRtcService} from './DetectWebRtcService';\nimport {ClientWebRtcInfo} from '../MediaContracts';\nimport filter from 'lodash/filter';\nimport {TsEnvironmentDetect} from '@techsee/techsee-common/lib/helpers/ts-environment-detect';\nimport {CameraTypes} from '../MediaConstants';\nimport last from 'lodash/last';\nimport first from 'lodash/first';\nimport get from 'lodash/get';\nimport {Nullable} from '@techsee/techsee-common';\n\nconst trace = getMediaTracer('MediaCapabilitiesService');\n\nfunction enumerateMediaDevices(): Promise<MediaDeviceInfo[]> {\n return new Promise((resolve, reject) => {\n if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) {\n return navigator.mediaDevices.enumerateDevices().then(resolve).catch(reject);\n }\n\n return resolve([]);\n });\n}\n\ntype GroupedDevices = {\n video: Nullable<{back?: MediaDeviceInfo; front?: MediaDeviceInfo}>;\n audio: Nullable<MediaDeviceInfo>;\n};\n\nexport class MediaCapabilitiesService {\n private static detectWebRtcService: DetectWebRtcService;\n private static webRtcSupportInfo: ClientWebRtcInfo | undefined;\n private static instance: MediaCapabilitiesService;\n private static devicesList: {video: MediaDeviceInfo[]; audio: MediaDeviceInfo[]};\n private static groupedDevices: GroupedDevices = {video: null, audio: null};\n private static _supportSwitchCamera: boolean;\n private static _backCameraDeviceId: string;\n private static _frontCameraDeviceId: string;\n private static _initPromise: Promise<MediaCapabilitiesService>;\n private environmentService = new TsEnvironmentDetect();\n\n public static async getInstance(): Promise<MediaCapabilitiesService> {\n try {\n if (this.instance) {\n return this.instance;\n }\n\n if (this._initPromise) {\n return await this._initPromise;\n }\n\n this._initPromise = this.init();\n\n return await this._initPromise;\n } catch (err) {\n trace.error(JSON.stringify(err));\n\n throw err;\n }\n }\n\n private static async init(): Promise<MediaCapabilitiesService> {\n try {\n this.instance = new MediaCapabilitiesService();\n\n this.detectWebRtcService = new DetectWebRtcService(DETECT_RTC_ADVISED_TIMEOUT);\n\n this.webRtcSupportInfo = await this.detectWebRtcService.getWebRtcInfo();\n await this.setDevicesList();\n\n trace.info('initialize media capabilities helper successfully');\n\n return this.instance;\n } catch (err) {\n trace.error(JSON.stringify(err));\n\n throw err;\n }\n }\n\n public hasCamera(): boolean {\n return MediaCapabilitiesService.devicesList.video.length > 0 && this.hasGetUserMediaSupported();\n }\n\n public hasMicrophone(): boolean {\n return this.environmentService.isIOS()\n ? this.hasGetUserMediaSupported()\n : MediaCapabilitiesService.devicesList.audio.length > 0 && this.hasGetUserMediaSupported();\n }\n\n public hasWebRTC(): boolean {\n return MediaCapabilitiesService.webRtcSupportInfo!.isWebRTCSupported;\n }\n\n public hasGetUserMediaSupported(): boolean {\n return MediaCapabilitiesService.webRtcSupportInfo!.isGetUserMediaSupported;\n }\n\n private static setGroupedDevices() {\n trace.info('setGroupedDevices', this.devicesList);\n this.groupedDevices = {\n video: this.devicesList.video\n ? {[CameraTypes.BACK]: last(this.devicesList.video), [CameraTypes.FRONT]: first(this.devicesList.video)}\n : null,\n audio: last(this.devicesList.audio) || null\n };\n this._supportSwitchCamera = this.devicesList.video.length > 1;\n this._backCameraDeviceId = get(this.groupedDevices, `video.${CameraTypes.BACK}.deviceId`) || '';\n this._frontCameraDeviceId = get(this.groupedDevices, `video.${CameraTypes.FRONT}.deviceId`) || '';\n }\n\n public static async setDevicesList(): Promise<void> {\n const localDevicesList = this.webRtcSupportInfo!.isWebRTCSupported ? await enumerateMediaDevices() : [];\n\n this.devicesList = {\n video: filter(localDevicesList, (device) => device.kind.toLowerCase() === 'videoinput'),\n audio: filter(localDevicesList, (device) => device.kind.toLowerCase() === 'audioinput')\n };\n\n this.setGroupedDevices();\n }\n\n public get supportSwitchCamera() {\n return MediaCapabilitiesService._supportSwitchCamera;\n }\n\n public get backCameraDeviceId() {\n return MediaCapabilitiesService._backCameraDeviceId;\n }\n\n public get frontCameraDeviceId() {\n return MediaCapabilitiesService._frontCameraDeviceId;\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/MultiParty/OpentokMultiPartyService.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmB,WAAW,EAAE,eAAe,EAAC,MAAM,aAAa,CAAC;AAG3E,OAAO,EAGH,iBAAiB,EACjB,cAAc,EAEjB,MAAM,qBAAqB,CAAC;AAc7B,qBAAa,wBAAyB,SAAQ,iBAAiB;IASpB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IARxE,OAAO,CAAC,cAAc,CAA+B;IAErD,OAAO,CAAC,OAAO,CAAC,CAAa;IAE7B,OAAO,CAAC,gBAAgB,CAAC,CAAe;IAExC,OAAO,CAAC,QAAQ,CAAC,CAAmB;gBAExB,QAAQ,EAAE,eAAe,EAAmB,gBAAgB,EAAE,CAAC,YAAY,CAAC,EAAE,MAAM,KAAK,MAAM;IAM3G,IAAI,eAAe,IAAI,MAAM,CAE5B;IAED,IAAI,WAAW,IAAI,cAAc,EAAE,CAElC;IAEM,OAAO,gBAAiB,WAAW,KAAG,IAAI,CA+B/C;IAEK,UAAU,QAAO,IAAI,CAW1B;IAEK,YAAY,iBAAkB,MAAM,KAAG,MAAM,GAAG,IAAI,CAUzD;IAEF,OAAO,CAAC,eAAe,CAwBrB;IAEK,YAAY,eAAsB,OAAO,KAAG,QAAQ,IAAI,CAAC,CAmB9D;IAEK,YAAY,uBAA8B,OAAO,KAAG,QAAQ,IAAI,CAAC,CAiBtE;IAEK,YAAY,QAAa,QAAQ,IAAI,CAAC,CAmC3C;IAEF,OAAO,CAAC,aAAa;IAIrB,aAAa,aAAoB,eAAe,KAAG,QAAQ,IAAI,CAAC,CA4F9D;IAEF,mBAAmB,aAAoB,eAAe,KAAG,QAAQ,IAAI,CAAC,CAkBpE;IAEF,oBAAoB,kBAIjB,QAAQ,IAAI,CAAC,CAkCd;IAEF,4BAA4B,UAAW,GAAG,KAAG,IAAI,CAY/C;IAEF,sBAAsB,kBAKnB,IAAI,CAcL;IAEF,wBAAwB,kBAIrB,IAAI,CAmBL;IAEF,0BAA0B,kBAKvB,IAAI,CAmBL;IAEF,kBAAkB,iCAAoB,IAAI,CAExC;IAEF,OAAO,CAAC,0BAA0B,CAIhC;IAEF,OAAO,CAAC,gBAAgB,CA0BtB;CACL","file":"OpentokMultiPartyService.d.ts","sourcesContent":["import {MultiPartyCameraTypes, SessionClientRole} from '../MediaConstants';\nimport {getMediaTracer} from '../MediaUtils';\nimport {BrandingSettings, Credentials, SessionSettings} from './Contracts';\nimport {MediaCapabilitiesService} from './MediaCapabilitiesService';\nimport {checkAudioCapabilities, checkIsMobile, checkVideoCapabilities} from './MediaCapabilitiesUtils';\nimport {\n DEFAULT_AVATAR_URI,\n MULTI_PARTY_EVENTS,\n MultiPartyService,\n SubscriberInfo,\n maxParticipantsNumber\n} from './MultiPartyService';\n\nconst trace = getMediaTracer('Multiparty');\n\n// The preferred camera position to use for the video source\nenum OpentokFacingMode {\n USER = 'user',\n ENVIRONMENT = 'environment'\n}\n\ntype OpentokSubscriberInfo = SubscriberInfo & {\n subscriber: OT.Subscriber;\n};\n\nexport class OpentokMultiPartyService extends MultiPartyService {\n private subscriberList: OpentokSubscriberInfo[] = [];\n\n private session?: OT.Session;\n\n private currentPublisher?: OT.Publisher;\n\n private branding?: BrandingSettings;\n\n constructor(settings: SessionSettings, private readonly getParticipantId: (connectionId?: string) => string) {\n super(settings);\n\n this.branding = settings.branding;\n }\n\n get ownConnectionId(): string {\n return this.session?.connection?.connectionId || '';\n }\n\n get subscribers(): SubscriberInfo[] {\n return [...this.subscriberList];\n }\n\n public connect = (credentials: Credentials): void => {\n try {\n this.session = OT.initSession(credentials.apiKey, credentials.sessionId);\n\n this.session.on('connectionCreated', this.connectionCreatedHandler);\n this.session.on('connectionDestroyed', this.connectionDestroyedHandler);\n this.session.on('streamCreated', this.streamCreatedHandler);\n this.session.on('streamDestroyed', this.streamDestroyedHandler);\n this.session.on('streamPropertyChanged', this.streamPropertyChangedHandler);\n this.session.on('sessionDisconnected', this.sessionDisconnectedHandler);\n\n this.session.connect(credentials.token, (error?: any) => {\n if (error) {\n this.session?.off();\n this.session = undefined;\n this.traceErrorCallback(error);\n this.emit(MULTI_PARTY_EVENTS.connectFailed, {\n meta: {err: JSON.stringify(error)}\n });\n\n return;\n }\n\n this.createPublisher(this.settings);\n });\n } catch (err) {\n trace.error(JSON.stringify(err));\n this.emit(MULTI_PARTY_EVENTS.connectFailed, {\n meta: {err: JSON.stringify(err)}\n });\n }\n };\n\n public disconnect = (): void => {\n try {\n trace.info('Disconnecting from session');\n\n if (this.session) {\n this.session.disconnect();\n trace.info('Disconnection from session completed');\n }\n } catch (err) {\n trace.error(JSON.stringify(err));\n }\n };\n\n public takeSnapshot = (connectionId: string): string | null => {\n if (!connectionId) {\n return null;\n }\n\n if (connectionId === this.ownConnectionId) {\n return this.currentPublisher?.getImgData() || null;\n }\n\n return this.subscribers.find((s) => s.connectionId === connectionId)?.subscriber.getImgData() || null;\n };\n\n private createPublisher = async (settings: Readonly<SessionSettings>): Promise<void> => {\n const canStartWithAudio = await checkAudioCapabilities();\n const canStartWithVideo = await checkVideoCapabilities();\n\n if (!canStartWithAudio && !canStartWithVideo) {\n // FIXME: use emitter\n trace.error('Did not create publisher due to lack of media capabilities.');\n\n return;\n }\n\n if (!this.currentPublisher) {\n await this.initPublisher({\n ...settings,\n cameraEnabled: settings.cameraEnabled && canStartWithVideo,\n startWithAudioMuted: settings.startWithAudioMuted && canStartWithAudio\n });\n\n if (!this.currentPublisher) {\n throw new Error('Creating session requires initialized publisher');\n }\n\n this.currentPublisher && this.session?.publish(this.currentPublisher, this.traceErrorCallback);\n }\n };\n\n public publishAudio = async (shouldMute: boolean): Promise<void> => {\n const canPublishAudio = await checkAudioCapabilities();\n\n // why do we care about audio if 'shouldMute' is true?\n if (shouldMute && !canPublishAudio) {\n trace.error('could not publish audio');\n\n return;\n }\n\n if (!this.currentPublisher) {\n await this.createPublisher({\n ...this.settings,\n // so if 'shouldMute' is true then we DON't mute?\n startWithAudioMuted: !shouldMute\n });\n } else {\n this.currentPublisher.publishAudio(shouldMute);\n }\n };\n\n public publishVideo = async (shouldPublishVideo: boolean): Promise<void> => {\n const canPublishVideo = await checkVideoCapabilities();\n\n if (shouldPublishVideo && !canPublishVideo) {\n trace.error('could not publish video');\n\n return;\n }\n\n if (!this.currentPublisher) {\n await this.createPublisher({\n ...this.settings,\n cameraEnabled: shouldPublishVideo\n });\n } else {\n this.currentPublisher.publishVideo(shouldPublishVideo);\n }\n };\n\n public switchCamera = async (): Promise<void> => {\n try {\n const mediaCapabilitiesService = await MediaCapabilitiesService.getInstance();\n\n if (!mediaCapabilitiesService.supportSwitchCamera) {\n throw new Error('Not Support switch camera');\n }\n\n let deviceId = '';\n let cameraType: MultiPartyCameraTypes;\n\n switch (this.settings.selectedCamera) {\n case MultiPartyCameraTypes.BACK:\n cameraType = MultiPartyCameraTypes.FRONT;\n deviceId = mediaCapabilitiesService.frontCameraDeviceId;\n break;\n case MultiPartyCameraTypes.FRONT:\n cameraType = MultiPartyCameraTypes.BACK;\n deviceId = mediaCapabilitiesService.backCameraDeviceId;\n break;\n default:\n throw new Error(`cameraType not valid: ${this.settings.selectedCamera}`);\n }\n\n return await this.currentPublisher?.setVideoSource(deviceId).then(() => {\n this.settings.selectedCamera = cameraType;\n this.emit(MULTI_PARTY_EVENTS.cameraSourceChanged, {\n cameraSource: cameraType\n });\n trace.info(`Success to switch camera to cameraType: ${cameraType}`);\n });\n } catch (err) {\n trace.error(JSON.stringify(err));\n throw err;\n }\n };\n\n private getClientRole(connection?: any): SessionClientRole {\n return (connection?.data && JSON.parse(connection.data).clientRole) || SessionClientRole.USER;\n }\n\n initPublisher = async (settings: SessionSettings): Promise<void> => {\n try {\n const {selectedCamera} = settings;\n const isMobile = checkIsMobile();\n\n const mediaCapabilitiesService = await MediaCapabilitiesService.getInstance();\n const {frontCameraDeviceId, backCameraDeviceId} = mediaCapabilitiesService;\n\n const videoSourceId =\n (selectedCamera === MultiPartyCameraTypes.FRONT ? frontCameraDeviceId : backCameraDeviceId) ||\n undefined;\n\n const mobileCameraType =\n settings.selectedCamera === MultiPartyCameraTypes.FRONT\n ? OpentokFacingMode.USER\n : OpentokFacingMode.ENVIRONMENT;\n\n this.currentPublisher = OT.initPublisher(\n this.getParticipantId(this.session?.connection?.connectionId || ''),\n {\n insertMode: 'append',\n fitMode: 'cover',\n width: '100%',\n height: '100%',\n style: {\n buttonDisplayMode: 'off',\n archiveStatusDisplayMode: 'off',\n audioLevelDisplayMode: 'off',\n backgroundImageURI: this.branding?.backgroundImageURI || DEFAULT_AVATAR_URI\n },\n publishAudio: !settings.startWithAudioMuted,\n publishVideo: settings.cameraEnabled,\n videoSource: !isMobile ? videoSourceId : undefined,\n facingMode: isMobile || !videoSourceId ? mobileCameraType : undefined\n },\n this.traceErrorCallback\n );\n if (!this.currentPublisher) {\n trace.error('failed to init publisher');\n } else {\n this.currentPublisher.on('videoElementCreated', (event: any) => {\n trace.info('videoElementCreated', event);\n\n this.emit(MULTI_PARTY_EVENTS.videoElementCreated, event);\n });\n this.currentPublisher.on('streamDestroyed', (event: any) => {\n trace.info('Publisher stream destroyed', event);\n\n this.emit(MULTI_PARTY_EVENTS.streamDestroyed, {\n event: event,\n participantId: this.getParticipantId(event?.stream?.connection.connectionId)\n });\n });\n this.currentPublisher.on('accessDenied', (event: any) => {\n trace.info('Publisher denied media permissions', event);\n\n const connection = this.session?.connection;\n const connectionId = connection?.connectionId;\n const participantId = this.getParticipantId(connectionId);\n const isOwnConnection = this.ownConnectionId === connectionId;\n\n this.emit(MULTI_PARTY_EVENTS.mediaDeviceAccessDenied, {\n connectionId,\n isOwnConnection,\n participantId\n });\n });\n this.currentPublisher.on('streamCreated', async (event: any) => {\n await MediaCapabilitiesService.setDevicesList();\n\n const participantId = this.getParticipantId(event?.stream?.connection.connectionId);\n\n this.emit(MULTI_PARTY_EVENTS.streamCreated, {\n event: event,\n participantId: participantId,\n startWithAudioMuted: this.settings.startWithAudioMuted,\n hasAudio: event?.stream?.hasAudio,\n hasVideo: event?.stream?.hasVideo,\n isOwnConnection: this.ownConnectionId === event?.stream?.connection?.connectionId\n });\n\n trace.info('publisher stream created', {\n event,\n hasAudio: this.currentPublisher?.stream?.hasAudio,\n participantId\n });\n });\n }\n } catch (err) {\n trace.error(JSON.stringify(err));\n throw err;\n }\n };\n\n reinitializeStreams = async (settings: SessionSettings): Promise<void> => {\n if (this.currentPublisher) {\n this.currentPublisher.destroy();\n this.currentPublisher = undefined;\n }\n await this.createPublisher(settings);\n\n this.subscriberList = this.subscriberList.map((s) => {\n const {connectionId, subscriber} = s;\n const {stream} = subscriber;\n\n this.session?.unsubscribe(subscriber);\n\n return {\n ...s,\n subscriber: this.createSubscriber(stream, connectionId)\n };\n });\n };\n\n streamCreatedHandler = async (\n event: any & {\n stream: any;\n }\n ): Promise<void> => {\n trace.info('streamCreatedHandler', event.stream);\n\n const {stream} = event;\n const {connection} = stream;\n const {connectionId} = connection;\n const participantId = this.getParticipantId(connectionId);\n\n await MediaCapabilitiesService.setDevicesList();\n\n if (this.ownConnectionId && stream && connectionId !== this.ownConnectionId) {\n if (this.subscriberList.length < maxParticipantsNumber - 1) {\n const subscriber = this.createSubscriber(stream, connectionId);\n\n this.subscriberList.push({\n subscriber,\n connectionId,\n clientRole: this.getClientRole(connection)\n });\n } else {\n trace.info(\n `a new subscriber cannot be added, as the number of participants reached its limit: ${maxParticipantsNumber}`\n );\n }\n }\n\n this.emit(MULTI_PARTY_EVENTS.streamCreated, {\n event: event,\n participantId: participantId,\n startWithAudioMuted: this.settings.startWithAudioMuted,\n isOwnConnection: this.ownConnectionId === connectionId,\n hasVideo: event?.stream?.hasVideo,\n hasAudio: event?.stream?.hasAudio\n });\n };\n\n streamPropertyChangedHandler = (event: any): void => {\n trace.info('streamPropertyChangedHandler', event);\n\n const {stream} = event;\n\n this.emit(MULTI_PARTY_EVENTS.videoAudioStatus, {\n hasVideo: stream?.hasVideo,\n hasAudio: stream?.hasAudio,\n participantId: this.getParticipantId(stream?.connection.connectionId),\n isOwnConnection: this.ownConnectionId === stream?.connection?.connectionId,\n stream\n });\n };\n\n streamDestroyedHandler = (\n event: any & {\n stream: any;\n reason: string;\n }\n ): void => {\n trace.info('streamDestroyedHandler', event);\n\n const {stream} = event;\n const destroyedConnectionId = stream && stream.connection && stream.connection.connectionId;\n\n this.subscriberList = this.subscriberList.filter(\n (subscriber) => subscriber.connectionId !== destroyedConnectionId\n );\n\n this.emit(MULTI_PARTY_EVENTS.streamDestroyed, {\n event: event,\n participantId: this.getParticipantId(event?.stream?.connection.connectionId)\n });\n };\n\n connectionCreatedHandler = (\n event: any & {\n connection: any;\n }\n ): void => {\n trace.info('connectionCreatedHandler', event);\n\n const {connection} = event;\n\n if (!this.ownConnectionId || !connection) {\n return;\n }\n\n this.emit(MULTI_PARTY_EVENTS.participantsNumberChanged, this.subscriberList.length);\n\n const clientRole = this.getClientRole(connection);\n\n this.emit(MULTI_PARTY_EVENTS.connectionCreated, {\n clientRole,\n event,\n participantId: this.getParticipantId(connection.connectionId),\n isOwnConnection: this.ownConnectionId === connection.connectionId\n });\n };\n\n connectionDestroyedHandler = (\n event: any & {\n connection: any;\n reason: string;\n }\n ): void => {\n const {connection} = event;\n const clientRole = this.getClientRole(connection);\n\n trace.info('connectionDestroyedHandler', event);\n\n this.emit(MULTI_PARTY_EVENTS.connectionDestroyed, {\n clientRole,\n event,\n participantId: this.getParticipantId(event?.connection.connectionId),\n isOwnConnection: this.ownConnectionId === connection.connectionId\n });\n\n if (!this.ownConnectionId || !connection || connection.connectionId !== this.ownConnectionId) {\n return;\n }\n\n this.emit(MULTI_PARTY_EVENTS.participantsNumberChanged, this.subscriberList.length);\n this.session = undefined;\n };\n\n traceErrorCallback = (error?: Error): void => {\n trace.error(JSON.stringify(error));\n };\n\n private sessionDisconnectedHandler = (event: any) => {\n trace.info('sessionDisconnectedHandler', event);\n // ToDo: Below line makes no sense (according to the OT types). This is not how OT signals work.\n // this?.session?.signal({ retryAfterReconnect: true });\n };\n\n private createSubscriber = (stream: any, connectionId: string): OT.Subscriber => {\n // ToDo: OT session can be undefined. This needs to be fixed.\n const subscriber = this.session!.subscribe(\n stream,\n this.getParticipantId(connectionId),\n {\n insertMode: 'append',\n fitMode: 'cover',\n width: '100%',\n height: '100%',\n style: {\n buttonDisplayMode: 'off',\n audioLevelDisplayMode: 'off',\n videoDisabledDisplayMode: 'off',\n backgroundImageURI: this.branding?.backgroundImageURI || DEFAULT_AVATAR_URI\n }\n },\n (err: any) => {\n this.traceErrorCallback(err);\n subscriber?.on('videoElementCreated', (event: any) => {\n trace.info('videoElementCreated', event);\n });\n }\n );\n\n return subscriber;\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/MultiParty/OpentokMultiPartyService.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmB,WAAW,EAAE,eAAe,EAAC,MAAM,aAAa,CAAC;AAG3E,OAAO,EAGH,iBAAiB,EACjB,cAAc,EAEjB,MAAM,qBAAqB,CAAC;AAc7B,qBAAa,wBAAyB,SAAQ,iBAAiB;IAMpB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IALxE,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,OAAO,CAAC,CAAa;IAC7B,OAAO,CAAC,gBAAgB,CAAC,CAAe;IACxC,OAAO,CAAC,QAAQ,CAAC,CAAmB;gBAExB,QAAQ,EAAE,eAAe,EAAmB,gBAAgB,EAAE,CAAC,YAAY,CAAC,EAAE,MAAM,KAAK,MAAM;IAM3G,IAAI,eAAe,IAAI,MAAM,CAE5B;IAED,IAAI,WAAW,IAAI,cAAc,EAAE,CAElC;IAEM,OAAO,gBAAiB,WAAW,KAAG,IAAI,CA+B/C;IAEK,UAAU,QAAO,IAAI,CAW1B;IAEK,YAAY,iBAAkB,MAAM,KAAG,MAAM,GAAG,IAAI,CAUzD;IAEF,OAAO,CAAC,eAAe,CAwBrB;IAEK,YAAY,eAAsB,OAAO,KAAG,QAAQ,IAAI,CAAC,CAmB9D;IAEK,YAAY,uBAA8B,OAAO,KAAG,QAAQ,IAAI,CAAC,CAiBtE;IAEK,YAAY,QAAa,QAAQ,IAAI,CAAC,CAmC3C;IAEF,OAAO,CAAC,aAAa;IAIrB,aAAa,aAAoB,eAAe,KAAG,QAAQ,IAAI,CAAC,CA4F9D;IAEF,mBAAmB,aAAoB,eAAe,KAAG,QAAQ,IAAI,CAAC,CAkBpE;IAEF,oBAAoB,kBAIjB,QAAQ,IAAI,CAAC,CAkCd;IAEF,4BAA4B,UAAW,GAAG,KAAG,IAAI,CAY/C;IAEF,sBAAsB,kBAKnB,IAAI,CAcL;IAEF,wBAAwB,kBAIrB,IAAI,CAmBL;IAEF,0BAA0B,kBAKvB,IAAI,CAmBL;IAEF,kBAAkB,iCAAoB,IAAI,CAExC;IAEF,OAAO,CAAC,0BAA0B,CAIhC;IAEF,OAAO,CAAC,gBAAgB,CA0BtB;CACL","file":"OpentokMultiPartyService.d.ts","sourcesContent":["import {MultiPartyCameraTypes, SessionClientRole} from '../MediaConstants';\nimport {getMediaTracer} from '../MediaUtils';\nimport {BrandingSettings, Credentials, SessionSettings} from './Contracts';\nimport {MediaCapabilitiesService} from './MediaCapabilitiesService';\nimport {checkAudioCapabilities, checkIsMobile, checkVideoCapabilities} from './MediaCapabilitiesUtils';\nimport {\n DEFAULT_AVATAR_URI,\n MULTI_PARTY_EVENTS,\n MultiPartyService,\n SubscriberInfo,\n maxParticipantsNumber\n} from './MultiPartyService';\n\nconst trace = getMediaTracer('Multiparty');\n\n// The preferred camera position to use for the video source\nenum OpentokFacingMode {\n USER = 'user',\n ENVIRONMENT = 'environment'\n}\n\ntype OpentokSubscriberInfo = SubscriberInfo & {\n subscriber: OT.Subscriber;\n};\n\nexport class OpentokMultiPartyService extends MultiPartyService {\n private subscriberList: OpentokSubscriberInfo[] = [];\n private session?: OT.Session;\n private currentPublisher?: OT.Publisher;\n private branding?: BrandingSettings;\n\n constructor(settings: SessionSettings, private readonly getParticipantId: (connectionId?: string) => string) {\n super(settings);\n\n this.branding = settings.branding;\n }\n\n get ownConnectionId(): string {\n return this.session?.connection?.connectionId || '';\n }\n\n get subscribers(): SubscriberInfo[] {\n return [...this.subscriberList];\n }\n\n public connect = (credentials: Credentials): void => {\n try {\n this.session = OT.initSession(credentials.apiKey, credentials.sessionId);\n\n this.session.on('connectionCreated', this.connectionCreatedHandler);\n this.session.on('connectionDestroyed', this.connectionDestroyedHandler);\n this.session.on('streamCreated', this.streamCreatedHandler);\n this.session.on('streamDestroyed', this.streamDestroyedHandler);\n this.session.on('streamPropertyChanged', this.streamPropertyChangedHandler);\n this.session.on('sessionDisconnected', this.sessionDisconnectedHandler);\n\n this.session.connect(credentials.token, (error?: any) => {\n if (error) {\n this.session?.off();\n this.session = undefined;\n this.traceErrorCallback(error);\n this.emit(MULTI_PARTY_EVENTS.connectFailed, {\n meta: {err: JSON.stringify(error)}\n });\n\n return;\n }\n\n this.createPublisher(this.settings);\n });\n } catch (err) {\n trace.error(JSON.stringify(err));\n this.emit(MULTI_PARTY_EVENTS.connectFailed, {\n meta: {err: JSON.stringify(err)}\n });\n }\n };\n\n public disconnect = (): void => {\n try {\n trace.info('Disconnecting from session');\n\n if (this.session) {\n this.session.disconnect();\n trace.info('Disconnection from session completed');\n }\n } catch (err) {\n trace.error(JSON.stringify(err));\n }\n };\n\n public takeSnapshot = (connectionId: string): string | null => {\n if (!connectionId) {\n return null;\n }\n\n if (connectionId === this.ownConnectionId) {\n return this.currentPublisher?.getImgData() || null;\n }\n\n return this.subscribers.find((s) => s.connectionId === connectionId)?.subscriber.getImgData() || null;\n };\n\n private createPublisher = async (settings: Readonly<SessionSettings>): Promise<void> => {\n const canStartWithAudio = await checkAudioCapabilities();\n const canStartWithVideo = await checkVideoCapabilities();\n\n if (!canStartWithAudio && !canStartWithVideo) {\n // FIXME: use emitter\n trace.error('Did not create publisher due to lack of media capabilities.');\n\n return;\n }\n\n if (!this.currentPublisher) {\n await this.initPublisher({\n ...settings,\n cameraEnabled: settings.cameraEnabled && canStartWithVideo,\n startWithAudioMuted: settings.startWithAudioMuted && canStartWithAudio\n });\n\n if (!this.currentPublisher) {\n throw new Error('Creating session requires initialized publisher');\n }\n\n this.currentPublisher && this.session?.publish(this.currentPublisher, this.traceErrorCallback);\n }\n };\n\n public publishAudio = async (shouldMute: boolean): Promise<void> => {\n const canPublishAudio = await checkAudioCapabilities();\n\n // why do we care about audio if 'shouldMute' is true?\n if (shouldMute && !canPublishAudio) {\n trace.error('could not publish audio');\n\n return;\n }\n\n if (!this.currentPublisher) {\n await this.createPublisher({\n ...this.settings,\n // so if 'shouldMute' is true then we DON't mute?\n startWithAudioMuted: !shouldMute\n });\n } else {\n this.currentPublisher.publishAudio(shouldMute);\n }\n };\n\n public publishVideo = async (shouldPublishVideo: boolean): Promise<void> => {\n const canPublishVideo = await checkVideoCapabilities();\n\n if (shouldPublishVideo && !canPublishVideo) {\n trace.error('could not publish video');\n\n return;\n }\n\n if (!this.currentPublisher) {\n await this.createPublisher({\n ...this.settings,\n cameraEnabled: shouldPublishVideo\n });\n } else {\n this.currentPublisher.publishVideo(shouldPublishVideo);\n }\n };\n\n public switchCamera = async (): Promise<void> => {\n try {\n const mediaCapabilitiesService = await MediaCapabilitiesService.getInstance();\n\n if (!mediaCapabilitiesService.supportSwitchCamera) {\n throw new Error('Not Support switch camera');\n }\n\n let deviceId = '';\n let cameraType: MultiPartyCameraTypes;\n\n switch (this.settings.selectedCamera) {\n case MultiPartyCameraTypes.BACK:\n cameraType = MultiPartyCameraTypes.FRONT;\n deviceId = mediaCapabilitiesService.frontCameraDeviceId;\n break;\n case MultiPartyCameraTypes.FRONT:\n cameraType = MultiPartyCameraTypes.BACK;\n deviceId = mediaCapabilitiesService.backCameraDeviceId;\n break;\n default:\n throw new Error(`cameraType not valid: ${this.settings.selectedCamera}`);\n }\n\n return await this.currentPublisher?.setVideoSource(deviceId).then(() => {\n this.settings.selectedCamera = cameraType;\n this.emit(MULTI_PARTY_EVENTS.cameraSourceChanged, {\n cameraSource: cameraType\n });\n trace.info(`Success to switch camera to cameraType: ${cameraType}`);\n });\n } catch (err) {\n trace.error(JSON.stringify(err));\n throw err;\n }\n };\n\n private getClientRole(connection?: any): SessionClientRole {\n return (connection?.data && JSON.parse(connection.data).clientRole) || SessionClientRole.USER;\n }\n\n initPublisher = async (settings: SessionSettings): Promise<void> => {\n try {\n const {selectedCamera} = settings;\n const isMobile = checkIsMobile();\n\n const mediaCapabilitiesService = await MediaCapabilitiesService.getInstance();\n const {frontCameraDeviceId, backCameraDeviceId} = mediaCapabilitiesService;\n\n const videoSourceId =\n (selectedCamera === MultiPartyCameraTypes.FRONT ? frontCameraDeviceId : backCameraDeviceId) ||\n undefined;\n\n const mobileCameraType =\n settings.selectedCamera === MultiPartyCameraTypes.FRONT\n ? OpentokFacingMode.USER\n : OpentokFacingMode.ENVIRONMENT;\n\n this.currentPublisher = OT.initPublisher(\n this.getParticipantId(this.session?.connection?.connectionId || ''),\n {\n insertMode: 'append',\n fitMode: 'cover',\n width: '100%',\n height: '100%',\n style: {\n buttonDisplayMode: 'off',\n archiveStatusDisplayMode: 'off',\n audioLevelDisplayMode: 'off',\n backgroundImageURI: this.branding?.backgroundImageURI || DEFAULT_AVATAR_URI\n },\n publishAudio: !settings.startWithAudioMuted,\n publishVideo: settings.cameraEnabled,\n videoSource: !isMobile ? videoSourceId : undefined,\n facingMode: isMobile || !videoSourceId ? mobileCameraType : undefined\n },\n this.traceErrorCallback\n );\n if (!this.currentPublisher) {\n trace.error('failed to init publisher');\n } else {\n this.currentPublisher.on('videoElementCreated', (event: any) => {\n trace.info('videoElementCreated', event);\n\n this.emit(MULTI_PARTY_EVENTS.videoElementCreated, event);\n });\n this.currentPublisher.on('streamDestroyed', (event: any) => {\n trace.info('Publisher stream destroyed', event);\n\n this.emit(MULTI_PARTY_EVENTS.streamDestroyed, {\n event: event,\n participantId: this.getParticipantId(event?.stream?.connection.connectionId)\n });\n });\n this.currentPublisher.on('accessDenied', (event: any) => {\n trace.info('Publisher denied media permissions', event);\n\n const connection = this.session?.connection;\n const connectionId = connection?.connectionId;\n const participantId = this.getParticipantId(connectionId);\n const isOwnConnection = this.ownConnectionId === connectionId;\n\n this.emit(MULTI_PARTY_EVENTS.mediaDeviceAccessDenied, {\n connectionId,\n isOwnConnection,\n participantId\n });\n });\n this.currentPublisher.on('streamCreated', async (event: any) => {\n await MediaCapabilitiesService.setDevicesList();\n\n const participantId = this.getParticipantId(event?.stream?.connection.connectionId);\n\n this.emit(MULTI_PARTY_EVENTS.streamCreated, {\n event: event,\n participantId: participantId,\n startWithAudioMuted: this.settings.startWithAudioMuted,\n hasAudio: event?.stream?.hasAudio,\n hasVideo: event?.stream?.hasVideo,\n isOwnConnection: this.ownConnectionId === event?.stream?.connection?.connectionId\n });\n\n trace.info('publisher stream created', {\n event,\n hasAudio: this.currentPublisher?.stream?.hasAudio,\n participantId\n });\n });\n }\n } catch (err) {\n trace.error(JSON.stringify(err));\n throw err;\n }\n };\n\n reinitializeStreams = async (settings: SessionSettings): Promise<void> => {\n if (this.currentPublisher) {\n this.currentPublisher.destroy();\n this.currentPublisher = undefined;\n }\n await this.createPublisher(settings);\n\n this.subscriberList = this.subscriberList.map((s) => {\n const {connectionId, subscriber} = s;\n const {stream} = subscriber;\n\n this.session?.unsubscribe(subscriber);\n\n return {\n ...s,\n subscriber: this.createSubscriber(stream, connectionId)\n };\n });\n };\n\n streamCreatedHandler = async (\n event: any & {\n stream: any;\n }\n ): Promise<void> => {\n trace.info('streamCreatedHandler', event.stream);\n\n const {stream} = event;\n const {connection} = stream;\n const {connectionId} = connection;\n const participantId = this.getParticipantId(connectionId);\n\n await MediaCapabilitiesService.setDevicesList();\n\n if (this.ownConnectionId && stream && connectionId !== this.ownConnectionId) {\n if (this.subscriberList.length < maxParticipantsNumber - 1) {\n const subscriber = this.createSubscriber(stream, connectionId);\n\n this.subscriberList.push({\n subscriber,\n connectionId,\n clientRole: this.getClientRole(connection)\n });\n } else {\n trace.info(\n `a new subscriber cannot be added, as the number of participants reached its limit: ${maxParticipantsNumber}`\n );\n }\n }\n\n this.emit(MULTI_PARTY_EVENTS.streamCreated, {\n event: event,\n participantId: participantId,\n startWithAudioMuted: this.settings.startWithAudioMuted,\n isOwnConnection: this.ownConnectionId === connectionId,\n hasVideo: event?.stream?.hasVideo,\n hasAudio: event?.stream?.hasAudio\n });\n };\n\n streamPropertyChangedHandler = (event: any): void => {\n trace.info('streamPropertyChangedHandler', event);\n\n const {stream} = event;\n\n this.emit(MULTI_PARTY_EVENTS.videoAudioStatus, {\n hasVideo: stream?.hasVideo,\n hasAudio: stream?.hasAudio,\n participantId: this.getParticipantId(stream?.connection.connectionId),\n isOwnConnection: this.ownConnectionId === stream?.connection?.connectionId,\n stream\n });\n };\n\n streamDestroyedHandler = (\n event: any & {\n stream: any;\n reason: string;\n }\n ): void => {\n trace.info('streamDestroyedHandler', event);\n\n const {stream} = event;\n const destroyedConnectionId = stream && stream.connection && stream.connection.connectionId;\n\n this.subscriberList = this.subscriberList.filter(\n (subscriber) => subscriber.connectionId !== destroyedConnectionId\n );\n\n this.emit(MULTI_PARTY_EVENTS.streamDestroyed, {\n event: event,\n participantId: this.getParticipantId(event?.stream?.connection.connectionId)\n });\n };\n\n connectionCreatedHandler = (\n event: any & {\n connection: any;\n }\n ): void => {\n trace.info('connectionCreatedHandler', event);\n\n const {connection} = event;\n\n if (!this.ownConnectionId || !connection) {\n return;\n }\n\n this.emit(MULTI_PARTY_EVENTS.participantsNumberChanged, this.subscriberList.length);\n\n const clientRole = this.getClientRole(connection);\n\n this.emit(MULTI_PARTY_EVENTS.connectionCreated, {\n clientRole,\n event,\n participantId: this.getParticipantId(connection.connectionId),\n isOwnConnection: this.ownConnectionId === connection.connectionId\n });\n };\n\n connectionDestroyedHandler = (\n event: any & {\n connection: any;\n reason: string;\n }\n ): void => {\n const {connection} = event;\n const clientRole = this.getClientRole(connection);\n\n trace.info('connectionDestroyedHandler', event);\n\n this.emit(MULTI_PARTY_EVENTS.connectionDestroyed, {\n clientRole,\n event,\n participantId: this.getParticipantId(event?.connection.connectionId),\n isOwnConnection: this.ownConnectionId === connection.connectionId\n });\n\n if (!this.ownConnectionId || !connection || connection.connectionId !== this.ownConnectionId) {\n return;\n }\n\n this.emit(MULTI_PARTY_EVENTS.participantsNumberChanged, this.subscriberList.length);\n this.session = undefined;\n };\n\n traceErrorCallback = (error?: Error): void => {\n trace.error(JSON.stringify(error));\n };\n\n private sessionDisconnectedHandler = (event: any) => {\n trace.info('sessionDisconnectedHandler', event);\n // ToDo: Below line makes no sense (according to the OT types). This is not how OT signals work.\n // this?.session?.signal({ retryAfterReconnect: true });\n };\n\n private createSubscriber = (stream: any, connectionId: string): OT.Subscriber => {\n // ToDo: OT session can be undefined. This needs to be fixed.\n const subscriber = this.session!.subscribe(\n stream,\n this.getParticipantId(connectionId),\n {\n insertMode: 'append',\n fitMode: 'cover',\n width: '100%',\n height: '100%',\n style: {\n buttonDisplayMode: 'off',\n audioLevelDisplayMode: 'off',\n videoDisabledDisplayMode: 'off',\n backgroundImageURI: this.branding?.backgroundImageURI || DEFAULT_AVATAR_URI\n }\n },\n (err: any) => {\n this.traceErrorCallback(err);\n subscriber?.on('videoElementCreated', (event: any) => {\n trace.info('videoElementCreated', event);\n });\n }\n );\n\n return subscriber;\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/TechseeMediaStream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,gBAAgB,EAAE,oBAAoB,EAAC,MAAM,kBAAkB,CAAC;AAExE,qBAAa,kBAAkB;IAC3B,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAmB;
|
|
1
|
+
{"version":3,"sources":["../src/TechseeMediaStream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,gBAAgB,EAAE,oBAAoB,EAAC,MAAM,kBAAkB,CAAC;AAExE,qBAAa,kBAAkB;IAC3B,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAmB;IACrD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAmB;IAC/C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAU;IACpC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAc;gBAElC,gBAAgB,EAAE,gBAAgB,EAAE,UAAU,EAAE,gBAAgB,EAAE,QAAQ,EAAE,OAAO;IAQ/F,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,IAAI,UAAU,IAAI,gBAAgB,CAEjC;IAED,IAAI,WAAW,IAAI,WAAW,CAE7B;IAED,IAAI,UAAU,IAAI,gBAAgB,CAEjC;IAED,IAAI,UAAU,IAAI,oBAAoB,CAErC;CACJ","file":"TechseeMediaStream.d.ts","sourcesContent":["import {KnownMediaStream, KnownMediaStreamKind} from './MediaConstants';\n\nexport class TechseeMediaStream {\n private readonly _mediaStreamTrack: MediaStreamTrack;\n private readonly _streamType: KnownMediaStream;\n private readonly _isRemote: boolean;\n private readonly dedicatedStream: MediaStream;\n\n constructor(mediaStreamTrack: MediaStreamTrack, streamType: KnownMediaStream, isRemote: boolean) {\n this._isRemote = isRemote;\n this._streamType = streamType;\n this._mediaStreamTrack = mediaStreamTrack;\n\n this.dedicatedStream = new MediaStream([mediaStreamTrack]);\n }\n\n get isRemote(): boolean {\n return this._isRemote;\n }\n\n get streamId(): string {\n return this._mediaStreamTrack.id;\n }\n\n get streamType(): KnownMediaStream {\n return this._streamType;\n }\n\n get mediaStream(): MediaStream {\n return this.dedicatedStream;\n }\n\n get mediaTrack(): MediaStreamTrack {\n return this._mediaStreamTrack;\n }\n\n get streamKind(): KnownMediaStreamKind {\n return this._mediaStreamTrack.kind as any;\n }\n}\n"]}
|