Skip to content

Commit 2af2a87

Browse files
committed
feat: refactor VideoRoomPage by removing unused components and optimizing state management
1 parent 6cfafbe commit 2af2a87

2 files changed

Lines changed: 24 additions & 178 deletions

File tree

web/src/pages/VideoRoomPage.css

Lines changed: 0 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,6 @@
6161
}
6262

6363
.video-room-shell {
64-
display: grid;
65-
grid-template-columns: minmax(0, 1fr) 360px;
66-
gap: 18px;
6764
max-width: 1720px;
6865
margin: 0 auto;
6966
}
@@ -100,113 +97,6 @@
10097
color: var(--ant-color-primary, #1677ff);
10198
}
10299

103-
.video-room-side {
104-
display: flex;
105-
flex-direction: column;
106-
gap: 14px;
107-
min-width: 0;
108-
}
109-
110-
.video-room-panel.ant-card {
111-
border-color: rgba(255, 255, 255, 0.08);
112-
background: #14191d;
113-
}
114-
115-
.video-room-panel .ant-card-head {
116-
min-height: 48px;
117-
border-bottom-color: rgba(255, 255, 255, 0.08);
118-
}
119-
120-
.video-room-panel .ant-card-head-title {
121-
color: rgba(255, 255, 255, 0.9);
122-
font-weight: 600;
123-
}
124-
125-
.video-room-panel .ant-card-body {
126-
padding: 16px;
127-
}
128-
129-
.video-room-dot {
130-
display: block;
131-
width: 8px;
132-
height: 8px;
133-
border-radius: 999px;
134-
background: var(--ant-color-error, #ff4d4f);
135-
box-shadow: 0 0 0 4px rgba(255, 77, 79, 0.14);
136-
}
137-
138-
.video-room-dot.is-connected {
139-
background: var(--ant-color-success, #52c41a);
140-
box-shadow: 0 0 0 4px rgba(82, 196, 26, 0.14);
141-
}
142-
143-
.video-room-status-list {
144-
display: flex;
145-
flex-direction: column;
146-
gap: 10px;
147-
margin-bottom: 16px;
148-
}
149-
150-
.video-room-status-item {
151-
display: flex;
152-
align-items: center;
153-
gap: 12px;
154-
min-width: 0;
155-
padding: 12px;
156-
border: 1px solid rgba(255, 255, 255, 0.06);
157-
border-radius: 8px;
158-
background: rgba(255, 255, 255, 0.03);
159-
}
160-
161-
.video-room-status-item > .anticon {
162-
flex: none;
163-
color: var(--ant-color-primary, #1677ff);
164-
font-size: 18px;
165-
}
166-
167-
.video-room-status-item div {
168-
min-width: 0;
169-
}
170-
171-
.video-room-status-item span {
172-
display: block;
173-
margin-bottom: 2px;
174-
color: rgba(255, 255, 255, 0.45);
175-
font-size: 12px;
176-
}
177-
178-
.video-room-status-item strong {
179-
display: block;
180-
overflow: hidden;
181-
color: rgba(255, 255, 255, 0.88);
182-
font-size: 14px;
183-
font-weight: 600;
184-
text-overflow: ellipsis;
185-
white-space: nowrap;
186-
}
187-
188-
.video-room-primary-action.ant-btn {
189-
height: 40px;
190-
}
191-
192-
.video-room-panel__text {
193-
margin: 0 0 14px;
194-
color: rgba(255, 255, 255, 0.55);
195-
font-size: 14px;
196-
line-height: 1.6;
197-
}
198-
199-
@media (max-width: 1180px) {
200-
.video-room-shell {
201-
grid-template-columns: 1fr;
202-
}
203-
204-
.video-room-side {
205-
display: grid;
206-
grid-template-columns: repeat(2, minmax(0, 1fr));
207-
}
208-
}
209-
210100
@media (max-width: 760px) {
211101
.video-room-page {
212102
padding: 14px;
@@ -231,8 +121,4 @@
231121
height: 56vh;
232122
min-height: 280px;
233123
}
234-
235-
.video-room-side {
236-
display: flex;
237-
}
238124
}

web/src/pages/VideoRoomPage.tsx

Lines changed: 24 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
import { memo, useEffect, useRef, useState } from 'react';
22
import { useParams } from 'react-router';
3-
import { Button, Card, Empty, Space, Spin, Tag, Tooltip, Typography, message } from 'antd';
3+
import { Button, Empty, Space, Spin, Tag, Tooltip, Typography, message } from 'antd';
44
import {
55
CheckCircleFilled,
6-
ClockCircleOutlined,
7-
CopyOutlined,
86
DisconnectOutlined,
9-
FileTextOutlined,
107
LinkOutlined,
11-
PlayCircleOutlined,
12-
ReloadOutlined,
138
} from '@ant-design/icons';
149
import Artplayer from 'artplayer';
1510
import { videoRoomsApi, type VideoRoomInfo, type VideoRoomState } from '../api/videoRooms';
@@ -20,6 +15,16 @@ const { Text, Title } = Typography;
2015

2116
const SYNC_THRESHOLD = 1.2;
2217

18+
function getSyncedTime(state: VideoRoomState) {
19+
const baseTime = Math.max(0, Number(state.current_time) || 0);
20+
if (state.paused || !state.updated_at) return baseTime;
21+
22+
const updatedAt = Date.parse(state.updated_at);
23+
if (Number.isNaN(updatedAt)) return baseTime;
24+
25+
return baseTime + Math.max(0, (Date.now() - updatedAt) / 1000);
26+
}
27+
2328
function formatTime(seconds: number) {
2429
const safeSeconds = Math.max(0, Math.floor(seconds || 0));
2530
const hours = Math.floor(safeSeconds / 3600);
@@ -43,6 +48,7 @@ const VideoRoomPage = memo(function VideoRoomPage() {
4348
const wsRef = useRef<WebSocket | null>(null);
4449
const applyingRemoteRef = useRef(false);
4550
const sendTimerRef = useRef<number | null>(null);
51+
const liveStateRef = useRef<VideoRoomState | null>(null);
4652
const [room, setRoom] = useState<VideoRoomInfo | null>(null);
4753
const [liveState, setLiveState] = useState<VideoRoomState | null>(null);
4854
const [loading, setLoading] = useState(true);
@@ -58,6 +64,7 @@ const VideoRoomPage = memo(function VideoRoomPage() {
5864
if (!mounted) return;
5965
setRoom(data);
6066
setLiveState(data.state);
67+
liveStateRef.current = data.state;
6168
})
6269
.catch((e: any) => {
6370
if (!mounted) return;
@@ -100,10 +107,11 @@ const VideoRoomPage = memo(function VideoRoomPage() {
100107

101108
const applyState = (state: VideoRoomState) => {
102109
const art = artInstance.current;
110+
liveStateRef.current = state;
103111
setLiveState(state);
104112
if (!art) return;
105113
const video = art.video;
106-
const targetTime = Math.max(0, Number(state.current_time) || 0);
114+
const targetTime = getSyncedTime(state);
107115
applyingRemoteRef.current = true;
108116
if (Math.abs((video.currentTime || 0) - targetTime) > SYNC_THRESHOLD) {
109117
video.currentTime = targetTime;
@@ -119,6 +127,10 @@ const VideoRoomPage = memo(function VideoRoomPage() {
119127
}, 250);
120128
};
121129

130+
const applyLatestState = () => {
131+
applyState(liveStateRef.current || room.state);
132+
};
133+
122134
const art = new Artplayer({
123135
container: artRef.current,
124136
url: videoRoomsApi.streamUrl(token),
@@ -131,7 +143,9 @@ const VideoRoomPage = memo(function VideoRoomPage() {
131143
});
132144
artInstance.current = art;
133145

134-
art.on('ready', () => applyState(room.state));
146+
art.on('ready', applyLatestState);
147+
art.video.addEventListener('loadedmetadata', applyLatestState);
148+
art.video.addEventListener('canplay', applyLatestState);
135149
art.on('play', sendStateSoon);
136150
art.on('pause', sendStateSoon);
137151
art.on('seek', sendStateSoon);
@@ -157,6 +171,8 @@ const VideoRoomPage = memo(function VideoRoomPage() {
157171
window.clearTimeout(sendTimerRef.current);
158172
sendTimerRef.current = null;
159173
}
174+
art.video.removeEventListener('loadedmetadata', applyLatestState);
175+
art.video.removeEventListener('canplay', applyLatestState);
160176
ws.close();
161177
art.destroy();
162178
wsRef.current = null;
@@ -169,18 +185,6 @@ const VideoRoomPage = memo(function VideoRoomPage() {
169185
message.success(t('Copied to clipboard'));
170186
};
171187

172-
const handleResync = () => {
173-
if (!liveState) return;
174-
const art = artInstance.current;
175-
if (!art) return;
176-
art.video.currentTime = Math.max(0, Number(liveState.current_time) || 0);
177-
if (liveState.paused) {
178-
void art.video.pause();
179-
} else {
180-
void art.video.play().catch(() => undefined);
181-
}
182-
};
183-
184188
if (loading) {
185189
return (
186190
<div className="video-room-page video-room-page--center">
@@ -238,50 +242,6 @@ const VideoRoomPage = memo(function VideoRoomPage() {
238242
<span>{t('Playback state is shared in this room')}</span>
239243
</div>
240244
</section>
241-
242-
<aside className="video-room-side">
243-
<Card
244-
className="video-room-panel"
245-
title={t('Room status')}
246-
extra={<span className={connected ? 'video-room-dot is-connected' : 'video-room-dot'} />}
247-
>
248-
<div className="video-room-status-list">
249-
<div className="video-room-status-item">
250-
<PlayCircleOutlined />
251-
<div>
252-
<span>{t('Playback')}</span>
253-
<strong>{state.paused ? t('Paused') : t('Playing')}</strong>
254-
</div>
255-
</div>
256-
<div className="video-room-status-item">
257-
<ClockCircleOutlined />
258-
<div>
259-
<span>{t('Current position')}</span>
260-
<strong>{formatTime(state.current_time)}</strong>
261-
</div>
262-
</div>
263-
<div className="video-room-status-item">
264-
<FileTextOutlined />
265-
<div>
266-
<span>{t('File')}</span>
267-
<strong title={room.path}>{fileName}</strong>
268-
</div>
269-
</div>
270-
</div>
271-
<Button block className="video-room-primary-action" icon={<ReloadOutlined />} onClick={handleResync}>
272-
{t('Resync playback')}
273-
</Button>
274-
</Card>
275-
276-
<Card className="video-room-panel" title={t('Room link')}>
277-
<p className="video-room-panel__text">
278-
{t('Share this room link with friends')}
279-
</p>
280-
<Button block icon={<CopyOutlined />} onClick={handleCopy}>
281-
{t('Copy Link')}
282-
</Button>
283-
</Card>
284-
</aside>
285245
</main>
286246
</div>
287247
);

0 commit comments

Comments
 (0)