Skip to content

Commit

Permalink
Merge pull request #181 from yuminn-k/refactor/p2p-to-sfu-migration
Browse files Browse the repository at this point in the history
🎨 ビデオチャット: P2P方式からSFU方式(MediaSoup)へのマイグレーション
  • Loading branch information
yuminn-k authored Nov 1, 2024
2 parents 1fccb21 + e3da9d3 commit 0ec22f2
Show file tree
Hide file tree
Showing 6 changed files with 872 additions and 268 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@
"@tanstack/react-query": "^5.17.10",
"@types/event-source-polyfill": "^1.0.5",
"@types/js-cookie": "^3.0.6",
"@types/webrtc": "^0.0.44",
"axios": "^1.6.5",
"date-fns": "^3.6.0",
"event-source-polyfill": "^1.0.31",
"ion-sdk-js": "^1.8.2",
"js-cookie": "^3.0.5",
"mediasoup-client": "^3.7.17",
"moment": "^2.30.1",
"next": "^14.2.7",
"react": "^18",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React, {memo} from 'react';

interface ConnectionStatusProps {
state: 'connecting' | 'connected' | 'disconnected';
}

const ConnectionStatus: React.FC<ConnectionStatusProps> = memo(({state}) => {
const getStatusData = (): {color: string; text: string} => {
const statusMap = {
connected: {color: 'bg-green-500', text: '接続中'},
connecting: {color: 'bg-yellow-500', text: '接続試行中...'},
disconnected: {color: 'bg-red-500', text: '未接続'},
};
return statusMap[state];
};

const {color, text} = getStatusData();

return (
<div
className="flex items-center mb-4"
role="status"
aria-live="polite"
aria-atomic="true"
>
<div
className={`w-3 h-3 rounded-full ${color} mr-2`}
aria-hidden="true"
/>
<span className="text-sm text-gray-600">{text}</span>
</div>
);
});

ConnectionStatus.displayName = 'ConnectionStatus';
export default ConnectionStatus;
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import React, {memo} from 'react';

interface MediaState {
video: boolean;
audio: boolean;
}

interface ControlButtonsProps {
isTeacher: boolean;
isSharingScreen: boolean;
mediaState: MediaState;
isLoading?: boolean;
disabled?: boolean;
onEndClass: () => void;
onToggleScreen: () => void;
onToggleAudio: () => void;
onToggleVideo: () => void;
}

const ControlButtons: React.FC<ControlButtonsProps> = memo(
({
isTeacher,
isSharingScreen,
mediaState,
isLoading = false,
disabled = false,
onEndClass,
onToggleScreen,
onToggleAudio,
onToggleVideo,
}) => {
const buttonClass = (active: boolean, color: string) =>
`${active ? `bg-${color}-500 hover:bg-${color}-600` : 'bg-gray-500'}
text-white font-bold py-2 px-4 rounded-md transition-colors
disabled:opacity-50 disabled:cursor-not-allowed`;

return (
<div className="flex flex-col items-center gap-4">
<button
onClick={onEndClass}
disabled={disabled || isLoading}
className={buttonClass(true, 'red')}
aria-label="授業を終了する"
>
{isLoading ? '処理中...' : '授業終了'}
</button>

{isTeacher && (
<button
onClick={onToggleScreen}
disabled={disabled}
className={buttonClass(
isSharingScreen,
isSharingScreen ? 'yellow' : 'green'
)}
aria-label={
isSharingScreen ? '画面共有を停止する' : '画面共有を開始する'
}
>
{isSharingScreen ? '画面共有停止' : '画面共有開始'}
</button>
)}

<div className="flex justify-center gap-4">
<button
onClick={onToggleAudio}
disabled={disabled}
className={buttonClass(mediaState.audio, 'blue')}
aria-label={
mediaState.audio ? 'マイクをオフにする' : 'マイクをオンにする'
}
>
{mediaState.audio ? 'マイクオン' : 'マイクオフ'}
</button>
<button
onClick={onToggleVideo}
disabled={disabled}
className={buttonClass(mediaState.video, 'blue')}
aria-label={
mediaState.video ? 'カメラをオフにする' : 'カメラをオンにする'
}
>
{mediaState.video ? 'カメラオン' : 'カメラオフ'}
</button>
</div>
</div>
);
}
);

ControlButtons.displayName = 'ControlButtons';
export default ControlButtons;
Loading

0 comments on commit 0ec22f2

Please sign in to comment.