'use client'
import { Suspense, useEffect, useRef } from 'react'
import { useSearchParams, useRouter } from 'next/navigation'
import { IconButton, Typography } from '@mui/material'
import {
Mic as MicIcon,
MicOff as MicOffIcon,
Videocam as VideocamIcon,
VideocamOff as VideocamOffIcon,
CallEnd as CallEndIcon,
} from '@mui/icons-material'
import { useWebRTC } from '@/hooks'
import './page.css'
// useSearchParams() を使うため Suspense 境界の内側に置く (静的 prerender 要件)。
function CallPageInner() {
const searchParams = useSearchParams()
const router = useRouter()
const callId = searchParams.get('callId') ?? ''
const accessKey = searchParams.get('accessKey') ?? ''
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL ?? ''
const wsUrl = callId && accessKey
? baseUrl.replace(/^https?:\/\//, 'wss://') + `/api/mobile/${accessKey}/call/${callId}`
: ''
const { callState, localStream, remoteStream, isMuted, isCameraOff, toggleMute, toggleCamera, hangup } =
useWebRTC({ callId, wsUrl, onHangup: () => {} })
const remoteVideoRef = useRef<HTMLVideoElement>(null)
const localVideoRef = useRef<HTMLVideoElement>(null)
useEffect(() => {
if (remoteVideoRef.current && remoteStream) {
remoteVideoRef.current.srcObject = remoteStream
}
}, [remoteStream])
useEffect(() => {
if (localVideoRef.current && localStream) {
localVideoRef.current.srcObject = localStream
}
}, [localStream])
// 通話終了・失敗時は2秒後に前のページへ戻る
useEffect(() => {
if (callState === 'ended' || callState === 'failed') {
const timer = setTimeout(() => router.back(), 2000)
return () => clearTimeout(timer)
}
}, [callState, router])
const statusText: Record<string, string> = {
connecting: '接続中…',
failed: '接続に失敗しました',
ended: '通話終了',
}
const isEnded = callState === 'ended' || callState === 'failed'
return (
<div className="callContainer">
{/* リモート映像(全画面) */}
<video
ref={remoteVideoRef}
className="remoteVideo"
autoPlay
playsInline
/>
{/* ローカル映像(右下小窓) */}
<video
ref={localVideoRef}
className="localVideo"
autoPlay
playsInline
muted
/>
{/* 状態テキスト */}
{callState !== 'connected' && statusText[callState] && (
<div className="statusOverlay">
<Typography sx={{ fontSize: '4vw', fontWeight: 'bold' }}>
{statusText[callState]}
</Typography>
</div>
)}
{/* コントロールバー */}
<div className="controlBar">
<IconButton
onClick={toggleMute}
disabled={isEnded}
sx={{ color: isMuted ? '#f44336' : '#fff', background: 'rgba(255,255,255,0.15)', '&:hover': { background: 'rgba(255,255,255,0.25)' } }}
size="large"
>
{isMuted ? <MicOffIcon fontSize="large" /> : <MicIcon fontSize="large" />}
</IconButton>
<IconButton
onClick={toggleCamera}
disabled={isEnded}
sx={{ color: isCameraOff ? '#f44336' : '#fff', background: 'rgba(255,255,255,0.15)', '&:hover': { background: 'rgba(255,255,255,0.25)' } }}
size="large"
>
{isCameraOff ? <VideocamOffIcon fontSize="large" /> : <VideocamIcon fontSize="large" />}
</IconButton>
<IconButton
onClick={hangup}
disabled={isEnded}
sx={{ color: '#fff', background: '#f44336', '&:hover': { background: '#d32f2f' }, width: 64, height: 64 }}
size="large"
>
<CallEndIcon fontSize="large" />
</IconButton>
</div>
</div>
)
}
export default function CallPage() {
return (
<Suspense fallback={<div className="callContainer" />}>
<CallPageInner />
</Suspense>
)
}