@@ -33,6 +33,7 @@ interface LiveStreamInputProps {
3333 * A component for inputting and confirming live stream URLs.
3434 * Displays an input field and confirm button positioned next to the chat mode button.
3535 *
36+ * @param globalState Global state for accessing BabylonJS scene and runtime.
3637 * @returns JSX.Element The live stream input UI component.
3738 */
3839export default function LiveStreamInput ( { globalState } : LiveStreamInputProps ) {
@@ -44,93 +45,107 @@ export default function LiveStreamInput({ globalState }: LiveStreamInputProps) {
4445 const clientRef = useRef < DanmakuWebSocketClient | null > ( null )
4546
4647 /**
47- * 解析直播流 URL,提取服务器地址和房间标识
48- * 支持两种格式:
48+ * Parse live stream URL and extract server address and room identifier.
49+ *
50+ * Supports two formats:
4951 * 1. file:// URL: file:///path/loader.html?url=http%3A%2F%2Flocalhost%3A12450%2Froom%2FKEY%3FroomKeyType%3D2
50- * 2. 直接 HTTP URL: http://localhost:12450/room/KEY?roomKeyType=2
52+ * 2. Direct HTTP URL: http://localhost:12450/room/KEY?roomKeyType=2
53+ *
54+ * @param url The live stream URL to parse.
55+ * @returns An object containing serverUrl and roomKey, or null if parsing fails.
5156 */
5257 const parseLiveStreamUrl = ( url : string ) : { serverUrl : string ; roomKey : RoomKey } | null => {
5358 try {
5459 let actualUrl = url
5560
56- // 处理 file:// 协议的 URL
61+ // Handle file:// protocol URLs
5762 if ( url . startsWith ( 'file://' ) ) {
5863 const urlObj = new URL ( url )
59- // 获取 url 查询参数
64+ // Get url query parameter
6065 const urlParam = urlObj . searchParams . get ( 'url' )
6166 if ( ! urlParam ) {
62- console . error ( 'file:// URL 中缺少 url 参数 ' )
67+ console . error ( 'Missing url parameter in file:// URL' )
6368 return null
6469 }
65- // URL 解码
70+ // URL decode
6671 actualUrl = decodeURIComponent ( urlParam )
6772 }
6873
69- // 解析实际的 URL
74+ // Parse the actual URL
7075 const urlObj = new URL ( actualUrl )
7176 const serverUrl = `${ urlObj . protocol } //${ urlObj . host } `
7277
73- // 从路径中提取 roomKey(例如: /room/F3RY9LX2M9KW9)
78+ // Extract roomKey from path (e.g., /room/F3RY9LX2M9KW9)
7479 const pathParts = urlObj . pathname . split ( '/' ) . filter ( Boolean )
7580 if ( pathParts . length < 2 || pathParts [ 0 ] !== 'room' ) {
76- console . error ( 'URL 路径格式不正确,期望格式 : /room/KEY' )
81+ console . error ( 'Invalid URL path format, expected format : /room/KEY' )
7782 return null
7883 }
7984
80- const roomValue = pathParts [ 1 ] // 获取 roomKey
85+ const roomValue = pathParts [ 1 ] // Get roomKey
8186
82- // 从查询参数中获取 roomKeyType,默认为 2(身份码)
87+ // Get roomKeyType from query parameter, default to 2 (identity code)
8388 const roomKeyTypeParam = urlObj . searchParams . get ( 'roomKeyType' )
84- let roomKeyType : 1 | 2 = 2 // 默认为身份码
89+ let roomKeyType : 1 | 2 = 2 // Default to identity code
8590
8691 if ( roomKeyTypeParam ) {
8792 const type = parseInt ( roomKeyTypeParam , 10 )
8893 if ( type === 1 || type === 2 ) {
8994 roomKeyType = type as 1 | 2
9095 }
9196 } else {
92- // 如果没有指定 roomKeyType,尝试根据 roomValue 判断
97+ // If roomKeyType is not specified, try to determine from roomValue
9398 const roomId = parseInt ( roomValue , 10 )
9499 if ( ! isNaN ( roomId ) ) {
95- roomKeyType = 1 // 数字则认为是房间ID
100+ roomKeyType = 1 // Numeric value is considered room ID
96101 }
97102 }
98103
99- // 构建 roomKey
104+ // Build roomKey
100105 const roomKey : RoomKey = {
101106 type : roomKeyType ,
102107 value : roomKeyType === 1 ? parseInt ( roomValue , 10 ) : roomValue ,
103108 }
104109
105110 return { serverUrl, roomKey }
106111 } catch ( error ) {
107- console . error ( '解析 URL 失败 :' , error )
112+ console . error ( 'Failed to parse URL :' , error )
108113 return null
109114 }
110115 }
111116
117+ /**
118+ * Send user text message to the state machine.
119+ *
120+ * Processes messages from queues (super chat, gift, text) and sends them to the state machine
121+ * when it's in IDLE state. In auto mode, sends a "no message" notification if no messages
122+ * have been received for more than 10 seconds.
123+ *
124+ * @param lastTextMessageTime The timestamp of the last text message, or null if none.
125+ * @returns void
126+ */
112127 const sendUserTextMessage = ( lastTextMessageTime : number | null ) => {
113128 if ( globalState ?. stateMachine ?. stateValue === States . IDLE ) {
114129 if ( clientRef . current ) {
115130 if ( clientRef . current . superChatQueue . size > 0 ) {
116131 const message = clientRef . current . superChatQueue . dequeue ( )
117132 if ( message ) {
118- const formattedMessages = `[${ message . authorName } ]发送了醒目留言 : ${ message . content } `
133+ const formattedMessages = `[${ message . authorName } ] sent super chat : ${ message . content } `
119134 globalState ?. stateMachine ?. putConditionedMessage (
120135 new ConditionedMessage ( Conditions . USER_TEXT_INPUT , { message : formattedMessages } ) ,
121136 )
122- console . log ( '[发送后台消息 ]: ' , formattedMessages )
137+ console . log ( '[Send background message ]: ' , formattedMessages )
123138 return
124139 }
125140 }
126141 if ( clientRef . current . giftQueue . size > 0 ) {
127142 const message = clientRef . current . giftQueue . dequeue ( )
128143 if ( message ) {
129- const formattedMessages = `[${ message . authorName } ]发送了礼物 : ${ message . giftName } , ${ message . num } 件 `
144+ const formattedMessages = `[${ message . authorName } ] sent gift : ${ message . giftName } , ${ message . num } pieces `
130145 globalState ?. stateMachine ?. putConditionedMessage (
131146 new ConditionedMessage ( Conditions . USER_TEXT_INPUT , { message : formattedMessages } ) ,
132147 )
133- console . log ( '[发送后台消息 ]: ' , formattedMessages )
148+ console . log ( '[Send background message ]: ' , formattedMessages )
134149 return
135150 }
136151 }
@@ -142,111 +157,120 @@ export default function LiveStreamInput({ globalState }: LiveStreamInputProps) {
142157 globalState ?. stateMachine ?. putConditionedMessage (
143158 new ConditionedMessage ( Conditions . USER_TEXT_INPUT , { message : formattedMessages } ) ,
144159 )
145- console . log ( '[发送后台消息 ]: ' , formattedMessages )
160+ console . log ( '[Send background message ]: ' , formattedMessages )
146161 return
147162 } else {
148163 if ( autoMode && lastTextMessageTime !== null && Date . now ( ) - lastTextMessageTime > 10000 ) {
149164 globalState ?. stateMachine ?. putConditionedMessage (
150- new ConditionedMessage ( Conditions . USER_TEXT_INPUT , { message : '无消息/ No message' } ) ,
165+ new ConditionedMessage ( Conditions . USER_TEXT_INPUT , { message : 'No message' } ) ,
151166 )
152- console . log ( '[发送后台消息 ]: 无消息/ No message' )
167+ console . log ( '[Send background message ]: No message' )
153168 }
154169 return
155170 }
156171 }
157172 }
158173 }
159174 /**
160- * 消息处理器
175+ * Message handler for danmaku WebSocket client.
161176 */
162177 const messageHandler : DanmakuMessageHandler = {
163178 onDebugMsg : ( msg : string ) => {
164- console . log ( '[弹幕客户端 ]' , msg )
179+ console . log ( '[Danmaku client ]' , msg )
165180 setConnectionStatus ( msg )
166181 } ,
167182 onHeartBeat : ( lastTextMessageTime : number | null ) => {
168183 sendUserTextMessage ( lastTextMessageTime )
169184 } ,
170185 onAddText : ( message : TextMessage ) => {
171- console . log ( '[弹幕 ]' , message . authorName , ':' , message . content )
186+ console . log ( '[Danmaku ]' , message . authorName , ':' , message . content )
172187 if ( clientRef . current ) {
173188 clientRef . current . messageQueue . enqueue ( message )
174189 sendUserTextMessage ( Date . now ( ) )
175190 }
176191 } ,
177192 onAddGift : ( message : GiftMessage ) => {
178- console . log ( '[礼物 ]' , message . authorName , ':' , message . giftName , 'x' , message . num )
193+ console . log ( '[Gift ]' , message . authorName , ':' , message . giftName , 'x' , message . num )
179194 if ( clientRef . current ) {
180195 clientRef . current . giftQueue . enqueue ( message )
181196 sendUserTextMessage ( Date . now ( ) )
182197 }
183198 } ,
184199 onAddMember : ( message : MemberMessage ) => {
185- console . log ( '[成员 ]' , message . authorName , '加入了直播间 ' )
186- // 这里可以添加处理成员消息的逻辑
200+ console . log ( '[Member ]' , message . authorName , 'joined the live stream ' )
201+ // Logic for handling member messages can be added here
187202 } ,
188203 onAddSuperChat : ( message : SuperChatMessage ) => {
189- console . log ( '[超级聊天 ]' , message . authorName , ':' , message . content , '¥' , message . price )
204+ console . log ( '[Super chat ]' , message . authorName , ':' , message . content , '¥' , message . price )
190205 if ( clientRef . current ) {
191206 clientRef . current . superChatQueue . enqueue ( message )
192207 sendUserTextMessage ( Date . now ( ) )
193208 }
194209 } ,
195210 onFatalError : ( error : string ) => {
196- console . error ( '[致命错误 ]' , error )
197- setConnectionStatus ( `连接失败 : ${ error } ` )
211+ console . error ( '[Fatal error ]' , error )
212+ setConnectionStatus ( `Connection failed : ${ error } ` )
198213 setIsConnected ( false )
199214 } ,
200215 }
201216
202217 /**
203218 * Handle live stream URL confirmation.
219+ *
220+ * Parses the URL, disconnects any existing connection, and creates a new WebSocket client
221+ * to connect to the live stream server.
222+ *
223+ * @returns void
204224 */
205225 const handleConfirm = ( ) => {
206226 if ( ! liveStreamUrl . trim ( ) ) {
207227 return
208228 }
209229
210- // 如果已经连接,先断开旧连接
230+ // If already connected, disconnect the old connection first
211231 if ( clientRef . current ) {
212232 clientRef . current . stop ( )
213233 clientRef . current = null
214234 }
215235
216- // 解析 URL
217- // URL 格式示例:
236+ // Parse URL
237+ // URL format examples:
218238 // - file:///path/loader.html?url=http%3A%2F%2Flocalhost%3A12450%2Froom%2FKEY%3FroomKeyType%3D2
219239 // - http://localhost:12450/room/KEY?roomKeyType=2
220240 const parsed = parseLiveStreamUrl ( liveStreamUrl . trim ( ) )
221241 if ( ! parsed ) {
222- alert ( '无效的直播流 URL,请检查格式\n支持格式 :\n1. file:// URL (带 url 参数 )\n2. http://server:port/room/KEY?roomKeyType=2' )
242+ alert ( 'Invalid live stream URL, please check the format\nSupported formats :\n1. file:// URL (with url parameter )\n2. http://server:port/room/KEY?roomKeyType=2' )
223243 return
224244 }
225245
226- // 创建 WebSocket 客户端
246+ // Create WebSocket client
227247 const client = new DanmakuWebSocketClient ( parsed . serverUrl , parsed . roomKey , false )
228248 client . setMsgHandler ( messageHandler )
229249 client . start ( )
230250
231251 clientRef . current = client
232252 setIsConnected ( true )
233- setConnectionStatus ( '连接中 ...' )
253+ setConnectionStatus ( 'Connecting ...' )
234254 }
235255
236256 /**
237- * 断开连接
257+ * Handle disconnection from the live stream.
258+ *
259+ * Stops the WebSocket client and updates the connection status.
260+ *
261+ * @returns void
238262 */
239263 const handleDisconnect = ( ) => {
240264 if ( clientRef . current ) {
241265 clientRef . current . stop ( )
242266 clientRef . current = null
243267 setIsConnected ( false )
244- setConnectionStatus ( '已断开 ' )
268+ setConnectionStatus ( 'Disconnected ' )
245269 }
246270 }
247271
248272 /**
249- * 组件卸载时清理连接
273+ * Clean up connection when component unmounts.
250274 */
251275 useEffect ( ( ) => {
252276 return ( ) => {
@@ -259,6 +283,11 @@ export default function LiveStreamInput({ globalState }: LiveStreamInputProps) {
259283
260284 /**
261285 * Handle Enter key press in input.
286+ *
287+ * Triggers the confirm action when Enter is pressed.
288+ *
289+ * @param e The keyboard event from the input element.
290+ * @returns void
262291 */
263292 const handleKeyDown = ( e : React . KeyboardEvent < HTMLInputElement > ) => {
264293 if ( e . key === 'Enter' ) {
0 commit comments