From e33f4ba9c0e8c29cf074f566cbb69c27007f6c57 Mon Sep 17 00:00:00 2001 From: Resynth Date: Sun, 25 Oct 2020 22:04:39 +0000 Subject: [PATCH 001/320] Warn on Access Token reveal Signed-off-by: Resynth --- .../views/dialogs/AccessTokenDialog.tsx | 39 +++++++++++++++++++ .../settings/tabs/user/HelpUserSettingsTab.js | 14 ++++++- 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 src/components/views/dialogs/AccessTokenDialog.tsx diff --git a/src/components/views/dialogs/AccessTokenDialog.tsx b/src/components/views/dialogs/AccessTokenDialog.tsx new file mode 100644 index 0000000000..81c48f219a --- /dev/null +++ b/src/components/views/dialogs/AccessTokenDialog.tsx @@ -0,0 +1,39 @@ + /* +Copyright 2017 Vector Creations Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import { _t } from '../../../languageHandler'; +import QuestionDialog from './QuestionDialog'; + +type IProps = Exclude< + React.ComponentProps, + "title" | "danger" | "description" + >; + +export default function AccessTokenDialog (props: IProps) { + return ( + + ); +} diff --git a/src/components/views/settings/tabs/user/HelpUserSettingsTab.js b/src/components/views/settings/tabs/user/HelpUserSettingsTab.js index 85ba22a353..585a54ff86 100644 --- a/src/components/views/settings/tabs/user/HelpUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/HelpUserSettingsTab.js @@ -27,6 +27,7 @@ import * as sdk from "../../../../../"; import PlatformPeg from "../../../../../PlatformPeg"; import * as KeyboardShortcuts from "../../../../../accessibility/KeyboardShortcuts"; import UpdateCheckButton from "../../UpdateCheckButton"; +import AccessTokenDialog from '../../../dialogs/AccessTokenDialog'; export default class HelpUserSettingsTab extends React.Component { static propTypes = { @@ -148,6 +149,17 @@ export default class HelpUserSettingsTab extends React.Component { ); } + onAccessTokenSpoilerClick = async (event) => { + // React throws away the event before we can use it (we are async, after all). + event.persist(); + + // We make the user accept a scary popup to combat Social Engineering. No peeking! + await Modal.createTrackedDialog('Reveal Access Token', '', AccessTokenDialog).finished; + + // Pass it onto the handler. + this._showSpoiler(event); + } + render() { const brand = SdkConfig.get().brand; @@ -266,7 +278,7 @@ export default class HelpUserSettingsTab extends React.Component { {_t("Homeserver is")} {MatrixClientPeg.get().getHomeserverUrl()}
{_t("Identity Server is")} {MatrixClientPeg.get().getIdentityServerUrl()}
{_t("Access Token:") + ' '} - <{ _t("click to reveal") }> From ae29168e077be47a83628f10b7765d6bcc9a7a0a Mon Sep 17 00:00:00 2001 From: Resynth Date: Sun, 25 Oct 2020 22:05:44 +0000 Subject: [PATCH 002/320] Lint Signed-off-by: Resynth --- src/components/views/dialogs/AccessTokenDialog.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/dialogs/AccessTokenDialog.tsx b/src/components/views/dialogs/AccessTokenDialog.tsx index 81c48f219a..f95effd523 100644 --- a/src/components/views/dialogs/AccessTokenDialog.tsx +++ b/src/components/views/dialogs/AccessTokenDialog.tsx @@ -1,4 +1,4 @@ - /* +/* Copyright 2017 Vector Creations Ltd Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,7 +23,7 @@ type IProps = Exclude< "title" | "danger" | "description" >; -export default function AccessTokenDialog (props: IProps) { +export default function AccessTokenDialog(props: IProps) { return ( ); From 76edd551e5833cb1c94c1025fa069aeb79a9faa2 Mon Sep 17 00:00:00 2001 From: Resynth Date: Mon, 26 Oct 2020 00:34:14 +0000 Subject: [PATCH 003/320] Fix i18n Signed-off-by: Resynth --- src/i18n/strings/en_EN.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index eda69d68ea..ad6593f704 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1585,6 +1585,7 @@ "Add a new server...": "Add a new server...", "%(networkName)s rooms": "%(networkName)s rooms", "Matrix rooms": "Matrix rooms", + "Do not reveal your Access Token to anyone, under any circumstances. Sharing your Access Token with someone would allow them to login to your account, and access your private information.": "Do not reveal your Access Token to anyone, under any circumstances. Sharing your Access Token with someone would allow them to login to your account, and access your private information.", "Matrix ID": "Matrix ID", "Matrix Room ID": "Matrix Room ID", "email address": "email address", From a3212c0477ec0fc9166a56a49a9579bae4712d5f Mon Sep 17 00:00:00 2001 From: Resynth Date: Mon, 26 Oct 2020 23:46:53 +0000 Subject: [PATCH 004/320] Fix weird formatting Signed-off-by: Resynth --- src/components/views/dialogs/AccessTokenDialog.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/dialogs/AccessTokenDialog.tsx b/src/components/views/dialogs/AccessTokenDialog.tsx index f95effd523..2d96d8ec20 100644 --- a/src/components/views/dialogs/AccessTokenDialog.tsx +++ b/src/components/views/dialogs/AccessTokenDialog.tsx @@ -1,5 +1,6 @@ /* Copyright 2017 Vector Creations Ltd +Copyright 2020 Resynth Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,7 +22,7 @@ import QuestionDialog from './QuestionDialog'; type IProps = Exclude< React.ComponentProps, "title" | "danger" | "description" - >; +>; export default function AccessTokenDialog(props: IProps) { return ( From 34fbed3fbbbbb0ffc17a15631d256b855080b1ee Mon Sep 17 00:00:00 2001 From: Qt Resynth Date: Fri, 20 Nov 2020 01:15:58 +0000 Subject: [PATCH 005/320] Update src/components/views/dialogs/AccessTokenDialog.tsx --- src/components/views/dialogs/AccessTokenDialog.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/views/dialogs/AccessTokenDialog.tsx b/src/components/views/dialogs/AccessTokenDialog.tsx index 2d96d8ec20..6a943eb5a8 100644 --- a/src/components/views/dialogs/AccessTokenDialog.tsx +++ b/src/components/views/dialogs/AccessTokenDialog.tsx @@ -31,9 +31,7 @@ export default function AccessTokenDialog(props: IProps) { title="Reveal Access Token" danger={true} description={_t( - "Do not reveal your Access Token to anyone, under any circumstances. " + - "Sharing your Access Token with someone would allow them to login to " + - "your account, and access your private information.", + "Your access token gives full access to your account. Do not share it with anyone." )} > ); From 0e4d656e4bfc0fd16d4628ac44668f6aa2f5ae83 Mon Sep 17 00:00:00 2001 From: Resynth Date: Fri, 20 Nov 2020 18:21:39 +0000 Subject: [PATCH 006/320] Update src/i18n/strings/en_EN.json --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index ad6593f704..8005f1cdef 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1585,7 +1585,7 @@ "Add a new server...": "Add a new server...", "%(networkName)s rooms": "%(networkName)s rooms", "Matrix rooms": "Matrix rooms", - "Do not reveal your Access Token to anyone, under any circumstances. Sharing your Access Token with someone would allow them to login to your account, and access your private information.": "Do not reveal your Access Token to anyone, under any circumstances. Sharing your Access Token with someone would allow them to login to your account, and access your private information.", + "Your access token gives full access to your account. Do not share it with anyone.", "Matrix ID": "Matrix ID", "Matrix Room ID": "Matrix Room ID", "email address": "email address", From 1b48b08525f75035b9a09993124a12d442d0f2e2 Mon Sep 17 00:00:00 2001 From: Resynth Date: Fri, 20 Nov 2020 18:23:18 +0000 Subject: [PATCH 007/320] Update src/i18n/strings/en_EN.json --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 8005f1cdef..a57a68e7b4 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1585,7 +1585,7 @@ "Add a new server...": "Add a new server...", "%(networkName)s rooms": "%(networkName)s rooms", "Matrix rooms": "Matrix rooms", - "Your access token gives full access to your account. Do not share it with anyone.", + "Your access token gives full access to your account. Do not share it with anyone.": "Your access token gives full access to your account. Do not share it with anyone.", "Matrix ID": "Matrix ID", "Matrix Room ID": "Matrix Room ID", "email address": "email address", From d44aab6d321d04deb6733d9ace5f54b5fe1104bf Mon Sep 17 00:00:00 2001 From: Resynth Date: Mon, 23 Nov 2020 12:57:06 +0000 Subject: [PATCH 008/320] Update src/components/views/dialogs/AccessTokenDialog.tsx Co-authored-by: Michael Telatynski <7t3chguy@googlemail.com> --- src/components/views/dialogs/AccessTokenDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/dialogs/AccessTokenDialog.tsx b/src/components/views/dialogs/AccessTokenDialog.tsx index 6a943eb5a8..c0b1b929df 100644 --- a/src/components/views/dialogs/AccessTokenDialog.tsx +++ b/src/components/views/dialogs/AccessTokenDialog.tsx @@ -31,7 +31,7 @@ export default function AccessTokenDialog(props: IProps) { title="Reveal Access Token" danger={true} description={_t( - "Your access token gives full access to your account. Do not share it with anyone." + "Your access token gives full access to your account. Do not share it with anyone.", )} >
); From 9ffef8f0726414490074110224804a1257223c0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 1 Mar 2021 12:53:10 +0100 Subject: [PATCH 009/320] Fix VoIP PIP frame color MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/voip/_CallView.scss | 3 ++- res/themes/dark/css/_dark.scss | 2 +- res/themes/light/css/_light.scss | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 7eb329594a..2d5ddec2a4 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -17,7 +17,7 @@ limitations under the License. .mx_CallView { border-radius: 8px; - background-color: $voipcall-plinth-color; + background-color: $dark-panel-bg-color; padding-left: 8px; padding-right: 8px; // XXX: CallContainer sets pointer-events: none - should probably be set back in a better place @@ -37,6 +37,7 @@ limitations under the License. width: 320px; padding-bottom: 8px; margin-top: 10px; + background-color: $voipcall-plinth-color; box-shadow: 0px 14px 24px rgba(0, 0, 0, 0.08); border-radius: 8px; diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index a878aa3cdd..0ad4ba7c58 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -109,7 +109,7 @@ $header-divider-color: $header-panel-text-primary-color; $composer-e2e-icon-color: $header-panel-text-primary-color; // this probably shouldn't have it's own colour -$voipcall-plinth-color: #21262c; +$voipcall-plinth-color: #24292f; // ******************** diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 1c89d83c01..bb673c28c9 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -167,7 +167,7 @@ $composer-e2e-icon-color: #91A1C0; $header-divider-color: #91A1C0; // this probably shouldn't have it's own colour -$voipcall-plinth-color: #f2f5f8; +$voipcall-plinth-color: #dddfe2; // ******************** From 62e9d7f46bfb6ee07b012904a993d443ee87590e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 6 Mar 2021 09:02:15 +0100 Subject: [PATCH 010/320] Cleaner imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/voip/CallView.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index b4dad3b19a..fbd30cbc9b 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -22,8 +22,7 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg'; import { _t, _td } from '../../../languageHandler'; import VideoFeed, { VideoFeedType } from "./VideoFeed"; import RoomAvatar from "../avatars/RoomAvatar"; -import { CallState, CallType, MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; -import { CallEvent } from 'matrix-js-sdk/src/webrtc/call'; +import { CallState, CallType, MatrixCall, CallEvent } from 'matrix-js-sdk/src/webrtc/call'; import classNames from 'classnames'; import AccessibleButton from '../elements/AccessibleButton'; import {isOnlyCtrlOrCmdKeyEvent, Key} from '../../../Keyboard'; From bb13dc49a6d89a1f1a50c762b3c241fc5d4b9757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 7 Mar 2021 08:13:35 +0100 Subject: [PATCH 011/320] Make CallView use CallFeed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/voip/_CallView.scss | 1 + res/css/views/voip/_VideoFeed.scss | 18 +++- src/CallHandler.tsx | 12 +-- src/components/views/voip/CallView.tsx | 122 ++++++++++++++++++------ src/components/views/voip/VideoFeed.tsx | 100 ++++++++++++++----- 5 files changed, 185 insertions(+), 68 deletions(-) diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 7e2d12e539..ed3ff2afa9 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -112,6 +112,7 @@ limitations under the License. z-index: 30; border-radius: 8px; overflow: hidden; + display: flex; } .mx_CallView_video_hold { diff --git a/res/css/views/voip/_VideoFeed.scss b/res/css/views/voip/_VideoFeed.scss index 8ead8bba3e..46cdf4f52c 100644 --- a/res/css/views/voip/_VideoFeed.scss +++ b/res/css/views/voip/_VideoFeed.scss @@ -14,11 +14,21 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_VideoFeed_remote { - width: 100%; - height: 100%; +.mx_VideoFeed_voice { + // We don't want to collide with the call controls that have 52px of height + padding-bottom: 52px; + background-color: $inverted-bg-color; +} + +.mx_VideoFeed_video { background-color: #000; - z-index: 50; +} + +.mx_VideoFeed_remote { + flex: 1; + display: flex; + justify-content: center; + align-items: center; } .mx_VideoFeed_local { diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 42a38c7a54..e090270c95 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -621,7 +621,6 @@ export default class CallHandler { private async placeCall( roomId: string, type: PlaceCallType, - localElement: HTMLVideoElement, remoteElement: HTMLVideoElement, ) { Analytics.trackEvent('voip', 'placeCall', 'type', type); CountlyAnalytics.instance.trackStartCall(roomId, type === PlaceCallType.Video, false); @@ -643,10 +642,7 @@ export default class CallHandler { if (type === PlaceCallType.Voice) { call.placeVoiceCall(); } else if (type === 'video') { - call.placeVideoCall( - remoteElement, - localElement, - ); + call.placeVideoCall(); } else if (type === PlaceCallType.ScreenSharing) { const screenCapErrorString = PlatformPeg.get().screenCaptureErrorString(); if (screenCapErrorString) { @@ -660,8 +656,6 @@ export default class CallHandler { } call.placeScreenSharingCall( - remoteElement, - localElement, async () : Promise => { const {finished} = Modal.createDialog(DesktopCapturerSourcePicker); const [source] = await finished; @@ -715,14 +709,12 @@ export default class CallHandler { } else if (members.length === 2) { console.info(`Place ${payload.type} call in ${payload.room_id}`); - this.placeCall(payload.room_id, payload.type, payload.local_element, payload.remote_element); + this.placeCall(payload.room_id, payload.type); } else { // > 2 dis.dispatch({ action: "place_conference_call", room_id: payload.room_id, type: payload.type, - remote_element: payload.remote_element, - local_element: payload.local_element, }); } } diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index fbd30cbc9b..86b17dcab2 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -20,7 +20,7 @@ import dis from '../../../dispatcher/dispatcher'; import CallHandler from '../../../CallHandler'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import { _t, _td } from '../../../languageHandler'; -import VideoFeed, { VideoFeedType } from "./VideoFeed"; +import VideoFeed from './VideoFeed'; import RoomAvatar from "../avatars/RoomAvatar"; import { CallState, CallType, MatrixCall, CallEvent } from 'matrix-js-sdk/src/webrtc/call'; import classNames from 'classnames'; @@ -30,6 +30,7 @@ import {alwaysAboveLeftOf, alwaysAboveRightOf, ChevronFace, ContextMenuButton} f import CallContextMenu from '../context_menus/CallContextMenu'; import { avatarUrlForMember } from '../../../Avatar'; import DialpadContextMenu from '../context_menus/DialpadContextMenu'; +import { CallFeed } from 'matrix-js-sdk/src/webrtc/callFeed'; interface IProps { // The call for us to display @@ -58,6 +59,7 @@ interface IState { controlsVisible: boolean, showMoreMenu: boolean, showDialpad: boolean, + feeds: CallFeed[], } function getFullScreenElement() { @@ -112,6 +114,7 @@ export default class CallView extends React.Component { controlsVisible: true, showMoreMenu: false, showDialpad: false, + feeds: this.props.call.getFeeds(), } this.updateCallListeners(null, this.props.call); @@ -169,11 +172,13 @@ export default class CallView extends React.Component { oldCall.removeListener(CallEvent.State, this.onCallState); oldCall.removeListener(CallEvent.LocalHoldUnhold, this.onCallLocalHoldUnhold); oldCall.removeListener(CallEvent.RemoteHoldUnhold, this.onCallRemoteHoldUnhold); + oldCall.removeListener(CallEvent.FeedsChanged, this.onFeedsChanged); } if (newCall) { newCall.on(CallEvent.State, this.onCallState); newCall.on(CallEvent.LocalHoldUnhold, this.onCallLocalHoldUnhold); newCall.on(CallEvent.RemoteHoldUnhold, this.onCallRemoteHoldUnhold); + newCall.on(CallEvent.FeedsChanged, this.onFeedsChanged); } } @@ -183,6 +188,10 @@ export default class CallView extends React.Component { }); }; + private onFeedsChanged = (newFeeds: Array) => { + this.setState({feeds: newFeeds}); + } + private onCallLocalHoldUnhold = () => { this.setState({ isLocalOnHold: this.props.call.isLocalOnHold(), @@ -486,44 +495,71 @@ export default class CallView extends React.Component { }); } - if (this.props.call.type === CallType.Video) { - let localVideoFeed = null; - let onHoldContent = null; - let onHoldBackground = null; - const backgroundStyle: CSSProperties = {}; - const containerClasses = classNames({ - mx_CallView_video: true, - mx_CallView_video_hold: isOnHold, - }); - if (isOnHold) { - onHoldContent =
- {onHoldText} -
; + const avatarSize = this.props.pipMode ? 76 : 160; + if (isOnHold) { + if (this.props.call.type === CallType.Video) { + const containerClasses = classNames({ + mx_CallView_video: true, + mx_CallView_video_hold: isOnHold, + }); + let onHoldContent = null; + let onHoldBackground = null; + const backgroundStyle: CSSProperties = {}; + onHoldContent = ( +
+ {onHoldText} +
+ ); const backgroundAvatarUrl = avatarUrlForMember( - // is it worth getting the size of the div to pass here? + // is it worth getting the size of the div to pass here? this.props.call.getOpponentMember(), 1024, 1024, 'crop', ); backgroundStyle.backgroundImage = 'url(' + backgroundAvatarUrl + ')'; onHoldBackground =
; - } - if (!this.state.vidMuted) { - localVideoFeed = ; - } - contentView =
- {onHoldBackground} - - {localVideoFeed} - {onHoldContent} - {callControls} -
; - } else { - const avatarSize = this.props.pipMode ? 76 : 160; + contentView = ( +
+ {onHoldBackground} + {onHoldContent} + {callControls} +
+ ); + } else { + const classes = classNames({ + mx_CallView_voice: true, + mx_CallView_voice_hold: isOnHold, + }); + + contentView =( +
+
+
+ +
+
+
{onHoldText}
+ {callControls} +
+ ); + } + } else if (this.props.call.noIncomingFeeds()) { + // Here we're reusing the css classes from voice on hold, because + // I am lazy. If this gets merged, the CallView might be subject + // to change anyway - I might take an axe to this file in order to + // try to get other things working const classes = classNames({ mx_CallView_voice: true, - mx_CallView_voice_hold: isOnHold, }); + // Saying "Connecting" here isn't really true, but the best thing + // I can come up with, but this might be subject to change as well contentView =
@@ -534,7 +570,33 @@ export default class CallView extends React.Component { />
-
{onHoldText}
+
{_t("Connecting")}
+ {callControls} +
; + } else { + const containerClasses = classNames({ + mx_CallView_video: true, + }); + + // TODO: Later the CallView should probably be reworked to support any + // number of feeds but now we can always expect there to be two feeds + const feeds = this.state.feeds.map((feed, i) => { + // Here we check to hide local audio feeds to achieve the same UI/UX + // as before. But once again this might be subject to change + if (feed.isAudioOnly() && feed.isLocal()) return; + return ( + + ); + }); + + contentView =
+ {feeds} {callControls}
; } diff --git a/src/components/views/voip/VideoFeed.tsx b/src/components/views/voip/VideoFeed.tsx index 1e950f3a2a..be674630b3 100644 --- a/src/components/views/voip/VideoFeed.tsx +++ b/src/components/views/voip/VideoFeed.tsx @@ -18,50 +18,81 @@ import classnames from 'classnames'; import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; import React, {createRef} from 'react'; import SettingsStore from "../../../settings/SettingsStore"; - -export enum VideoFeedType { - Local, - Remote, -} +import { CallFeed, CallFeedEvent } from 'matrix-js-sdk/src/webrtc/callFeed'; +import { MatrixClientPeg } from '../../../MatrixClientPeg'; +import { logger } from 'matrix-js-sdk/src/logger'; +import MemberAvatar from "../avatars/MemberAvatar" +import CallHandler from '../../../CallHandler'; interface IProps { call: MatrixCall, - type: VideoFeedType, + feed: CallFeed, + + // Whether this call view is for picture-in-pictue mode + // otherwise, it's the larger call view when viewing the room the call is in. + // This is sort of a proxy for a number of things but we currently have no + // need to control those things separately, so this is simpler. + pipMode?: boolean; // a callback which is called when the video element is resized // due to a change in video metadata onResize?: (e: Event) => void, } -export default class VideoFeed extends React.Component { +interface IState { + audioOnly: boolean; +} + +export default class VideoFeed extends React.Component { private vid = createRef(); - componentDidMount() { - this.vid.current.addEventListener('resize', this.onResize); - this.setVideoElement(); + constructor(props: IProps) { + super(props); + + this.state = { + audioOnly: this.props.feed.isAudioOnly(), + }; } - componentDidUpdate(prevProps) { - if (this.props.call !== prevProps.call) { - this.setVideoElement(); + componentDidMount() { + this.props.feed.addListener(CallFeedEvent.NewStream, this.onNewStream); + if (!this.vid.current) return; + // A note on calling methods on media elements: + // We used to have queues per media element to serialise all calls on those elements. + // The reason given for this was that load() and play() were racing. However, we now + // never call load() explicitly so this seems unnecessary. However, serialising every + // operation was causing bugs where video would not resume because some play command + // had got stuck and all media operations were queued up behind it. If necessary, we + // should serialise the ones that need to be serialised but then be able to interrupt + // them with another load() which will cancel the pending one, but since we don't call + // load() explicitly, it shouldn't be a problem. - Dave + this.vid.current.srcObject = this.props.feed.stream; + this.vid.current.autoplay = true; + this.vid.current.muted = true; + try { + this.vid.current.play(); + } catch (e) { + logger.info("Failed to play video element with feed", this.props.feed, e); } } componentWillUnmount() { + this.props.feed.removeListener(CallFeedEvent.NewStream, this.onNewStream); + if (!this.vid.current) return; this.vid.current.removeEventListener('resize', this.onResize); + this.vid.current.pause(); + this.vid.current.srcObject = null; } - private setVideoElement() { - if (this.props.type === VideoFeedType.Local) { - this.props.call.setLocalVideoElement(this.vid.current); - } else { - this.props.call.setRemoteVideoElement(this.vid.current); - } + onNewStream = (newStream: MediaStream) => { + this.setState({ audioOnly: this.props.feed.isAudioOnly()}); + if (!this.vid.current) return; + this.vid.current.srcObject = newStream; } onResize = (e) => { - if (this.props.onResize) { + if (this.props.onResize && !this.props.feed.isLocal()) { this.props.onResize(e); } }; @@ -69,14 +100,35 @@ export default class VideoFeed extends React.Component { render() { const videoClasses = { mx_VideoFeed: true, - mx_VideoFeed_local: this.props.type === VideoFeedType.Local, - mx_VideoFeed_remote: this.props.type === VideoFeedType.Remote, + mx_VideoFeed_local: this.props.feed.isLocal(), + mx_VideoFeed_remote: !this.props.feed.isLocal(), + mx_VideoFeed_voice: this.state.audioOnly, + mx_VideoFeed_video: !this.state.audioOnly, mx_VideoFeed_mirror: ( - this.props.type === VideoFeedType.Local && + this.props.feed.isLocal() && SettingsStore.getValue('VideoView.flipVideoHorizontally') ), }; - return
); } else { return ( -