enhance: polish voice
This commit is contained in:
@@ -309,3 +309,8 @@ svg:focus {
|
|||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
/* camera video object-fit */
|
||||||
|
|
||||||
|
[id^="CAMERA_"] video {
|
||||||
|
object-fit: cover !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { memo, useEffect } from 'react';
|
|||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { useGetAgoraChannelsQuery, useGetAgoraStatusQuery, useGenerateAgoraTokenMutation } from '../../app/services/server';
|
import { useGetAgoraChannelsQuery, useGetAgoraStatusQuery, useGenerateAgoraTokenMutation } from '../../app/services/server';
|
||||||
import { updateChannelVisibleAside } from '../../app/slices/footprint';
|
import { updateChannelVisibleAside } from '../../app/slices/footprint';
|
||||||
import { addVoiceMember, removeVoiceMember, updateConnectionState, updateDeafenStatus, updateMuteStatus, updateVoicingInfo, updateVoicingMember, updateVoicingNetworkQuality, upsertVoiceList } from '../../app/slices/voice';
|
import { addVoiceMember, removeVoiceMember, updateConnectionState, updateDeafenStatus, updateMuteStatus, updatePin, updateVoicingInfo, updateVoicingMember, updateVoicingNetworkQuality, upsertVoiceList } from '../../app/slices/voice';
|
||||||
import { useAppSelector } from '../../app/store';
|
import { useAppSelector } from '../../app/store';
|
||||||
import AudioJoin from '../../assets/join.wav';
|
import AudioJoin from '../../assets/join.wav';
|
||||||
import { playAgoraVideo } from '../utils';
|
import { playAgoraVideo } from '../utils';
|
||||||
@@ -202,6 +202,8 @@ const useVoice = ({ id, context = "channel" }: VoiceProps) => {
|
|||||||
};
|
};
|
||||||
const openCamera = async () => {
|
const openCamera = async () => {
|
||||||
const localVideoTrack = await AgoraRTC.createCameraVideoTrack();
|
const localVideoTrack = await AgoraRTC.createCameraVideoTrack();
|
||||||
|
// 取消正在进行的桌面共享
|
||||||
|
await stopShareScreen();
|
||||||
await window.VOICE_CLIENT?.publish(localVideoTrack);
|
await window.VOICE_CLIENT?.publish(localVideoTrack);
|
||||||
dispatch(updateVoicingInfo({
|
dispatch(updateVoicingInfo({
|
||||||
video: true,
|
video: true,
|
||||||
@@ -253,7 +255,7 @@ const useVoice = ({ id, context = "channel" }: VoiceProps) => {
|
|||||||
const startShareScreen = async () => {
|
const startShareScreen = async () => {
|
||||||
try {
|
try {
|
||||||
const localVideoTrack = await AgoraRTC.createScreenVideoTrack({
|
const localVideoTrack = await AgoraRTC.createScreenVideoTrack({
|
||||||
electronScreenSourceId: "share_screen",
|
// electronScreenSourceId: "share_screen",
|
||||||
selfBrowserSurface: "exclude",
|
selfBrowserSurface: "exclude",
|
||||||
// 配置屏幕共享编码参数,详情请查看 API 文档。
|
// 配置屏幕共享编码参数,详情请查看 API 文档。
|
||||||
encoderConfig: "1080p_1",
|
encoderConfig: "1080p_1",
|
||||||
@@ -273,6 +275,11 @@ const useVoice = ({ id, context = "channel" }: VoiceProps) => {
|
|||||||
// 放到全局变量里
|
// 放到全局变量里
|
||||||
window.VIDEO_TRACK_MAP[loginUid] = localVideoTrack;
|
window.VIDEO_TRACK_MAP[loginUid] = localVideoTrack;
|
||||||
playAgoraVideo(loginUid);
|
playAgoraVideo(loginUid);
|
||||||
|
// 进入全屏并Pin自己
|
||||||
|
if (context == "channel") {
|
||||||
|
dispatch(updateChannelVisibleAside({ id, aside: "voice_fullscreen" }));
|
||||||
|
dispatch(updatePin({ uid: loginUid, action: "pin" }));
|
||||||
|
}
|
||||||
// 监听屏幕共享结束事件
|
// 监听屏幕共享结束事件
|
||||||
if ("close" in localVideoTrack) {
|
if ("close" in localVideoTrack) {
|
||||||
localVideoTrack.getMediaStreamTrack().onended = () => {
|
localVideoTrack.getMediaStreamTrack().onended = () => {
|
||||||
@@ -281,7 +288,6 @@ const useVoice = ({ id, context = "channel" }: VoiceProps) => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("start share screen error", error);
|
console.log("start share screen error", error);
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ const Layout: FC<Props> = ({
|
|||||||
{aside}
|
{aside}
|
||||||
</div>}
|
</div>}
|
||||||
{users && <div className="hidden md:block">{users}</div>}
|
{users && <div className="hidden md:block">{users}</div>}
|
||||||
{voice && <div className="absolute right-11 top-14 h-fit overflow-y-scroll bg-white dark:!bg-gray-700 md:block">{voice}</div>}
|
{voice && <div className="max-md:absolute max-md:right-11 max-md:top-14 max-md:h-fit max-md:overflow-y-scroll bg-white dark:!bg-gray-700 md:block">{voice}</div>}
|
||||||
{!readonly && inputMode == "text" && isActive && (
|
{!readonly && inputMode == "text" && isActive && (
|
||||||
<DnDTip context={context} name={name} />
|
<DnDTip context={context} name={name} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -106,8 +106,13 @@ const VoiceManagement = ({ id, context, info, setMute, leave, closeCamera, openC
|
|||||||
{muted ? <IconMicOff className="w-4 fill-gray-500 dark:fill-gray-400" /> : <IconMic className="w-4 fill-gray-500 dark:fill-gray-400" />}
|
{muted ? <IconMicOff className="w-4 fill-gray-500 dark:fill-gray-400" /> : <IconMic className="w-4 fill-gray-500 dark:fill-gray-400" />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div onDoubleClick={handleFullscreen.bind(null, uid)} id={`CAMERA_${uid}`} className="w-[98%] rounded overflow-hidden m-auto">
|
<div className="overflow-hidden group rounded relative" onDoubleClick={handleFullscreen.bind(null, uid)}>
|
||||||
{/* camera placeholder */}
|
<div id={`CAMERA_${uid}`} className="w-[98%] m-auto">
|
||||||
|
{/* camera placeholder */}
|
||||||
|
</div>
|
||||||
|
<button onClick={handleFullscreen.bind(null, uid)} className="invisible group-hover:visible w-5 h-5 p-1 bg-black/40 top-1 right-1.5 absolute rounded">
|
||||||
|
<IconFullscreen className="w-full h-full fill-white" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</li>;
|
</li>;
|
||||||
})}
|
})}
|
||||||
@@ -127,7 +132,7 @@ const VoiceManagement = ({ id, context, info, setMute, leave, closeCamera, openC
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip tip={"Share Screen"} placement="top">
|
<Tooltip tip={"Share Screen"} placement="top">
|
||||||
<li role={"button"} onClick={shareScreen ? stopShareScreen : startShareScreen} className={clsx("py-2 px-3 rounded ", shareScreen ? "bg-green-700" : "bg-gray-100 dark:bg-gray-900")}>
|
<li role={"button"} onClick={shareScreen ? stopShareScreen : startShareScreen} className={clsx("py-2 px-3 rounded ", shareScreen ? "bg-green-700" : "bg-gray-100 dark:bg-gray-900")}>
|
||||||
<IconScreen className="fill-gray-700 dark:fill-gray-300" />
|
<IconScreen className={clsx("dark:fill-gray-300", shareScreen ? "fill-gray-200" : "fill-gray-800")} />
|
||||||
</li>
|
</li>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip tip={"Fullscreen"} placement="top">
|
<Tooltip tip={"Fullscreen"} placement="top">
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ const VoiceFullscreen = ({ id, context }: Props) => {
|
|||||||
<div className="px-7 py-6 flex justify-between">
|
<div className="px-7 py-6 flex justify-between">
|
||||||
<span className='text-sm font-semibold'>{_name}</span>
|
<span className='text-sm font-semibold'>{_name}</span>
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<IconExitScreen role="button" onClick={handleExitFullscreen} className="fill-gray-300" />
|
<IconExitScreen role="button" onClick={handleExitFullscreen} className="fill-gray-200" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* middle */}
|
{/* middle */}
|
||||||
@@ -111,15 +111,15 @@ const VoiceFullscreen = ({ id, context }: Props) => {
|
|||||||
{shareScreen ? <div className={clsx("w-1 h-1 absolute z-40 rounded-full bg-green-700/60", special ? "top-2 left-2" : "top-1 left-1 px-2")} /> : null}
|
{shareScreen ? <div className={clsx("w-1 h-1 absolute z-40 rounded-full bg-green-700/60", special ? "top-2 left-2" : "top-1 left-1 px-2")} /> : null}
|
||||||
{!disablePin &&
|
{!disablePin &&
|
||||||
<Tooltip tip={"Remove pin"} disabled={!special} placement="right">
|
<Tooltip tip={"Remove pin"} disabled={!special} placement="right">
|
||||||
<button data-uid={uid} data-action={special ? "unpin" : "pin"} role={"button"} onClick={handlePin} className={clsx("absolute left-1 top-1 z-40 p-1 rounded bg-black/50", special ? "" : "invisible group-hover:visible")}>
|
<button data-uid={uid} data-action={special ? "unpin" : "pin"} role={"button"} onClick={handlePin} className={clsx("absolute left-1 top-1 z-40 p-1 rounded bg-black/50", special ? "px-2 py-0.5" : "invisible group-hover:visible")}>
|
||||||
{special ? <IconPin className="w-3 h-3 fill-gray-300" /> : <IconPin className="w-3 h-3 fill-gray-300" />}
|
{special ? <IconPin className={clsx("fill-gray-200", special ? "w-4" : "w-3")} /> : <IconPin className={clsx("fill-gray-200", special ? "w-4" : "w-3")} />}
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>}
|
</Tooltip>}
|
||||||
<span className={clsx("text-gray-300 bg-black/50 rounded-lg absolute z-40", special ? "left-2 bottom-2 px-2 py-1 text-sm " : "left-1 bottom-1 p-1 text-xs")} title={curr?.name}>
|
<span className={clsx("text-gray-300 bg-black/50 rounded-lg absolute z-40", special ? "left-2 bottom-2 px-2 py-1 text-sm " : "left-1 bottom-1 p-1 text-xs")} title={curr?.name}>
|
||||||
{curr?.name}
|
{curr?.name}
|
||||||
</span>
|
</span>
|
||||||
<div className={clsx("flex items-center gap-2 absolute z-40 rounded bg-black/50", special ? "bottom-2 right-2 px-2 py-0.5" : "bottom-1 right-1 px-2")}>
|
<div className={clsx("flex items-center gap-2 absolute z-40 rounded bg-black/50", special ? "bottom-2 right-2 px-2 py-0.5" : "bottom-1 right-1 px-2")}>
|
||||||
{muted ? <IconMicOff className={clsx("fill-gray-300", special ? "w-4" : "w-3")} /> : <IconMic className={clsx(" fill-gray-300 ", special ? "w-4" : "w-3")} />}
|
{muted ? <IconMicOff className={clsx("fill-gray-200", special ? "w-4" : "w-3")} /> : <IconMic className={clsx("fill-gray-200 ", special ? "w-4" : "w-3")} />}
|
||||||
</div>
|
</div>
|
||||||
<div id={`CAMERA_${uid}`} className={clsx("absolute top-0 left-0 z-30 w-full h-full overflow-hidden m-auto", special ? "" : "rounded")}>
|
<div id={`CAMERA_${uid}`} className={clsx("absolute top-0 left-0 z-30 w-full h-full overflow-hidden m-auto", special ? "" : "rounded")}>
|
||||||
{/* camera placeholder */}
|
{/* camera placeholder */}
|
||||||
@@ -132,17 +132,17 @@ const VoiceFullscreen = ({ id, context }: Props) => {
|
|||||||
<ul className='py-4 flex justify-center gap-2'>
|
<ul className='py-4 flex justify-center gap-2'>
|
||||||
<Tooltip tip={muted ? t("unmute") : t("mute")} placement="top">
|
<Tooltip tip={muted ? t("unmute") : t("mute")} placement="top">
|
||||||
<li role={"button"} onClick={setMute.bind(null, !muted)} className="flex-center py-1 px-3 rounded bg-gray-100 dark:bg-gray-900">
|
<li role={"button"} onClick={setMute.bind(null, !muted)} className="flex-center py-1 px-3 rounded bg-gray-100 dark:bg-gray-900">
|
||||||
{muted ? <IconMicOff className="fill-gray-700 dark:fill-gray-300" /> : <IconMic className="fill-gray-700 dark:fill-gray-300" />}
|
{muted ? <IconMicOff className="fill-gray-700 dark:fill-gray-200" /> : <IconMic className="fill-gray-700 dark:fill-gray-200" />}
|
||||||
</li>
|
</li>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip tip={video ? t("camera_off") : t("camera_on")} placement="top">
|
<Tooltip tip={video ? t("camera_off") : t("camera_on")} placement="top">
|
||||||
<li role={"button"} onClick={video ? closeCamera : openCamera} className="flex-center py-1 px-3 rounded bg-gray-100 dark:bg-gray-900">
|
<li role={"button"} onClick={video ? closeCamera : openCamera} className="flex-center py-1 px-3 rounded bg-gray-100 dark:bg-gray-900">
|
||||||
{video ? <IconCamera className="fill-gray-700 dark:fill-gray-300" /> : <IconCameraOff className="fill-gray-700 dark:fill-gray-300" />}
|
{video ? <IconCamera className="fill-gray-700 dark:fill-gray-200" /> : <IconCameraOff className="fill-gray-700 dark:fill-gray-200" />}
|
||||||
</li>
|
</li>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip tip={"Share Screen"} placement="top">
|
<Tooltip tip={"Share Screen"} placement="top">
|
||||||
<li role={"button"} onClick={shareScreen ? stopShareScreen : startShareScreen} className={clsx("flex-center py-1 px-3 rounded ", shareScreen ? "bg-green-700" : "bg-gray-100 dark:bg-gray-900")}>
|
<li role={"button"} onClick={shareScreen ? stopShareScreen : startShareScreen} className={clsx("flex-center py-1 px-3 rounded ", shareScreen ? "bg-green-700" : "bg-gray-100 dark:bg-gray-900")}>
|
||||||
<IconScreen className={clsx("dark:fill-gray-300 w-6 h-6", shareScreen ? "fill-gray-300" : "fill-gray-700")} />
|
<IconScreen className={clsx("dark:fill-gray-200 w-6 h-6", shareScreen ? "fill-gray-200" : "fill-gray-700")} />
|
||||||
</li>
|
</li>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<StyledButton onClick={leave} className='bg-red-600 hover:!bg-red-700 text-center'>
|
<StyledButton onClick={leave} className='bg-red-600 hover:!bg-red-700 text-center'>
|
||||||
|
|||||||
Reference in New Issue
Block a user