Implement voice broadcast device selection (#9572)

This commit is contained in:
Michael Weimann 2022-11-15 10:02:40 +01:00 committed by GitHub
parent 272aae0973
commit 436146105e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 248 additions and 51 deletions

View file

@ -12,7 +12,8 @@ limitations under the License.
*/
import React from "react";
import { Room, RoomMember } from "matrix-js-sdk/src/matrix";
import { Room } from "matrix-js-sdk/src/matrix";
import classNames from "classnames";
import { LiveBadge } from "../..";
import { Icon as LiveIcon } from "../../../../res/img/element-icons/live.svg";
@ -28,8 +29,9 @@ import { formatTimeLeft } from "../../../DateUtils";
interface VoiceBroadcastHeaderProps {
live?: boolean;
onCloseClick?: () => void;
onMicrophoneLineClick?: () => void;
room: Room;
sender: RoomMember;
microphoneLabel?: string;
showBroadcast?: boolean;
timeLeft?: number;
showClose?: boolean;
@ -38,8 +40,9 @@ interface VoiceBroadcastHeaderProps {
export const VoiceBroadcastHeader: React.FC<VoiceBroadcastHeaderProps> = ({
live = false,
onCloseClick = () => {},
onMicrophoneLineClick,
room,
sender,
microphoneLabel,
showBroadcast = false,
showClose = false,
timeLeft,
@ -66,16 +69,28 @@ export const VoiceBroadcastHeader: React.FC<VoiceBroadcastHeaderProps> = ({
</div>
: null;
const microphoneLineClasses = classNames({
mx_VoiceBroadcastHeader_line: true,
["mx_VoiceBroadcastHeader_mic--clickable"]: onMicrophoneLineClick,
});
const microphoneLine = microphoneLabel
? <div
className={microphoneLineClasses}
onClick={onMicrophoneLineClick}
>
<MicrophoneIcon className="mx_Icon mx_Icon_16" />
<span>{ microphoneLabel }</span>
</div>
: null;
return <div className="mx_VoiceBroadcastHeader">
<RoomAvatar room={room} width={32} height={32} />
<div className="mx_VoiceBroadcastHeader_content">
<div className="mx_VoiceBroadcastHeader_room">
{ room.name }
</div>
<div className="mx_VoiceBroadcastHeader_line">
<MicrophoneIcon className="mx_Icon mx_Icon_16" />
<span>{ sender.name }</span>
</div>
{ microphoneLine }
{ timeLeftLine }
{ broadcast }
</div>

View file

@ -80,7 +80,7 @@ export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProp
<div className="mx_VoiceBroadcastBody">
<VoiceBroadcastHeader
live={live}
sender={sender}
microphoneLabel={sender?.name}
room={room}
showBroadcast={true}
/>

View file

@ -14,26 +14,106 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
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/element-icons/live.svg";
import { _t } from "../../../languageHandler";
import IconizedContextMenu, {
IconizedContextMenuOptionList,
IconizedContextMenuRadio,
} from "../../../components/views/context_menus/IconizedContextMenu";
import { requestMediaPermissions } from "../../../utils/media/requestMediaPermissions";
import MediaDeviceHandler from "../../../MediaDeviceHandler";
import { toLeftOrRightOf } from "../../../components/structures/ContextMenu";
interface Props {
voiceBroadcastPreRecording: VoiceBroadcastPreRecording;
}
interface State {
devices: MediaDeviceInfo[];
device: MediaDeviceInfo | null;
showDeviceSelect: boolean;
}
export const VoiceBroadcastPreRecordingPip: React.FC<Props> = ({
voiceBroadcastPreRecording,
}) => {
return <div className="mx_VoiceBroadcastBody mx_VoiceBroadcastBody--pip">
const shouldRequestPermissionsRef = useRef<boolean>(true);
const pipRef = useRef<HTMLDivElement>(null);
const [state, setState] = useState<State>({
devices: [],
device: null,
showDeviceSelect: false,
});
if (shouldRequestPermissionsRef.current) {
shouldRequestPermissionsRef.current = false;
requestMediaPermissions(false).then((stream: MediaStream | undefined) => {
MediaDeviceHandler.getDevices().then(({ audioinput }) => {
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 onDeviceOptionClick = (device: MediaDeviceInfo) => {
setState({
...state,
device,
showDeviceSelect: false,
});
};
const onMicrophoneLineClick = () => {
setState({
...state,
showDeviceSelect: true,
});
};
const deviceOptions = state.devices.map((d: MediaDeviceInfo) => {
return <IconizedContextMenuRadio
key={d.deviceId}
active={d.deviceId === state.device?.deviceId}
onClick={() => onDeviceOptionClick(d)}
label={d.label}
/>;
});
const devicesMenu = state.showDeviceSelect && pipRef.current
? <IconizedContextMenu
mountAsChild={false}
onFinished={() => {}}
{...toLeftOrRightOf(pipRef.current.getBoundingClientRect(), 0)}
>
<IconizedContextMenuOptionList>
{ deviceOptions }
</IconizedContextMenuOptionList>
</IconizedContextMenu>
: null;
return <div
className="mx_VoiceBroadcastBody mx_VoiceBroadcastBody--pip"
ref={pipRef}
>
<VoiceBroadcastHeader
onCloseClick={voiceBroadcastPreRecording.cancel}
onMicrophoneLineClick={onMicrophoneLineClick}
room={voiceBroadcastPreRecording.room}
sender={voiceBroadcastPreRecording.sender}
microphoneLabel={state.device?.label || _t('Default Device')}
showClose={true}
/>
<AccessibleButton
@ -44,5 +124,6 @@ export const VoiceBroadcastPreRecordingPip: React.FC<Props> = ({
<LiveIcon className="mx_Icon mx_Icon_16" />
{ _t("Go live") }
</AccessibleButton>
{ devicesMenu }
</div>;
};

View file

@ -30,7 +30,7 @@ export const VoiceBroadcastRecordingBody: React.FC<VoiceBroadcastRecordingBodyPr
<div className="mx_VoiceBroadcastBody">
<VoiceBroadcastHeader
live={live}
sender={sender}
microphoneLabel={sender?.name}
room={room}
/>
</div>

View file

@ -38,7 +38,6 @@ export const VoiceBroadcastRecordingPip: React.FC<VoiceBroadcastRecordingPipProp
timeLeft,
recordingState,
room,
sender,
stopRecording,
toggleRecording,
} = useVoiceBroadcastRecording(recording);
@ -57,7 +56,6 @@ export const VoiceBroadcastRecordingPip: React.FC<VoiceBroadcastRecordingPipProp
>
<VoiceBroadcastHeader
live={live}
sender={sender}
room={room}
timeLeft={timeLeft}
/>