From d8ebc68aa893f7b546a298dc72c489e616b28bda Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 2 Dec 2024 10:53:27 +0000 Subject: [PATCH] Remove abandoned Voice Broadcasts labs flag (#28548) * Remove abandoned Voice Broadcasts labs flag Any existing voice broadcasts will be shown as a series of voice messages which will sequence play as normal Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Remove dead code Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update snapshots Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- docs/config.md | 1 - res/css/_components.pcss | 6 - res/css/structures/_UserMenu.pcss | 14 - res/css/views/rooms/_MessageComposer.pcss | 4 - res/css/voice-broadcast/atoms/_LiveBadge.pcss | 23 - .../atoms/_VoiceBroadcastControl.pcss | 28 - .../atoms/_VoiceBroadcastHeader.pcss | 60 -- ...oiceBroadcastRecordingConnectionError.pcss | 18 - .../atoms/_VoiceBroadcastRoomSubtitle.pcss | 14 - .../molecules/_VoiceBroadcastBody.pcss | 75 -- res/themes/dark/css/_dark.pcss | 5 - res/themes/legacy-dark/css/_legacy-dark.pcss | 5 - .../legacy-light/css/_legacy-light.pcss | 5 - res/themes/light/css/_light.pcss | 5 - src/@types/matrix-js-sdk.d.ts | 6 - src/IConfigOptions.ts | 7 - src/LegacyCallHandler.tsx | 11 - src/Notifier.ts | 15 - src/SdkConfig.ts | 4 - src/TextForEvent.tsx | 2 - src/components/structures/MatrixChat.tsx | 12 +- src/components/structures/PipContainer.tsx | 83 +- src/components/structures/UserMenu.tsx | 25 - .../structures/grouper/CreationGrouper.tsx | 6 - .../audio_messages/DevicesContextMenu.tsx | 45 - .../CantStartVoiceMessageBroadcastDialog.tsx | 21 - .../views/dialogs/ConfirmRedactDialog.tsx | 16 +- .../views/dialogs/DevtoolsDialog.tsx | 2 - .../views/messages/MessageActionBar.tsx | 4 +- .../views/messages/MessageEvent.tsx | 5 - .../views/rooms/MessageComposer.tsx | 34 +- .../views/rooms/MessageComposerButtons.tsx | 16 - src/components/views/rooms/RoomTile.tsx | 24 +- .../views/rooms/RoomTileSubtitle.tsx | 14 +- .../tabs/room/RolesRoomSettingsTab.tsx | 3 - src/contexts/SDKContext.ts | 29 - src/events/EventTileFactory.tsx | 22 - src/events/forward/getForwardableEvent.ts | 3 - src/hooks/useAudioDeviceSelection.ts | 76 -- src/i18n/strings/en_EN.json | 47 - src/settings/Settings.tsx | 15 - src/stores/RoomViewStore.tsx | 63 -- src/stores/room-list/MessagePreviewStore.ts | 6 - .../room-list/previews/MessageEventPreview.ts | 4 - .../previews/VoiceBroadcastPreview.ts | 23 - src/stores/widgets/StopGapWidget.ts | 4 - src/utils/EventRenderingUtils.ts | 7 +- src/utils/EventUtils.ts | 5 +- .../audio/VoiceBroadcastRecorder.ts | 181 ---- .../components/VoiceBroadcastBody.tsx | 58 -- .../components/atoms/LiveBadge.tsx | 30 - .../components/atoms/SeekButton.tsx | 25 - .../atoms/VoiceBroadcastControl.tsx | 31 - .../components/atoms/VoiceBroadcastError.tsx | 23 - .../components/atoms/VoiceBroadcastHeader.tsx | 139 --- .../atoms/VoiceBroadcastPlaybackControl.tsx | 51 - ...VoiceBroadcastRecordingConnectionError.tsx | 21 - .../atoms/VoiceBroadcastRoomSubtitle.tsx | 21 - .../ConfirmListenBroadcastStopCurrent.tsx | 38 - .../molecules/VoiceBroadcastPlaybackBody.tsx | 102 -- .../VoiceBroadcastPreRecordingPip.tsx | 82 -- .../molecules/VoiceBroadcastRecordingBody.tsx | 31 - .../molecules/VoiceBroadcastRecordingPip.tsx | 116 --- .../VoiceBroadcastSmallPlaybackBody.tsx | 44 - .../hooks/useCurrentVoiceBroadcastPlayback.ts | 32 - .../useCurrentVoiceBroadcastPreRecording.ts | 29 - .../useCurrentVoiceBroadcastRecording.ts | 28 - .../hooks/useHasRoomLiveVoiceBroadcast.ts | 39 - .../hooks/useVoiceBroadcastPlayback.ts | 90 -- .../hooks/useVoiceBroadcastRecording.tsx | 96 -- src/voice-broadcast/index.ts | 57 -- .../models/VoiceBroadcastPlayback.ts | 651 ------------- .../models/VoiceBroadcastPreRecording.ts | 48 - .../models/VoiceBroadcastRecording.ts | 441 --------- .../stores/VoiceBroadcastPlaybacksStore.ts | 113 --- .../stores/VoiceBroadcastPreRecordingStore.ts | 63 -- .../stores/VoiceBroadcastRecordingsStore.ts | 89 -- src/voice-broadcast/types.ts | 32 - .../utils/VoiceBroadcastChunkEvents.ts | 147 --- .../utils/VoiceBroadcastResumer.ts | 90 -- .../checkVoiceBroadcastPreConditions.tsx | 86 -- .../utils/cleanUpBroadcasts.ts | 20 - .../utils/determineVoiceBroadcastLiveness.ts | 20 - ...rCurrentVoiceBroadcastPlaybackIfStopped.ts | 18 - ...doMaybeSetCurrentVoiceBroadcastPlayback.ts | 56 -- ...RoomLiveVoiceBroadcastFromUserAndDevice.ts | 29 - src/voice-broadcast/utils/getChunkLength.ts | 23 - .../utils/getMaxBroadcastLength.ts | 19 - .../utils/hasRoomLiveVoiceBroadcast.ts | 57 -- .../utils/isRelatedToVoiceBroadcast.ts | 21 - .../utils/isVoiceBroadcastStartedEvent.ts | 17 - .../pauseNonLiveBroadcastFromOtherRoom.ts | 29 - .../utils/retrieveStartedInfoEvent.ts | 37 - .../utils/setUpVoiceBroadcastPreRecording.ts | 43 - ...uldDisplayAsVoiceBroadcastRecordingTile.ts | 25 - ...houldDisplayAsVoiceBroadcastStoppedText.ts | 16 - .../shouldDisplayAsVoiceBroadcastTile.ts | 15 - .../utils/showCantStartACallDialog.tsx | 21 - .../utils/startNewVoiceBroadcastRecording.ts | 92 -- .../textForVoiceBroadcastStoppedEvent.tsx | 41 - ...orVoiceBroadcastStoppedEventWithoutLink.ts | 23 - test/unit-tests/LegacyCallHandler-test.ts | 54 -- test/unit-tests/Notifier-test.ts | 43 +- test/unit-tests/SdkConfig-test.ts | 6 - test/unit-tests/TestSdkContext.ts | 8 - .../LegacyCallHandler-test.ts.snap | 24 - .../components/structures/MatrixChat-test.tsx | 12 - .../structures/PipContainer-test.tsx | 201 +--- .../components/structures/UserMenu-test.tsx | 75 +- .../__snapshots__/UserMenu-test.tsx.snap | 33 - .../context_menus/MessageContextMenu-test.tsx | 13 - .../dialogs/ConfirmRedactDialog-test.tsx | 54 +- .../DevtoolsDialog-test.tsx.snap | 27 - .../views/messages/MessageEvent-test.tsx | 26 - .../views/rooms/MessageComposer-test.tsx | 51 +- .../rooms/MessageComposerButtons-test.tsx | 23 - .../components/views/rooms/RoomTile-test.tsx | 69 +- .../tabs/room/RolesRoomSettingsTab-test.tsx | 29 - test/unit-tests/contexts/SdkContext-test.ts | 9 - .../events/EventTileFactory-test.ts | 70 +- test/unit-tests/stores/RoomViewStore-test.ts | 90 -- .../__snapshots__/RoomViewStore-test.ts.snap | 23 - .../previews/MessageEventPreview-test.ts | 13 - .../previews/VoiceBroadcastPreview-test.ts | 54 -- .../stores/widgets/StopGapWidget-test.ts | 38 - .../utils/EventRenderingUtils-test.ts | 46 - test/unit-tests/utils/EventUtils-test.ts | 18 - .../audio/VoiceBroadcastRecorder-test.ts | 251 ----- .../components/VoiceBroadcastBody-test.tsx | 171 ---- .../components/atoms/LiveBadge-test.tsx | 24 - .../atoms/VoiceBroadcastControl-test.tsx | 44 - .../atoms/VoiceBroadcastHeader-test.tsx | 89 -- .../VoiceBroadcastPlaybackControl-test.tsx | 51 - .../__snapshots__/LiveBadge-test.tsx.snap | 27 - .../VoiceBroadcastControl-test.tsx.snap | 16 - .../VoiceBroadcastHeader-test.tsx.snap | 277 ------ ...oiceBroadcastPlaybackControl-test.tsx.snap | 97 -- .../VoiceBroadcastPlaybackBody-test.tsx | 249 ----- .../VoiceBroadcastPreRecordingPip-test.tsx | 163 ---- .../VoiceBroadcastRecordingBody-test.tsx | 79 -- .../VoiceBroadcastRecordingPip-test.tsx | 217 ----- .../VoiceBroadcastSmallPlaybackBody-test.tsx | 129 --- .../VoiceBroadcastPlaybackBody-test.tsx.snap | 914 ------------------ ...oiceBroadcastPreRecordingPip-test.tsx.snap | 98 -- .../VoiceBroadcastRecordingBody-test.tsx.snap | 131 --- .../VoiceBroadcastRecordingPip-test.tsx.snap | 238 ----- ...ceBroadcastSmallPlaybackBody-test.tsx.snap | 558 ----------- .../models/VoiceBroadcastPlayback-test.tsx | 747 -------------- .../models/VoiceBroadcastPreRecording-test.ts | 68 -- .../models/VoiceBroadcastRecording-test.ts | 660 ------------- .../VoiceBroadcastPlaybacksStore-test.ts | 162 ---- .../VoiceBroadcastPreRecordingStore-test.ts | 130 --- .../VoiceBroadcastRecordingsStore-test.ts | 167 ---- .../utils/VoiceBroadcastChunkEvents-test.ts | 142 --- .../utils/VoiceBroadcastResumer-test.ts | 179 ---- ...tUpVoiceBroadcastPreRecording-test.ts.snap | 24 - ...artNewVoiceBroadcastRecording-test.ts.snap | 116 --- ...orVoiceBroadcastStoppedEvent-test.tsx.snap | 42 - .../utils/cleanUpBroadcasts-test.ts | 51 - .../determineVoiceBroadcastLiveness-test.ts | 33 - ...iveVoiceBroadcastFromUserAndDevice-test.ts | 116 --- .../utils/getChunkLength-test.ts | 63 -- .../utils/getMaxBroadcastLength-test.ts | 43 - .../utils/hasRoomLiveVoiceBroadcast-test.ts | 195 ---- .../utils/isRelatedToVoiceBroadcast-test.ts | 109 --- ...pauseNonLiveBroadcastFromOtherRoom-test.ts | 98 -- .../utils/retrieveStartedInfoEvent-test.ts | 81 -- .../setUpVoiceBroadcastPreRecording-test.ts | 123 --- ...splayAsVoiceBroadcastRecordingTile-test.ts | 72 -- .../shouldDisplayAsVoiceBroadcastTile-test.ts | 138 --- .../startNewVoiceBroadcastRecording-test.ts | 234 ----- .../voice-broadcast/utils/test-utils.ts | 131 --- ...textForVoiceBroadcastStoppedEvent-test.tsx | 90 -- ...ceBroadcastStoppedEventWithoutLink-test.ts | 47 - 174 files changed, 29 insertions(+), 13632 deletions(-) delete mode 100644 res/css/voice-broadcast/atoms/_LiveBadge.pcss delete mode 100644 res/css/voice-broadcast/atoms/_VoiceBroadcastControl.pcss delete mode 100644 res/css/voice-broadcast/atoms/_VoiceBroadcastHeader.pcss delete mode 100644 res/css/voice-broadcast/atoms/_VoiceBroadcastRecordingConnectionError.pcss delete mode 100644 res/css/voice-broadcast/atoms/_VoiceBroadcastRoomSubtitle.pcss delete mode 100644 res/css/voice-broadcast/molecules/_VoiceBroadcastBody.pcss delete mode 100644 src/components/views/audio_messages/DevicesContextMenu.tsx delete mode 100644 src/components/views/dialogs/CantStartVoiceMessageBroadcastDialog.tsx delete mode 100644 src/hooks/useAudioDeviceSelection.ts delete mode 100644 src/stores/room-list/previews/VoiceBroadcastPreview.ts delete mode 100644 src/voice-broadcast/audio/VoiceBroadcastRecorder.ts delete mode 100644 src/voice-broadcast/components/VoiceBroadcastBody.tsx delete mode 100644 src/voice-broadcast/components/atoms/LiveBadge.tsx delete mode 100644 src/voice-broadcast/components/atoms/SeekButton.tsx delete mode 100644 src/voice-broadcast/components/atoms/VoiceBroadcastControl.tsx delete mode 100644 src/voice-broadcast/components/atoms/VoiceBroadcastError.tsx delete mode 100644 src/voice-broadcast/components/atoms/VoiceBroadcastHeader.tsx delete mode 100644 src/voice-broadcast/components/atoms/VoiceBroadcastPlaybackControl.tsx delete mode 100644 src/voice-broadcast/components/atoms/VoiceBroadcastRecordingConnectionError.tsx delete mode 100644 src/voice-broadcast/components/atoms/VoiceBroadcastRoomSubtitle.tsx delete mode 100644 src/voice-broadcast/components/molecules/ConfirmListenBroadcastStopCurrent.tsx delete mode 100644 src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx delete mode 100644 src/voice-broadcast/components/molecules/VoiceBroadcastPreRecordingPip.tsx delete mode 100644 src/voice-broadcast/components/molecules/VoiceBroadcastRecordingBody.tsx delete mode 100644 src/voice-broadcast/components/molecules/VoiceBroadcastRecordingPip.tsx delete mode 100644 src/voice-broadcast/components/molecules/VoiceBroadcastSmallPlaybackBody.tsx delete mode 100644 src/voice-broadcast/hooks/useCurrentVoiceBroadcastPlayback.ts delete mode 100644 src/voice-broadcast/hooks/useCurrentVoiceBroadcastPreRecording.ts delete mode 100644 src/voice-broadcast/hooks/useCurrentVoiceBroadcastRecording.ts delete mode 100644 src/voice-broadcast/hooks/useHasRoomLiveVoiceBroadcast.ts delete mode 100644 src/voice-broadcast/hooks/useVoiceBroadcastPlayback.ts delete mode 100644 src/voice-broadcast/hooks/useVoiceBroadcastRecording.tsx delete mode 100644 src/voice-broadcast/index.ts delete mode 100644 src/voice-broadcast/models/VoiceBroadcastPlayback.ts delete mode 100644 src/voice-broadcast/models/VoiceBroadcastPreRecording.ts delete mode 100644 src/voice-broadcast/models/VoiceBroadcastRecording.ts delete mode 100644 src/voice-broadcast/stores/VoiceBroadcastPlaybacksStore.ts delete mode 100644 src/voice-broadcast/stores/VoiceBroadcastPreRecordingStore.ts delete mode 100644 src/voice-broadcast/stores/VoiceBroadcastRecordingsStore.ts delete mode 100644 src/voice-broadcast/types.ts delete mode 100644 src/voice-broadcast/utils/VoiceBroadcastChunkEvents.ts delete mode 100644 src/voice-broadcast/utils/VoiceBroadcastResumer.ts delete mode 100644 src/voice-broadcast/utils/checkVoiceBroadcastPreConditions.tsx delete mode 100644 src/voice-broadcast/utils/cleanUpBroadcasts.ts delete mode 100644 src/voice-broadcast/utils/determineVoiceBroadcastLiveness.ts delete mode 100644 src/voice-broadcast/utils/doClearCurrentVoiceBroadcastPlaybackIfStopped.ts delete mode 100644 src/voice-broadcast/utils/doMaybeSetCurrentVoiceBroadcastPlayback.ts delete mode 100644 src/voice-broadcast/utils/findRoomLiveVoiceBroadcastFromUserAndDevice.ts delete mode 100644 src/voice-broadcast/utils/getChunkLength.ts delete mode 100644 src/voice-broadcast/utils/getMaxBroadcastLength.ts delete mode 100644 src/voice-broadcast/utils/hasRoomLiveVoiceBroadcast.ts delete mode 100644 src/voice-broadcast/utils/isRelatedToVoiceBroadcast.ts delete mode 100644 src/voice-broadcast/utils/isVoiceBroadcastStartedEvent.ts delete mode 100644 src/voice-broadcast/utils/pauseNonLiveBroadcastFromOtherRoom.ts delete mode 100644 src/voice-broadcast/utils/retrieveStartedInfoEvent.ts delete mode 100644 src/voice-broadcast/utils/setUpVoiceBroadcastPreRecording.ts delete mode 100644 src/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastRecordingTile.ts delete mode 100644 src/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastStoppedText.ts delete mode 100644 src/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastTile.ts delete mode 100644 src/voice-broadcast/utils/showCantStartACallDialog.tsx delete mode 100644 src/voice-broadcast/utils/startNewVoiceBroadcastRecording.ts delete mode 100644 src/voice-broadcast/utils/textForVoiceBroadcastStoppedEvent.tsx delete mode 100644 src/voice-broadcast/utils/textForVoiceBroadcastStoppedEventWithoutLink.ts delete mode 100644 test/unit-tests/__snapshots__/LegacyCallHandler-test.ts.snap delete mode 100644 test/unit-tests/components/structures/__snapshots__/UserMenu-test.tsx.snap delete mode 100644 test/unit-tests/stores/room-list/previews/VoiceBroadcastPreview-test.ts delete mode 100644 test/unit-tests/utils/EventRenderingUtils-test.ts delete mode 100644 test/unit-tests/voice-broadcast/audio/VoiceBroadcastRecorder-test.ts delete mode 100644 test/unit-tests/voice-broadcast/components/VoiceBroadcastBody-test.tsx delete mode 100644 test/unit-tests/voice-broadcast/components/atoms/LiveBadge-test.tsx delete mode 100644 test/unit-tests/voice-broadcast/components/atoms/VoiceBroadcastControl-test.tsx delete mode 100644 test/unit-tests/voice-broadcast/components/atoms/VoiceBroadcastHeader-test.tsx delete mode 100644 test/unit-tests/voice-broadcast/components/atoms/VoiceBroadcastPlaybackControl-test.tsx delete mode 100644 test/unit-tests/voice-broadcast/components/atoms/__snapshots__/LiveBadge-test.tsx.snap delete mode 100644 test/unit-tests/voice-broadcast/components/atoms/__snapshots__/VoiceBroadcastControl-test.tsx.snap delete mode 100644 test/unit-tests/voice-broadcast/components/atoms/__snapshots__/VoiceBroadcastHeader-test.tsx.snap delete mode 100644 test/unit-tests/voice-broadcast/components/atoms/__snapshots__/VoiceBroadcastPlaybackControl-test.tsx.snap delete mode 100644 test/unit-tests/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody-test.tsx delete mode 100644 test/unit-tests/voice-broadcast/components/molecules/VoiceBroadcastPreRecordingPip-test.tsx delete mode 100644 test/unit-tests/voice-broadcast/components/molecules/VoiceBroadcastRecordingBody-test.tsx delete mode 100644 test/unit-tests/voice-broadcast/components/molecules/VoiceBroadcastRecordingPip-test.tsx delete mode 100644 test/unit-tests/voice-broadcast/components/molecules/VoiceBroadcastSmallPlaybackBody-test.tsx delete mode 100644 test/unit-tests/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastPlaybackBody-test.tsx.snap delete mode 100644 test/unit-tests/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastPreRecordingPip-test.tsx.snap delete mode 100644 test/unit-tests/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastRecordingBody-test.tsx.snap delete mode 100644 test/unit-tests/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastRecordingPip-test.tsx.snap delete mode 100644 test/unit-tests/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastSmallPlaybackBody-test.tsx.snap delete mode 100644 test/unit-tests/voice-broadcast/models/VoiceBroadcastPlayback-test.tsx delete mode 100644 test/unit-tests/voice-broadcast/models/VoiceBroadcastPreRecording-test.ts delete mode 100644 test/unit-tests/voice-broadcast/models/VoiceBroadcastRecording-test.ts delete mode 100644 test/unit-tests/voice-broadcast/stores/VoiceBroadcastPlaybacksStore-test.ts delete mode 100644 test/unit-tests/voice-broadcast/stores/VoiceBroadcastPreRecordingStore-test.ts delete mode 100644 test/unit-tests/voice-broadcast/stores/VoiceBroadcastRecordingsStore-test.ts delete mode 100644 test/unit-tests/voice-broadcast/utils/VoiceBroadcastChunkEvents-test.ts delete mode 100644 test/unit-tests/voice-broadcast/utils/VoiceBroadcastResumer-test.ts delete mode 100644 test/unit-tests/voice-broadcast/utils/__snapshots__/setUpVoiceBroadcastPreRecording-test.ts.snap delete mode 100644 test/unit-tests/voice-broadcast/utils/__snapshots__/startNewVoiceBroadcastRecording-test.ts.snap delete mode 100644 test/unit-tests/voice-broadcast/utils/__snapshots__/textForVoiceBroadcastStoppedEvent-test.tsx.snap delete mode 100644 test/unit-tests/voice-broadcast/utils/cleanUpBroadcasts-test.ts delete mode 100644 test/unit-tests/voice-broadcast/utils/determineVoiceBroadcastLiveness-test.ts delete mode 100644 test/unit-tests/voice-broadcast/utils/findRoomLiveVoiceBroadcastFromUserAndDevice-test.ts delete mode 100644 test/unit-tests/voice-broadcast/utils/getChunkLength-test.ts delete mode 100644 test/unit-tests/voice-broadcast/utils/getMaxBroadcastLength-test.ts delete mode 100644 test/unit-tests/voice-broadcast/utils/hasRoomLiveVoiceBroadcast-test.ts delete mode 100644 test/unit-tests/voice-broadcast/utils/isRelatedToVoiceBroadcast-test.ts delete mode 100644 test/unit-tests/voice-broadcast/utils/pauseNonLiveBroadcastFromOtherRoom-test.ts delete mode 100644 test/unit-tests/voice-broadcast/utils/retrieveStartedInfoEvent-test.ts delete mode 100644 test/unit-tests/voice-broadcast/utils/setUpVoiceBroadcastPreRecording-test.ts delete mode 100644 test/unit-tests/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastRecordingTile-test.ts delete mode 100644 test/unit-tests/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastTile-test.ts delete mode 100644 test/unit-tests/voice-broadcast/utils/startNewVoiceBroadcastRecording-test.ts delete mode 100644 test/unit-tests/voice-broadcast/utils/test-utils.ts delete mode 100644 test/unit-tests/voice-broadcast/utils/textForVoiceBroadcastStoppedEvent-test.tsx delete mode 100644 test/unit-tests/voice-broadcast/utils/textForVoiceBroadcastStoppedEventWithoutLink-test.ts diff --git a/docs/config.md b/docs/config.md index 7a5445f3035..8ca4ba4eb8b 100644 --- a/docs/config.md +++ b/docs/config.md @@ -592,4 +592,3 @@ The following are undocumented or intended for developer use only. 2. `sync_timeline_limit` 3. `dangerously_allow_unsafe_and_insecure_passwords` 4. `latex_maths_delims`: An optional setting to override the default delimiters used for maths parsing. See https://github.com/matrix-org/matrix-react-sdk/pull/5939 for details. Only used when `feature_latex_maths` is enabled. -5. `voice_broadcast.chunk_length`: Target chunk length in seconds for the Voice Broadcast feature currently under development. diff --git a/res/css/_components.pcss b/res/css/_components.pcss index 0fcdf6dee6e..e9a53cd43cc 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -393,9 +393,3 @@ @import "./views/voip/_LegacyCallViewHeader.pcss"; @import "./views/voip/_LegacyCallViewSidebar.pcss"; @import "./views/voip/_VideoFeed.pcss"; -@import "./voice-broadcast/atoms/_LiveBadge.pcss"; -@import "./voice-broadcast/atoms/_VoiceBroadcastControl.pcss"; -@import "./voice-broadcast/atoms/_VoiceBroadcastHeader.pcss"; -@import "./voice-broadcast/atoms/_VoiceBroadcastRecordingConnectionError.pcss"; -@import "./voice-broadcast/atoms/_VoiceBroadcastRoomSubtitle.pcss"; -@import "./voice-broadcast/molecules/_VoiceBroadcastBody.pcss"; diff --git a/res/css/structures/_UserMenu.pcss b/res/css/structures/_UserMenu.pcss index 741a4e90dca..d24a6e4ac7a 100644 --- a/res/css/structures/_UserMenu.pcss +++ b/res/css/structures/_UserMenu.pcss @@ -22,20 +22,6 @@ Please see LICENSE files in the repository root for full details. pointer-events: none; /* makes the avatar non-draggable */ } } - - .mx_UserMenu_userAvatarLive { - align-items: center; - background-color: $alert; - border-radius: 6px; - color: $live-badge-color; - display: flex; - height: 12px; - justify-content: center; - left: 25px; - position: absolute; - top: 20px; - width: 12px; - } } .mx_UserMenu_contextMenuButton { diff --git a/res/css/views/rooms/_MessageComposer.pcss b/res/css/views/rooms/_MessageComposer.pcss index 3f11e9fa6c8..73ac15c9c9e 100644 --- a/res/css/views/rooms/_MessageComposer.pcss +++ b/res/css/views/rooms/_MessageComposer.pcss @@ -256,10 +256,6 @@ Please see LICENSE files in the repository root for full details. mask-image: url("@vector-im/compound-design-tokens/icons/mic-on-solid.svg"); } -.mx_MessageComposer_voiceBroadcast::before { - mask-image: url("$(res)/img/element-icons/live.svg"); -} - .mx_MessageComposer_plain_text::before { mask-image: url("$(res)/img/element-icons/room/composer/plain_text.svg"); } diff --git a/res/css/voice-broadcast/atoms/_LiveBadge.pcss b/res/css/voice-broadcast/atoms/_LiveBadge.pcss deleted file mode 100644 index 7d5f23819be..00000000000 --- a/res/css/voice-broadcast/atoms/_LiveBadge.pcss +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -.mx_LiveBadge { - align-items: center; - background-color: $alert; - border-radius: 2px; - color: $live-badge-color; - display: inline-flex; - font-size: $font-12px; - font-weight: var(--cpd-font-weight-semibold); - gap: $spacing-4; - padding: 2px 4px; -} - -.mx_LiveBadge--grey { - background-color: $quaternary-content; -} diff --git a/res/css/voice-broadcast/atoms/_VoiceBroadcastControl.pcss b/res/css/voice-broadcast/atoms/_VoiceBroadcastControl.pcss deleted file mode 100644 index 5bd7bfe0982..00000000000 --- a/res/css/voice-broadcast/atoms/_VoiceBroadcastControl.pcss +++ /dev/null @@ -1,28 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -.mx_VoiceBroadcastControl { - align-items: center; - background-color: $background; - border-radius: 50%; - color: $secondary-content; - display: flex; - flex: 0 0 32px; - height: 32px; - justify-content: center; - width: 32px; -} - -.mx_VoiceBroadcastControl-recording { - color: $alert; -} - -.mx_VoiceBroadcastControl-play .mx_Icon { - left: 1px; - position: relative; -} diff --git a/res/css/voice-broadcast/atoms/_VoiceBroadcastHeader.pcss b/res/css/voice-broadcast/atoms/_VoiceBroadcastHeader.pcss deleted file mode 100644 index c5e21233b7c..00000000000 --- a/res/css/voice-broadcast/atoms/_VoiceBroadcastHeader.pcss +++ /dev/null @@ -1,60 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -.mx_VoiceBroadcastHeader { - align-items: flex-start; - display: flex; - gap: $spacing-8; - line-height: 20px; - margin-bottom: $spacing-16; - min-width: 0; -} - -.mx_VoiceBroadcastHeader_content { - flex-grow: 1; - min-width: 0; -} - -.mx_VoiceBroadcastHeader_room_wrapper { - align-items: center; - display: flex; - gap: 4px; - justify-content: flex-start; -} - -.mx_VoiceBroadcastHeader_room { - font-size: $font-12px; - font-weight: var(--cpd-font-weight-semibold); - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.mx_VoiceBroadcastHeader_line { - align-items: center; - color: $secondary-content; - font-size: $font-12px; - display: flex; - gap: $spacing-4; - - .mx_Spinner { - flex: 0 0 14px; - padding: 1px; - } - - span { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } -} - -.mx_VoiceBroadcastHeader_mic--clickable { - cursor: pointer; -} diff --git a/res/css/voice-broadcast/atoms/_VoiceBroadcastRecordingConnectionError.pcss b/res/css/voice-broadcast/atoms/_VoiceBroadcastRecordingConnectionError.pcss deleted file mode 100644 index f21c0bb7331..00000000000 --- a/res/css/voice-broadcast/atoms/_VoiceBroadcastRecordingConnectionError.pcss +++ /dev/null @@ -1,18 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2023 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -.mx_VoiceBroadcastRecordingConnectionError { - align-items: center; - color: $alert; - display: flex; - gap: $spacing-12; - - svg path { - fill: $alert; - } -} diff --git a/res/css/voice-broadcast/atoms/_VoiceBroadcastRoomSubtitle.pcss b/res/css/voice-broadcast/atoms/_VoiceBroadcastRoomSubtitle.pcss deleted file mode 100644 index e0748e76269..00000000000 --- a/res/css/voice-broadcast/atoms/_VoiceBroadcastRoomSubtitle.pcss +++ /dev/null @@ -1,14 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -.mx_RoomTile .mx_RoomTile_titleContainer .mx_RoomTile_subtitle.mx_RoomTile_subtitle--voice-broadcast { - align-items: center; - color: $alert; - display: flex; - gap: $spacing-4; -} diff --git a/res/css/voice-broadcast/molecules/_VoiceBroadcastBody.pcss b/res/css/voice-broadcast/molecules/_VoiceBroadcastBody.pcss deleted file mode 100644 index 45ed0e98f97..00000000000 --- a/res/css/voice-broadcast/molecules/_VoiceBroadcastBody.pcss +++ /dev/null @@ -1,75 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -.mx_VoiceBroadcastBody { - background-color: $quinary-content; - border-radius: 8px; - color: $secondary-content; - display: inline-block; - font-size: $font-12px; - padding: $spacing-12; - width: 271px; - - .mx_Clock { - line-height: 1; - } -} - -.mx_VoiceBroadcastBody--pip { - background-color: $system; - box-shadow: 0 2px 8px 0 #0000004a; -} - -.mx_VoiceBroadcastBody--small { - display: flex; - gap: $spacing-8; - width: 192px; - - .mx_VoiceBroadcastHeader { - margin-bottom: 0; - } - - .mx_VoiceBroadcastControl { - align-self: center; - } - - .mx_LiveBadge { - margin-top: 4px; - } -} - -.mx_VoiceBroadcastBody_divider { - background-color: $quinary-content; - border: 0; - height: 1px; - margin: $spacing-12 0; -} - -.mx_VoiceBroadcastBody_controls { - align-items: center; - display: flex; - gap: $spacing-32; - justify-content: center; - margin-bottom: $spacing-8; -} - -.mx_VoiceBroadcastBody_timerow { - display: flex; - justify-content: space-between; -} - -.mx_AccessibleButton.mx_VoiceBroadcastBody_blockButton { - display: flex; - gap: $spacing-8; -} - -.mx_VoiceBroadcastBody__small-close { - right: 8px; - position: absolute; - top: 8px; -} diff --git a/res/themes/dark/css/_dark.pcss b/res/themes/dark/css/_dark.pcss index 8b0673f692b..2d3ea2e4f42 100644 --- a/res/themes/dark/css/_dark.pcss +++ b/res/themes/dark/css/_dark.pcss @@ -240,11 +240,6 @@ $location-live-secondary-color: #deddfd; } /* ******************** */ -/* Voice Broadcast */ -/* ******************** */ -$live-badge-color: #ffffff; -/* ******************** */ - /* One-off colors */ /* ******************** */ $progressbar-bg-color: var(--cpd-color-gray-200); diff --git a/res/themes/legacy-dark/css/_legacy-dark.pcss b/res/themes/legacy-dark/css/_legacy-dark.pcss index 45bb1870f1b..ea5228b6c74 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.pcss +++ b/res/themes/legacy-dark/css/_legacy-dark.pcss @@ -226,11 +226,6 @@ $location-live-color: #5c56f5; $location-live-secondary-color: #deddfd; /* ******************** */ -/* Voice Broadcast */ -/* ******************** */ -$live-badge-color: #ffffff; -/* ******************** */ - body { color-scheme: dark; } diff --git a/res/themes/legacy-light/css/_legacy-light.pcss b/res/themes/legacy-light/css/_legacy-light.pcss index 76e0eec588a..32ca7d3d1a0 100644 --- a/res/themes/legacy-light/css/_legacy-light.pcss +++ b/res/themes/legacy-light/css/_legacy-light.pcss @@ -325,11 +325,6 @@ $location-live-color: #5c56f5; $location-live-secondary-color: #deddfd; /* ******************** */ -/* Voice Broadcast */ -/* ******************** */ -$live-badge-color: #ffffff; -/* ******************** */ - body { color-scheme: light; } diff --git a/res/themes/light/css/_light.pcss b/res/themes/light/css/_light.pcss index 32629a55f75..1a1705a9c15 100644 --- a/res/themes/light/css/_light.pcss +++ b/res/themes/light/css/_light.pcss @@ -355,11 +355,6 @@ $location-live-color: var(--cpd-color-purple-900); $location-live-secondary-color: var(--cpd-color-purple-600); /* ******************** */ -/* Voice Broadcast */ -/* ******************** */ -$live-badge-color: var(--cpd-color-icon-on-solid-primary); -/* ******************** */ - body { color-scheme: light; } diff --git a/src/@types/matrix-js-sdk.d.ts b/src/@types/matrix-js-sdk.d.ts index 73366f2fee3..41ccfcbb3b2 100644 --- a/src/@types/matrix-js-sdk.d.ts +++ b/src/@types/matrix-js-sdk.d.ts @@ -10,7 +10,6 @@ import type { IWidget } from "matrix-widget-api"; import type { BLURHASH_FIELD } from "../utils/image-media"; import type { JitsiCallMemberEventType, JitsiCallMemberContent } from "../call-types"; import type { ILayoutStateEvent, WIDGET_LAYOUT_EVENT_TYPE } from "../stores/widgets/types"; -import type { VoiceBroadcastInfoEventContent, VoiceBroadcastInfoEventType } from "../voice-broadcast/types"; import type { EncryptedFile } from "matrix-js-sdk/src/types"; // Extend Matrix JS SDK types via Typescript declaration merging to support unspecced event fields and types @@ -37,9 +36,6 @@ declare module "matrix-js-sdk/src/types" { "im.vector.modular.widgets": IWidget | {}; [WIDGET_LAYOUT_EVENT_TYPE]: ILayoutStateEvent; - // Unstable voice broadcast state events - [VoiceBroadcastInfoEventType]: VoiceBroadcastInfoEventContent; - // Element custom state events "im.vector.web.settings": Record; "org.matrix.room.preview_urls": { disable: boolean }; @@ -78,7 +74,5 @@ declare module "matrix-js-sdk/src/types" { waveform?: number[]; }; "org.matrix.msc3245.voice"?: {}; - - "io.element.voice_broadcast_chunk"?: { sequence: number }; } } diff --git a/src/IConfigOptions.ts b/src/IConfigOptions.ts index 72bee5d0abc..5dd500402d4 100644 --- a/src/IConfigOptions.ts +++ b/src/IConfigOptions.ts @@ -175,13 +175,6 @@ export interface IConfigOptions { sync_timeline_limit?: number; dangerously_allow_unsafe_and_insecure_passwords?: boolean; // developer option - voice_broadcast?: { - // length per voice chunk in seconds - chunk_length?: number; - // max voice broadcast length in seconds - max_length?: number; - }; - user_notice?: { title: string; description: string; diff --git a/src/LegacyCallHandler.tsx b/src/LegacyCallHandler.tsx index a06480e9cd7..b804ca0084d 100644 --- a/src/LegacyCallHandler.tsx +++ b/src/LegacyCallHandler.tsx @@ -55,8 +55,6 @@ import { OpenInviteDialogPayload } from "./dispatcher/payloads/OpenInviteDialogP import { findDMForUser } from "./utils/dm/findDMForUser"; import { getJoinedNonFunctionalMembers } from "./utils/room/getJoinedNonFunctionalMembers"; import { localNotificationsAreSilenced } from "./utils/notifications"; -import { SdkContextClass } from "./contexts/SDKContext"; -import { showCantStartACallDialog } from "./voice-broadcast/utils/showCantStartACallDialog"; import { isNotNull } from "./Typeguards"; import { BackgroundAudio } from "./audio/BackgroundAudio"; import { Jitsi } from "./widgets/Jitsi.ts"; @@ -859,15 +857,6 @@ export default class LegacyCallHandler extends EventEmitter { return; } - // Pause current broadcast, if any - SdkContextClass.instance.voiceBroadcastPlaybacksStore.getCurrent()?.pause(); - - if (SdkContextClass.instance.voiceBroadcastRecordingsStore.getCurrent()) { - // Do not start a call, if recording a broadcast - showCantStartACallDialog(); - return; - } - // We might be using managed hybrid widgets if (isManagedHybridWidgetEnabled(room)) { await addManagedHybridWidget(room); diff --git a/src/Notifier.ts b/src/Notifier.ts index 961d2171a86..45e6a1195d3 100644 --- a/src/Notifier.ts +++ b/src/Notifier.ts @@ -49,8 +49,6 @@ import { SdkContextClass } from "./contexts/SDKContext"; import { localNotificationsAreSilenced, createLocalNotificationSettingsIfNeeded } from "./utils/notifications"; import { getIncomingCallToastKey, IncomingCallToast } from "./toasts/IncomingCallToast"; import ToastStore from "./stores/ToastStore"; -import { VoiceBroadcastChunkEventType, VoiceBroadcastInfoEventType } from "./voice-broadcast"; -import { getSenderName } from "./utils/event/getSenderName"; import { stripPlainReply } from "./utils/Reply"; import { BackgroundAudio } from "./audio/BackgroundAudio"; @@ -81,17 +79,6 @@ const msgTypeHandlers: Record string | null> = { return TextForEvent.textForLocationEvent(event)(); }, [MsgType.Audio]: (event: MatrixEvent): string | null => { - if (event.getContent()?.[VoiceBroadcastChunkEventType]) { - if (event.getContent()?.[VoiceBroadcastChunkEventType]?.sequence === 1) { - // Show a notification for the first broadcast chunk. - // At this point a user received something to listen to. - return _t("notifier|io.element.voice_broadcast_chunk", { senderName: getSenderName(event) }); - } - - // Mute other broadcast chunks - return null; - } - return TextForEvent.textForEvent(event, MatrixClientPeg.safeGet()); }, }; @@ -460,8 +447,6 @@ class NotifierClass extends TypedEventEmitter = { logo: require("../res/img/element-desktop-logo.svg").default, url: "https://element.io/get-started", }, - voice_broadcast: { - chunk_length: 2 * 60, // two minutes - max_length: 4 * 60 * 60, // four hours - }, feedback: { existing_issues_url: diff --git a/src/TextForEvent.tsx b/src/TextForEvent.tsx index 1ffae62aea3..49d8b739b73 100644 --- a/src/TextForEvent.tsx +++ b/src/TextForEvent.tsx @@ -36,7 +36,6 @@ import AccessibleButton from "./components/views/elements/AccessibleButton"; import RightPanelStore from "./stores/right-panel/RightPanelStore"; import { highlightEvent, isLocationEvent } from "./utils/EventUtils"; import { ElementCall } from "./models/Call"; -import { textForVoiceBroadcastStoppedEvent, VoiceBroadcastInfoEventType } from "./voice-broadcast"; import { getSenderName } from "./utils/event/getSenderName"; import PosthogTrackers from "./PosthogTrackers.ts"; @@ -906,7 +905,6 @@ const stateHandlers: IHandlers = { // TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111) "im.vector.modular.widgets": textForWidgetEvent, [WIDGET_LAYOUT_EVENT_TYPE]: textForWidgetLayoutEvent, - [VoiceBroadcastInfoEventType]: textForVoiceBroadcastStoppedEvent, }; // Add all the Mjolnir stuff to the renderer diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 9f9e2253523..548dbff983c 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -119,7 +119,6 @@ import { ValidatedServerConfig } from "../../utils/ValidatedServerConfig"; import { isLocalRoom } from "../../utils/localRoom/isLocalRoom"; import { SDKContext, SdkContextClass } from "../../contexts/SDKContext"; import { viewUserDeviceSettings } from "../../actions/handlers/viewUserDeviceSettings"; -import { cleanUpBroadcasts, VoiceBroadcastResumer } from "../../voice-broadcast"; import GenericToast from "../views/toasts/GenericToast"; import RovingSpotlightDialog from "../views/dialogs/spotlight/SpotlightDialog"; import { findDMForUser } from "../../utils/dm/findDMForUser"; @@ -227,7 +226,6 @@ export default class MatrixChat extends React.PureComponent { private focusNext: FocusNextType; private subTitleStatus: string; private prevWindowWidth: number; - private voiceBroadcastResumer?: VoiceBroadcastResumer; private readonly loggedInView = createRef(); private dispatcherRef?: string; @@ -501,7 +499,6 @@ export default class MatrixChat extends React.PureComponent { window.removeEventListener("resize", this.onWindowResized); this.stores.accountPasswordStore.clearPassword(); - this.voiceBroadcastResumer?.destroy(); } private onWindowResized = (): void => { @@ -651,10 +648,9 @@ export default class MatrixChat extends React.PureComponent { break; case "logout": LegacyCallHandler.instance.hangupAllCalls(); - Promise.all([ - ...[...CallStore.instance.connectedCalls].map((call) => call.disconnect()), - cleanUpBroadcasts(this.stores), - ]).finally(() => Lifecycle.logout(this.stores.oidcClientStore)); + Promise.all([...[...CallStore.instance.connectedCalls].map((call) => call.disconnect())]).finally(() => + Lifecycle.logout(this.stores.oidcClientStore), + ); break; case "require_registration": startAnyRegistrationFlow(payload as any); @@ -1679,8 +1675,6 @@ export default class MatrixChat extends React.PureComponent { }); } }); - - this.voiceBroadcastResumer = new VoiceBroadcastResumer(cli); } /** diff --git a/src/components/structures/PipContainer.tsx b/src/components/structures/PipContainer.tsx index 731e720b127..c9fabfe0c9e 100644 --- a/src/components/structures/PipContainer.tsx +++ b/src/components/structures/PipContainer.tsx @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ -import React, { MutableRefObject, ReactNode, useContext, useRef } from "react"; +import React, { MutableRefObject, ReactNode, useRef } from "react"; import { CallEvent, CallState, MatrixCall } from "matrix-js-sdk/src/webrtc/call"; import { logger } from "matrix-js-sdk/src/logger"; import { Optional } from "matrix-events-sdk"; @@ -21,19 +21,7 @@ import { WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutStore"; import ActiveWidgetStore, { ActiveWidgetStoreEvent } from "../../stores/ActiveWidgetStore"; import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload"; import { UPDATE_EVENT } from "../../stores/AsyncStore"; -import { SDKContext, SdkContextClass } from "../../contexts/SDKContext"; -import { - useCurrentVoiceBroadcastPreRecording, - useCurrentVoiceBroadcastRecording, - VoiceBroadcastPlayback, - VoiceBroadcastPlaybackBody, - VoiceBroadcastPreRecording, - VoiceBroadcastPreRecordingPip, - VoiceBroadcastRecording, - VoiceBroadcastRecordingPip, - VoiceBroadcastSmallPlaybackBody, -} from "../../voice-broadcast"; -import { useCurrentVoiceBroadcastPlayback } from "../../voice-broadcast/hooks/useCurrentVoiceBroadcastPlayback"; +import { SdkContextClass } from "../../contexts/SDKContext"; import { WidgetPip } from "../views/pips/WidgetPip"; const SHOW_CALL_IN_STATES = [ @@ -46,9 +34,6 @@ const SHOW_CALL_IN_STATES = [ ]; interface IProps { - voiceBroadcastRecording: Optional; - voiceBroadcastPreRecording: Optional; - voiceBroadcastPlayback: Optional; movePersistedElement: MutableRefObject<(() => void) | undefined>; } @@ -245,52 +230,9 @@ class PipContainerInner extends React.Component { this.setState({ showWidgetInPip, persistentWidgetId, persistentRoomId }); } - private createVoiceBroadcastPlaybackPipContent(voiceBroadcastPlayback: VoiceBroadcastPlayback): CreatePipChildren { - const content = - this.state.viewedRoomId === voiceBroadcastPlayback.infoEvent.getRoomId() ? ( - - ) : ( - - ); - - return ({ onStartMoving }) => ( -
- {content} -
- ); - } - - private createVoiceBroadcastPreRecordingPipContent( - voiceBroadcastPreRecording: VoiceBroadcastPreRecording, - ): CreatePipChildren { - return ({ onStartMoving }) => ( -
- -
- ); - } - - private createVoiceBroadcastRecordingPipContent( - voiceBroadcastRecording: VoiceBroadcastRecording, - ): CreatePipChildren { - return ({ onStartMoving }) => ( -
- -
- ); - } - public render(): ReactNode { const pipMode = true; - let pipContent: Array = []; - - if (this.props.voiceBroadcastRecording) { - pipContent = [this.createVoiceBroadcastRecordingPipContent(this.props.voiceBroadcastRecording)]; - } else if (this.props.voiceBroadcastPreRecording) { - pipContent = [this.createVoiceBroadcastPreRecordingPipContent(this.props.voiceBroadcastPreRecording)]; - } else if (this.props.voiceBroadcastPlayback) { - pipContent = [this.createVoiceBroadcastPlaybackPipContent(this.props.voiceBroadcastPlayback)]; - } + const pipContent: Array = []; if (this.state.primaryCall) { // get a ref to call inside the current scope @@ -338,24 +280,7 @@ class PipContainerInner extends React.Component { } export const PipContainer: React.FC = () => { - const sdkContext = useContext(SDKContext); - const voiceBroadcastPreRecordingStore = sdkContext.voiceBroadcastPreRecordingStore; - const { currentVoiceBroadcastPreRecording } = useCurrentVoiceBroadcastPreRecording(voiceBroadcastPreRecordingStore); - - const voiceBroadcastRecordingsStore = sdkContext.voiceBroadcastRecordingsStore; - const { currentVoiceBroadcastRecording } = useCurrentVoiceBroadcastRecording(voiceBroadcastRecordingsStore); - - const voiceBroadcastPlaybacksStore = sdkContext.voiceBroadcastPlaybacksStore; - const { currentVoiceBroadcastPlayback } = useCurrentVoiceBroadcastPlayback(voiceBroadcastPlaybacksStore); - const movePersistedElement = useRef<() => void>(); - return ( - - ); + return ; }; diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 5cd6ea7484c..c5f8ef841d3 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -40,8 +40,6 @@ import { UPDATE_SELECTED_SPACE } from "../../stores/spaces"; import UserIdentifierCustomisations from "../../customisations/UserIdentifier"; import PosthogTrackers from "../../PosthogTrackers"; import { ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePagePayload"; -import { Icon as LiveIcon } from "../../../res/img/compound/live-8px.svg"; -import { VoiceBroadcastRecording, VoiceBroadcastRecordingsStoreEvent } from "../../voice-broadcast"; import { SDKContext } from "../../contexts/SDKContext"; import { shouldShowFeedback } from "../../utils/Feedback"; import DarkLightModeSvg from "../../../res/img/element-icons/roomlist/dark-light-mode.svg"; @@ -58,7 +56,6 @@ interface IState { isDarkTheme: boolean; isHighContrast: boolean; selectedSpace?: Room | null; - showLiveAvatarAddon: boolean; } const toRightOf = (rect: PartialDOMRect): MenuProps => { @@ -94,7 +91,6 @@ export default class UserMenu extends React.Component { isDarkTheme: this.isUserOnDarkTheme(), isHighContrast: this.isUserOnHighContrastTheme(), selectedSpace: SpaceStore.instance.activeSpaceRoom, - showLiveAvatarAddon: this.context.voiceBroadcastRecordingsStore.hasCurrent(), }; } @@ -102,19 +98,9 @@ export default class UserMenu extends React.Component { return !!getHomePageUrl(SdkConfig.get(), this.context.client!); } - private onCurrentVoiceBroadcastRecordingChanged = (recording: VoiceBroadcastRecording | null): void => { - this.setState({ - showLiveAvatarAddon: recording !== null, - }); - }; - public componentDidMount(): void { OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate); SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdate); - this.context.voiceBroadcastRecordingsStore.on( - VoiceBroadcastRecordingsStoreEvent.CurrentChanged, - this.onCurrentVoiceBroadcastRecordingChanged, - ); this.dispatcherRef = defaultDispatcher.register(this.onAction); this.themeWatcherRef = SettingsStore.watchSetting("theme", null, this.onThemeChanged); } @@ -125,10 +111,6 @@ export default class UserMenu extends React.Component { defaultDispatcher.unregister(this.dispatcherRef); OwnProfileStore.instance.off(UPDATE_EVENT, this.onProfileUpdate); SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdate); - this.context.voiceBroadcastRecordingsStore.off( - VoiceBroadcastRecordingsStoreEvent.CurrentChanged, - this.onCurrentVoiceBroadcastRecordingChanged, - ); } private isUserOnDarkTheme(): boolean { @@ -435,12 +417,6 @@ export default class UserMenu extends React.Component { name =
{displayName}
; } - const liveAvatarAddon = this.state.showLiveAvatarAddon ? ( -
- -
- ) : null; - return (
{ size={avatarSize + "px"} className="mx_UserMenu_userAvatar_BaseAvatar" /> - {liveAvatarAddon}
{name} {this.renderContextMenu()} diff --git a/src/components/structures/grouper/CreationGrouper.tsx b/src/components/structures/grouper/CreationGrouper.tsx index db4542e8362..84982066c36 100644 --- a/src/components/structures/grouper/CreationGrouper.tsx +++ b/src/components/structures/grouper/CreationGrouper.tsx @@ -12,7 +12,6 @@ import { KnownMembership } from "matrix-js-sdk/src/types"; import { BaseGrouper } from "./BaseGrouper"; import MessagePanel, { WrappedEvent } from "../MessagePanel"; -import { VoiceBroadcastInfoEventType } from "../../../voice-broadcast"; import DMRoomMap from "../../../utils/DMRoomMap"; import { _t } from "../../../languageHandler"; import DateSeparator from "../../views/messages/DateSeparator"; @@ -53,11 +52,6 @@ export class CreationGrouper extends BaseGrouper { return false; } - if (VoiceBroadcastInfoEventType === eventType) { - // always show voice broadcast info events in timeline - return false; - } - if (event.isState() && event.getSender() === createEvent.getSender()) { return true; } diff --git a/src/components/views/audio_messages/DevicesContextMenu.tsx b/src/components/views/audio_messages/DevicesContextMenu.tsx deleted file mode 100644 index a88a2802428..00000000000 --- a/src/components/views/audio_messages/DevicesContextMenu.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022, 2023 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React, { MutableRefObject } from "react"; - -import { toLeftOrRightOf } from "../../structures/ContextMenu"; -import IconizedContextMenu, { - IconizedContextMenuOptionList, - IconizedContextMenuRadio, -} from "../context_menus/IconizedContextMenu"; - -interface Props { - containerRef: MutableRefObject; - currentDevice: MediaDeviceInfo | null; - devices: MediaDeviceInfo[]; - onDeviceSelect: (device: MediaDeviceInfo) => void; -} - -export const DevicesContextMenu: React.FC = ({ containerRef, currentDevice, devices, onDeviceSelect }) => { - const deviceOptions = devices.map((d: MediaDeviceInfo) => { - return ( - onDeviceSelect(d)} - label={d.label} - /> - ); - }); - - return ( - {}} - {...(containerRef.current ? toLeftOrRightOf(containerRef.current.getBoundingClientRect(), 0) : {})} - > - {deviceOptions} - - ); -}; diff --git a/src/components/views/dialogs/CantStartVoiceMessageBroadcastDialog.tsx b/src/components/views/dialogs/CantStartVoiceMessageBroadcastDialog.tsx deleted file mode 100644 index eb565c00726..00000000000 --- a/src/components/views/dialogs/CantStartVoiceMessageBroadcastDialog.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; - -import { _t } from "../../../languageHandler"; -import Modal from "../../../Modal"; -import InfoDialog from "./InfoDialog"; - -export const createCantStartVoiceMessageBroadcastDialog = (): void => { - Modal.createDialog(InfoDialog, { - title: _t("voice_message|cant_start_broadcast_title"), - description:

{_t("voice_message|cant_start_broadcast_description")}

, - hasCloseButton: true, - }); -}; diff --git a/src/components/views/dialogs/ConfirmRedactDialog.tsx b/src/components/views/dialogs/ConfirmRedactDialog.tsx index 27823ec4784..f4258c9d6d4 100644 --- a/src/components/views/dialogs/ConfirmRedactDialog.tsx +++ b/src/components/views/dialogs/ConfirmRedactDialog.tsx @@ -6,14 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ -import { Feature, ServerSupport } from "matrix-js-sdk/src/feature"; -import { IRedactOpts, MatrixEvent, RelationType } from "matrix-js-sdk/src/matrix"; +import { IRedactOpts, MatrixEvent } from "matrix-js-sdk/src/matrix"; import React from "react"; import { _t } from "../../../languageHandler"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import Modal from "../../../Modal"; -import { isVoiceBroadcastStartedEvent } from "../../../voice-broadcast/utils/isVoiceBroadcastStartedEvent"; import ErrorDialog from "./ErrorDialog"; import TextInputDialog from "./TextInputDialog"; @@ -70,18 +68,6 @@ export function createRedactEventDialog({ const cli = MatrixClientPeg.safeGet(); const withRelTypes: Pick = {}; - // redact related events if this is a voice broadcast started event and - // server has support for relation based redactions - if (isVoiceBroadcastStartedEvent(mxEvent)) { - const relationBasedRedactionsSupport = cli.canSupport.get(Feature.RelationBasedRedactions); - if ( - relationBasedRedactionsSupport && - relationBasedRedactionsSupport !== ServerSupport.Unsupported - ) { - withRelTypes.with_rel_types = [RelationType.Reference]; - } - } - try { onCloseDialog?.(); await cli.redactEvent(roomId, eventId, undefined, { diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx index 7319685014c..7dc683469a8 100644 --- a/src/components/views/dialogs/DevtoolsDialog.tsx +++ b/src/components/views/dialogs/DevtoolsDialog.tsx @@ -22,7 +22,6 @@ import { AccountDataExplorer, RoomAccountDataExplorer } from "./devtools/Account import SettingsFlag from "../elements/SettingsFlag"; import { SettingLevel } from "../../../settings/SettingLevel"; import ServerInfo from "./devtools/ServerInfo"; -import { Features } from "../../../settings/Settings"; import CopyableText from "../elements/CopyableText"; import RoomNotifications from "./devtools/RoomNotifications"; @@ -100,7 +99,6 @@ const DevtoolsDialog: React.FC = ({ roomId, threadRootId, onFinished }) - ); diff --git a/src/components/views/messages/MessageActionBar.tsx b/src/components/views/messages/MessageActionBar.tsx index bf92c993c56..9d21b8fa45a 100644 --- a/src/components/views/messages/MessageActionBar.tsx +++ b/src/components/views/messages/MessageActionBar.tsx @@ -58,7 +58,6 @@ import { ALTERNATE_KEY_NAME } from "../../../accessibility/KeyboardShortcuts"; import { Action } from "../../../dispatcher/actions"; import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload"; import { GetRelationsForEvent, IEventTileType } from "../rooms/EventTile"; -import { VoiceBroadcastInfoEventType } from "../../../voice-broadcast/types"; import { ButtonEvent } from "../elements/AccessibleButton"; import PinningUtils from "../../../utils/PinningUtils"; import PosthogTrackers from "../../../PosthogTrackers.ts"; @@ -354,8 +353,7 @@ export default class MessageActionBar extends React.PureComponent { @@ -276,10 +275,6 @@ export default class MessageEvent extends React.Component implements IMe if (M_LOCATION.matches(type) || (type === EventType.RoomMessage && msgtype === MsgType.Location)) { BodyType = MLocationBody; } - - if (type === VoiceBroadcastInfoEventType && content?.state === VoiceBroadcastInfoState.Started) { - BodyType = VoiceBroadcastBody; - } } if (SettingsStore.getValue("feature_mjolnir")) { diff --git a/src/components/views/rooms/MessageComposer.tsx b/src/components/views/rooms/MessageComposer.tsx index 9dcd107fed4..f5716d728b6 100644 --- a/src/components/views/rooms/MessageComposer.tsx +++ b/src/components/views/rooms/MessageComposer.tsx @@ -48,14 +48,9 @@ import MessageComposerButtons from "./MessageComposerButtons"; import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { isLocalRoom } from "../../../utils/localRoom/isLocalRoom"; -import { Features } from "../../../settings/Settings"; import { VoiceMessageRecording } from "../../../audio/VoiceMessageRecording"; import { SendWysiwygComposer, sendMessage, getConversionFunctions } from "./wysiwyg_composer/"; import { MatrixClientProps, withMatrixClientHOC } from "../../../contexts/MatrixClientContext"; -import { setUpVoiceBroadcastPreRecording } from "../../../voice-broadcast/utils/setUpVoiceBroadcastPreRecording"; -import { SdkContextClass } from "../../../contexts/SDKContext"; -import { VoiceBroadcastInfoState } from "../../../voice-broadcast"; -import { createCantStartVoiceMessageBroadcastDialog } from "../dialogs/CantStartVoiceMessageBroadcastDialog"; import { UIFeature } from "../../../settings/UIFeature"; import { formatTimeLeft } from "../../../DateUtils"; import RoomReplacedSvg from "../../../../res/img/room_replaced.svg"; @@ -101,7 +96,6 @@ interface IState { isStickerPickerOpen: boolean; showStickersButton: boolean; showPollsButton: boolean; - showVoiceBroadcastButton: boolean; isWysiwygLabEnabled: boolean; isRichTextEnabled: boolean; initialComposerContent: string; @@ -127,7 +121,6 @@ export class MessageComposer extends React.Component { public static defaultProps = { compact: false, - showVoiceBroadcastButton: false, isRichTextEnabled: true, }; @@ -155,7 +148,6 @@ export class MessageComposer extends React.Component { isStickerPickerOpen: false, showStickersButton: SettingsStore.getValue("MessageComposerInput.showStickersButton"), showPollsButton: SettingsStore.getValue("MessageComposerInput.showPollsButton"), - showVoiceBroadcastButton: SettingsStore.getValue(Features.VoiceBroadcast), isWysiwygLabEnabled: isWysiwygLabEnabled, isRichTextEnabled: isRichTextEnabled, initialComposerContent: initialComposerContent, @@ -250,7 +242,6 @@ export class MessageComposer extends React.Component { SettingsStore.monitorSetting("MessageComposerInput.showStickersButton", null); SettingsStore.monitorSetting("MessageComposerInput.showPollsButton", null); - SettingsStore.monitorSetting(Features.VoiceBroadcast, null); SettingsStore.monitorSetting("feature_wysiwyg_composer", null); this.dispatcherRef = dis.register(this.onAction); @@ -301,12 +292,6 @@ export class MessageComposer extends React.Component { } break; } - case Features.VoiceBroadcast: { - if (this.state.showVoiceBroadcastButton !== settingUpdatedPayload.newValue) { - this.setState({ showVoiceBroadcastButton: !!settingUpdatedPayload.newValue }); - } - break; - } case "feature_wysiwyg_composer": { if (this.state.isWysiwygLabEnabled !== settingUpdatedPayload.newValue) { this.setState({ isWysiwygLabEnabled: Boolean(settingUpdatedPayload.newValue) }); @@ -533,13 +518,7 @@ export class MessageComposer extends React.Component { } private onRecordStartEndClick = (): void => { - const currentBroadcastRecording = SdkContextClass.instance.voiceBroadcastRecordingsStore.getCurrent(); - - if (currentBroadcastRecording && currentBroadcastRecording.getState() !== VoiceBroadcastInfoState.Stopped) { - createCantStartVoiceMessageBroadcastDialog(); - } else { - this.voiceRecordingButton.current?.onRecordStartEndClick(); - } + this.voiceRecordingButton.current?.onRecordStartEndClick(); if (this.context.narrow) { this.toggleButtonMenu(); @@ -698,17 +677,6 @@ export class MessageComposer extends React.Component { isRichTextEnabled={this.state.isRichTextEnabled} onComposerModeClick={this.onRichTextToggle} toggleButtonMenu={this.toggleButtonMenu} - showVoiceBroadcastButton={this.state.showVoiceBroadcastButton} - onStartVoiceBroadcastClick={() => { - setUpVoiceBroadcastPreRecording( - this.props.room, - MatrixClientPeg.safeGet(), - SdkContextClass.instance.voiceBroadcastPlaybacksStore, - SdkContextClass.instance.voiceBroadcastRecordingsStore, - SdkContextClass.instance.voiceBroadcastPreRecordingStore, - ); - this.toggleButtonMenu(); - }} /> )} {showSendButton && ( diff --git a/src/components/views/rooms/MessageComposerButtons.tsx b/src/components/views/rooms/MessageComposerButtons.tsx index 370bc0861ce..19b86834dd7 100644 --- a/src/components/views/rooms/MessageComposerButtons.tsx +++ b/src/components/views/rooms/MessageComposerButtons.tsx @@ -43,8 +43,6 @@ interface IProps { showPollsButton: boolean; showStickersButton: boolean; toggleButtonMenu: () => void; - showVoiceBroadcastButton: boolean; - onStartVoiceBroadcastClick: () => void; isRichTextEnabled: boolean; onComposerModeClick: () => void; } @@ -80,7 +78,6 @@ const MessageComposerButtons: React.FC = (props: IProps) => { uploadButton(), // props passed via UploadButtonContext showStickersButton(props), voiceRecordingButton(props, narrow), - startVoiceBroadcastButton(props), props.showPollsButton ? pollButton(room, props.relation) : null, showLocationButton(props, room, matrixClient), ]; @@ -100,7 +97,6 @@ const MessageComposerButtons: React.FC = (props: IProps) => { moreButtons = [ showStickersButton(props), voiceRecordingButton(props, narrow), - startVoiceBroadcastButton(props), props.showPollsButton ? pollButton(room, props.relation) : null, showLocationButton(props, room, matrixClient), ]; @@ -254,18 +250,6 @@ function showStickersButton(props: IProps): ReactElement | null { ) : null; } -const startVoiceBroadcastButton: React.FC = (props: IProps): ReactElement | null => { - return props.showVoiceBroadcastButton ? ( - - ) : null; -}; - function voiceRecordingButton(props: IProps, narrow: boolean): ReactElement | null { // XXX: recording UI does not work well in narrow mode, so hide for now return narrow ? null : ( diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx index 8351c176ff0..7953c5068db 100644 --- a/src/components/views/rooms/RoomTile.tsx +++ b/src/components/views/rooms/RoomTile.tsx @@ -39,7 +39,6 @@ import { getKeyBindingsManager } from "../../../KeyBindingsManager"; import { RoomGeneralContextMenu } from "../context_menus/RoomGeneralContextMenu"; import { CallStore, CallStoreEvent } from "../../../stores/CallStore"; import { SdkContextClass } from "../../../contexts/SDKContext"; -import { useHasRoomLiveVoiceBroadcast } from "../../../voice-broadcast"; import { RoomTileSubtitle } from "./RoomTileSubtitle"; import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; import { UIComponent } from "../../../settings/UIFeature"; @@ -53,10 +52,6 @@ interface Props { tag: TagID; } -interface ClassProps extends Props { - hasLiveVoiceBroadcast: boolean; -} - type PartialDOMRect = Pick; interface State { @@ -77,13 +72,13 @@ export const contextMenuBelow = (elementRect: PartialDOMRect): MenuProps => { return { left, top, chevronFace }; }; -export class RoomTile extends React.PureComponent { +class RoomTile extends React.PureComponent { private dispatcherRef?: string; private roomTileRef = createRef(); private notificationState: NotificationState; private roomProps: RoomEchoChamber; - public constructor(props: ClassProps) { + public constructor(props: Props) { super(props); this.state = { @@ -370,15 +365,10 @@ export class RoomTile extends React.PureComponent { /** * RoomTile has a subtile if one of the following applies: * - there is a call - * - there is a live voice broadcast * - message previews are enabled and there is a previewable message */ private get shouldRenderSubtitle(): boolean { - return ( - !!this.state.call || - this.props.hasLiveVoiceBroadcast || - (this.props.showMessagePreview && !!this.state.messagePreview) - ); + return !!this.state.call || (this.props.showMessagePreview && !!this.state.messagePreview); } public render(): React.ReactElement { @@ -409,7 +399,6 @@ export class RoomTile extends React.PureComponent { const subtitle = this.shouldRenderSubtitle ? ( { } } -const RoomTileHOC: React.FC = (props: Props) => { - const hasLiveVoiceBroadcast = useHasRoomLiveVoiceBroadcast(props.room); - return ; -}; - -export default RoomTileHOC; +export default RoomTile; diff --git a/src/components/views/rooms/RoomTileSubtitle.tsx b/src/components/views/rooms/RoomTileSubtitle.tsx index ea4a96d2593..479b9c4f717 100644 --- a/src/components/views/rooms/RoomTileSubtitle.tsx +++ b/src/components/views/rooms/RoomTileSubtitle.tsx @@ -13,11 +13,9 @@ import { ThreadsIcon } from "@vector-im/compound-design-tokens/assets/web/icons" import { MessagePreview } from "../../../stores/room-list/MessagePreviewStore"; import { Call } from "../../../models/Call"; import { RoomTileCallSummary } from "./RoomTileCallSummary"; -import { VoiceBroadcastRoomSubtitle } from "../../../voice-broadcast"; interface Props { call: Call | null; - hasLiveVoiceBroadcast: boolean; messagePreview: MessagePreview | null; roomId: string; showMessagePreview: boolean; @@ -25,13 +23,7 @@ interface Props { const messagePreviewId = (roomId: string): string => `mx_RoomTile_messagePreview_${roomId}`; -export const RoomTileSubtitle: React.FC = ({ - call, - hasLiveVoiceBroadcast, - messagePreview, - roomId, - showMessagePreview, -}) => { +export const RoomTileSubtitle: React.FC = ({ call, messagePreview, roomId, showMessagePreview }) => { if (call) { return (
@@ -40,10 +32,6 @@ export const RoomTileSubtitle: React.FC = ({ ); } - if (hasLiveVoiceBroadcast) { - return ; - } - if (showMessagePreview && messagePreview) { const className = classNames("mx_RoomTile_subtitle", { "mx_RoomTile_subtitle--thread-reply": messagePreview.isThreadReply, diff --git a/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx index 5fada0b6bc4..baf4b412539 100644 --- a/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx @@ -19,7 +19,6 @@ import ErrorDialog from "../../../dialogs/ErrorDialog"; import PowerSelector from "../../../elements/PowerSelector"; import SettingsFieldset from "../../SettingsFieldset"; import SettingsStore from "../../../../../settings/SettingsStore"; -import { VoiceBroadcastInfoEventType } from "../../../../../voice-broadcast"; import { ElementCall } from "../../../../../models/Call"; import SdkConfig, { DEFAULTS } from "../../../../../SdkConfig"; import { AddPrivilegedUsers } from "../../AddPrivilegedUsers"; @@ -62,7 +61,6 @@ const plEventsToShow: Record = { // TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111) "im.vector.modular.widgets": { isState: true, hideForSpace: true }, - [VoiceBroadcastInfoEventType]: { isState: true, hideForSpace: true }, }; // parse a string as an integer; if the input is undefined, or cannot be parsed @@ -289,7 +287,6 @@ export default class RolesRoomSettingsTab extends React.Component void, -): { - currentDevice: MediaDeviceInfo | null; - currentDeviceLabel: string; - devices: MediaDeviceInfo[]; - setDevice(device: MediaDeviceInfo): void; -} => { - const shouldRequestPermissionsRef = useRef(true); - const [state, setState] = useState({ - devices: [], - device: null, - }); - - if (shouldRequestPermissionsRef.current) { - shouldRequestPermissionsRef.current = false; - requestMediaPermissions(false).then((stream: MediaStream | undefined) => { - MediaDeviceHandler.getDevices().then((devices) => { - if (!devices) return; - const { audioinput } = devices; - MediaDeviceHandler.getDefaultDevice(audioinput); - const deviceFromSettings = MediaDeviceHandler.getAudioInput(); - const device = - audioinput.find((d) => { - return d.deviceId === deviceFromSettings; - }) || audioinput[0]; - setState({ - ...state, - devices: audioinput, - device, - }); - stream?.getTracks().forEach((t) => t.stop()); - }); - }); - } - - const setDevice = (device: MediaDeviceInfo): void => { - const shouldNotify = device.deviceId !== state.device?.deviceId; - MediaDeviceHandler.instance.setDevice(device.deviceId, MediaDeviceKindEnum.AudioInput); - - setState({ - ...state, - device, - }); - - if (shouldNotify) { - onDeviceChanged?.(device); - } - }; - - return { - currentDevice: state.device, - currentDeviceLabel: state.device?.label || _t("voip|default_device"), - devices: state.devices, - setDevice, - }; -}; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 50ca4ae1e42..31a6c71c1ac 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1087,10 +1087,6 @@ }, "error_user_not_logged_in": "User is not logged in", "event_preview": { - "io.element.voice_broadcast_info": { - "user": "%(senderName)s ended a voice broadcast", - "you": "You ended a voice broadcast" - }, "m.call.answer": { "dm": "Call in progress", "user": "%(senderName)s joined the call", @@ -1491,8 +1487,6 @@ "video_rooms_faq2_answer": "Yes, the chat timeline is displayed alongside the video.", "video_rooms_faq2_question": "Can I use text chat alongside the video call?", "video_rooms_feedbackSubheading": "Thank you for trying the beta, please go into as much detail as you can so we can improve it.", - "voice_broadcast": "Voice broadcast", - "voice_broadcast_force_small_chunks": "Force 15s voice broadcast chunk length", "wysiwyg_composer": "Rich text editor" }, "labs_mjolnir": { @@ -1638,7 +1632,6 @@ "mute_description": "You won't get any notifications" }, "notifier": { - "io.element.voice_broadcast_chunk": "%(senderName)s started a voice broadcast", "m.key.verification.request": "%(name)s is requesting verification" }, "onboarding": { @@ -2253,7 +2246,6 @@ "error_unbanning": "Failed to unban", "events_default": "Send messages", "invite": "Invite users", - "io.element.voice_broadcast_info": "Voice broadcasts", "kick": "Remove users", "m.call": "Start %(brand)s calls", "m.call.member": "Join %(brand)s calls", @@ -3287,10 +3279,6 @@ "error_rendering_message": "Can't load this message", "historical_messages_unavailable": "You can't see earlier messages", "in_room_name": " in %(room)s", - "io.element.voice_broadcast_info": { - "user": "%(senderName)s ended a voice broadcast", - "you": "You ended a voice broadcast" - }, "io.element.widgets.layout": "%(senderName)s has updated the room layout", "late_event_separator": "Originally sent %(dateTime)s", "load_error": { @@ -3840,38 +3828,6 @@ "switch_theme_dark": "Switch to dark mode", "switch_theme_light": "Switch to light mode" }, - "voice_broadcast": { - "30s_backward": "30s backward", - "30s_forward": "30s forward", - "action": "Voice broadcast", - "buffering": "Buffering…", - "confirm_listen_affirm": "Yes, end my recording", - "confirm_listen_description": "If you start listening to this live broadcast, your current live broadcast recording will be ended.", - "confirm_listen_title": "Listen to live broadcast?", - "confirm_stop_affirm": "Yes, stop broadcast", - "confirm_stop_description": "Are you sure you want to stop your live broadcast? This will end the broadcast and the full recording will be available in the room.", - "confirm_stop_title": "Stop live broadcasting?", - "connection_error": "Connection error - Recording paused", - "failed_already_recording_description": "You are already recording a voice broadcast. Please end your current voice broadcast to start a new one.", - "failed_already_recording_title": "Can't start a new voice broadcast", - "failed_decrypt": "Unable to decrypt voice broadcast", - "failed_generic": "Unable to play this voice broadcast", - "failed_insufficient_permission_description": "You don't have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions.", - "failed_insufficient_permission_title": "Can't start a new voice broadcast", - "failed_no_connection_description": "Unfortunately we're unable to start a recording right now. Please try again later.", - "failed_no_connection_title": "Connection error", - "failed_others_already_recording_description": "Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one.", - "failed_others_already_recording_title": "Can't start a new voice broadcast", - "go_live": "Go live", - "live": "Live", - "pause": "pause voice broadcast", - "play": "play voice broadcast", - "resume": "resume voice broadcast" - }, - "voice_message": { - "cant_start_broadcast_description": "You can't start a voice message as you are currently recording a live broadcast. Please end your live broadcast in order to start recording a voice message.", - "cant_start_broadcast_title": "Can't start voice message" - }, "voip": { "already_in_call": "Already in call", "already_in_call_person": "You're already in a call with this person.", @@ -3891,7 +3847,6 @@ "camera_disabled": "Your camera is turned off", "camera_enabled": "Your camera is still enabled", "cannot_call_yourself_description": "You cannot place a call with yourself.", - "change_input_device": "Change input device", "close_lobby": "Close lobby", "connecting": "Connecting", "connection_lost": "Connectivity to the server has been lost", @@ -3910,8 +3865,6 @@ "enable_camera": "Turn on camera", "enable_microphone": "Unmute microphone", "expand": "Return to call", - "failed_call_live_broadcast_description": "You can’t start a call as you are currently recording a live broadcast. Please end your live broadcast in order to start a call.", - "failed_call_live_broadcast_title": "Can’t start a call", "get_call_link": "Share call link", "hangup": "Hangup", "hide_sidebar_button": "Hide sidebar", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 08cff0c1bbb..6cd5b15a515 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -85,8 +85,6 @@ export enum LabGroup { } export enum Features { - VoiceBroadcast = "feature_voice_broadcast", - VoiceBroadcastForceSmallChunks = "feature_voice_broadcast_force_small_chunks", NotificationSettings2 = "feature_notification_settings2", OidcNativeFlow = "feature_oidc_native_flow", ReleaseAnnouncement = "feature_release_announcement", @@ -440,19 +438,6 @@ export const SETTINGS: { [setting: string]: ISetting } = { shouldWarn: true, default: false, }, - [Features.VoiceBroadcast]: { - isFeature: true, - labsGroup: LabGroup.Messaging, - supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG_PRIORITISED, - supportedLevelsAreOrdered: true, - displayName: _td("labs|voice_broadcast"), - default: false, - }, - [Features.VoiceBroadcastForceSmallChunks]: { - supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, - displayName: _td("labs|voice_broadcast_force_small_chunks"), - default: false, - }, [Features.OidcNativeFlow]: { isFeature: true, labsGroup: LabGroup.Developer, diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx index 53e25736f07..66644c06a1f 100644 --- a/src/stores/RoomViewStore.tsx +++ b/src/stores/RoomViewStore.tsx @@ -42,15 +42,6 @@ import { UPDATE_EVENT } from "./AsyncStore"; import { SdkContextClass } from "../contexts/SDKContext"; import { CallStore } from "./CallStore"; import { ThreadPayload } from "../dispatcher/payloads/ThreadPayload"; -import { - doClearCurrentVoiceBroadcastPlaybackIfStopped, - doMaybeSetCurrentVoiceBroadcastPlayback, - VoiceBroadcastRecording, - VoiceBroadcastRecordingsStoreEvent, -} from "../voice-broadcast"; -import { IRoomStateEventsActionPayload } from "../actions/MatrixActionCreators"; -import { showCantStartACallDialog } from "../voice-broadcast/utils/showCantStartACallDialog"; -import { pauseNonLiveBroadcastFromOtherRoom } from "../voice-broadcast/utils/pauseNonLiveBroadcastFromOtherRoom"; import { ActionPayload } from "../dispatcher/payloads"; import { CancelAskToJoinPayload } from "../dispatcher/payloads/CancelAskToJoinPayload"; import { SubmitAskToJoinPayload } from "../dispatcher/payloads/SubmitAskToJoinPayload"; @@ -164,10 +155,6 @@ export class RoomViewStore extends EventEmitter { ) { super(); this.resetDispatcher(dis); - this.stores.voiceBroadcastRecordingsStore.addListener( - VoiceBroadcastRecordingsStoreEvent.CurrentChanged, - this.onCurrentBroadcastRecordingChanged, - ); } public addRoomListener(roomId: string, fn: Listener): void { @@ -182,16 +169,6 @@ export class RoomViewStore extends EventEmitter { this.emit(roomId, isActive); } - private onCurrentBroadcastRecordingChanged = (recording: VoiceBroadcastRecording | null): void => { - if (recording === null) { - const room = this.stores.client?.getRoom(this.state.roomId || undefined); - - if (room) { - this.doMaybeSetCurrentVoiceBroadcastPlayback(room); - } - } - }; - private setState(newState: Partial): void { // If values haven't changed, there's nothing to do. // This only tries a shallow comparison, so unchanged objects will slip @@ -207,16 +184,6 @@ export class RoomViewStore extends EventEmitter { return; } - if (newState.viewingCall) { - // Pause current broadcast, if any - this.stores.voiceBroadcastPlaybacksStore.getCurrent()?.pause(); - - if (this.stores.voiceBroadcastRecordingsStore.getCurrent()) { - showCantStartACallDialog(); - newState.viewingCall = false; - } - } - const lastRoomId = this.state.roomId; this.state = Object.assign(this.state, newState); if (lastRoomId !== this.state.roomId) { @@ -235,29 +202,6 @@ export class RoomViewStore extends EventEmitter { this.emit(UPDATE_EVENT); } - private doMaybeSetCurrentVoiceBroadcastPlayback(room: Room): void { - if (!this.stores.client) return; - doMaybeSetCurrentVoiceBroadcastPlayback( - room, - this.stores.client, - this.stores.voiceBroadcastPlaybacksStore, - this.stores.voiceBroadcastRecordingsStore, - ); - } - - private onRoomStateEvents(event: MatrixEvent): void { - const roomId = event.getRoomId?.(); - - // no room or not current room - if (!roomId || roomId !== this.state.roomId) return; - - const room = this.stores.client?.getRoom(roomId); - - if (room) { - this.doMaybeSetCurrentVoiceBroadcastPlayback(room); - } - } - private onDispatch(payload: ActionPayload): void { // eslint-disable-line @typescript-eslint/naming-convention switch (payload.action) { @@ -283,10 +227,6 @@ export class RoomViewStore extends EventEmitter { wasContextSwitch: false, viewingCall: false, }); - doClearCurrentVoiceBroadcastPlaybackIfStopped(this.stores.voiceBroadcastPlaybacksStore); - break; - case "MatrixActions.RoomState.events": - this.onRoomStateEvents((payload as IRoomStateEventsActionPayload).event); break; case Action.ViewRoomError: this.viewRoomError(payload as ViewRoomErrorPayload); @@ -489,9 +429,6 @@ export class RoomViewStore extends EventEmitter { } if (room) { - pauseNonLiveBroadcastFromOtherRoom(room, this.stores.voiceBroadcastPlaybacksStore); - this.doMaybeSetCurrentVoiceBroadcastPlayback(room); - await setMarkedUnreadState(room, MatrixClientPeg.safeGet(), false); } } else if (payload.room_alias) { diff --git a/src/stores/room-list/MessagePreviewStore.ts b/src/stores/room-list/MessagePreviewStore.ts index e0e06ec980e..2577b2ba235 100644 --- a/src/stores/room-list/MessagePreviewStore.ts +++ b/src/stores/room-list/MessagePreviewStore.ts @@ -22,8 +22,6 @@ import { StickerEventPreview } from "./previews/StickerEventPreview"; import { ReactionEventPreview } from "./previews/ReactionEventPreview"; import { UPDATE_EVENT } from "../AsyncStore"; import { IPreview } from "./previews/IPreview"; -import { VoiceBroadcastInfoEventType } from "../../voice-broadcast"; -import { VoiceBroadcastPreview } from "./previews/VoiceBroadcastPreview"; import shouldHideEvent from "../../shouldHideEvent"; // Emitted event for when a room's preview has changed. First argument will the room for which @@ -69,10 +67,6 @@ const PREVIEWS: Record< isState: false, previewer: new PollStartEventPreview(), }, - [VoiceBroadcastInfoEventType]: { - isState: true, - previewer: new VoiceBroadcastPreview(), - }, }; // The maximum number of events we're willing to look back on to get a preview. diff --git a/src/stores/room-list/previews/MessageEventPreview.ts b/src/stores/room-list/previews/MessageEventPreview.ts index 2873320cf38..20631f1425b 100644 --- a/src/stores/room-list/previews/MessageEventPreview.ts +++ b/src/stores/room-list/previews/MessageEventPreview.ts @@ -14,15 +14,11 @@ import { _t, sanitizeForTranslation } from "../../../languageHandler"; import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils"; import { getHtmlText } from "../../../HtmlUtils"; import { stripHTMLReply, stripPlainReply } from "../../../utils/Reply"; -import { VoiceBroadcastChunkEventType } from "../../../voice-broadcast/types"; export class MessageEventPreview implements IPreview { public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string | null { let eventContent = event.getContent(); - // no preview for broadcast chunks - if (eventContent[VoiceBroadcastChunkEventType]) return null; - if (event.isRelation(RelationType.Replace)) { // It's an edit, generate the preview on the new text eventContent = event.getContent()["m.new_content"]; diff --git a/src/stores/room-list/previews/VoiceBroadcastPreview.ts b/src/stores/room-list/previews/VoiceBroadcastPreview.ts deleted file mode 100644 index 94116692a69..00000000000 --- a/src/stores/room-list/previews/VoiceBroadcastPreview.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { MatrixEvent } from "matrix-js-sdk/src/matrix"; - -import { VoiceBroadcastInfoState } from "../../../voice-broadcast/types"; -import { textForVoiceBroadcastStoppedEventWithoutLink } from "../../../voice-broadcast/utils/textForVoiceBroadcastStoppedEventWithoutLink"; -import { IPreview } from "./IPreview"; - -export class VoiceBroadcastPreview implements IPreview { - public getTextFor(event: MatrixEvent, tagId?: string, isThread?: boolean): string | null { - if (!event.isRedacted() && event.getContent()?.state === VoiceBroadcastInfoState.Stopped) { - return textForVoiceBroadcastStoppedEventWithoutLink(event); - } - - return null; - } -} diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts index 8362f1048a0..0472b1664b1 100644 --- a/src/stores/widgets/StopGapWidget.ts +++ b/src/stores/widgets/StopGapWidget.ts @@ -284,10 +284,6 @@ export class StopGapWidget extends EventEmitter { }); this.messaging.on("capabilitiesNotified", () => this.emit("capabilitiesNotified")); this.messaging.on(`action:${WidgetApiFromWidgetAction.OpenModalWidget}`, this.onOpenModal); - this.messaging.on(`action:${ElementWidgetActions.JoinCall}`, () => { - // pause voice broadcast recording when any widget sends a "join" - SdkContextClass.instance.voiceBroadcastRecordingsStore.getCurrent()?.pause(); - }); // Always attach a handler for ViewRoom, but permission check it internally this.messaging.on(`action:${ElementWidgetActions.ViewRoom}`, (ev: CustomEvent) => { diff --git a/src/utils/EventRenderingUtils.ts b/src/utils/EventRenderingUtils.ts index 099bf768d87..ed8d4af1012 100644 --- a/src/utils/EventRenderingUtils.ts +++ b/src/utils/EventRenderingUtils.ts @@ -21,7 +21,6 @@ import SettingsStore from "../settings/SettingsStore"; import { haveRendererForEvent, JitsiEventFactory, JSONEventFactory, pickFactory } from "../events/EventTileFactory"; import { getMessageModerationState, isLocationEvent, MessageModerationState } from "./EventUtils"; import { ElementCall } from "../models/Call"; -import { VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from "../voice-broadcast"; const calcIsInfoMessage = ( eventType: EventType | string, @@ -38,8 +37,7 @@ const calcIsInfoMessage = ( eventType !== EventType.RoomCreate && !M_POLL_START.matches(eventType) && !M_POLL_END.matches(eventType) && - !M_BEACON_INFO.matches(eventType) && - !(eventType === VoiceBroadcastInfoEventType && content?.state === VoiceBroadcastInfoState.Started) + !M_BEACON_INFO.matches(eventType) ); }; @@ -91,8 +89,7 @@ export function getEventDisplayInfo( (eventType === EventType.RoomMessage && msgtype === MsgType.Emote) || M_POLL_START.matches(eventType) || M_BEACON_INFO.matches(eventType) || - isLocationEvent(mxEvent) || - eventType === VoiceBroadcastInfoEventType; + isLocationEvent(mxEvent); // If we're showing hidden events in the timeline, we should use the // source tile when there's no regular tile for an event and also for diff --git a/src/utils/EventUtils.ts b/src/utils/EventUtils.ts index 7c5b80697bf..d57cefa1b53 100644 --- a/src/utils/EventUtils.ts +++ b/src/utils/EventUtils.ts @@ -30,7 +30,6 @@ import { TimelineRenderingType } from "../contexts/RoomContext"; import { launchPollEditor } from "../components/views/messages/MPollBody"; import { Action } from "../dispatcher/actions"; import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload"; -import { VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from "../voice-broadcast/types"; /** * Returns whether an event should allow actions like reply, reactions, edit, etc. @@ -56,9 +55,7 @@ export function isContentActionable(mxEvent: MatrixEvent): boolean { mxEvent.getType() === "m.sticker" || M_POLL_START.matches(mxEvent.getType()) || M_POLL_END.matches(mxEvent.getType()) || - M_BEACON_INFO.matches(mxEvent.getType()) || - (mxEvent.getType() === VoiceBroadcastInfoEventType && - mxEvent.getContent()?.state === VoiceBroadcastInfoState.Started) + M_BEACON_INFO.matches(mxEvent.getType()) ) { return true; } diff --git a/src/voice-broadcast/audio/VoiceBroadcastRecorder.ts b/src/voice-broadcast/audio/VoiceBroadcastRecorder.ts deleted file mode 100644 index 8a6e17a1a5f..00000000000 --- a/src/voice-broadcast/audio/VoiceBroadcastRecorder.ts +++ /dev/null @@ -1,181 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { isEqual } from "lodash"; -import { Optional } from "matrix-events-sdk"; -import { logger } from "matrix-js-sdk/src/logger"; -import { TypedEventEmitter } from "matrix-js-sdk/src/matrix"; - -import { getChunkLength } from ".."; -import { IRecordingUpdate, VoiceRecording } from "../../audio/VoiceRecording"; -import { concat } from "../../utils/arrays"; -import { IDestroyable } from "../../utils/IDestroyable"; -import { Singleflight } from "../../utils/Singleflight"; - -export enum VoiceBroadcastRecorderEvent { - ChunkRecorded = "chunk_recorded", - CurrentChunkLengthUpdated = "current_chunk_length_updated", -} - -interface EventMap { - [VoiceBroadcastRecorderEvent.ChunkRecorded]: (chunk: ChunkRecordedPayload) => void; - [VoiceBroadcastRecorderEvent.CurrentChunkLengthUpdated]: (length: number) => void; -} - -export interface ChunkRecordedPayload { - buffer: Uint8Array; - length: number; -} - -// char sequence of "OpusHead" -const OpusHead = [79, 112, 117, 115, 72, 101, 97, 100]; - -// char sequence of "OpusTags" -const OpusTags = [79, 112, 117, 115, 84, 97, 103, 115]; - -/** - * This class provides the function to seamlessly record fixed length chunks. - * Subscribe with on(VoiceBroadcastRecordingEvents.ChunkRecorded, (payload: ChunkRecordedPayload) => {}) - * to retrieve chunks while recording. - */ -export class VoiceBroadcastRecorder - extends TypedEventEmitter - implements IDestroyable -{ - private opusHead?: Uint8Array; - private opusTags?: Uint8Array; - private chunkBuffer = new Uint8Array(0); - // position of the previous chunk in seconds - private previousChunkEndTimePosition = 0; - // current chunk length in seconds - private currentChunkLength = 0; - - public constructor( - private voiceRecording: VoiceRecording, - public readonly targetChunkLength: number, - ) { - super(); - this.voiceRecording.onDataAvailable = this.onDataAvailable; - } - - public async start(): Promise { - await this.voiceRecording.start(); - this.voiceRecording.liveData.onUpdate((data: IRecordingUpdate) => { - this.setCurrentChunkLength(data.timeSeconds - this.previousChunkEndTimePosition); - }); - } - - /** - * Stops the recording and returns the remaining chunk (if any). - */ - public async stop(): Promise> { - try { - await this.voiceRecording.stop(); - } catch { - // Ignore if the recording raises any error. - } - - // forget about that call, so that we can stop it again later - Singleflight.forgetAllFor(this.voiceRecording); - const chunk = this.extractChunk(); - this.currentChunkLength = 0; - this.previousChunkEndTimePosition = 0; - return chunk; - } - - public get contentType(): string { - return this.voiceRecording.contentType; - } - - private setCurrentChunkLength(currentChunkLength: number): void { - if (this.currentChunkLength === currentChunkLength) return; - - this.currentChunkLength = currentChunkLength; - this.emit(VoiceBroadcastRecorderEvent.CurrentChunkLengthUpdated, currentChunkLength); - } - - public getCurrentChunkLength(): number { - return this.currentChunkLength; - } - - private onDataAvailable = (data: ArrayBuffer): void => { - const dataArray = new Uint8Array(data); - - // extract the part, that contains the header type info - const headerType = Array.from(dataArray.slice(28, 36)); - - if (isEqual(OpusHead, headerType)) { - // data seems to be an "OpusHead" header - this.opusHead = dataArray; - return; - } - - if (isEqual(OpusTags, headerType)) { - // data seems to be an "OpusTags" header - this.opusTags = dataArray; - return; - } - - this.setCurrentChunkLength(this.voiceRecording.recorderSeconds! - this.previousChunkEndTimePosition); - this.handleData(dataArray); - }; - - private handleData(data: Uint8Array): void { - this.chunkBuffer = concat(this.chunkBuffer, data); - this.emitChunkIfTargetLengthReached(); - } - - private emitChunkIfTargetLengthReached(): void { - if (this.getCurrentChunkLength() >= this.targetChunkLength) { - this.emitAndResetChunk(); - } - } - - /** - * Extracts the current chunk and resets the buffer. - */ - private extractChunk(): Optional { - if (this.chunkBuffer.length === 0) { - return null; - } - - if (!this.opusHead || !this.opusTags) { - logger.warn("Broadcast chunk cannot be extracted. OpusHead or OpusTags is missing."); - return null; - } - - const currentRecorderTime = this.voiceRecording.recorderSeconds!; - const payload: ChunkRecordedPayload = { - buffer: concat(this.opusHead!, this.opusTags!, this.chunkBuffer), - length: this.getCurrentChunkLength(), - }; - this.chunkBuffer = new Uint8Array(0); - this.setCurrentChunkLength(0); - this.previousChunkEndTimePosition = currentRecorderTime; - return payload; - } - - private emitAndResetChunk(): void { - if (this.chunkBuffer.length === 0) { - return; - } - - this.emit(VoiceBroadcastRecorderEvent.ChunkRecorded, this.extractChunk()!); - } - - public destroy(): void { - this.removeAllListeners(); - this.voiceRecording.destroy(); - } -} - -export const createVoiceBroadcastRecorder = (): VoiceBroadcastRecorder => { - const voiceRecording = new VoiceRecording(); - voiceRecording.disableMaxLength(); - return new VoiceBroadcastRecorder(voiceRecording, getChunkLength()); -}; diff --git a/src/voice-broadcast/components/VoiceBroadcastBody.tsx b/src/voice-broadcast/components/VoiceBroadcastBody.tsx deleted file mode 100644 index 916ee9f9072..00000000000 --- a/src/voice-broadcast/components/VoiceBroadcastBody.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React, { useContext, useEffect, useState } from "react"; -import { MatrixEvent, RelationType } from "matrix-js-sdk/src/matrix"; - -import { - VoiceBroadcastRecordingBody, - shouldDisplayAsVoiceBroadcastRecordingTile, - VoiceBroadcastInfoEventType, - VoiceBroadcastPlaybackBody, - VoiceBroadcastInfoState, -} from ".."; -import { IBodyProps } from "../../components/views/messages/IBodyProps"; -import { RelationsHelper, RelationsHelperEvent } from "../../events/RelationsHelper"; -import { SDKContext } from "../../contexts/SDKContext"; -import { useMatrixClientContext } from "../../contexts/MatrixClientContext"; - -export const VoiceBroadcastBody: React.FC = ({ mxEvent }) => { - const sdkContext = useContext(SDKContext); - const client = useMatrixClientContext(); - const [infoState, setInfoState] = useState(mxEvent.getContent()?.state || VoiceBroadcastInfoState.Stopped); - - useEffect(() => { - const onInfoEvent = (event: MatrixEvent): void => { - if (event.getContent()?.state === VoiceBroadcastInfoState.Stopped) { - // only a stopped event can change the tile state - setInfoState(VoiceBroadcastInfoState.Stopped); - } - }; - - const relationsHelper = new RelationsHelper( - mxEvent, - RelationType.Reference, - VoiceBroadcastInfoEventType, - client, - ); - relationsHelper.on(RelationsHelperEvent.Add, onInfoEvent); - relationsHelper.emitCurrent(); - - return () => { - relationsHelper.destroy(); - }; - }); - - if (shouldDisplayAsVoiceBroadcastRecordingTile(infoState, client, mxEvent)) { - const recording = sdkContext.voiceBroadcastRecordingsStore.getByInfoEvent(mxEvent, client); - return ; - } - - const playback = sdkContext.voiceBroadcastPlaybacksStore.getByInfoEvent(mxEvent, client); - return ; -}; diff --git a/src/voice-broadcast/components/atoms/LiveBadge.tsx b/src/voice-broadcast/components/atoms/LiveBadge.tsx deleted file mode 100644 index 2591fee4357..00000000000 --- a/src/voice-broadcast/components/atoms/LiveBadge.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import classNames from "classnames"; -import React from "react"; - -import { Icon as LiveIcon } from "../../../../res/img/compound/live-16px.svg"; -import { _t } from "../../../languageHandler"; - -interface Props { - grey?: boolean; -} - -export const LiveBadge: React.FC = ({ grey = false }) => { - const liveBadgeClasses = classNames("mx_LiveBadge", { - "mx_LiveBadge--grey": grey, - }); - - return ( -
- - {_t("voice_broadcast|live")} -
- ); -}; diff --git a/src/voice-broadcast/components/atoms/SeekButton.tsx b/src/voice-broadcast/components/atoms/SeekButton.tsx deleted file mode 100644 index 5ee08264888..00000000000 --- a/src/voice-broadcast/components/atoms/SeekButton.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; - -import AccessibleButton from "../../../components/views/elements/AccessibleButton"; - -interface Props { - icon: React.FC>; - label: string; - onClick: () => void; -} - -export const SeekButton: React.FC = ({ onClick, icon: Icon, label }) => { - return ( - - - - ); -}; diff --git a/src/voice-broadcast/components/atoms/VoiceBroadcastControl.tsx b/src/voice-broadcast/components/atoms/VoiceBroadcastControl.tsx deleted file mode 100644 index 177b8fd732f..00000000000 --- a/src/voice-broadcast/components/atoms/VoiceBroadcastControl.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import classNames from "classnames"; -import React, { ReactElement } from "react"; - -import AccessibleButton from "../../../components/views/elements/AccessibleButton"; - -interface Props { - className?: string; - icon: ReactElement; - label: string; - onClick: () => void; -} - -export const VoiceBroadcastControl: React.FC = ({ className = "", icon, label, onClick }) => { - return ( - - {icon} - - ); -}; diff --git a/src/voice-broadcast/components/atoms/VoiceBroadcastError.tsx b/src/voice-broadcast/components/atoms/VoiceBroadcastError.tsx deleted file mode 100644 index d326853f4e7..00000000000 --- a/src/voice-broadcast/components/atoms/VoiceBroadcastError.tsx +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2023 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; -import { WarningIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; - -interface Props { - message: string; -} - -export const VoiceBroadcastError: React.FC = ({ message }) => { - return ( -
- - {message} -
- ); -}; diff --git a/src/voice-broadcast/components/atoms/VoiceBroadcastHeader.tsx b/src/voice-broadcast/components/atoms/VoiceBroadcastHeader.tsx deleted file mode 100644 index 52c0251c5ea..00000000000 --- a/src/voice-broadcast/components/atoms/VoiceBroadcastHeader.tsx +++ /dev/null @@ -1,139 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; -import { Room } from "matrix-js-sdk/src/matrix"; -import classNames from "classnames"; -import CloseIcon from "@vector-im/compound-design-tokens/assets/web/icons/close"; -import MicrophoneIcon from "@vector-im/compound-design-tokens/assets/web/icons/mic-on-solid"; - -import { LiveBadge, VoiceBroadcastLiveness } from "../.."; -import { Icon as LiveIcon } from "../../../../res/img/compound/live-16px.svg"; -import { Icon as TimerIcon } from "../../../../res/img/compound/timer-16px.svg"; -import { _t } from "../../../languageHandler"; -import RoomAvatar from "../../../components/views/avatars/RoomAvatar"; -import AccessibleButton, { ButtonEvent } from "../../../components/views/elements/AccessibleButton"; -import Clock from "../../../components/views/audio_messages/Clock"; -import { formatTimeLeft } from "../../../DateUtils"; -import Spinner from "../../../components/views/elements/Spinner"; -import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; -import { Action } from "../../../dispatcher/actions"; -import dis from "../../../dispatcher/dispatcher"; - -interface VoiceBroadcastHeaderProps { - linkToRoom?: boolean; - live?: VoiceBroadcastLiveness; - liveBadgePosition?: "middle" | "right"; - onCloseClick?: () => void; - onMicrophoneLineClick?: ((e: ButtonEvent) => void | Promise) | null; - room: Room; - microphoneLabel?: string; - showBroadcast?: boolean; - showBuffering?: boolean; - bufferingPosition?: "line" | "title"; - timeLeft?: number; - showClose?: boolean; -} - -export const VoiceBroadcastHeader: React.FC = ({ - linkToRoom = false, - live = "not-live", - liveBadgePosition = "right", - onCloseClick = (): void => {}, - onMicrophoneLineClick = null, - room, - microphoneLabel, - showBroadcast = false, - showBuffering = false, - bufferingPosition = "line", - showClose = false, - timeLeft, -}) => { - const broadcast = showBroadcast && ( -
- - {_t("voice_broadcast|action")} -
- ); - - const liveBadge = live !== "not-live" && ; - - const closeButton = showClose && ( - - - - ); - - const timeLeftLine = timeLeft && ( -
- - -
- ); - - const bufferingLine = showBuffering && bufferingPosition === "line" && ( -
- - {_t("voice_broadcast|buffering")} -
- ); - - const microphoneLineClasses = classNames({ - mx_VoiceBroadcastHeader_line: true, - ["mx_VoiceBroadcastHeader_mic--clickable"]: onMicrophoneLineClick, - }); - - const microphoneLine = microphoneLabel && ( - - - {microphoneLabel} - - ); - - const onRoomAvatarOrNameClick = (): void => { - dis.dispatch({ - action: Action.ViewRoom, - room_id: room.roomId, - metricsTrigger: undefined, // other - }); - }; - - let roomAvatar = ; - let roomName = ( -
-
{room.name}
- {showBuffering && bufferingPosition === "title" && } -
- ); - - if (linkToRoom) { - roomAvatar = {roomAvatar}; - - roomName = {roomName}; - } - - return ( -
- {roomAvatar} -
- {roomName} - {microphoneLine} - {timeLeftLine} - {broadcast} - {bufferingLine} - {liveBadgePosition === "middle" && liveBadge} -
- {liveBadgePosition === "right" && liveBadge} - {closeButton} -
- ); -}; diff --git a/src/voice-broadcast/components/atoms/VoiceBroadcastPlaybackControl.tsx b/src/voice-broadcast/components/atoms/VoiceBroadcastPlaybackControl.tsx deleted file mode 100644 index 08531b8afd9..00000000000 --- a/src/voice-broadcast/components/atoms/VoiceBroadcastPlaybackControl.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022, 2023 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React, { ReactElement } from "react"; -import PauseIcon from "@vector-im/compound-design-tokens/assets/web/icons/pause-solid"; -import PlayIcon from "@vector-im/compound-design-tokens/assets/web/icons/play-solid"; - -import { _t } from "../../../languageHandler"; -import { VoiceBroadcastControl, VoiceBroadcastPlaybackState } from "../.."; - -interface Props { - onClick: () => void; - state: VoiceBroadcastPlaybackState; -} - -export const VoiceBroadcastPlaybackControl: React.FC = ({ onClick, state }) => { - let controlIcon: ReactElement | null = null; - let controlLabel: string | null = null; - let className = ""; - - switch (state) { - case VoiceBroadcastPlaybackState.Stopped: - controlIcon = ; - className = "mx_VoiceBroadcastControl-play"; - controlLabel = _t("voice_broadcast|play"); - break; - case VoiceBroadcastPlaybackState.Paused: - controlIcon = ; - className = "mx_VoiceBroadcastControl-play"; - controlLabel = _t("voice_broadcast|resume"); - break; - case VoiceBroadcastPlaybackState.Buffering: - case VoiceBroadcastPlaybackState.Playing: - controlIcon = ; - controlLabel = _t("voice_broadcast|pause"); - break; - } - - if (controlIcon && controlLabel) { - return ( - - ); - } - - return null; -}; diff --git a/src/voice-broadcast/components/atoms/VoiceBroadcastRecordingConnectionError.tsx b/src/voice-broadcast/components/atoms/VoiceBroadcastRecordingConnectionError.tsx deleted file mode 100644 index 250d71f2f30..00000000000 --- a/src/voice-broadcast/components/atoms/VoiceBroadcastRecordingConnectionError.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2023 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; -import { WarningIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; - -import { _t } from "../../../languageHandler"; - -export const VoiceBroadcastRecordingConnectionError: React.FC = () => { - return ( -
- - {_t("voice_broadcast|connection_error")} -
- ); -}; diff --git a/src/voice-broadcast/components/atoms/VoiceBroadcastRoomSubtitle.tsx b/src/voice-broadcast/components/atoms/VoiceBroadcastRoomSubtitle.tsx deleted file mode 100644 index 20b73797893..00000000000 --- a/src/voice-broadcast/components/atoms/VoiceBroadcastRoomSubtitle.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; - -import { Icon as LiveIcon } from "../../../../res/img/compound/live-16px.svg"; -import { _t } from "../../../languageHandler"; - -export const VoiceBroadcastRoomSubtitle: React.FC = () => { - return ( -
- - {_t("voice_broadcast|live")} -
- ); -}; diff --git a/src/voice-broadcast/components/molecules/ConfirmListenBroadcastStopCurrent.tsx b/src/voice-broadcast/components/molecules/ConfirmListenBroadcastStopCurrent.tsx deleted file mode 100644 index 3dadfeba60d..00000000000 --- a/src/voice-broadcast/components/molecules/ConfirmListenBroadcastStopCurrent.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; - -import BaseDialog from "../../../components/views/dialogs/BaseDialog"; -import DialogButtons from "../../../components/views/elements/DialogButtons"; -import { _t } from "../../../languageHandler"; -import Modal from "../../../Modal"; - -interface Props { - onFinished: (confirmed?: boolean) => void; -} - -export const ConfirmListenBroadcastStopCurrentDialog: React.FC = ({ onFinished }) => { - return ( - -

{_t("voice_broadcast|confirm_listen_description")}

- onFinished(true)} - primaryButton={_t("voice_broadcast|confirm_listen_affirm")} - cancelButton={_t("action|no")} - onCancel={() => onFinished(false)} - /> -
- ); -}; - -export const showConfirmListenBroadcastStopCurrentDialog = async (): Promise => { - const { finished } = Modal.createDialog(ConfirmListenBroadcastStopCurrentDialog); - const [confirmed] = await finished; - return !!confirmed; -}; diff --git a/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx b/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx deleted file mode 100644 index 913b144960d..00000000000 --- a/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx +++ /dev/null @@ -1,102 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React, { ReactElement } from "react"; -import classNames from "classnames"; - -import { - VoiceBroadcastError, - VoiceBroadcastHeader, - VoiceBroadcastPlayback, - VoiceBroadcastPlaybackControl, - VoiceBroadcastPlaybackState, -} from "../.."; -import { useVoiceBroadcastPlayback } from "../../hooks/useVoiceBroadcastPlayback"; -import { Icon as Back30sIcon } from "../../../../res/img/compound/back-30s-24px.svg"; -import { Icon as Forward30sIcon } from "../../../../res/img/compound/forward-30s-24px.svg"; -import { _t } from "../../../languageHandler"; -import Clock from "../../../components/views/audio_messages/Clock"; -import SeekBar from "../../../components/views/audio_messages/SeekBar"; -import { SeekButton } from "../atoms/SeekButton"; - -const SEEK_TIME = 30; - -interface VoiceBroadcastPlaybackBodyProps { - pip?: boolean; - playback: VoiceBroadcastPlayback; -} - -export const VoiceBroadcastPlaybackBody: React.FC = ({ pip = false, playback }) => { - const { times, liveness, playbackState, room, sender, toggle } = useVoiceBroadcastPlayback(playback); - - let seekBackwardButton: ReactElement | null = null; - let seekForwardButton: ReactElement | null = null; - - if (playbackState !== VoiceBroadcastPlaybackState.Stopped) { - const onSeekBackwardButtonClick = (): void => { - playback.skipTo(Math.max(0, times.position - SEEK_TIME)); - }; - - seekBackwardButton = ( - - ); - - const onSeekForwardButtonClick = (): void => { - playback.skipTo(Math.min(times.duration, times.position + SEEK_TIME)); - }; - - seekForwardButton = ( - - ); - } - - const classes = classNames({ - mx_VoiceBroadcastBody: true, - ["mx_VoiceBroadcastBody--pip"]: pip, - }); - - const content = - playbackState === VoiceBroadcastPlaybackState.Error ? ( - - ) : ( - <> -
- {seekBackwardButton} - - {seekForwardButton} -
- -
- - -
- - ); - - return ( -
- - {content} -
- ); -}; diff --git a/src/voice-broadcast/components/molecules/VoiceBroadcastPreRecordingPip.tsx b/src/voice-broadcast/components/molecules/VoiceBroadcastPreRecordingPip.tsx deleted file mode 100644 index ac742e0fd81..00000000000 --- a/src/voice-broadcast/components/molecules/VoiceBroadcastPreRecordingPip.tsx +++ /dev/null @@ -1,82 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React, { useRef, useState } from "react"; - -import { VoiceBroadcastHeader } from "../.."; -import AccessibleButton from "../../../components/views/elements/AccessibleButton"; -import { VoiceBroadcastPreRecording } from "../../models/VoiceBroadcastPreRecording"; -import { Icon as LiveIcon } from "../../../../res/img/compound/live-16px.svg"; -import { _t } from "../../../languageHandler"; -import { useAudioDeviceSelection } from "../../../hooks/useAudioDeviceSelection"; -import { DevicesContextMenu } from "../../../components/views/audio_messages/DevicesContextMenu"; - -interface Props { - voiceBroadcastPreRecording: VoiceBroadcastPreRecording; -} - -interface State { - showDeviceSelect: boolean; - disableStartButton: boolean; -} - -export const VoiceBroadcastPreRecordingPip: React.FC = ({ voiceBroadcastPreRecording }) => { - const pipRef = useRef(null); - const { currentDevice, currentDeviceLabel, devices, setDevice } = useAudioDeviceSelection(); - const [state, setState] = useState({ - showDeviceSelect: false, - disableStartButton: false, - }); - - const onDeviceSelect = (device: MediaDeviceInfo): void => { - setState((state) => ({ - ...state, - showDeviceSelect: false, - })); - setDevice(device); - }; - - const onStartBroadcastClick = (): void => { - setState((state) => ({ - ...state, - disableStartButton: true, - })); - - voiceBroadcastPreRecording.start(); - }; - - return ( -
- setState({ ...state, showDeviceSelect: true })} - room={voiceBroadcastPreRecording.room} - microphoneLabel={currentDeviceLabel} - showClose={true} - /> - - - {_t("voice_broadcast|go_live")} - - {state.showDeviceSelect && ( - - )} -
- ); -}; diff --git a/src/voice-broadcast/components/molecules/VoiceBroadcastRecordingBody.tsx b/src/voice-broadcast/components/molecules/VoiceBroadcastRecordingBody.tsx deleted file mode 100644 index 15547792db8..00000000000 --- a/src/voice-broadcast/components/molecules/VoiceBroadcastRecordingBody.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; - -import { - useVoiceBroadcastRecording, - VoiceBroadcastHeader, - VoiceBroadcastRecording, - VoiceBroadcastRecordingConnectionError, -} from "../.."; - -interface VoiceBroadcastRecordingBodyProps { - recording: VoiceBroadcastRecording; -} - -export const VoiceBroadcastRecordingBody: React.FC = ({ recording }) => { - const { live, room, sender, recordingState } = useVoiceBroadcastRecording(recording); - - return ( -
- - {recordingState === "connection_error" && } -
- ); -}; diff --git a/src/voice-broadcast/components/molecules/VoiceBroadcastRecordingPip.tsx b/src/voice-broadcast/components/molecules/VoiceBroadcastRecordingPip.tsx deleted file mode 100644 index d04132b2200..00000000000 --- a/src/voice-broadcast/components/molecules/VoiceBroadcastRecordingPip.tsx +++ /dev/null @@ -1,116 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React, { useRef, useState } from "react"; -import PauseIcon from "@vector-im/compound-design-tokens/assets/web/icons/pause-solid"; -import MicrophoneIcon from "@vector-im/compound-design-tokens/assets/web/icons/mic-on-solid"; - -import { - VoiceBroadcastControl, - VoiceBroadcastInfoState, - VoiceBroadcastRecording, - VoiceBroadcastRecordingConnectionError, - VoiceBroadcastRecordingState, -} from "../.."; -import { useVoiceBroadcastRecording } from "../../hooks/useVoiceBroadcastRecording"; -import { VoiceBroadcastHeader } from "../atoms/VoiceBroadcastHeader"; -import { Icon as StopIcon } from "../../../../res/img/compound/stop-16.svg"; -import { Icon as RecordIcon } from "../../../../res/img/compound/record-10px.svg"; -import { _t } from "../../../languageHandler"; -import { useAudioDeviceSelection } from "../../../hooks/useAudioDeviceSelection"; -import { DevicesContextMenu } from "../../../components/views/audio_messages/DevicesContextMenu"; -import AccessibleButton from "../../../components/views/elements/AccessibleButton"; - -interface VoiceBroadcastRecordingPipProps { - recording: VoiceBroadcastRecording; -} - -export const VoiceBroadcastRecordingPip: React.FC = ({ recording }) => { - const pipRef = useRef(null); - const { live, timeLeft, recordingState, room, stopRecording, toggleRecording } = - useVoiceBroadcastRecording(recording); - const { currentDevice, devices, setDevice } = useAudioDeviceSelection(); - - const onDeviceSelect = async (device: MediaDeviceInfo): Promise => { - setShowDeviceSelect(false); - - if (currentDevice?.deviceId === device.deviceId) { - // device unchanged - return; - } - - setDevice(device); - - if ( - ( - [VoiceBroadcastInfoState.Paused, VoiceBroadcastInfoState.Stopped] as VoiceBroadcastRecordingState[] - ).includes(recordingState) - ) { - // Nothing to do in these cases. Resume will use the selected device. - return; - } - - // pause and resume to switch the input device - await recording.pause(); - await recording.resume(); - }; - - const [showDeviceSelect, setShowDeviceSelect] = useState(false); - - const toggleControl = - recordingState === VoiceBroadcastInfoState.Paused ? ( - } - label={_t("voice_broadcast|resume")} - /> - ) : ( - } - label={_t("voice_broadcast|pause")} - /> - ); - - const controls = - recordingState === "connection_error" ? ( - - ) : ( -
- {toggleControl} - setShowDeviceSelect(true)} - title={_t("voip|change_input_device")} - > - - - } - label="Stop Recording" - onClick={stopRecording} - /> -
- ); - - return ( -
- -
- {controls} - {showDeviceSelect && ( - - )} -
- ); -}; diff --git a/src/voice-broadcast/components/molecules/VoiceBroadcastSmallPlaybackBody.tsx b/src/voice-broadcast/components/molecules/VoiceBroadcastSmallPlaybackBody.tsx deleted file mode 100644 index a791ac75d77..00000000000 --- a/src/voice-broadcast/components/molecules/VoiceBroadcastSmallPlaybackBody.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; -import CloseIcon from "@vector-im/compound-design-tokens/assets/web/icons/close"; - -import { - VoiceBroadcastHeader, - VoiceBroadcastPlayback, - VoiceBroadcastPlaybackControl, - VoiceBroadcastPlaybackState, -} from "../.."; -import AccessibleButton from "../../../components/views/elements/AccessibleButton"; -import { useVoiceBroadcastPlayback } from "../../hooks/useVoiceBroadcastPlayback"; - -interface VoiceBroadcastSmallPlaybackBodyProps { - playback: VoiceBroadcastPlayback; -} - -export const VoiceBroadcastSmallPlaybackBody: React.FC = ({ playback }) => { - const { liveness, playbackState, room, sender, toggle } = useVoiceBroadcastPlayback(playback); - return ( -
- - - playback.stop()}> - - -
- ); -}; diff --git a/src/voice-broadcast/hooks/useCurrentVoiceBroadcastPlayback.ts b/src/voice-broadcast/hooks/useCurrentVoiceBroadcastPlayback.ts deleted file mode 100644 index 3ff4081a9fd..00000000000 --- a/src/voice-broadcast/hooks/useCurrentVoiceBroadcastPlayback.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { useTypedEventEmitterState } from "../../hooks/useEventEmitter"; -import { VoiceBroadcastPlayback } from "../models/VoiceBroadcastPlayback"; -import { - VoiceBroadcastPlaybacksStore, - VoiceBroadcastPlaybacksStoreEvent, -} from "../stores/VoiceBroadcastPlaybacksStore"; - -export const useCurrentVoiceBroadcastPlayback = ( - voiceBroadcastPlaybackStore: VoiceBroadcastPlaybacksStore, -): { - currentVoiceBroadcastPlayback: VoiceBroadcastPlayback | null; -} => { - const currentVoiceBroadcastPlayback = useTypedEventEmitterState( - voiceBroadcastPlaybackStore, - VoiceBroadcastPlaybacksStoreEvent.CurrentChanged, - (playback?: VoiceBroadcastPlayback) => { - return playback ?? voiceBroadcastPlaybackStore.getCurrent(); - }, - ); - - return { - currentVoiceBroadcastPlayback, - }; -}; diff --git a/src/voice-broadcast/hooks/useCurrentVoiceBroadcastPreRecording.ts b/src/voice-broadcast/hooks/useCurrentVoiceBroadcastPreRecording.ts deleted file mode 100644 index bb14e386404..00000000000 --- a/src/voice-broadcast/hooks/useCurrentVoiceBroadcastPreRecording.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { useTypedEventEmitterState } from "../../hooks/useEventEmitter"; -import { VoiceBroadcastPreRecordingStore } from "../stores/VoiceBroadcastPreRecordingStore"; -import { VoiceBroadcastPreRecording } from "../models/VoiceBroadcastPreRecording"; - -export const useCurrentVoiceBroadcastPreRecording = ( - voiceBroadcastPreRecordingStore: VoiceBroadcastPreRecordingStore, -): { - currentVoiceBroadcastPreRecording: VoiceBroadcastPreRecording | null; -} => { - const currentVoiceBroadcastPreRecording = useTypedEventEmitterState( - voiceBroadcastPreRecordingStore, - "changed", - (preRecording?: VoiceBroadcastPreRecording) => { - return preRecording ?? voiceBroadcastPreRecordingStore.getCurrent(); - }, - ); - - return { - currentVoiceBroadcastPreRecording, - }; -}; diff --git a/src/voice-broadcast/hooks/useCurrentVoiceBroadcastRecording.ts b/src/voice-broadcast/hooks/useCurrentVoiceBroadcastRecording.ts deleted file mode 100644 index 1d4abe3f10e..00000000000 --- a/src/voice-broadcast/hooks/useCurrentVoiceBroadcastRecording.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { VoiceBroadcastRecording, VoiceBroadcastRecordingsStore, VoiceBroadcastRecordingsStoreEvent } from ".."; -import { useTypedEventEmitterState } from "../../hooks/useEventEmitter"; - -export const useCurrentVoiceBroadcastRecording = ( - voiceBroadcastRecordingsStore: VoiceBroadcastRecordingsStore, -): { - currentVoiceBroadcastRecording: VoiceBroadcastRecording | null; -} => { - const currentVoiceBroadcastRecording = useTypedEventEmitterState( - voiceBroadcastRecordingsStore, - VoiceBroadcastRecordingsStoreEvent.CurrentChanged, - (recording?: VoiceBroadcastRecording) => { - return recording ?? voiceBroadcastRecordingsStore.getCurrent(); - }, - ); - - return { - currentVoiceBroadcastRecording, - }; -}; diff --git a/src/voice-broadcast/hooks/useHasRoomLiveVoiceBroadcast.ts b/src/voice-broadcast/hooks/useHasRoomLiveVoiceBroadcast.ts deleted file mode 100644 index a298f4dc830..00000000000 --- a/src/voice-broadcast/hooks/useHasRoomLiveVoiceBroadcast.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { useContext, useEffect, useMemo, useState } from "react"; -import { Room, RoomStateEvent } from "matrix-js-sdk/src/matrix"; - -import { hasRoomLiveVoiceBroadcast } from "../utils/hasRoomLiveVoiceBroadcast"; -import { useTypedEventEmitter } from "../../hooks/useEventEmitter"; -import { SDKContext } from "../../contexts/SDKContext"; - -export const useHasRoomLiveVoiceBroadcast = (room: Room): boolean => { - const sdkContext = useContext(SDKContext); - const [hasLiveVoiceBroadcast, setHasLiveVoiceBroadcast] = useState(false); - - const update = useMemo(() => { - return sdkContext?.client - ? () => { - hasRoomLiveVoiceBroadcast(sdkContext.client!, room).then( - ({ hasBroadcast }) => { - setHasLiveVoiceBroadcast(hasBroadcast); - }, - () => {}, // no update on error - ); - } - : () => {}; // noop without client - }, [room, sdkContext, setHasLiveVoiceBroadcast]); - - useEffect(() => { - update(); - }, [update]); - - useTypedEventEmitter(room.currentState, RoomStateEvent.Update, () => update()); - return hasLiveVoiceBroadcast; -}; diff --git a/src/voice-broadcast/hooks/useVoiceBroadcastPlayback.ts b/src/voice-broadcast/hooks/useVoiceBroadcastPlayback.ts deleted file mode 100644 index eb50b0de08b..00000000000 --- a/src/voice-broadcast/hooks/useVoiceBroadcastPlayback.ts +++ /dev/null @@ -1,90 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022, 2023 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { Room, RoomMember } from "matrix-js-sdk/src/matrix"; - -import { useTypedEventEmitterState } from "../../hooks/useEventEmitter"; -import { MatrixClientPeg } from "../../MatrixClientPeg"; -import { - VoiceBroadcastLiveness, - VoiceBroadcastPlayback, - VoiceBroadcastPlaybackEvent, - VoiceBroadcastPlaybackState, - VoiceBroadcastPlaybackTimes, -} from ".."; - -export const useVoiceBroadcastPlayback = ( - playback: VoiceBroadcastPlayback, -): { - times: { - duration: number; - position: number; - timeLeft: number; - }; - sender: RoomMember | null; - liveness: VoiceBroadcastLiveness; - playbackState: VoiceBroadcastPlaybackState; - toggle(): void; - room: Room; -} => { - const client = MatrixClientPeg.safeGet(); - const room = client.getRoom(playback.infoEvent.getRoomId()); - - if (!room) { - throw new Error(`Voice Broadcast room not found (event ${playback.infoEvent.getId()})`); - } - - const sender = playback.infoEvent.sender; - - if (!sender) { - throw new Error(`Voice Broadcast sender not found (event ${playback.infoEvent.getId()})`); - } - - const playbackToggle = (): void => { - playback.toggle(); - }; - - const playbackState = useTypedEventEmitterState( - playback, - VoiceBroadcastPlaybackEvent.StateChanged, - (state?: VoiceBroadcastPlaybackState) => { - return state ?? playback.getState(); - }, - ); - - const times = useTypedEventEmitterState( - playback, - VoiceBroadcastPlaybackEvent.TimesChanged, - (t?: VoiceBroadcastPlaybackTimes) => { - return ( - t ?? { - duration: playback.durationSeconds, - position: playback.timeSeconds, - timeLeft: playback.timeLeftSeconds, - } - ); - }, - ); - - const liveness = useTypedEventEmitterState( - playback, - VoiceBroadcastPlaybackEvent.LivenessChanged, - (l?: VoiceBroadcastLiveness) => { - return l ?? playback.getLiveness(); - }, - ); - - return { - times, - liveness: liveness, - playbackState, - room: room, - sender, - toggle: playbackToggle, - }; -}; diff --git a/src/voice-broadcast/hooks/useVoiceBroadcastRecording.tsx b/src/voice-broadcast/hooks/useVoiceBroadcastRecording.tsx deleted file mode 100644 index fa3c635bc92..00000000000 --- a/src/voice-broadcast/hooks/useVoiceBroadcastRecording.tsx +++ /dev/null @@ -1,96 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022, 2023 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { Room, RoomMember } from "matrix-js-sdk/src/matrix"; -import React from "react"; - -import { - VoiceBroadcastInfoState, - VoiceBroadcastRecording, - VoiceBroadcastRecordingEvent, - VoiceBroadcastRecordingState, -} from ".."; -import QuestionDialog from "../../components/views/dialogs/QuestionDialog"; -import { useTypedEventEmitterState } from "../../hooks/useEventEmitter"; -import { _t } from "../../languageHandler"; -import { MatrixClientPeg } from "../../MatrixClientPeg"; -import Modal from "../../Modal"; - -const showStopBroadcastingDialog = async (): Promise => { - const { finished } = Modal.createDialog(QuestionDialog, { - title: _t("voice_broadcast|confirm_stop_title"), - description:

{_t("voice_broadcast|confirm_stop_description")}

, - button: _t("voice_broadcast|confirm_stop_affirm"), - }); - const [confirmed] = await finished; - return !!confirmed; -}; - -export const useVoiceBroadcastRecording = ( - recording: VoiceBroadcastRecording, -): { - live: boolean; - timeLeft: number; - recordingState: VoiceBroadcastRecordingState; - room: Room; - sender: RoomMember | null; - stopRecording(): void; - toggleRecording(): void; -} => { - const client = MatrixClientPeg.safeGet(); - const roomId = recording.infoEvent.getRoomId(); - const room = client.getRoom(roomId); - - if (!room) { - throw new Error("Unable to find voice broadcast room with Id: " + roomId); - } - - const sender = recording.infoEvent.sender; - - if (!sender) { - throw new Error(`Voice Broadcast sender not found (event ${recording.infoEvent.getId()})`); - } - - const stopRecording = async (): Promise => { - const confirmed = await showStopBroadcastingDialog(); - - if (confirmed) { - await recording.stop(); - } - }; - - const recordingState = useTypedEventEmitterState( - recording, - VoiceBroadcastRecordingEvent.StateChanged, - (state?: VoiceBroadcastRecordingState) => { - return state ?? recording.getState(); - }, - ); - - const timeLeft = useTypedEventEmitterState( - recording, - VoiceBroadcastRecordingEvent.TimeLeftChanged, - (t?: number) => { - return t ?? recording.getTimeLeft(); - }, - ); - - const live = ( - [VoiceBroadcastInfoState.Started, VoiceBroadcastInfoState.Resumed] as VoiceBroadcastRecordingState[] - ).includes(recordingState); - - return { - live, - timeLeft, - recordingState, - room, - sender, - stopRecording, - toggleRecording: recording.toggle, - }; -}; diff --git a/src/voice-broadcast/index.ts b/src/voice-broadcast/index.ts deleted file mode 100644 index 712c25fdc21..00000000000 --- a/src/voice-broadcast/index.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -/** - * Voice Broadcast module - * {@link https://github.com/vector-im/element-meta/discussions/632} - */ - -export * from "./types"; -export * from "./models/VoiceBroadcastPlayback"; -export * from "./models/VoiceBroadcastPreRecording"; -export * from "./models/VoiceBroadcastRecording"; -export * from "./audio/VoiceBroadcastRecorder"; -export * from "./components/VoiceBroadcastBody"; -export * from "./components/atoms/LiveBadge"; -export * from "./components/atoms/VoiceBroadcastControl"; -export * from "./components/atoms/VoiceBroadcastError"; -export * from "./components/atoms/VoiceBroadcastHeader"; -export * from "./components/atoms/VoiceBroadcastPlaybackControl"; -export * from "./components/atoms/VoiceBroadcastRecordingConnectionError"; -export * from "./components/atoms/VoiceBroadcastRoomSubtitle"; -export * from "./components/molecules/ConfirmListenBroadcastStopCurrent"; -export * from "./components/molecules/VoiceBroadcastPlaybackBody"; -export * from "./components/molecules/VoiceBroadcastSmallPlaybackBody"; -export * from "./components/molecules/VoiceBroadcastPreRecordingPip"; -export * from "./components/molecules/VoiceBroadcastRecordingBody"; -export * from "./components/molecules/VoiceBroadcastRecordingPip"; -export * from "./hooks/useCurrentVoiceBroadcastPreRecording"; -export * from "./hooks/useCurrentVoiceBroadcastRecording"; -export * from "./hooks/useHasRoomLiveVoiceBroadcast"; -export * from "./hooks/useVoiceBroadcastRecording"; -export * from "./stores/VoiceBroadcastPlaybacksStore"; -export * from "./stores/VoiceBroadcastPreRecordingStore"; -export * from "./stores/VoiceBroadcastRecordingsStore"; -export * from "./utils/checkVoiceBroadcastPreConditions"; -export * from "./utils/cleanUpBroadcasts"; -export * from "./utils/doClearCurrentVoiceBroadcastPlaybackIfStopped"; -export * from "./utils/doMaybeSetCurrentVoiceBroadcastPlayback"; -export * from "./utils/getChunkLength"; -export * from "./utils/getMaxBroadcastLength"; -export * from "./utils/hasRoomLiveVoiceBroadcast"; -export * from "./utils/isRelatedToVoiceBroadcast"; -export * from "./utils/isVoiceBroadcastStartedEvent"; -export * from "./utils/findRoomLiveVoiceBroadcastFromUserAndDevice"; -export * from "./utils/retrieveStartedInfoEvent"; -export * from "./utils/shouldDisplayAsVoiceBroadcastRecordingTile"; -export * from "./utils/shouldDisplayAsVoiceBroadcastTile"; -export * from "./utils/shouldDisplayAsVoiceBroadcastStoppedText"; -export * from "./utils/startNewVoiceBroadcastRecording"; -export * from "./utils/textForVoiceBroadcastStoppedEvent"; -export * from "./utils/textForVoiceBroadcastStoppedEventWithoutLink"; -export * from "./utils/VoiceBroadcastResumer"; diff --git a/src/voice-broadcast/models/VoiceBroadcastPlayback.ts b/src/voice-broadcast/models/VoiceBroadcastPlayback.ts deleted file mode 100644 index ce6215312fb..00000000000 --- a/src/voice-broadcast/models/VoiceBroadcastPlayback.ts +++ /dev/null @@ -1,651 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { - EventType, - MatrixClient, - MatrixEvent, - MatrixEventEvent, - MsgType, - RelationType, - TypedEventEmitter, -} from "matrix-js-sdk/src/matrix"; -import { SimpleObservable } from "matrix-widget-api"; -import { logger } from "matrix-js-sdk/src/logger"; -import { defer, IDeferred } from "matrix-js-sdk/src/utils"; - -import { Playback, PlaybackInterface, PlaybackState } from "../../audio/Playback"; -import { PlaybackManager } from "../../audio/PlaybackManager"; -import { UPDATE_EVENT } from "../../stores/AsyncStore"; -import { MediaEventHelper } from "../../utils/MediaEventHelper"; -import { IDestroyable } from "../../utils/IDestroyable"; -import { - VoiceBroadcastLiveness, - VoiceBroadcastInfoEventType, - VoiceBroadcastInfoState, - VoiceBroadcastInfoEventContent, - VoiceBroadcastRecordingsStore, - showConfirmListenBroadcastStopCurrentDialog, -} from ".."; -import { RelationsHelper, RelationsHelperEvent } from "../../events/RelationsHelper"; -import { VoiceBroadcastChunkEvents } from "../utils/VoiceBroadcastChunkEvents"; -import { determineVoiceBroadcastLiveness } from "../utils/determineVoiceBroadcastLiveness"; -import { _t } from "../../languageHandler"; - -export enum VoiceBroadcastPlaybackState { - Paused = "pause", - Playing = "playing", - Stopped = "stopped", - Buffering = "buffering", - Error = "error", -} - -export enum VoiceBroadcastPlaybackEvent { - TimesChanged = "times_changed", - LivenessChanged = "liveness_changed", - StateChanged = "state_changed", - InfoStateChanged = "info_state_changed", -} - -export type VoiceBroadcastPlaybackTimes = { - duration: number; - position: number; - timeLeft: number; -}; - -interface EventMap { - [VoiceBroadcastPlaybackEvent.TimesChanged]: (times: VoiceBroadcastPlaybackTimes) => void; - [VoiceBroadcastPlaybackEvent.LivenessChanged]: (liveness: VoiceBroadcastLiveness) => void; - [VoiceBroadcastPlaybackEvent.StateChanged]: ( - state: VoiceBroadcastPlaybackState, - playback: VoiceBroadcastPlayback, - ) => void; - [VoiceBroadcastPlaybackEvent.InfoStateChanged]: (state: VoiceBroadcastInfoState) => void; -} - -export class VoiceBroadcastPlayback - extends TypedEventEmitter - implements IDestroyable, PlaybackInterface -{ - private state = VoiceBroadcastPlaybackState.Stopped; - private chunkEvents = new VoiceBroadcastChunkEvents(); - /** @var Map: event Id → undecryptable event */ - private utdChunkEvents: Map = new Map(); - private playbacks = new Map(); - private currentlyPlaying: MatrixEvent | null = null; - /** @var total duration of all chunks in milliseconds */ - private duration = 0; - /** @var current playback position in milliseconds */ - private position = 0; - public readonly liveData = new SimpleObservable(); - private liveness: VoiceBroadcastLiveness = "not-live"; - - // set via addInfoEvent() in constructor - private infoState!: VoiceBroadcastInfoState; - private lastInfoEvent!: MatrixEvent; - - // set via setUpRelationsHelper() in constructor - private chunkRelationHelper!: RelationsHelper; - private infoRelationHelper!: RelationsHelper; - - private skipToNext?: number; - private skipToDeferred?: IDeferred; - - public constructor( - public readonly infoEvent: MatrixEvent, - private client: MatrixClient, - private recordings: VoiceBroadcastRecordingsStore, - ) { - super(); - this.addInfoEvent(this.infoEvent); - this.infoEvent.on(MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction); - this.setUpRelationsHelper(); - } - - private async setUpRelationsHelper(): Promise { - this.infoRelationHelper = new RelationsHelper( - this.infoEvent, - RelationType.Reference, - VoiceBroadcastInfoEventType, - this.client, - ); - this.infoRelationHelper.getCurrent().forEach(this.addInfoEvent); - - if (this.infoState !== VoiceBroadcastInfoState.Stopped) { - // Only required if not stopped. Stopped is the final state. - this.infoRelationHelper.on(RelationsHelperEvent.Add, this.addInfoEvent); - - try { - await this.infoRelationHelper.emitFetchCurrent(); - } catch (err) { - logger.warn("error fetching server side relation for voice broadcast info", err); - // fall back to local events - this.infoRelationHelper.emitCurrent(); - } - } - - this.chunkRelationHelper = new RelationsHelper( - this.infoEvent, - RelationType.Reference, - EventType.RoomMessage, - this.client, - ); - this.chunkRelationHelper.on(RelationsHelperEvent.Add, this.addChunkEvent); - - try { - // TODO Michael W: only fetch events if needed, blocked by PSF-1708 - await this.chunkRelationHelper.emitFetchCurrent(); - } catch (err) { - logger.warn("error fetching server side relation for voice broadcast chunks", err); - // fall back to local events - this.chunkRelationHelper.emitCurrent(); - } - } - - private addChunkEvent = async (event: MatrixEvent): Promise => { - if (!event.getId() && !event.getTxnId()) { - // skip events without id and txn id - return false; - } - - if (event.isDecryptionFailure()) { - this.onChunkEventDecryptionFailure(event); - return false; - } - - if (event.getContent()?.msgtype !== MsgType.Audio) { - // skip non-audio event - return false; - } - - this.chunkEvents.addEvent(event); - this.setDuration(this.chunkEvents.getLength()); - - if (this.getState() === VoiceBroadcastPlaybackState.Buffering) { - await this.startOrPlayNext(); - } - - return true; - }; - - private onChunkEventDecryptionFailure = (event: MatrixEvent): void => { - const eventId = event.getId(); - - if (!eventId) { - // This should not happen, as the existence of the Id is checked before the call. - // Log anyway and return. - logger.warn("Broadcast chunk decryption failure for event without Id", { - broadcast: this.infoEvent.getId(), - }); - return; - } - - if (!this.utdChunkEvents.has(eventId)) { - event.once(MatrixEventEvent.Decrypted, this.onChunkEventDecrypted); - } - - this.utdChunkEvents.set(eventId, event); - this.setError(); - }; - - private onChunkEventDecrypted = async (event: MatrixEvent): Promise => { - const eventId = event.getId(); - - if (!eventId) { - // This should not happen, as the existence of the Id is checked before the call. - // Log anyway and return. - logger.warn("Broadcast chunk decrypted for event without Id", { broadcast: this.infoEvent.getId() }); - return; - } - - this.utdChunkEvents.delete(eventId); - await this.addChunkEvent(event); - - if (this.utdChunkEvents.size === 0) { - // no more UTD events, recover from error to paused - this.setState(VoiceBroadcastPlaybackState.Paused); - } - }; - - private startOrPlayNext = async (): Promise => { - if (this.currentlyPlaying) { - return this.playNext(); - } - - return await this.start(); - }; - - private addInfoEvent = (event: MatrixEvent): void => { - if (this.lastInfoEvent && this.lastInfoEvent.getTs() >= event.getTs()) { - // Only handle newer events - return; - } - - const state = event.getContent()?.state; - - if (!Object.values(VoiceBroadcastInfoState).includes(state)) { - // Do not handle unknown voice broadcast states - return; - } - - this.lastInfoEvent = event; - this.setInfoState(state); - }; - - private onBeforeRedaction = (): void => { - if (this.getState() !== VoiceBroadcastPlaybackState.Stopped) { - this.stop(); - // destroy cleans up everything - this.destroy(); - } - }; - - private async tryLoadPlayback(chunkEvent: MatrixEvent): Promise { - try { - return await this.loadPlayback(chunkEvent); - } catch (err: any) { - logger.warn("Unable to load broadcast playback", { - message: err.message, - broadcastId: this.infoEvent.getId(), - chunkId: chunkEvent.getId(), - }); - this.setError(); - } - } - - private async loadPlayback(chunkEvent: MatrixEvent): Promise { - const eventId = chunkEvent.getId(); - - if (!eventId) { - throw new Error("Broadcast chunk event without Id occurred"); - } - - const helper = new MediaEventHelper(chunkEvent); - const blob = await helper.sourceBlob.value; - const buffer = await blob.arrayBuffer(); - const playback = PlaybackManager.instance.createPlaybackInstance(buffer); - await playback.prepare(); - playback.clockInfo.populatePlaceholdersFrom(chunkEvent); - this.playbacks.set(eventId, playback); - playback.on(UPDATE_EVENT, (state) => this.onPlaybackStateChange(chunkEvent, state)); - playback.clockInfo.liveData.onUpdate(([position]) => { - this.onPlaybackPositionUpdate(chunkEvent, position); - }); - } - - private unloadPlayback(event: MatrixEvent): void { - const playback = this.playbacks.get(event.getId()!); - if (!playback) return; - - playback.destroy(); - this.playbacks.delete(event.getId()!); - } - - private onPlaybackPositionUpdate = (event: MatrixEvent, position: number): void => { - if (event !== this.currentlyPlaying) return; - - const newPosition = this.chunkEvents.getLengthTo(event) + position * 1000; // observable sends seconds - - // do not jump backwards - this can happen when transiting from one to another chunk - if (newPosition < this.position) return; - - this.setPosition(newPosition); - }; - - private setDuration(duration: number): void { - if (this.duration === duration) return; - - this.duration = duration; - this.emitTimesChanged(); - this.liveData.update([this.timeSeconds, this.durationSeconds]); - } - - private setPosition(position: number): void { - if (this.position === position) return; - - this.position = position; - this.emitTimesChanged(); - this.liveData.update([this.timeSeconds, this.durationSeconds]); - } - - private emitTimesChanged(): void { - this.emit(VoiceBroadcastPlaybackEvent.TimesChanged, { - duration: this.durationSeconds, - position: this.timeSeconds, - timeLeft: this.timeLeftSeconds, - }); - } - - private onPlaybackStateChange = async (event: MatrixEvent, newState: PlaybackState): Promise => { - if (event !== this.currentlyPlaying) return; - if (newState !== PlaybackState.Stopped) return; - - await this.playNext(); - this.unloadPlayback(event); - }; - - private async playNext(): Promise { - if (!this.currentlyPlaying) return; - - const next = this.chunkEvents.getNext(this.currentlyPlaying); - - if (next) { - return this.playEvent(next); - } - - if ( - this.getInfoState() === VoiceBroadcastInfoState.Stopped && - this.chunkEvents.getSequenceForEvent(this.currentlyPlaying) === this.lastChunkSequence - ) { - this.stop(); - } else { - // No more chunks available, although the broadcast is not finished → enter buffering state. - this.setState(VoiceBroadcastPlaybackState.Buffering); - } - } - - /** - * @returns {number} The last chunk sequence from the latest info event. - * Falls back to the length of received chunks if the info event does not provide the number. - */ - private get lastChunkSequence(): number { - return ( - this.lastInfoEvent.getContent()?.last_chunk_sequence || - this.chunkEvents.getNumberOfEvents() - ); - } - - private async playEvent(event: MatrixEvent): Promise { - this.setState(VoiceBroadcastPlaybackState.Playing); - this.currentlyPlaying = event; - const playback = await this.tryGetOrLoadPlaybackForEvent(event); - playback?.play(); - } - - private async tryGetOrLoadPlaybackForEvent(event: MatrixEvent): Promise { - try { - return await this.getOrLoadPlaybackForEvent(event); - } catch (err: any) { - logger.warn("Unable to load broadcast playback", { - message: err.message, - broadcastId: this.infoEvent.getId(), - chunkId: event.getId(), - }); - this.setError(); - } - } - - private async getOrLoadPlaybackForEvent(event: MatrixEvent): Promise { - const eventId = event.getId(); - - if (!eventId) { - throw new Error("Broadcast chunk event without Id occurred"); - } - - if (!this.playbacks.has(eventId)) { - // set to buffering while loading the chunk data - const currentState = this.getState(); - this.setState(VoiceBroadcastPlaybackState.Buffering); - await this.loadPlayback(event); - this.setState(currentState); - } - - const playback = this.playbacks.get(eventId); - - if (!playback) { - throw new Error(`Unable to find playback for event ${event.getId()}`); - } - - // try to load the playback for the next event for a smooth(er) playback - const nextEvent = this.chunkEvents.getNext(event); - if (nextEvent) this.tryLoadPlayback(nextEvent); - - return playback; - } - - private getCurrentPlayback(): Playback | undefined { - if (!this.currentlyPlaying) return; - return this.playbacks.get(this.currentlyPlaying.getId()!); - } - - public getLiveness(): VoiceBroadcastLiveness { - return this.liveness; - } - - private setLiveness(liveness: VoiceBroadcastLiveness): void { - if (this.liveness === liveness) return; - - this.liveness = liveness; - this.emit(VoiceBroadcastPlaybackEvent.LivenessChanged, liveness); - } - - public get currentState(): PlaybackState { - return PlaybackState.Playing; - } - - public get timeSeconds(): number { - return this.position / 1000; - } - - public get durationSeconds(): number { - return this.duration / 1000; - } - - public get timeLeftSeconds(): number { - // Sometimes the meta data and the audio files are a little bit out of sync. - // Be sure it never returns a negative value. - return Math.max(0, Math.round(this.durationSeconds) - this.timeSeconds); - } - - public async skipTo(timeSeconds: number): Promise { - this.skipToNext = timeSeconds; - - if (this.skipToDeferred) { - // Skip to position is already in progress. Return the promise for that. - return this.skipToDeferred.promise; - } - - this.skipToDeferred = defer(); - - while (this.skipToNext !== undefined) { - // Skip to position until skipToNext is undefined. - // skipToNext can be set if skipTo is called while already skipping. - const skipToNext = this.skipToNext; - this.skipToNext = undefined; - await this.doSkipTo(skipToNext); - } - - this.skipToDeferred.resolve(); - this.skipToDeferred = undefined; - } - - private async doSkipTo(timeSeconds: number): Promise { - const time = timeSeconds * 1000; - const event = this.chunkEvents.findByTime(time); - - if (!event) { - logger.warn("voice broadcast chunk event to skip to not found"); - return; - } - - const currentPlayback = this.getCurrentPlayback(); - const skipToPlayback = await this.tryGetOrLoadPlaybackForEvent(event); - const currentPlaybackEvent = this.currentlyPlaying; - - if (!skipToPlayback) { - logger.warn("voice broadcast chunk to skip to not found", event); - return; - } - - this.currentlyPlaying = event; - - if (currentPlayback && currentPlaybackEvent && currentPlayback !== skipToPlayback) { - // only stop and unload the playback here without triggering other effects, e.g. play next - currentPlayback.off(UPDATE_EVENT, this.onPlaybackStateChange); - await currentPlayback.stop(); - currentPlayback.on(UPDATE_EVENT, this.onPlaybackStateChange); - this.unloadPlayback(currentPlaybackEvent); - } - - const offsetInChunk = time - this.chunkEvents.getLengthTo(event); - await skipToPlayback.skipTo(offsetInChunk / 1000); - - if (this.state === VoiceBroadcastPlaybackState.Playing && !skipToPlayback.isPlaying) { - await skipToPlayback.play(); - } - - this.setPosition(time); - } - - public async start(): Promise { - if (this.state === VoiceBroadcastPlaybackState.Playing) return; - - const currentRecording = this.recordings.getCurrent(); - - if (currentRecording && currentRecording.getState() !== VoiceBroadcastInfoState.Stopped) { - const shouldStopRecording = await showConfirmListenBroadcastStopCurrentDialog(); - - if (!shouldStopRecording) { - // keep recording - return; - } - - await this.recordings.getCurrent()?.stop(); - } - - const chunkEvents = this.chunkEvents.getEvents(); - - const toPlay = - this.getInfoState() === VoiceBroadcastInfoState.Stopped - ? chunkEvents[0] // start at the beginning for an ended voice broadcast - : chunkEvents[chunkEvents.length - 1]; // start at the current chunk for an ongoing voice broadcast - - if (toPlay) { - return this.playEvent(toPlay); - } - - this.setState(VoiceBroadcastPlaybackState.Buffering); - } - - public stop(): void { - // error is a final state - if (this.getState() === VoiceBroadcastPlaybackState.Error) return; - - this.setState(VoiceBroadcastPlaybackState.Stopped); - this.getCurrentPlayback()?.stop(); - this.currentlyPlaying = null; - this.setPosition(0); - } - - public pause(): void { - // error is a final state - if (this.getState() === VoiceBroadcastPlaybackState.Error) return; - - // stopped voice broadcasts cannot be paused - if (this.getState() === VoiceBroadcastPlaybackState.Stopped) return; - - this.setState(VoiceBroadcastPlaybackState.Paused); - this.getCurrentPlayback()?.pause(); - } - - public resume(): void { - // error is a final state - if (this.getState() === VoiceBroadcastPlaybackState.Error) return; - - if (!this.currentlyPlaying) { - // no playback to resume, start from the beginning - this.start(); - return; - } - - this.setState(VoiceBroadcastPlaybackState.Playing); - this.getCurrentPlayback()?.play(); - } - - /** - * Toggles the playback: - * stopped → playing - * playing → paused - * paused → playing - */ - public async toggle(): Promise { - // error is a final state - if (this.getState() === VoiceBroadcastPlaybackState.Error) return; - - if (this.state === VoiceBroadcastPlaybackState.Stopped) { - await this.start(); - return; - } - - if (this.state === VoiceBroadcastPlaybackState.Paused) { - this.resume(); - return; - } - - this.pause(); - } - - public getState(): VoiceBroadcastPlaybackState { - return this.state; - } - - private setState(state: VoiceBroadcastPlaybackState): void { - if (this.state === state) { - return; - } - - this.state = state; - this.emit(VoiceBroadcastPlaybackEvent.StateChanged, state, this); - } - - /** - * Set error state. Stop current playback, if any. - */ - private setError(): void { - this.setState(VoiceBroadcastPlaybackState.Error); - this.getCurrentPlayback()?.stop(); - this.currentlyPlaying = null; - this.setPosition(0); - } - - public getInfoState(): VoiceBroadcastInfoState { - return this.infoState; - } - - private setInfoState(state: VoiceBroadcastInfoState): void { - if (this.infoState === state) { - return; - } - - this.infoState = state; - this.emit(VoiceBroadcastPlaybackEvent.InfoStateChanged, state); - this.setLiveness(determineVoiceBroadcastLiveness(this.infoState)); - } - - public get errorMessage(): string { - if (this.getState() !== VoiceBroadcastPlaybackState.Error) return ""; - if (this.utdChunkEvents.size) return _t("voice_broadcast|failed_decrypt"); - return _t("voice_broadcast|failed_generic"); - } - - public destroy(): void { - for (const [, utdEvent] of this.utdChunkEvents) { - utdEvent.off(MatrixEventEvent.Decrypted, this.onChunkEventDecrypted); - } - - this.utdChunkEvents.clear(); - - this.chunkRelationHelper.destroy(); - this.infoRelationHelper.destroy(); - this.removeAllListeners(); - - this.chunkEvents = new VoiceBroadcastChunkEvents(); - this.playbacks.forEach((p) => p.destroy()); - this.playbacks = new Map(); - } -} diff --git a/src/voice-broadcast/models/VoiceBroadcastPreRecording.ts b/src/voice-broadcast/models/VoiceBroadcastPreRecording.ts deleted file mode 100644 index 0cf47c6f214..00000000000 --- a/src/voice-broadcast/models/VoiceBroadcastPreRecording.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { MatrixClient, Room, RoomMember, TypedEventEmitter } from "matrix-js-sdk/src/matrix"; - -import { IDestroyable } from "../../utils/IDestroyable"; -import { VoiceBroadcastPlaybacksStore } from "../stores/VoiceBroadcastPlaybacksStore"; -import { VoiceBroadcastRecordingsStore } from "../stores/VoiceBroadcastRecordingsStore"; -import { startNewVoiceBroadcastRecording } from "../utils/startNewVoiceBroadcastRecording"; - -type VoiceBroadcastPreRecordingEvent = "dismiss"; - -interface EventMap { - dismiss: (voiceBroadcastPreRecording: VoiceBroadcastPreRecording) => void; -} - -export class VoiceBroadcastPreRecording - extends TypedEventEmitter - implements IDestroyable -{ - public constructor( - public room: Room, - public sender: RoomMember, - private client: MatrixClient, - private playbacksStore: VoiceBroadcastPlaybacksStore, - private recordingsStore: VoiceBroadcastRecordingsStore, - ) { - super(); - } - - public start = async (): Promise => { - await startNewVoiceBroadcastRecording(this.room, this.client, this.playbacksStore, this.recordingsStore); - this.emit("dismiss", this); - }; - - public cancel = (): void => { - this.emit("dismiss", this); - }; - - public destroy(): void { - this.removeAllListeners(); - } -} diff --git a/src/voice-broadcast/models/VoiceBroadcastRecording.ts b/src/voice-broadcast/models/VoiceBroadcastRecording.ts deleted file mode 100644 index ebf8ee697f8..00000000000 --- a/src/voice-broadcast/models/VoiceBroadcastRecording.ts +++ /dev/null @@ -1,441 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { logger } from "matrix-js-sdk/src/logger"; -import { - ClientEvent, - ClientEventHandlerMap, - EventType, - MatrixClient, - MatrixEvent, - MatrixEventEvent, - MsgType, - RelationType, - TypedEventEmitter, -} from "matrix-js-sdk/src/matrix"; -import { AudioContent, EncryptedFile } from "matrix-js-sdk/src/types"; - -import { - ChunkRecordedPayload, - createVoiceBroadcastRecorder, - getMaxBroadcastLength, - VoiceBroadcastInfoEventContent, - VoiceBroadcastInfoEventType, - VoiceBroadcastInfoState, - VoiceBroadcastRecorder, - VoiceBroadcastRecorderEvent, -} from ".."; -import { uploadFile } from "../../ContentMessages"; -import { createVoiceMessageContent } from "../../utils/createVoiceMessageContent"; -import { IDestroyable } from "../../utils/IDestroyable"; -import dis from "../../dispatcher/dispatcher"; -import { ActionPayload } from "../../dispatcher/payloads"; -import { VoiceBroadcastChunkEvents } from "../utils/VoiceBroadcastChunkEvents"; -import { RelationsHelper, RelationsHelperEvent } from "../../events/RelationsHelper"; -import { createReconnectedListener } from "../../utils/connection"; -import { localNotificationsAreSilenced } from "../../utils/notifications"; -import { BackgroundAudio } from "../../audio/BackgroundAudio"; - -export enum VoiceBroadcastRecordingEvent { - StateChanged = "liveness_changed", - TimeLeftChanged = "time_left_changed", -} - -export type VoiceBroadcastRecordingState = VoiceBroadcastInfoState | "connection_error"; - -interface EventMap { - [VoiceBroadcastRecordingEvent.StateChanged]: (state: VoiceBroadcastRecordingState) => void; - [VoiceBroadcastRecordingEvent.TimeLeftChanged]: (timeLeft: number) => void; -} - -export class VoiceBroadcastRecording - extends TypedEventEmitter - implements IDestroyable -{ - private state: VoiceBroadcastRecordingState; - private recorder: VoiceBroadcastRecorder | null = null; - private dispatcherRef: string; - private chunkEvents = new VoiceBroadcastChunkEvents(); - private chunkRelationHelper: RelationsHelper; - private maxLength: number; - private timeLeft: number; - private toRetry: Array<() => Promise> = []; - private reconnectedListener: ClientEventHandlerMap[ClientEvent.Sync]; - private roomId: string; - private infoEventId: string; - private backgroundAudio = new BackgroundAudio(); - - /** - * Broadcast chunks have a sequence number to bring them in the correct order and to know if a message is missing. - * This variable holds the last sequence number. - * Starts with 0 because there is no chunk at the beginning of a broadcast. - * Will be incremented when a chunk message is created. - */ - private sequence = 0; - - public constructor( - public readonly infoEvent: MatrixEvent, - private client: MatrixClient, - initialState?: VoiceBroadcastInfoState, - ) { - super(); - this.maxLength = getMaxBroadcastLength(); - this.timeLeft = this.maxLength; - this.infoEventId = this.determineEventIdFromInfoEvent(); - this.roomId = this.determineRoomIdFromInfoEvent(); - - if (initialState) { - this.state = initialState; - } else { - this.state = this.determineInitialStateFromInfoEvent(); - } - - // TODO Michael W: listen for state updates - - this.infoEvent.on(MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction); - this.dispatcherRef = dis.register(this.onAction); - this.chunkRelationHelper = this.initialiseChunkEventRelation(); - this.reconnectedListener = createReconnectedListener(this.onReconnect); - this.client.on(ClientEvent.Sync, this.reconnectedListener); - } - - private initialiseChunkEventRelation(): RelationsHelper { - const relationsHelper = new RelationsHelper( - this.infoEvent, - RelationType.Reference, - EventType.RoomMessage, - this.client, - ); - relationsHelper.on(RelationsHelperEvent.Add, this.onChunkEvent); - - relationsHelper.emitFetchCurrent().catch((err) => { - logger.warn("error fetching server side relation for voice broadcast chunks", err); - // fall back to local events - relationsHelper.emitCurrent(); - }); - - return relationsHelper; - } - - private onChunkEvent = (event: MatrixEvent): void => { - if ( - (!event.getId() && !event.getTxnId()) || - event.getContent()?.msgtype !== MsgType.Audio // don't add non-audio event - ) { - return; - } - - this.chunkEvents.addEvent(event); - }; - - private determineEventIdFromInfoEvent(): string { - const infoEventId = this.infoEvent.getId(); - - if (!infoEventId) { - throw new Error("Cannot create broadcast for info event without Id."); - } - - return infoEventId; - } - - private determineRoomIdFromInfoEvent(): string { - const roomId = this.infoEvent.getRoomId(); - - if (!roomId) { - throw new Error(`Cannot create broadcast for unknown room (info event ${this.infoEventId})`); - } - - return roomId; - } - - /** - * Determines the initial broadcast state. - * Checks all related events. If one has the "stopped" state → stopped, else started. - */ - private determineInitialStateFromInfoEvent(): VoiceBroadcastRecordingState { - const room = this.client.getRoom(this.roomId); - const relations = room - ?.getUnfilteredTimelineSet() - ?.relations?.getChildEventsForEvent(this.infoEventId, RelationType.Reference, VoiceBroadcastInfoEventType); - const relatedEvents = relations?.getRelations(); - return !relatedEvents?.find((event: MatrixEvent) => { - return event.getContent()?.state === VoiceBroadcastInfoState.Stopped; - }) - ? VoiceBroadcastInfoState.Started - : VoiceBroadcastInfoState.Stopped; - } - - public getTimeLeft(): number { - return this.timeLeft; - } - - /** - * Retries failed actions on reconnect. - */ - private onReconnect = async (): Promise => { - // Do nothing if not in connection_error state. - if (this.state !== "connection_error") return; - - // Copy the array, so that it is possible to remove elements from it while iterating over the original. - const toRetryCopy = [...this.toRetry]; - - for (const retryFn of this.toRetry) { - try { - await retryFn(); - // Successfully retried. Remove from array copy. - toRetryCopy.splice(toRetryCopy.indexOf(retryFn), 1); - } catch { - // The current retry callback failed. Stop the loop. - break; - } - } - - this.toRetry = toRetryCopy; - - if (this.toRetry.length === 0) { - // Everything has been successfully retried. Recover from error state to paused. - await this.pause(); - } - }; - - private async setTimeLeft(timeLeft: number): Promise { - if (timeLeft <= 0) { - // time is up - stop the recording - return await this.stop(); - } - - // do never increase time left; no action if equals - if (timeLeft >= this.timeLeft) return; - - this.timeLeft = timeLeft; - this.emit(VoiceBroadcastRecordingEvent.TimeLeftChanged, timeLeft); - } - - public async start(): Promise { - return this.getRecorder().start(); - } - - public async stop(): Promise { - if (this.state === VoiceBroadcastInfoState.Stopped) return; - - this.setState(VoiceBroadcastInfoState.Stopped); - await this.stopRecorder(); - await this.sendInfoStateEvent(VoiceBroadcastInfoState.Stopped); - } - - public async pause(): Promise { - // stopped or already paused recordings cannot be paused - if ( - ( - [VoiceBroadcastInfoState.Stopped, VoiceBroadcastInfoState.Paused] as VoiceBroadcastRecordingState[] - ).includes(this.state) - ) - return; - - this.setState(VoiceBroadcastInfoState.Paused); - await this.stopRecorder(); - await this.sendInfoStateEvent(VoiceBroadcastInfoState.Paused); - } - - public async resume(): Promise { - if (this.state !== VoiceBroadcastInfoState.Paused) return; - - this.setState(VoiceBroadcastInfoState.Resumed); - await this.getRecorder().start(); - await this.sendInfoStateEvent(VoiceBroadcastInfoState.Resumed); - } - - public toggle = async (): Promise => { - if (this.getState() === VoiceBroadcastInfoState.Paused) return this.resume(); - - if ( - ( - [VoiceBroadcastInfoState.Started, VoiceBroadcastInfoState.Resumed] as VoiceBroadcastRecordingState[] - ).includes(this.getState()) - ) { - return this.pause(); - } - }; - - public getState(): VoiceBroadcastRecordingState { - return this.state; - } - - private getRecorder(): VoiceBroadcastRecorder { - if (!this.recorder) { - this.recorder = createVoiceBroadcastRecorder(); - this.recorder.on(VoiceBroadcastRecorderEvent.ChunkRecorded, this.onChunkRecorded); - this.recorder.on(VoiceBroadcastRecorderEvent.CurrentChunkLengthUpdated, this.onCurrentChunkLengthUpdated); - } - - return this.recorder; - } - - public async destroy(): Promise { - if (this.recorder) { - this.recorder.stop(); - this.recorder.destroy(); - } - - this.infoEvent.off(MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction); - this.removeAllListeners(); - dis.unregister(this.dispatcherRef); - this.chunkEvents = new VoiceBroadcastChunkEvents(); - this.chunkRelationHelper.destroy(); - this.client.off(ClientEvent.Sync, this.reconnectedListener); - } - - private onBeforeRedaction = (): void => { - if (this.getState() !== VoiceBroadcastInfoState.Stopped) { - this.setState(VoiceBroadcastInfoState.Stopped); - // destroy cleans up everything - this.destroy(); - } - }; - - private onAction = (payload: ActionPayload): void => { - if (payload.action !== "call_state") return; - - // pause on any call action - this.pause(); - }; - - private setState(state: VoiceBroadcastRecordingState): void { - this.state = state; - this.emit(VoiceBroadcastRecordingEvent.StateChanged, this.state); - } - - private onCurrentChunkLengthUpdated = (currentChunkLength: number): void => { - this.setTimeLeft(this.maxLength - this.chunkEvents.getLengthSeconds() - currentChunkLength); - }; - - private onChunkRecorded = async (chunk: ChunkRecordedPayload): Promise => { - const uploadAndSendFn = async (): Promise => { - const { url, file } = await this.uploadFile(chunk); - await this.sendVoiceMessage(chunk, url, file); - }; - - await this.callWithRetry(uploadAndSendFn); - }; - - /** - * This function is called on connection errors. - * It sets the connection error state and stops the recorder. - */ - private async onConnectionError(): Promise { - this.playConnectionErrorAudioNotification().catch(() => { - // Error logged in playConnectionErrorAudioNotification(). - }); - await this.stopRecorder(false); - this.setState("connection_error"); - } - - private async playConnectionErrorAudioNotification(): Promise { - if (localNotificationsAreSilenced(this.client)) { - return; - } - - await this.backgroundAudio.pickFormatAndPlay("./media/error", ["mp3", "ogg"]); - } - - private async uploadFile(chunk: ChunkRecordedPayload): ReturnType { - return uploadFile( - this.client, - this.roomId, - new Blob([chunk.buffer], { - type: this.getRecorder().contentType, - }), - ); - } - - private async sendVoiceMessage(chunk: ChunkRecordedPayload, url?: string, file?: EncryptedFile): Promise { - /** - * Increment the last sequence number and use it for this message. - * Done outside of the sendMessageFn to get a scoped value. - * Also see {@link VoiceBroadcastRecording.sequence}. - */ - const sequence = ++this.sequence; - - const sendMessageFn = async (): Promise => { - const content = createVoiceMessageContent( - url, - this.getRecorder().contentType, - Math.round(chunk.length * 1000), - chunk.buffer.length, - file, - ); - content["m.relates_to"] = { - rel_type: RelationType.Reference, - event_id: this.infoEventId, - }; - (content)["io.element.voice_broadcast_chunk"] = { - sequence, - }; - - await this.client.sendMessage(this.roomId, content); - }; - - await this.callWithRetry(sendMessageFn); - } - - /** - * Sends an info state event with given state. - * On error stores a resend function and setState(state) in {@link toRetry} and - * sets the broadcast state to connection_error. - */ - private async sendInfoStateEvent(state: VoiceBroadcastInfoState): Promise { - const sendEventFn = async (): Promise => { - await this.client.sendStateEvent( - this.roomId, - VoiceBroadcastInfoEventType, - { - device_id: this.client.getDeviceId(), - state, - last_chunk_sequence: this.sequence, - ["m.relates_to"]: { - rel_type: RelationType.Reference, - event_id: this.infoEventId, - }, - } as VoiceBroadcastInfoEventContent, - this.client.getSafeUserId(), - ); - }; - - await this.callWithRetry(sendEventFn); - } - - /** - * Calls the function. - * On failure adds it to the retry list and triggers connection error. - * {@link toRetry} - * {@link onConnectionError} - */ - private async callWithRetry(retryAbleFn: () => Promise): Promise { - try { - await retryAbleFn(); - } catch { - this.toRetry.push(retryAbleFn); - this.onConnectionError(); - } - } - - private async stopRecorder(emit = true): Promise { - if (!this.recorder) { - return; - } - - try { - const lastChunk = await this.recorder.stop(); - if (lastChunk && emit) { - await this.onChunkRecorded(lastChunk); - } - } catch (err) { - logger.warn("error stopping voice broadcast recorder", err); - } - } -} diff --git a/src/voice-broadcast/stores/VoiceBroadcastPlaybacksStore.ts b/src/voice-broadcast/stores/VoiceBroadcastPlaybacksStore.ts deleted file mode 100644 index 69b8c21d903..00000000000 --- a/src/voice-broadcast/stores/VoiceBroadcastPlaybacksStore.ts +++ /dev/null @@ -1,113 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { MatrixClient, MatrixEvent, TypedEventEmitter } from "matrix-js-sdk/src/matrix"; - -import { - VoiceBroadcastPlayback, - VoiceBroadcastPlaybackEvent, - VoiceBroadcastPlaybackState, - VoiceBroadcastRecordingsStore, -} from ".."; -import { IDestroyable } from "../../utils/IDestroyable"; - -export enum VoiceBroadcastPlaybacksStoreEvent { - CurrentChanged = "current_changed", -} - -interface EventMap { - [VoiceBroadcastPlaybacksStoreEvent.CurrentChanged]: (recording: VoiceBroadcastPlayback | null) => void; -} - -/** - * This store manages VoiceBroadcastPlaybacks: - * - access the currently playing voice broadcast - * - ensures that only once broadcast is playing at a time - */ -export class VoiceBroadcastPlaybacksStore - extends TypedEventEmitter - implements IDestroyable -{ - private current: VoiceBroadcastPlayback | null = null; - - /** Playbacks indexed by their info event id. */ - private playbacks = new Map(); - - public constructor(private recordings: VoiceBroadcastRecordingsStore) { - super(); - } - - public setCurrent(current: VoiceBroadcastPlayback): void { - if (this.current === current) return; - - this.current = current; - this.addPlayback(current); - this.emit(VoiceBroadcastPlaybacksStoreEvent.CurrentChanged, current); - } - - public clearCurrent(): void { - if (this.current === null) return; - - this.current = null; - this.emit(VoiceBroadcastPlaybacksStoreEvent.CurrentChanged, null); - } - - public getCurrent(): VoiceBroadcastPlayback | null { - return this.current; - } - - public getByInfoEvent(infoEvent: MatrixEvent, client: MatrixClient): VoiceBroadcastPlayback { - const infoEventId = infoEvent.getId()!; - - if (!this.playbacks.has(infoEventId)) { - this.addPlayback(new VoiceBroadcastPlayback(infoEvent, client, this.recordings)); - } - - return this.playbacks.get(infoEventId)!; - } - - private addPlayback(playback: VoiceBroadcastPlayback): void { - const infoEventId = playback.infoEvent.getId()!; - - if (this.playbacks.has(infoEventId)) return; - - this.playbacks.set(infoEventId, playback); - playback.on(VoiceBroadcastPlaybackEvent.StateChanged, this.onPlaybackStateChanged); - } - - private onPlaybackStateChanged = (state: VoiceBroadcastPlaybackState, playback: VoiceBroadcastPlayback): void => { - switch (state) { - case VoiceBroadcastPlaybackState.Buffering: - case VoiceBroadcastPlaybackState.Playing: - this.pauseExcept(playback); - this.setCurrent(playback); - break; - case VoiceBroadcastPlaybackState.Stopped: - this.clearCurrent(); - break; - } - }; - - private pauseExcept(playbackNotToPause: VoiceBroadcastPlayback): void { - for (const playback of this.playbacks.values()) { - if (playback !== playbackNotToPause) { - playback.pause(); - } - } - } - - public destroy(): void { - this.removeAllListeners(); - - for (const playback of this.playbacks.values()) { - playback.off(VoiceBroadcastPlaybackEvent.StateChanged, this.onPlaybackStateChanged); - } - - this.playbacks = new Map(); - } -} diff --git a/src/voice-broadcast/stores/VoiceBroadcastPreRecordingStore.ts b/src/voice-broadcast/stores/VoiceBroadcastPreRecordingStore.ts deleted file mode 100644 index 3552930687b..00000000000 --- a/src/voice-broadcast/stores/VoiceBroadcastPreRecordingStore.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { TypedEventEmitter } from "matrix-js-sdk/src/matrix"; - -import { VoiceBroadcastPreRecording } from ".."; -import { IDestroyable } from "../../utils/IDestroyable"; - -export type VoiceBroadcastPreRecordingEvent = "changed"; - -interface EventMap { - changed: (preRecording: VoiceBroadcastPreRecording | null) => void; -} - -export class VoiceBroadcastPreRecordingStore - extends TypedEventEmitter - implements IDestroyable -{ - private current: VoiceBroadcastPreRecording | null = null; - - public setCurrent(current: VoiceBroadcastPreRecording): void { - if (this.current === current) return; - - if (this.current) { - this.current.off("dismiss", this.onCancel); - } - - this.current = current; - current.on("dismiss", this.onCancel); - this.emit("changed", current); - } - - public clearCurrent(): void { - if (this.current === null) return; - - this.current.off("dismiss", this.onCancel); - this.current = null; - this.emit("changed", null); - } - - public getCurrent(): VoiceBroadcastPreRecording | null { - return this.current; - } - - public destroy(): void { - this.removeAllListeners(); - - if (this.current) { - this.current.off("dismiss", this.onCancel); - } - } - - private onCancel = (voiceBroadcastPreRecording: VoiceBroadcastPreRecording): void => { - if (this.current === voiceBroadcastPreRecording) { - this.clearCurrent(); - } - }; -} diff --git a/src/voice-broadcast/stores/VoiceBroadcastRecordingsStore.ts b/src/voice-broadcast/stores/VoiceBroadcastRecordingsStore.ts deleted file mode 100644 index ff0f67b910e..00000000000 --- a/src/voice-broadcast/stores/VoiceBroadcastRecordingsStore.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022, 2023 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { MatrixClient, MatrixEvent, TypedEventEmitter } from "matrix-js-sdk/src/matrix"; - -import { - VoiceBroadcastInfoState, - VoiceBroadcastRecording, - VoiceBroadcastRecordingEvent, - VoiceBroadcastRecordingState, -} from ".."; - -export enum VoiceBroadcastRecordingsStoreEvent { - CurrentChanged = "current_changed", -} - -interface EventMap { - [VoiceBroadcastRecordingsStoreEvent.CurrentChanged]: (recording: VoiceBroadcastRecording | null) => void; -} - -/** - * This store provides access to the current and specific Voice Broadcast recordings. - */ -export class VoiceBroadcastRecordingsStore extends TypedEventEmitter { - private current: VoiceBroadcastRecording | null = null; - private recordings = new Map(); - - public constructor() { - super(); - } - - public setCurrent(current: VoiceBroadcastRecording): void { - if (this.current === current) return; - - const infoEventId = current.infoEvent.getId(); - - if (!infoEventId) { - throw new Error("Got broadcast info event without Id"); - } - - if (this.current) { - this.current.off(VoiceBroadcastRecordingEvent.StateChanged, this.onCurrentStateChanged); - } - - this.current = current; - this.current.on(VoiceBroadcastRecordingEvent.StateChanged, this.onCurrentStateChanged); - this.recordings.set(infoEventId, current); - this.emit(VoiceBroadcastRecordingsStoreEvent.CurrentChanged, current); - } - - public getCurrent(): VoiceBroadcastRecording | null { - return this.current; - } - - public hasCurrent(): boolean { - return this.current !== null; - } - - public clearCurrent(): void { - if (!this.current) return; - - this.current.off(VoiceBroadcastRecordingEvent.StateChanged, this.onCurrentStateChanged); - this.current = null; - this.emit(VoiceBroadcastRecordingsStoreEvent.CurrentChanged, null); - } - - public getByInfoEvent(infoEvent: MatrixEvent, client: MatrixClient): VoiceBroadcastRecording { - const infoEventId = infoEvent.getId(); - - if (!infoEventId) { - throw new Error("Got broadcast info event without Id"); - } - - const recording = this.recordings.get(infoEventId) || new VoiceBroadcastRecording(infoEvent, client); - this.recordings.set(infoEventId, recording); - return recording; - } - - private onCurrentStateChanged = (state: VoiceBroadcastRecordingState): void => { - if (state === VoiceBroadcastInfoState.Stopped) { - this.clearCurrent(); - } - }; -} diff --git a/src/voice-broadcast/types.ts b/src/voice-broadcast/types.ts deleted file mode 100644 index 8191a0be162..00000000000 --- a/src/voice-broadcast/types.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { RelationType } from "matrix-js-sdk/src/matrix"; - -export const VoiceBroadcastInfoEventType = "io.element.voice_broadcast_info"; -export const VoiceBroadcastChunkEventType = "io.element.voice_broadcast_chunk"; - -export type VoiceBroadcastLiveness = "live" | "not-live" | "grey"; - -export enum VoiceBroadcastInfoState { - Started = "started", - Paused = "paused", - Resumed = "resumed", - Stopped = "stopped", -} - -export interface VoiceBroadcastInfoEventContent { - device_id: string; - state: VoiceBroadcastInfoState; - chunk_length?: number; - last_chunk_sequence?: number; - ["m.relates_to"]?: { - rel_type: RelationType; - event_id: string; - }; -} diff --git a/src/voice-broadcast/utils/VoiceBroadcastChunkEvents.ts b/src/voice-broadcast/utils/VoiceBroadcastChunkEvents.ts deleted file mode 100644 index 039749cf8d0..00000000000 --- a/src/voice-broadcast/utils/VoiceBroadcastChunkEvents.ts +++ /dev/null @@ -1,147 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { MatrixEvent } from "matrix-js-sdk/src/matrix"; - -import { VoiceBroadcastChunkEventType } from ".."; - -/** - * Voice broadcast chunk collection. - * Orders chunks by sequence (if available) or timestamp. - */ -export class VoiceBroadcastChunkEvents { - private events: MatrixEvent[] = []; - - public getEvents(): MatrixEvent[] { - return [...this.events]; - } - - public getNext(event: MatrixEvent): MatrixEvent | undefined { - return this.events[this.events.indexOf(event) + 1]; - } - - public addEvent(event: MatrixEvent): void { - if (this.addOrReplaceEvent(event)) { - this.sort(); - } - } - - public addEvents(events: MatrixEvent[]): void { - const atLeastOneNew = events.reduce((newSoFar: boolean, event: MatrixEvent): boolean => { - return this.addOrReplaceEvent(event) || newSoFar; - }, false); - - if (atLeastOneNew) { - this.sort(); - } - } - - public includes(event: MatrixEvent): boolean { - return !!this.events.find((e) => this.equalByTxnIdOrId(event, e)); - } - - /** - * @returns {number} Length in milliseconds - */ - public getLength(): number { - return this.events.reduce((length: number, event: MatrixEvent) => { - return length + this.calculateChunkLength(event); - }, 0); - } - - public getLengthSeconds(): number { - return this.getLength() / 1000; - } - - /** - * Returns the accumulated length to (excl.) a chunk event. - */ - public getLengthTo(event: MatrixEvent): number { - let length = 0; - - for (let i = 0; i < this.events.indexOf(event); i++) { - length += this.calculateChunkLength(this.events[i]); - } - - return length; - } - - public findByTime(time: number): MatrixEvent | null { - let lengthSoFar = 0; - - for (let i = 0; i < this.events.length; i++) { - lengthSoFar += this.calculateChunkLength(this.events[i]); - - if (lengthSoFar >= time) { - return this.events[i]; - } - } - - return null; - } - - public isLast(event: MatrixEvent): boolean { - return this.events.indexOf(event) >= this.events.length - 1; - } - - public getSequenceForEvent(event: MatrixEvent): number | null { - const sequence = parseInt(event.getContent()?.[VoiceBroadcastChunkEventType]?.sequence, 10); - if (!isNaN(sequence)) return sequence; - - if (this.events.includes(event)) return this.events.indexOf(event) + 1; - - return null; - } - - public getNumberOfEvents(): number { - return this.events.length; - } - - private calculateChunkLength(event: MatrixEvent): number { - return event.getContent()?.["org.matrix.msc1767.audio"]?.duration || event.getContent()?.info?.duration || 0; - } - - private addOrReplaceEvent = (event: MatrixEvent): boolean => { - this.events = this.events.filter((e) => !this.equalByTxnIdOrId(event, e)); - this.events.push(event); - return true; - }; - - private equalByTxnIdOrId(eventA: MatrixEvent, eventB: MatrixEvent): boolean { - return ( - (eventA.getTxnId() && eventB.getTxnId() && eventA.getTxnId() === eventB.getTxnId()) || - eventA.getId() === eventB.getId() - ); - } - - /** - * Sort by sequence, if available for all events. - * Else fall back to timestamp. - */ - private sort(): void { - const compareFn = this.allHaveSequence() ? this.compareBySequence : this.compareByTimestamp; - this.events.sort(compareFn); - } - - private compareBySequence = (a: MatrixEvent, b: MatrixEvent): number => { - const aSequence = a.getContent()?.[VoiceBroadcastChunkEventType]?.sequence || 0; - const bSequence = b.getContent()?.[VoiceBroadcastChunkEventType]?.sequence || 0; - return aSequence - bSequence; - }; - - private compareByTimestamp = (a: MatrixEvent, b: MatrixEvent): number => { - return a.getTs() - b.getTs(); - }; - - private allHaveSequence(): boolean { - return !this.events.some((event: MatrixEvent) => { - const sequence = event.getContent()?.[VoiceBroadcastChunkEventType]?.sequence; - return parseInt(sequence, 10) !== sequence; - }); - } -} diff --git a/src/voice-broadcast/utils/VoiceBroadcastResumer.ts b/src/voice-broadcast/utils/VoiceBroadcastResumer.ts deleted file mode 100644 index 963b6ef3a62..00000000000 --- a/src/voice-broadcast/utils/VoiceBroadcastResumer.ts +++ /dev/null @@ -1,90 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { ClientEvent, MatrixClient, MatrixEvent, RelationType, Room, SyncState } from "matrix-js-sdk/src/matrix"; - -import { VoiceBroadcastInfoEventContent, VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from ".."; -import { IDestroyable } from "../../utils/IDestroyable"; -import { findRoomLiveVoiceBroadcastFromUserAndDevice } from "./findRoomLiveVoiceBroadcastFromUserAndDevice"; - -/** - * Handles voice broadcasts on app resume (after logging in, reload, crash…). - */ -export class VoiceBroadcastResumer implements IDestroyable { - public constructor(private client: MatrixClient) { - if (client.isInitialSyncComplete()) { - this.resume(); - } else { - // wait for initial sync - client.on(ClientEvent.Sync, this.onClientSync); - } - } - - private onClientSync = (): void => { - if (this.client.getSyncState() === SyncState.Syncing) { - this.client.off(ClientEvent.Sync, this.onClientSync); - this.resume(); - } - }; - - private resume(): void { - const userId = this.client.getUserId(); - const deviceId = this.client.getDeviceId(); - - if (!userId || !deviceId) { - // Resuming a voice broadcast only makes sense if there is a user. - return; - } - - this.client.getRooms().forEach((room: Room) => { - const infoEvent = findRoomLiveVoiceBroadcastFromUserAndDevice(room, userId, deviceId); - - if (infoEvent) { - // Found a live broadcast event from current device; stop it. - // Stopping it is a temporary solution (see PSF-1669). - this.sendStopVoiceBroadcastStateEvent(infoEvent); - return false; - } - }); - } - - private sendStopVoiceBroadcastStateEvent(infoEvent: MatrixEvent): void { - const userId = this.client.getUserId(); - const deviceId = this.client.getDeviceId(); - const roomId = infoEvent.getRoomId(); - - if (!userId || !deviceId || !roomId) { - // We can only send a state event if we know all the IDs. - return; - } - - const content: VoiceBroadcastInfoEventContent = { - device_id: deviceId, - state: VoiceBroadcastInfoState.Stopped, - }; - - // all events should reference the started event - const referencedEventId = - infoEvent.getContent()?.state === VoiceBroadcastInfoState.Started - ? infoEvent.getId() - : infoEvent.getContent()?.["m.relates_to"]?.event_id; - - if (referencedEventId) { - content["m.relates_to"] = { - rel_type: RelationType.Reference, - event_id: referencedEventId, - }; - } - - this.client.sendStateEvent(roomId, VoiceBroadcastInfoEventType, content, userId); - } - - public destroy(): void { - this.client.off(ClientEvent.Sync, this.onClientSync); - } -} diff --git a/src/voice-broadcast/utils/checkVoiceBroadcastPreConditions.tsx b/src/voice-broadcast/utils/checkVoiceBroadcastPreConditions.tsx deleted file mode 100644 index ae96bc0b14e..00000000000 --- a/src/voice-broadcast/utils/checkVoiceBroadcastPreConditions.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; -import { MatrixClient, Room, SyncState } from "matrix-js-sdk/src/matrix"; - -import { hasRoomLiveVoiceBroadcast, VoiceBroadcastInfoEventType, VoiceBroadcastRecordingsStore } from ".."; -import InfoDialog from "../../components/views/dialogs/InfoDialog"; -import { _t } from "../../languageHandler"; -import Modal from "../../Modal"; - -const showAlreadyRecordingDialog = (): void => { - Modal.createDialog(InfoDialog, { - title: _t("voice_broadcast|failed_already_recording_title"), - description:

{_t("voice_broadcast|failed_already_recording_description")}

, - hasCloseButton: true, - }); -}; - -const showInsufficientPermissionsDialog = (): void => { - Modal.createDialog(InfoDialog, { - title: _t("voice_broadcast|failed_insufficient_permission_title"), - description:

{_t("voice_broadcast|failed_insufficient_permission_description")}

, - hasCloseButton: true, - }); -}; - -const showOthersAlreadyRecordingDialog = (): void => { - Modal.createDialog(InfoDialog, { - title: _t("voice_broadcast|failed_others_already_recording_title"), - description:

{_t("voice_broadcast|failed_others_already_recording_description")}

, - hasCloseButton: true, - }); -}; - -const showNoConnectionDialog = (): void => { - Modal.createDialog(InfoDialog, { - title: _t("voice_broadcast|failed_no_connection_title"), - description:

{_t("voice_broadcast|failed_no_connection_description")}

, - hasCloseButton: true, - }); -}; - -export const checkVoiceBroadcastPreConditions = async ( - room: Room, - client: MatrixClient, - recordingsStore: VoiceBroadcastRecordingsStore, -): Promise => { - if (recordingsStore.getCurrent()) { - showAlreadyRecordingDialog(); - return false; - } - - const currentUserId = client.getUserId(); - - if (!currentUserId) return false; - - if (!room.currentState.maySendStateEvent(VoiceBroadcastInfoEventType, currentUserId)) { - showInsufficientPermissionsDialog(); - return false; - } - - if (client.getSyncState() === SyncState.Error) { - showNoConnectionDialog(); - return false; - } - - const { hasBroadcast, startedByUser } = await hasRoomLiveVoiceBroadcast(client, room, currentUserId); - - if (hasBroadcast && startedByUser) { - showAlreadyRecordingDialog(); - return false; - } - - if (hasBroadcast) { - showOthersAlreadyRecordingDialog(); - return false; - } - - return true; -}; diff --git a/src/voice-broadcast/utils/cleanUpBroadcasts.ts b/src/voice-broadcast/utils/cleanUpBroadcasts.ts deleted file mode 100644 index 50133274b0d..00000000000 --- a/src/voice-broadcast/utils/cleanUpBroadcasts.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2023 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { SdkContextClass } from "../../contexts/SDKContext"; - -export const cleanUpBroadcasts = async (stores: SdkContextClass): Promise => { - stores.voiceBroadcastPlaybacksStore.getCurrent()?.stop(); - stores.voiceBroadcastPlaybacksStore.clearCurrent(); - - await stores.voiceBroadcastRecordingsStore.getCurrent()?.stop(); - stores.voiceBroadcastRecordingsStore.clearCurrent(); - - stores.voiceBroadcastPreRecordingStore.getCurrent()?.cancel(); - stores.voiceBroadcastPreRecordingStore.clearCurrent(); -}; diff --git a/src/voice-broadcast/utils/determineVoiceBroadcastLiveness.ts b/src/voice-broadcast/utils/determineVoiceBroadcastLiveness.ts deleted file mode 100644 index 8d9660c572c..00000000000 --- a/src/voice-broadcast/utils/determineVoiceBroadcastLiveness.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { VoiceBroadcastInfoState, VoiceBroadcastLiveness } from ".."; - -const stateLivenessMap: Map = new Map([ - ["started", "live"], - ["resumed", "live"], - ["paused", "grey"], - ["stopped", "not-live"], -] as Array<[VoiceBroadcastInfoState, VoiceBroadcastLiveness]>); - -export const determineVoiceBroadcastLiveness = (infoState: VoiceBroadcastInfoState): VoiceBroadcastLiveness => { - return stateLivenessMap.get(infoState) ?? "not-live"; -}; diff --git a/src/voice-broadcast/utils/doClearCurrentVoiceBroadcastPlaybackIfStopped.ts b/src/voice-broadcast/utils/doClearCurrentVoiceBroadcastPlaybackIfStopped.ts deleted file mode 100644 index ef0e1e7aed1..00000000000 --- a/src/voice-broadcast/utils/doClearCurrentVoiceBroadcastPlaybackIfStopped.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { VoiceBroadcastPlaybacksStore, VoiceBroadcastPlaybackState } from ".."; - -export const doClearCurrentVoiceBroadcastPlaybackIfStopped = ( - voiceBroadcastPlaybacksStore: VoiceBroadcastPlaybacksStore, -): void => { - if (voiceBroadcastPlaybacksStore.getCurrent()?.getState() === VoiceBroadcastPlaybackState.Stopped) { - // clear current if stopped - return; - } -}; diff --git a/src/voice-broadcast/utils/doMaybeSetCurrentVoiceBroadcastPlayback.ts b/src/voice-broadcast/utils/doMaybeSetCurrentVoiceBroadcastPlayback.ts deleted file mode 100644 index 2ec4ab185df..00000000000 --- a/src/voice-broadcast/utils/doMaybeSetCurrentVoiceBroadcastPlayback.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { MatrixClient, Room } from "matrix-js-sdk/src/matrix"; - -import { - hasRoomLiveVoiceBroadcast, - VoiceBroadcastPlaybacksStore, - VoiceBroadcastPlaybackState, - VoiceBroadcastRecordingsStore, -} from ".."; - -/** - * When a live voice broadcast is in the room and - * another voice broadcast is not currently being listened to or recorded - * the live broadcast in the room is set as the current broadcast to listen to. - * When there is no live broadcast in the room: clear current broadcast. - * - * @param {Room} room The room to check for a live voice broadcast - * @param {MatrixClient} client - * @param {VoiceBroadcastPlaybacksStore} voiceBroadcastPlaybacksStore - * @param {VoiceBroadcastRecordingsStore} voiceBroadcastRecordingsStore - */ -export const doMaybeSetCurrentVoiceBroadcastPlayback = async ( - room: Room, - client: MatrixClient, - voiceBroadcastPlaybacksStore: VoiceBroadcastPlaybacksStore, - voiceBroadcastRecordingsStore: VoiceBroadcastRecordingsStore, -): Promise => { - // do not disturb the current recording - if (voiceBroadcastRecordingsStore.hasCurrent()) return; - - const currentPlayback = voiceBroadcastPlaybacksStore.getCurrent(); - - if (currentPlayback && currentPlayback.getState() !== VoiceBroadcastPlaybackState.Stopped) { - // do not disturb the current playback - return; - } - - const { infoEvent } = await hasRoomLiveVoiceBroadcast(client, room); - - if (infoEvent) { - // live broadcast in the room + no recording + not listening yet: set the current broadcast - const voiceBroadcastPlayback = voiceBroadcastPlaybacksStore.getByInfoEvent(infoEvent, client); - voiceBroadcastPlaybacksStore.setCurrent(voiceBroadcastPlayback); - return; - } - - // no broadcast; not listening: clear current - voiceBroadcastPlaybacksStore.clearCurrent(); -}; diff --git a/src/voice-broadcast/utils/findRoomLiveVoiceBroadcastFromUserAndDevice.ts b/src/voice-broadcast/utils/findRoomLiveVoiceBroadcastFromUserAndDevice.ts deleted file mode 100644 index fbd02d44fb7..00000000000 --- a/src/voice-broadcast/utils/findRoomLiveVoiceBroadcastFromUserAndDevice.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; - -import { VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from ".."; - -export const findRoomLiveVoiceBroadcastFromUserAndDevice = ( - room: Room, - userId: string, - deviceId: string, -): MatrixEvent | null => { - const stateEvent = room.currentState.getStateEvents(VoiceBroadcastInfoEventType, userId); - - // no broadcast from that user - if (!stateEvent) return null; - - const content = stateEvent.getContent() || {}; - - // stopped broadcast - if (content.state === VoiceBroadcastInfoState.Stopped) return null; - - return content.device_id === deviceId ? stateEvent : null; -}; diff --git a/src/voice-broadcast/utils/getChunkLength.ts b/src/voice-broadcast/utils/getChunkLength.ts deleted file mode 100644 index b3fe2f557d4..00000000000 --- a/src/voice-broadcast/utils/getChunkLength.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import SdkConfig, { DEFAULTS } from "../../SdkConfig"; -import { Features } from "../../settings/Settings"; -import SettingsStore from "../../settings/SettingsStore"; - -/** - * Returns the target chunk length for voice broadcasts: - * - If {@see Features.VoiceBroadcastForceSmallChunks} is enabled uses 15s chunk length - * - Otherwise to get the value from the voice_broadcast.chunk_length config - * - If that fails from DEFAULTS - * - If that fails fall back to 120 (two minutes) - */ -export const getChunkLength = (): number => { - if (SettingsStore.getValue(Features.VoiceBroadcastForceSmallChunks)) return 15; - return SdkConfig.get("voice_broadcast")?.chunk_length || DEFAULTS.voice_broadcast?.chunk_length || 120; -}; diff --git a/src/voice-broadcast/utils/getMaxBroadcastLength.ts b/src/voice-broadcast/utils/getMaxBroadcastLength.ts deleted file mode 100644 index e5df83ef059..00000000000 --- a/src/voice-broadcast/utils/getMaxBroadcastLength.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import SdkConfig, { DEFAULTS } from "../../SdkConfig"; - -/** - * Returns the max length for voice broadcasts: - * - Tries to get the value from the voice_broadcast.max_length config - * - If that fails from DEFAULTS - * - If that fails fall back to four hours - */ -export const getMaxBroadcastLength = (): number => { - return SdkConfig.get("voice_broadcast")?.max_length || DEFAULTS.voice_broadcast?.max_length || 4 * 60 * 60; -}; diff --git a/src/voice-broadcast/utils/hasRoomLiveVoiceBroadcast.ts b/src/voice-broadcast/utils/hasRoomLiveVoiceBroadcast.ts deleted file mode 100644 index 939eb1a0c27..00000000000 --- a/src/voice-broadcast/utils/hasRoomLiveVoiceBroadcast.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; - -import { retrieveStartedInfoEvent, VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from ".."; -import { asyncEvery } from "../../utils/arrays"; - -interface Result { - // whether there is a live broadcast in the room - hasBroadcast: boolean; - // info event of any live broadcast in the room - infoEvent: MatrixEvent | null; - // whether the broadcast was started by the user - startedByUser: boolean; -} - -export const hasRoomLiveVoiceBroadcast = async (client: MatrixClient, room: Room, userId?: string): Promise => { - let hasBroadcast = false; - let startedByUser = false; - let infoEvent: MatrixEvent | null = null; - - const stateEvents = room.currentState.getStateEvents(VoiceBroadcastInfoEventType); - await asyncEvery(stateEvents, async (event: MatrixEvent) => { - const state = event.getContent()?.state; - - if (state && state !== VoiceBroadcastInfoState.Stopped) { - const startEvent = await retrieveStartedInfoEvent(event, client); - - // skip if started voice broadcast event is redacted - if (startEvent?.isRedacted()) return true; - - hasBroadcast = true; - infoEvent = startEvent; - - // state key = sender's MXID - if (event.getStateKey() === userId) { - startedByUser = true; - // break here, because more than true / true is not possible - return false; - } - } - - return true; - }); - - return { - hasBroadcast, - infoEvent, - startedByUser, - }; -}; diff --git a/src/voice-broadcast/utils/isRelatedToVoiceBroadcast.ts b/src/voice-broadcast/utils/isRelatedToVoiceBroadcast.ts deleted file mode 100644 index eca8f890e05..00000000000 --- a/src/voice-broadcast/utils/isRelatedToVoiceBroadcast.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { MatrixClient, MatrixEvent, RelationType } from "matrix-js-sdk/src/matrix"; - -import { VoiceBroadcastInfoEventType } from "../types"; - -export const isRelatedToVoiceBroadcast = (event: MatrixEvent, client: MatrixClient): boolean => { - const relation = event.getRelation(); - - return ( - relation?.rel_type === RelationType.Reference && - !!relation.event_id && - client.getRoom(event.getRoomId())?.findEventById(relation.event_id)?.getType() === VoiceBroadcastInfoEventType - ); -}; diff --git a/src/voice-broadcast/utils/isVoiceBroadcastStartedEvent.ts b/src/voice-broadcast/utils/isVoiceBroadcastStartedEvent.ts deleted file mode 100644 index fffe45850e9..00000000000 --- a/src/voice-broadcast/utils/isVoiceBroadcastStartedEvent.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { MatrixEvent } from "matrix-js-sdk/src/matrix"; - -import { VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from "../types"; - -export const isVoiceBroadcastStartedEvent = (event: MatrixEvent): boolean => { - return ( - event.getType() === VoiceBroadcastInfoEventType && event.getContent()?.state === VoiceBroadcastInfoState.Started - ); -}; diff --git a/src/voice-broadcast/utils/pauseNonLiveBroadcastFromOtherRoom.ts b/src/voice-broadcast/utils/pauseNonLiveBroadcastFromOtherRoom.ts deleted file mode 100644 index e854ba9bac9..00000000000 --- a/src/voice-broadcast/utils/pauseNonLiveBroadcastFromOtherRoom.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { Room } from "matrix-js-sdk/src/matrix"; - -import { VoiceBroadcastPlaybacksStore } from ".."; - -export const pauseNonLiveBroadcastFromOtherRoom = ( - room: Room, - voiceBroadcastPlaybacksStore: VoiceBroadcastPlaybacksStore, -): void => { - const playingBroadcast = voiceBroadcastPlaybacksStore.getCurrent(); - - if ( - !playingBroadcast || - playingBroadcast?.getLiveness() === "live" || - playingBroadcast?.infoEvent.getRoomId() === room.roomId - ) { - return; - } - - voiceBroadcastPlaybacksStore.clearCurrent(); - playingBroadcast.pause(); -}; diff --git a/src/voice-broadcast/utils/retrieveStartedInfoEvent.ts b/src/voice-broadcast/utils/retrieveStartedInfoEvent.ts deleted file mode 100644 index cc5be144c98..00000000000 --- a/src/voice-broadcast/utils/retrieveStartedInfoEvent.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix"; - -import { VoiceBroadcastInfoState } from ".."; - -export const retrieveStartedInfoEvent = async ( - event: MatrixEvent, - client: MatrixClient, -): Promise => { - // started event passed as argument - if (event.getContent()?.state === VoiceBroadcastInfoState.Started) return event; - - const relatedEventId = event.getRelation()?.event_id; - - // no related event - if (!relatedEventId) return null; - - const roomId = event.getRoomId() || ""; - const relatedEventFromRoom = client.getRoom(roomId)?.findEventById(relatedEventId); - - // event found - if (relatedEventFromRoom) return relatedEventFromRoom; - - try { - const relatedEventData = await client.fetchRoomEvent(roomId, relatedEventId); - return new MatrixEvent(relatedEventData); - } catch {} - - return null; -}; diff --git a/src/voice-broadcast/utils/setUpVoiceBroadcastPreRecording.ts b/src/voice-broadcast/utils/setUpVoiceBroadcastPreRecording.ts deleted file mode 100644 index c50607c58fb..00000000000 --- a/src/voice-broadcast/utils/setUpVoiceBroadcastPreRecording.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { MatrixClient, Room } from "matrix-js-sdk/src/matrix"; - -import { - checkVoiceBroadcastPreConditions, - VoiceBroadcastPlaybacksStore, - VoiceBroadcastPreRecording, - VoiceBroadcastPreRecordingStore, - VoiceBroadcastRecordingsStore, -} from ".."; - -export const setUpVoiceBroadcastPreRecording = async ( - room: Room, - client: MatrixClient, - playbacksStore: VoiceBroadcastPlaybacksStore, - recordingsStore: VoiceBroadcastRecordingsStore, - preRecordingStore: VoiceBroadcastPreRecordingStore, -): Promise => { - if (!(await checkVoiceBroadcastPreConditions(room, client, recordingsStore))) { - return null; - } - - const userId = client.getUserId(); - if (!userId) return null; - - const sender = room.getMember(userId); - if (!sender) return null; - - // pause and clear current playback (if any) - playbacksStore.getCurrent()?.pause(); - playbacksStore.clearCurrent(); - - const preRecording = new VoiceBroadcastPreRecording(room, sender, client, playbacksStore, recordingsStore); - preRecordingStore.setCurrent(preRecording); - return preRecording; -}; diff --git a/src/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastRecordingTile.ts b/src/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastRecordingTile.ts deleted file mode 100644 index d729d9e1ca1..00000000000 --- a/src/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastRecordingTile.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix"; - -import { VoiceBroadcastInfoEventContent, VoiceBroadcastInfoState } from ".."; - -export const shouldDisplayAsVoiceBroadcastRecordingTile = ( - state: VoiceBroadcastInfoState, - client: MatrixClient, - event: MatrixEvent, -): boolean => { - const userId = client.getUserId(); - return ( - !!userId && - userId === event.getSender() && - client.getDeviceId() === event.getContent()?.device_id && - state !== VoiceBroadcastInfoState.Stopped - ); -}; diff --git a/src/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastStoppedText.ts b/src/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastStoppedText.ts deleted file mode 100644 index 2179aff3b76..00000000000 --- a/src/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastStoppedText.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { MatrixEvent } from "matrix-js-sdk/src/matrix"; - -import { VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from ".."; - -export const shouldDisplayAsVoiceBroadcastStoppedText = (event: MatrixEvent): boolean => - event.getType() === VoiceBroadcastInfoEventType && - event.getContent()?.state === VoiceBroadcastInfoState.Stopped && - !event.isRedacted(); diff --git a/src/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastTile.ts b/src/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastTile.ts deleted file mode 100644 index 9a51b33c9a6..00000000000 --- a/src/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastTile.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { MatrixEvent } from "matrix-js-sdk/src/matrix"; - -import { VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from ".."; - -export const shouldDisplayAsVoiceBroadcastTile = (event: MatrixEvent): boolean => - event.getType?.() === VoiceBroadcastInfoEventType && - (event.getContent?.()?.state === VoiceBroadcastInfoState.Started || event.isRedacted()); diff --git a/src/voice-broadcast/utils/showCantStartACallDialog.tsx b/src/voice-broadcast/utils/showCantStartACallDialog.tsx deleted file mode 100644 index eeeb86ee07b..00000000000 --- a/src/voice-broadcast/utils/showCantStartACallDialog.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; - -import InfoDialog from "../../components/views/dialogs/InfoDialog"; -import { _t } from "../../languageHandler"; -import Modal from "../../Modal"; - -export const showCantStartACallDialog = (): void => { - Modal.createDialog(InfoDialog, { - title: _t("voip|failed_call_live_broadcast_title"), - description:

{_t("voip|failed_call_live_broadcast_description")}

, - hasCloseButton: true, - }); -}; diff --git a/src/voice-broadcast/utils/startNewVoiceBroadcastRecording.ts b/src/voice-broadcast/utils/startNewVoiceBroadcastRecording.ts deleted file mode 100644 index f0c5a919329..00000000000 --- a/src/voice-broadcast/utils/startNewVoiceBroadcastRecording.ts +++ /dev/null @@ -1,92 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { ISendEventResponse, MatrixClient, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix"; -import { defer } from "matrix-js-sdk/src/utils"; - -import { - VoiceBroadcastInfoEventContent, - VoiceBroadcastInfoEventType, - VoiceBroadcastInfoState, - VoiceBroadcastRecordingsStore, - VoiceBroadcastRecording, - getChunkLength, - VoiceBroadcastPlaybacksStore, -} from ".."; -import { checkVoiceBroadcastPreConditions } from "./checkVoiceBroadcastPreConditions"; - -const startBroadcast = async ( - room: Room, - client: MatrixClient, - recordingsStore: VoiceBroadcastRecordingsStore, -): Promise => { - const { promise, resolve, reject } = defer(); - - const userId = client.getUserId(); - - if (!userId) { - reject("unable to start voice broadcast if current user is unknown"); - return promise; - } - - let result: ISendEventResponse | null = null; - - const onRoomStateEvents = (): void => { - if (!result) return; - - const voiceBroadcastEvent = room.currentState.getStateEvents(VoiceBroadcastInfoEventType, userId); - - if (voiceBroadcastEvent?.getId() === result.event_id) { - room.off(RoomStateEvent.Events, onRoomStateEvents); - const recording = new VoiceBroadcastRecording(voiceBroadcastEvent, client); - recordingsStore.setCurrent(recording); - recording.start(); - resolve(recording); - } - }; - - room.on(RoomStateEvent.Events, onRoomStateEvents); - - // XXX Michael W: refactor to live event - result = await client.sendStateEvent( - room.roomId, - VoiceBroadcastInfoEventType, - { - device_id: client.getDeviceId(), - state: VoiceBroadcastInfoState.Started, - chunk_length: getChunkLength(), - } as VoiceBroadcastInfoEventContent, - userId, - ); - - return promise; -}; - -/** - * Starts a new Voice Broadcast Recording, if - * - the user has the permissions to do so in the room - * - the user is not already recording a voice broadcast - * - there is no other broadcast being recorded in the room, yet - * Sends a voice_broadcast_info state event and waits for the event to actually appear in the room state. - */ -export const startNewVoiceBroadcastRecording = async ( - room: Room, - client: MatrixClient, - playbacksStore: VoiceBroadcastPlaybacksStore, - recordingsStore: VoiceBroadcastRecordingsStore, -): Promise => { - if (!(await checkVoiceBroadcastPreConditions(room, client, recordingsStore))) { - return null; - } - - // pause and clear current playback (if any) - playbacksStore.getCurrent()?.pause(); - playbacksStore.clearCurrent(); - - return startBroadcast(room, client, recordingsStore); -}; diff --git a/src/voice-broadcast/utils/textForVoiceBroadcastStoppedEvent.tsx b/src/voice-broadcast/utils/textForVoiceBroadcastStoppedEvent.tsx deleted file mode 100644 index bc2aa412a5a..00000000000 --- a/src/voice-broadcast/utils/textForVoiceBroadcastStoppedEvent.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React, { ReactNode } from "react"; -import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix"; - -import { MatrixClientPeg } from "../../MatrixClientPeg"; -import AccessibleButton from "../../components/views/elements/AccessibleButton"; -import { highlightEvent } from "../../utils/EventUtils"; -import { _t } from "../../languageHandler"; -import { getSenderName } from "../../utils/event/getSenderName"; - -export const textForVoiceBroadcastStoppedEvent = (event: MatrixEvent, client: MatrixClient): (() => ReactNode) => { - return (): ReactNode => { - const ownUserId = MatrixClientPeg.get()?.getUserId(); - const startEventId = event.getRelation()?.event_id; - const roomId = event.getRoomId(); - - const templateTags = { - a: (text: string) => - startEventId && roomId ? ( - highlightEvent(roomId, startEventId)}> - {text} - - ) : ( - text - ), - }; - - if (ownUserId && ownUserId === event.getSender()) { - return _t("timeline|io.element.voice_broadcast_info|you", {}, templateTags); - } - - return _t("timeline|io.element.voice_broadcast_info|user", { senderName: getSenderName(event) }, templateTags); - }; -}; diff --git a/src/voice-broadcast/utils/textForVoiceBroadcastStoppedEventWithoutLink.ts b/src/voice-broadcast/utils/textForVoiceBroadcastStoppedEventWithoutLink.ts deleted file mode 100644 index 13d7f47c484..00000000000 --- a/src/voice-broadcast/utils/textForVoiceBroadcastStoppedEventWithoutLink.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { MatrixEvent } from "matrix-js-sdk/src/matrix"; - -import { _t } from "../../languageHandler"; -import { MatrixClientPeg } from "../../MatrixClientPeg"; -import { getSenderName } from "../../utils/event/getSenderName"; - -export const textForVoiceBroadcastStoppedEventWithoutLink = (event: MatrixEvent): string => { - const ownUserId = MatrixClientPeg.get()?.getUserId(); - - if (ownUserId && ownUserId === event.getSender()) { - return _t("event_preview|io.element.voice_broadcast_info|you", {}); - } - - return _t("event_preview|io.element.voice_broadcast_info|user", { senderName: getSenderName(event) }); -}; diff --git a/test/unit-tests/LegacyCallHandler-test.ts b/test/unit-tests/LegacyCallHandler-test.ts index c3e64dcf941..476d89a1f0f 100644 --- a/test/unit-tests/LegacyCallHandler-test.ts +++ b/test/unit-tests/LegacyCallHandler-test.ts @@ -39,10 +39,6 @@ import { Action } from "../../src/dispatcher/actions"; import { getFunctionalMembers } from "../../src/utils/room/getFunctionalMembers"; import SettingsStore from "../../src/settings/SettingsStore"; import { UIFeature } from "../../src/settings/UIFeature"; -import { VoiceBroadcastInfoState, VoiceBroadcastPlayback, VoiceBroadcastRecording } from "../../src/voice-broadcast"; -import { mkVoiceBroadcastInfoStateEvent } from "./voice-broadcast/utils/test-utils"; -import { SdkContextClass } from "../../src/contexts/SDKContext"; -import Modal from "../../src/Modal"; import { createAudioContext } from "../../src/audio/compat"; import * as ManagedHybrid from "../../src/widgets/ManagedHybrid"; @@ -403,53 +399,6 @@ describe("LegacyCallHandler", () => { await callHandler.placeCall(NATIVE_ROOM_ALICE, CallType.Voice); expect(spy).toHaveBeenCalledWith(MatrixClientPeg.safeGet().getRoom(NATIVE_ROOM_ALICE)); }); - - describe("when listening to a voice broadcast", () => { - let voiceBroadcastPlayback: VoiceBroadcastPlayback; - - beforeEach(() => { - voiceBroadcastPlayback = new VoiceBroadcastPlayback( - mkVoiceBroadcastInfoStateEvent( - "!room:example.com", - VoiceBroadcastInfoState.Started, - MatrixClientPeg.safeGet().getSafeUserId(), - "d42", - ), - MatrixClientPeg.safeGet(), - SdkContextClass.instance.voiceBroadcastRecordingsStore, - ); - SdkContextClass.instance.voiceBroadcastPlaybacksStore.setCurrent(voiceBroadcastPlayback); - jest.spyOn(voiceBroadcastPlayback, "pause").mockImplementation(); - }); - - it("and placing a call should pause the broadcast", async () => { - callHandler.placeCall(NATIVE_ROOM_ALICE, CallType.Voice); - await untilCallHandlerEvent(callHandler, LegacyCallHandlerEvent.CallState); - - expect(voiceBroadcastPlayback.pause).toHaveBeenCalled(); - }); - }); - - describe("when recording a voice broadcast", () => { - beforeEach(() => { - SdkContextClass.instance.voiceBroadcastRecordingsStore.setCurrent( - new VoiceBroadcastRecording( - mkVoiceBroadcastInfoStateEvent( - "!room:example.com", - VoiceBroadcastInfoState.Started, - MatrixClientPeg.safeGet().getSafeUserId(), - "d42", - ), - MatrixClientPeg.safeGet(), - ), - ); - }); - - it("and placing a call should show the info dialog", async () => { - callHandler.placeCall(NATIVE_ROOM_ALICE, CallType.Voice); - expect(Modal.createDialog).toMatchSnapshot(); - }); - }); }); describe("LegacyCallHandler without third party protocols", () => { @@ -528,9 +477,6 @@ describe("LegacyCallHandler without third party protocols", () => { audioElement.id = "remoteAudio"; document.body.appendChild(audioElement); - SdkContextClass.instance.voiceBroadcastPlaybacksStore.clearCurrent(); - SdkContextClass.instance.voiceBroadcastRecordingsStore.clearCurrent(); - fetchMock.get( "/media/ring.mp3", { body: new Blob(["1", "2", "3", "4"], { type: "audio/mpeg" }) }, diff --git a/test/unit-tests/Notifier-test.ts b/test/unit-tests/Notifier-test.ts index 0996c625b5e..f94f50724d0 100644 --- a/test/unit-tests/Notifier-test.ts +++ b/test/unit-tests/Notifier-test.ts @@ -43,8 +43,6 @@ import { mkThread } from "../test-utils/threads"; import dis from "../../src/dispatcher/dispatcher"; import { ThreadPayload } from "../../src/dispatcher/payloads/ThreadPayload"; import { Action } from "../../src/dispatcher/actions"; -import { VoiceBroadcastChunkEventType, VoiceBroadcastInfoState } from "../../src/voice-broadcast"; -import { mkVoiceBroadcastInfoStateEvent } from "./voice-broadcast/utils/test-utils"; import { addReplyToMessageContent } from "../../src/utils/Reply"; jest.mock("../../src/utils/notifications", () => ({ @@ -85,16 +83,13 @@ describe("Notifier", () => { }); }; - const mkAudioEvent = (broadcastChunkContent?: object): MatrixEvent => { - const chunkContent = broadcastChunkContent ? { [VoiceBroadcastChunkEventType]: broadcastChunkContent } : {}; - + const mkAudioEvent = (): MatrixEvent => { return mkEvent({ event: true, type: EventType.RoomMessage, user: "@user:example.com", room: "!room:example.com", content: { - ...chunkContent, msgtype: MsgType.Audio, body: "test audio message", }, @@ -320,24 +315,6 @@ describe("Notifier", () => { ); }); - it("should display the expected notification for a broadcast chunk with sequence = 1", () => { - const audioEvent = mkAudioEvent({ sequence: 1 }); - Notifier.displayPopupNotification(audioEvent, testRoom); - expect(MockPlatform.displayNotification).toHaveBeenCalledWith( - "@user:example.com (!room1:server)", - "@user:example.com started a voice broadcast", - "data:image/png;base64,00", - testRoom, - audioEvent, - ); - }); - - it("should display the expected notification for a broadcast chunk with sequence = 2", () => { - const audioEvent = mkAudioEvent({ sequence: 2 }); - Notifier.displayPopupNotification(audioEvent, testRoom); - expect(MockPlatform.displayNotification).not.toHaveBeenCalled(); - }); - it("should strip reply fallback", () => { const event = mkMessage({ msg: "Test", @@ -581,24 +558,6 @@ describe("Notifier", () => { Notifier.evaluateEvent(mkAudioEvent()); expect(Notifier.displayPopupNotification).toHaveBeenCalledTimes(1); }); - - it("should not show a notification for broadcast info events in any case", () => { - // Let client decide to show a notification - mockClient.getPushActionsForEvent.mockReturnValue({ - notify: true, - tweaks: {}, - }); - - const broadcastStartedEvent = mkVoiceBroadcastInfoStateEvent( - "!other:example.org", - VoiceBroadcastInfoState.Started, - "@user:example.com", - "ABC123", - ); - - Notifier.evaluateEvent(broadcastStartedEvent); - expect(Notifier.displayPopupNotification).not.toHaveBeenCalled(); - }); }); describe("setPromptHidden", () => { diff --git a/test/unit-tests/SdkConfig-test.ts b/test/unit-tests/SdkConfig-test.ts index 4204a698fab..19d0eec9c30 100644 --- a/test/unit-tests/SdkConfig-test.ts +++ b/test/unit-tests/SdkConfig-test.ts @@ -18,10 +18,6 @@ describe("SdkConfig", () => { describe("with custom values", () => { beforeEach(() => { SdkConfig.put({ - voice_broadcast: { - chunk_length: 42, - max_length: 1337, - }, feedback: { existing_issues_url: "https://existing", } as any, @@ -30,8 +26,6 @@ describe("SdkConfig", () => { it("should return the custom config", () => { const customConfig = JSON.parse(JSON.stringify(DEFAULTS)); - customConfig.voice_broadcast.chunk_length = 42; - customConfig.voice_broadcast.max_length = 1337; customConfig.feedback.existing_issues_url = "https://existing"; expect(SdkConfig.get()).toEqual(customConfig); }); diff --git a/test/unit-tests/TestSdkContext.ts b/test/unit-tests/TestSdkContext.ts index 0d7e806514c..f60b083beff 100644 --- a/test/unit-tests/TestSdkContext.ts +++ b/test/unit-tests/TestSdkContext.ts @@ -16,11 +16,6 @@ import { SpaceStoreClass } from "../../src/stores/spaces/SpaceStore"; import { WidgetLayoutStore } from "../../src/stores/widgets/WidgetLayoutStore"; import { WidgetPermissionStore } from "../../src/stores/widgets/WidgetPermissionStore"; import WidgetStore from "../../src/stores/WidgetStore"; -import { - VoiceBroadcastPlaybacksStore, - VoiceBroadcastPreRecordingStore, - VoiceBroadcastRecordingsStore, -} from "../../src/voice-broadcast"; /** * A class which provides the same API as SdkContextClass but adds additional unsafe setters which can @@ -36,9 +31,6 @@ export class TestSdkContext extends SdkContextClass { declare public _PosthogAnalytics?: PosthogAnalytics; declare public _SlidingSyncManager?: SlidingSyncManager; declare public _SpaceStore?: SpaceStoreClass; - declare public _VoiceBroadcastRecordingsStore?: VoiceBroadcastRecordingsStore; - declare public _VoiceBroadcastPreRecordingStore?: VoiceBroadcastPreRecordingStore; - declare public _VoiceBroadcastPlaybacksStore?: VoiceBroadcastPlaybacksStore; constructor() { super(); diff --git a/test/unit-tests/__snapshots__/LegacyCallHandler-test.ts.snap b/test/unit-tests/__snapshots__/LegacyCallHandler-test.ts.snap deleted file mode 100644 index aaf4d787584..00000000000 --- a/test/unit-tests/__snapshots__/LegacyCallHandler-test.ts.snap +++ /dev/null @@ -1,24 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`LegacyCallHandler when recording a voice broadcast and placing a call should show the info dialog 1`] = ` -[MockFunction] { - "calls": [ - [ - [Function], - { - "description":

- You can’t start a call as you are currently recording a live broadcast. Please end your live broadcast in order to start a call. -

, - "hasCloseButton": true, - "title": "Can’t start a call", - }, - ], - ], - "results": [ - { - "type": "return", - "value": undefined, - }, - ], -} -`; diff --git a/test/unit-tests/components/structures/MatrixChat-test.tsx b/test/unit-tests/components/structures/MatrixChat-test.tsx index 28bf99fa978..fd17ccf5838 100644 --- a/test/unit-tests/components/structures/MatrixChat-test.tsx +++ b/test/unit-tests/components/structures/MatrixChat-test.tsx @@ -44,7 +44,6 @@ import { } from "../../../test-utils"; import * as leaveRoomUtils from "../../../../src/utils/leave-behaviour"; import { OidcClientError } from "../../../../src/utils/oidc/error"; -import * as voiceBroadcastUtils from "../../../../src/voice-broadcast/utils/cleanUpBroadcasts"; import LegacyCallHandler from "../../../../src/LegacyCallHandler"; import { CallStore } from "../../../../src/stores/CallStore"; import { Call } from "../../../../src/models/Call"; @@ -811,7 +810,6 @@ describe("", () => { jest.spyOn(LegacyCallHandler.instance, "hangupAllCalls") .mockClear() .mockImplementation(() => {}); - jest.spyOn(voiceBroadcastUtils, "cleanUpBroadcasts").mockImplementation(async () => {}); jest.spyOn(PosthogAnalytics.instance, "logout").mockImplementation(() => {}); jest.spyOn(EventIndexPeg, "deleteEventIndex").mockImplementation(async () => {}); @@ -831,22 +829,12 @@ describe("", () => { jest.spyOn(logger, "warn").mockClear(); }); - afterAll(() => { - jest.spyOn(voiceBroadcastUtils, "cleanUpBroadcasts").mockRestore(); - }); - it("should hangup all legacy calls", async () => { await getComponentAndWaitForReady(); await dispatchLogoutAndWait(); expect(LegacyCallHandler.instance.hangupAllCalls).toHaveBeenCalled(); }); - it("should cleanup broadcasts", async () => { - await getComponentAndWaitForReady(); - await dispatchLogoutAndWait(); - expect(voiceBroadcastUtils.cleanUpBroadcasts).toHaveBeenCalled(); - }); - it("should disconnect all calls", async () => { await getComponentAndWaitForReady(); await dispatchLogoutAndWait(); diff --git a/test/unit-tests/components/structures/PipContainer-test.tsx b/test/unit-tests/components/structures/PipContainer-test.tsx index 446727c74e2..f573b0a0cde 100644 --- a/test/unit-tests/components/structures/PipContainer-test.tsx +++ b/test/unit-tests/components/structures/PipContainer-test.tsx @@ -10,7 +10,7 @@ import React from "react"; import { mocked, Mocked } from "jest-mock"; import { screen, render, act, cleanup } from "jest-matrix-react"; import userEvent from "@testing-library/user-event"; -import { MatrixClient, PendingEventOrdering, Room, MatrixEvent, RoomStateEvent } from "matrix-js-sdk/src/matrix"; +import { MatrixClient, PendingEventOrdering, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix"; import { Widget, ClientWidgetApi } from "matrix-widget-api"; import { UserEvent } from "@testing-library/user-event/dist/types/setup/setup"; @@ -26,7 +26,6 @@ import { wrapInSdkContext, mkRoomCreateEvent, mockPlatformPeg, - flushPromises, useMockMediaDevices, } from "../../../test-utils"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; @@ -39,17 +38,7 @@ import defaultDispatcher from "../../../../src/dispatcher/dispatcher"; import { Action } from "../../../../src/dispatcher/actions"; import { ViewRoomPayload } from "../../../../src/dispatcher/payloads/ViewRoomPayload"; import { TestSdkContext } from "../../TestSdkContext"; -import { - VoiceBroadcastInfoState, - VoiceBroadcastPlaybacksStore, - VoiceBroadcastPreRecording, - VoiceBroadcastPreRecordingStore, - VoiceBroadcastRecording, - VoiceBroadcastRecordingsStore, -} from "../../../../src/voice-broadcast"; -import { mkVoiceBroadcastInfoStateEvent } from "../../voice-broadcast/utils/test-utils"; import { RoomViewStore } from "../../../../src/stores/RoomViewStore"; -import { IRoomStateEventsActionPayload } from "../../../../src/actions/MatrixActionCreators"; import { Container, WidgetLayoutStore } from "../../../../src/stores/widgets/WidgetLayoutStore"; import WidgetStore from "../../../../src/stores/WidgetStore"; import { WidgetType } from "../../../../src/widgets/WidgetType"; @@ -76,13 +65,6 @@ describe("PipContainer", () => { let room: Room; let room2: Room; let alice: RoomMember; - let voiceBroadcastRecordingsStore: VoiceBroadcastRecordingsStore; - let voiceBroadcastPreRecordingStore: VoiceBroadcastPreRecordingStore; - let voiceBroadcastPlaybacksStore: VoiceBroadcastPlaybacksStore; - - const actFlushPromises = async () => { - await flushPromises(); - }; beforeEach(async () => { useMockMediaDevices(); @@ -125,13 +107,7 @@ describe("PipContainer", () => { sdkContext = new TestSdkContext(); // @ts-ignore PipContainer uses SDKContext in the constructor SdkContextClass.instance = sdkContext; - voiceBroadcastRecordingsStore = new VoiceBroadcastRecordingsStore(); - voiceBroadcastPreRecordingStore = new VoiceBroadcastPreRecordingStore(); - voiceBroadcastPlaybacksStore = new VoiceBroadcastPlaybacksStore(voiceBroadcastRecordingsStore); sdkContext.client = client; - sdkContext._VoiceBroadcastRecordingsStore = voiceBroadcastRecordingsStore; - sdkContext._VoiceBroadcastPreRecordingStore = voiceBroadcastPreRecordingStore; - sdkContext._VoiceBroadcastPlaybacksStore = voiceBroadcastPlaybacksStore; }); afterEach(async () => { @@ -190,51 +166,10 @@ describe("PipContainer", () => { ActiveWidgetStore.instance.destroyPersistentWidget("1", room.roomId); }; - const makeVoiceBroadcastInfoStateEvent = (): MatrixEvent => { - return mkVoiceBroadcastInfoStateEvent( - room.roomId, - VoiceBroadcastInfoState.Started, - alice.userId, - client.getDeviceId() || "", - ); - }; - - const setUpVoiceBroadcastRecording = () => { - const infoEvent = makeVoiceBroadcastInfoStateEvent(); - const voiceBroadcastRecording = new VoiceBroadcastRecording(infoEvent, client); - voiceBroadcastRecordingsStore.setCurrent(voiceBroadcastRecording); - }; - - const setUpVoiceBroadcastPreRecording = () => { - const voiceBroadcastPreRecording = new VoiceBroadcastPreRecording( - room, - alice, - client, - voiceBroadcastPlaybacksStore, - voiceBroadcastRecordingsStore, - ); - voiceBroadcastPreRecordingStore.setCurrent(voiceBroadcastPreRecording); - }; - const setUpRoomViewStore = () => { sdkContext._RoomViewStore = new RoomViewStore(defaultDispatcher, sdkContext); }; - const mkVoiceBroadcast = (room: Room): MatrixEvent => { - const infoEvent = makeVoiceBroadcastInfoStateEvent(); - room.currentState.setStateEvents([infoEvent]); - defaultDispatcher.dispatch( - { - action: "MatrixActions.RoomState.events", - event: infoEvent, - state: room.currentState, - lastStateEvent: null, - }, - true, - ); - return infoEvent; - }; - it("hides if there's no content", () => { renderPip(); expect(screen.queryByRole("complementary")).toBeNull(); @@ -339,138 +274,4 @@ describe("PipContainer", () => { WidgetStore.instance.removeVirtualWidget("1", room.roomId); }); - - describe("when there is a voice broadcast recording and pre-recording", () => { - beforeEach(async () => { - setUpVoiceBroadcastPreRecording(); - setUpVoiceBroadcastRecording(); - renderPip(); - await actFlushPromises(); - }); - - it("should render the voice broadcast recording PiP", () => { - // check for the „Live“ badge to be present - expect(screen.queryByText("Live")).toBeInTheDocument(); - }); - - it("and a call it should show both, the call and the recording", async () => { - await withCall(async () => { - // Broadcast: Check for the „Live“ badge to be present - expect(screen.queryByText("Live")).toBeInTheDocument(); - // Call: Check for the „Leave“ button to be present - screen.getByRole("button", { name: "Leave" }); - }); - }); - }); - - describe("when there is a voice broadcast playback and pre-recording", () => { - beforeEach(async () => { - mkVoiceBroadcast(room); - setUpVoiceBroadcastPreRecording(); - renderPip(); - await actFlushPromises(); - }); - - it("should render the voice broadcast pre-recording PiP", () => { - // check for the „Go live“ button - expect(screen.queryByText("Go live")).toBeInTheDocument(); - }); - }); - - describe("when there is a voice broadcast pre-recording", () => { - beforeEach(async () => { - setUpVoiceBroadcastPreRecording(); - renderPip(); - await actFlushPromises(); - }); - - it("should render the voice broadcast pre-recording PiP", () => { - // check for the „Go live“ button - expect(screen.queryByText("Go live")).toBeInTheDocument(); - }); - }); - - describe("when listening to a voice broadcast in a room and then switching to another room", () => { - beforeEach(async () => { - setUpRoomViewStore(); - viewRoom(room.roomId); - mkVoiceBroadcast(room); - await actFlushPromises(); - - expect(voiceBroadcastPlaybacksStore.getCurrent()).toBeTruthy(); - - await voiceBroadcastPlaybacksStore.getCurrent()?.start(); - viewRoom(room2.roomId); - renderPip(); - }); - - it("should render the small voice broadcast playback PiP", () => { - // check for the „pause voice broadcast“ button - expect(screen.getByLabelText("pause voice broadcast")).toBeInTheDocument(); - // check for the absence of the „30s forward“ button - expect(screen.queryByLabelText("30s forward")).not.toBeInTheDocument(); - }); - }); - - describe("when viewing a room with a live voice broadcast", () => { - let startEvent!: MatrixEvent; - - beforeEach(async () => { - setUpRoomViewStore(); - viewRoom(room.roomId); - startEvent = mkVoiceBroadcast(room); - renderPip(); - await actFlushPromises(); - }); - - it("should render the voice broadcast playback pip", () => { - // check for the „resume voice broadcast“ button - expect(screen.queryByLabelText("play voice broadcast")).toBeInTheDocument(); - }); - - describe("and the broadcast stops", () => { - beforeEach(async () => { - const stopEvent = mkVoiceBroadcastInfoStateEvent( - room.roomId, - VoiceBroadcastInfoState.Stopped, - alice.userId, - client.getDeviceId() || "", - startEvent, - ); - - await act(async () => { - room.currentState.setStateEvents([stopEvent]); - defaultDispatcher.dispatch( - { - action: "MatrixActions.RoomState.events", - event: stopEvent, - state: room.currentState, - lastStateEvent: stopEvent, - }, - true, - ); - await flushPromises(); - }); - }); - - it("should not render the voice broadcast playback pip", () => { - // check for the „resume voice broadcast“ button - expect(screen.queryByLabelText("play voice broadcast")).not.toBeInTheDocument(); - }); - }); - - describe("and leaving the room", () => { - beforeEach(async () => { - await act(async () => { - viewRoom(room2.roomId); - await flushPromises(); - }); - }); - - it("should not render the voice broadcast playback pip", () => { - // check for the „resume voice broadcast“ button - expect(screen.queryByLabelText("play voice broadcast")).not.toBeInTheDocument(); - }); - }); - }); }); diff --git a/test/unit-tests/components/structures/UserMenu-test.tsx b/test/unit-tests/components/structures/UserMenu-test.tsx index ac76aba2ade..907bf664b7f 100644 --- a/test/unit-tests/components/structures/UserMenu-test.tsx +++ b/test/unit-tests/components/structures/UserMenu-test.tsx @@ -7,20 +7,14 @@ Please see LICENSE files in the repository root for full details. */ import React from "react"; -import { act, render, RenderResult, screen, waitFor } from "jest-matrix-react"; -import { DEVICE_CODE_SCOPE, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; +import { render, screen, waitFor } from "jest-matrix-react"; +import { DEVICE_CODE_SCOPE, MatrixClient, Room } from "matrix-js-sdk/src/matrix"; import { CryptoApi } from "matrix-js-sdk/src/crypto-api"; import { mocked } from "jest-mock"; import fetchMock from "fetch-mock-jest"; import UnwrappedUserMenu from "../../../../src/components/structures/UserMenu"; import { stubClient, wrapInSdkContext } from "../../../test-utils"; -import { - VoiceBroadcastInfoState, - VoiceBroadcastRecording, - VoiceBroadcastRecordingsStore, -} from "../../../../src/voice-broadcast"; -import { mkVoiceBroadcastInfoStateEvent } from "../../voice-broadcast/utils/test-utils"; import { TestSdkContext } from "../../TestSdkContext"; import defaultDispatcher from "../../../../src/dispatcher/dispatcher"; import LogoutDialog from "../../../../src/components/views/dialogs/LogoutDialog"; @@ -34,71 +28,12 @@ import { UserTab } from "../../../../src/components/views/dialogs/UserTab"; describe("", () => { let client: MatrixClient; - let renderResult: RenderResult; let sdkContext: TestSdkContext; beforeEach(() => { sdkContext = new TestSdkContext(); }); - describe(" when video broadcast", () => { - let voiceBroadcastInfoEvent: MatrixEvent; - let voiceBroadcastRecording: VoiceBroadcastRecording; - let voiceBroadcastRecordingsStore: VoiceBroadcastRecordingsStore; - - beforeAll(() => { - client = stubClient(); - voiceBroadcastInfoEvent = mkVoiceBroadcastInfoStateEvent( - "!room:example.com", - VoiceBroadcastInfoState.Started, - client.getUserId() || "", - client.getDeviceId() || "", - ); - }); - - beforeEach(() => { - voiceBroadcastRecordingsStore = new VoiceBroadcastRecordingsStore(); - sdkContext._VoiceBroadcastRecordingsStore = voiceBroadcastRecordingsStore; - - voiceBroadcastRecording = new VoiceBroadcastRecording(voiceBroadcastInfoEvent, client); - }); - - describe("when rendered", () => { - beforeEach(() => { - const UserMenu = wrapInSdkContext(UnwrappedUserMenu, sdkContext); - renderResult = render(); - }); - - it("should render as expected", () => { - expect(renderResult.container).toMatchSnapshot(); - }); - - describe("and a live voice broadcast starts", () => { - beforeEach(() => { - act(() => { - voiceBroadcastRecordingsStore.setCurrent(voiceBroadcastRecording); - }); - }); - - it("should render the live voice broadcast avatar addon", () => { - expect(renderResult.queryByTestId("user-menu-live-vb")).toBeInTheDocument(); - }); - - describe("and the broadcast ends", () => { - beforeEach(() => { - act(() => { - voiceBroadcastRecordingsStore.clearCurrent(); - }); - }); - - it("should not render the live voice broadcast avatar addon", () => { - expect(renderResult.queryByTestId("user-menu-live-vb")).not.toBeInTheDocument(); - }); - }); - }); - }); - }); - describe(" logout", () => { beforeEach(() => { client = stubClient(); @@ -106,7 +41,7 @@ describe("", () => { it("should logout directly if no crypto", async () => { const UserMenu = wrapInSdkContext(UnwrappedUserMenu, sdkContext); - renderResult = render(); + render(); mocked(client.getRooms).mockReturnValue([ { @@ -128,7 +63,7 @@ describe("", () => { it("should logout directly if no encrypted rooms", async () => { const UserMenu = wrapInSdkContext(UnwrappedUserMenu, sdkContext); - renderResult = render(); + render(); mocked(client.getRooms).mockReturnValue([ { @@ -152,7 +87,7 @@ describe("", () => { it("should show dialog if some encrypted rooms", async () => { const UserMenu = wrapInSdkContext(UnwrappedUserMenu, sdkContext); - renderResult = render(); + render(); mocked(client.getRooms).mockReturnValue([ { diff --git a/test/unit-tests/components/structures/__snapshots__/UserMenu-test.tsx.snap b/test/unit-tests/components/structures/__snapshots__/UserMenu-test.tsx.snap deleted file mode 100644 index 029db9bfd4a..00000000000 --- a/test/unit-tests/components/structures/__snapshots__/UserMenu-test.tsx.snap +++ /dev/null @@ -1,33 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` when video broadcast when rendered should render as expected 1`] = ` -
-
- -
-
-`; diff --git a/test/unit-tests/components/views/context_menus/MessageContextMenu-test.tsx b/test/unit-tests/components/views/context_menus/MessageContextMenu-test.tsx index 892ca6dbeda..142840fd5a5 100644 --- a/test/unit-tests/components/views/context_menus/MessageContextMenu-test.tsx +++ b/test/unit-tests/components/views/context_menus/MessageContextMenu-test.tsx @@ -37,8 +37,6 @@ import dispatcher from "../../../../../src/dispatcher/dispatcher"; import SettingsStore from "../../../../../src/settings/SettingsStore"; import { ReadPinsEventId } from "../../../../../src/components/views/right_panel/types"; import { Action } from "../../../../../src/dispatcher/actions"; -import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils"; -import { VoiceBroadcastInfoState } from "../../../../../src/voice-broadcast"; import { createMessageEventContent } from "../../../../test-utils/events"; import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx"; @@ -234,17 +232,6 @@ describe("MessageContextMenu", () => { expect(document.querySelector('li[aria-label="Forward"]')).toBeFalsy(); }); - it("should not allow forwarding a voice broadcast", () => { - const broadcastStartEvent = mkVoiceBroadcastInfoStateEvent( - roomId, - VoiceBroadcastInfoState.Started, - "@user:example.com", - "ABC123", - ); - createMenu(broadcastStartEvent); - expect(document.querySelector('li[aria-label="Forward"]')).toBeFalsy(); - }); - describe("forwarding beacons", () => { const aliceId = "@alice:server.org"; diff --git a/test/unit-tests/components/views/dialogs/ConfirmRedactDialog-test.tsx b/test/unit-tests/components/views/dialogs/ConfirmRedactDialog-test.tsx index c648f416f90..ed5b545ed5d 100644 --- a/test/unit-tests/components/views/dialogs/ConfirmRedactDialog-test.tsx +++ b/test/unit-tests/components/views/dialogs/ConfirmRedactDialog-test.tsx @@ -6,14 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ -import { Feature, ServerSupport } from "matrix-js-sdk/src/feature"; -import { MatrixClient, MatrixEvent, RelationType } from "matrix-js-sdk/src/matrix"; +import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix"; import { screen, act } from "jest-matrix-react"; import userEvent from "@testing-library/user-event"; import { flushPromises, mkEvent, stubClient } from "../../../../test-utils"; -import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils"; -import { VoiceBroadcastInfoState } from "../../../../../src/voice-broadcast"; import { createRedactEventDialog } from "../../../../../src/components/views/dialogs/ConfirmRedactDialog"; describe("ConfirmRedactDialog", () => { @@ -21,15 +18,6 @@ describe("ConfirmRedactDialog", () => { let client: MatrixClient; let mxEvent: MatrixEvent; - const setUpVoiceBroadcastStartedEvent = () => { - mxEvent = mkVoiceBroadcastInfoStateEvent( - roomId, - VoiceBroadcastInfoState.Started, - client.getUserId()!, - client.deviceId!, - ); - }; - const confirmDeleteVoiceBroadcastStartedEvent = async () => { act(() => createRedactEventDialog({ mxEvent })); // double-flush promises required for the dialog to show up @@ -68,44 +56,4 @@ describe("ConfirmRedactDialog", () => { `cannot redact event ${mxEvent.getId()} without room ID`, ); }); - - describe("when redacting a voice broadcast started event", () => { - beforeEach(() => { - setUpVoiceBroadcastStartedEvent(); - }); - - describe("and the server does not support relation based redactions", () => { - beforeEach(() => { - client.canSupport.set(Feature.RelationBasedRedactions, ServerSupport.Unsupported); - }); - - describe("and displaying and confirm the dialog for a voice broadcast", () => { - beforeEach(async () => { - await confirmDeleteVoiceBroadcastStartedEvent(); - }); - - it("should call redact without `with_rel_types`", () => { - expect(client.redactEvent).toHaveBeenCalledWith(roomId, mxEvent.getId(), undefined, {}); - }); - }); - }); - - describe("and the server supports relation based redactions", () => { - beforeEach(() => { - client.canSupport.set(Feature.RelationBasedRedactions, ServerSupport.Unstable); - }); - - describe("and displaying and confirm the dialog for a voice broadcast", () => { - beforeEach(async () => { - await confirmDeleteVoiceBroadcastStartedEvent(); - }); - - it("should call redact with `with_rel_types`", () => { - expect(client.redactEvent).toHaveBeenCalledWith(roomId, mxEvent.getId(), undefined, { - with_rel_types: [RelationType.Reference], - }); - }); - }); - }); - }); }); diff --git a/test/unit-tests/components/views/dialogs/__snapshots__/DevtoolsDialog-test.tsx.snap b/test/unit-tests/components/views/dialogs/__snapshots__/DevtoolsDialog-test.tsx.snap index 910569c1120..622ed320652 100644 --- a/test/unit-tests/components/views/dialogs/__snapshots__/DevtoolsDialog-test.tsx.snap +++ b/test/unit-tests/components/views/dialogs/__snapshots__/DevtoolsDialog-test.tsx.snap @@ -185,33 +185,6 @@ exports[`DevtoolsDialog renders the devtools dialog 1`] = ` />
-
- -
-
-
-
({ default: () =>
, })); -jest.mock("../../../../../src/voice-broadcast/components/VoiceBroadcastBody", () => ({ - VoiceBroadcastBody: () =>
, -})); - jest.mock("../../../../../src/components/views/messages/MImageBody", () => ({ __esModule: true, default: () =>
, @@ -81,27 +76,6 @@ describe("MessageEvent", () => { jest.spyOn(SettingsStore, "unwatchSetting").mockImplementation(jest.fn()); }); - describe("when a voice broadcast start event occurs", () => { - let result: RenderResult; - - beforeEach(() => { - event = mkEvent({ - event: true, - type: VoiceBroadcastInfoEventType, - user: client.getUserId()!, - room: room.roomId, - content: { - state: VoiceBroadcastInfoState.Started, - }, - }); - result = renderMessageEvent(); - }); - - it("should render a VoiceBroadcast component", () => { - result.getByTestId("voice-broadcast-body"); - }); - }); - describe("when an image with a caption is sent", () => { let result: RenderResult; diff --git a/test/unit-tests/components/views/rooms/MessageComposer-test.tsx b/test/unit-tests/components/views/rooms/MessageComposer-test.tsx index 4ef533091a6..6849612fee5 100644 --- a/test/unit-tests/components/views/rooms/MessageComposer-test.tsx +++ b/test/unit-tests/components/views/rooms/MessageComposer-test.tsx @@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details. */ import * as React from "react"; -import { EventType, MatrixEvent, Room, RoomMember, THREAD_RELATION_TYPE } from "matrix-js-sdk/src/matrix"; +import { EventType, MatrixEvent, RoomMember, THREAD_RELATION_TYPE } from "matrix-js-sdk/src/matrix"; import { act, fireEvent, render, screen, waitFor } from "jest-matrix-react"; import userEvent from "@testing-library/user-event"; @@ -19,7 +19,6 @@ import { mkStubRoom, mockPlatformPeg, stubClient, - waitEnoughCyclesForModal, } from "../../../../test-utils"; import MessageComposer from "../../../../../src/components/views/rooms/MessageComposer"; import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext"; @@ -28,7 +27,6 @@ import { IRoomState } from "../../../../../src/components/structures/RoomView"; import ResizeNotifier from "../../../../../src/utils/ResizeNotifier"; import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks"; import { LocalRoom } from "../../../../../src/models/LocalRoom"; -import { Features } from "../../../../../src/settings/Settings"; import SettingsStore from "../../../../../src/settings/SettingsStore"; import { SettingLevel } from "../../../../../src/settings/SettingLevel"; import dis from "../../../../../src/dispatcher/dispatcher"; @@ -36,9 +34,6 @@ import { E2EStatus } from "../../../../../src/utils/ShieldUtils"; import { addTextToComposerRTL } from "../../../../test-utils/composer"; import UIStore, { UI_EVENTS } from "../../../../../src/stores/UIStore"; import { Action } from "../../../../../src/dispatcher/actions"; -import { VoiceBroadcastInfoState, VoiceBroadcastRecording } from "../../../../../src/voice-broadcast"; -import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils"; -import { SdkContextClass } from "../../../../../src/contexts/SDKContext"; import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx"; const openStickerPicker = async (): Promise => { @@ -51,15 +46,6 @@ const startVoiceMessage = async (): Promise => { await userEvent.click(screen.getByLabelText("Voice Message")); }; -const setCurrentBroadcastRecording = (room: Room, state: VoiceBroadcastInfoState): void => { - const recording = new VoiceBroadcastRecording( - mkVoiceBroadcastInfoStateEvent(room.roomId, state, "@user:example.com", "ABC123"), - MatrixClientPeg.safeGet(), - state, - ); - act(() => SdkContextClass.instance.voiceBroadcastRecordingsStore.setCurrent(recording)); -}; - const expectVoiceMessageRecordingTriggered = (): void => { // Checking for the voice message dialog text, if no mic can be found. // By this we know at least that starting a voice message was triggered. @@ -78,14 +64,11 @@ describe("MessageComposer", () => { await clearAllModals(); jest.useRealTimers(); - SdkContextClass.instance.voiceBroadcastRecordingsStore.clearCurrent(); - // restore settings act(() => { [ "MessageComposerInput.showStickersButton", "MessageComposerInput.showPollsButton", - Features.VoiceBroadcast, "feature_wysiwyg_composer", ].forEach((setting: string): void => { SettingsStore.setValue(setting, null, SettingLevel.DEVICE, SettingsStore.getDefaultValue(setting)); @@ -212,10 +195,6 @@ describe("MessageComposer", () => { setting: "MessageComposerInput.showPollsButton", buttonLabel: "Poll", }, - { - setting: Features.VoiceBroadcast, - buttonLabel: "Voice broadcast", - }, ].forEach(({ setting, buttonLabel }) => { [true, false].forEach((value: boolean) => { describe(`when ${setting} = ${value}`, () => { @@ -437,34 +416,6 @@ describe("MessageComposer", () => { expectVoiceMessageRecordingTriggered(); }); }); - - describe("when recording a voice broadcast and trying to start a voice message", () => { - beforeEach(async () => { - setCurrentBroadcastRecording(room, VoiceBroadcastInfoState.Started); - wrapAndRender({ room }); - await startVoiceMessage(); - await waitEnoughCyclesForModal(); - }); - - it("should not start a voice message and display the info dialog", async () => { - expect(screen.queryByLabelText("Stop recording")).not.toBeInTheDocument(); - expect(screen.getByText("Can't start voice message")).toBeInTheDocument(); - }); - }); - - describe("when there is a stopped voice broadcast recording and trying to start a voice message", () => { - beforeEach(async () => { - setCurrentBroadcastRecording(room, VoiceBroadcastInfoState.Stopped); - wrapAndRender({ room }); - await startVoiceMessage(); - await waitEnoughCyclesForModal(); - }); - - it("should try to start a voice message and should not display the info dialog", async () => { - expect(screen.queryByText("Can't start voice message")).not.toBeInTheDocument(); - expectVoiceMessageRecordingTriggered(); - }); - }); }); describe("for a LocalRoom", () => { diff --git a/test/unit-tests/components/views/rooms/MessageComposerButtons-test.tsx b/test/unit-tests/components/views/rooms/MessageComposerButtons-test.tsx index 08204350ca0..c2f56dc968d 100644 --- a/test/unit-tests/components/views/rooms/MessageComposerButtons-test.tsx +++ b/test/unit-tests/components/views/rooms/MessageComposerButtons-test.tsx @@ -168,27 +168,4 @@ describe("MessageComposerButtons", () => { ]); }); }); - - describe("with showVoiceBroadcastButton = true", () => { - it("should render the »Voice broadcast« button", () => { - wrapAndRender( - , - false, - ); - - expect(getButtonLabels()).toEqual([ - "Emoji", - "Attachment", - "More options", - ["Sticker", "Voice Message", "Voice broadcast", "Poll", "Location"], - ]); - }); - }); }); diff --git a/test/unit-tests/components/views/rooms/RoomTile-test.tsx b/test/unit-tests/components/views/rooms/RoomTile-test.tsx index 7aa9ad8462a..c14c699699f 100644 --- a/test/unit-tests/components/views/rooms/RoomTile-test.tsx +++ b/test/unit-tests/components/views/rooms/RoomTile-test.tsx @@ -9,14 +9,7 @@ Please see LICENSE files in the repository root for full details. import React from "react"; import { render, screen, act, RenderResult } from "jest-matrix-react"; import { mocked, Mocked } from "jest-mock"; -import { - MatrixClient, - PendingEventOrdering, - Room, - MatrixEvent, - RoomStateEvent, - Thread, -} from "matrix-js-sdk/src/matrix"; +import { MatrixClient, PendingEventOrdering, Room, RoomStateEvent, Thread } from "matrix-js-sdk/src/matrix"; import { KnownMembership } from "matrix-js-sdk/src/types"; import { Widget } from "matrix-widget-api"; @@ -40,8 +33,6 @@ import DMRoomMap from "../../../../../src/utils/DMRoomMap"; import PlatformPeg from "../../../../../src/PlatformPeg"; import BasePlatform from "../../../../../src/BasePlatform"; import { WidgetMessagingStore } from "../../../../../src/stores/widgets/WidgetMessagingStore"; -import { VoiceBroadcastInfoState } from "../../../../../src/voice-broadcast"; -import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils"; import { TestSdkContext } from "../../../TestSdkContext"; import { SDKContext } from "../../../../../src/contexts/SDKContext"; import { shouldShowComponent } from "../../../../../src/customisations/helpers/UIComponents"; @@ -61,20 +52,6 @@ describe("RoomTile", () => { } as unknown as BasePlatform); useMockedCalls(); - const setUpVoiceBroadcast = async (state: VoiceBroadcastInfoState): Promise => { - voiceBroadcastInfoEvent = mkVoiceBroadcastInfoStateEvent( - room.roomId, - state, - client.getSafeUserId(), - client.getDeviceId()!, - ); - - await act(async () => { - room.currentState.setStateEvents([voiceBroadcastInfoEvent]); - await flushPromises(); - }); - }; - const renderRoomTile = (): RenderResult => { return render( @@ -89,7 +66,6 @@ describe("RoomTile", () => { }; let client: Mocked; - let voiceBroadcastInfoEvent: MatrixEvent; let room: Room; let sdkContext: TestSdkContext; let showMessagePreview = false; @@ -303,49 +279,6 @@ describe("RoomTile", () => { }); expect(screen.queryByLabelText(/participant/)).toBe(null); }); - - describe("and a live broadcast starts", () => { - beforeEach(async () => { - renderRoomTile(); - await setUpVoiceBroadcast(VoiceBroadcastInfoState.Started); - }); - - it("should still render the call subtitle", () => { - expect(screen.queryByText("Video")).toBeInTheDocument(); - expect(screen.queryByText("Live")).not.toBeInTheDocument(); - }); - }); - }); - - describe("when a live voice broadcast starts", () => { - beforeEach(async () => { - renderRoomTile(); - await setUpVoiceBroadcast(VoiceBroadcastInfoState.Started); - }); - - it("should render the »Live« subtitle", () => { - expect(screen.queryByText("Live")).toBeInTheDocument(); - }); - - describe("and the broadcast stops", () => { - beforeEach(async () => { - const stopEvent = mkVoiceBroadcastInfoStateEvent( - room.roomId, - VoiceBroadcastInfoState.Stopped, - client.getSafeUserId(), - client.getDeviceId()!, - voiceBroadcastInfoEvent, - ); - await act(async () => { - room.currentState.setStateEvents([stopEvent]); - await flushPromises(); - }); - }); - - it("should not render the »Live« subtitle", () => { - expect(screen.queryByText("Live")).not.toBeInTheDocument(); - }); - }); }); }); diff --git a/test/unit-tests/components/views/settings/tabs/room/RolesRoomSettingsTab-test.tsx b/test/unit-tests/components/views/settings/tabs/room/RolesRoomSettingsTab-test.tsx index 000c38c771d..e1a451c9d57 100644 --- a/test/unit-tests/components/views/settings/tabs/room/RolesRoomSettingsTab-test.tsx +++ b/test/unit-tests/components/views/settings/tabs/room/RolesRoomSettingsTab-test.tsx @@ -17,7 +17,6 @@ import userEvent from "@testing-library/user-event"; import RolesRoomSettingsTab from "../../../../../../../src/components/views/settings/tabs/room/RolesRoomSettingsTab"; import { mkStubRoom, withClientContextRenderOptions, stubClient } from "../../../../../../test-utils"; import { MatrixClientPeg } from "../../../../../../../src/MatrixClientPeg"; -import { VoiceBroadcastInfoEventType } from "../../../../../../../src/voice-broadcast"; import SettingsStore from "../../../../../../../src/settings/SettingsStore"; import { ElementCall } from "../../../../../../../src/models/Call"; @@ -34,14 +33,6 @@ describe("RolesRoomSettingsTab", () => { return renderResult; }; - const getVoiceBroadcastsSelect = async (): Promise => { - return (await renderTab()).container.querySelector("select[label='Voice broadcasts']")!; - }; - - const getVoiceBroadcastsSelectedOption = async (): Promise => { - return (await renderTab()).container.querySelector("select[label='Voice broadcasts'] option:checked")!; - }; - beforeEach(() => { stubClient(); cli = MatrixClientPeg.safeGet(); @@ -76,26 +67,6 @@ describe("RolesRoomSettingsTab", () => { expect(container.querySelector(`[placeholder="@admin:server"]`)).toBeDisabled(); }); - it("should initially show »Moderator« permission for »Voice broadcasts«", async () => { - expect((await getVoiceBroadcastsSelectedOption()).textContent).toBe("Moderator"); - }); - - describe("when setting »Default« permission for »Voice broadcasts«", () => { - beforeEach(async () => { - fireEvent.change(await getVoiceBroadcastsSelect(), { - target: { value: 0 }, - }); - }); - - it("should update the power levels", () => { - expect(cli.sendStateEvent).toHaveBeenCalledWith(roomId, EventType.RoomPowerLevels, { - events: { - [VoiceBroadcastInfoEventType]: 0, - }, - }); - }); - }); - describe("Element Call", () => { const setGroupCallsEnabled = (val: boolean): void => { jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => { diff --git a/test/unit-tests/contexts/SdkContext-test.ts b/test/unit-tests/contexts/SdkContext-test.ts index 21f356ed94c..340fabdd3dd 100644 --- a/test/unit-tests/contexts/SdkContext-test.ts +++ b/test/unit-tests/contexts/SdkContext-test.ts @@ -11,11 +11,8 @@ import { MatrixClient } from "matrix-js-sdk/src/matrix"; import { SdkContextClass } from "../../../src/contexts/SDKContext"; import { OidcClientStore } from "../../../src/stores/oidc/OidcClientStore"; import { UserProfilesStore } from "../../../src/stores/UserProfilesStore"; -import { VoiceBroadcastPreRecordingStore } from "../../../src/voice-broadcast"; import { createTestClient } from "../../test-utils"; -jest.mock("../../../src/voice-broadcast/stores/VoiceBroadcastPreRecordingStore"); - describe("SdkContextClass", () => { let sdkContext = SdkContextClass.instance; let client: MatrixClient; @@ -33,12 +30,6 @@ describe("SdkContextClass", () => { expect(SdkContextClass.instance).toBe(globalInstance); }); - it("voiceBroadcastPreRecordingStore should always return the same VoiceBroadcastPreRecordingStore", () => { - const first = sdkContext.voiceBroadcastPreRecordingStore; - expect(first).toBeInstanceOf(VoiceBroadcastPreRecordingStore); - expect(sdkContext.voiceBroadcastPreRecordingStore).toBe(first); - }); - it("userProfilesStore should raise an error without a client", () => { expect(() => sdkContext.userProfilesStore).toThrow("Unable to create UserProfilesStore without a client"); }); diff --git a/test/unit-tests/events/EventTileFactory-test.ts b/test/unit-tests/events/EventTileFactory-test.ts index 7044a883d00..8a7d09c434f 100644 --- a/test/unit-tests/events/EventTileFactory-test.ts +++ b/test/unit-tests/events/EventTileFactory-test.ts @@ -7,19 +7,16 @@ Please see LICENSE files in the repository root for full details. */ import { mocked } from "jest-mock"; -import { EventType, MatrixClient, MatrixEvent, MsgType, RelationType, Room } from "matrix-js-sdk/src/matrix"; +import { EventType, MatrixClient, MatrixEvent, MsgType, Room } from "matrix-js-sdk/src/matrix"; import { JSONEventFactory, MessageEventFactory, pickFactory, RoomCreateEventFactory, - TextualEventFactory, } from "../../../src/events/EventTileFactory"; import SettingsStore from "../../../src/settings/SettingsStore"; -import { VoiceBroadcastChunkEventType, VoiceBroadcastInfoState } from "../../../src/voice-broadcast"; import { createTestClient, mkEvent } from "../../test-utils"; -import { mkVoiceBroadcastInfoStateEvent } from "../voice-broadcast/utils/test-utils"; const roomId = "!room:example.com"; @@ -31,11 +28,7 @@ describe("pickFactory", () => { let createEventWithoutPredecessor: MatrixEvent; let dynamicPredecessorEvent: MatrixEvent; - let voiceBroadcastStartedEvent: MatrixEvent; - let voiceBroadcastStoppedEvent: MatrixEvent; - let voiceBroadcastChunkEvent: MatrixEvent; let utdEvent: MatrixEvent; - let utdBroadcastChunkEvent: MatrixEvent; let audioMessageEvent: MatrixEvent; beforeAll(() => { @@ -82,29 +75,6 @@ describe("pickFactory", () => { last_known_event_id: null, }, }); - voiceBroadcastStartedEvent = mkVoiceBroadcastInfoStateEvent( - roomId, - VoiceBroadcastInfoState.Started, - client.getUserId()!, - client.deviceId!, - ); - room.addLiveEvents([voiceBroadcastStartedEvent], { addToState: true }); - voiceBroadcastStoppedEvent = mkVoiceBroadcastInfoStateEvent( - roomId, - VoiceBroadcastInfoState.Stopped, - client.getUserId()!, - client.deviceId!, - ); - voiceBroadcastChunkEvent = mkEvent({ - event: true, - type: EventType.RoomMessage, - user: client.getUserId()!, - room: roomId, - content: { - msgtype: MsgType.Audio, - [VoiceBroadcastChunkEventType]: {}, - }, - }); audioMessageEvent = mkEvent({ event: true, type: EventType.RoomMessage, @@ -123,20 +93,6 @@ describe("pickFactory", () => { msgtype: "m.bad.encrypted", }, }); - utdBroadcastChunkEvent = mkEvent({ - event: true, - type: EventType.RoomMessage, - user: client.getUserId()!, - room: roomId, - content: { - "msgtype": "m.bad.encrypted", - "m.relates_to": { - rel_type: RelationType.Reference, - event_id: voiceBroadcastStartedEvent.getId(), - }, - }, - }); - jest.spyOn(utdBroadcastChunkEvent, "isDecryptionFailure").mockReturnValue(true); }); it("should return JSONEventFactory for a no-op m.room.power_levels event", () => { @@ -151,10 +107,6 @@ describe("pickFactory", () => { }); describe("when showing hidden events", () => { - it("should return a JSONEventFactory for a voice broadcast event", () => { - expect(pickFactory(voiceBroadcastChunkEvent, client, true)).toBe(JSONEventFactory); - }); - it("should return a JSONEventFactory for a room create event without predecessor", () => { room.currentState.events.set( EventType.RoomCreate, @@ -164,17 +116,9 @@ describe("pickFactory", () => { expect(pickFactory(createEventWithoutPredecessor, client, true)).toBe(JSONEventFactory); }); - it("should return a TextualEventFactory for a voice broadcast stopped event", () => { - expect(pickFactory(voiceBroadcastStoppedEvent, client, true)).toBe(TextualEventFactory); - }); - it("should return a MessageEventFactory for an audio message event", () => { expect(pickFactory(audioMessageEvent, client, true)).toBe(MessageEventFactory); }); - - it("should return a MessageEventFactory for a UTD broadcast chunk event", () => { - expect(pickFactory(utdBroadcastChunkEvent, client, true)).toBe(MessageEventFactory); - }); }); describe("when not showing hidden events", () => { @@ -252,14 +196,6 @@ describe("pickFactory", () => { }); }); - it("should return undefined for a voice broadcast event", () => { - expect(pickFactory(voiceBroadcastChunkEvent, client, false)).toBeUndefined(); - }); - - it("should return a TextualEventFactory for a voice broadcast stopped event", () => { - expect(pickFactory(voiceBroadcastStoppedEvent, client, false)).toBe(TextualEventFactory); - }); - it("should return a MessageEventFactory for an audio message event", () => { expect(pickFactory(audioMessageEvent, client, false)).toBe(MessageEventFactory); }); @@ -267,9 +203,5 @@ describe("pickFactory", () => { it("should return a MessageEventFactory for a UTD event", () => { expect(pickFactory(utdEvent, client, false)).toBe(MessageEventFactory); }); - - it("should return undefined for a UTD broadcast chunk event", () => { - expect(pickFactory(utdBroadcastChunkEvent, client, false)).toBeUndefined(); - }); }); }); diff --git a/test/unit-tests/stores/RoomViewStore-test.ts b/test/unit-tests/stores/RoomViewStore-test.ts index 01f07e89ac6..782bb797385 100644 --- a/test/unit-tests/stores/RoomViewStore-test.ts +++ b/test/unit-tests/stores/RoomViewStore-test.ts @@ -24,13 +24,6 @@ import { ActiveRoomChangedPayload } from "../../../src/dispatcher/payloads/Activ import { SpaceStoreClass } from "../../../src/stores/spaces/SpaceStore"; import { TestSdkContext } from "../TestSdkContext"; import { ViewRoomPayload } from "../../../src/dispatcher/payloads/ViewRoomPayload"; -import { - VoiceBroadcastInfoState, - VoiceBroadcastPlayback, - VoiceBroadcastPlaybacksStore, - VoiceBroadcastRecording, -} from "../../../src/voice-broadcast"; -import { mkVoiceBroadcastInfoStateEvent } from "../voice-broadcast/utils/test-utils"; import Modal from "../../../src/Modal"; import ErrorDialog from "../../../src/components/views/dialogs/ErrorDialog"; import { CancelAskToJoinPayload } from "../../../src/dispatcher/payloads/CancelAskToJoinPayload"; @@ -160,7 +153,6 @@ describe("RoomViewStore", function () { stores._SlidingSyncManager = slidingSyncManager; stores._PosthogAnalytics = new MockPosthogAnalytics(); stores._SpaceStore = new MockSpaceStore(); - stores._VoiceBroadcastPlaybacksStore = new VoiceBroadcastPlaybacksStore(stores.voiceBroadcastRecordingsStore); roomViewStore = new RoomViewStore(dis, stores); stores._RoomViewStore = roomViewStore; }); @@ -343,88 +335,6 @@ describe("RoomViewStore", function () { }); }); - describe("when listening to a voice broadcast", () => { - let voiceBroadcastPlayback: VoiceBroadcastPlayback; - - beforeEach(() => { - voiceBroadcastPlayback = new VoiceBroadcastPlayback( - mkVoiceBroadcastInfoStateEvent( - roomId, - VoiceBroadcastInfoState.Started, - mockClient.getSafeUserId(), - "d42", - ), - mockClient, - stores.voiceBroadcastRecordingsStore, - ); - stores.voiceBroadcastPlaybacksStore.setCurrent(voiceBroadcastPlayback); - jest.spyOn(voiceBroadcastPlayback, "pause").mockImplementation(); - }); - - it("and viewing a call it should pause the current broadcast", async () => { - await viewCall(); - expect(voiceBroadcastPlayback.pause).toHaveBeenCalled(); - expect(roomViewStore.isViewingCall()).toBe(true); - }); - }); - - describe("when recording a voice broadcast", () => { - beforeEach(() => { - stores.voiceBroadcastRecordingsStore.setCurrent( - new VoiceBroadcastRecording( - mkVoiceBroadcastInfoStateEvent( - roomId, - VoiceBroadcastInfoState.Started, - mockClient.getSafeUserId(), - "d42", - ), - mockClient, - ), - ); - }); - - it("and trying to view a call, it should not actually view it and show the info dialog", async () => { - await viewCall(); - expect(Modal.createDialog).toMatchSnapshot(); - expect(roomViewStore.isViewingCall()).toBe(false); - }); - - describe("and viewing a room with a broadcast", () => { - beforeEach(async () => { - const broadcastEvent = mkVoiceBroadcastInfoStateEvent( - roomId2, - VoiceBroadcastInfoState.Started, - mockClient.getSafeUserId(), - "ABC123", - ); - room2.addLiveEvents([broadcastEvent], { addToState: true }); - - stores.voiceBroadcastPlaybacksStore.getByInfoEvent(broadcastEvent, mockClient); - dis.dispatch({ action: Action.ViewRoom, room_id: roomId2 }); - await untilDispatch(Action.ActiveRoomChanged, dis); - }); - - it("should continue recording", () => { - expect(stores.voiceBroadcastPlaybacksStore.getCurrent()).toBeNull(); - expect(stores.voiceBroadcastRecordingsStore.getCurrent()?.getState()).toBe( - VoiceBroadcastInfoState.Started, - ); - }); - - describe("and stopping the recording", () => { - beforeEach(async () => { - await stores.voiceBroadcastRecordingsStore.getCurrent()?.stop(); - // check test precondition - expect(stores.voiceBroadcastRecordingsStore.getCurrent()).toBeNull(); - }); - - it("should view the broadcast", () => { - expect(stores.voiceBroadcastPlaybacksStore.getCurrent()?.infoEvent.getRoomId()).toBe(roomId2); - }); - }); - }); - }); - describe("Sliding Sync", function () { beforeEach(() => { jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName, roomId, value) => { diff --git a/test/unit-tests/stores/__snapshots__/RoomViewStore-test.ts.snap b/test/unit-tests/stores/__snapshots__/RoomViewStore-test.ts.snap index a6b7953697b..77ee50df5b9 100644 --- a/test/unit-tests/stores/__snapshots__/RoomViewStore-test.ts.snap +++ b/test/unit-tests/stores/__snapshots__/RoomViewStore-test.ts.snap @@ -18,26 +18,3 @@ exports[`RoomViewStore should display the generic error message when the roomId "title": "Failed to join", } `; - -exports[`RoomViewStore when recording a voice broadcast and trying to view a call, it should not actually view it and show the info dialog 1`] = ` -[MockFunction] { - "calls": [ - [ - [Function], - { - "description":

- You can’t start a call as you are currently recording a live broadcast. Please end your live broadcast in order to start a call. -

, - "hasCloseButton": true, - "title": "Can’t start a call", - }, - ], - ], - "results": [ - { - "type": "return", - "value": undefined, - }, - ], -} -`; diff --git a/test/unit-tests/stores/room-list/previews/MessageEventPreview-test.ts b/test/unit-tests/stores/room-list/previews/MessageEventPreview-test.ts index fd6626e6b63..ec97cdd20e4 100644 --- a/test/unit-tests/stores/room-list/previews/MessageEventPreview-test.ts +++ b/test/unit-tests/stores/room-list/previews/MessageEventPreview-test.ts @@ -71,18 +71,5 @@ describe("MessageEventPreview", () => { }); expect(preview.getTextFor(event)).toBe(`${userId}: test new content body`); }); - - it("when called with a broadcast chunk event it should return null", () => { - const event = mkEvent({ - event: true, - content: { - body: "test body", - ["io.element.voice_broadcast_chunk"]: {}, - }, - user: userId, - type: "m.room.message", - }); - expect(preview.getTextFor(event)).toBeNull(); - }); }); }); diff --git a/test/unit-tests/stores/room-list/previews/VoiceBroadcastPreview-test.ts b/test/unit-tests/stores/room-list/previews/VoiceBroadcastPreview-test.ts deleted file mode 100644 index a96f3c11bd5..00000000000 --- a/test/unit-tests/stores/room-list/previews/VoiceBroadcastPreview-test.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { Room } from "matrix-js-sdk/src/matrix"; - -import { VoiceBroadcastPreview } from "../../../../../src/stores/room-list/previews/VoiceBroadcastPreview"; -import { VoiceBroadcastInfoState } from "../../../../../src/voice-broadcast"; -import { mkEvent, stubClient } from "../../../../test-utils"; -import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils"; - -describe("VoiceBroadcastPreview.getTextFor", () => { - const roomId = "!room:example.com"; - const userId = "@user:example.com"; - const deviceId = "d42"; - let preview: VoiceBroadcastPreview; - - beforeAll(() => { - preview = new VoiceBroadcastPreview(); - }); - - it("when passing an event with empty content, it should return null", () => { - const event = mkEvent({ - event: true, - content: {}, - user: userId, - type: "m.room.message", - }); - expect(preview.getTextFor(event)).toBeNull(); - }); - - it("when passing a broadcast started event, it should return null", () => { - const event = mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Started, userId, deviceId); - expect(preview.getTextFor(event)).toBeNull(); - }); - - it("when passing a broadcast stopped event, it should return the expected text", () => { - const event = mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Stopped, userId, deviceId); - expect(preview.getTextFor(event)).toBe("@user:example.com ended a voice broadcast"); - }); - - it("when passing a redacted broadcast stopped event, it should return null", () => { - const event = mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Stopped, userId, deviceId); - event.makeRedacted( - mkEvent({ event: true, content: {}, user: userId, type: "m.room.redaction" }), - new Room(roomId, stubClient(), userId), - ); - expect(preview.getTextFor(event)).toBeNull(); - }); -}); diff --git a/test/unit-tests/stores/widgets/StopGapWidget-test.ts b/test/unit-tests/stores/widgets/StopGapWidget-test.ts index 397c289d224..1416711017f 100644 --- a/test/unit-tests/stores/widgets/StopGapWidget-test.ts +++ b/test/unit-tests/stores/widgets/StopGapWidget-test.ts @@ -22,9 +22,6 @@ import { waitFor } from "jest-matrix-react"; import { stubClient, mkRoom, mkEvent } from "../../../test-utils"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; import { StopGapWidget } from "../../../../src/stores/widgets/StopGapWidget"; -import { ElementWidgetActions } from "../../../../src/stores/widgets/ElementWidgetActions"; -import { VoiceBroadcastInfoEventType, VoiceBroadcastRecording } from "../../../../src/voice-broadcast"; -import { SdkContextClass } from "../../../../src/contexts/SDKContext"; import ActiveWidgetStore from "../../../../src/stores/ActiveWidgetStore"; import SettingsStore from "../../../../src/settings/SettingsStore"; @@ -225,41 +222,6 @@ describe("StopGapWidget", () => { expect(messaging.feedEvent).toHaveBeenLastCalledWith(event.getEffectiveEvent(), "!1:example.org"); }); }); - - describe("when there is a voice broadcast recording", () => { - let voiceBroadcastInfoEvent: MatrixEvent; - let voiceBroadcastRecording: VoiceBroadcastRecording; - - beforeEach(() => { - voiceBroadcastInfoEvent = mkEvent({ - event: true, - room: client.getRoom("x")?.roomId, - user: client.getUserId()!, - type: VoiceBroadcastInfoEventType, - content: {}, - }); - voiceBroadcastRecording = new VoiceBroadcastRecording(voiceBroadcastInfoEvent, client); - jest.spyOn(voiceBroadcastRecording, "pause"); - jest.spyOn(SdkContextClass.instance.voiceBroadcastRecordingsStore, "getCurrent").mockReturnValue( - voiceBroadcastRecording, - ); - }); - - describe(`and receiving a action:${ElementWidgetActions.JoinCall} message`, () => { - beforeEach(async () => { - messaging.on.mock.calls.find(([event, listener]) => { - if (event === `action:${ElementWidgetActions.JoinCall}`) { - listener(); - return true; - } - }); - }); - - it("should pause the current voice broadcast recording", () => { - expect(voiceBroadcastRecording.pause).toHaveBeenCalled(); - }); - }); - }); }); describe("StopGapWidget with stickyPromise", () => { let client: MockedObject; diff --git a/test/unit-tests/utils/EventRenderingUtils-test.ts b/test/unit-tests/utils/EventRenderingUtils-test.ts deleted file mode 100644 index 8a3cfded4a7..00000000000 --- a/test/unit-tests/utils/EventRenderingUtils-test.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { getEventDisplayInfo } from "../../../src/utils/EventRenderingUtils"; -import { VoiceBroadcastInfoState } from "../../../src/voice-broadcast"; -import { mkVoiceBroadcastInfoStateEvent } from "../voice-broadcast/utils/test-utils"; -import { createTestClient } from "../../test-utils"; - -describe("getEventDisplayInfo", () => { - const mkBroadcastInfoEvent = (state: VoiceBroadcastInfoState) => { - return mkVoiceBroadcastInfoStateEvent("!room:example.com", state, "@user:example.com", "ASD123"); - }; - - it("should return the expected value for a broadcast started event", () => { - expect(getEventDisplayInfo(createTestClient(), mkBroadcastInfoEvent(VoiceBroadcastInfoState.Started), false)) - .toMatchInlineSnapshot(` - { - "hasRenderer": true, - "isBubbleMessage": false, - "isInfoMessage": false, - "isLeftAlignedBubbleMessage": false, - "isSeeingThroughMessageHiddenForModeration": false, - "noBubbleEvent": true, - } - `); - }); - - it("should return the expected value for a broadcast stopped event", () => { - expect(getEventDisplayInfo(createTestClient(), mkBroadcastInfoEvent(VoiceBroadcastInfoState.Stopped), false)) - .toMatchInlineSnapshot(` - { - "hasRenderer": true, - "isBubbleMessage": false, - "isInfoMessage": true, - "isLeftAlignedBubbleMessage": false, - "isSeeingThroughMessageHiddenForModeration": false, - "noBubbleEvent": true, - } - `); - }); -}); diff --git a/test/unit-tests/utils/EventUtils-test.ts b/test/unit-tests/utils/EventUtils-test.ts index 2fc13b507c2..c7828c3a2fc 100644 --- a/test/unit-tests/utils/EventUtils-test.ts +++ b/test/unit-tests/utils/EventUtils-test.ts @@ -35,8 +35,6 @@ import { import { getMockClientWithEventEmitter, makeBeaconInfoEvent, makePollStartEvent, stubClient } from "../../test-utils"; import dis from "../../../src/dispatcher/dispatcher"; import { Action } from "../../../src/dispatcher/actions"; -import { mkVoiceBroadcastInfoStateEvent } from "../voice-broadcast/utils/test-utils"; -import { VoiceBroadcastInfoState } from "../../../src/voice-broadcast/types"; jest.mock("../../../src/dispatcher/dispatcher"); @@ -148,20 +146,6 @@ describe("EventUtils", () => { }, }); - const voiceBroadcastStart = mkVoiceBroadcastInfoStateEvent( - "!room:example.com", - VoiceBroadcastInfoState.Started, - "@user:example.com", - "ABC123", - ); - - const voiceBroadcastStop = mkVoiceBroadcastInfoStateEvent( - "!room:example.com", - VoiceBroadcastInfoState.Stopped, - "@user:example.com", - "ABC123", - ); - describe("isContentActionable()", () => { type TestCase = [string, MatrixEvent]; it.each([ @@ -172,7 +156,6 @@ describe("EventUtils", () => { ["room member event", roomMemberEvent], ["event without msgtype", noMsgType], ["event without content body property", noContentBody], - ["broadcast stop event", voiceBroadcastStop], ])("returns false for %s", (_description, event) => { expect(isContentActionable(event)).toBe(false); }); @@ -183,7 +166,6 @@ describe("EventUtils", () => { ["event with empty content body", emptyContentBody], ["event with a content body", niceTextMessage], ["beacon_info event", beaconInfoEvent], - ["broadcast start event", voiceBroadcastStart], ])("returns true for %s", (_description, event) => { expect(isContentActionable(event)).toBe(true); }); diff --git a/test/unit-tests/voice-broadcast/audio/VoiceBroadcastRecorder-test.ts b/test/unit-tests/voice-broadcast/audio/VoiceBroadcastRecorder-test.ts deleted file mode 100644 index 5718fc118c6..00000000000 --- a/test/unit-tests/voice-broadcast/audio/VoiceBroadcastRecorder-test.ts +++ /dev/null @@ -1,251 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { mocked } from "jest-mock"; -import { Optional } from "matrix-events-sdk"; - -import { VoiceRecording } from "../../../../src/audio/VoiceRecording"; -import SdkConfig from "../../../../src/SdkConfig"; -import { concat } from "../../../../src/utils/arrays"; -import { - ChunkRecordedPayload, - createVoiceBroadcastRecorder, - VoiceBroadcastRecorder, - VoiceBroadcastRecorderEvent, -} from "../../../../src/voice-broadcast"; - -// mock VoiceRecording because it contains all the audio APIs -jest.mock("../../../../src/audio/VoiceRecording", () => ({ - VoiceRecording: jest.fn().mockReturnValue({ - disableMaxLength: jest.fn(), - emit: jest.fn(), - liveData: { - onUpdate: jest.fn(), - }, - start: jest.fn(), - stop: jest.fn(), - destroy: jest.fn(), - }), -})); - -jest.mock("../../../../src/settings/SettingsStore"); - -describe("VoiceBroadcastRecorder", () => { - describe("createVoiceBroadcastRecorder", () => { - beforeEach(() => { - jest.spyOn(SdkConfig, "get").mockImplementation((key: string) => { - if (key === "voice_broadcast") { - return { - chunk_length: 1337, - }; - } - }); - }); - - afterEach(() => { - mocked(SdkConfig.get).mockRestore(); - }); - - it("should return a VoiceBroadcastRecorder instance with targetChunkLength from config", () => { - const voiceBroadcastRecorder = createVoiceBroadcastRecorder(); - expect(voiceBroadcastRecorder).toBeInstanceOf(VoiceBroadcastRecorder); - expect(voiceBroadcastRecorder.targetChunkLength).toBe(1337); - }); - }); - - describe("instance", () => { - const chunkLength = 30; - // 0... OpusHead - const headers1 = new Uint8Array([...Array(28).fill(0), 79, 112, 117, 115, 72, 101, 97, 100]); - // 0... OpusTags - const headers2 = new Uint8Array([...Array(28).fill(0), 79, 112, 117, 115, 84, 97, 103, 115]); - const chunk1 = new Uint8Array([5, 6]); - const chunk2a = new Uint8Array([7, 8]); - const chunk2b = new Uint8Array([9, 10]); - const contentType = "test content type"; - - let voiceRecording: VoiceRecording; - let voiceBroadcastRecorder: VoiceBroadcastRecorder; - let onChunkRecorded: (chunk: ChunkRecordedPayload) => void; - - const simulateFirstChunk = (): void => { - // send headers in wrong order and multiple times to test robustness for that - voiceRecording.onDataAvailable!(headers2); - voiceRecording.onDataAvailable!(headers1); - voiceRecording.onDataAvailable!(headers1); - voiceRecording.onDataAvailable!(headers2); - // set recorder seconds to something greater than the test chunk length of 30 - // @ts-ignore - voiceRecording.recorderSeconds = 42; - voiceRecording.onDataAvailable!(chunk1); - voiceRecording.onDataAvailable!(headers1); - }; - - const expectOnFirstChunkRecorded = (): void => { - expect(onChunkRecorded).toHaveBeenNthCalledWith(1, { - buffer: concat(headers1, headers2, chunk1), - length: 42, - }); - }; - - const itShouldNotEmitAChunkRecordedEvent = (): void => { - it("should not emit a ChunkRecorded event", (): void => { - expect(voiceRecording.emit).not.toHaveBeenCalledWith( - VoiceBroadcastRecorderEvent.ChunkRecorded, - expect.anything(), - ); - }); - }; - - beforeEach(() => { - voiceRecording = new VoiceRecording(); - // @ts-ignore - voiceRecording.recorderSeconds = 23; - // @ts-ignore - voiceRecording.contentType = contentType; - - voiceBroadcastRecorder = new VoiceBroadcastRecorder(voiceRecording, chunkLength); - jest.spyOn(voiceBroadcastRecorder, "removeAllListeners"); - onChunkRecorded = jest.fn(); - voiceBroadcastRecorder.on(VoiceBroadcastRecorderEvent.ChunkRecorded, onChunkRecorded); - }); - - afterEach(() => { - voiceBroadcastRecorder.destroy(); - }); - - it("start should forward the call to VoiceRecording.start", async () => { - await voiceBroadcastRecorder.start(); - expect(voiceRecording.start).toHaveBeenCalled(); - }); - - describe("stop", () => { - beforeEach(async () => { - await voiceBroadcastRecorder.stop(); - }); - - it("should forward the call to VoiceRecording.stop", async () => { - expect(voiceRecording.stop).toHaveBeenCalled(); - }); - - itShouldNotEmitAChunkRecordedEvent(); - }); - - describe("when calling destroy", () => { - beforeEach(() => { - voiceBroadcastRecorder.destroy(); - }); - - it("should call VoiceRecording.destroy", () => { - expect(voiceRecording.destroy).toHaveBeenCalled(); - }); - - it("should remove all listeners", () => { - expect(voiceBroadcastRecorder.removeAllListeners).toHaveBeenCalled(); - }); - }); - - it("contentType should return the value from VoiceRecording", () => { - expect(voiceBroadcastRecorder.contentType).toBe(contentType); - }); - - describe("when the first header from recorder has been received", () => { - beforeEach(() => { - voiceRecording.onDataAvailable!(headers1); - }); - - itShouldNotEmitAChunkRecordedEvent(); - }); - - describe("when the second header from recorder has been received", () => { - beforeEach(() => { - voiceRecording.onDataAvailable!(headers1); - voiceRecording.onDataAvailable!(headers2); - }); - - itShouldNotEmitAChunkRecordedEvent(); - }); - - describe("when a third page from recorder has been received", () => { - beforeEach(() => { - voiceRecording.onDataAvailable!(headers1); - voiceRecording.onDataAvailable!(headers2); - voiceRecording.onDataAvailable!(chunk1); - }); - - itShouldNotEmitAChunkRecordedEvent(); - - describe("and calling stop", () => { - let stopPayload: Optional; - - beforeEach(async () => { - stopPayload = await voiceBroadcastRecorder.stop(); - }); - - it("should return the remaining chunk", () => { - expect(stopPayload).toEqual({ - buffer: concat(headers1, headers2, chunk1), - length: 23, - }); - }); - - describe("and calling start again and receiving some data", () => { - beforeEach(() => { - simulateFirstChunk(); - }); - - it("should emit the ChunkRecorded event for the first chunk", () => { - expectOnFirstChunkRecorded(); - }); - }); - }); - - describe("and calling stop() with recording.stop error)", () => { - let stopPayload: Optional; - - beforeEach(async () => { - mocked(voiceRecording.stop).mockRejectedValue("Error"); - stopPayload = await voiceBroadcastRecorder.stop(); - }); - - it("should return the remaining chunk", () => { - expect(stopPayload).toEqual({ - buffer: concat(headers1, headers2, chunk1), - length: 23, - }); - }); - }); - }); - - describe("when some chunks have been received", () => { - beforeEach(() => { - simulateFirstChunk(); - - // simulate a second chunk - voiceRecording.onDataAvailable!(chunk2a); - - // send headers again to test robustness for that - voiceRecording.onDataAvailable!(headers2); - - // add another 30 seconds for the next chunk - // @ts-ignore - voiceRecording.recorderSeconds = 72; - voiceRecording.onDataAvailable!(chunk2b); - }); - - it("should emit ChunkRecorded events", () => { - expectOnFirstChunkRecorded(); - - expect(onChunkRecorded).toHaveBeenNthCalledWith(2, { - buffer: concat(headers1, headers2, chunk2a, chunk2b), - length: 72 - 42, // 72 (position at second chunk) - 42 (position of first chunk) - }); - }); - }); - }); -}); diff --git a/test/unit-tests/voice-broadcast/components/VoiceBroadcastBody-test.tsx b/test/unit-tests/voice-broadcast/components/VoiceBroadcastBody-test.tsx deleted file mode 100644 index fd416de8ce0..00000000000 --- a/test/unit-tests/voice-broadcast/components/VoiceBroadcastBody-test.tsx +++ /dev/null @@ -1,171 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React, { ReactElement } from "react"; -import { act, render, screen } from "jest-matrix-react"; -import { mocked } from "jest-mock"; -import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; - -import { - VoiceBroadcastBody as UnwrappedVoiceBroadcastBody, - VoiceBroadcastInfoState, - VoiceBroadcastRecordingBody, - VoiceBroadcastRecording, - VoiceBroadcastPlaybackBody, - VoiceBroadcastPlayback, - VoiceBroadcastRecordingsStore, -} from "../../../../src/voice-broadcast"; -import { withClientContextRenderOptions, stubClient, wrapInSdkContext } from "../../../test-utils"; -import { mkVoiceBroadcastInfoStateEvent } from "../utils/test-utils"; -import { MediaEventHelper } from "../../../../src/utils/MediaEventHelper"; -import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks"; -import { SdkContextClass } from "../../../../src/contexts/SDKContext"; - -jest.mock("../../../../src/voice-broadcast/components/molecules/VoiceBroadcastRecordingBody", () => ({ - VoiceBroadcastRecordingBody: jest.fn(), -})); - -jest.mock("../../../../src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody", () => ({ - VoiceBroadcastPlaybackBody: jest.fn(), -})); - -jest.mock("../../../../src/utils/permalinks/Permalinks"); -jest.mock("../../../../src/utils/MediaEventHelper"); -jest.mock("../../../../src/stores/WidgetStore"); -jest.mock("../../../../src/stores/widgets/WidgetLayoutStore"); - -describe("VoiceBroadcastBody", () => { - const roomId = "!room:example.com"; - let userId: string; - let deviceId: string; - let client: MatrixClient; - let room: Room; - let infoEvent: MatrixEvent; - let stoppedEvent: MatrixEvent; - let testRecording: VoiceBroadcastRecording; - let testPlayback: VoiceBroadcastPlayback; - - const renderVoiceBroadcast = () => { - const VoiceBroadcastBody = wrapInSdkContext(UnwrappedVoiceBroadcastBody, SdkContextClass.instance); - render( - {}} - onMessageAllowed={() => {}} - permalinkCreator={new RoomPermalinkCreator(room)} - />, - withClientContextRenderOptions(client), - ); - testRecording = SdkContextClass.instance.voiceBroadcastRecordingsStore.getByInfoEvent(infoEvent, client); - }; - - beforeEach(() => { - client = stubClient(); - userId = client.getUserId() || ""; - deviceId = client.getDeviceId() || ""; - mocked(client.relations).mockClear(); - mocked(client.relations).mockResolvedValue({ events: [] }); - room = new Room(roomId, client, userId); - mocked(client.getRoom).mockImplementation((getRoomId?: string) => { - if (getRoomId === roomId) return room; - return null; - }); - - infoEvent = mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Started, userId, deviceId); - stoppedEvent = mkVoiceBroadcastInfoStateEvent( - roomId, - VoiceBroadcastInfoState.Stopped, - userId, - deviceId, - infoEvent, - ); - room.addEventsToTimeline([infoEvent], true, true, room.getLiveTimeline()); - testRecording = new VoiceBroadcastRecording(infoEvent, client); - testPlayback = new VoiceBroadcastPlayback(infoEvent, client, new VoiceBroadcastRecordingsStore()); - mocked(VoiceBroadcastRecordingBody).mockImplementation(({ recording }): ReactElement | null => { - if (testRecording === recording) { - return
; - } - - return null; - }); - - mocked(VoiceBroadcastPlaybackBody).mockImplementation(({ playback }): ReactElement | null => { - if (testPlayback === playback) { - return
; - } - - return null; - }); - - jest.spyOn(SdkContextClass.instance.voiceBroadcastRecordingsStore, "getByInfoEvent").mockImplementation( - (getEvent: MatrixEvent, getClient: MatrixClient): VoiceBroadcastRecording => { - if (getEvent === infoEvent && getClient === client) { - return testRecording; - } - - throw new Error("unexpected event"); - }, - ); - - jest.spyOn(SdkContextClass.instance.voiceBroadcastPlaybacksStore, "getByInfoEvent").mockImplementation( - (getEvent: MatrixEvent): VoiceBroadcastPlayback => { - if (getEvent === infoEvent) { - return testPlayback; - } - - throw new Error("unexpected event"); - }, - ); - }); - - describe("when there is a stopped voice broadcast", () => { - beforeEach(() => { - room.addEventsToTimeline([stoppedEvent], true, true, room.getLiveTimeline()); - renderVoiceBroadcast(); - }); - - it("should render a voice broadcast playback body", () => { - screen.getByTestId("voice-broadcast-playback-body"); - }); - }); - - describe("when there is a started voice broadcast from the current user", () => { - beforeEach(() => { - renderVoiceBroadcast(); - }); - - it("should render a voice broadcast recording body", () => { - screen.getByTestId("voice-broadcast-recording-body"); - }); - - describe("and the recordings ends", () => { - beforeEach(() => { - act(() => { - room.addEventsToTimeline([stoppedEvent], true, true, room.getLiveTimeline()); - }); - }); - - it("should render a voice broadcast playback body", () => { - screen.getByTestId("voice-broadcast-playback-body"); - }); - }); - }); - - describe("when displaying a voice broadcast playback", () => { - beforeEach(() => { - mocked(client).getUserId.mockReturnValue("@other:example.com"); - renderVoiceBroadcast(); - }); - - it("should render a voice broadcast playback body", () => { - screen.getByTestId("voice-broadcast-playback-body"); - }); - }); -}); diff --git a/test/unit-tests/voice-broadcast/components/atoms/LiveBadge-test.tsx b/test/unit-tests/voice-broadcast/components/atoms/LiveBadge-test.tsx deleted file mode 100644 index 2d630fca9da..00000000000 --- a/test/unit-tests/voice-broadcast/components/atoms/LiveBadge-test.tsx +++ /dev/null @@ -1,24 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; -import { render } from "jest-matrix-react"; - -import { LiveBadge } from "../../../../../src/voice-broadcast"; - -describe("LiveBadge", () => { - it("should render as expected with default props", () => { - const { container } = render(); - expect(container).toMatchSnapshot(); - }); - - it("should render in grey as expected", () => { - const { container } = render(); - expect(container).toMatchSnapshot(); - }); -}); diff --git a/test/unit-tests/voice-broadcast/components/atoms/VoiceBroadcastControl-test.tsx b/test/unit-tests/voice-broadcast/components/atoms/VoiceBroadcastControl-test.tsx deleted file mode 100644 index 8c52062b5e3..00000000000 --- a/test/unit-tests/voice-broadcast/components/atoms/VoiceBroadcastControl-test.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; -import { render, RenderResult, screen } from "jest-matrix-react"; -import userEvent from "@testing-library/user-event"; - -import { VoiceBroadcastControl } from "../../../../../src/voice-broadcast"; -import { Icon as StopIcon } from "../../../../res/img/compound/stop-16.svg"; - -describe("VoiceBroadcastControl", () => { - let result: RenderResult; - let onClick: () => void; - - beforeEach(() => { - onClick = jest.fn(); - }); - - describe("when rendering it", () => { - beforeEach(() => { - const stopIcon = ; - result = render(); - }); - - it("should render as expected", () => { - expect(result.container).toMatchSnapshot(); - }); - - describe("when clicking it", () => { - beforeEach(async () => { - await userEvent.click(screen.getByLabelText("test label")); - }); - - it("should call onClick", () => { - expect(onClick).toHaveBeenCalled(); - }); - }); - }); -}); diff --git a/test/unit-tests/voice-broadcast/components/atoms/VoiceBroadcastHeader-test.tsx b/test/unit-tests/voice-broadcast/components/atoms/VoiceBroadcastHeader-test.tsx deleted file mode 100644 index 6a0e7190d8e..00000000000 --- a/test/unit-tests/voice-broadcast/components/atoms/VoiceBroadcastHeader-test.tsx +++ /dev/null @@ -1,89 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; -import { MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix"; -import { render, RenderResult } from "jest-matrix-react"; - -import { VoiceBroadcastHeader, VoiceBroadcastLiveness } from "../../../../../src/voice-broadcast"; -import { mkRoom, stubClient } from "../../../../test-utils"; - -// mock RoomAvatar, because it is doing too much fancy stuff -jest.mock("../../../../../src/components/views/avatars/RoomAvatar", () => ({ - __esModule: true, - default: jest.fn().mockImplementation(({ room }) => { - return
room avatar: {room.name}
; - }), -})); - -describe("VoiceBroadcastHeader", () => { - const userId = "@user:example.com"; - const roomId = "!room:example.com"; - let client: MatrixClient; - let room: Room; - const sender = new RoomMember(roomId, userId); - let container: RenderResult["container"]; - - const renderHeader = (live: VoiceBroadcastLiveness, showBroadcast?: boolean, buffering?: boolean): RenderResult => { - return render( - , - ); - }; - - beforeAll(() => { - client = stubClient(); - room = mkRoom(client, roomId); - sender.name = "test user"; - }); - - describe("when rendering a live broadcast header with broadcast info", () => { - beforeEach(() => { - container = renderHeader("live", true, true).container; - }); - - it("should render the header with a red live badge", () => { - expect(container).toMatchSnapshot(); - }); - }); - - describe("when rendering a buffering live broadcast header with broadcast info", () => { - beforeEach(() => { - container = renderHeader("live", true).container; - }); - - it("should render the header with a red live badge", () => { - expect(container).toMatchSnapshot(); - }); - }); - - describe("when rendering a live (grey) broadcast header with broadcast info", () => { - beforeEach(() => { - container = renderHeader("grey", true).container; - }); - - it("should render the header with a grey live badge", () => { - expect(container).toMatchSnapshot(); - }); - }); - - describe("when rendering a non-live broadcast header", () => { - beforeEach(() => { - container = renderHeader("not-live").container; - }); - - it("should render the header without a live badge", () => { - expect(container).toMatchSnapshot(); - }); - }); -}); diff --git a/test/unit-tests/voice-broadcast/components/atoms/VoiceBroadcastPlaybackControl-test.tsx b/test/unit-tests/voice-broadcast/components/atoms/VoiceBroadcastPlaybackControl-test.tsx deleted file mode 100644 index 9eef863eb47..00000000000 --- a/test/unit-tests/voice-broadcast/components/atoms/VoiceBroadcastPlaybackControl-test.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022, 2023 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; -import { render, RenderResult, screen } from "jest-matrix-react"; -import userEvent from "@testing-library/user-event"; - -import { VoiceBroadcastPlaybackControl, VoiceBroadcastPlaybackState } from "../../../../../src/voice-broadcast"; - -describe("", () => { - const renderControl = (state: VoiceBroadcastPlaybackState): { result: RenderResult; onClick: () => void } => { - const onClick = jest.fn(); - return { - onClick, - result: render(), - }; - }; - - it.each([ - VoiceBroadcastPlaybackState.Stopped, - VoiceBroadcastPlaybackState.Paused, - VoiceBroadcastPlaybackState.Buffering, - VoiceBroadcastPlaybackState.Playing, - ])("should render state %s as expected", (state: VoiceBroadcastPlaybackState) => { - expect(renderControl(state).result.container).toMatchSnapshot(); - }); - - it("should not render for error state", () => { - expect(renderControl(VoiceBroadcastPlaybackState.Error).result.asFragment()).toMatchInlineSnapshot( - ``, - ); - }); - - describe("when clicking the control", () => { - let onClick: () => void; - - beforeEach(async () => { - onClick = renderControl(VoiceBroadcastPlaybackState.Playing).onClick; - await userEvent.click(screen.getByLabelText("pause voice broadcast")); - }); - - it("should invoke the onClick callback", () => { - expect(onClick).toHaveBeenCalled(); - }); - }); -}); diff --git a/test/unit-tests/voice-broadcast/components/atoms/__snapshots__/LiveBadge-test.tsx.snap b/test/unit-tests/voice-broadcast/components/atoms/__snapshots__/LiveBadge-test.tsx.snap deleted file mode 100644 index bd4b8d2bcc4..00000000000 --- a/test/unit-tests/voice-broadcast/components/atoms/__snapshots__/LiveBadge-test.tsx.snap +++ /dev/null @@ -1,27 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`LiveBadge should render as expected with default props 1`] = ` -
-
-
- Live -
-
-`; - -exports[`LiveBadge should render in grey as expected 1`] = ` -
-
-
- Live -
-
-`; diff --git a/test/unit-tests/voice-broadcast/components/atoms/__snapshots__/VoiceBroadcastControl-test.tsx.snap b/test/unit-tests/voice-broadcast/components/atoms/__snapshots__/VoiceBroadcastControl-test.tsx.snap deleted file mode 100644 index a28e105b4cc..00000000000 --- a/test/unit-tests/voice-broadcast/components/atoms/__snapshots__/VoiceBroadcastControl-test.tsx.snap +++ /dev/null @@ -1,16 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`VoiceBroadcastControl when rendering it should render as expected 1`] = ` -
-
-
-
-
-`; diff --git a/test/unit-tests/voice-broadcast/components/atoms/__snapshots__/VoiceBroadcastHeader-test.tsx.snap b/test/unit-tests/voice-broadcast/components/atoms/__snapshots__/VoiceBroadcastHeader-test.tsx.snap deleted file mode 100644 index 0c1c966f732..00000000000 --- a/test/unit-tests/voice-broadcast/components/atoms/__snapshots__/VoiceBroadcastHeader-test.tsx.snap +++ /dev/null @@ -1,277 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`VoiceBroadcastHeader when rendering a buffering live broadcast header with broadcast info should render the header with a red live badge 1`] = ` -
-
-
- room avatar: - !room:example.com -
-
-
-
- !room:example.com -
-
-
- - - - - - test user - -
-
-
- Voice broadcast -
-
-
-
- Live -
-
-
-`; - -exports[`VoiceBroadcastHeader when rendering a live (grey) broadcast header with broadcast info should render the header with a grey live badge 1`] = ` -
-
-
- room avatar: - !room:example.com -
-
-
-
- !room:example.com -
-
-
- - - - - - test user - -
-
-
- Voice broadcast -
-
-
-
- Live -
-
-
-`; - -exports[`VoiceBroadcastHeader when rendering a live broadcast header with broadcast info should render the header with a red live badge 1`] = ` -
-
-
- room avatar: - !room:example.com -
-
-
-
- !room:example.com -
-
-
- - - - - - test user - -
-
-
- Voice broadcast -
-
-
-
-
- Buffering… -
-
-
-
- Live -
-
-
-`; - -exports[`VoiceBroadcastHeader when rendering a non-live broadcast header should render the header without a live badge 1`] = ` -
-
-
- room avatar: - !room:example.com -
-
-
-
- !room:example.com -
-
-
- - - - - - test user - -
-
-
-
-`; diff --git a/test/unit-tests/voice-broadcast/components/atoms/__snapshots__/VoiceBroadcastPlaybackControl-test.tsx.snap b/test/unit-tests/voice-broadcast/components/atoms/__snapshots__/VoiceBroadcastPlaybackControl-test.tsx.snap deleted file mode 100644 index 1ce23ca8b5a..00000000000 --- a/test/unit-tests/voice-broadcast/components/atoms/__snapshots__/VoiceBroadcastPlaybackControl-test.tsx.snap +++ /dev/null @@ -1,97 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` should render state buffering as expected 1`] = ` -
-
- - - -
-
-`; - -exports[` should render state pause as expected 1`] = ` -
-
- - - -
-
-`; - -exports[` should render state playing as expected 1`] = ` -
-
- - - -
-
-`; - -exports[` should render state stopped as expected 1`] = ` -
-
- - - -
-
-`; diff --git a/test/unit-tests/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody-test.tsx b/test/unit-tests/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody-test.tsx deleted file mode 100644 index a6dd10e5c1b..00000000000 --- a/test/unit-tests/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody-test.tsx +++ /dev/null @@ -1,249 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022, 2023 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; -import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix"; -import { act, render, RenderResult, screen } from "jest-matrix-react"; -import userEvent from "@testing-library/user-event"; -import { mocked } from "jest-mock"; - -import { - VoiceBroadcastInfoState, - VoiceBroadcastLiveness, - VoiceBroadcastPlayback, - VoiceBroadcastPlaybackBody, - VoiceBroadcastPlaybackEvent, - VoiceBroadcastPlaybackState, -} from "../../../../../src/voice-broadcast"; -import { filterConsole, stubClient } from "../../../../test-utils"; -import { mkVoiceBroadcastInfoStateEvent } from "../../utils/test-utils"; -import dis from "../../../../../src/dispatcher/dispatcher"; -import { Action } from "../../../../../src/dispatcher/actions"; -import { SdkContextClass } from "../../../../../src/contexts/SDKContext"; - -jest.mock("../../../../../src/dispatcher/dispatcher"); - -// mock RoomAvatar, because it is doing too much fancy stuff -jest.mock("../../../../../src/components/views/avatars/RoomAvatar", () => ({ - __esModule: true, - default: jest.fn().mockImplementation(({ room }) => { - return
room avatar: {room.name}
; - }), -})); - -describe("VoiceBroadcastPlaybackBody", () => { - const userId = "@user:example.com"; - const roomId = "!room:example.com"; - const duration = 23 * 60 + 42; // 23:42 - let client: MatrixClient; - let infoEvent: MatrixEvent; - let playback: VoiceBroadcastPlayback; - let renderResult: RenderResult; - - filterConsole( - // expected for some tests - "voice broadcast chunk event to skip to not found", - ); - - beforeAll(() => { - client = stubClient(); - mocked(client.relations).mockClear(); - mocked(client.relations).mockResolvedValue({ events: [] }); - - infoEvent = mkVoiceBroadcastInfoStateEvent( - roomId, - VoiceBroadcastInfoState.Stopped, - userId, - client.getDeviceId(), - ); - }); - - beforeEach(() => { - playback = new VoiceBroadcastPlayback( - infoEvent, - client, - SdkContextClass.instance.voiceBroadcastRecordingsStore, - ); - jest.spyOn(playback, "toggle").mockImplementation(() => Promise.resolve()); - jest.spyOn(playback, "getLiveness"); - jest.spyOn(playback, "getState"); - jest.spyOn(playback, "skipTo"); - jest.spyOn(playback, "durationSeconds", "get").mockReturnValue(duration); - }); - - describe("when rendering a buffering voice broadcast", () => { - beforeEach(() => { - mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Buffering); - mocked(playback.getLiveness).mockReturnValue("live"); - renderResult = render(); - }); - - it("should render as expected", () => { - expect(renderResult.container).toMatchSnapshot(); - }); - }); - - describe("when rendering a playing broadcast", () => { - beforeEach(() => { - mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Playing); - mocked(playback.getLiveness).mockReturnValue("not-live"); - renderResult = render(); - }); - - it("should render as expected", () => { - expect(renderResult.container).toMatchSnapshot(); - }); - - describe("and being in the middle of the playback", () => { - beforeEach(() => { - act(() => { - playback.emit(VoiceBroadcastPlaybackEvent.TimesChanged, { - duration, - position: 10 * 60, - timeLeft: duration - 10 * 60, - }); - }); - }); - - describe("and clicking 30s backward", () => { - beforeEach(async () => { - await act(async () => { - await userEvent.click(screen.getByLabelText("30s backward")); - }); - }); - - it("should seek 30s backward", () => { - expect(playback.skipTo).toHaveBeenCalledWith(9 * 60 + 30); - }); - }); - - describe("and clicking 30s forward", () => { - beforeEach(async () => { - await act(async () => { - await userEvent.click(screen.getByLabelText("30s forward")); - }); - }); - - it("should seek 30s forward", () => { - expect(playback.skipTo).toHaveBeenCalledWith(10 * 60 + 30); - }); - }); - }); - - describe("and clicking the room name", () => { - beforeEach(async () => { - await userEvent.click(screen.getByText("My room")); - }); - - it("should not view the room", () => { - expect(dis.dispatch).not.toHaveBeenCalled(); - }); - }); - }); - - describe("when rendering a playing broadcast in pip mode", () => { - beforeEach(() => { - mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Playing); - mocked(playback.getLiveness).mockReturnValue("not-live"); - renderResult = render(); - }); - - it("should render as expected", () => { - expect(renderResult.container).toMatchSnapshot(); - }); - - describe("and clicking the room name", () => { - beforeEach(async () => { - await userEvent.click(screen.getByText("My room")); - }); - - it("should view the room", () => { - expect(dis.dispatch).toHaveBeenCalledWith({ - action: Action.ViewRoom, - room_id: roomId, - metricsTrigger: undefined, - }); - }); - }); - }); - - describe(`when rendering a stopped broadcast`, () => { - beforeEach(() => { - mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Stopped); - mocked(playback.getLiveness).mockReturnValue("not-live"); - renderResult = render(); - }); - - it("should render as expected", () => { - expect(renderResult.container).toMatchSnapshot(); - }); - - describe("and clicking the play button", () => { - beforeEach(async () => { - await userEvent.click(renderResult.getByLabelText("play voice broadcast")); - }); - - it("should toggle the recording", () => { - expect(playback.toggle).toHaveBeenCalled(); - }); - }); - - describe("and the times update", () => { - beforeEach(() => { - act(() => { - playback.emit(VoiceBroadcastPlaybackEvent.TimesChanged, { - duration, - position: 5 * 60 + 13, - timeLeft: 7 * 60 + 5, - }); - }); - }); - - it("should render the times", async () => { - expect(await screen.findByText("05:13")).toBeInTheDocument(); - expect(await screen.findByText("-07:05")).toBeInTheDocument(); - }); - }); - }); - - describe("when rendering an error broadcast", () => { - beforeEach(() => { - mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Error); - renderResult = render(); - }); - - it("should render as expected", () => { - expect(renderResult.container).toMatchSnapshot(); - }); - }); - - describe.each([ - [VoiceBroadcastPlaybackState.Paused, "not-live"], - [VoiceBroadcastPlaybackState.Playing, "live"], - ] satisfies [VoiceBroadcastPlaybackState, VoiceBroadcastLiveness][])( - "when rendering a %s/%s broadcast", - (state: VoiceBroadcastPlaybackState, liveness: VoiceBroadcastLiveness) => { - beforeEach(() => { - mocked(playback.getState).mockReturnValue(state); - mocked(playback.getLiveness).mockReturnValue(liveness); - renderResult = render(); - }); - - it("should render as expected", () => { - expect(renderResult.container).toMatchSnapshot(); - }); - }, - ); - - it("when there is a broadcast without sender, it should raise an error", () => { - infoEvent.sender = null; - expect(() => { - render(); - }).toThrow(`Voice Broadcast sender not found (event ${playback.infoEvent.getId()})`); - }); -}); diff --git a/test/unit-tests/voice-broadcast/components/molecules/VoiceBroadcastPreRecordingPip-test.tsx b/test/unit-tests/voice-broadcast/components/molecules/VoiceBroadcastPreRecordingPip-test.tsx deleted file mode 100644 index c21cc8d7e43..00000000000 --- a/test/unit-tests/voice-broadcast/components/molecules/VoiceBroadcastPreRecordingPip-test.tsx +++ /dev/null @@ -1,163 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; -import { mocked } from "jest-mock"; -import { MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix"; -import { act, render, RenderResult, screen } from "jest-matrix-react"; -import userEvent from "@testing-library/user-event"; - -import { - VoiceBroadcastPlaybacksStore, - VoiceBroadcastPreRecording, - VoiceBroadcastPreRecordingPip, - VoiceBroadcastRecordingsStore, -} from "../../../../../src/voice-broadcast"; -import { flushPromises, stubClient } from "../../../../test-utils"; -import { requestMediaPermissions } from "../../../../../src/utils/media/requestMediaPermissions"; -import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../../../src/MediaDeviceHandler"; -import dis from "../../../../../src/dispatcher/dispatcher"; -import { Action } from "../../../../../src/dispatcher/actions"; - -jest.mock("../../../../../src/dispatcher/dispatcher"); -jest.mock("../../../../../src/utils/media/requestMediaPermissions"); - -// mock RoomAvatar, because it is doing too much fancy stuff -jest.mock("../../../../../src/components/views/avatars/RoomAvatar", () => ({ - __esModule: true, - default: jest.fn().mockImplementation(({ room }) => { - return
room avatar: {room.name}
; - }), -})); - -describe("VoiceBroadcastPreRecordingPip", () => { - let renderResult: RenderResult; - let preRecording: VoiceBroadcastPreRecording; - let playbacksStore: VoiceBroadcastPlaybacksStore; - let recordingsStore: VoiceBroadcastRecordingsStore; - let client: MatrixClient; - let room: Room; - let sender: RoomMember; - - const itShouldShowTheBroadcastRoom = () => { - it("should show the broadcast room", () => { - expect(dis.dispatch).toHaveBeenCalledWith({ - action: Action.ViewRoom, - room_id: room.roomId, - metricsTrigger: undefined, - }); - }); - }; - - beforeEach(() => { - client = stubClient(); - room = new Room("!room@example.com", client, client.getUserId() || ""); - sender = new RoomMember(room.roomId, client.getUserId() || ""); - recordingsStore = new VoiceBroadcastRecordingsStore(); - playbacksStore = new VoiceBroadcastPlaybacksStore(recordingsStore); - mocked(requestMediaPermissions).mockResolvedValue({ - getTracks: (): Array => [], - } as unknown as MediaStream); - jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({ - [MediaDeviceKindEnum.AudioInput]: [ - { - deviceId: "d1", - label: "Device 1", - } as MediaDeviceInfo, - { - deviceId: "d2", - label: "Device 2", - } as MediaDeviceInfo, - ], - [MediaDeviceKindEnum.AudioOutput]: [], - [MediaDeviceKindEnum.VideoInput]: [], - }); - jest.spyOn(MediaDeviceHandler.instance, "setDevice").mockImplementation(); - preRecording = new VoiceBroadcastPreRecording(room, sender, client, playbacksStore, recordingsStore); - jest.spyOn(preRecording, "start").mockResolvedValue(); - }); - - afterAll(() => { - jest.resetAllMocks(); - }); - - describe("when rendered", () => { - beforeEach(async () => { - renderResult = render(); - - await flushPromises(); - }); - - it("should match the snapshot", () => { - expect(renderResult.container).toMatchSnapshot(); - }); - - describe("and double clicking »Go live«", () => { - beforeEach(async () => { - await userEvent.click(screen.getByText("Go live")); - await userEvent.click(screen.getByText("Go live")); - }); - - it("should call start once", () => { - expect(preRecording.start).toHaveBeenCalledTimes(1); - }); - }); - - describe("and clicking the room name", () => { - beforeEach(async () => { - await userEvent.click(screen.getByText(room.name)); - }); - - itShouldShowTheBroadcastRoom(); - }); - - describe("and clicking the room avatar", () => { - beforeEach(async () => { - await userEvent.click(screen.getByText(`room avatar: ${room.name}`)); - }); - - itShouldShowTheBroadcastRoom(); - }); - - describe("and clicking the device label", () => { - beforeEach(async () => { - await act(async () => { - await userEvent.click(screen.getByText("Default Device")); - }); - }); - - it("should display the device selection", () => { - expect(screen.queryAllByText("Default Device").length).toBe(2); - expect(screen.queryByText("Device 1")).toBeInTheDocument(); - expect(screen.queryByText("Device 2")).toBeInTheDocument(); - }); - - describe("and selecting a device", () => { - beforeEach(async () => { - await act(async () => { - await userEvent.click(screen.getByText("Device 1")); - }); - }); - - it("should set it as current device", () => { - expect(MediaDeviceHandler.instance.setDevice).toHaveBeenCalledWith( - "d1", - MediaDeviceKindEnum.AudioInput, - ); - }); - - it("should not show the device selection", () => { - expect(screen.queryByText("Default Device")).not.toBeInTheDocument(); - // expected to be one in the document, displayed in the pip directly - expect(screen.queryByText("Device 1")).toBeInTheDocument(); - expect(screen.queryByText("Device 2")).not.toBeInTheDocument(); - }); - }); - }); - }); -}); diff --git a/test/unit-tests/voice-broadcast/components/molecules/VoiceBroadcastRecordingBody-test.tsx b/test/unit-tests/voice-broadcast/components/molecules/VoiceBroadcastRecordingBody-test.tsx deleted file mode 100644 index 6c4d0353739..00000000000 --- a/test/unit-tests/voice-broadcast/components/molecules/VoiceBroadcastRecordingBody-test.tsx +++ /dev/null @@ -1,79 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022, 2023 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; -import { render, RenderResult } from "jest-matrix-react"; -import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix"; - -import { - VoiceBroadcastInfoEventType, - VoiceBroadcastInfoState, - VoiceBroadcastRecording, - VoiceBroadcastRecordingBody, -} from "../../../../../src/voice-broadcast"; -import { mkEvent, stubClient } from "../../../../test-utils"; - -// mock RoomAvatar, because it is doing too much fancy stuff -jest.mock("../../../../../src/components/views/avatars/RoomAvatar", () => ({ - __esModule: true, - default: jest.fn().mockImplementation(({ room }) => { - return
room avatar: {room.name}
; - }), -})); - -describe("VoiceBroadcastRecordingBody", () => { - const userId = "@user:example.com"; - const roomId = "!room:example.com"; - let client: MatrixClient; - let infoEvent: MatrixEvent; - let recording: VoiceBroadcastRecording; - - beforeAll(() => { - client = stubClient(); - infoEvent = mkEvent({ - event: true, - type: VoiceBroadcastInfoEventType, - content: {}, - room: roomId, - user: userId, - }); - recording = new VoiceBroadcastRecording(infoEvent, client, VoiceBroadcastInfoState.Resumed); - }); - - describe("when rendering a live broadcast", () => { - let renderResult: RenderResult; - - beforeEach(() => { - renderResult = render(); - }); - - it("should render with a red live badge", () => { - expect(renderResult.container).toMatchSnapshot(); - }); - }); - - describe("when rendering a paused broadcast", () => { - let renderResult: RenderResult; - - beforeEach(async () => { - await recording.pause(); - renderResult = render(); - }); - - it("should render with a grey live badge", () => { - expect(renderResult.container).toMatchSnapshot(); - }); - }); - - it("when there is a broadcast without sender, it should raise an error", () => { - infoEvent.sender = null; - expect(() => { - render(); - }).toThrow(`Voice Broadcast sender not found (event ${recording.infoEvent.getId()})`); - }); -}); diff --git a/test/unit-tests/voice-broadcast/components/molecules/VoiceBroadcastRecordingPip-test.tsx b/test/unit-tests/voice-broadcast/components/molecules/VoiceBroadcastRecordingPip-test.tsx deleted file mode 100644 index eafa0d0af6c..00000000000 --- a/test/unit-tests/voice-broadcast/components/molecules/VoiceBroadcastRecordingPip-test.tsx +++ /dev/null @@ -1,217 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ -// - -import React from "react"; -import { render, RenderResult, screen } from "jest-matrix-react"; -import userEvent from "@testing-library/user-event"; -import { ClientEvent, MatrixClient, MatrixEvent, SyncState } from "matrix-js-sdk/src/matrix"; -import { sleep } from "matrix-js-sdk/src/utils"; -import { mocked } from "jest-mock"; - -import { - VoiceBroadcastInfoState, - VoiceBroadcastRecording, - VoiceBroadcastRecordingPip, -} from "../../../../../src/voice-broadcast"; -import { flushPromises, stubClient } from "../../../../test-utils"; -import { mkVoiceBroadcastInfoStateEvent } from "../../utils/test-utils"; -import { requestMediaPermissions } from "../../../../../src/utils/media/requestMediaPermissions"; -import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../../../src/MediaDeviceHandler"; -import dis from "../../../../../src/dispatcher/dispatcher"; -import { Action } from "../../../../../src/dispatcher/actions"; - -jest.mock("../../../../../src/dispatcher/dispatcher"); -jest.mock("../../../../../src/utils/media/requestMediaPermissions"); - -// mock RoomAvatar, because it is doing too much fancy stuff -jest.mock("../../../../../src/components/views/avatars/RoomAvatar", () => ({ - __esModule: true, - default: jest.fn().mockImplementation(({ room }) => { - return
room avatar: {room.name}
; - }), -})); - -// mock VoiceRecording because it contains all the audio APIs -jest.mock("../../../../../src/audio/VoiceRecording", () => ({ - VoiceRecording: jest.fn().mockReturnValue({ - disableMaxLength: jest.fn(), - liveData: { - onUpdate: jest.fn(), - }, - start: jest.fn(), - }), -})); - -describe("VoiceBroadcastRecordingPip", () => { - const roomId = "!room:example.com"; - let client: MatrixClient; - let infoEvent: MatrixEvent; - let recording: VoiceBroadcastRecording; - let renderResult: RenderResult; - - const renderPip = async (state: VoiceBroadcastInfoState) => { - infoEvent = mkVoiceBroadcastInfoStateEvent(roomId, state, client.getUserId() || "", client.getDeviceId() || ""); - recording = new VoiceBroadcastRecording(infoEvent, client, state); - jest.spyOn(recording, "pause"); - jest.spyOn(recording, "resume"); - renderResult = render(); - await flushPromises(); - }; - - const itShouldShowTheBroadcastRoom = () => { - it("should show the broadcast room", () => { - expect(dis.dispatch).toHaveBeenCalledWith({ - action: Action.ViewRoom, - room_id: roomId, - metricsTrigger: undefined, - }); - }); - }; - - beforeAll(() => { - client = stubClient(); - mocked(requestMediaPermissions).mockResolvedValue({ - getTracks: (): Array => [], - } as unknown as MediaStream); - jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({ - [MediaDeviceKindEnum.AudioInput]: [ - { - deviceId: "d1", - label: "Device 1", - } as MediaDeviceInfo, - { - deviceId: "d2", - label: "Device 2", - } as MediaDeviceInfo, - ], - [MediaDeviceKindEnum.AudioOutput]: [], - [MediaDeviceKindEnum.VideoInput]: [], - }); - jest.spyOn(MediaDeviceHandler.instance, "setDevice").mockImplementation(); - }); - - describe("when rendering a started recording", () => { - beforeEach(async () => { - await renderPip(VoiceBroadcastInfoState.Started); - }); - - it("should render as expected", () => { - expect(renderResult.container).toMatchSnapshot(); - }); - - describe("and selecting another input device", () => { - beforeEach(async () => { - await userEvent.click(screen.getByLabelText("Change input device")); - await userEvent.click(screen.getByText("Device 1")); - }); - - it("should select the device and pause and resume the broadcast", () => { - expect(MediaDeviceHandler.instance.setDevice).toHaveBeenCalledWith( - "d1", - MediaDeviceKindEnum.AudioInput, - ); - expect(recording.pause).toHaveBeenCalled(); - expect(recording.resume).toHaveBeenCalled(); - }); - }); - - describe("and clicking the room name", () => { - beforeEach(async () => { - await userEvent.click(screen.getByText("My room")); - }); - - itShouldShowTheBroadcastRoom(); - }); - - describe("and clicking the room avatar", () => { - beforeEach(async () => { - await userEvent.click(screen.getByText("room avatar: My room")); - }); - - itShouldShowTheBroadcastRoom(); - }); - - describe("and clicking the pause button", () => { - beforeEach(async () => { - await userEvent.click(screen.getByLabelText("pause voice broadcast")); - }); - - it("should pause the recording", () => { - expect(recording.getState()).toBe(VoiceBroadcastInfoState.Paused); - }); - }); - - describe("and clicking the stop button", () => { - beforeEach(async () => { - await userEvent.click(screen.getByLabelText("Stop Recording")); - await screen.findByText("Stop live broadcasting?"); - // modal rendering has some weird sleeps - await sleep(200); - }); - - it("should display the confirm end dialog", () => { - screen.getByText("Stop live broadcasting?"); - }); - - describe("and confirming the dialog", () => { - beforeEach(async () => { - await userEvent.click(screen.getByText("Yes, stop broadcast")); - }); - - it("should end the recording", () => { - expect(recording.getState()).toBe(VoiceBroadcastInfoState.Stopped); - }); - }); - }); - - describe("and there is no connection and clicking the pause button", () => { - beforeEach(async () => { - mocked(client.sendStateEvent).mockImplementation(() => { - throw new Error(); - }); - await userEvent.click(screen.getByLabelText("pause voice broadcast")); - }); - - it("should show a connection error info", () => { - expect(screen.getByText("Connection error - Recording paused")).toBeInTheDocument(); - }); - - describe("and the connection is back", () => { - beforeEach(() => { - mocked(client.sendStateEvent).mockResolvedValue({ event_id: "e1" }); - client.emit(ClientEvent.Sync, SyncState.Catchup, SyncState.Error); - }); - - it("should render a paused recording", async () => { - await expect(screen.findByLabelText("resume voice broadcast")).resolves.toBeInTheDocument(); - }); - }); - }); - }); - - describe("when rendering a paused recording", () => { - beforeEach(async () => { - await renderPip(VoiceBroadcastInfoState.Paused); - }); - - it("should render as expected", () => { - expect(renderResult.container).toMatchSnapshot(); - }); - - describe("and clicking the resume button", () => { - beforeEach(async () => { - await userEvent.click(screen.getByLabelText("resume voice broadcast")); - }); - - it("should resume the recording", () => { - expect(recording.getState()).toBe(VoiceBroadcastInfoState.Resumed); - }); - }); - }); -}); diff --git a/test/unit-tests/voice-broadcast/components/molecules/VoiceBroadcastSmallPlaybackBody-test.tsx b/test/unit-tests/voice-broadcast/components/molecules/VoiceBroadcastSmallPlaybackBody-test.tsx deleted file mode 100644 index b65bbcf583a..00000000000 --- a/test/unit-tests/voice-broadcast/components/molecules/VoiceBroadcastSmallPlaybackBody-test.tsx +++ /dev/null @@ -1,129 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; -import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix"; -import { render, RenderResult } from "jest-matrix-react"; -import userEvent from "@testing-library/user-event"; -import { mocked } from "jest-mock"; - -import { - VoiceBroadcastInfoState, - VoiceBroadcastLiveness, - VoiceBroadcastPlayback, - VoiceBroadcastSmallPlaybackBody, - VoiceBroadcastPlaybackState, -} from "../../../../../src/voice-broadcast"; -import { stubClient } from "../../../../test-utils"; -import { mkVoiceBroadcastInfoStateEvent } from "../../utils/test-utils"; -import { SdkContextClass } from "../../../../../src/contexts/SDKContext"; - -// mock RoomAvatar, because it is doing too much fancy stuff -jest.mock("../../../../../src/components/views/avatars/RoomAvatar", () => ({ - __esModule: true, - default: jest.fn().mockImplementation(({ room }) => { - return
room avatar: {room.name}
; - }), -})); - -describe("", () => { - const userId = "@user:example.com"; - const roomId = "!room:example.com"; - let client: MatrixClient; - let infoEvent: MatrixEvent; - let playback: VoiceBroadcastPlayback; - let renderResult: RenderResult; - - beforeAll(() => { - client = stubClient(); - mocked(client.relations).mockClear(); - mocked(client.relations).mockResolvedValue({ events: [] }); - - infoEvent = mkVoiceBroadcastInfoStateEvent( - roomId, - VoiceBroadcastInfoState.Stopped, - userId, - client.getDeviceId()!, - ); - }); - - beforeEach(() => { - playback = new VoiceBroadcastPlayback( - infoEvent, - client, - SdkContextClass.instance.voiceBroadcastRecordingsStore, - ); - jest.spyOn(playback, "toggle").mockImplementation(() => Promise.resolve()); - jest.spyOn(playback, "getLiveness"); - jest.spyOn(playback, "getState"); - }); - - describe("when rendering a buffering broadcast", () => { - beforeEach(() => { - mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Buffering); - mocked(playback.getLiveness).mockReturnValue("live"); - renderResult = render(); - }); - - it("should render as expected", () => { - expect(renderResult.container).toMatchSnapshot(); - }); - }); - - describe("when rendering a playing broadcast", () => { - beforeEach(() => { - mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Playing); - mocked(playback.getLiveness).mockReturnValue("not-live"); - renderResult = render(); - }); - - it("should render as expected", () => { - expect(renderResult.container).toMatchSnapshot(); - }); - }); - - describe(`when rendering a stopped broadcast`, () => { - beforeEach(() => { - mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Stopped); - mocked(playback.getLiveness).mockReturnValue("not-live"); - renderResult = render(); - }); - - it("should render as expected", () => { - expect(renderResult.container).toMatchSnapshot(); - }); - - describe("and clicking the play button", () => { - beforeEach(async () => { - await userEvent.click(renderResult.getByLabelText("play voice broadcast")); - }); - - it("should toggle the playback", () => { - expect(playback.toggle).toHaveBeenCalled(); - }); - }); - }); - - describe.each([ - { state: VoiceBroadcastPlaybackState.Paused, liveness: "not-live" }, - { state: VoiceBroadcastPlaybackState.Playing, liveness: "live" }, - ] as Array<{ state: VoiceBroadcastPlaybackState; liveness: VoiceBroadcastLiveness }>)( - "when rendering a %s/%s broadcast", - ({ state, liveness }) => { - beforeEach(() => { - mocked(playback.getState).mockReturnValue(state); - mocked(playback.getLiveness).mockReturnValue(liveness); - renderResult = render(); - }); - - it("should render as expected", () => { - expect(renderResult.container).toMatchSnapshot(); - }); - }, - ); -}); diff --git a/test/unit-tests/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastPlaybackBody-test.tsx.snap b/test/unit-tests/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastPlaybackBody-test.tsx.snap deleted file mode 100644 index cb063c395c2..00000000000 --- a/test/unit-tests/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastPlaybackBody-test.tsx.snap +++ /dev/null @@ -1,914 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`VoiceBroadcastPlaybackBody when rendering a buffering voice broadcast should render as expected 1`] = ` -
-
-
-
- room avatar: - My room -
-
-
-
- My room -
-
-
- - - - - - @user:example.com - -
-
-
-
-
- Buffering… -
-
-
-
- Live -
-
-
-
-
-
-
- - - -
-
-
-
-
- -
- - -
-
-
-`; - -exports[`VoiceBroadcastPlaybackBody when rendering a pause/not-live broadcast should render as expected 1`] = ` -
-
-
-
- room avatar: - My room -
-
-
-
- My room -
-
-
- - - - - - @user:example.com - -
-
-
- Voice broadcast -
-
-
-
-
-
-
-
- - - -
-
-
-
-
- -
- - -
-
-
-`; - -exports[`VoiceBroadcastPlaybackBody when rendering a playing broadcast in pip mode should render as expected 1`] = ` -
-
-
-
-
- room avatar: - My room -
-
-
-
-
-
- My room -
-
-
-
- - - - - - @user:example.com - -
-
-
- Voice broadcast -
-
-
-
-
-
-
-
- - - -
-
-
-
-
- -
- - -
-
-
-`; - -exports[`VoiceBroadcastPlaybackBody when rendering a playing broadcast should render as expected 1`] = ` -
-
-
-
- room avatar: - My room -
-
-
-
- My room -
-
-
- - - - - - @user:example.com - -
-
-
- Voice broadcast -
-
-
-
-
-
-
-
- - - -
-
-
-
-
- -
- - -
-
-
-`; - -exports[`VoiceBroadcastPlaybackBody when rendering a playing/live broadcast should render as expected 1`] = ` -
-
-
-
- room avatar: - My room -
-
-
-
- My room -
-
-
- - - - - - @user:example.com - -
-
-
- Voice broadcast -
-
-
-
- Live -
-
-
-
-
-
-
- - - -
-
-
-
-
- -
- - -
-
-
-`; - -exports[`VoiceBroadcastPlaybackBody when rendering a stopped broadcast should render as expected 1`] = ` -
-
-
-
- room avatar: - My room -
-
-
-
- My room -
-
-
- - - - - - @user:example.com - -
-
-
- Voice broadcast -
-
-
-
-
- - - -
-
- -
- - -
-
-
-`; - -exports[`VoiceBroadcastPlaybackBody when rendering an error broadcast should render as expected 1`] = ` -
-
-
-
- room avatar: - My room -
-
-
-
- My room -
-
-
- - - - - - @user:example.com - -
-
-
- Voice broadcast -
-
-
-
- - - - - Unable to play this voice broadcast -
-
-
-`; diff --git a/test/unit-tests/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastPreRecordingPip-test.tsx.snap b/test/unit-tests/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastPreRecordingPip-test.tsx.snap deleted file mode 100644 index f50cdc3be40..00000000000 --- a/test/unit-tests/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastPreRecordingPip-test.tsx.snap +++ /dev/null @@ -1,98 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`VoiceBroadcastPreRecordingPip when rendered should match the snapshot 1`] = ` -
-
-
-
-
- room avatar: - !room@example.com -
-
-
-
-
-
- !room@example.com -
-
-
-
- - - - - - Default Device - -
-
-
- - - -
-
-
-
- Go live -
-
-
-`; diff --git a/test/unit-tests/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastRecordingBody-test.tsx.snap b/test/unit-tests/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastRecordingBody-test.tsx.snap deleted file mode 100644 index c2e6fdcd542..00000000000 --- a/test/unit-tests/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastRecordingBody-test.tsx.snap +++ /dev/null @@ -1,131 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`VoiceBroadcastRecordingBody when rendering a live broadcast should render with a red live badge 1`] = ` -
-
-
-
- room avatar: - My room -
-
-
-
- My room -
-
-
- - - - - - @user:example.com - -
-
-
-
- Live -
-
-
-
-`; - -exports[`VoiceBroadcastRecordingBody when rendering a paused broadcast should render with a grey live badge 1`] = ` -
-
-
-
- room avatar: - My room -
-
-
-
- My room -
-
-
- - - - - - @user:example.com - -
-
-
-
- Live -
-
-
-
-`; diff --git a/test/unit-tests/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastRecordingPip-test.tsx.snap b/test/unit-tests/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastRecordingPip-test.tsx.snap deleted file mode 100644 index 2fc23345754..00000000000 --- a/test/unit-tests/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastRecordingPip-test.tsx.snap +++ /dev/null @@ -1,238 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`VoiceBroadcastRecordingPip when rendering a paused recording should render as expected 1`] = ` -
-
-
-
-
- room avatar: - My room -
-
-
-
-
-
- My room -
-
-
-
-
- -
-
-
-
- Live -
-
-
-
-
-
-
-
- - - - -
-
-
-
-
-
-
-`; - -exports[`VoiceBroadcastRecordingPip when rendering a started recording should render as expected 1`] = ` -
-
-
-
-
- room avatar: - My room -
-
-
-
-
-
- My room -
-
-
-
-
- -
-
-
-
- Live -
-
-
-
-
- - - -
-
- - - - -
-
-
-
-
-
-
-`; diff --git a/test/unit-tests/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastSmallPlaybackBody-test.tsx.snap b/test/unit-tests/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastSmallPlaybackBody-test.tsx.snap deleted file mode 100644 index 088151158bf..00000000000 --- a/test/unit-tests/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastSmallPlaybackBody-test.tsx.snap +++ /dev/null @@ -1,558 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` when rendering a { state: 'pause', liveness: 'not-live' }/%s broadcast should render as expected 1`] = ` -
-
-
-
-
- room avatar: - My room -
-
-
-
-
-
- My room -
-
-
-
- - - - - - @user:example.com - -
-
-
-
- - - -
-
- - - -
-
-
-`; - -exports[` when rendering a { state: 'playing', liveness: 'live' }/%s broadcast should render as expected 1`] = ` -
-
-
-
-
- room avatar: - My room -
-
-
-
-
-
- My room -
-
-
-
- - - - - - @user:example.com - -
-
-
- Live -
-
-
-
- - - -
-
- - - -
-
-
-`; - -exports[` when rendering a buffering broadcast should render as expected 1`] = ` -
-
-
-
-
- room avatar: - My room -
-
-
-
-
-
- My room -
-
-
-
-
-
-
- - - - - - @user:example.com - -
-
-
- Live -
-
-
-
- - - -
-
- - - -
-
-
-`; - -exports[` when rendering a playing broadcast should render as expected 1`] = ` -
-
-
-
-
- room avatar: - My room -
-
-
-
-
-
- My room -
-
-
-
- - - - - - @user:example.com - -
-
-
-
- - - -
-
- - - -
-
-
-`; - -exports[` when rendering a stopped broadcast should render as expected 1`] = ` -
-
-
-
-
- room avatar: - My room -
-
-
-
-
-
- My room -
-
-
-
- - - - - - @user:example.com - -
-
-
-
- - - -
-
- - - -
-
-
-`; diff --git a/test/unit-tests/voice-broadcast/models/VoiceBroadcastPlayback-test.tsx b/test/unit-tests/voice-broadcast/models/VoiceBroadcastPlayback-test.tsx deleted file mode 100644 index 9ce24e5921b..00000000000 --- a/test/unit-tests/voice-broadcast/models/VoiceBroadcastPlayback-test.tsx +++ /dev/null @@ -1,747 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { mocked } from "jest-mock"; -import { screen } from "jest-matrix-react"; -import userEvent from "@testing-library/user-event"; -import { MatrixClient, MatrixEvent, MatrixEventEvent, Room } from "matrix-js-sdk/src/matrix"; -import { defer } from "matrix-js-sdk/src/utils"; - -import { Playback, PlaybackState } from "../../../../src/audio/Playback"; -import { PlaybackManager } from "../../../../src/audio/PlaybackManager"; -import { SdkContextClass } from "../../../../src/contexts/SDKContext"; -import { MediaEventHelper } from "../../../../src/utils/MediaEventHelper"; -import { - VoiceBroadcastInfoState, - VoiceBroadcastLiveness, - VoiceBroadcastPlayback, - VoiceBroadcastPlaybackEvent, - VoiceBroadcastPlaybackState, - VoiceBroadcastRecording, -} from "../../../../src/voice-broadcast"; -import { - filterConsole, - flushPromises, - flushPromisesWithFakeTimers, - stubClient, - waitEnoughCyclesForModal, -} from "../../../test-utils"; -import { createTestPlayback } from "../../../test-utils/audio"; -import { mkVoiceBroadcastChunkEvent, mkVoiceBroadcastInfoStateEvent } from "../utils/test-utils"; -import { LazyValue } from "../../../../src/utils/LazyValue"; - -jest.mock("../../../../src/utils/MediaEventHelper", () => ({ - MediaEventHelper: jest.fn(), -})); - -describe("VoiceBroadcastPlayback", () => { - const userId = "@user:example.com"; - let deviceId: string; - const roomId = "!room:example.com"; - let room: Room; - let client: MatrixClient; - let infoEvent: MatrixEvent; - let playback: VoiceBroadcastPlayback; - let onStateChanged: (state: VoiceBroadcastPlaybackState) => void; - let chunk1Event: MatrixEvent; - let deplayedChunk1Event: MatrixEvent; - let chunk2Event: MatrixEvent; - let chunk2BEvent: MatrixEvent; - let chunk3Event: MatrixEvent; - const chunk1Length = 2300; - const chunk2Length = 4200; - const chunk3Length = 6900; - const chunk1Data = new ArrayBuffer(2); - const chunk2Data = new ArrayBuffer(3); - const chunk3Data = new ArrayBuffer(3); - let delayedChunk1Helper: MediaEventHelper; - let chunk1Helper: MediaEventHelper; - let chunk2Helper: MediaEventHelper; - let chunk3Helper: MediaEventHelper; - let chunk1Playback: Playback; - let chunk2Playback: Playback; - let chunk3Playback: Playback; - let middleOfSecondChunk!: number; - let middleOfThirdChunk!: number; - - const queryConfirmListeningDialog = () => { - return screen.queryByText( - "If you start listening to this live broadcast, your current live broadcast recording will be ended.", - ); - }; - - const itShouldSetTheStateTo = (state: VoiceBroadcastPlaybackState) => { - it(`should set the state to ${state}`, () => { - expect(playback.getState()).toBe(state); - }); - }; - - const itShouldEmitAStateChangedEvent = (state: VoiceBroadcastPlaybackState) => { - it(`should emit a ${state} state changed event`, () => { - expect(mocked(onStateChanged)).toHaveBeenCalledWith(state, playback); - }); - }; - - const itShouldHaveLiveness = (liveness: VoiceBroadcastLiveness): void => { - it(`should have liveness ${liveness}`, () => { - expect(playback.getLiveness()).toBe(liveness); - }); - }; - - const startPlayback = () => { - beforeEach(() => { - playback.start(); - }); - }; - - const pausePlayback = () => { - beforeEach(() => { - playback.pause(); - }); - }; - - const stopPlayback = () => { - beforeEach(() => { - playback.stop(); - }); - }; - - const mkChunkHelper = (data: ArrayBuffer): MediaEventHelper => { - return { - sourceBlob: { - cachedValue: new Blob(), - done: false, - value: { - // @ts-ignore - arrayBuffer: jest.fn().mockResolvedValue(data), - }, - }, - }; - }; - - const mkDeplayedChunkHelper = (data: ArrayBuffer): MediaEventHelper => { - const deferred = defer>(); - - setTimeout(() => { - deferred.resolve({ - // @ts-ignore - arrayBuffer: jest.fn().mockResolvedValue(data), - }); - }, 7500); - - return { - sourceBlob: { - cachedValue: new Blob(), - done: false, - // @ts-ignore - value: deferred.promise, - }, - }; - }; - - const simulateFirstChunkArrived = async (): Promise => { - jest.advanceTimersByTime(10000); - await flushPromisesWithFakeTimers(); - }; - - const mkInfoEvent = (state: VoiceBroadcastInfoState) => { - return mkVoiceBroadcastInfoStateEvent(roomId, state, userId, deviceId); - }; - - const mkPlayback = async (fakeTimers = false): Promise => { - const playback = new VoiceBroadcastPlayback( - infoEvent, - client, - SdkContextClass.instance.voiceBroadcastRecordingsStore, - ); - jest.spyOn(playback, "removeAllListeners"); - jest.spyOn(playback, "destroy"); - playback.on(VoiceBroadcastPlaybackEvent.StateChanged, onStateChanged); - if (fakeTimers) { - await flushPromisesWithFakeTimers(); - } else { - await flushPromises(); - } - return playback; - }; - - const setUpChunkEvents = (chunkEvents: MatrixEvent[]) => { - mocked(client.relations).mockResolvedValueOnce({ - events: chunkEvents, - }); - }; - - const createChunkEvents = () => { - chunk1Event = mkVoiceBroadcastChunkEvent(infoEvent.getId()!, userId, roomId, chunk1Length, 1); - deplayedChunk1Event = mkVoiceBroadcastChunkEvent(infoEvent.getId()!, userId, roomId, chunk1Length, 1); - chunk2Event = mkVoiceBroadcastChunkEvent(infoEvent.getId()!, userId, roomId, chunk2Length, 2); - chunk2Event.setTxnId("tx-id-1"); - chunk2BEvent = mkVoiceBroadcastChunkEvent(infoEvent.getId()!, userId, roomId, chunk2Length, 2); - chunk2BEvent.setTxnId("tx-id-1"); - chunk3Event = mkVoiceBroadcastChunkEvent(infoEvent.getId()!, userId, roomId, chunk3Length, 3); - - chunk1Helper = mkChunkHelper(chunk1Data); - delayedChunk1Helper = mkDeplayedChunkHelper(chunk1Data); - chunk2Helper = mkChunkHelper(chunk2Data); - chunk3Helper = mkChunkHelper(chunk3Data); - - chunk1Playback = createTestPlayback(); - chunk2Playback = createTestPlayback(); - chunk3Playback = createTestPlayback(); - - middleOfSecondChunk = (chunk1Length + chunk2Length / 2) / 1000; - middleOfThirdChunk = (chunk1Length + chunk2Length + chunk3Length / 2) / 1000; - - jest.spyOn(PlaybackManager.instance, "createPlaybackInstance").mockImplementation( - (buffer: ArrayBuffer, _waveForm?: number[]) => { - if (buffer === chunk1Data) return chunk1Playback; - if (buffer === chunk2Data) return chunk2Playback; - if (buffer === chunk3Data) return chunk3Playback; - - throw new Error("unexpected buffer"); - }, - ); - - mocked(MediaEventHelper).mockImplementation((event: MatrixEvent): any => { - if (event === chunk1Event) return chunk1Helper; - if (event === deplayedChunk1Event) return delayedChunk1Helper; - if (event === chunk2Event) return chunk2Helper; - if (event === chunk3Event) return chunk3Helper; - }); - }; - - filterConsole( - // expected for some tests - "Unable to load broadcast playback", - ); - - beforeEach(() => { - client = stubClient(); - deviceId = client.getDeviceId() || ""; - room = new Room(roomId, client, client.getSafeUserId()); - mocked(client.getRoom).mockImplementation((roomId: string): Room | null => { - if (roomId === room.roomId) return room; - return null; - }); - onStateChanged = jest.fn(); - }); - - afterEach(async () => { - SdkContextClass.instance.voiceBroadcastPlaybacksStore.getCurrent()?.stop(); - SdkContextClass.instance.voiceBroadcastPlaybacksStore.clearCurrent(); - await SdkContextClass.instance.voiceBroadcastRecordingsStore.getCurrent()?.stop(); - SdkContextClass.instance.voiceBroadcastRecordingsStore.clearCurrent(); - playback.destroy(); - }); - - describe(`when there is a ${VoiceBroadcastInfoState.Resumed} broadcast without chunks yet`, () => { - beforeEach(async () => { - infoEvent = mkInfoEvent(VoiceBroadcastInfoState.Resumed); - createChunkEvents(); - room.addLiveEvents([infoEvent], { addToState: true }); - playback = await mkPlayback(); - }); - - describe("and calling start", () => { - startPlayback(); - - itShouldHaveLiveness("live"); - - it("should be in buffering state", () => { - expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Buffering); - }); - - it("should have duration 0", () => { - expect(playback.durationSeconds).toBe(0); - }); - - it("should be at time 0", () => { - expect(playback.timeSeconds).toBe(0); - }); - - describe("and calling stop", () => { - stopPlayback(); - itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Stopped); - - describe("and calling pause", () => { - pausePlayback(); - // stopped voice broadcasts cannot be paused - itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Stopped); - }); - }); - - describe("and calling pause", () => { - pausePlayback(); - itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Paused); - }); - - describe("and receiving the first chunk", () => { - beforeEach(() => { - room.relations.aggregateChildEvent(chunk1Event); - }); - - itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Playing); - itShouldHaveLiveness("live"); - - it("should update the duration", () => { - expect(playback.durationSeconds).toBe(2.3); - }); - - it("should play the first chunk", () => { - expect(chunk1Playback.play).toHaveBeenCalled(); - }); - }); - - describe("and receiving the first undecryptable chunk", () => { - beforeEach(() => { - jest.spyOn(chunk1Event, "isDecryptionFailure").mockReturnValue(true); - room.relations.aggregateChildEvent(chunk1Event); - }); - - itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Error); - - it("should not update the duration", () => { - expect(playback.durationSeconds).toBe(0); - }); - - describe("and the chunk is decrypted", () => { - beforeEach(() => { - mocked(chunk1Event.isDecryptionFailure).mockReturnValue(false); - chunk1Event.emit(MatrixEventEvent.Decrypted, chunk1Event); - }); - - itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Paused); - - it("should not update the duration", () => { - expect(playback.durationSeconds).toBe(2.3); - }); - }); - }); - }); - }); - - describe(`when there is a ${VoiceBroadcastInfoState.Resumed} voice broadcast with some chunks`, () => { - beforeEach(async () => { - mocked(client.relations).mockResolvedValueOnce({ events: [] }); - infoEvent = mkInfoEvent(VoiceBroadcastInfoState.Resumed); - createChunkEvents(); - setUpChunkEvents([chunk2Event, chunk1Event]); - room.addLiveEvents([infoEvent, chunk1Event, chunk2Event], { addToState: true }); - room.relations.aggregateChildEvent(chunk2Event); - room.relations.aggregateChildEvent(chunk1Event); - playback = await mkPlayback(); - }); - - it("durationSeconds should have the length of the known chunks", () => { - expect(playback.durationSeconds).toEqual(6.5); - }); - - describe("and starting a playback with a broken chunk", () => { - beforeEach(async () => { - mocked(chunk2Playback.prepare).mockRejectedValue("Error decoding chunk"); - await playback.start(); - }); - - itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Error); - - it("start() should keep it in the error state)", async () => { - await playback.start(); - expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Error); - }); - - it("stop() should keep it in the error state)", () => { - playback.stop(); - expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Error); - }); - - it("toggle() should keep it in the error state)", async () => { - await playback.toggle(); - expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Error); - }); - - it("pause() should keep it in the error state)", () => { - playback.pause(); - expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Error); - }); - }); - - describe("and an event with the same transaction Id occurs", () => { - beforeEach(() => { - room.addLiveEvents([chunk2BEvent], { addToState: true }); - room.relations.aggregateChildEvent(chunk2BEvent); - }); - - it("durationSeconds should not change", () => { - expect(playback.durationSeconds).toEqual(6.5); - }); - }); - - describe("and calling start", () => { - startPlayback(); - - it("should play the last chunk", () => { - expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Playing); - // assert that the last chunk is played first - expect(chunk2Playback.play).toHaveBeenCalled(); - expect(chunk1Playback.play).not.toHaveBeenCalled(); - }); - - describe( - "and receiving a stop info event with last_chunk_sequence = 2 and " + - "the playback of the last available chunk ends", - () => { - beforeEach(() => { - const stoppedEvent = mkVoiceBroadcastInfoStateEvent( - roomId, - VoiceBroadcastInfoState.Stopped, - client.getSafeUserId(), - client.deviceId!, - infoEvent, - 2, - ); - room.addLiveEvents([stoppedEvent], { addToState: true }); - room.relations.aggregateChildEvent(stoppedEvent); - chunk2Playback.emit(PlaybackState.Stopped); - }); - - itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Stopped); - }, - ); - - describe( - "and receiving a stop info event with last_chunk_sequence = 3 and " + - "the playback of the last available chunk ends", - () => { - beforeEach(() => { - const stoppedEvent = mkVoiceBroadcastInfoStateEvent( - roomId, - VoiceBroadcastInfoState.Stopped, - client.getSafeUserId(), - client.deviceId!, - infoEvent, - 3, - ); - room.addLiveEvents([stoppedEvent], { addToState: true }); - room.relations.aggregateChildEvent(stoppedEvent); - chunk2Playback.emit(PlaybackState.Stopped); - }); - - itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Buffering); - - describe("and the next chunk arrives", () => { - beforeEach(() => { - room.addLiveEvents([chunk3Event], { addToState: true }); - room.relations.aggregateChildEvent(chunk3Event); - }); - - itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Playing); - - it("should play the next chunk", () => { - expect(chunk3Playback.play).toHaveBeenCalled(); - }); - }); - }, - ); - - describe("and the info event is deleted", () => { - beforeEach(() => { - infoEvent.makeRedacted(new MatrixEvent({}), room); - }); - - it("should stop and destroy the playback", () => { - expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Stopped); - expect(playback.destroy).toHaveBeenCalled(); - }); - }); - }); - - describe("and currently recording a broadcast", () => { - let recording: VoiceBroadcastRecording; - - beforeEach(async () => { - recording = new VoiceBroadcastRecording( - mkVoiceBroadcastInfoStateEvent( - roomId, - VoiceBroadcastInfoState.Started, - client.getSafeUserId(), - client.deviceId, - ), - client, - ); - jest.spyOn(recording, "stop"); - SdkContextClass.instance.voiceBroadcastRecordingsStore.setCurrent(recording); - playback.start(); - await waitEnoughCyclesForModal(); - }); - - it("should display a confirm modal", () => { - expect(queryConfirmListeningDialog()).toBeInTheDocument(); - }); - - describe("when confirming the dialog", () => { - beforeEach(async () => { - await userEvent.click(screen.getByText("Yes, end my recording")); - }); - - it("should stop the recording", () => { - expect(recording.stop).toHaveBeenCalled(); - expect(SdkContextClass.instance.voiceBroadcastRecordingsStore.getCurrent()).toBeNull(); - }); - - it("should not start the playback", () => { - expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Playing); - }); - }); - - describe("when not confirming the dialog", () => { - beforeEach(async () => { - await userEvent.click(screen.getByText("No")); - }); - - it("should not stop the recording", () => { - expect(recording.stop).not.toHaveBeenCalled(); - }); - - it("should start the playback", () => { - expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Stopped); - }); - }); - }); - }); - - describe("when there is a stopped voice broadcast", () => { - beforeEach(async () => { - jest.useFakeTimers(); - infoEvent = mkInfoEvent(VoiceBroadcastInfoState.Stopped); - createChunkEvents(); - // use delayed first chunk here to simulate loading time - setUpChunkEvents([chunk2Event, deplayedChunk1Event, chunk3Event]); - room.addLiveEvents([infoEvent, deplayedChunk1Event, chunk2Event, chunk3Event], { addToState: true }); - playback = await mkPlayback(true); - }); - - afterEach(() => { - jest.useRealTimers(); - }); - - it("should expose the info event", () => { - expect(playback.infoEvent).toBe(infoEvent); - }); - - itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Stopped); - - describe("and calling start", () => { - startPlayback(); - - itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Buffering); - - describe("and the first chunk data has been loaded", () => { - beforeEach(async () => { - await simulateFirstChunkArrived(); - }); - - itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Playing); - - it("should play the chunks beginning with the first one", () => { - // assert that the first chunk is being played - expect(chunk1Playback.play).toHaveBeenCalled(); - expect(chunk2Playback.play).not.toHaveBeenCalled(); - }); - - describe("and calling start again", () => { - it("should not play the first chunk a second time", () => { - expect(chunk1Playback.play).toHaveBeenCalledTimes(1); - }); - }); - - describe("and the chunk playback progresses", () => { - beforeEach(() => { - chunk1Playback.clockInfo.liveData.update([11]); - }); - - it("should update the time", () => { - expect(playback.timeSeconds).toBe(11); - }); - }); - - describe("and the chunk playback progresses across the actual time", () => { - // This can be the case if the meta data is out of sync with the actual audio data. - - beforeEach(() => { - chunk1Playback.clockInfo.liveData.update([15]); - }); - - it("should update the time", () => { - expect(playback.timeSeconds).toBe(15); - expect(playback.timeLeftSeconds).toBe(0); - }); - }); - - describe("and skipping to the middle of the second chunk", () => { - const middleOfSecondChunk = (chunk1Length + chunk2Length / 2) / 1000; - - beforeEach(async () => { - await playback.skipTo(middleOfSecondChunk); - }); - - it("should play the second chunk", () => { - expect(chunk1Playback.stop).toHaveBeenCalled(); - expect(chunk1Playback.destroy).toHaveBeenCalled(); - expect(chunk2Playback.play).toHaveBeenCalled(); - }); - - it("should update the time", () => { - expect(playback.timeSeconds).toBe(middleOfSecondChunk); - }); - - describe("and skipping to the start", () => { - beforeEach(async () => { - await playback.skipTo(0); - }); - - it("should play the first chunk", () => { - expect(chunk2Playback.stop).toHaveBeenCalled(); - expect(chunk2Playback.destroy).toHaveBeenCalled(); - expect(chunk1Playback.play).toHaveBeenCalled(); - }); - - it("should update the time", () => { - expect(playback.timeSeconds).toBe(0); - }); - }); - }); - - describe("and skipping multiple times", () => { - beforeEach(async () => { - return Promise.all([ - playback.skipTo(middleOfSecondChunk), - playback.skipTo(middleOfThirdChunk), - playback.skipTo(0), - ]); - }); - - it("should only skip to the first and last position", () => { - expect(chunk1Playback.stop).toHaveBeenCalled(); - expect(chunk1Playback.destroy).toHaveBeenCalled(); - expect(chunk2Playback.play).toHaveBeenCalled(); - - expect(chunk3Playback.play).not.toHaveBeenCalled(); - - expect(chunk2Playback.stop).toHaveBeenCalled(); - expect(chunk2Playback.destroy).toHaveBeenCalled(); - expect(chunk1Playback.play).toHaveBeenCalled(); - }); - }); - - describe("and the first chunk ends", () => { - beforeEach(() => { - chunk1Playback.emit(PlaybackState.Stopped); - }); - - it("should play until the end", () => { - // assert first chunk was unloaded - expect(chunk1Playback.destroy).toHaveBeenCalled(); - - // assert that the second chunk is being played - expect(chunk2Playback.play).toHaveBeenCalled(); - - // simulate end of second and third chunk - chunk2Playback.emit(PlaybackState.Stopped); - chunk3Playback.emit(PlaybackState.Stopped); - - // assert that the entire playback is now in stopped state - expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Stopped); - }); - }); - - describe("and calling pause", () => { - pausePlayback(); - itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Paused); - itShouldEmitAStateChangedEvent(VoiceBroadcastPlaybackState.Paused); - }); - - describe("and calling stop", () => { - stopPlayback(); - itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Stopped); - - it("should stop the playback", () => { - expect(chunk1Playback.stop).toHaveBeenCalled(); - }); - - describe("and skipping to somewhere in the middle of the first chunk", () => { - beforeEach(async () => { - mocked(chunk1Playback.play).mockClear(); - await playback.skipTo(1); - }); - - it("should not start the playback", () => { - expect(chunk1Playback.play).not.toHaveBeenCalled(); - }); - }); - }); - - describe("and calling destroy", () => { - beforeEach(() => { - playback.destroy(); - }); - - it("should call removeAllListeners", () => { - expect(playback.removeAllListeners).toHaveBeenCalled(); - }); - - it("should call destroy on the playbacks", () => { - expect(chunk1Playback.destroy).toHaveBeenCalled(); - expect(chunk2Playback.destroy).toHaveBeenCalled(); - }); - }); - }); - }); - - describe("and calling toggle for the first time", () => { - beforeEach(async () => { - playback.toggle(); - await simulateFirstChunkArrived(); - }); - - itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Playing); - - describe("and calling toggle a second time", () => { - beforeEach(async () => { - await playback.toggle(); - }); - - itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Paused); - - describe("and calling toggle a third time", () => { - beforeEach(async () => { - await playback.toggle(); - }); - - itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Playing); - }); - }); - }); - - describe("and calling stop", () => { - stopPlayback(); - - itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Stopped); - - describe("and calling toggle", () => { - beforeEach(async () => { - mocked(onStateChanged).mockReset(); - playback.toggle(); - await simulateFirstChunkArrived(); - }); - - itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Playing); - itShouldEmitAStateChangedEvent(VoiceBroadcastPlaybackState.Playing); - }); - }); - }); -}); diff --git a/test/unit-tests/voice-broadcast/models/VoiceBroadcastPreRecording-test.ts b/test/unit-tests/voice-broadcast/models/VoiceBroadcastPreRecording-test.ts deleted file mode 100644 index f8b9e89d3e4..00000000000 --- a/test/unit-tests/voice-broadcast/models/VoiceBroadcastPreRecording-test.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix"; - -import { - startNewVoiceBroadcastRecording, - VoiceBroadcastPlaybacksStore, - VoiceBroadcastPreRecording, - VoiceBroadcastRecordingsStore, -} from "../../../../src/voice-broadcast"; -import { stubClient } from "../../../test-utils"; - -jest.mock("../../../../src/voice-broadcast/utils/startNewVoiceBroadcastRecording"); - -describe("VoiceBroadcastPreRecording", () => { - const roomId = "!room:example.com"; - let client: MatrixClient; - let room: Room; - let sender: RoomMember; - let playbacksStore: VoiceBroadcastPlaybacksStore; - let recordingsStore: VoiceBroadcastRecordingsStore; - let preRecording: VoiceBroadcastPreRecording; - let onDismiss: (voiceBroadcastPreRecording: VoiceBroadcastPreRecording) => void; - - beforeAll(() => { - client = stubClient(); - room = new Room(roomId, client, client.getUserId() || ""); - sender = new RoomMember(roomId, client.getUserId() || ""); - recordingsStore = new VoiceBroadcastRecordingsStore(); - playbacksStore = new VoiceBroadcastPlaybacksStore(recordingsStore); - }); - - beforeEach(() => { - onDismiss = jest.fn(); - preRecording = new VoiceBroadcastPreRecording(room, sender, client, playbacksStore, recordingsStore); - preRecording.on("dismiss", onDismiss); - }); - - describe("start", () => { - beforeEach(() => { - preRecording.start(); - }); - - it("should start a new voice broadcast recording", () => { - expect(startNewVoiceBroadcastRecording).toHaveBeenCalledWith(room, client, playbacksStore, recordingsStore); - }); - - it("should emit a dismiss event", () => { - expect(onDismiss).toHaveBeenCalledWith(preRecording); - }); - }); - - describe("cancel", () => { - beforeEach(() => { - preRecording.cancel(); - }); - - it("should emit a dismiss event", () => { - expect(onDismiss).toHaveBeenCalledWith(preRecording); - }); - }); -}); diff --git a/test/unit-tests/voice-broadcast/models/VoiceBroadcastRecording-test.ts b/test/unit-tests/voice-broadcast/models/VoiceBroadcastRecording-test.ts deleted file mode 100644 index 4f6bd8f47be..00000000000 --- a/test/unit-tests/voice-broadcast/models/VoiceBroadcastRecording-test.ts +++ /dev/null @@ -1,660 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { mocked } from "jest-mock"; -import { - ClientEvent, - EventTimelineSet, - EventType, - LOCAL_NOTIFICATION_SETTINGS_PREFIX, - MatrixClient, - MatrixEvent, - MatrixEventEvent, - MsgType, - RelationType, - Room, - Relations, - SyncState, -} from "matrix-js-sdk/src/matrix"; -import { EncryptedFile } from "matrix-js-sdk/src/types"; -import fetchMock from "fetch-mock-jest"; - -import { uploadFile } from "../../../../src/ContentMessages"; -import { createVoiceMessageContent } from "../../../../src/utils/createVoiceMessageContent"; -import { - createVoiceBroadcastRecorder, - getChunkLength, - getMaxBroadcastLength, - VoiceBroadcastInfoEventContent, - VoiceBroadcastInfoEventType, - VoiceBroadcastInfoState, - VoiceBroadcastRecorder, - VoiceBroadcastRecorderEvent, - VoiceBroadcastRecording, - VoiceBroadcastRecordingEvent, - VoiceBroadcastRecordingState, -} from "../../../../src/voice-broadcast"; -import { mkEvent, mkStubRoom, stubClient } from "../../../test-utils"; -import dis from "../../../../src/dispatcher/dispatcher"; -import { VoiceRecording } from "../../../../src/audio/VoiceRecording"; -import { createAudioContext } from "../../../../src/audio/compat"; - -jest.mock("../../../../src/voice-broadcast/audio/VoiceBroadcastRecorder", () => ({ - ...(jest.requireActual("../../../../src/voice-broadcast/audio/VoiceBroadcastRecorder") as object), - createVoiceBroadcastRecorder: jest.fn(), -})); - -// mock VoiceRecording because it contains all the audio APIs -jest.mock("../../../../src/audio/VoiceRecording", () => ({ - VoiceRecording: jest.fn().mockReturnValue({ - disableMaxLength: jest.fn(), - liveData: { - onUpdate: jest.fn(), - }, - off: jest.fn(), - on: jest.fn(), - start: jest.fn(), - stop: jest.fn(), - destroy: jest.fn(), - contentType: "audio/ogg", - }), -})); - -jest.mock("../../../../src/ContentMessages", () => ({ - uploadFile: jest.fn(), -})); - -jest.mock("../../../../src/utils/createVoiceMessageContent", () => ({ - createVoiceMessageContent: jest.fn(), -})); - -jest.mock("../../../../src/audio/compat", () => ({ - ...jest.requireActual("../../../../src/audio/compat"), - createAudioContext: jest.fn(), -})); - -describe("VoiceBroadcastRecording", () => { - const roomId = "!room:example.com"; - const uploadedUrl = "mxc://example.com/vb"; - const uploadedFile = { file: true } as unknown as EncryptedFile; - const maxLength = getMaxBroadcastLength(); - let room: Room; - let client: MatrixClient; - let infoEvent: MatrixEvent; - let voiceBroadcastRecording: VoiceBroadcastRecording; - let onStateChanged: (state: VoiceBroadcastRecordingState) => void; - let voiceBroadcastRecorder: VoiceBroadcastRecorder; - let audioElement: HTMLAudioElement; - - const mkVoiceBroadcastInfoEvent = (content: VoiceBroadcastInfoEventContent) => { - return mkEvent({ - event: true, - type: VoiceBroadcastInfoEventType, - user: client.getSafeUserId(), - room: roomId, - content, - }); - }; - - const setUpVoiceBroadcastRecording = () => { - voiceBroadcastRecording = new VoiceBroadcastRecording(infoEvent, client); - voiceBroadcastRecording.on(VoiceBroadcastRecordingEvent.StateChanged, onStateChanged); - jest.spyOn(voiceBroadcastRecording, "destroy"); - jest.spyOn(voiceBroadcastRecording, "emit"); - jest.spyOn(voiceBroadcastRecording, "removeAllListeners"); - }; - - const itShouldBeInState = (state: VoiceBroadcastRecordingState) => { - it(`should be in state stopped ${state}`, () => { - expect(voiceBroadcastRecording.getState()).toBe(state); - }); - }; - - const emitFirsChunkRecorded = () => { - voiceBroadcastRecorder.emit(VoiceBroadcastRecorderEvent.ChunkRecorded, { - buffer: new Uint8Array([1, 2, 3]), - length: 23, - }); - }; - - const itShouldSendAnInfoEvent = (state: VoiceBroadcastInfoState, lastChunkSequence: number) => { - it(`should send a ${state} info event`, () => { - expect(client.sendStateEvent).toHaveBeenCalledWith( - roomId, - VoiceBroadcastInfoEventType, - { - device_id: client.getDeviceId(), - state, - last_chunk_sequence: lastChunkSequence, - ["m.relates_to"]: { - rel_type: RelationType.Reference, - event_id: infoEvent.getId(), - }, - } as VoiceBroadcastInfoEventContent, - client.getUserId()!, - ); - }); - }; - - const itShouldSendAVoiceMessage = (data: number[], size: number, duration: number, sequence: number) => { - // events contain milliseconds - duration *= 1000; - - it("should send a voice message", () => { - expect(uploadFile).toHaveBeenCalledWith( - client, - roomId, - new Blob([new Uint8Array(data)], { type: voiceBroadcastRecorder.contentType }), - ); - - expect(mocked(client.sendMessage)).toHaveBeenCalledWith(roomId, { - body: "Voice message", - file: { - file: true, - }, - info: { - duration, - mimetype: "audio/ogg", - size, - }, - ["m.relates_to"]: { - event_id: infoEvent.getId(), - rel_type: "m.reference", - }, - msgtype: "m.audio", - ["org.matrix.msc1767.audio"]: { - duration, - waveform: undefined, - }, - ["org.matrix.msc1767.file"]: { - file: { - file: true, - }, - mimetype: "audio/ogg", - name: "Voice message.ogg", - size, - url: "mxc://example.com/vb", - }, - ["org.matrix.msc1767.text"]: "Voice message", - ["org.matrix.msc3245.voice"]: {}, - url: "mxc://example.com/vb", - ["io.element.voice_broadcast_chunk"]: { - sequence, - }, - }); - }); - }; - - const setUpUploadFileMock = () => { - mocked(uploadFile).mockResolvedValue({ - url: uploadedUrl, - file: uploadedFile, - }); - }; - - const mockAudioBufferSourceNode = { - addEventListener: jest.fn(), - connect: jest.fn(), - start: jest.fn(), - }; - const mockAudioContext = { - decodeAudioData: jest.fn(), - suspend: jest.fn(), - resume: jest.fn(), - createBufferSource: jest.fn().mockReturnValue(mockAudioBufferSourceNode), - currentTime: 1337, - }; - - beforeEach(() => { - client = stubClient(); - room = mkStubRoom(roomId, "Test Room", client); - mocked(client.getRoom).mockImplementation((getRoomId: string | undefined): Room | null => { - if (getRoomId === roomId) { - return room; - } - - return null; - }); - onStateChanged = jest.fn(); - voiceBroadcastRecorder = new VoiceBroadcastRecorder(new VoiceRecording(), getChunkLength()); - jest.spyOn(voiceBroadcastRecorder, "start"); - jest.spyOn(voiceBroadcastRecorder, "stop"); - jest.spyOn(voiceBroadcastRecorder, "destroy"); - mocked(createVoiceBroadcastRecorder).mockReturnValue(voiceBroadcastRecorder); - - setUpUploadFileMock(); - - mocked(createVoiceMessageContent).mockImplementation( - ( - mxc: string | undefined, - mimetype: string, - duration: number, - size: number, - file?: EncryptedFile, - waveform?: number[], - ) => { - return { - body: "Voice message", - msgtype: MsgType.Audio, - url: mxc, - file, - info: { - duration, - mimetype, - size, - }, - ["org.matrix.msc1767.text"]: "Voice message", - ["org.matrix.msc1767.file"]: { - url: mxc, - file, - name: "Voice message.ogg", - mimetype, - size, - }, - ["org.matrix.msc1767.audio"]: { - duration, - // https://github.com/matrix-org/matrix-doc/pull/3246 - waveform, - }, - ["org.matrix.msc3245.voice"]: {}, // No content, this is a rendering hint - }; - }, - ); - - audioElement = { - play: jest.fn(), - } as any as HTMLAudioElement; - - jest.spyOn(document, "querySelector").mockImplementation((selector: string) => { - if (selector === "audio#errorAudio") { - return audioElement; - } - - return null; - }); - - mocked(createAudioContext).mockReturnValue(mockAudioContext as unknown as AudioContext); - }); - - afterEach(() => { - voiceBroadcastRecording?.off(VoiceBroadcastRecordingEvent.StateChanged, onStateChanged); - }); - - describe("when there is an info event without id", () => { - beforeEach(() => { - infoEvent = mkVoiceBroadcastInfoEvent({ - device_id: client.getDeviceId()!, - state: VoiceBroadcastInfoState.Started, - }); - jest.spyOn(infoEvent, "getId").mockReturnValue(undefined); - }); - - it("should raise an error when creating a broadcast", () => { - expect(() => { - setUpVoiceBroadcastRecording(); - }).toThrow("Cannot create broadcast for info event without Id."); - }); - }); - - describe("when there is an info event without room", () => { - beforeEach(() => { - infoEvent = mkVoiceBroadcastInfoEvent({ - device_id: client.getDeviceId()!, - state: VoiceBroadcastInfoState.Started, - }); - jest.spyOn(infoEvent, "getRoomId").mockReturnValue(undefined); - }); - - it("should raise an error when creating a broadcast", () => { - expect(() => { - setUpVoiceBroadcastRecording(); - }).toThrow(`Cannot create broadcast for unknown room (info event ${infoEvent.getId()})`); - }); - }); - - describe("when created for a Voice Broadcast Info without relations", () => { - beforeEach(() => { - infoEvent = mkVoiceBroadcastInfoEvent({ - device_id: client.getDeviceId()!, - state: VoiceBroadcastInfoState.Started, - }); - setUpVoiceBroadcastRecording(); - }); - - it("should be in Started state", () => { - expect(voiceBroadcastRecording.getState()).toBe(VoiceBroadcastInfoState.Started); - }); - - describe("and calling stop", () => { - beforeEach(() => { - voiceBroadcastRecording.stop(); - }); - - itShouldSendAnInfoEvent(VoiceBroadcastInfoState.Stopped, 0); - itShouldBeInState(VoiceBroadcastInfoState.Stopped); - - it("should emit a stopped state changed event", () => { - expect(onStateChanged).toHaveBeenCalledWith(VoiceBroadcastInfoState.Stopped); - }); - }); - - describe("and calling start", () => { - beforeEach(async () => { - await voiceBroadcastRecording.start(); - }); - - it("should start the recorder", () => { - expect(voiceBroadcastRecorder.start).toHaveBeenCalled(); - }); - - describe("and the info event is redacted", () => { - beforeEach(() => { - infoEvent.emit( - MatrixEventEvent.BeforeRedaction, - infoEvent, - mkEvent({ - event: true, - type: EventType.RoomRedaction, - user: client.getSafeUserId(), - content: {}, - }), - ); - }); - - itShouldBeInState(VoiceBroadcastInfoState.Stopped); - - it("should destroy the recording", () => { - expect(voiceBroadcastRecording.destroy).toHaveBeenCalled(); - }); - }); - - describe("and receiving a call action", () => { - beforeEach(() => { - dis.dispatch( - { - action: "call_state", - }, - true, - ); - }); - - itShouldBeInState(VoiceBroadcastInfoState.Paused); - }); - - describe("and a chunk time update occurs", () => { - beforeEach(() => { - voiceBroadcastRecorder.emit(VoiceBroadcastRecorderEvent.CurrentChunkLengthUpdated, 10); - }); - - it("should update time left", () => { - expect(voiceBroadcastRecording.getTimeLeft()).toBe(maxLength - 10); - expect(voiceBroadcastRecording.emit).toHaveBeenCalledWith( - VoiceBroadcastRecordingEvent.TimeLeftChanged, - maxLength - 10, - ); - }); - - describe("and a chunk time update occurs, that would increase time left", () => { - beforeEach(() => { - mocked(voiceBroadcastRecording.emit).mockClear(); - voiceBroadcastRecorder.emit(VoiceBroadcastRecorderEvent.CurrentChunkLengthUpdated, 5); - }); - - it("should not change time left", () => { - expect(voiceBroadcastRecording.getTimeLeft()).toBe(maxLength - 10); - expect(voiceBroadcastRecording.emit).not.toHaveBeenCalled(); - }); - }); - }); - - describe("and a chunk has been recorded", () => { - beforeEach(async () => { - emitFirsChunkRecorded(); - }); - - itShouldSendAVoiceMessage([1, 2, 3], 3, 23, 1); - - describe("and another chunk has been recorded, that exceeds the max time", () => { - beforeEach(() => { - mocked(voiceBroadcastRecorder.stop).mockResolvedValue({ - buffer: new Uint8Array([23, 24, 25]), - length: getMaxBroadcastLength(), - }); - voiceBroadcastRecorder.emit( - VoiceBroadcastRecorderEvent.CurrentChunkLengthUpdated, - getMaxBroadcastLength(), - ); - }); - - itShouldBeInState(VoiceBroadcastInfoState.Stopped); - itShouldSendAVoiceMessage([23, 24, 25], 3, getMaxBroadcastLength(), 2); - itShouldSendAnInfoEvent(VoiceBroadcastInfoState.Stopped, 2); - }); - }); - - describe("and calling stop", () => { - beforeEach(async () => { - mocked(voiceBroadcastRecorder.stop).mockResolvedValue({ - buffer: new Uint8Array([4, 5, 6]), - length: 42, - }); - await voiceBroadcastRecording.stop(); - }); - - itShouldSendAVoiceMessage([4, 5, 6], 3, 42, 1); - itShouldSendAnInfoEvent(VoiceBroadcastInfoState.Stopped, 1); - }); - - describe.each([ - ["pause", async () => voiceBroadcastRecording.pause()], - ["toggle", async () => voiceBroadcastRecording.toggle()], - ])("and calling %s", (_case: string, action: () => Promise) => { - beforeEach(async () => { - await action(); - }); - - itShouldBeInState(VoiceBroadcastInfoState.Paused); - itShouldSendAnInfoEvent(VoiceBroadcastInfoState.Paused, 0); - - it("should stop the recorder", () => { - expect(mocked(voiceBroadcastRecorder.stop)).toHaveBeenCalled(); - }); - - it("should emit a paused state changed event", () => { - expect(onStateChanged).toHaveBeenCalledWith(VoiceBroadcastInfoState.Paused); - }); - }); - - describe("and there is no connection", () => { - beforeEach(() => { - mocked(client.sendStateEvent).mockImplementation(() => { - throw new Error(); - }); - }); - - describe.each([ - ["pause", async () => voiceBroadcastRecording.pause()], - ["toggle", async () => voiceBroadcastRecording.toggle()], - ])("and calling %s", (_case: string, action: () => Promise) => { - beforeEach(async () => { - await action(); - }); - - itShouldBeInState("connection_error"); - - describe("and the connection is back", () => { - beforeEach(() => { - mocked(client.sendStateEvent).mockResolvedValue({ event_id: "e1" }); - client.emit(ClientEvent.Sync, SyncState.Catchup, SyncState.Error); - }); - - itShouldBeInState(VoiceBroadcastInfoState.Paused); - }); - }); - }); - - describe("and calling destroy", () => { - beforeEach(() => { - voiceBroadcastRecording.destroy(); - }); - - it("should stop the recorder and remove all listeners", () => { - expect(mocked(voiceBroadcastRecorder.stop)).toHaveBeenCalled(); - expect(mocked(voiceBroadcastRecorder.destroy)).toHaveBeenCalled(); - expect(mocked(voiceBroadcastRecording.removeAllListeners)).toHaveBeenCalled(); - }); - }); - - describe("and a chunk has been recorded and the upload fails", () => { - beforeEach(() => { - mocked(uploadFile).mockRejectedValue("Error"); - emitFirsChunkRecorded(); - }); - - itShouldBeInState("connection_error"); - - describe("and the connection is back", () => { - beforeEach(() => { - setUpUploadFileMock(); - client.emit(ClientEvent.Sync, SyncState.Catchup, SyncState.Error); - }); - - itShouldBeInState(VoiceBroadcastInfoState.Paused); - itShouldSendAVoiceMessage([1, 2, 3], 3, 23, 1); - }); - }); - - describe("and audible notifications are disabled", () => { - beforeEach(() => { - const notificationSettings = mkEvent({ - event: true, - type: `${LOCAL_NOTIFICATION_SETTINGS_PREFIX.name}.${client.getDeviceId()}`, - user: client.getSafeUserId(), - content: { - is_silenced: true, - }, - }); - mocked(client.getAccountData).mockReturnValue(notificationSettings); - }); - - describe("and a chunk has been recorded and sending the voice message fails", () => { - beforeEach(() => { - mocked(client.sendMessage).mockRejectedValue("Error"); - emitFirsChunkRecorded(); - }); - - itShouldBeInState("connection_error"); - - it("should not play a notification", () => { - expect(audioElement.play).not.toHaveBeenCalled(); - }); - }); - }); - - describe("and a chunk has been recorded and sending the voice message fails", () => { - beforeEach(() => { - mocked(client.sendMessage).mockRejectedValue("Error"); - emitFirsChunkRecorded(); - fetchMock.get("media/error.mp3", 200); - }); - - itShouldBeInState("connection_error"); - - it("should play a notification", () => { - expect(mockAudioBufferSourceNode.start).toHaveBeenCalled(); - }); - - describe("and the connection is back", () => { - beforeEach(() => { - mocked(client.sendMessage).mockClear(); - mocked(client.sendMessage).mockResolvedValue({ event_id: "e23" }); - client.emit(ClientEvent.Sync, SyncState.Catchup, SyncState.Error); - }); - - itShouldBeInState(VoiceBroadcastInfoState.Paused); - itShouldSendAVoiceMessage([1, 2, 3], 3, 23, 1); - }); - }); - }); - - describe("and it is in paused state", () => { - beforeEach(async () => { - await voiceBroadcastRecording.pause(); - }); - - describe.each([ - ["resume", async () => voiceBroadcastRecording.resume()], - ["toggle", async () => voiceBroadcastRecording.toggle()], - ])("and calling %s", (_case: string, action: () => Promise) => { - beforeEach(async () => { - await action(); - }); - - itShouldBeInState(VoiceBroadcastInfoState.Resumed); - itShouldSendAnInfoEvent(VoiceBroadcastInfoState.Resumed, 0); - - it("should start the recorder", () => { - expect(mocked(voiceBroadcastRecorder.start)).toHaveBeenCalled(); - }); - - it(`should emit a ${VoiceBroadcastInfoState.Resumed} state changed event`, () => { - expect(onStateChanged).toHaveBeenCalledWith(VoiceBroadcastInfoState.Resumed); - }); - }); - }); - }); - - describe("when created for a Voice Broadcast Info with a Stopped relation", () => { - beforeEach(() => { - infoEvent = mkVoiceBroadcastInfoEvent({ - device_id: client.getDeviceId()!, - state: VoiceBroadcastInfoState.Started, - chunk_length: 120, - }); - - const relationsContainer = { - getRelations: jest.fn(), - } as unknown as Relations; - mocked(relationsContainer.getRelations).mockReturnValue([ - mkVoiceBroadcastInfoEvent({ - device_id: client.getDeviceId()!, - state: VoiceBroadcastInfoState.Stopped, - ["m.relates_to"]: { - rel_type: RelationType.Reference, - event_id: infoEvent.getId()!, - }, - }), - ]); - - const timelineSet = { - relations: { - getChildEventsForEvent: jest - .fn() - .mockImplementation( - (eventId: string, relationType: RelationType | string, eventType: EventType | string) => { - if ( - eventId === infoEvent.getId() && - relationType === RelationType.Reference && - eventType === VoiceBroadcastInfoEventType - ) { - return relationsContainer; - } - }, - ), - }, - } as unknown as EventTimelineSet; - mocked(room.getUnfilteredTimelineSet).mockReturnValue(timelineSet); - - setUpVoiceBroadcastRecording(); - }); - - it("should be in Stopped state", () => { - expect(voiceBroadcastRecording.getState()).toBe(VoiceBroadcastInfoState.Stopped); - }); - }); -}); diff --git a/test/unit-tests/voice-broadcast/stores/VoiceBroadcastPlaybacksStore-test.ts b/test/unit-tests/voice-broadcast/stores/VoiceBroadcastPlaybacksStore-test.ts deleted file mode 100644 index 29026345cf5..00000000000 --- a/test/unit-tests/voice-broadcast/stores/VoiceBroadcastPlaybacksStore-test.ts +++ /dev/null @@ -1,162 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { mocked } from "jest-mock"; -import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; - -import { - VoiceBroadcastInfoState, - VoiceBroadcastPlayback, - VoiceBroadcastPlaybackEvent, - VoiceBroadcastPlaybacksStore, - VoiceBroadcastPlaybacksStoreEvent, - VoiceBroadcastPlaybackState, - VoiceBroadcastRecordingsStore, -} from "../../../../src/voice-broadcast"; -import { mkStubRoom, stubClient } from "../../../test-utils"; -import { mkVoiceBroadcastInfoStateEvent } from "../utils/test-utils"; - -describe("VoiceBroadcastPlaybacksStore", () => { - const roomId = "!room:example.com"; - let client: MatrixClient; - let userId: string; - let deviceId: string; - let room: Room; - let infoEvent1: MatrixEvent; - let infoEvent2: MatrixEvent; - let playback1: VoiceBroadcastPlayback; - let playback2: VoiceBroadcastPlayback; - let playbacks: VoiceBroadcastPlaybacksStore; - let onCurrentChanged: (playback: VoiceBroadcastPlayback | null) => void; - - beforeEach(() => { - client = stubClient(); - userId = client.getUserId() || ""; - deviceId = client.getDeviceId() || ""; - mocked(client.relations).mockClear(); - mocked(client.relations).mockResolvedValue({ events: [] }); - - room = mkStubRoom(roomId, "test room", client); - mocked(client.getRoom).mockImplementation((roomId: string): Room | null => { - if (roomId === room.roomId) { - return room; - } - - return null; - }); - - infoEvent1 = mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Started, userId, deviceId); - infoEvent2 = mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Started, userId, deviceId); - const recordings = new VoiceBroadcastRecordingsStore(); - playback1 = new VoiceBroadcastPlayback(infoEvent1, client, recordings); - jest.spyOn(playback1, "off"); - playback2 = new VoiceBroadcastPlayback(infoEvent2, client, recordings); - jest.spyOn(playback2, "off"); - - playbacks = new VoiceBroadcastPlaybacksStore(recordings); - jest.spyOn(playbacks, "removeAllListeners"); - onCurrentChanged = jest.fn(); - playbacks.on(VoiceBroadcastPlaybacksStoreEvent.CurrentChanged, onCurrentChanged); - }); - - afterEach(() => { - playbacks.off(VoiceBroadcastPlaybacksStoreEvent.CurrentChanged, onCurrentChanged); - }); - - describe("when setting a current Voice Broadcast playback", () => { - beforeEach(() => { - playbacks.setCurrent(playback1); - }); - - it("should return it as current", () => { - expect(playbacks.getCurrent()).toBe(playback1); - }); - - it("should return it by id", () => { - expect(playbacks.getByInfoEvent(infoEvent1, client)).toBe(playback1); - }); - - it("should emit a CurrentChanged event", () => { - expect(onCurrentChanged).toHaveBeenCalledWith(playback1); - }); - - describe("and setting the same again", () => { - beforeEach(() => { - mocked(onCurrentChanged).mockClear(); - playbacks.setCurrent(playback1); - }); - - it("should not emit a CurrentChanged event", () => { - expect(onCurrentChanged).not.toHaveBeenCalled(); - }); - }); - - describe("and setting another playback and start both", () => { - beforeEach(() => { - playbacks.setCurrent(playback2); - playback1.start(); - playback2.start(); - }); - - it("should set playback1 to paused", () => { - expect(playback1.getState()).toBe(VoiceBroadcastPlaybackState.Paused); - }); - - it("should set playback2 to buffering", () => { - // buffering because there are no chunks, yet - expect(playback2.getState()).toBe(VoiceBroadcastPlaybackState.Buffering); - }); - - describe("and calling destroy", () => { - beforeEach(() => { - playbacks.destroy(); - }); - - it("should remove all listeners", () => { - expect(playbacks.removeAllListeners).toHaveBeenCalled(); - }); - - it("should deregister the listeners on the playbacks", () => { - expect(playback1.off).toHaveBeenCalledWith( - VoiceBroadcastPlaybackEvent.StateChanged, - expect.any(Function), - ); - expect(playback2.off).toHaveBeenCalledWith( - VoiceBroadcastPlaybackEvent.StateChanged, - expect.any(Function), - ); - }); - }); - }); - }); - - describe("getByInfoEventId", () => { - let returnedPlayback: VoiceBroadcastPlayback; - - describe("when retrieving a known playback", () => { - beforeEach(() => { - playbacks.setCurrent(playback1); - returnedPlayback = playbacks.getByInfoEvent(infoEvent1, client); - }); - - it("should return the playback", () => { - expect(returnedPlayback).toBe(playback1); - }); - }); - - describe("when retrieving an unknown playback", () => { - beforeEach(() => { - returnedPlayback = playbacks.getByInfoEvent(infoEvent1, client); - }); - - it("should return the playback", () => { - expect(returnedPlayback.infoEvent).toBe(infoEvent1); - }); - }); - }); -}); diff --git a/test/unit-tests/voice-broadcast/stores/VoiceBroadcastPreRecordingStore-test.ts b/test/unit-tests/voice-broadcast/stores/VoiceBroadcastPreRecordingStore-test.ts deleted file mode 100644 index 53d3a4c0f2a..00000000000 --- a/test/unit-tests/voice-broadcast/stores/VoiceBroadcastPreRecordingStore-test.ts +++ /dev/null @@ -1,130 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { mocked } from "jest-mock"; -import { MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix"; - -import { - VoiceBroadcastPlaybacksStore, - VoiceBroadcastPreRecording, - VoiceBroadcastPreRecordingStore, - VoiceBroadcastRecordingsStore, -} from "../../../../src/voice-broadcast"; -import { stubClient } from "../../../test-utils"; - -describe("VoiceBroadcastPreRecordingStore", () => { - const roomId = "!room:example.com"; - let client: MatrixClient; - let room: Room; - let sender: RoomMember; - let playbacksStore: VoiceBroadcastPlaybacksStore; - let recordingsStore: VoiceBroadcastRecordingsStore; - let store: VoiceBroadcastPreRecordingStore; - let preRecording1: VoiceBroadcastPreRecording; - - beforeAll(() => { - client = stubClient(); - room = new Room(roomId, client, client.getUserId() || ""); - sender = new RoomMember(roomId, client.getUserId() || ""); - recordingsStore = new VoiceBroadcastRecordingsStore(); - playbacksStore = new VoiceBroadcastPlaybacksStore(recordingsStore); - }); - - beforeEach(() => { - store = new VoiceBroadcastPreRecordingStore(); - jest.spyOn(store, "emit"); - jest.spyOn(store, "removeAllListeners"); - preRecording1 = new VoiceBroadcastPreRecording(room, sender, client, playbacksStore, recordingsStore); - jest.spyOn(preRecording1, "off"); - }); - - it("getCurrent() should return null", () => { - expect(store.getCurrent()).toBeNull(); - }); - - it("clearCurrent() should work", () => { - store.clearCurrent(); - expect(store.getCurrent()).toBeNull(); - }); - - describe("when setting a current recording", () => { - beforeEach(() => { - store.setCurrent(preRecording1); - }); - - it("getCurrent() should return the recording", () => { - expect(store.getCurrent()).toBe(preRecording1); - }); - - it("should emit a changed event with the recording", () => { - expect(store.emit).toHaveBeenCalledWith("changed", preRecording1); - }); - - describe("and calling destroy()", () => { - beforeEach(() => { - store.destroy(); - }); - - it("should remove all listeners", () => { - expect(store.removeAllListeners).toHaveBeenCalled(); - }); - - it("should deregister from the pre-recordings", () => { - expect(preRecording1.off).toHaveBeenCalledWith("dismiss", expect.any(Function)); - }); - }); - - describe("and cancelling the pre-recording", () => { - beforeEach(() => { - preRecording1.cancel(); - }); - - it("should clear the current recording", () => { - expect(store.getCurrent()).toBeNull(); - }); - - it("should emit a changed event with null", () => { - expect(store.emit).toHaveBeenCalledWith("changed", null); - }); - }); - - describe("and setting the same pre-recording again", () => { - beforeEach(() => { - mocked(store.emit).mockClear(); - store.setCurrent(preRecording1); - }); - - it("should not emit a changed event", () => { - expect(store.emit).not.toHaveBeenCalled(); - }); - }); - - describe("and setting another pre-recording", () => { - let preRecording2: VoiceBroadcastPreRecording; - - beforeEach(() => { - mocked(store.emit).mockClear(); - mocked(preRecording1.off).mockClear(); - preRecording2 = new VoiceBroadcastPreRecording(room, sender, client, playbacksStore, recordingsStore); - store.setCurrent(preRecording2); - }); - - it("should deregister from the current pre-recording", () => { - expect(preRecording1.off).toHaveBeenCalledWith("dismiss", expect.any(Function)); - }); - - it("getCurrent() should return the new recording", () => { - expect(store.getCurrent()).toBe(preRecording2); - }); - - it("should emit a changed event with the new recording", () => { - expect(store.emit).toHaveBeenCalledWith("changed", preRecording2); - }); - }); - }); -}); diff --git a/test/unit-tests/voice-broadcast/stores/VoiceBroadcastRecordingsStore-test.ts b/test/unit-tests/voice-broadcast/stores/VoiceBroadcastRecordingsStore-test.ts deleted file mode 100644 index 97b756fce19..00000000000 --- a/test/unit-tests/voice-broadcast/stores/VoiceBroadcastRecordingsStore-test.ts +++ /dev/null @@ -1,167 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { mocked } from "jest-mock"; -import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; - -import { - VoiceBroadcastRecordingsStore, - VoiceBroadcastRecordingsStoreEvent, - VoiceBroadcastRecording, - VoiceBroadcastInfoState, -} from "../../../../src/voice-broadcast"; -import { mkStubRoom, stubClient } from "../../../test-utils"; -import { mkVoiceBroadcastInfoStateEvent } from "../utils/test-utils"; - -describe("VoiceBroadcastRecordingsStore", () => { - const roomId = "!room:example.com"; - let client: MatrixClient; - let room: Room; - let infoEvent: MatrixEvent; - let otherInfoEvent: MatrixEvent; - let recording: VoiceBroadcastRecording; - let otherRecording: VoiceBroadcastRecording; - let recordings: VoiceBroadcastRecordingsStore; - let onCurrentChanged: (recording: VoiceBroadcastRecording | null) => void; - - beforeEach(() => { - client = stubClient(); - room = mkStubRoom(roomId, "test room", client); - mocked(client.getRoom).mockImplementation((roomId: string) => { - if (roomId === room.roomId) { - return room; - } - return null; - }); - infoEvent = mkVoiceBroadcastInfoStateEvent( - roomId, - VoiceBroadcastInfoState.Started, - client.getUserId()!, - client.getDeviceId()!, - ); - otherInfoEvent = mkVoiceBroadcastInfoStateEvent( - roomId, - VoiceBroadcastInfoState.Started, - client.getUserId()!, - client.getDeviceId()!, - ); - recording = new VoiceBroadcastRecording(infoEvent, client); - otherRecording = new VoiceBroadcastRecording(otherInfoEvent, client); - recordings = new VoiceBroadcastRecordingsStore(); - onCurrentChanged = jest.fn(); - recordings.on(VoiceBroadcastRecordingsStoreEvent.CurrentChanged, onCurrentChanged); - }); - - afterEach(() => { - recording.destroy(); - recordings.off(VoiceBroadcastRecordingsStoreEvent.CurrentChanged, onCurrentChanged); - }); - - it("when setting a recording without info event Id, it should raise an error", () => { - infoEvent.event.event_id = undefined; - expect(() => { - recordings.setCurrent(recording); - }).toThrow("Got broadcast info event without Id"); - }); - - describe("when setting a current Voice Broadcast recording", () => { - beforeEach(() => { - recordings.setCurrent(recording); - }); - - it("should return it as current", () => { - expect(recordings.hasCurrent()).toBe(true); - expect(recordings.getCurrent()).toBe(recording); - }); - - it("should return it by id", () => { - expect(recordings.getByInfoEvent(infoEvent, client)).toBe(recording); - }); - - it("should emit a CurrentChanged event", () => { - expect(onCurrentChanged).toHaveBeenCalledWith(recording); - }); - - describe("and setting the same again", () => { - beforeEach(() => { - mocked(onCurrentChanged).mockClear(); - recordings.setCurrent(recording); - }); - - it("should not emit a CurrentChanged event", () => { - expect(onCurrentChanged).not.toHaveBeenCalled(); - }); - }); - - describe("and calling clearCurrent()", () => { - beforeEach(() => { - recordings.clearCurrent(); - }); - - it("should clear the current recording", () => { - expect(recordings.hasCurrent()).toBe(false); - expect(recordings.getCurrent()).toBeNull(); - }); - - it("should emit a current changed event", () => { - expect(onCurrentChanged).toHaveBeenCalledWith(null); - }); - - it("and calling it again should work", () => { - recordings.clearCurrent(); - expect(recordings.getCurrent()).toBeNull(); - }); - }); - - describe("and setting another recording and stopping the previous recording", () => { - beforeEach(() => { - recordings.setCurrent(otherRecording); - recording.stop(); - }); - - it("should keep the current recording", () => { - expect(recordings.getCurrent()).toBe(otherRecording); - }); - }); - - describe("and the recording stops", () => { - beforeEach(() => { - recording.stop(); - }); - - it("should clear the current recording", () => { - expect(recordings.getCurrent()).toBeNull(); - }); - }); - }); - - describe("getByInfoEventId", () => { - let returnedRecording: VoiceBroadcastRecording; - - describe("when retrieving a known recording", () => { - beforeEach(() => { - recordings.setCurrent(recording); - returnedRecording = recordings.getByInfoEvent(infoEvent, client); - }); - - it("should return the recording", () => { - expect(returnedRecording).toBe(recording); - }); - }); - - describe("when retrieving an unknown recording", () => { - beforeEach(() => { - returnedRecording = recordings.getByInfoEvent(infoEvent, client); - }); - - it("should return the recording", () => { - expect(returnedRecording.infoEvent).toBe(infoEvent); - }); - }); - }); -}); diff --git a/test/unit-tests/voice-broadcast/utils/VoiceBroadcastChunkEvents-test.ts b/test/unit-tests/voice-broadcast/utils/VoiceBroadcastChunkEvents-test.ts deleted file mode 100644 index 817538ef178..00000000000 --- a/test/unit-tests/voice-broadcast/utils/VoiceBroadcastChunkEvents-test.ts +++ /dev/null @@ -1,142 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { MatrixEvent } from "matrix-js-sdk/src/matrix"; - -import { VoiceBroadcastChunkEvents } from "../../../../src/voice-broadcast/utils/VoiceBroadcastChunkEvents"; -import { mkVoiceBroadcastChunkEvent } from "./test-utils"; - -describe("VoiceBroadcastChunkEvents", () => { - const userId = "@user:example.com"; - const roomId = "!room:example.com"; - const txnId = "txn-id"; - let eventSeq1Time1: MatrixEvent; - let eventSeq2Time4: MatrixEvent; - let eventSeq3Time2: MatrixEvent; - let eventSeq3Time2T: MatrixEvent; - let eventSeq4Time1: MatrixEvent; - let eventSeqUTime3: MatrixEvent; - let eventSeq2Time4Dup: MatrixEvent; - let chunkEvents: VoiceBroadcastChunkEvents; - - beforeEach(() => { - eventSeq1Time1 = mkVoiceBroadcastChunkEvent("info1", userId, roomId, 7, 1, 1); - eventSeq2Time4 = mkVoiceBroadcastChunkEvent("info1", userId, roomId, 23, 2, 4); - eventSeq2Time4Dup = mkVoiceBroadcastChunkEvent("info1", userId, roomId, 3141, 2, 4); - jest.spyOn(eventSeq2Time4Dup, "getId").mockReturnValue(eventSeq2Time4.getId()); - eventSeq3Time2 = mkVoiceBroadcastChunkEvent("info1", userId, roomId, 42, 3, 2); - eventSeq3Time2.setTxnId(txnId); - eventSeq3Time2T = mkVoiceBroadcastChunkEvent("info1", userId, roomId, 42, 3, 2); - eventSeq3Time2T.setTxnId(txnId); - eventSeq4Time1 = mkVoiceBroadcastChunkEvent("info1", userId, roomId, 69, 4, 1); - eventSeqUTime3 = mkVoiceBroadcastChunkEvent("info1", userId, roomId, 314, undefined, 3); - chunkEvents = new VoiceBroadcastChunkEvents(); - }); - - describe("when adding events that all have a sequence", () => { - beforeEach(() => { - chunkEvents.addEvent(eventSeq2Time4); - chunkEvents.addEvent(eventSeq1Time1); - chunkEvents.addEvents([eventSeq4Time1, eventSeq2Time4Dup, eventSeq3Time2]); - }); - - it("should provide the events sort by sequence", () => { - expect(chunkEvents.getEvents()).toEqual([ - eventSeq1Time1, - eventSeq2Time4Dup, - eventSeq3Time2, - eventSeq4Time1, - ]); - }); - - it("getNumberOfEvents should return 4", () => { - expect(chunkEvents.getNumberOfEvents()).toBe(4); - }); - - it("getLength should return the total length of all chunks", () => { - expect(chunkEvents.getLength()).toBe(3259); - }); - - it("getLengthTo(first event) should return 0", () => { - expect(chunkEvents.getLengthTo(eventSeq1Time1)).toBe(0); - }); - - it("getLengthTo(some event) should return the time excl. that event", () => { - expect(chunkEvents.getLengthTo(eventSeq3Time2)).toBe(7 + 3141); - }); - - it("getLengthTo(last event) should return the time excl. that event", () => { - expect(chunkEvents.getLengthTo(eventSeq4Time1)).toBe(7 + 3141 + 42); - }); - - it("should return the expected next chunk", () => { - expect(chunkEvents.getNext(eventSeq2Time4Dup)).toBe(eventSeq3Time2); - }); - - it("should return undefined for next last chunk", () => { - expect(chunkEvents.getNext(eventSeq4Time1)).toBeUndefined(); - }); - - it("findByTime(0) should return the first chunk", () => { - expect(chunkEvents.findByTime(0)).toBe(eventSeq1Time1); - }); - - it("findByTime(some time) should return the chunk with this time", () => { - expect(chunkEvents.findByTime(7 + 3141 + 21)).toBe(eventSeq3Time2); - }); - - it("findByTime(entire duration) should return the last chunk", () => { - expect(chunkEvents.findByTime(7 + 3141 + 42 + 69)).toBe(eventSeq4Time1); - }); - - describe("and adding an event with a known transaction Id", () => { - beforeEach(() => { - chunkEvents.addEvent(eventSeq3Time2T); - }); - - it("should replace the previous event", () => { - expect(chunkEvents.getEvents()).toEqual([ - eventSeq1Time1, - eventSeq2Time4Dup, - eventSeq3Time2T, - eventSeq4Time1, - ]); - expect(chunkEvents.getNumberOfEvents()).toBe(4); - }); - }); - }); - - describe("when adding events where at least one does not have a sequence", () => { - beforeEach(() => { - chunkEvents.addEvent(eventSeq2Time4); - chunkEvents.addEvent(eventSeq1Time1); - chunkEvents.addEvents([eventSeq4Time1, eventSeqUTime3, eventSeq2Time4Dup, eventSeq3Time2]); - }); - - it("should provide the events sort by timestamp without duplicates", () => { - expect(chunkEvents.getEvents()).toEqual([ - eventSeq1Time1, - eventSeq4Time1, - eventSeq3Time2, - eventSeqUTime3, - eventSeq2Time4Dup, - ]); - expect(chunkEvents.getNumberOfEvents()).toBe(5); - }); - - describe("getSequenceForEvent", () => { - it("should return the sequence if provided by the event", () => { - expect(chunkEvents.getSequenceForEvent(eventSeq3Time2)).toBe(3); - }); - - it("should return the index if no sequence provided by event", () => { - expect(chunkEvents.getSequenceForEvent(eventSeqUTime3)).toBe(4); - }); - }); - }); -}); diff --git a/test/unit-tests/voice-broadcast/utils/VoiceBroadcastResumer-test.ts b/test/unit-tests/voice-broadcast/utils/VoiceBroadcastResumer-test.ts deleted file mode 100644 index 81bcce5f3fd..00000000000 --- a/test/unit-tests/voice-broadcast/utils/VoiceBroadcastResumer-test.ts +++ /dev/null @@ -1,179 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { mocked } from "jest-mock"; -import { ClientEvent, MatrixClient, MatrixEvent, RelationType, Room, SyncState } from "matrix-js-sdk/src/matrix"; - -import { - VoiceBroadcastInfoEventContent, - VoiceBroadcastInfoEventType, - VoiceBroadcastInfoState, - VoiceBroadcastResumer, -} from "../../../../src/voice-broadcast"; -import { stubClient } from "../../../test-utils"; -import { mkVoiceBroadcastInfoStateEvent } from "./test-utils"; - -describe("VoiceBroadcastResumer", () => { - const roomId = "!room:example.com"; - let client: MatrixClient; - let room: Room; - let resumer: VoiceBroadcastResumer; - let startedInfoEvent: MatrixEvent; - let pausedInfoEvent: MatrixEvent; - - const itShouldNotSendAStateEvent = (): void => { - it("should not send a state event", () => { - expect(client.sendStateEvent).not.toHaveBeenCalled(); - }); - }; - - const itShouldSendAStoppedStateEvent = (): void => { - it("should send a stopped state event", () => { - expect(client.sendStateEvent).toHaveBeenCalledWith( - startedInfoEvent.getRoomId(), - VoiceBroadcastInfoEventType, - { - "device_id": client.getDeviceId(), - "state": VoiceBroadcastInfoState.Stopped, - "m.relates_to": { - rel_type: RelationType.Reference, - event_id: startedInfoEvent.getId(), - }, - } as VoiceBroadcastInfoEventContent, - client.getUserId()!, - ); - }); - }; - - const itShouldDeregisterFromTheClient = () => { - it("should deregister from the client", () => { - expect(client.off).toHaveBeenCalledWith(ClientEvent.Sync, expect.any(Function)); - }); - }; - - beforeEach(() => { - client = stubClient(); - jest.spyOn(client, "off"); - room = new Room(roomId, client, client.getUserId()!); - mocked(client.getRoom).mockImplementation((getRoomId: string | undefined) => { - if (getRoomId === roomId) return room; - - return null; - }); - mocked(client.getRooms).mockReturnValue([room]); - startedInfoEvent = mkVoiceBroadcastInfoStateEvent( - roomId, - VoiceBroadcastInfoState.Started, - client.getUserId()!, - client.getDeviceId()!, - ); - pausedInfoEvent = mkVoiceBroadcastInfoStateEvent( - roomId, - VoiceBroadcastInfoState.Paused, - client.getUserId()!, - client.getDeviceId()!, - startedInfoEvent, - ); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe("when the initial sync is completed", () => { - beforeEach(() => { - mocked(client.isInitialSyncComplete).mockReturnValue(true); - }); - - describe("and there is no info event", () => { - beforeEach(() => { - resumer = new VoiceBroadcastResumer(client); - }); - - itShouldNotSendAStateEvent(); - - describe("and calling destroy", () => { - beforeEach(() => { - resumer.destroy(); - }); - - itShouldDeregisterFromTheClient(); - }); - }); - - describe("and there is a started info event", () => { - beforeEach(() => { - room.currentState.setStateEvents([startedInfoEvent]); - }); - - describe("and the client knows about the user and device", () => { - beforeEach(() => { - resumer = new VoiceBroadcastResumer(client); - }); - - itShouldSendAStoppedStateEvent(); - }); - - describe("and the client doesn't know about the user", () => { - beforeEach(() => { - mocked(client.getUserId).mockReturnValue(null); - resumer = new VoiceBroadcastResumer(client); - }); - - itShouldNotSendAStateEvent(); - }); - - describe("and the client doesn't know about the device", () => { - beforeEach(() => { - mocked(client.getDeviceId).mockReturnValue(null); - resumer = new VoiceBroadcastResumer(client); - }); - - itShouldNotSendAStateEvent(); - }); - }); - - describe("and there is a paused info event", () => { - beforeEach(() => { - room.currentState.setStateEvents([pausedInfoEvent]); - resumer = new VoiceBroadcastResumer(client); - }); - - itShouldSendAStoppedStateEvent(); - }); - }); - - describe("when the initial sync is not completed", () => { - beforeEach(() => { - room.currentState.setStateEvents([pausedInfoEvent]); - mocked(client.isInitialSyncComplete).mockReturnValue(false); - mocked(client.getSyncState).mockReturnValue(SyncState.Prepared); - resumer = new VoiceBroadcastResumer(client); - }); - - itShouldNotSendAStateEvent(); - - describe("and a sync event appears", () => { - beforeEach(() => { - client.emit(ClientEvent.Sync, SyncState.Prepared, SyncState.Stopped); - }); - - itShouldNotSendAStateEvent(); - - describe("and the initial sync completed and a sync event appears", () => { - beforeEach(() => { - mocked(client.getSyncState).mockReturnValue(SyncState.Syncing); - client.emit(ClientEvent.Sync, SyncState.Syncing, SyncState.Prepared); - }); - - itShouldSendAStoppedStateEvent(); - itShouldDeregisterFromTheClient(); - }); - }); - }); -}); diff --git a/test/unit-tests/voice-broadcast/utils/__snapshots__/setUpVoiceBroadcastPreRecording-test.ts.snap b/test/unit-tests/voice-broadcast/utils/__snapshots__/setUpVoiceBroadcastPreRecording-test.ts.snap deleted file mode 100644 index 2d6ba0a409b..00000000000 --- a/test/unit-tests/voice-broadcast/utils/__snapshots__/setUpVoiceBroadcastPreRecording-test.ts.snap +++ /dev/null @@ -1,24 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`setUpVoiceBroadcastPreRecording when trying to start a broadcast if there is no connection should show an info dialog and not set up a pre-recording 1`] = ` -[MockFunction] { - "calls": [ - [ - [Function], - { - "description":

- Unfortunately we're unable to start a recording right now. Please try again later. -

, - "hasCloseButton": true, - "title": "Connection error", - }, - ], - ], - "results": [ - { - "type": "return", - "value": undefined, - }, - ], -} -`; diff --git a/test/unit-tests/voice-broadcast/utils/__snapshots__/startNewVoiceBroadcastRecording-test.ts.snap b/test/unit-tests/voice-broadcast/utils/__snapshots__/startNewVoiceBroadcastRecording-test.ts.snap deleted file mode 100644 index dd5aa15305c..00000000000 --- a/test/unit-tests/voice-broadcast/utils/__snapshots__/startNewVoiceBroadcastRecording-test.ts.snap +++ /dev/null @@ -1,116 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`startNewVoiceBroadcastRecording when the current user is allowed to send voice broadcast info state events when there already is a live broadcast of another user should show an info dialog 1`] = ` -[MockFunction] { - "calls": [ - [ - [Function], - { - "description":

- Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one. -

, - "hasCloseButton": true, - "title": "Can't start a new voice broadcast", - }, - ], - ], - "results": [ - { - "type": "return", - "value": undefined, - }, - ], -} -`; - -exports[`startNewVoiceBroadcastRecording when the current user is allowed to send voice broadcast info state events when there already is a live broadcast of the current user in the room should show an info dialog 1`] = ` -[MockFunction] { - "calls": [ - [ - [Function], - { - "description":

- You are already recording a voice broadcast. Please end your current voice broadcast to start a new one. -

, - "hasCloseButton": true, - "title": "Can't start a new voice broadcast", - }, - ], - ], - "results": [ - { - "type": "return", - "value": undefined, - }, - ], -} -`; - -exports[`startNewVoiceBroadcastRecording when the current user is allowed to send voice broadcast info state events when there is already a current voice broadcast should show an info dialog 1`] = ` -[MockFunction] { - "calls": [ - [ - [Function], - { - "description":

- You are already recording a voice broadcast. Please end your current voice broadcast to start a new one. -

, - "hasCloseButton": true, - "title": "Can't start a new voice broadcast", - }, - ], - ], - "results": [ - { - "type": "return", - "value": undefined, - }, - ], -} -`; - -exports[`startNewVoiceBroadcastRecording when the current user is not allowed to send voice broadcast info state events should show an info dialog 1`] = ` -[MockFunction] { - "calls": [ - [ - [Function], - { - "description":

- You don't have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions. -

, - "hasCloseButton": true, - "title": "Can't start a new voice broadcast", - }, - ], - ], - "results": [ - { - "type": "return", - "value": undefined, - }, - ], -} -`; - -exports[`startNewVoiceBroadcastRecording when trying to start a broadcast if there is no connection should show an info dialog and not start a recording 1`] = ` -[MockFunction] { - "calls": [ - [ - [Function], - { - "description":

- Unfortunately we're unable to start a recording right now. Please try again later. -

, - "hasCloseButton": true, - "title": "Connection error", - }, - ], - ], - "results": [ - { - "type": "return", - "value": undefined, - }, - ], -} -`; diff --git a/test/unit-tests/voice-broadcast/utils/__snapshots__/textForVoiceBroadcastStoppedEvent-test.tsx.snap b/test/unit-tests/voice-broadcast/utils/__snapshots__/textForVoiceBroadcastStoppedEvent-test.tsx.snap deleted file mode 100644 index cf1e93db13b..00000000000 --- a/test/unit-tests/voice-broadcast/utils/__snapshots__/textForVoiceBroadcastStoppedEvent-test.tsx.snap +++ /dev/null @@ -1,42 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`textForVoiceBroadcastStoppedEvent should render other users broadcast as expected 1`] = ` -
-
- @other:example.com ended a voice broadcast -
-
-`; - -exports[`textForVoiceBroadcastStoppedEvent should render own broadcast as expected 1`] = ` -
-
- You ended a voice broadcast -
-
-`; - -exports[`textForVoiceBroadcastStoppedEvent should render without login as expected 1`] = ` -
-
- @other:example.com ended a voice broadcast -
-
-`; - -exports[`textForVoiceBroadcastStoppedEvent when rendering an event with relation to the start event should render events with relation to the start event 1`] = ` -
-
- - You ended a - - -
-
-`; diff --git a/test/unit-tests/voice-broadcast/utils/cleanUpBroadcasts-test.ts b/test/unit-tests/voice-broadcast/utils/cleanUpBroadcasts-test.ts deleted file mode 100644 index ebf0de390c7..00000000000 --- a/test/unit-tests/voice-broadcast/utils/cleanUpBroadcasts-test.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2023 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { - cleanUpBroadcasts, - VoiceBroadcastPlayback, - VoiceBroadcastPreRecording, - VoiceBroadcastRecording, -} from "../../../../src/voice-broadcast"; -import { stubClient } from "../../../test-utils"; -import { TestSdkContext } from "../../TestSdkContext"; -import { mkVoiceBroadcastPlayback, mkVoiceBroadcastPreRecording, mkVoiceBroadcastRecording } from "./test-utils"; - -describe("cleanUpBroadcasts", () => { - let playback: VoiceBroadcastPlayback; - let recording: VoiceBroadcastRecording; - let preRecording: VoiceBroadcastPreRecording; - let stores: TestSdkContext; - - beforeEach(() => { - stores = new TestSdkContext(); - stores.client = stubClient(); - - playback = mkVoiceBroadcastPlayback(stores); - jest.spyOn(playback, "stop").mockReturnValue(); - stores.voiceBroadcastPlaybacksStore.setCurrent(playback); - - recording = mkVoiceBroadcastRecording(stores); - jest.spyOn(recording, "stop").mockResolvedValue(); - stores.voiceBroadcastRecordingsStore.setCurrent(recording); - - preRecording = mkVoiceBroadcastPreRecording(stores); - jest.spyOn(preRecording, "cancel").mockReturnValue(); - stores.voiceBroadcastPreRecordingStore.setCurrent(preRecording); - }); - - it("should stop and clear all broadcast related stuff", async () => { - await cleanUpBroadcasts(stores); - expect(playback.stop).toHaveBeenCalled(); - expect(stores.voiceBroadcastPlaybacksStore.getCurrent()).toBeNull(); - expect(recording.stop).toHaveBeenCalled(); - expect(stores.voiceBroadcastRecordingsStore.getCurrent()).toBeNull(); - expect(preRecording.cancel).toHaveBeenCalled(); - expect(stores.voiceBroadcastPreRecordingStore.getCurrent()).toBeNull(); - }); -}); diff --git a/test/unit-tests/voice-broadcast/utils/determineVoiceBroadcastLiveness-test.ts b/test/unit-tests/voice-broadcast/utils/determineVoiceBroadcastLiveness-test.ts deleted file mode 100644 index c1a9b2db137..00000000000 --- a/test/unit-tests/voice-broadcast/utils/determineVoiceBroadcastLiveness-test.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { VoiceBroadcastInfoState, VoiceBroadcastLiveness } from "../../../../src/voice-broadcast"; -import { determineVoiceBroadcastLiveness } from "../../../../src/voice-broadcast/utils/determineVoiceBroadcastLiveness"; - -const testData: Array<{ state: VoiceBroadcastInfoState; expected: VoiceBroadcastLiveness }> = [ - { state: VoiceBroadcastInfoState.Started, expected: "live" }, - { state: VoiceBroadcastInfoState.Resumed, expected: "live" }, - { state: VoiceBroadcastInfoState.Paused, expected: "grey" }, - { state: VoiceBroadcastInfoState.Stopped, expected: "not-live" }, -]; - -describe("determineVoiceBroadcastLiveness", () => { - it.each(testData)("should return the expected value for a %s broadcast", ({ state, expected }) => { - expect(determineVoiceBroadcastLiveness(state)).toBe(expected); - }); - - it("should return »non-live« for an undefined state", () => { - // @ts-ignore - expect(determineVoiceBroadcastLiveness(undefined)).toBe("not-live"); - }); - - it("should return »non-live« for an unknown state", () => { - // @ts-ignore - expect(determineVoiceBroadcastLiveness("unknown test state")).toBe("not-live"); - }); -}); diff --git a/test/unit-tests/voice-broadcast/utils/findRoomLiveVoiceBroadcastFromUserAndDevice-test.ts b/test/unit-tests/voice-broadcast/utils/findRoomLiveVoiceBroadcastFromUserAndDevice-test.ts deleted file mode 100644 index 3120e2b5179..00000000000 --- a/test/unit-tests/voice-broadcast/utils/findRoomLiveVoiceBroadcastFromUserAndDevice-test.ts +++ /dev/null @@ -1,116 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { mocked } from "jest-mock"; -import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; - -import { - findRoomLiveVoiceBroadcastFromUserAndDevice, - VoiceBroadcastInfoEventType, - VoiceBroadcastInfoState, -} from "../../../../src/voice-broadcast"; -import { mkEvent, stubClient } from "../../../test-utils"; -import { mkVoiceBroadcastInfoStateEvent } from "./test-utils"; - -describe("findRoomLiveVoiceBroadcastFromUserAndDevice", () => { - const roomId = "!room:example.com"; - let client: MatrixClient; - let room: Room; - - const itShouldReturnNull = () => { - it("should return null", () => { - expect( - findRoomLiveVoiceBroadcastFromUserAndDevice(room, client.getUserId()!, client.getDeviceId()!), - ).toBeNull(); - }); - }; - - beforeAll(() => { - client = stubClient(); - room = new Room(roomId, client, client.getUserId()!); - jest.spyOn(room.currentState, "getStateEvents"); - mocked(client.getRoom).mockImplementation((getRoomId: string) => { - if (getRoomId === roomId) return room; - return null; - }); - }); - - describe("when there is no info event", () => { - itShouldReturnNull(); - }); - - describe("when there is an info event without content", () => { - beforeEach(() => { - room.currentState.setStateEvents([ - mkEvent({ - event: true, - type: VoiceBroadcastInfoEventType, - room: roomId, - user: client.getUserId()!, - content: {}, - }), - ]); - }); - - itShouldReturnNull(); - }); - - describe("when there is a stopped info event", () => { - beforeEach(() => { - room.currentState.setStateEvents([ - mkVoiceBroadcastInfoStateEvent( - roomId, - VoiceBroadcastInfoState.Stopped, - client.getUserId()!, - client.getDeviceId(), - ), - ]); - }); - - itShouldReturnNull(); - }); - - describe("when there is a started info event from another device", () => { - beforeEach(() => { - const event = mkVoiceBroadcastInfoStateEvent( - roomId, - VoiceBroadcastInfoState.Stopped, - client.getUserId()!, - "JKL123", - ); - room.currentState.setStateEvents([event]); - }); - - itShouldReturnNull(); - }); - - describe("when there is a started info event", () => { - let event: MatrixEvent; - - beforeEach(() => { - event = mkVoiceBroadcastInfoStateEvent( - roomId, - VoiceBroadcastInfoState.Started, - client.getUserId()!, - client.getDeviceId(), - ); - room.currentState.setStateEvents([event]); - }); - - it("should return this event", () => { - expect(room.currentState.getStateEvents).toHaveBeenCalledWith( - VoiceBroadcastInfoEventType, - client.getUserId()!, - ); - - expect(findRoomLiveVoiceBroadcastFromUserAndDevice(room, client.getUserId()!, client.getDeviceId()!)).toBe( - event, - ); - }); - }); -}); diff --git a/test/unit-tests/voice-broadcast/utils/getChunkLength-test.ts b/test/unit-tests/voice-broadcast/utils/getChunkLength-test.ts deleted file mode 100644 index b2b41ae577f..00000000000 --- a/test/unit-tests/voice-broadcast/utils/getChunkLength-test.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import SdkConfig from "../../../../src/SdkConfig"; -import { SettingLevel } from "../../../../src/settings/SettingLevel"; -import { Features } from "../../../../src/settings/Settings"; -import SettingsStore from "../../../../src/settings/SettingsStore"; -import { getChunkLength } from "../../../../src/voice-broadcast/utils/getChunkLength"; - -describe("getChunkLength", () => { - afterEach(() => { - SdkConfig.reset(); - }); - - describe("when there is a value provided by Sdk config", () => { - beforeEach(() => { - SdkConfig.add({ - voice_broadcast: { - chunk_length: 42, - }, - }); - }); - - it("should return this value", () => { - expect(getChunkLength()).toBe(42); - }); - }); - - describe("when Sdk config does not provide a value", () => { - beforeEach(() => { - SdkConfig.add({ - voice_broadcast: { - chunk_length: 23, - }, - }); - }); - - it("should return this value", () => { - expect(getChunkLength()).toBe(23); - }); - }); - - describe("when there are no defaults", () => { - it("should return the fallback value", () => { - expect(getChunkLength()).toBe(120); - }); - }); - - describe("when the Features.VoiceBroadcastForceSmallChunks is enabled", () => { - beforeEach(async () => { - await SettingsStore.setValue(Features.VoiceBroadcastForceSmallChunks, null, SettingLevel.DEVICE, true); - }); - - it("should return a chunk length of 15 seconds", () => { - expect(getChunkLength()).toBe(15); - }); - }); -}); diff --git a/test/unit-tests/voice-broadcast/utils/getMaxBroadcastLength-test.ts b/test/unit-tests/voice-broadcast/utils/getMaxBroadcastLength-test.ts deleted file mode 100644 index 6bc38bee980..00000000000 --- a/test/unit-tests/voice-broadcast/utils/getMaxBroadcastLength-test.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import SdkConfig, { DEFAULTS } from "../../../../src/SdkConfig"; -import { getMaxBroadcastLength } from "../../../../src/voice-broadcast"; - -describe("getMaxBroadcastLength", () => { - afterEach(() => { - SdkConfig.reset(); - }); - - describe("when there is a value provided by Sdk config", () => { - beforeEach(() => { - SdkConfig.put({ - voice_broadcast: { - max_length: 42, - }, - }); - }); - - it("should return this value", () => { - expect(getMaxBroadcastLength()).toBe(42); - }); - }); - - describe("when Sdk config does not provide a value", () => { - it("should return this value", () => { - expect(getMaxBroadcastLength()).toBe(DEFAULTS.voice_broadcast!.max_length); - }); - }); - - describe("if there are no defaults", () => { - it("should return the fallback value", () => { - expect(DEFAULTS.voice_broadcast!.max_length).toBe(4 * 60 * 60); - expect(getMaxBroadcastLength()).toBe(4 * 60 * 60); - }); - }); -}); diff --git a/test/unit-tests/voice-broadcast/utils/hasRoomLiveVoiceBroadcast-test.ts b/test/unit-tests/voice-broadcast/utils/hasRoomLiveVoiceBroadcast-test.ts deleted file mode 100644 index 37fbf0c277b..00000000000 --- a/test/unit-tests/voice-broadcast/utils/hasRoomLiveVoiceBroadcast-test.ts +++ /dev/null @@ -1,195 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { mocked } from "jest-mock"; -import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; - -import { - hasRoomLiveVoiceBroadcast, - VoiceBroadcastInfoEventType, - VoiceBroadcastInfoState, -} from "../../../../src/voice-broadcast"; -import { mkEvent, stubClient } from "../../../test-utils"; -import { mkVoiceBroadcastInfoStateEvent } from "./test-utils"; - -describe("hasRoomLiveVoiceBroadcast", () => { - const otherUserId = "@other:example.com"; - const otherDeviceId = "ASD123"; - const roomId = "!room:example.com"; - let client: MatrixClient; - let room: Room; - let expectedEvent: MatrixEvent | null = null; - - const addVoiceBroadcastInfoEvent = ( - state: VoiceBroadcastInfoState, - userId: string, - deviceId: string, - startedEvent?: MatrixEvent, - ): MatrixEvent => { - const infoEvent = mkVoiceBroadcastInfoStateEvent(room.roomId, state, userId, deviceId, startedEvent); - room.addLiveEvents([infoEvent], { addToState: true }); - room.currentState.setStateEvents([infoEvent]); - room.relations.aggregateChildEvent(infoEvent); - return infoEvent; - }; - - const itShouldReturnTrueTrue = () => { - it("should return true/true", async () => { - expect(await hasRoomLiveVoiceBroadcast(client, room, client.getSafeUserId())).toEqual({ - hasBroadcast: true, - infoEvent: expectedEvent, - startedByUser: true, - }); - }); - }; - - const itShouldReturnTrueFalse = () => { - it("should return true/false", async () => { - expect(await hasRoomLiveVoiceBroadcast(client, room, client.getSafeUserId())).toEqual({ - hasBroadcast: true, - infoEvent: expectedEvent, - startedByUser: false, - }); - }); - }; - - const itShouldReturnFalseFalse = () => { - it("should return false/false", async () => { - expect(await hasRoomLiveVoiceBroadcast(client, room, client.getSafeUserId())).toEqual({ - hasBroadcast: false, - infoEvent: null, - startedByUser: false, - }); - }); - }; - - beforeEach(() => { - client = stubClient(); - room = new Room(roomId, client, client.getSafeUserId()); - mocked(client.getRoom).mockImplementation((roomId: string): Room | null => { - return roomId === room.roomId ? room : null; - }); - expectedEvent = null; - }); - - describe("when there is no voice broadcast info at all", () => { - itShouldReturnFalseFalse(); - }); - - describe("when the »state« prop is missing", () => { - beforeEach(() => { - room.currentState.setStateEvents([ - mkEvent({ - event: true, - room: room.roomId, - user: client.getSafeUserId(), - type: VoiceBroadcastInfoEventType, - skey: client.getSafeUserId(), - content: {}, - }), - ]); - }); - itShouldReturnFalseFalse(); - }); - - describe("when there is a live broadcast from the current and another user", () => { - beforeEach(() => { - expectedEvent = addVoiceBroadcastInfoEvent( - VoiceBroadcastInfoState.Started, - client.getSafeUserId(), - client.getDeviceId()!, - ); - addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Started, otherUserId, otherDeviceId); - }); - - itShouldReturnTrueTrue(); - }); - - describe("when there are only stopped info events", () => { - beforeEach(() => { - addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Stopped, client.getSafeUserId(), client.getDeviceId()!); - addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Stopped, otherUserId, otherDeviceId); - }); - - itShouldReturnFalseFalse(); - }); - - describe("when there is a live, started broadcast from the current user", () => { - beforeEach(() => { - expectedEvent = addVoiceBroadcastInfoEvent( - VoiceBroadcastInfoState.Started, - client.getSafeUserId(), - client.getDeviceId()!, - ); - }); - - itShouldReturnTrueTrue(); - }); - - describe("when there is a live, paused broadcast from the current user", () => { - beforeEach(() => { - expectedEvent = addVoiceBroadcastInfoEvent( - VoiceBroadcastInfoState.Started, - client.getSafeUserId(), - client.getDeviceId()!, - ); - addVoiceBroadcastInfoEvent( - VoiceBroadcastInfoState.Paused, - client.getSafeUserId(), - client.getDeviceId()!, - expectedEvent, - ); - }); - - itShouldReturnTrueTrue(); - }); - - describe("when there is a live, resumed broadcast from the current user", () => { - beforeEach(() => { - expectedEvent = addVoiceBroadcastInfoEvent( - VoiceBroadcastInfoState.Started, - client.getSafeUserId(), - client.getDeviceId()!, - ); - addVoiceBroadcastInfoEvent( - VoiceBroadcastInfoState.Resumed, - client.getSafeUserId(), - client.getDeviceId()!, - expectedEvent, - ); - }); - - itShouldReturnTrueTrue(); - }); - - describe("when there was a live broadcast, that has been stopped", () => { - beforeEach(() => { - const startedEvent = addVoiceBroadcastInfoEvent( - VoiceBroadcastInfoState.Started, - client.getSafeUserId(), - client.getDeviceId()!, - ); - addVoiceBroadcastInfoEvent( - VoiceBroadcastInfoState.Stopped, - client.getSafeUserId(), - client.getDeviceId()!, - startedEvent, - ); - }); - - itShouldReturnFalseFalse(); - }); - - describe("when there is a live broadcast from another user", () => { - beforeEach(() => { - expectedEvent = addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Started, otherUserId, otherDeviceId); - }); - - itShouldReturnTrueFalse(); - }); -}); diff --git a/test/unit-tests/voice-broadcast/utils/isRelatedToVoiceBroadcast-test.ts b/test/unit-tests/voice-broadcast/utils/isRelatedToVoiceBroadcast-test.ts deleted file mode 100644 index eae78df109f..00000000000 --- a/test/unit-tests/voice-broadcast/utils/isRelatedToVoiceBroadcast-test.ts +++ /dev/null @@ -1,109 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2023 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { EventType, MatrixEvent, RelationType, Room, MatrixClient } from "matrix-js-sdk/src/matrix"; -import { mocked } from "jest-mock"; - -import { isRelatedToVoiceBroadcast, VoiceBroadcastInfoState } from "../../../../src/voice-broadcast"; -import { mkEvent, stubClient } from "../../../test-utils"; -import { mkVoiceBroadcastInfoStateEvent } from "./test-utils"; - -const mkRelatedEvent = ( - room: Room, - relationType: RelationType, - relatesTo: MatrixEvent | undefined, - client: MatrixClient, -): MatrixEvent => { - const event = mkEvent({ - event: true, - type: EventType.RoomMessage, - room: room.roomId, - content: { - "m.relates_to": { - rel_type: relationType, - event_id: relatesTo?.getId(), - }, - }, - user: client.getSafeUserId(), - }); - room.addLiveEvents([event], { addToState: true }); - return event; -}; - -describe("isRelatedToVoiceBroadcast", () => { - const roomId = "!room:example.com"; - let client: MatrixClient; - let room: Room; - let broadcastEvent: MatrixEvent; - let nonBroadcastEvent: MatrixEvent; - - beforeAll(() => { - client = stubClient(); - room = new Room(roomId, client, client.getSafeUserId()); - - mocked(client.getRoom).mockImplementation((getRoomId: string): Room | null => { - if (getRoomId === roomId) return room; - return null; - }); - - broadcastEvent = mkVoiceBroadcastInfoStateEvent( - roomId, - VoiceBroadcastInfoState.Started, - client.getSafeUserId(), - "ABC123", - ); - nonBroadcastEvent = mkEvent({ - event: true, - type: EventType.RoomMessage, - room: roomId, - content: {}, - user: client.getSafeUserId(), - }); - - room.addLiveEvents([broadcastEvent, nonBroadcastEvent], { addToState: true }); - }); - - it("should return true if related (reference) to a broadcast event", () => { - expect( - isRelatedToVoiceBroadcast(mkRelatedEvent(room, RelationType.Reference, broadcastEvent, client), client), - ).toBe(true); - }); - - it("should return false if related (reference) is undefeind", () => { - expect(isRelatedToVoiceBroadcast(mkRelatedEvent(room, RelationType.Reference, undefined, client), client)).toBe( - false, - ); - }); - - it("should return false if related (referenireplace) to a broadcast event", () => { - expect( - isRelatedToVoiceBroadcast(mkRelatedEvent(room, RelationType.Replace, broadcastEvent, client), client), - ).toBe(false); - }); - - it("should return false if the event has no relation", () => { - const noRelationEvent = mkEvent({ - event: true, - type: EventType.RoomMessage, - room: room.roomId, - content: {}, - user: client.getSafeUserId(), - }); - expect(isRelatedToVoiceBroadcast(noRelationEvent, client)).toBe(false); - }); - - it("should return false for an unknown room", () => { - const otherRoom = new Room("!other:example.com", client, client.getSafeUserId()); - expect( - isRelatedToVoiceBroadcast( - mkRelatedEvent(otherRoom, RelationType.Reference, broadcastEvent, client), - client, - ), - ).toBe(false); - }); -}); diff --git a/test/unit-tests/voice-broadcast/utils/pauseNonLiveBroadcastFromOtherRoom-test.ts b/test/unit-tests/voice-broadcast/utils/pauseNonLiveBroadcastFromOtherRoom-test.ts deleted file mode 100644 index 565ec77053c..00000000000 --- a/test/unit-tests/voice-broadcast/utils/pauseNonLiveBroadcastFromOtherRoom-test.ts +++ /dev/null @@ -1,98 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { MatrixClient, Room } from "matrix-js-sdk/src/matrix"; - -import { - VoiceBroadcastInfoState, - VoiceBroadcastPlayback, - VoiceBroadcastPlaybacksStore, - VoiceBroadcastRecordingsStore, -} from "../../../../src/voice-broadcast"; -import { pauseNonLiveBroadcastFromOtherRoom } from "../../../../src/voice-broadcast/utils/pauseNonLiveBroadcastFromOtherRoom"; -import { stubClient } from "../../../test-utils"; -import { mkVoiceBroadcastInfoStateEvent } from "./test-utils"; - -describe("pauseNonLiveBroadcastFromOtherRoom", () => { - const roomId = "!room:example.com"; - const roomId2 = "!room2@example.com"; - let room: Room; - let client: MatrixClient; - let playback: VoiceBroadcastPlayback; - let playbacks: VoiceBroadcastPlaybacksStore; - let recordings: VoiceBroadcastRecordingsStore; - - const mkPlayback = (infoState: VoiceBroadcastInfoState, roomId: string): VoiceBroadcastPlayback => { - const infoEvent = mkVoiceBroadcastInfoStateEvent( - roomId, - infoState, - client.getSafeUserId(), - client.getDeviceId()!, - ); - const playback = new VoiceBroadcastPlayback(infoEvent, client, recordings); - jest.spyOn(playback, "pause"); - playbacks.setCurrent(playback); - return playback; - }; - - beforeEach(() => { - client = stubClient(); - room = new Room(roomId, client, client.getSafeUserId()); - recordings = new VoiceBroadcastRecordingsStore(); - playbacks = new VoiceBroadcastPlaybacksStore(recordings); - jest.spyOn(playbacks, "clearCurrent"); - }); - - afterEach(() => { - playback?.destroy(); - playbacks.destroy(); - }); - - describe("when there is no current playback", () => { - it("should not clear the current playback", () => { - pauseNonLiveBroadcastFromOtherRoom(room, playbacks); - expect(playbacks.clearCurrent).not.toHaveBeenCalled(); - }); - }); - - describe("when listening to a live broadcast in another room", () => { - beforeEach(() => { - playback = mkPlayback(VoiceBroadcastInfoState.Started, roomId2); - }); - - it("should not clear current / pause the playback", () => { - pauseNonLiveBroadcastFromOtherRoom(room, playbacks); - expect(playbacks.clearCurrent).not.toHaveBeenCalled(); - expect(playback.pause).not.toHaveBeenCalled(); - }); - }); - - describe("when listening to a non-live broadcast in the same room", () => { - beforeEach(() => { - playback = mkPlayback(VoiceBroadcastInfoState.Stopped, roomId); - }); - - it("should not clear current / pause the playback", () => { - pauseNonLiveBroadcastFromOtherRoom(room, playbacks); - expect(playbacks.clearCurrent).not.toHaveBeenCalled(); - expect(playback.pause).not.toHaveBeenCalled(); - }); - }); - - describe("when listening to a non-live broadcast in another room", () => { - beforeEach(() => { - playback = mkPlayback(VoiceBroadcastInfoState.Stopped, roomId2); - }); - - it("should clear current and pause the playback", () => { - pauseNonLiveBroadcastFromOtherRoom(room, playbacks); - expect(playbacks.getCurrent()).toBeNull(); - expect(playback.pause).toHaveBeenCalled(); - }); - }); -}); diff --git a/test/unit-tests/voice-broadcast/utils/retrieveStartedInfoEvent-test.ts b/test/unit-tests/voice-broadcast/utils/retrieveStartedInfoEvent-test.ts deleted file mode 100644 index 70316f3b29e..00000000000 --- a/test/unit-tests/voice-broadcast/utils/retrieveStartedInfoEvent-test.ts +++ /dev/null @@ -1,81 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { mocked } from "jest-mock"; -import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; - -import { - retrieveStartedInfoEvent, - VoiceBroadcastInfoEventType, - VoiceBroadcastInfoState, -} from "../../../../src/voice-broadcast"; -import { mkEvent, stubClient } from "../../../test-utils"; -import { mkVoiceBroadcastInfoStateEvent } from "./test-utils"; - -describe("retrieveStartedInfoEvent", () => { - let client: MatrixClient; - let room: Room; - - const mkStartEvent = () => { - return mkVoiceBroadcastInfoStateEvent( - room.roomId, - VoiceBroadcastInfoState.Started, - client.getUserId()!, - client.deviceId!, - ); - }; - - const mkStopEvent = (startEvent: MatrixEvent) => { - return mkVoiceBroadcastInfoStateEvent( - room.roomId, - VoiceBroadcastInfoState.Stopped, - client.getUserId()!, - client.deviceId!, - startEvent, - ); - }; - - beforeEach(() => { - client = stubClient(); - room = new Room("!room:example.com", client, client.getUserId()!); - mocked(client.getRoom).mockImplementation((roomId: string): Room | null => { - if (roomId === room.roomId) return room; - return null; - }); - }); - - it("when passing a started event, it should return the event", async () => { - const event = mkStartEvent(); - expect(await retrieveStartedInfoEvent(event, client)).toBe(event); - }); - - it("when passing an event without relation, it should return null", async () => { - const event = mkEvent({ - event: true, - type: VoiceBroadcastInfoEventType, - user: client.getUserId()!, - content: {}, - }); - expect(await retrieveStartedInfoEvent(event, client)).toBeNull(); - }); - - it("when the room contains the event, it should return it", async () => { - const startEvent = mkStartEvent(); - const stopEvent = mkStopEvent(startEvent); - room.addLiveEvents([startEvent], { addToState: true }); - expect(await retrieveStartedInfoEvent(stopEvent, client)).toBe(startEvent); - }); - - it("when the room not contains the event, it should fetch it", async () => { - const startEvent = mkStartEvent(); - const stopEvent = mkStopEvent(startEvent); - mocked(client.fetchRoomEvent).mockResolvedValue(startEvent.event); - expect((await retrieveStartedInfoEvent(stopEvent, client))?.getId()).toBe(startEvent.getId()); - expect(client.fetchRoomEvent).toHaveBeenCalledWith(room.roomId, startEvent.getId()); - }); -}); diff --git a/test/unit-tests/voice-broadcast/utils/setUpVoiceBroadcastPreRecording-test.ts b/test/unit-tests/voice-broadcast/utils/setUpVoiceBroadcastPreRecording-test.ts deleted file mode 100644 index 40c94aa3711..00000000000 --- a/test/unit-tests/voice-broadcast/utils/setUpVoiceBroadcastPreRecording-test.ts +++ /dev/null @@ -1,123 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { mocked } from "jest-mock"; -import { MatrixClient, MatrixEvent, Room, SyncState } from "matrix-js-sdk/src/matrix"; - -import Modal from "../../../../src/Modal"; -import { - VoiceBroadcastInfoState, - VoiceBroadcastPlayback, - VoiceBroadcastPlaybacksStore, - VoiceBroadcastPreRecording, - VoiceBroadcastPreRecordingStore, - VoiceBroadcastRecordingsStore, -} from "../../../../src/voice-broadcast"; -import { setUpVoiceBroadcastPreRecording } from "../../../../src/voice-broadcast/utils/setUpVoiceBroadcastPreRecording"; -import { mkRoomMemberJoinEvent, stubClient } from "../../../test-utils"; -import { mkVoiceBroadcastInfoStateEvent } from "./test-utils"; - -jest.mock("../../../../src/Modal"); - -describe("setUpVoiceBroadcastPreRecording", () => { - const roomId = "!room:example.com"; - let client: MatrixClient; - let userId: string; - let room: Room; - let preRecordingStore: VoiceBroadcastPreRecordingStore; - let infoEvent: MatrixEvent; - let playback: VoiceBroadcastPlayback; - let playbacksStore: VoiceBroadcastPlaybacksStore; - let recordingsStore: VoiceBroadcastRecordingsStore; - let preRecording: VoiceBroadcastPreRecording | null; - - const itShouldNotCreateAPreRecording = () => { - it("should return null", () => { - expect(preRecording).toBeNull(); - }); - - it("should not create a broadcast pre recording", () => { - expect(preRecordingStore.getCurrent()).toBeNull(); - }); - }; - - const setUpPreRecording = async () => { - preRecording = await setUpVoiceBroadcastPreRecording( - room, - client, - playbacksStore, - recordingsStore, - preRecordingStore, - ); - }; - - beforeEach(() => { - client = stubClient(); - userId = client.getSafeUserId(); - room = new Room(roomId, client, userId); - infoEvent = mkVoiceBroadcastInfoStateEvent( - roomId, - VoiceBroadcastInfoState.Started, - client.getUserId()!, - client.getDeviceId()!, - ); - preRecording = null; - preRecordingStore = new VoiceBroadcastPreRecordingStore(); - recordingsStore = new VoiceBroadcastRecordingsStore(); - playback = new VoiceBroadcastPlayback(infoEvent, client, recordingsStore); - jest.spyOn(playback, "pause"); - playbacksStore = new VoiceBroadcastPlaybacksStore(recordingsStore); - }); - - describe("when trying to start a broadcast if there is no connection", () => { - beforeEach(async () => { - mocked(client.getSyncState).mockReturnValue(SyncState.Error); - await setUpPreRecording(); - }); - - it("should show an info dialog and not set up a pre-recording", () => { - expect(preRecordingStore.getCurrent()).toBeNull(); - expect(Modal.createDialog).toMatchSnapshot(); - }); - }); - - describe("when setting up a pre-recording", () => { - describe("and there is no user id", () => { - beforeEach(async () => { - mocked(client.getUserId).mockReturnValue(null); - await setUpPreRecording(); - }); - - itShouldNotCreateAPreRecording(); - }); - - describe("and there is no room member", () => { - beforeEach(async () => { - // check test precondition - expect(room.getMember(userId)).toBeNull(); - await setUpPreRecording(); - }); - - itShouldNotCreateAPreRecording(); - }); - - describe("and there is a room member and listening to another broadcast", () => { - beforeEach(async () => { - playbacksStore.setCurrent(playback); - room.currentState.setStateEvents([mkRoomMemberJoinEvent(userId, roomId)]); - await setUpPreRecording(); - }); - - it("should pause the current playback and create a voice broadcast pre-recording", () => { - expect(playback.pause).toHaveBeenCalled(); - expect(playbacksStore.getCurrent()).toBeNull(); - expect(preRecording).toBeInstanceOf(VoiceBroadcastPreRecording); - }); - }); - }); -}); diff --git a/test/unit-tests/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastRecordingTile-test.ts b/test/unit-tests/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastRecordingTile-test.ts deleted file mode 100644 index 6448e3d2e75..00000000000 --- a/test/unit-tests/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastRecordingTile-test.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { mocked } from "jest-mock"; -import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix"; - -import { shouldDisplayAsVoiceBroadcastRecordingTile, VoiceBroadcastInfoState } from "../../../../src/voice-broadcast"; -import { createTestClient } from "../../../test-utils"; -import { mkVoiceBroadcastInfoStateEvent } from "./test-utils"; - -type TestTuple = [string | null, string, string, string, VoiceBroadcastInfoState, boolean]; - -const testCases: TestTuple[] = [ - [ - "@user1:example.com", // own MXID - "@user1:example.com", // sender MXID - "ABC123", // own device ID - "ABC123", // sender device ID - VoiceBroadcastInfoState.Started, - true, // expected return value - ], - ["@user1:example.com", "@user1:example.com", "ABC123", "ABC123", VoiceBroadcastInfoState.Paused, true], - ["@user1:example.com", "@user1:example.com", "ABC123", "ABC123", VoiceBroadcastInfoState.Resumed, true], - ["@user1:example.com", "@user1:example.com", "ABC123", "ABC123", VoiceBroadcastInfoState.Stopped, false], - ["@user2:example.com", "@user1:example.com", "ABC123", "ABC123", VoiceBroadcastInfoState.Started, false], - [null, "@user1:example.com", "ABC123", "ABC123", VoiceBroadcastInfoState.Started, false], - // other device - ["@user1:example.com", "@user1:example.com", "ABC123", "JKL123", VoiceBroadcastInfoState.Started, false], - ["@user1:example.com", "@user1:example.com", "ABC123", "JKL123", VoiceBroadcastInfoState.Paused, false], - ["@user1:example.com", "@user1:example.com", "ABC123", "JKL123", VoiceBroadcastInfoState.Resumed, false], -]; - -describe("shouldDisplayAsVoiceBroadcastRecordingTile", () => { - let event: MatrixEvent; - let client: MatrixClient; - - beforeAll(() => { - client = createTestClient(); - }); - - describe.each(testCases)( - "when called with user »%s«, sender »%s«, device »%s«, sender device »%s« state »%s«", - (userId, senderId, deviceId, senderDeviceId, state, expected) => { - beforeEach(() => { - event = mkVoiceBroadcastInfoStateEvent("!room:example.com", state, senderId, senderDeviceId); - mocked(client.getUserId).mockReturnValue(userId); - mocked(client.getDeviceId).mockReturnValue(deviceId); - }); - - it(`should return ${expected}`, () => { - expect(shouldDisplayAsVoiceBroadcastRecordingTile(state, client, event)).toBe(expected); - }); - }, - ); - - it("should return false, when all params are null", () => { - event = mkVoiceBroadcastInfoStateEvent("!room:example.com", null, null, null); - // @ts-ignore Simulate null state received for any reason. - expect(shouldDisplayAsVoiceBroadcastRecordingTile(null, client, event)).toBe(false); - }); - - it("should return false, when all params are undefined", () => { - event = mkVoiceBroadcastInfoStateEvent("!room:example.com", undefined, undefined, undefined); - // @ts-ignore Simulate undefined state received for any reason. - expect(shouldDisplayAsVoiceBroadcastRecordingTile(undefined, client, event)).toBe(false); - }); -}); diff --git a/test/unit-tests/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastTile-test.ts b/test/unit-tests/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastTile-test.ts deleted file mode 100644 index fd3d1f91c44..00000000000 --- a/test/unit-tests/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastTile-test.ts +++ /dev/null @@ -1,138 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { EventType, IEvent, MatrixEvent } from "matrix-js-sdk/src/matrix"; - -import { - shouldDisplayAsVoiceBroadcastTile, - VoiceBroadcastInfoEventType, - VoiceBroadcastInfoState, -} from "../../../../src/voice-broadcast"; -import { mkEvent } from "../../../test-utils"; - -describe("shouldDisplayAsVoiceBroadcastTile", () => { - let event: MatrixEvent; - const roomId = "!room:example.com"; - const senderId = "@user:example.com"; - - const itShouldReturnFalse = () => { - it("should return false", () => { - expect(shouldDisplayAsVoiceBroadcastTile(event)).toBe(false); - }); - }; - - const itShouldReturnTrue = () => { - it("should return true", () => { - expect(shouldDisplayAsVoiceBroadcastTile(event)).toBe(true); - }); - }; - - describe("when a broken event occurs", () => { - beforeEach(() => { - event = 23 as unknown as MatrixEvent; - }); - - itShouldReturnFalse(); - }); - - describe("when a non-voice broadcast info event occurs", () => { - beforeEach(() => { - event = mkEvent({ - event: true, - type: EventType.RoomMessage, - room: roomId, - user: senderId, - content: {}, - }); - }); - - itShouldReturnFalse(); - }); - - describe("when a voice broadcast info event with empty content occurs", () => { - beforeEach(() => { - event = mkEvent({ - event: true, - type: VoiceBroadcastInfoEventType, - room: roomId, - user: senderId, - content: {}, - }); - }); - - itShouldReturnFalse(); - }); - - describe("when a voice broadcast info event with undefined content occurs", () => { - beforeEach(() => { - event = mkEvent({ - event: true, - type: VoiceBroadcastInfoEventType, - room: roomId, - user: senderId, - content: {}, - }); - event.getContent = () => ({}) as any; - }); - - itShouldReturnFalse(); - }); - - describe("when a voice broadcast info event in state started occurs", () => { - beforeEach(() => { - event = mkEvent({ - event: true, - type: VoiceBroadcastInfoEventType, - room: roomId, - user: senderId, - content: { - state: VoiceBroadcastInfoState.Started, - }, - }); - }); - - itShouldReturnTrue(); - }); - - describe("when a redacted event occurs", () => { - beforeEach(() => { - event = mkEvent({ - event: true, - type: VoiceBroadcastInfoEventType, - room: roomId, - user: senderId, - content: {}, - unsigned: { - redacted_because: {} as unknown as IEvent, - }, - }); - event.getContent = () => ({}) as any; - }); - - itShouldReturnTrue(); - }); - - describe.each([VoiceBroadcastInfoState.Paused, VoiceBroadcastInfoState.Resumed, VoiceBroadcastInfoState.Stopped])( - "when a voice broadcast info event in state %s occurs", - (state: VoiceBroadcastInfoState) => { - beforeEach(() => { - event = mkEvent({ - event: true, - type: VoiceBroadcastInfoEventType, - room: roomId, - user: senderId, - content: { - state, - }, - }); - }); - - itShouldReturnFalse(); - }, - ); -}); diff --git a/test/unit-tests/voice-broadcast/utils/startNewVoiceBroadcastRecording-test.ts b/test/unit-tests/voice-broadcast/utils/startNewVoiceBroadcastRecording-test.ts deleted file mode 100644 index c22920c1746..00000000000 --- a/test/unit-tests/voice-broadcast/utils/startNewVoiceBroadcastRecording-test.ts +++ /dev/null @@ -1,234 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { mocked } from "jest-mock"; -import { EventType, ISendEventResponse, MatrixClient, MatrixEvent, Room, SyncState } from "matrix-js-sdk/src/matrix"; - -import Modal from "../../../../src/Modal"; -import { - startNewVoiceBroadcastRecording, - VoiceBroadcastInfoEventType, - VoiceBroadcastInfoState, - VoiceBroadcastRecordingsStore, - VoiceBroadcastRecording, - VoiceBroadcastPlaybacksStore, - VoiceBroadcastPlayback, -} from "../../../../src/voice-broadcast"; -import { mkEvent, stubClient } from "../../../test-utils"; -import { mkVoiceBroadcastInfoStateEvent } from "./test-utils"; - -jest.mock("../../../../src/voice-broadcast/models/VoiceBroadcastRecording", () => ({ - VoiceBroadcastRecording: jest.fn(), -})); - -jest.mock("../../../../src/Modal"); - -describe("startNewVoiceBroadcastRecording", () => { - const roomId = "!room:example.com"; - const otherUserId = "@other:example.com"; - let client: MatrixClient; - let playbacksStore: VoiceBroadcastPlaybacksStore; - let recordingsStore: VoiceBroadcastRecordingsStore; - let room: Room; - let infoEvent: MatrixEvent; - let otherEvent: MatrixEvent; - let result: VoiceBroadcastRecording | null; - - beforeEach(() => { - client = stubClient(); - room = new Room(roomId, client, client.getUserId()!); - jest.spyOn(room.currentState, "maySendStateEvent"); - - mocked(client.getRoom).mockImplementation((getRoomId: string) => { - if (getRoomId === roomId) { - return room; - } - - return null; - }); - mocked(client.sendStateEvent).mockImplementation( - (sendRoomId: string, eventType: string, content: any, stateKey: string): Promise => { - if (sendRoomId === roomId && eventType === VoiceBroadcastInfoEventType) { - return Promise.resolve({ event_id: infoEvent.getId()! }); - } - - throw new Error("Unexpected sendStateEvent call"); - }, - ); - - infoEvent = mkVoiceBroadcastInfoStateEvent( - roomId, - VoiceBroadcastInfoState.Started, - client.getUserId()!, - client.getDeviceId()!, - ); - otherEvent = mkEvent({ - event: true, - type: EventType.RoomMember, - content: {}, - user: client.getUserId()!, - room: roomId, - skey: "", - }); - - playbacksStore = new VoiceBroadcastPlaybacksStore(recordingsStore); - recordingsStore = { - setCurrent: jest.fn(), - getCurrent: jest.fn(), - } as unknown as VoiceBroadcastRecordingsStore; - - mocked(VoiceBroadcastRecording).mockImplementation((infoEvent: MatrixEvent, client: MatrixClient): any => { - return { - infoEvent, - client, - start: jest.fn(), - } as unknown as VoiceBroadcastRecording; - }); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe("when trying to start a broadcast if there is no connection", () => { - beforeEach(async () => { - mocked(client.getSyncState).mockReturnValue(SyncState.Error); - result = await startNewVoiceBroadcastRecording(room, client, playbacksStore, recordingsStore); - }); - - it("should show an info dialog and not start a recording", () => { - expect(result).toBeNull(); - expect(Modal.createDialog).toMatchSnapshot(); - }); - }); - - describe("when the current user is allowed to send voice broadcast info state events", () => { - beforeEach(() => { - mocked(room.currentState.maySendStateEvent).mockReturnValue(true); - }); - - describe("when currently listening to a broadcast and there is no recording", () => { - let playback: VoiceBroadcastPlayback; - - beforeEach(() => { - playback = new VoiceBroadcastPlayback(infoEvent, client, recordingsStore); - jest.spyOn(playback, "pause"); - playbacksStore.setCurrent(playback); - }); - - it("should stop listen to the current broadcast and create a new recording", async () => { - mocked(client.sendStateEvent).mockImplementation( - async ( - _roomId: string, - _eventType: string, - _content: any, - _stateKey = "", - ): Promise => { - window.setTimeout(() => { - // emit state events after resolving the promise - room.currentState.setStateEvents([otherEvent]); - room.currentState.setStateEvents([infoEvent]); - }, 0); - return { event_id: infoEvent.getId()! }; - }, - ); - const recording = await startNewVoiceBroadcastRecording(room, client, playbacksStore, recordingsStore); - expect(recording).not.toBeNull(); - - // expect to stop and clear the current playback - expect(playback.pause).toHaveBeenCalled(); - expect(playbacksStore.getCurrent()).toBeNull(); - - expect(client.sendStateEvent).toHaveBeenCalledWith( - roomId, - VoiceBroadcastInfoEventType, - { - chunk_length: 120, - device_id: client.getDeviceId(), - state: VoiceBroadcastInfoState.Started, - }, - client.getUserId()!, - ); - expect(recording!.infoEvent).toBe(infoEvent); - expect(recording!.start).toHaveBeenCalled(); - }); - }); - - describe("when there is already a current voice broadcast", () => { - beforeEach(async () => { - mocked(recordingsStore.getCurrent).mockReturnValue(new VoiceBroadcastRecording(infoEvent, client)); - - result = await startNewVoiceBroadcastRecording(room, client, playbacksStore, recordingsStore); - }); - - it("should not start a voice broadcast", () => { - expect(result).toBeNull(); - }); - - it("should show an info dialog", () => { - expect(Modal.createDialog).toMatchSnapshot(); - }); - }); - - describe("when there already is a live broadcast of the current user in the room", () => { - beforeEach(async () => { - room.currentState.setStateEvents([ - mkVoiceBroadcastInfoStateEvent( - roomId, - VoiceBroadcastInfoState.Resumed, - client.getUserId()!, - client.getDeviceId()!, - ), - ]); - - result = await startNewVoiceBroadcastRecording(room, client, playbacksStore, recordingsStore); - }); - - it("should not start a voice broadcast", () => { - expect(result).toBeNull(); - }); - - it("should show an info dialog", () => { - expect(Modal.createDialog).toMatchSnapshot(); - }); - }); - - describe("when there already is a live broadcast of another user", () => { - beforeEach(async () => { - room.currentState.setStateEvents([ - mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Resumed, otherUserId, "ASD123"), - ]); - - result = await startNewVoiceBroadcastRecording(room, client, playbacksStore, recordingsStore); - }); - - it("should not start a voice broadcast", () => { - expect(result).toBeNull(); - }); - - it("should show an info dialog", () => { - expect(Modal.createDialog).toMatchSnapshot(); - }); - }); - }); - - describe("when the current user is not allowed to send voice broadcast info state events", () => { - beforeEach(async () => { - mocked(room.currentState.maySendStateEvent).mockReturnValue(false); - result = await startNewVoiceBroadcastRecording(room, client, playbacksStore, recordingsStore); - }); - - it("should not start a voice broadcast", () => { - expect(result).toBeNull(); - }); - - it("should show an info dialog", () => { - expect(Modal.createDialog).toMatchSnapshot(); - }); - }); -}); diff --git a/test/unit-tests/voice-broadcast/utils/test-utils.ts b/test/unit-tests/voice-broadcast/utils/test-utils.ts deleted file mode 100644 index 4d465e84dc8..00000000000 --- a/test/unit-tests/voice-broadcast/utils/test-utils.ts +++ /dev/null @@ -1,131 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { Optional } from "matrix-events-sdk"; -import { EventType, IContent, MatrixEvent, MsgType, RelationType, Room, RoomMember } from "matrix-js-sdk/src/matrix"; - -import { SdkContextClass } from "../../../../src/contexts/SDKContext"; -import { - VoiceBroadcastPlayback, - VoiceBroadcastPreRecording, - VoiceBroadcastRecording, -} from "../../../../src/voice-broadcast"; -import { - VoiceBroadcastChunkEventType, - VoiceBroadcastInfoEventType, - VoiceBroadcastInfoState, -} from "../../../../src/voice-broadcast/types"; -import { mkEvent } from "../../../test-utils"; - -// timestamp incremented on each call to prevent duplicate timestamp -let timestamp = new Date().getTime(); - -export const mkVoiceBroadcastInfoStateEvent = ( - roomId: Optional, - state: Optional, - senderId: Optional, - senderDeviceId: Optional, - startedInfoEvent?: MatrixEvent, - lastChunkSequence?: number, -): MatrixEvent => { - const relationContent: IContent = {}; - - if (startedInfoEvent) { - relationContent["m.relates_to"] = { - event_id: startedInfoEvent.getId(), - rel_type: "m.reference", - }; - } - - const lastChunkSequenceContent = lastChunkSequence ? { last_chunk_sequence: lastChunkSequence } : {}; - - return mkEvent({ - event: true, - // @ts-ignore allow everything here for edge test cases - room: roomId, - // @ts-ignore allow everything here for edge test cases - user: senderId, - type: VoiceBroadcastInfoEventType, - // @ts-ignore allow everything here for edge test cases - skey: senderId, - content: { - state, - device_id: senderDeviceId, - ...relationContent, - ...lastChunkSequenceContent, - }, - ts: timestamp++, - }); -}; - -export const mkVoiceBroadcastChunkEvent = ( - infoEventId: string, - userId: string, - roomId: string, - duration: number, - sequence?: number, - timestamp?: number, -): MatrixEvent => { - return mkEvent({ - event: true, - user: userId, - room: roomId, - type: EventType.RoomMessage, - content: { - msgtype: MsgType.Audio, - ["org.matrix.msc1767.audio"]: { - duration, - }, - info: { - duration, - }, - [VoiceBroadcastChunkEventType]: { - ...(sequence ? { sequence } : {}), - }, - ["m.relates_to"]: { - rel_type: RelationType.Reference, - event_id: infoEventId, - }, - }, - ts: timestamp, - }); -}; - -export const mkVoiceBroadcastPlayback = (stores: SdkContextClass): VoiceBroadcastPlayback => { - const infoEvent = mkVoiceBroadcastInfoStateEvent( - "!room:example.com", - VoiceBroadcastInfoState.Started, - "@user:example.com", - "ASD123", - ); - return new VoiceBroadcastPlayback(infoEvent, stores.client!, stores.voiceBroadcastRecordingsStore); -}; - -export const mkVoiceBroadcastRecording = (stores: SdkContextClass): VoiceBroadcastRecording => { - const infoEvent = mkVoiceBroadcastInfoStateEvent( - "!room:example.com", - VoiceBroadcastInfoState.Started, - "@user:example.com", - "ASD123", - ); - return new VoiceBroadcastRecording(infoEvent, stores.client!); -}; - -export const mkVoiceBroadcastPreRecording = (stores: SdkContextClass): VoiceBroadcastPreRecording => { - const roomId = "!room:example.com"; - const userId = "@user:example.com"; - const room = new Room(roomId, stores.client!, userId); - const roomMember = new RoomMember(roomId, userId); - return new VoiceBroadcastPreRecording( - room, - roomMember, - stores.client!, - stores.voiceBroadcastPlaybacksStore, - stores.voiceBroadcastRecordingsStore, - ); -}; diff --git a/test/unit-tests/voice-broadcast/utils/textForVoiceBroadcastStoppedEvent-test.tsx b/test/unit-tests/voice-broadcast/utils/textForVoiceBroadcastStoppedEvent-test.tsx deleted file mode 100644 index 37871993d0b..00000000000 --- a/test/unit-tests/voice-broadcast/utils/textForVoiceBroadcastStoppedEvent-test.tsx +++ /dev/null @@ -1,90 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import React from "react"; -import { render, RenderResult, screen } from "jest-matrix-react"; -import userEvent from "@testing-library/user-event"; -import { mocked } from "jest-mock"; -import { MatrixClient, RelationType } from "matrix-js-sdk/src/matrix"; - -import { textForVoiceBroadcastStoppedEvent, VoiceBroadcastInfoState } from "../../../../src/voice-broadcast"; -import { stubClient } from "../../../test-utils"; -import { mkVoiceBroadcastInfoStateEvent } from "./test-utils"; -import dis from "../../../../src/dispatcher/dispatcher"; -import { Action } from "../../../../src/dispatcher/actions"; - -jest.mock("../../../../src/dispatcher/dispatcher"); - -describe("textForVoiceBroadcastStoppedEvent", () => { - const otherUserId = "@other:example.com"; - const roomId = "!room:example.com"; - let client: MatrixClient; - - const renderText = (senderId: string, startEventId?: string) => { - const event = mkVoiceBroadcastInfoStateEvent( - roomId, - VoiceBroadcastInfoState.Stopped, - senderId, - client.deviceId!, - ); - - if (startEventId) { - event.getContent()["m.relates_to"] = { - rel_type: RelationType.Reference, - event_id: startEventId, - }; - } - - return render(
{textForVoiceBroadcastStoppedEvent(event, client)()}
); - }; - - beforeEach(() => { - client = stubClient(); - }); - - it("should render own broadcast as expected", () => { - expect(renderText(client.getUserId()!).container).toMatchSnapshot(); - }); - - it("should render other users broadcast as expected", () => { - expect(renderText(otherUserId).container).toMatchSnapshot(); - }); - - it("should render without login as expected", () => { - mocked(client.getUserId).mockReturnValue(null); - expect(renderText(otherUserId).container).toMatchSnapshot(); - }); - - describe("when rendering an event with relation to the start event", () => { - let result: RenderResult; - - beforeEach(() => { - result = renderText(client.getUserId()!, "$start-id"); - }); - - it("should render events with relation to the start event", () => { - expect(result.container).toMatchSnapshot(); - }); - - describe("and clicking the link", () => { - beforeEach(async () => { - await userEvent.click(screen.getByRole("button")); - }); - - it("should dispatch an action to highlight the event", () => { - expect(dis.dispatch).toHaveBeenCalledWith({ - action: Action.ViewRoom, - event_id: "$start-id", - highlighted: true, - room_id: roomId, - metricsTrigger: undefined, // room doesn't change - }); - }); - }); - }); -}); diff --git a/test/unit-tests/voice-broadcast/utils/textForVoiceBroadcastStoppedEventWithoutLink-test.ts b/test/unit-tests/voice-broadcast/utils/textForVoiceBroadcastStoppedEventWithoutLink-test.ts deleted file mode 100644 index a010a6ec7c7..00000000000 --- a/test/unit-tests/voice-broadcast/utils/textForVoiceBroadcastStoppedEventWithoutLink-test.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only -Please see LICENSE files in the repository root for full details. -*/ - -import { mocked } from "jest-mock"; -import { MatrixClient } from "matrix-js-sdk/src/matrix"; - -import { textForVoiceBroadcastStoppedEventWithoutLink, VoiceBroadcastInfoState } from "../../../../src/voice-broadcast"; -import { stubClient } from "../../../test-utils"; -import { mkVoiceBroadcastInfoStateEvent } from "./test-utils"; - -describe("textForVoiceBroadcastStoppedEventWithoutLink", () => { - const otherUserId = "@other:example.com"; - const roomId = "!room:example.com"; - let client: MatrixClient; - - beforeAll(() => { - client = stubClient(); - }); - - const getText = (senderId: string, startEventId?: string) => { - const event = mkVoiceBroadcastInfoStateEvent( - roomId, - VoiceBroadcastInfoState.Stopped, - senderId, - client.deviceId!, - ); - return textForVoiceBroadcastStoppedEventWithoutLink(event); - }; - - it("when called for an own broadcast it should return the expected text", () => { - expect(getText(client.getUserId()!)).toBe("You ended a voice broadcast"); - }); - - it("when called for other ones broadcast it should return the expected text", () => { - expect(getText(otherUserId)).toBe(`${otherUserId} ended a voice broadcast`); - }); - - it("when not logged in it should return the exptected text", () => { - mocked(client.getUserId).mockReturnValue(null); - expect(getText(otherUserId)).toBe(`${otherUserId} ended a voice broadcast`); - }); -});