matrix-js-sdk 41.8.0 → 41.9.0
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/CHANGELOG.md +11 -0
- package/README.md +1 -1
- package/lib/@types/AESEncryptedSecretStoragePayload.js +3 -1
- package/lib/@types/IIdentityServerProvider.js +3 -1
- package/lib/@types/PushRules.js +7 -7
- package/lib/@types/PushRules.js.map +1 -1
- package/lib/@types/auth.js +4 -4
- package/lib/@types/auth.js.map +1 -1
- package/lib/@types/beacon.js +2 -2
- package/lib/@types/beacon.js.map +1 -1
- package/lib/@types/common.js +3 -1
- package/lib/@types/crypto.js +3 -1
- package/lib/@types/event.js +20 -20
- package/lib/@types/event.js.map +1 -1
- package/lib/@types/events.js +3 -1
- package/lib/@types/extensible_events.js +6 -6
- package/lib/@types/extensible_events.js.map +1 -1
- package/lib/@types/global.d.js.map +1 -1
- package/lib/@types/json.js +3 -1
- package/lib/@types/local_notifications.js +3 -1
- package/lib/@types/location.js +4 -4
- package/lib/@types/location.js.map +1 -1
- package/lib/@types/matrix-sdk-crypto-wasm.d.js +3 -1
- package/lib/@types/media.js +3 -1
- package/lib/@types/membership.js +1 -1
- package/lib/@types/membership.js.map +1 -1
- package/lib/@types/partials.js +6 -6
- package/lib/@types/partials.js.map +1 -1
- package/lib/@types/polls.js +5 -5
- package/lib/@types/polls.js.map +1 -1
- package/lib/@types/read_receipts.js +2 -2
- package/lib/@types/read_receipts.js.map +1 -1
- package/lib/@types/registration.js +3 -1
- package/lib/@types/requests.js +1 -1
- package/lib/@types/requests.js.map +1 -1
- package/lib/@types/retention.js +1 -1
- package/lib/@types/retention.js.map +1 -1
- package/lib/@types/search.js +1 -1
- package/lib/@types/search.js.map +1 -1
- package/lib/@types/signed.js +3 -1
- package/lib/@types/spaces.js +3 -1
- package/lib/@types/state_events.js +3 -1
- package/lib/@types/synapse.js +3 -1
- package/lib/@types/sync.js +1 -1
- package/lib/@types/sync.js.map +1 -1
- package/lib/@types/threepids.js +1 -1
- package/lib/@types/threepids.js.map +1 -1
- package/lib/@types/topic.js +1 -1
- package/lib/@types/topic.js.map +1 -1
- package/lib/@types/uia.js +3 -1
- package/lib/NamespacedValue.js +8 -8
- package/lib/NamespacedValue.js.map +1 -1
- package/lib/ReEmitter.js +9 -16
- package/lib/ReEmitter.js.map +1 -1
- package/lib/ToDeviceMessageQueue.js +49 -57
- package/lib/ToDeviceMessageQueue.js.map +1 -1
- package/lib/autodiscovery.js +232 -247
- package/lib/autodiscovery.js.map +1 -1
- package/lib/base64.js +1 -1
- package/lib/base64.js.map +1 -1
- package/lib/browser-index.d.ts +2 -2
- package/lib/browser-index.d.ts.map +1 -1
- package/lib/browser-index.js +5 -5
- package/lib/browser-index.js.map +1 -1
- package/lib/capabilityPoller.js +19 -22
- package/lib/capabilityPoller.js.map +1 -1
- package/lib/client.d.ts +2 -8
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +2043 -2487
- package/lib/client.js.map +1 -1
- package/lib/common-crypto/CryptoBackend.js +2 -2
- package/lib/common-crypto/CryptoBackend.js.map +1 -1
- package/lib/common-crypto/key-passphrase.js.map +1 -1
- package/lib/content-helpers.js +43 -48
- package/lib/content-helpers.js.map +1 -1
- package/lib/content-repo.js +14 -24
- package/lib/content-repo.js.map +1 -1
- package/lib/crypto/store/base.js +6 -6
- package/lib/crypto/store/base.js.map +1 -1
- package/lib/crypto/store/indexeddb-crypto-store-backend.js +190 -238
- package/lib/crypto/store/indexeddb-crypto-store-backend.js.map +1 -1
- package/lib/crypto/store/indexeddb-crypto-store.js +25 -30
- package/lib/crypto/store/indexeddb-crypto-store.js.map +1 -1
- package/lib/crypto/store/localStorage-crypto-store.js +113 -145
- package/lib/crypto/store/localStorage-crypto-store.js.map +1 -1
- package/lib/crypto/store/memory-crypto-store.js +74 -105
- package/lib/crypto/store/memory-crypto-store.js.map +1 -1
- package/lib/crypto-api/CryptoEvent.js +1 -1
- package/lib/crypto-api/CryptoEvent.js.map +1 -1
- package/lib/crypto-api/CryptoEventHandlerMap.js +3 -1
- package/lib/crypto-api/index.d.ts +15 -6
- package/lib/crypto-api/index.d.ts.map +1 -1
- package/lib/crypto-api/index.js +22 -22
- package/lib/crypto-api/index.js.map +1 -1
- package/lib/crypto-api/key-passphrase.js +15 -23
- package/lib/crypto-api/key-passphrase.js.map +1 -1
- package/lib/crypto-api/keybackup.js +3 -1
- package/lib/crypto-api/recovery-key.js +11 -12
- package/lib/crypto-api/recovery-key.js.map +1 -1
- package/lib/crypto-api/verification.js +3 -3
- package/lib/crypto-api/verification.js.map +1 -1
- package/lib/digest.js +7 -14
- package/lib/digest.js.map +1 -1
- package/lib/embedded.d.ts.map +1 -1
- package/lib/embedded.js +380 -505
- package/lib/embedded.js.map +1 -1
- package/lib/errors.js +2 -2
- package/lib/errors.js.map +1 -1
- package/lib/event-mapper.js +10 -12
- package/lib/event-mapper.js.map +1 -1
- package/lib/extensible_events_v1/ExtensibleEvent.js.map +1 -1
- package/lib/extensible_events_v1/InvalidEventError.js.map +1 -1
- package/lib/extensible_events_v1/MessageEvent.js +11 -13
- package/lib/extensible_events_v1/MessageEvent.js.map +1 -1
- package/lib/extensible_events_v1/PollEndEvent.js +3 -4
- package/lib/extensible_events_v1/PollEndEvent.js.map +1 -1
- package/lib/extensible_events_v1/PollResponseEvent.js +5 -6
- package/lib/extensible_events_v1/PollResponseEvent.js.map +1 -1
- package/lib/extensible_events_v1/PollStartEvent.js +8 -10
- package/lib/extensible_events_v1/PollStartEvent.js.map +1 -1
- package/lib/extensible_events_v1/utilities.js.map +1 -1
- package/lib/feature.js +18 -31
- package/lib/feature.js.map +1 -1
- package/lib/filter-component.d.ts.map +1 -1
- package/lib/filter-component.js +20 -26
- package/lib/filter-component.js.map +1 -1
- package/lib/filter.js +14 -17
- package/lib/filter.js.map +1 -1
- package/lib/http-api/errors.js +28 -43
- package/lib/http-api/errors.js.map +1 -1
- package/lib/http-api/fetch.js +141 -159
- package/lib/http-api/fetch.js.map +1 -1
- package/lib/http-api/index.js +17 -20
- package/lib/http-api/index.js.map +1 -1
- package/lib/http-api/interface.js +1 -1
- package/lib/http-api/interface.js.map +1 -1
- package/lib/http-api/method.js +1 -1
- package/lib/http-api/method.js.map +1 -1
- package/lib/http-api/prefix.js +3 -3
- package/lib/http-api/prefix.js.map +1 -1
- package/lib/http-api/refresh.js +74 -94
- package/lib/http-api/refresh.js.map +1 -1
- package/lib/http-api/utils.d.ts +1 -1
- package/lib/http-api/utils.d.ts.map +1 -1
- package/lib/http-api/utils.js +34 -42
- package/lib/http-api/utils.js.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/indexeddb-helpers.js +3 -3
- package/lib/indexeddb-helpers.js.map +1 -1
- package/lib/indexeddb-worker.js.map +1 -1
- package/lib/interactive-auth.d.ts +5 -5
- package/lib/interactive-auth.d.ts.map +1 -1
- package/lib/interactive-auth.js +172 -204
- package/lib/interactive-auth.js.map +1 -1
- package/lib/logger.js +21 -57
- package/lib/logger.js.map +1 -1
- package/lib/matrix.js +7 -11
- package/lib/matrix.js.map +1 -1
- package/lib/matrixrtc/CallMembership.js +80 -74
- package/lib/matrixrtc/CallMembership.js.map +1 -1
- package/lib/matrixrtc/EncryptionManager.js +1 -1
- package/lib/matrixrtc/EncryptionManager.js.map +1 -1
- package/lib/matrixrtc/IKeyTransport.js +1 -1
- package/lib/matrixrtc/IKeyTransport.js.map +1 -1
- package/lib/matrixrtc/IMembershipManager.js +1 -1
- package/lib/matrixrtc/IMembershipManager.js.map +1 -1
- package/lib/matrixrtc/LivekitTransport.d.ts +1 -1
- package/lib/matrixrtc/LivekitTransport.js +4 -4
- package/lib/matrixrtc/LivekitTransport.js.map +1 -1
- package/lib/matrixrtc/MatrixRTCSession.js +137 -187
- package/lib/matrixrtc/MatrixRTCSession.js.map +1 -1
- package/lib/matrixrtc/MatrixRTCSessionManager.js +36 -41
- package/lib/matrixrtc/MatrixRTCSessionManager.js.map +1 -1
- package/lib/matrixrtc/MembershipManager.js +332 -378
- package/lib/matrixrtc/MembershipManager.js.map +1 -1
- package/lib/matrixrtc/MembershipManagerActionScheduler.js +54 -63
- package/lib/matrixrtc/MembershipManagerActionScheduler.js.map +1 -1
- package/lib/matrixrtc/RTCEncryptionManager.js +119 -143
- package/lib/matrixrtc/RTCEncryptionManager.js.map +1 -1
- package/lib/matrixrtc/ToDeviceKeyTransport.js +53 -58
- package/lib/matrixrtc/ToDeviceKeyTransport.js.map +1 -1
- package/lib/matrixrtc/index.js.map +1 -1
- package/lib/matrixrtc/membershipData/common.js +1 -1
- package/lib/matrixrtc/membershipData/common.js.map +1 -1
- package/lib/matrixrtc/membershipData/index.js.map +1 -1
- package/lib/matrixrtc/membershipData/rtc.js +15 -23
- package/lib/matrixrtc/membershipData/rtc.js.map +1 -1
- package/lib/matrixrtc/membershipData/session.js +4 -5
- package/lib/matrixrtc/membershipData/session.js.map +1 -1
- package/lib/matrixrtc/types.js +4 -6
- package/lib/matrixrtc/types.js.map +1 -1
- package/lib/matrixrtc/utils.js +4 -11
- package/lib/matrixrtc/utils.js.map +1 -1
- package/lib/models/MSC3089Branch.js +91 -116
- package/lib/models/MSC3089Branch.js.map +1 -1
- package/lib/models/MSC3089TreeSpace.js +209 -245
- package/lib/models/MSC3089TreeSpace.js.map +1 -1
- package/lib/models/ToDeviceMessage.js +3 -1
- package/lib/models/beacon.js +14 -13
- package/lib/models/beacon.js.map +1 -1
- package/lib/models/compare-event-ordering.js +13 -13
- package/lib/models/compare-event-ordering.js.map +1 -1
- package/lib/models/device.js +3 -3
- package/lib/models/device.js.map +1 -1
- package/lib/models/event-context.js +5 -8
- package/lib/models/event-context.js.map +1 -1
- package/lib/models/event-status.js +1 -1
- package/lib/models/event-status.js.map +1 -1
- package/lib/models/event-timeline-set.js +88 -94
- package/lib/models/event-timeline-set.js.map +1 -1
- package/lib/models/event-timeline.js +28 -30
- package/lib/models/event-timeline.js.map +1 -1
- package/lib/models/event.js +182 -226
- package/lib/models/event.js.map +1 -1
- package/lib/models/invites-ignorer-types.js +4 -4
- package/lib/models/invites-ignorer-types.js.map +1 -1
- package/lib/models/invites-ignorer.js +159 -179
- package/lib/models/invites-ignorer.js.map +1 -1
- package/lib/models/poll.js +71 -81
- package/lib/models/poll.js.map +1 -1
- package/lib/models/profile-keys.js +2 -2
- package/lib/models/profile-keys.js.map +1 -1
- package/lib/models/read-receipt.js +40 -56
- package/lib/models/read-receipt.js.map +1 -1
- package/lib/models/related-relations.js.map +1 -1
- package/lib/models/relations-container.js +22 -23
- package/lib/models/relations-container.js.map +1 -1
- package/lib/models/relations.js +133 -159
- package/lib/models/relations.js.map +1 -1
- package/lib/models/room-member.js +24 -29
- package/lib/models/room-member.js.map +1 -1
- package/lib/models/room-receipts.js +29 -43
- package/lib/models/room-receipts.js.map +1 -1
- package/lib/models/room-retention.js +74 -83
- package/lib/models/room-retention.js.map +1 -1
- package/lib/models/room-state.js +143 -172
- package/lib/models/room-state.js.map +1 -1
- package/lib/models/room-sticky-events.d.ts +1 -0
- package/lib/models/room-sticky-events.d.ts.map +1 -1
- package/lib/models/room-sticky-events.js +45 -65
- package/lib/models/room-sticky-events.js.map +1 -1
- package/lib/models/room-summary.js.map +1 -1
- package/lib/models/room.d.ts.map +1 -1
- package/lib/models/room.js +765 -909
- package/lib/models/room.js.map +1 -1
- package/lib/models/search-result.js +5 -5
- package/lib/models/search-result.js.map +1 -1
- package/lib/models/thread.js +225 -285
- package/lib/models/thread.js.map +1 -1
- package/lib/models/typed-event-emitter.js +7 -18
- package/lib/models/typed-event-emitter.js.map +1 -1
- package/lib/models/user.js +8 -8
- package/lib/models/user.js.map +1 -1
- package/lib/oidc/authorize.js +208 -241
- package/lib/oidc/authorize.js.map +1 -1
- package/lib/oidc/discovery.js +23 -36
- package/lib/oidc/discovery.js.map +1 -1
- package/lib/oidc/error.js +1 -1
- package/lib/oidc/error.js.map +1 -1
- package/lib/oidc/index.js +1 -0
- package/lib/oidc/index.js.map +1 -1
- package/lib/oidc/register.js +60 -66
- package/lib/oidc/register.js.map +1 -1
- package/lib/oidc/tokenRefresher.js +77 -91
- package/lib/oidc/tokenRefresher.js.map +1 -1
- package/lib/oidc/validate.js +18 -18
- package/lib/oidc/validate.js.map +1 -1
- package/lib/pushprocessor.js +71 -82
- package/lib/pushprocessor.js.map +1 -1
- package/lib/randomstring.js +9 -9
- package/lib/randomstring.js.map +1 -1
- package/lib/realtime-callbacks.js +25 -28
- package/lib/realtime-callbacks.js.map +1 -1
- package/lib/receipt-accumulator.d.ts +2 -0
- package/lib/receipt-accumulator.d.ts.map +1 -1
- package/lib/receipt-accumulator.js +14 -22
- package/lib/receipt-accumulator.js.map +1 -1
- package/lib/rendezvous/MSC4108SignInWithQR.js +271 -302
- package/lib/rendezvous/MSC4108SignInWithQR.js.map +1 -1
- package/lib/rendezvous/RendezvousChannel.js +3 -1
- package/lib/rendezvous/RendezvousCode.js +3 -1
- package/lib/rendezvous/RendezvousError.js.map +1 -1
- package/lib/rendezvous/RendezvousFailureReason.js +2 -2
- package/lib/rendezvous/RendezvousFailureReason.js.map +1 -1
- package/lib/rendezvous/RendezvousIntent.js +1 -1
- package/lib/rendezvous/RendezvousIntent.js.map +1 -1
- package/lib/rendezvous/RendezvousTransport.js +3 -1
- package/lib/rendezvous/channels/MSC4108SecureChannel.js +123 -147
- package/lib/rendezvous/channels/MSC4108SecureChannel.js.map +1 -1
- package/lib/rendezvous/index.js +44 -69
- package/lib/rendezvous/index.js.map +1 -1
- package/lib/rendezvous/transports/MSC4108RendezvousSession.d.ts +1 -1
- package/lib/rendezvous/transports/MSC4108RendezvousSession.js +134 -152
- package/lib/rendezvous/transports/MSC4108RendezvousSession.js.map +1 -1
- package/lib/retentionPolicy.js +6 -9
- package/lib/retentionPolicy.js.map +1 -1
- package/lib/room-hierarchy.js +52 -60
- package/lib/room-hierarchy.js.map +1 -1
- package/lib/rust-crypto/CrossSigningIdentity.js +94 -104
- package/lib/rust-crypto/CrossSigningIdentity.js.map +1 -1
- package/lib/rust-crypto/DehydratedDeviceManager.js +157 -190
- package/lib/rust-crypto/DehydratedDeviceManager.js.map +1 -1
- package/lib/rust-crypto/KeyClaimManager.js +18 -22
- package/lib/rust-crypto/KeyClaimManager.js.map +1 -1
- package/lib/rust-crypto/OutgoingRequestProcessor.js +109 -139
- package/lib/rust-crypto/OutgoingRequestProcessor.js.map +1 -1
- package/lib/rust-crypto/OutgoingRequestsManager.js +66 -80
- package/lib/rust-crypto/OutgoingRequestsManager.js.map +1 -1
- package/lib/rust-crypto/PerSessionKeyBackupDownloader.d.ts +1 -1
- package/lib/rust-crypto/PerSessionKeyBackupDownloader.js +196 -224
- package/lib/rust-crypto/PerSessionKeyBackupDownloader.js.map +1 -1
- package/lib/rust-crypto/RoomEncryptor.js +127 -143
- package/lib/rust-crypto/RoomEncryptor.js.map +1 -1
- package/lib/rust-crypto/backup.js +453 -549
- package/lib/rust-crypto/backup.js.map +1 -1
- package/lib/rust-crypto/constants.js +1 -1
- package/lib/rust-crypto/constants.js.map +1 -1
- package/lib/rust-crypto/device-converter.js +15 -28
- package/lib/rust-crypto/device-converter.js.map +1 -1
- package/lib/rust-crypto/index.js +103 -116
- package/lib/rust-crypto/index.js.map +1 -1
- package/lib/rust-crypto/libolm_migration.js +303 -365
- package/lib/rust-crypto/libolm_migration.js.map +1 -1
- package/lib/rust-crypto/rust-crypto.d.ts +5 -1
- package/lib/rust-crypto/rust-crypto.d.ts.map +1 -1
- package/lib/rust-crypto/rust-crypto.js +1067 -1332
- package/lib/rust-crypto/rust-crypto.js.map +1 -1
- package/lib/rust-crypto/secret-storage.js +12 -25
- package/lib/rust-crypto/secret-storage.js.map +1 -1
- package/lib/rust-crypto/verification.js +137 -189
- package/lib/rust-crypto/verification.js.map +1 -1
- package/lib/scheduler.js +44 -19
- package/lib/scheduler.js.map +1 -1
- package/lib/secret-storage.js +179 -213
- package/lib/secret-storage.js.map +1 -1
- package/lib/serverCapabilities.js +6 -9
- package/lib/serverCapabilities.js.map +1 -1
- package/lib/service-types.js +1 -1
- package/lib/service-types.js.map +1 -1
- package/lib/sliding-sync-sdk.d.ts.map +1 -1
- package/lib/sliding-sync-sdk.js +368 -424
- package/lib/sliding-sync-sdk.js.map +1 -1
- package/lib/sliding-sync.js +135 -171
- package/lib/sliding-sync.js.map +1 -1
- package/lib/store/index.js +3 -1
- package/lib/store/indexeddb-backend.js +3 -1
- package/lib/store/indexeddb-local-backend.js +194 -253
- package/lib/store/indexeddb-local-backend.js.map +1 -1
- package/lib/store/indexeddb-remote-backend.js +33 -63
- package/lib/store/indexeddb-remote-backend.js.map +1 -1
- package/lib/store/indexeddb-store-worker.js +22 -23
- package/lib/store/indexeddb-store-worker.js.map +1 -1
- package/lib/store/indexeddb.js +44 -71
- package/lib/store/indexeddb.js.map +1 -1
- package/lib/store/local-storage-events-emitter.js +2 -2
- package/lib/store/local-storage-events-emitter.js.map +1 -1
- package/lib/store/memory.js +34 -57
- package/lib/store/memory.js.map +1 -1
- package/lib/store/stub.js +22 -35
- package/lib/store/stub.js.map +1 -1
- package/lib/sync-accumulator.js +54 -66
- package/lib/sync-accumulator.js.map +1 -1
- package/lib/sync.d.ts.map +1 -1
- package/lib/sync.js +905 -998
- package/lib/sync.js.map +1 -1
- package/lib/testing.js +63 -105
- package/lib/testing.js.map +1 -1
- package/lib/thread-utils.js +1 -4
- package/lib/thread-utils.js.map +1 -1
- package/lib/timeline-window.js +89 -102
- package/lib/timeline-window.js.map +1 -1
- package/lib/types.js +1 -1
- package/lib/types.js.map +1 -1
- package/lib/utils/decryptAESSecretStorageItem.js +14 -25
- package/lib/utils/decryptAESSecretStorageItem.js.map +1 -1
- package/lib/utils/encryptAESSecretStorageItem.js +27 -38
- package/lib/utils/encryptAESSecretStorageItem.js.map +1 -1
- package/lib/utils/internal/deriveKeys.js +25 -32
- package/lib/utils/internal/deriveKeys.js.map +1 -1
- package/lib/utils/roomVersion.js +1 -1
- package/lib/utils/roomVersion.js.map +1 -1
- package/lib/utils.js +83 -135
- package/lib/utils.js.map +1 -1
- package/lib/version-support.js +3 -3
- package/lib/version-support.js.map +1 -1
- package/lib/webrtc/audioContext.js +5 -6
- package/lib/webrtc/audioContext.js.map +1 -1
- package/lib/webrtc/call.js +1081 -1250
- package/lib/webrtc/call.js.map +1 -1
- package/lib/webrtc/callEventHandler.js +202 -214
- package/lib/webrtc/callEventHandler.js.map +1 -1
- package/lib/webrtc/callEventTypes.js +2 -2
- package/lib/webrtc/callEventTypes.js.map +1 -1
- package/lib/webrtc/callFeed.js +18 -20
- package/lib/webrtc/callFeed.js.map +1 -1
- package/lib/webrtc/groupCall.js +513 -602
- package/lib/webrtc/groupCall.js.map +1 -1
- package/lib/webrtc/groupCallEventHandler.js +59 -62
- package/lib/webrtc/groupCallEventHandler.js.map +1 -1
- package/lib/webrtc/mediaHandler.js +198 -232
- package/lib/webrtc/mediaHandler.js.map +1 -1
- package/lib/webrtc/stats/callFeedStatsReporter.js +16 -20
- package/lib/webrtc/stats/callFeedStatsReporter.js.map +1 -1
- package/lib/webrtc/stats/callStatsReportGatherer.js +67 -75
- package/lib/webrtc/stats/callStatsReportGatherer.js.map +1 -1
- package/lib/webrtc/stats/callStatsReportSummary.js +3 -1
- package/lib/webrtc/stats/connectionStats.js.map +1 -1
- package/lib/webrtc/stats/connectionStatsBuilder.js +2 -2
- package/lib/webrtc/stats/connectionStatsBuilder.js.map +1 -1
- package/lib/webrtc/stats/connectionStatsReportBuilder.js +20 -24
- package/lib/webrtc/stats/connectionStatsReportBuilder.js.map +1 -1
- package/lib/webrtc/stats/groupCallStats.js +6 -8
- package/lib/webrtc/stats/groupCallStats.js.map +1 -1
- package/lib/webrtc/stats/media/mediaSsrcHandler.js +7 -8
- package/lib/webrtc/stats/media/mediaSsrcHandler.js.map +1 -1
- package/lib/webrtc/stats/media/mediaTrackHandler.js +7 -9
- package/lib/webrtc/stats/media/mediaTrackHandler.js.map +1 -1
- package/lib/webrtc/stats/media/mediaTrackStats.js +3 -3
- package/lib/webrtc/stats/media/mediaTrackStats.js.map +1 -1
- package/lib/webrtc/stats/media/mediaTrackStatsHandler.js +7 -7
- package/lib/webrtc/stats/media/mediaTrackStatsHandler.js.map +1 -1
- package/lib/webrtc/stats/statsReport.js +1 -1
- package/lib/webrtc/stats/statsReport.js.map +1 -1
- package/lib/webrtc/stats/statsReportEmitter.js.map +1 -1
- package/lib/webrtc/stats/summaryStatsReportGatherer.js +14 -14
- package/lib/webrtc/stats/summaryStatsReportGatherer.js.map +1 -1
- package/lib/webrtc/stats/trackStatsBuilder.js +32 -34
- package/lib/webrtc/stats/trackStatsBuilder.js.map +1 -1
- package/lib/webrtc/stats/transportStats.js +3 -1
- package/lib/webrtc/stats/transportStatsBuilder.js +9 -9
- package/lib/webrtc/stats/transportStatsBuilder.js.map +1 -1
- package/lib/webrtc/stats/valueFormatter.js +1 -1
- package/lib/webrtc/stats/valueFormatter.js.map +1 -1
- package/package.json +18 -33
- package/src/@types/global.d.ts +2 -2
- package/src/ToDeviceMessageQueue.ts +2 -2
- package/src/browser-index.ts +4 -4
- package/src/client.ts +15 -20
- package/src/crypto-api/index.ts +17 -6
- package/src/embedded.ts +4 -6
- package/src/filter-component.ts +1 -0
- package/src/http-api/utils.ts +1 -1
- package/src/interactive-auth.ts +11 -5
- package/src/matrixrtc/LivekitTransport.ts +1 -1
- package/src/matrixrtc/MatrixRTCSession.ts +1 -1
- package/src/models/MSC3089Branch.ts +1 -1
- package/src/models/room-sticky-events.ts +1 -0
- package/src/models/room.ts +9 -11
- package/src/receipt-accumulator.ts +2 -0
- package/src/rendezvous/index.ts +1 -1
- package/src/rendezvous/transports/MSC4108RendezvousSession.ts +2 -2
- package/src/rust-crypto/PerSessionKeyBackupDownloader.ts +1 -1
- package/src/rust-crypto/index.ts +6 -6
- package/src/rust-crypto/rust-crypto.ts +31 -7
- package/src/rust-crypto/verification.ts +1 -1
- package/src/sliding-sync-sdk.ts +5 -7
- package/src/store/indexeddb-local-backend.ts +3 -3
- package/src/sync.ts +6 -8
package/lib/webrtc/groupCall.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
1
|
+
import _objectSpread from "@babel/runtime/helpers/objectSpread2";
|
|
3
2
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
4
|
-
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
5
|
-
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
6
3
|
import { TypedEventEmitter } from "../models/typed-event-emitter.js";
|
|
7
4
|
import { CallFeed, SPEAKING_THRESHOLD } from "./callFeed.js";
|
|
8
5
|
import { CallErrorCode, CallEvent, CallState, genCallID, setTracksEnabled, createNewMatrixCall, CallError } from "./call.js";
|
|
@@ -19,18 +16,18 @@ import { StatsReport } from "./stats/statsReport.js";
|
|
|
19
16
|
import { SummaryStatsReportGatherer } from "./stats/summaryStatsReportGatherer.js";
|
|
20
17
|
import { CallFeedStatsReporter } from "./stats/callFeedStatsReporter.js";
|
|
21
18
|
import { KnownMembership } from "../@types/membership.js";
|
|
22
|
-
export
|
|
19
|
+
export let GroupCallIntent = /*#__PURE__*/function (GroupCallIntent) {
|
|
23
20
|
GroupCallIntent["Ring"] = "m.ring";
|
|
24
21
|
GroupCallIntent["Prompt"] = "m.prompt";
|
|
25
22
|
GroupCallIntent["Room"] = "m.room";
|
|
26
23
|
return GroupCallIntent;
|
|
27
24
|
}({});
|
|
28
|
-
export
|
|
25
|
+
export let GroupCallType = /*#__PURE__*/function (GroupCallType) {
|
|
29
26
|
GroupCallType["Video"] = "m.video";
|
|
30
27
|
GroupCallType["Voice"] = "m.voice";
|
|
31
28
|
return GroupCallType;
|
|
32
29
|
}({});
|
|
33
|
-
export
|
|
30
|
+
export let GroupCallTerminationReason = /*#__PURE__*/function (GroupCallTerminationReason) {
|
|
34
31
|
GroupCallTerminationReason["CallEnded"] = "call_ended";
|
|
35
32
|
return GroupCallTerminationReason;
|
|
36
33
|
}({});
|
|
@@ -39,7 +36,7 @@ export var GroupCallTerminationReason = /*#__PURE__*/function (GroupCallTerminat
|
|
|
39
36
|
* to be unique over all event types of event emitter.
|
|
40
37
|
* Some objects could emit more then one set of events.
|
|
41
38
|
*/
|
|
42
|
-
export
|
|
39
|
+
export let GroupCallEvent = /*#__PURE__*/function (GroupCallEvent) {
|
|
43
40
|
GroupCallEvent["GroupCallStateChanged"] = "group_call_state_changed";
|
|
44
41
|
GroupCallEvent["ActiveSpeakerChanged"] = "active_speaker_changed";
|
|
45
42
|
GroupCallEvent["CallsChanged"] = "calls_changed";
|
|
@@ -51,7 +48,7 @@ export var GroupCallEvent = /*#__PURE__*/function (GroupCallEvent) {
|
|
|
51
48
|
GroupCallEvent["Error"] = "group_call_error";
|
|
52
49
|
return GroupCallEvent;
|
|
53
50
|
}({});
|
|
54
|
-
export
|
|
51
|
+
export let GroupCallStatsReportEvent = /*#__PURE__*/function (GroupCallStatsReportEvent) {
|
|
55
52
|
GroupCallStatsReportEvent["ConnectionStats"] = "GroupCall.connection_stats";
|
|
56
53
|
GroupCallStatsReportEvent["ByteSentStats"] = "GroupCall.byte_sent_stats";
|
|
57
54
|
GroupCallStatsReportEvent["SummaryStats"] = "GroupCall.summary_stats";
|
|
@@ -63,7 +60,7 @@ export var GroupCallStatsReportEvent = /*#__PURE__*/function (GroupCallStatsRepo
|
|
|
63
60
|
* The final report-events that get consumed by client.
|
|
64
61
|
*/
|
|
65
62
|
|
|
66
|
-
export
|
|
63
|
+
export let GroupCallErrorCode = /*#__PURE__*/function (GroupCallErrorCode) {
|
|
67
64
|
GroupCallErrorCode["NoUserMedia"] = "no_user_media";
|
|
68
65
|
GroupCallErrorCode["UnknownDevice"] = "unknown_device";
|
|
69
66
|
GroupCallErrorCode["PlaceCallFailed"] = "place_call_failed";
|
|
@@ -93,7 +90,7 @@ export class OtherUserSpeakingError extends Error {
|
|
|
93
90
|
super("Cannot unmute: another user is speaking");
|
|
94
91
|
}
|
|
95
92
|
}
|
|
96
|
-
export
|
|
93
|
+
export let GroupCallState = /*#__PURE__*/function (GroupCallState) {
|
|
97
94
|
GroupCallState["LocalCallFeedUninitialized"] = "local_call_feed_uninitialized";
|
|
98
95
|
GroupCallState["InitializingLocalCallFeed"] = "initializing_local_call_feed";
|
|
99
96
|
GroupCallState["LocalCallFeedInitialized"] = "local_call_feed_initialized";
|
|
@@ -101,26 +98,18 @@ export var GroupCallState = /*#__PURE__*/function (GroupCallState) {
|
|
|
101
98
|
GroupCallState["Ended"] = "ended";
|
|
102
99
|
return GroupCallState;
|
|
103
100
|
}({});
|
|
104
|
-
|
|
101
|
+
const DEVICE_TIMEOUT = 1000 * 60 * 60; // 1 hour
|
|
105
102
|
|
|
106
103
|
function getCallUserId(call) {
|
|
107
|
-
|
|
108
|
-
return ((_call$getOpponentMemb = call.getOpponentMember()) === null || _call$getOpponentMemb === void 0 ? void 0 : _call$getOpponentMemb.userId) || call.invitee || null;
|
|
104
|
+
return call.getOpponentMember()?.userId || call.invitee || null;
|
|
109
105
|
}
|
|
110
106
|
export class GroupCall extends TypedEventEmitter {
|
|
111
|
-
constructor(client, room, type, isPtt, intent, groupCallId, dataChannelsEnabled, dataChannelOptions, isCallWithoutVideoAndAudio
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
107
|
+
constructor(client, room, type, isPtt, intent, groupCallId, dataChannelsEnabled, dataChannelOptions, isCallWithoutVideoAndAudio,
|
|
108
|
+
// this tells the js-sdk not to actually establish any calls to exchange media and just to
|
|
109
|
+
// create the group call signaling events, with the intention that the actual media will be
|
|
110
|
+
// handled using livekit. The js-sdk doesn't contain any code to do the actual livekit call though.
|
|
111
|
+
useLivekit = false, livekitServiceURL) {
|
|
115
112
|
super();
|
|
116
|
-
this.client = client;
|
|
117
|
-
this.room = room;
|
|
118
|
-
this.type = type;
|
|
119
|
-
this.isPtt = isPtt;
|
|
120
|
-
this.intent = intent;
|
|
121
|
-
this.dataChannelsEnabled = dataChannelsEnabled;
|
|
122
|
-
this.dataChannelOptions = dataChannelOptions;
|
|
123
|
-
this.useLivekit = useLivekit;
|
|
124
113
|
// Config
|
|
125
114
|
_defineProperty(this, "activeSpeakerInterval", 1000);
|
|
126
115
|
_defineProperty(this, "retryCallInterval", 5000);
|
|
@@ -179,7 +168,7 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
179
168
|
if (this.localCallFeed) {
|
|
180
169
|
report = CallFeedStatsReporter.expandCallFeedReport(report, [this.localCallFeed], "from-local-feed");
|
|
181
170
|
}
|
|
182
|
-
|
|
171
|
+
const callFeeds = [];
|
|
183
172
|
this.forEachCall(call => {
|
|
184
173
|
if (call.callId === report.callId) {
|
|
185
174
|
call.getFeeds().forEach(f => callFeeds.push(f));
|
|
@@ -203,33 +192,32 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
203
192
|
* as they are observed by the RoomState.members event.
|
|
204
193
|
*/
|
|
205
194
|
_defineProperty(this, "onIncomingCall", newCall => {
|
|
206
|
-
var _newCall$getOpponentM, _this$calls$get;
|
|
207
195
|
// The incoming calls may be for another room, which we will ignore.
|
|
208
196
|
if (newCall.roomId !== this.room.roomId) {
|
|
209
197
|
return;
|
|
210
198
|
}
|
|
211
199
|
if (newCall.state !== CallState.Ringing) {
|
|
212
|
-
logger.warn(
|
|
200
|
+
logger.warn(`GroupCall ${this.groupCallId} onIncomingCall() incoming call no longer in ringing state - ignoring`);
|
|
213
201
|
return;
|
|
214
202
|
}
|
|
215
203
|
if (!newCall.groupCallId || newCall.groupCallId !== this.groupCallId) {
|
|
216
|
-
logger.log(
|
|
204
|
+
logger.log(`GroupCall ${this.groupCallId} onIncomingCall() ignored because it doesn't match the current group call`);
|
|
217
205
|
newCall.reject();
|
|
218
206
|
return;
|
|
219
207
|
}
|
|
220
|
-
|
|
208
|
+
const opponentUserId = newCall.getOpponentMember()?.userId;
|
|
221
209
|
if (opponentUserId === undefined) {
|
|
222
|
-
logger.warn(
|
|
210
|
+
logger.warn(`GroupCall ${this.groupCallId} onIncomingCall() incoming call with no member - ignoring`);
|
|
223
211
|
return;
|
|
224
212
|
}
|
|
225
213
|
if (this.useLivekit) {
|
|
226
214
|
logger.info("Received incoming call whilst in signaling-only mode! Ignoring.");
|
|
227
215
|
return;
|
|
228
216
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (
|
|
232
|
-
logger.log(
|
|
217
|
+
const deviceMap = this.calls.get(opponentUserId) ?? new Map();
|
|
218
|
+
const prevCall = deviceMap.get(newCall.getOpponentDeviceId());
|
|
219
|
+
if (prevCall?.callId === newCall.callId) return;
|
|
220
|
+
logger.log(`GroupCall ${this.groupCallId} onIncomingCall() incoming call (userId=${opponentUserId}, callId=${newCall.callId})`);
|
|
233
221
|
if (prevCall) prevCall.hangup(CallErrorCode.Replaced, false);
|
|
234
222
|
// We must do this before we start initialising / answering the call as we
|
|
235
223
|
// need to know it is the active call for this user+deviceId and to not ignore
|
|
@@ -237,11 +225,11 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
237
225
|
deviceMap.set(newCall.getOpponentDeviceId(), newCall);
|
|
238
226
|
this.calls.set(opponentUserId, deviceMap);
|
|
239
227
|
this.initCall(newCall);
|
|
240
|
-
|
|
228
|
+
const feeds = this.getLocalFeeds().map(feed => feed.clone());
|
|
241
229
|
if (!this.callExpected(newCall)) {
|
|
242
230
|
// Disable our tracks for users not explicitly participating in the
|
|
243
231
|
// call but trying to receive the feeds
|
|
244
|
-
for (
|
|
232
|
+
for (const feed of feeds) {
|
|
245
233
|
setTracksEnabled(feed.stream.getAudioTracks(), false);
|
|
246
234
|
setTracksEnabled(feed.stream.getVideoTracks(), false);
|
|
247
235
|
}
|
|
@@ -250,24 +238,19 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
250
238
|
this.emit(GroupCallEvent.CallsChanged, this.calls);
|
|
251
239
|
});
|
|
252
240
|
_defineProperty(this, "onRetryCallLoop", () => {
|
|
253
|
-
|
|
254
|
-
for (
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
var deviceId = _ref5[0];
|
|
264
|
-
var participant = _ref5[1];
|
|
265
|
-
var call = callMap === null || callMap === void 0 ? void 0 : callMap.get(deviceId);
|
|
266
|
-
var retries = (_retriesMap$get = (_retriesMap = retriesMap) === null || _retriesMap === void 0 ? void 0 : _retriesMap.get(deviceId)) !== null && _retriesMap$get !== void 0 ? _retriesMap$get : 0;
|
|
267
|
-
if ((call === null || call === void 0 ? void 0 : call.getOpponentSessionId()) !== participant.sessionId && this.wantsOutgoingCall(_userId, deviceId) && retries < 3) {
|
|
241
|
+
let needsRetry = false;
|
|
242
|
+
for (const [{
|
|
243
|
+
userId
|
|
244
|
+
}, participantMap] of this.participants) {
|
|
245
|
+
const callMap = this.calls.get(userId);
|
|
246
|
+
let retriesMap = this.retryCallCounts.get(userId);
|
|
247
|
+
for (const [deviceId, participant] of participantMap) {
|
|
248
|
+
const call = callMap?.get(deviceId);
|
|
249
|
+
const retries = retriesMap?.get(deviceId) ?? 0;
|
|
250
|
+
if (call?.getOpponentSessionId() !== participant.sessionId && this.wantsOutgoingCall(userId, deviceId) && retries < 3) {
|
|
268
251
|
if (retriesMap === undefined) {
|
|
269
252
|
retriesMap = new Map();
|
|
270
|
-
this.retryCallCounts.set(
|
|
253
|
+
this.retryCallCounts.set(userId, retriesMap);
|
|
271
254
|
}
|
|
272
255
|
retriesMap.set(deviceId, retries + 1);
|
|
273
256
|
needsRetry = true;
|
|
@@ -277,17 +260,17 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
277
260
|
if (needsRetry) this.placeOutgoingCalls();
|
|
278
261
|
});
|
|
279
262
|
_defineProperty(this, "onCallFeedsChanged", call => {
|
|
280
|
-
|
|
281
|
-
|
|
263
|
+
const opponentMemberId = getCallUserId(call);
|
|
264
|
+
const opponentDeviceId = call.getOpponentDeviceId();
|
|
282
265
|
if (!opponentMemberId) {
|
|
283
266
|
throw new Error("Cannot change call feeds without user id");
|
|
284
267
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
if (
|
|
268
|
+
const currentUserMediaFeed = this.getUserMediaFeed(opponentMemberId, opponentDeviceId);
|
|
269
|
+
const remoteUsermediaFeed = call.remoteUsermediaFeed;
|
|
270
|
+
const remoteFeedChanged = remoteUsermediaFeed !== currentUserMediaFeed;
|
|
271
|
+
const deviceMap = this.calls.get(opponentMemberId);
|
|
272
|
+
const currentCallForUserDevice = deviceMap?.get(opponentDeviceId);
|
|
273
|
+
if (currentCallForUserDevice?.callId !== call.callId) {
|
|
291
274
|
// the call in question is not the current call for this user/deviceId
|
|
292
275
|
// so ignore feed events from it otherwise we'll remove our real feeds
|
|
293
276
|
return;
|
|
@@ -301,9 +284,9 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
301
284
|
this.removeUserMediaFeed(currentUserMediaFeed);
|
|
302
285
|
}
|
|
303
286
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
287
|
+
const currentScreenshareFeed = this.getScreenshareFeed(opponentMemberId, opponentDeviceId);
|
|
288
|
+
const remoteScreensharingFeed = call.remoteScreensharingFeed;
|
|
289
|
+
const remoteScreenshareFeedChanged = remoteScreensharingFeed !== currentScreenshareFeed;
|
|
307
290
|
if (remoteScreenshareFeedChanged) {
|
|
308
291
|
if (!currentScreenshareFeed && remoteScreensharingFeed) {
|
|
309
292
|
this.addScreenshareFeed(remoteScreensharingFeed);
|
|
@@ -315,31 +298,29 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
315
298
|
}
|
|
316
299
|
});
|
|
317
300
|
_defineProperty(this, "onCallStateChanged", (call, state, _oldState) => {
|
|
318
|
-
var _call$getOpponentMemb2;
|
|
319
301
|
if (state === CallState.Ended) return;
|
|
320
|
-
|
|
302
|
+
const audioMuted = this.localCallFeed.isAudioMuted();
|
|
321
303
|
if (call.localUsermediaStream && call.isMicrophoneMuted() !== audioMuted) {
|
|
322
304
|
call.setMicrophoneMuted(audioMuted);
|
|
323
305
|
}
|
|
324
|
-
|
|
306
|
+
const videoMuted = this.localCallFeed.isVideoMuted();
|
|
325
307
|
if (call.localUsermediaStream && call.isLocalVideoMuted() !== videoMuted) {
|
|
326
308
|
call.setLocalVideoMuted(videoMuted);
|
|
327
309
|
}
|
|
328
|
-
|
|
310
|
+
const opponentUserId = call.getOpponentMember()?.userId;
|
|
329
311
|
if (state === CallState.Connected && opponentUserId) {
|
|
330
|
-
|
|
331
|
-
retriesMap
|
|
332
|
-
if (
|
|
312
|
+
const retriesMap = this.retryCallCounts.get(opponentUserId);
|
|
313
|
+
retriesMap?.delete(call.getOpponentDeviceId());
|
|
314
|
+
if (retriesMap?.size === 0) this.retryCallCounts.delete(opponentUserId);
|
|
333
315
|
}
|
|
334
316
|
});
|
|
335
317
|
_defineProperty(this, "onCallHangup", call => {
|
|
336
|
-
var _call$getOpponentMemb3, _call$getOpponentMemb4;
|
|
337
318
|
if (call.hangupReason === CallErrorCode.Replaced) return;
|
|
338
|
-
|
|
339
|
-
|
|
319
|
+
const opponentUserId = call.getOpponentMember()?.userId ?? this.room.getMember(call.invitee).userId;
|
|
320
|
+
const deviceMap = this.calls.get(opponentUserId);
|
|
340
321
|
|
|
341
322
|
// Sanity check that this call is in fact in the map
|
|
342
|
-
if (
|
|
323
|
+
if (deviceMap?.get(call.getOpponentDeviceId()) === call) {
|
|
343
324
|
this.disposeCall(call, call.hangupReason);
|
|
344
325
|
deviceMap.delete(call.getOpponentDeviceId());
|
|
345
326
|
if (deviceMap.size === 0) this.calls.delete(opponentUserId);
|
|
@@ -347,8 +328,8 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
347
328
|
}
|
|
348
329
|
});
|
|
349
330
|
_defineProperty(this, "onCallReplaced", (prevCall, newCall) => {
|
|
350
|
-
|
|
351
|
-
|
|
331
|
+
const opponentUserId = prevCall.getOpponentMember().userId;
|
|
332
|
+
let deviceMap = this.calls.get(opponentUserId);
|
|
352
333
|
if (deviceMap === undefined) {
|
|
353
334
|
deviceMap = new Map();
|
|
354
335
|
this.calls.set(opponentUserId, deviceMap);
|
|
@@ -359,12 +340,12 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
359
340
|
this.emit(GroupCallEvent.CallsChanged, this.calls);
|
|
360
341
|
});
|
|
361
342
|
_defineProperty(this, "onActiveSpeakerLoop", () => {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
for (
|
|
343
|
+
let topAvg = undefined;
|
|
344
|
+
let nextActiveSpeaker = undefined;
|
|
345
|
+
for (const callFeed of this.userMediaFeeds) {
|
|
365
346
|
if (callFeed.isLocal() && this.userMediaFeeds.length > 1) continue;
|
|
366
|
-
|
|
367
|
-
|
|
347
|
+
const total = callFeed.speakingVolumeSamples.reduce((acc, volume) => acc + Math.max(volume, SPEAKING_THRESHOLD));
|
|
348
|
+
const avg = total / callFeed.speakingVolumeSamples.length;
|
|
368
349
|
if (!topAvg || avg > topAvg) {
|
|
369
350
|
topAvg = avg;
|
|
370
351
|
nextActiveSpeaker = callFeed;
|
|
@@ -381,8 +362,8 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
381
362
|
// left get denied access to our media, and participants that just
|
|
382
363
|
// joined get granted access
|
|
383
364
|
this.forEachCall(call => {
|
|
384
|
-
|
|
385
|
-
for (
|
|
365
|
+
const expected = this.callExpected(call);
|
|
366
|
+
for (const feed of call.getLocalFeeds()) {
|
|
386
367
|
setTracksEnabled(feed.stream.getAudioTracks(), !feed.isAudioMuted() && expected);
|
|
387
368
|
setTracksEnabled(feed.stream.getVideoTracks(), !feed.isVideoMuted() && expected);
|
|
388
369
|
}
|
|
@@ -395,18 +376,26 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
395
376
|
if (newState === GroupCallState.Entered || oldState === GroupCallState.Entered || newState === GroupCallState.Ended) {
|
|
396
377
|
// We either entered, left, or ended the call
|
|
397
378
|
this.updateParticipants();
|
|
398
|
-
this.updateMemberState().catch(e => logger.error(
|
|
379
|
+
this.updateMemberState().catch(e => logger.error(`GroupCall ${this.groupCallId} onStateChanged() failed to update member state devices"`, e));
|
|
399
380
|
}
|
|
400
381
|
});
|
|
401
382
|
_defineProperty(this, "onLocalFeedsChanged", () => {
|
|
402
383
|
if (this.state === GroupCallState.Entered) {
|
|
403
|
-
this.updateMemberState().catch(e => logger.error(
|
|
384
|
+
this.updateMemberState().catch(e => logger.error(`GroupCall ${this.groupCallId} onLocalFeedsChanged() failed to update member state feeds`, e));
|
|
404
385
|
}
|
|
405
386
|
});
|
|
387
|
+
this.client = client;
|
|
388
|
+
this.room = room;
|
|
389
|
+
this.type = type;
|
|
390
|
+
this.isPtt = isPtt;
|
|
391
|
+
this.intent = intent;
|
|
392
|
+
this.dataChannelsEnabled = dataChannelsEnabled;
|
|
393
|
+
this.dataChannelOptions = dataChannelOptions;
|
|
394
|
+
this.useLivekit = useLivekit;
|
|
406
395
|
this.reEmitter = new ReEmitter(this);
|
|
407
|
-
this.groupCallId = groupCallId
|
|
396
|
+
this.groupCallId = groupCallId ?? genCallID();
|
|
408
397
|
this._livekitServiceURL = livekitServiceURL;
|
|
409
|
-
this.creationTs =
|
|
398
|
+
this.creationTs = room.currentState.getStateEvents(EventType.GroupCallPrefix, this.groupCallId)?.getTs() ?? null;
|
|
410
399
|
this.updateParticipants();
|
|
411
400
|
room.on(RoomStateEvent.Update, this.onRoomState);
|
|
412
401
|
this.on(GroupCallEvent.ParticipantsChanged, this.onParticipantsChanged);
|
|
@@ -414,32 +403,26 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
414
403
|
this.on(GroupCallEvent.LocalScreenshareStateChanged, this.onLocalFeedsChanged);
|
|
415
404
|
this.allowCallWithoutVideoAndAudio = !!isCallWithoutVideoAndAudio;
|
|
416
405
|
}
|
|
417
|
-
create() {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
yield _this.sendCallStateEvent();
|
|
424
|
-
return _this;
|
|
425
|
-
})();
|
|
406
|
+
async create() {
|
|
407
|
+
this.creationTs = Date.now();
|
|
408
|
+
this.client.groupCallEventHandler.groupCalls.set(this.room.roomId, this);
|
|
409
|
+
this.client.emit(GroupCallEventHandlerEvent.Outgoing, this);
|
|
410
|
+
await this.sendCallStateEvent();
|
|
411
|
+
return this;
|
|
426
412
|
}
|
|
427
|
-
sendCallStateEvent() {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
}
|
|
441
|
-
yield _this2.client.sendStateEvent(_this2.room.roomId, EventType.GroupCallPrefix, groupCallState, _this2.groupCallId);
|
|
442
|
-
})();
|
|
413
|
+
async sendCallStateEvent() {
|
|
414
|
+
const groupCallState = {
|
|
415
|
+
"m.intent": this.intent,
|
|
416
|
+
"m.type": this.type,
|
|
417
|
+
"io.element.ptt": this.isPtt,
|
|
418
|
+
// TODO: Specify data-channels better
|
|
419
|
+
"dataChannelsEnabled": this.dataChannelsEnabled,
|
|
420
|
+
"dataChannelOptions": this.dataChannelsEnabled ? this.dataChannelOptions : undefined
|
|
421
|
+
};
|
|
422
|
+
if (this.livekitServiceURL) {
|
|
423
|
+
groupCallState["io.element.livekit_service_url"] = this.livekitServiceURL;
|
|
424
|
+
}
|
|
425
|
+
await this.client.sendStateEvent(this.room.roomId, EventType.GroupCallPrefix, groupCallState, this.groupCallId);
|
|
443
426
|
}
|
|
444
427
|
get livekitServiceURL() {
|
|
445
428
|
return this._livekitServiceURL;
|
|
@@ -455,7 +438,7 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
455
438
|
return this._state;
|
|
456
439
|
}
|
|
457
440
|
set state(value) {
|
|
458
|
-
|
|
441
|
+
const prevValue = this._state;
|
|
459
442
|
if (value !== prevValue) {
|
|
460
443
|
this._state = value;
|
|
461
444
|
this.emit(GroupCallEvent.GroupCallStateChanged, value, prevValue);
|
|
@@ -469,9 +452,9 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
469
452
|
return this._participants;
|
|
470
453
|
}
|
|
471
454
|
set participants(value) {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
455
|
+
const prevValue = this._participants;
|
|
456
|
+
const participantStateEqual = (x, y) => x.sessionId === y.sessionId && x.screensharing === y.screensharing;
|
|
457
|
+
const deviceMapsEqual = (x, y) => mapsEqual(x, y, participantStateEqual);
|
|
475
458
|
|
|
476
459
|
// Only update if the map actually changed
|
|
477
460
|
if (!mapsEqual(value, prevValue, deviceMapsEqual)) {
|
|
@@ -506,19 +489,18 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
506
489
|
* @param f - The callback.
|
|
507
490
|
*/
|
|
508
491
|
forEachCall(f) {
|
|
509
|
-
for (
|
|
510
|
-
for (
|
|
492
|
+
for (const deviceMap of this.calls.values()) {
|
|
493
|
+
for (const call of deviceMap.values()) f(call);
|
|
511
494
|
}
|
|
512
495
|
}
|
|
513
496
|
getLocalFeeds() {
|
|
514
|
-
|
|
497
|
+
const feeds = [];
|
|
515
498
|
if (this.localCallFeed) feeds.push(this.localCallFeed);
|
|
516
499
|
if (this.localScreenshareFeed) feeds.push(this.localScreenshareFeed);
|
|
517
500
|
return feeds;
|
|
518
501
|
}
|
|
519
502
|
hasLocalParticipant() {
|
|
520
|
-
|
|
521
|
-
return (_this$participants$ge = (_this$participants$ge2 = this.participants.get(this.room.getMember(this.client.getUserId()))) === null || _this$participants$ge2 === void 0 ? void 0 : _this$participants$ge2.has(this.client.getDeviceId())) !== null && _this$participants$ge !== void 0 ? _this$participants$ge : false;
|
|
503
|
+
return this.participants.get(this.room.getMember(this.client.getUserId()))?.has(this.client.getDeviceId()) ?? false;
|
|
522
504
|
}
|
|
523
505
|
|
|
524
506
|
/**
|
|
@@ -526,117 +508,103 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
526
508
|
* given our knowledge of who is participating in the group call.
|
|
527
509
|
*/
|
|
528
510
|
callExpected(call) {
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
return member !== null && deviceId !== undefined && ((_this$participants$ge3 = this.participants.get(member)) === null || _this$participants$ge3 === void 0 ? void 0 : _this$participants$ge3.get(deviceId)) !== undefined;
|
|
511
|
+
const userId = getCallUserId(call);
|
|
512
|
+
const member = userId === null ? null : this.room.getMember(userId);
|
|
513
|
+
const deviceId = call.getOpponentDeviceId();
|
|
514
|
+
return member !== null && deviceId !== undefined && this.participants.get(member)?.get(deviceId) !== undefined;
|
|
534
515
|
}
|
|
535
|
-
initLocalCallFeed() {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
}
|
|
545
|
-
_this3.state = GroupCallState.InitializingLocalCallFeed;
|
|
516
|
+
async initLocalCallFeed() {
|
|
517
|
+
if (this.useLivekit) {
|
|
518
|
+
logger.info("Livekit group call: not starting local call feed.");
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
if (this.state !== GroupCallState.LocalCallFeedUninitialized) {
|
|
522
|
+
throw new Error(`Cannot initialize local call feed in the "${this.state}" state.`);
|
|
523
|
+
}
|
|
524
|
+
this.state = GroupCallState.InitializingLocalCallFeed;
|
|
546
525
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
})();
|
|
526
|
+
// wraps the real method to serialise calls, because we don't want to try starting
|
|
527
|
+
// multiple call feeds at once
|
|
528
|
+
if (this.initCallFeedPromise) return this.initCallFeedPromise;
|
|
529
|
+
try {
|
|
530
|
+
this.initCallFeedPromise = this.initLocalCallFeedInternal();
|
|
531
|
+
await this.initCallFeedPromise;
|
|
532
|
+
} finally {
|
|
533
|
+
this.initCallFeedPromise = undefined;
|
|
534
|
+
}
|
|
557
535
|
}
|
|
558
|
-
initLocalCallFeedInternal() {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
_this4.state = GroupCallState.LocalCallFeedUninitialized;
|
|
573
|
-
throw error;
|
|
574
|
-
}
|
|
536
|
+
async initLocalCallFeedInternal() {
|
|
537
|
+
logger.log(`GroupCall ${this.groupCallId} initLocalCallFeedInternal() running`);
|
|
538
|
+
let stream;
|
|
539
|
+
try {
|
|
540
|
+
stream = await this.client.getMediaHandler().getUserMediaStream(true, this.type === GroupCallType.Video);
|
|
541
|
+
} catch (error) {
|
|
542
|
+
// If is allowed to join a call without a media stream, then we
|
|
543
|
+
// don't throw an error here. But we need an empty Local Feed to establish
|
|
544
|
+
// a connection later.
|
|
545
|
+
if (this.allowCallWithoutVideoAndAudio) {
|
|
546
|
+
stream = new MediaStream();
|
|
547
|
+
} else {
|
|
548
|
+
this.state = GroupCallState.LocalCallFeedUninitialized;
|
|
549
|
+
throw error;
|
|
575
550
|
}
|
|
551
|
+
}
|
|
576
552
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
})();
|
|
553
|
+
// The call could've been disposed while we were waiting, and could
|
|
554
|
+
// also have been started back up again (hello, React 18) so if we're
|
|
555
|
+
// still in this 'initializing' state, carry on, otherwise bail.
|
|
556
|
+
if (this._state !== GroupCallState.InitializingLocalCallFeed) {
|
|
557
|
+
this.client.getMediaHandler().stopUserMediaStream(stream);
|
|
558
|
+
throw new Error("Group call disposed while gathering media stream");
|
|
559
|
+
}
|
|
560
|
+
const callFeed = new CallFeed({
|
|
561
|
+
client: this.client,
|
|
562
|
+
roomId: this.room.roomId,
|
|
563
|
+
userId: this.client.getUserId(),
|
|
564
|
+
deviceId: this.client.getDeviceId(),
|
|
565
|
+
stream,
|
|
566
|
+
purpose: SDPStreamMetadataPurpose.Usermedia,
|
|
567
|
+
audioMuted: this.initWithAudioMuted || stream.getAudioTracks().length === 0 || this.isPtt,
|
|
568
|
+
videoMuted: this.initWithVideoMuted || stream.getVideoTracks().length === 0
|
|
569
|
+
});
|
|
570
|
+
setTracksEnabled(stream.getAudioTracks(), !callFeed.isAudioMuted());
|
|
571
|
+
setTracksEnabled(stream.getVideoTracks(), !callFeed.isVideoMuted());
|
|
572
|
+
this.localCallFeed = callFeed;
|
|
573
|
+
this.addUserMediaFeed(callFeed);
|
|
574
|
+
this.state = GroupCallState.LocalCallFeedInitialized;
|
|
600
575
|
}
|
|
601
|
-
updateLocalUsermediaStream(stream) {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
_this5.client.getMediaHandler().stopUserMediaStream(oldStream);
|
|
613
|
-
}
|
|
614
|
-
})();
|
|
576
|
+
async updateLocalUsermediaStream(stream) {
|
|
577
|
+
if (this.localCallFeed) {
|
|
578
|
+
const oldStream = this.localCallFeed.stream;
|
|
579
|
+
this.localCallFeed.setNewStream(stream);
|
|
580
|
+
const micShouldBeMuted = this.localCallFeed.isAudioMuted();
|
|
581
|
+
const vidShouldBeMuted = this.localCallFeed.isVideoMuted();
|
|
582
|
+
logger.log(`GroupCall ${this.groupCallId} updateLocalUsermediaStream() (oldStreamId=${oldStream.id}, newStreamId=${stream.id}, micShouldBeMuted=${micShouldBeMuted}, vidShouldBeMuted=${vidShouldBeMuted})`);
|
|
583
|
+
setTracksEnabled(stream.getAudioTracks(), !micShouldBeMuted);
|
|
584
|
+
setTracksEnabled(stream.getVideoTracks(), !vidShouldBeMuted);
|
|
585
|
+
this.client.getMediaHandler().stopUserMediaStream(oldStream);
|
|
586
|
+
}
|
|
615
587
|
}
|
|
616
|
-
enter() {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
_this6.activeSpeakerLoopInterval = setInterval(_this6.onActiveSpeakerLoop, _this6.activeSpeakerInterval);
|
|
635
|
-
}
|
|
636
|
-
})();
|
|
588
|
+
async enter() {
|
|
589
|
+
if (this.state === GroupCallState.LocalCallFeedUninitialized) {
|
|
590
|
+
await this.initLocalCallFeed();
|
|
591
|
+
} else if (this.state !== GroupCallState.LocalCallFeedInitialized) {
|
|
592
|
+
throw new Error(`Cannot enter call in the "${this.state}" state`);
|
|
593
|
+
}
|
|
594
|
+
logger.log(`GroupCall ${this.groupCallId} enter() running`);
|
|
595
|
+
this.state = GroupCallState.Entered;
|
|
596
|
+
this.client.on(CallEventHandlerEvent.Incoming, this.onIncomingCall);
|
|
597
|
+
for (const call of this.client.callEventHandler.calls.values()) {
|
|
598
|
+
this.onIncomingCall(call);
|
|
599
|
+
}
|
|
600
|
+
if (!this.useLivekit) {
|
|
601
|
+
this.retryCallLoopInterval = setInterval(this.onRetryCallLoop, this.retryCallInterval);
|
|
602
|
+
this.activeSpeaker = undefined;
|
|
603
|
+
this.onActiveSpeakerLoop();
|
|
604
|
+
this.activeSpeakerLoopInterval = setInterval(this.onActiveSpeakerLoop, this.activeSpeakerInterval);
|
|
605
|
+
}
|
|
637
606
|
}
|
|
638
607
|
dispose() {
|
|
639
|
-
var _this$stats;
|
|
640
608
|
if (this.localCallFeed) {
|
|
641
609
|
this.removeUserMediaFeed(this.localCallFeed);
|
|
642
610
|
this.localCallFeed = undefined;
|
|
@@ -669,29 +637,24 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
669
637
|
this.retryCallCounts.clear();
|
|
670
638
|
clearInterval(this.retryCallLoopInterval);
|
|
671
639
|
this.client.removeListener(CallEventHandlerEvent.Incoming, this.onIncomingCall);
|
|
672
|
-
|
|
640
|
+
this.stats?.stop();
|
|
673
641
|
}
|
|
674
642
|
leave() {
|
|
675
643
|
this.dispose();
|
|
676
644
|
this.state = GroupCallState.LocalCallFeedUninitialized;
|
|
677
645
|
}
|
|
678
|
-
terminate() {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
yield _this7.client.sendStateEvent(_this7.room.roomId, EventType.GroupCallPrefix, _objectSpread(_objectSpread({}, existingStateEvent.getContent()), {}, {
|
|
691
|
-
"m.terminated": GroupCallTerminationReason.CallEnded
|
|
692
|
-
}), _this7.groupCallId);
|
|
693
|
-
}
|
|
694
|
-
})();
|
|
646
|
+
async terminate(emitStateEvent = true) {
|
|
647
|
+
this.dispose();
|
|
648
|
+
this.room.off(RoomStateEvent.Update, this.onRoomState);
|
|
649
|
+
this.client.groupCallEventHandler.groupCalls.delete(this.room.roomId);
|
|
650
|
+
this.client.emit(GroupCallEventHandlerEvent.Ended, this);
|
|
651
|
+
this.state = GroupCallState.Ended;
|
|
652
|
+
if (emitStateEvent) {
|
|
653
|
+
const existingStateEvent = this.room.currentState.getStateEvents(EventType.GroupCallPrefix, this.groupCallId);
|
|
654
|
+
await this.client.sendStateEvent(this.room.roomId, EventType.GroupCallPrefix, _objectSpread(_objectSpread({}, existingStateEvent.getContent()), {}, {
|
|
655
|
+
"m.terminated": GroupCallTerminationReason.CallEnded
|
|
656
|
+
}), this.groupCallId);
|
|
657
|
+
}
|
|
695
658
|
}
|
|
696
659
|
|
|
697
660
|
/*
|
|
@@ -716,65 +679,54 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
716
679
|
* @param muted - Whether to mute the microphone
|
|
717
680
|
* @returns Whether muting/unmuting was successful
|
|
718
681
|
*/
|
|
719
|
-
setMicrophoneMuted(muted) {
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
}
|
|
728
|
-
var sendUpdatesBefore = !muted && _this8.isPtt;
|
|
682
|
+
async setMicrophoneMuted(muted) {
|
|
683
|
+
// hasAudioDevice can block indefinitely if the window has lost focus,
|
|
684
|
+
// and it doesn't make much sense to keep a device from being muted, so
|
|
685
|
+
// we always allow muted = true changes to go through
|
|
686
|
+
if (!muted && !(await this.client.getMediaHandler().hasAudioDevice())) {
|
|
687
|
+
return false;
|
|
688
|
+
}
|
|
689
|
+
const sendUpdatesBefore = !muted && this.isPtt;
|
|
729
690
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
}
|
|
691
|
+
// set a timer for the maximum transmit time on PTT calls
|
|
692
|
+
if (this.isPtt) {
|
|
693
|
+
// Set or clear the max transmit timer
|
|
694
|
+
if (!muted && this.isMicrophoneMuted()) {
|
|
695
|
+
this.transmitTimer = setTimeout(() => {
|
|
696
|
+
this.setMicrophoneMuted(true);
|
|
697
|
+
}, this.pttMaxTransmitTime);
|
|
698
|
+
} else if (muted && !this.isMicrophoneMuted()) {
|
|
699
|
+
if (this.transmitTimer !== null) clearTimeout(this.transmitTimer);
|
|
700
|
+
this.transmitTimer = null;
|
|
741
701
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
}();
|
|
756
|
-
if (sendUpdatesBefore) yield sendUpdates();
|
|
757
|
-
if (_this8.localCallFeed) {
|
|
758
|
-
logger.log("GroupCall ".concat(_this8.groupCallId, " setMicrophoneMuted() (streamId=").concat(_this8.localCallFeed.stream.id, ", muted=").concat(muted, ")"));
|
|
759
|
-
var hasPermission = yield _this8.checkAudioPermissionIfNecessary(muted);
|
|
760
|
-
if (!hasPermission) {
|
|
761
|
-
return false;
|
|
762
|
-
}
|
|
763
|
-
_this8.localCallFeed.setAudioVideoMuted(muted, null);
|
|
764
|
-
// I don't believe its actually necessary to enable these tracks: they
|
|
765
|
-
// are the one on the GroupCall's own CallFeed and are cloned before being
|
|
766
|
-
// given to any of the actual calls, so these tracks don't actually go
|
|
767
|
-
// anywhere. Let's do it anyway to avoid confusion.
|
|
768
|
-
setTracksEnabled(_this8.localCallFeed.stream.getAudioTracks(), !muted);
|
|
769
|
-
} else {
|
|
770
|
-
logger.log("GroupCall ".concat(_this8.groupCallId, " setMicrophoneMuted() no stream muted (muted=").concat(muted, ")"));
|
|
771
|
-
_this8.initWithAudioMuted = muted;
|
|
702
|
+
}
|
|
703
|
+
this.forEachCall(call => call.localUsermediaFeed?.setAudioVideoMuted(muted, null));
|
|
704
|
+
const sendUpdates = async () => {
|
|
705
|
+
const updates = [];
|
|
706
|
+
this.forEachCall(call => updates.push(call.sendMetadataUpdate()));
|
|
707
|
+
await Promise.all(updates).catch(e => logger.info(`GroupCall ${this.groupCallId} setMicrophoneMuted() failed to send some metadata updates`, e));
|
|
708
|
+
};
|
|
709
|
+
if (sendUpdatesBefore) await sendUpdates();
|
|
710
|
+
if (this.localCallFeed) {
|
|
711
|
+
logger.log(`GroupCall ${this.groupCallId} setMicrophoneMuted() (streamId=${this.localCallFeed.stream.id}, muted=${muted})`);
|
|
712
|
+
const hasPermission = await this.checkAudioPermissionIfNecessary(muted);
|
|
713
|
+
if (!hasPermission) {
|
|
714
|
+
return false;
|
|
772
715
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
716
|
+
this.localCallFeed.setAudioVideoMuted(muted, null);
|
|
717
|
+
// I don't believe its actually necessary to enable these tracks: they
|
|
718
|
+
// are the one on the GroupCall's own CallFeed and are cloned before being
|
|
719
|
+
// given to any of the actual calls, so these tracks don't actually go
|
|
720
|
+
// anywhere. Let's do it anyway to avoid confusion.
|
|
721
|
+
setTracksEnabled(this.localCallFeed.stream.getAudioTracks(), !muted);
|
|
722
|
+
} else {
|
|
723
|
+
logger.log(`GroupCall ${this.groupCallId} setMicrophoneMuted() no stream muted (muted=${muted})`);
|
|
724
|
+
this.initWithAudioMuted = muted;
|
|
725
|
+
}
|
|
726
|
+
this.forEachCall(call => setTracksEnabled(call.localUsermediaFeed.stream.getAudioTracks(), !muted && this.callExpected(call)));
|
|
727
|
+
this.emit(GroupCallEvent.LocalMuteStateChanged, muted, this.isLocalVideoMuted());
|
|
728
|
+
if (!sendUpdatesBefore) await sendUpdates();
|
|
729
|
+
return true;
|
|
778
730
|
}
|
|
779
731
|
|
|
780
732
|
/**
|
|
@@ -786,27 +738,24 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
786
738
|
* not Audio Track exists.
|
|
787
739
|
* As such, this is a compromise, because, the access rights should always be queried before the call.
|
|
788
740
|
*/
|
|
789
|
-
checkAudioPermissionIfNecessary(muted) {
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
if (
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
logger.log("GroupCall ".concat(_this9.groupCallId, " setMicrophoneMuted() no device to receive local stream, muted=").concat(muted));
|
|
800
|
-
return false;
|
|
801
|
-
}
|
|
741
|
+
async checkAudioPermissionIfNecessary(muted) {
|
|
742
|
+
// We needed this here to avoid an error in case user join a call without a device.
|
|
743
|
+
try {
|
|
744
|
+
if (!muted && this.localCallFeed && !this.localCallFeed.hasAudioTrack) {
|
|
745
|
+
const stream = await this.client.getMediaHandler().getUserMediaStream(true, !this.localCallFeed.isVideoMuted());
|
|
746
|
+
if (stream?.getTracks().length === 0) {
|
|
747
|
+
// if case permission denied to get a stream stop this here
|
|
748
|
+
/* istanbul ignore next */
|
|
749
|
+
logger.log(`GroupCall ${this.groupCallId} setMicrophoneMuted() no device to receive local stream, muted=${muted}`);
|
|
750
|
+
return false;
|
|
802
751
|
}
|
|
803
|
-
} catch (_unused) {
|
|
804
|
-
/* istanbul ignore next */
|
|
805
|
-
logger.log("GroupCall ".concat(_this9.groupCallId, " setMicrophoneMuted() no device or permission to receive local stream, muted=").concat(muted));
|
|
806
|
-
return false;
|
|
807
752
|
}
|
|
808
|
-
|
|
809
|
-
|
|
753
|
+
} catch {
|
|
754
|
+
/* istanbul ignore next */
|
|
755
|
+
logger.log(`GroupCall ${this.groupCallId} setMicrophoneMuted() no device or permission to receive local stream, muted=${muted}`);
|
|
756
|
+
return false;
|
|
757
|
+
}
|
|
758
|
+
return true;
|
|
810
759
|
}
|
|
811
760
|
|
|
812
761
|
/**
|
|
@@ -814,102 +763,91 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
814
763
|
* @param muted - Whether to mute the video
|
|
815
764
|
* @returns Whether muting/unmuting was successful
|
|
816
765
|
*/
|
|
817
|
-
setLocalVideoMuted(muted) {
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
766
|
+
async setLocalVideoMuted(muted) {
|
|
767
|
+
// hasAudioDevice can block indefinitely if the window has lost focus,
|
|
768
|
+
// and it doesn't make much sense to keep a device from being muted, so
|
|
769
|
+
// we always allow muted = true changes to go through
|
|
770
|
+
if (!muted && !(await this.client.getMediaHandler().hasVideoDevice())) {
|
|
771
|
+
return false;
|
|
772
|
+
}
|
|
773
|
+
if (this.localCallFeed) {
|
|
774
|
+
/* istanbul ignore next */
|
|
775
|
+
logger.log(`GroupCall ${this.groupCallId} setLocalVideoMuted() (stream=${this.localCallFeed.stream.id}, muted=${muted})`);
|
|
776
|
+
try {
|
|
777
|
+
const stream = await this.client.getMediaHandler().getUserMediaStream(true, !muted);
|
|
778
|
+
await this.updateLocalUsermediaStream(stream);
|
|
779
|
+
this.localCallFeed.setAudioVideoMuted(null, muted);
|
|
780
|
+
setTracksEnabled(this.localCallFeed.stream.getVideoTracks(), !muted);
|
|
781
|
+
} catch {
|
|
782
|
+
// No permission to video device
|
|
827
783
|
/* istanbul ignore next */
|
|
828
|
-
logger.log(
|
|
829
|
-
|
|
830
|
-
var stream = yield _this0.client.getMediaHandler().getUserMediaStream(true, !muted);
|
|
831
|
-
yield _this0.updateLocalUsermediaStream(stream);
|
|
832
|
-
_this0.localCallFeed.setAudioVideoMuted(null, muted);
|
|
833
|
-
setTracksEnabled(_this0.localCallFeed.stream.getVideoTracks(), !muted);
|
|
834
|
-
} catch (_unused2) {
|
|
835
|
-
// No permission to video device
|
|
836
|
-
/* istanbul ignore next */
|
|
837
|
-
logger.log("GroupCall ".concat(_this0.groupCallId, " setLocalVideoMuted() no device or permission to receive local stream, muted=").concat(muted));
|
|
838
|
-
return false;
|
|
839
|
-
}
|
|
840
|
-
} else {
|
|
841
|
-
logger.log("GroupCall ".concat(_this0.groupCallId, " setLocalVideoMuted() no stream muted (muted=").concat(muted, ")"));
|
|
842
|
-
_this0.initWithVideoMuted = muted;
|
|
784
|
+
logger.log(`GroupCall ${this.groupCallId} setLocalVideoMuted() no device or permission to receive local stream, muted=${muted}`);
|
|
785
|
+
return false;
|
|
843
786
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
787
|
+
} else {
|
|
788
|
+
logger.log(`GroupCall ${this.groupCallId} setLocalVideoMuted() no stream muted (muted=${muted})`);
|
|
789
|
+
this.initWithVideoMuted = muted;
|
|
790
|
+
}
|
|
791
|
+
const updates = [];
|
|
792
|
+
this.forEachCall(call => updates.push(call.setLocalVideoMuted(muted)));
|
|
793
|
+
await Promise.all(updates);
|
|
847
794
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
})();
|
|
795
|
+
// We setTracksEnabled again, independently from the call doing it
|
|
796
|
+
// internally, since we might not be expecting the call
|
|
797
|
+
this.forEachCall(call => setTracksEnabled(call.localUsermediaFeed.stream.getVideoTracks(), !muted && this.callExpected(call)));
|
|
798
|
+
this.emit(GroupCallEvent.LocalMuteStateChanged, this.isMicrophoneMuted(), muted);
|
|
799
|
+
return true;
|
|
854
800
|
}
|
|
855
|
-
setScreensharingEnabled(enabled) {
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
var _loop = function* _loop(track) {
|
|
868
|
-
var onTrackEnded = () => {
|
|
869
|
-
_this1.setScreensharingEnabled(false);
|
|
870
|
-
track.removeEventListener("ended", onTrackEnded);
|
|
871
|
-
};
|
|
872
|
-
track.addEventListener("ended", onTrackEnded);
|
|
801
|
+
async setScreensharingEnabled(enabled, opts = {}) {
|
|
802
|
+
if (enabled === this.isScreensharing()) {
|
|
803
|
+
return enabled;
|
|
804
|
+
}
|
|
805
|
+
if (enabled) {
|
|
806
|
+
try {
|
|
807
|
+
logger.log(`GroupCall ${this.groupCallId} setScreensharingEnabled() is asking for screensharing permissions`);
|
|
808
|
+
const stream = await this.client.getMediaHandler().getScreensharingStream(opts);
|
|
809
|
+
for (const track of stream.getTracks()) {
|
|
810
|
+
const onTrackEnded = () => {
|
|
811
|
+
this.setScreensharingEnabled(false);
|
|
812
|
+
track.removeEventListener("ended", onTrackEnded);
|
|
873
813
|
};
|
|
874
|
-
|
|
875
|
-
yield* _loop(track);
|
|
876
|
-
}
|
|
877
|
-
logger.log("GroupCall ".concat(_this1.groupCallId, " setScreensharingEnabled() granted screensharing permissions. Setting screensharing enabled on all calls"));
|
|
878
|
-
_this1.localDesktopCapturerSourceId = opts.desktopCapturerSourceId;
|
|
879
|
-
_this1.localScreenshareFeed = new CallFeed({
|
|
880
|
-
client: _this1.client,
|
|
881
|
-
roomId: _this1.room.roomId,
|
|
882
|
-
userId: _this1.client.getUserId(),
|
|
883
|
-
deviceId: _this1.client.getDeviceId(),
|
|
884
|
-
stream,
|
|
885
|
-
purpose: SDPStreamMetadataPurpose.Screenshare,
|
|
886
|
-
audioMuted: false,
|
|
887
|
-
videoMuted: false
|
|
888
|
-
});
|
|
889
|
-
_this1.addScreenshareFeed(_this1.localScreenshareFeed);
|
|
890
|
-
_this1.emit(GroupCallEvent.LocalScreenshareStateChanged, true, _this1.localScreenshareFeed, _this1.localDesktopCapturerSourceId);
|
|
891
|
-
|
|
892
|
-
// TODO: handle errors
|
|
893
|
-
_this1.forEachCall(call => call.pushLocalFeed(_this1.localScreenshareFeed.clone()));
|
|
894
|
-
return true;
|
|
895
|
-
} catch (error) {
|
|
896
|
-
if (opts.throwOnFail) throw error;
|
|
897
|
-
logger.error("GroupCall ".concat(_this1.groupCallId, " setScreensharingEnabled() enabling screensharing error"), error);
|
|
898
|
-
_this1.emit(GroupCallEvent.Error, new GroupCallError(GroupCallErrorCode.NoUserMedia, "Failed to get screen-sharing stream: ", error));
|
|
899
|
-
return false;
|
|
814
|
+
track.addEventListener("ended", onTrackEnded);
|
|
900
815
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
816
|
+
logger.log(`GroupCall ${this.groupCallId} setScreensharingEnabled() granted screensharing permissions. Setting screensharing enabled on all calls`);
|
|
817
|
+
this.localDesktopCapturerSourceId = opts.desktopCapturerSourceId;
|
|
818
|
+
this.localScreenshareFeed = new CallFeed({
|
|
819
|
+
client: this.client,
|
|
820
|
+
roomId: this.room.roomId,
|
|
821
|
+
userId: this.client.getUserId(),
|
|
822
|
+
deviceId: this.client.getDeviceId(),
|
|
823
|
+
stream,
|
|
824
|
+
purpose: SDPStreamMetadataPurpose.Screenshare,
|
|
825
|
+
audioMuted: false,
|
|
826
|
+
videoMuted: false
|
|
904
827
|
});
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
828
|
+
this.addScreenshareFeed(this.localScreenshareFeed);
|
|
829
|
+
this.emit(GroupCallEvent.LocalScreenshareStateChanged, true, this.localScreenshareFeed, this.localDesktopCapturerSourceId);
|
|
830
|
+
|
|
831
|
+
// TODO: handle errors
|
|
832
|
+
this.forEachCall(call => call.pushLocalFeed(this.localScreenshareFeed.clone()));
|
|
833
|
+
return true;
|
|
834
|
+
} catch (error) {
|
|
835
|
+
if (opts.throwOnFail) throw error;
|
|
836
|
+
logger.error(`GroupCall ${this.groupCallId} setScreensharingEnabled() enabling screensharing error`, error);
|
|
837
|
+
this.emit(GroupCallEvent.Error, new GroupCallError(GroupCallErrorCode.NoUserMedia, "Failed to get screen-sharing stream: ", error));
|
|
910
838
|
return false;
|
|
911
839
|
}
|
|
912
|
-
}
|
|
840
|
+
} else {
|
|
841
|
+
this.forEachCall(call => {
|
|
842
|
+
if (call.localScreensharingFeed) call.removeLocalFeed(call.localScreensharingFeed);
|
|
843
|
+
});
|
|
844
|
+
this.client.getMediaHandler().stopScreensharingStream(this.localScreenshareFeed.stream);
|
|
845
|
+
this.removeScreenshareFeed(this.localScreenshareFeed);
|
|
846
|
+
this.localScreenshareFeed = undefined;
|
|
847
|
+
this.localDesktopCapturerSourceId = undefined;
|
|
848
|
+
this.emit(GroupCallEvent.LocalScreenshareStateChanged, false, undefined, undefined);
|
|
849
|
+
return false;
|
|
850
|
+
}
|
|
913
851
|
}
|
|
914
852
|
isScreensharing() {
|
|
915
853
|
return !!this.localScreenshareFeed;
|
|
@@ -922,8 +860,8 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
922
860
|
* @returns Whether we need to place an outgoing call to the participant.
|
|
923
861
|
*/
|
|
924
862
|
wantsOutgoingCall(userId, deviceId) {
|
|
925
|
-
|
|
926
|
-
|
|
863
|
+
const localUserId = this.client.getUserId();
|
|
864
|
+
const localDeviceId = this.client.getDeviceId();
|
|
927
865
|
return (
|
|
928
866
|
// If a user's ID is less than our own, they'll call us
|
|
929
867
|
userId >= localUserId && (
|
|
@@ -936,68 +874,54 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
936
874
|
* Places calls to all participants that we're responsible for calling.
|
|
937
875
|
*/
|
|
938
876
|
placeOutgoingCalls() {
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
877
|
+
let callsChanged = false;
|
|
878
|
+
for (const [{
|
|
879
|
+
userId
|
|
880
|
+
}, participantMap] of this.participants) {
|
|
881
|
+
const callMap = this.calls.get(userId) ?? new Map();
|
|
882
|
+
for (const [deviceId, participant] of participantMap) {
|
|
883
|
+
const prevCall = callMap.get(deviceId);
|
|
884
|
+
if (prevCall?.getOpponentSessionId() !== participant.sessionId && this.wantsOutgoingCall(userId, deviceId)) {
|
|
885
|
+
callsChanged = true;
|
|
886
|
+
if (prevCall !== undefined) {
|
|
887
|
+
logger.debug(`GroupCall ${this.groupCallId} placeOutgoingCalls() replacing call (userId=${userId}, deviceId=${deviceId}, callId=${prevCall.callId})`);
|
|
888
|
+
prevCall.hangup(CallErrorCode.NewSession, false);
|
|
889
|
+
}
|
|
890
|
+
const newCall = createNewMatrixCall(this.client, this.room.roomId, {
|
|
891
|
+
invitee: userId,
|
|
892
|
+
opponentDeviceId: deviceId,
|
|
893
|
+
opponentSessionId: participant.sessionId,
|
|
894
|
+
groupCallId: this.groupCallId
|
|
895
|
+
});
|
|
896
|
+
if (newCall === null) {
|
|
897
|
+
logger.error(`GroupCall ${this.groupCallId} placeOutgoingCalls() failed to create call (userId=${userId}, device=${deviceId})`);
|
|
898
|
+
callMap.delete(deviceId);
|
|
899
|
+
} else {
|
|
900
|
+
this.initCall(newCall);
|
|
901
|
+
callMap.set(deviceId, newCall);
|
|
902
|
+
logger.debug(`GroupCall ${this.groupCallId} placeOutgoingCalls() placing call (userId=${userId}, deviceId=${deviceId}, sessionId=${participant.sessionId})`);
|
|
903
|
+
newCall.placeCallWithCallFeeds(this.getLocalFeeds().map(feed => feed.clone()), participant.screensharing).then(() => {
|
|
904
|
+
if (this.dataChannelsEnabled) {
|
|
905
|
+
newCall.createDataChannel("datachannel", this.dataChannelOptions);
|
|
957
906
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
groupCallId: _this10.groupCallId
|
|
963
|
-
});
|
|
964
|
-
if (newCall === null) {
|
|
965
|
-
logger.error("GroupCall ".concat(_this10.groupCallId, " placeOutgoingCalls() failed to create call (userId=").concat(userId, ", device=").concat(deviceId, ")"));
|
|
966
|
-
callMap.delete(deviceId);
|
|
907
|
+
}).catch(e => {
|
|
908
|
+
logger.warn(`GroupCall ${this.groupCallId} placeOutgoingCalls() failed to place call (userId=${userId})`, e);
|
|
909
|
+
if (e instanceof CallError && e.code === GroupCallErrorCode.UnknownDevice) {
|
|
910
|
+
this.emit(GroupCallEvent.Error, e);
|
|
967
911
|
} else {
|
|
968
|
-
|
|
969
|
-
callMap.set(deviceId, newCall);
|
|
970
|
-
logger.debug("GroupCall ".concat(_this10.groupCallId, " placeOutgoingCalls() placing call (userId=").concat(userId, ", deviceId=").concat(deviceId, ", sessionId=").concat(participant.sessionId, ")"));
|
|
971
|
-
newCall.placeCallWithCallFeeds(_this10.getLocalFeeds().map(feed => feed.clone()), participant.screensharing).then(() => {
|
|
972
|
-
if (_this10.dataChannelsEnabled) {
|
|
973
|
-
newCall.createDataChannel("datachannel", _this10.dataChannelOptions);
|
|
974
|
-
}
|
|
975
|
-
}).catch(e => {
|
|
976
|
-
logger.warn("GroupCall ".concat(_this10.groupCallId, " placeOutgoingCalls() failed to place call (userId=").concat(userId, ")"), e);
|
|
977
|
-
if (e instanceof CallError && e.code === GroupCallErrorCode.UnknownDevice) {
|
|
978
|
-
_this10.emit(GroupCallEvent.Error, e);
|
|
979
|
-
} else {
|
|
980
|
-
_this10.emit(GroupCallEvent.Error, new GroupCallError(GroupCallErrorCode.PlaceCallFailed, "Failed to place call to ".concat(userId)));
|
|
981
|
-
}
|
|
982
|
-
newCall.hangup(CallErrorCode.SignallingFailed, false);
|
|
983
|
-
if (callMap.get(deviceId) === newCall) callMap.delete(deviceId);
|
|
984
|
-
});
|
|
912
|
+
this.emit(GroupCallEvent.Error, new GroupCallError(GroupCallErrorCode.PlaceCallFailed, `Failed to place call to ${userId}`));
|
|
985
913
|
}
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
_loop3();
|
|
991
|
-
}
|
|
992
|
-
if (callMap.size > 0) {
|
|
993
|
-
_this10.calls.set(userId, callMap);
|
|
994
|
-
} else {
|
|
995
|
-
_this10.calls.delete(userId);
|
|
914
|
+
newCall.hangup(CallErrorCode.SignallingFailed, false);
|
|
915
|
+
if (callMap.get(deviceId) === newCall) callMap.delete(deviceId);
|
|
916
|
+
});
|
|
917
|
+
}
|
|
996
918
|
}
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
919
|
+
}
|
|
920
|
+
if (callMap.size > 0) {
|
|
921
|
+
this.calls.set(userId, callMap);
|
|
922
|
+
} else {
|
|
923
|
+
this.calls.delete(userId);
|
|
924
|
+
}
|
|
1001
925
|
}
|
|
1002
926
|
if (callsChanged) this.emit(GroupCallEvent.CallsChanged, this.calls);
|
|
1003
927
|
}
|
|
@@ -1010,15 +934,15 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
1010
934
|
return userId === undefined ? this.room.currentState.getStateEvents(EventType.GroupCallMemberPrefix) : this.room.currentState.getStateEvents(EventType.GroupCallMemberPrefix, userId);
|
|
1011
935
|
}
|
|
1012
936
|
initCall(call) {
|
|
1013
|
-
|
|
937
|
+
const opponentMemberId = getCallUserId(call);
|
|
1014
938
|
if (!opponentMemberId) {
|
|
1015
939
|
throw new Error("Cannot init call without user id");
|
|
1016
940
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
941
|
+
const onCallFeedsChanged = () => this.onCallFeedsChanged(call);
|
|
942
|
+
const onCallStateChanged = (state, oldState) => this.onCallStateChanged(call, state, oldState);
|
|
943
|
+
const onCallHangup = this.onCallHangup;
|
|
944
|
+
const onCallReplaced = newCall => this.onCallReplaced(call, newCall);
|
|
945
|
+
let deviceMap = this.callHandlers.get(opponentMemberId);
|
|
1022
946
|
if (deviceMap === undefined) {
|
|
1023
947
|
deviceMap = new Map();
|
|
1024
948
|
this.callHandlers.set(opponentMemberId, deviceMap);
|
|
@@ -1039,17 +963,18 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
1039
963
|
onCallFeedsChanged();
|
|
1040
964
|
}
|
|
1041
965
|
disposeCall(call, hangupReason) {
|
|
1042
|
-
|
|
1043
|
-
|
|
966
|
+
const opponentMemberId = getCallUserId(call);
|
|
967
|
+
const opponentDeviceId = call.getOpponentDeviceId();
|
|
1044
968
|
if (!opponentMemberId) {
|
|
1045
969
|
throw new Error("Cannot dispose call without user id");
|
|
1046
970
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
onCallFeedsChanged
|
|
1050
|
-
onCallStateChanged
|
|
1051
|
-
onCallHangup
|
|
1052
|
-
onCallReplaced
|
|
971
|
+
const deviceMap = this.callHandlers.get(opponentMemberId);
|
|
972
|
+
const {
|
|
973
|
+
onCallFeedsChanged,
|
|
974
|
+
onCallStateChanged,
|
|
975
|
+
onCallHangup,
|
|
976
|
+
onCallReplaced
|
|
977
|
+
} = deviceMap.get(opponentDeviceId);
|
|
1053
978
|
call.removeListener(CallEvent.FeedsChanged, onCallFeedsChanged);
|
|
1054
979
|
call.removeListener(CallEvent.State, onCallStateChanged);
|
|
1055
980
|
call.removeListener(CallEvent.Hangup, onCallHangup);
|
|
@@ -1059,11 +984,11 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
1059
984
|
if (call.hangupReason === CallErrorCode.Replaced) {
|
|
1060
985
|
return;
|
|
1061
986
|
}
|
|
1062
|
-
|
|
987
|
+
const usermediaFeed = this.getUserMediaFeed(opponentMemberId, opponentDeviceId);
|
|
1063
988
|
if (usermediaFeed) {
|
|
1064
989
|
this.removeUserMediaFeed(usermediaFeed);
|
|
1065
990
|
}
|
|
1066
|
-
|
|
991
|
+
const screenshareFeed = this.getScreenshareFeed(opponentMemberId, opponentDeviceId);
|
|
1067
992
|
if (screenshareFeed) {
|
|
1068
993
|
this.removeScreenshareFeed(screenshareFeed);
|
|
1069
994
|
}
|
|
@@ -1081,7 +1006,7 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
1081
1006
|
this.emit(GroupCallEvent.UserMediaFeedsChanged, this.userMediaFeeds);
|
|
1082
1007
|
}
|
|
1083
1008
|
replaceUserMediaFeed(existingFeed, replacementFeed) {
|
|
1084
|
-
|
|
1009
|
+
const feedIndex = this.userMediaFeeds.findIndex(f => f.userId === existingFeed.userId && f.deviceId === existingFeed.deviceId);
|
|
1085
1010
|
if (feedIndex === -1) {
|
|
1086
1011
|
throw new Error("Couldn't find user media feed to replace");
|
|
1087
1012
|
}
|
|
@@ -1091,7 +1016,7 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
1091
1016
|
this.emit(GroupCallEvent.UserMediaFeedsChanged, this.userMediaFeeds);
|
|
1092
1017
|
}
|
|
1093
1018
|
removeUserMediaFeed(callFeed) {
|
|
1094
|
-
|
|
1019
|
+
const feedIndex = this.userMediaFeeds.findIndex(f => f.userId === callFeed.userId && f.deviceId === callFeed.deviceId);
|
|
1095
1020
|
if (feedIndex === -1) {
|
|
1096
1021
|
throw new Error("Couldn't find user media feed to remove");
|
|
1097
1022
|
}
|
|
@@ -1115,7 +1040,7 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
1115
1040
|
this.emit(GroupCallEvent.ScreenshareFeedsChanged, this.screenshareFeeds);
|
|
1116
1041
|
}
|
|
1117
1042
|
replaceScreenshareFeed(existingFeed, replacementFeed) {
|
|
1118
|
-
|
|
1043
|
+
const feedIndex = this.screenshareFeeds.findIndex(f => f.userId === existingFeed.userId && f.deviceId === existingFeed.deviceId);
|
|
1119
1044
|
if (feedIndex === -1) {
|
|
1120
1045
|
throw new Error("Couldn't find screenshare feed to replace");
|
|
1121
1046
|
}
|
|
@@ -1124,7 +1049,7 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
1124
1049
|
this.emit(GroupCallEvent.ScreenshareFeedsChanged, this.screenshareFeeds);
|
|
1125
1050
|
}
|
|
1126
1051
|
removeScreenshareFeed(callFeed) {
|
|
1127
|
-
|
|
1052
|
+
const feedIndex = this.screenshareFeeds.findIndex(f => f.userId === callFeed.userId && f.deviceId === callFeed.deviceId);
|
|
1128
1053
|
if (feedIndex === -1) {
|
|
1129
1054
|
throw new Error("Couldn't find screenshare feed to remove");
|
|
1130
1055
|
}
|
|
@@ -1137,11 +1062,11 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
1137
1062
|
* Recalculates and updates the participant map to match the room state.
|
|
1138
1063
|
*/
|
|
1139
1064
|
updateParticipants() {
|
|
1140
|
-
|
|
1065
|
+
const localMember = this.room.getMember(this.client.getSafeUserId());
|
|
1141
1066
|
if (!localMember) {
|
|
1142
1067
|
// The client hasn't fetched enough of the room state to get our own member
|
|
1143
1068
|
// event. This probably shouldn't happen, but sanity check & exit for now.
|
|
1144
|
-
logger.warn(
|
|
1069
|
+
logger.warn(`GroupCall ${this.groupCallId} updateParticipants() tried to update participants before local room member is available`);
|
|
1145
1070
|
return;
|
|
1146
1071
|
}
|
|
1147
1072
|
if (this.participantsExpirationTimer !== null) {
|
|
@@ -1152,30 +1077,30 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
1152
1077
|
this.participants = new Map();
|
|
1153
1078
|
return;
|
|
1154
1079
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
for (
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1080
|
+
const participants = new Map();
|
|
1081
|
+
const now = Date.now();
|
|
1082
|
+
const entered = this.state === GroupCallState.Entered || this.enteredViaAnotherSession;
|
|
1083
|
+
let nextExpiration = Infinity;
|
|
1084
|
+
for (const e of this.getMemberStateEvents()) {
|
|
1085
|
+
const member = this.room.getMember(e.getStateKey());
|
|
1086
|
+
const content = e.getContent();
|
|
1087
|
+
const calls = Array.isArray(content["m.calls"]) ? content["m.calls"] : [];
|
|
1088
|
+
const call = calls.find(call => call["m.call_id"] === this.groupCallId);
|
|
1089
|
+
const devices = Array.isArray(call?.["m.devices"]) ? call["m.devices"] : [];
|
|
1165
1090
|
|
|
1166
1091
|
// Filter out invalid and expired devices
|
|
1167
|
-
|
|
1092
|
+
let validDevices = devices.filter(d => typeof d.device_id === "string" && typeof d.session_id === "string" && typeof d.expires_ts === "number" && d.expires_ts > now && Array.isArray(d.feeds));
|
|
1168
1093
|
|
|
1169
1094
|
// Apply local echo for the unentered case
|
|
1170
|
-
if (!entered &&
|
|
1095
|
+
if (!entered && member?.userId === this.client.getUserId()) {
|
|
1171
1096
|
validDevices = validDevices.filter(d => d.device_id !== this.client.getDeviceId());
|
|
1172
1097
|
}
|
|
1173
1098
|
|
|
1174
1099
|
// Must have a connected device and be joined to the room
|
|
1175
|
-
if (validDevices.length > 0 &&
|
|
1176
|
-
|
|
1100
|
+
if (validDevices.length > 0 && member?.membership === KnownMembership.Join) {
|
|
1101
|
+
const deviceMap = new Map();
|
|
1177
1102
|
participants.set(member, deviceMap);
|
|
1178
|
-
for (
|
|
1103
|
+
for (const d of validDevices) {
|
|
1179
1104
|
deviceMap.set(d.device_id, {
|
|
1180
1105
|
sessionId: d.session_id,
|
|
1181
1106
|
screensharing: d.feeds.some(f => f.purpose === SDPStreamMetadataPurpose.Screenshare)
|
|
@@ -1187,13 +1112,13 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
1187
1112
|
|
|
1188
1113
|
// Apply local echo for the entered case
|
|
1189
1114
|
if (entered) {
|
|
1190
|
-
|
|
1191
|
-
if (
|
|
1192
|
-
|
|
1193
|
-
participants.set(localMember,
|
|
1115
|
+
let deviceMap = participants.get(localMember);
|
|
1116
|
+
if (deviceMap === undefined) {
|
|
1117
|
+
deviceMap = new Map();
|
|
1118
|
+
participants.set(localMember, deviceMap);
|
|
1194
1119
|
}
|
|
1195
|
-
if (!
|
|
1196
|
-
|
|
1120
|
+
if (!deviceMap.has(this.client.getDeviceId())) {
|
|
1121
|
+
deviceMap.set(this.client.getDeviceId(), {
|
|
1197
1122
|
sessionId: this.client.getSessionId(),
|
|
1198
1123
|
screensharing: this.getLocalFeeds().some(f => f.purpose === SDPStreamMetadataPurpose.Screenshare)
|
|
1199
1124
|
});
|
|
@@ -1211,116 +1136,102 @@ export class GroupCall extends TypedEventEmitter {
|
|
|
1211
1136
|
* returns null, the update will be skipped.
|
|
1212
1137
|
* @param keepAlive - Whether the request should outlive the window.
|
|
1213
1138
|
*/
|
|
1214
|
-
updateDevices(fn) {
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
for (var c of calls) {
|
|
1228
|
-
if (c["m.call_id"] === _this11.groupCallId) {
|
|
1229
|
-
call = c;
|
|
1230
|
-
} else {
|
|
1231
|
-
otherCalls.push(c);
|
|
1232
|
-
}
|
|
1139
|
+
async updateDevices(fn, keepAlive = false) {
|
|
1140
|
+
const now = Date.now();
|
|
1141
|
+
const localUserId = this.client.getUserId();
|
|
1142
|
+
const event = this.getMemberStateEvents(localUserId);
|
|
1143
|
+
const content = event?.getContent() ?? {};
|
|
1144
|
+
const calls = Array.isArray(content["m.calls"]) ? content["m.calls"] : [];
|
|
1145
|
+
let call = null;
|
|
1146
|
+
const otherCalls = [];
|
|
1147
|
+
for (const c of calls) {
|
|
1148
|
+
if (c["m.call_id"] === this.groupCallId) {
|
|
1149
|
+
call = c;
|
|
1150
|
+
} else {
|
|
1151
|
+
otherCalls.push(c);
|
|
1233
1152
|
}
|
|
1234
|
-
|
|
1235
|
-
|
|
1153
|
+
}
|
|
1154
|
+
if (call === null) call = {};
|
|
1155
|
+
const devices = Array.isArray(call["m.devices"]) ? call["m.devices"] : [];
|
|
1236
1156
|
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
})();
|
|
1157
|
+
// Filter out invalid and expired devices
|
|
1158
|
+
const validDevices = devices.filter(d => typeof d.device_id === "string" && typeof d.session_id === "string" && typeof d.expires_ts === "number" && d.expires_ts > now && Array.isArray(d.feeds));
|
|
1159
|
+
const newDevices = fn(validDevices);
|
|
1160
|
+
if (newDevices === null) return;
|
|
1161
|
+
const newCalls = [...otherCalls];
|
|
1162
|
+
if (newDevices.length > 0) {
|
|
1163
|
+
newCalls.push(_objectSpread(_objectSpread({}, call), {}, {
|
|
1164
|
+
"m.call_id": this.groupCallId,
|
|
1165
|
+
"m.devices": newDevices
|
|
1166
|
+
}));
|
|
1167
|
+
}
|
|
1168
|
+
const newContent = {
|
|
1169
|
+
"m.calls": newCalls
|
|
1170
|
+
};
|
|
1171
|
+
await this.client.sendStateEvent(this.room.roomId, EventType.GroupCallMemberPrefix, newContent, localUserId, {
|
|
1172
|
+
keepAlive
|
|
1173
|
+
});
|
|
1255
1174
|
}
|
|
1256
|
-
addDeviceToMemberState() {
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
// TODO: Add data channels
|
|
1267
|
-
}]);
|
|
1268
|
-
})();
|
|
1175
|
+
async addDeviceToMemberState() {
|
|
1176
|
+
await this.updateDevices(devices => [...devices.filter(d => d.device_id !== this.client.getDeviceId()), {
|
|
1177
|
+
device_id: this.client.getDeviceId(),
|
|
1178
|
+
session_id: this.client.getSessionId(),
|
|
1179
|
+
expires_ts: Date.now() + DEVICE_TIMEOUT,
|
|
1180
|
+
feeds: this.getLocalFeeds().map(feed => ({
|
|
1181
|
+
purpose: feed.purpose
|
|
1182
|
+
}))
|
|
1183
|
+
// TODO: Add data channels
|
|
1184
|
+
}]);
|
|
1269
1185
|
}
|
|
1270
|
-
updateMemberState() {
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
// Add the local device
|
|
1280
|
-
yield _this13.addDeviceToMemberState();
|
|
1186
|
+
async updateMemberState() {
|
|
1187
|
+
// Clear the old update interval before proceeding
|
|
1188
|
+
if (this.resendMemberStateTimer !== null) {
|
|
1189
|
+
clearInterval(this.resendMemberStateTimer);
|
|
1190
|
+
this.resendMemberStateTimer = null;
|
|
1191
|
+
}
|
|
1192
|
+
if (this.state === GroupCallState.Entered) {
|
|
1193
|
+
// Add the local device
|
|
1194
|
+
await this.addDeviceToMemberState();
|
|
1281
1195
|
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
})();
|
|
1196
|
+
// Resend the state event every so often so it doesn't become stale
|
|
1197
|
+
this.resendMemberStateTimer = setInterval(async () => {
|
|
1198
|
+
logger.log(`GroupCall ${this.groupCallId} updateMemberState() resending call member state"`);
|
|
1199
|
+
try {
|
|
1200
|
+
await this.addDeviceToMemberState();
|
|
1201
|
+
} catch (e) {
|
|
1202
|
+
logger.error(`GroupCall ${this.groupCallId} updateMemberState() failed to resend call member state`, e);
|
|
1203
|
+
}
|
|
1204
|
+
}, DEVICE_TIMEOUT * 3 / 4);
|
|
1205
|
+
} else {
|
|
1206
|
+
// Remove the local device
|
|
1207
|
+
await this.updateDevices(devices => devices.filter(d => d.device_id !== this.client.getDeviceId()), true);
|
|
1208
|
+
}
|
|
1296
1209
|
}
|
|
1297
1210
|
|
|
1298
1211
|
/**
|
|
1299
1212
|
* Cleans up our member state by filtering out logged out devices, inactive
|
|
1300
1213
|
* devices, and our own device (if we know we haven't entered).
|
|
1301
1214
|
*/
|
|
1302
|
-
cleanMemberState() {
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
var deviceMap = new Map(myDevices.map(d => [d.device_id, d]));
|
|
1215
|
+
async cleanMemberState() {
|
|
1216
|
+
const {
|
|
1217
|
+
devices: myDevices
|
|
1218
|
+
} = await this.client.getDevices();
|
|
1219
|
+
const deviceMap = new Map(myDevices.map(d => [d.device_id, d]));
|
|
1308
1220
|
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
});
|
|
1315
|
-
|
|
1316
|
-
// Skip the update if the devices are unchanged
|
|
1317
|
-
return newDevices.length === devices.length ? null : newDevices;
|
|
1221
|
+
// updateDevices takes care of filtering out inactive devices for us
|
|
1222
|
+
await this.updateDevices(devices => {
|
|
1223
|
+
const newDevices = devices.filter(d => {
|
|
1224
|
+
const device = deviceMap.get(d.device_id);
|
|
1225
|
+
return device?.last_seen_ts !== undefined && !(d.device_id === this.client.getDeviceId() && this.state !== GroupCallState.Entered && !this.enteredViaAnotherSession);
|
|
1318
1226
|
});
|
|
1319
|
-
|
|
1227
|
+
|
|
1228
|
+
// Skip the update if the devices are unchanged
|
|
1229
|
+
return newDevices.length === devices.length ? null : newDevices;
|
|
1230
|
+
});
|
|
1320
1231
|
}
|
|
1321
1232
|
getGroupCallStats() {
|
|
1322
1233
|
if (this.stats === undefined) {
|
|
1323
|
-
|
|
1234
|
+
const userID = this.client.getUserId() || "unknown";
|
|
1324
1235
|
this.stats = new GroupCallStats(this.groupCallId, userID, this.statsCollectIntervalTime);
|
|
1325
1236
|
this.stats.reports.on(StatsReport.CONNECTION_STATS, this.onConnectionStats);
|
|
1326
1237
|
this.stats.reports.on(StatsReport.BYTE_SENT_STATS, this.onByteSentStats);
|