Implement voice broadcast device selection (#9572)
This commit is contained in:
parent
272aae0973
commit
436146105e
15 changed files with 248 additions and 51 deletions
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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>;
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue