RoomPage.tsx 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. import {faSquare, faThLarge, faUserFriends} from '@fortawesome/free-solid-svg-icons';
  2. import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
  3. import {Room, RoomEvent, setLogLevel, VideoPresets} from 'livekit-client';
  4. import {DisplayContext, DisplayOptions, LiveKitRoom} from '@livekit/react-components';
  5. import {useState} from 'react';
  6. import 'react-aspect-ratio/aspect-ratio.css';
  7. import {useNavigate, useLocation} from 'react-router-dom';
  8. // 新增代码
  9. let currentRoom: Room;
  10. export const RoomPage = () => {
  11. const [numParticipants, setNumParticipants] = useState(0);
  12. const [displayOptions, setDisplayOptions] = useState<DisplayOptions>({
  13. stageLayout: 'grid',
  14. showStats: false,
  15. });
  16. const navigate = useNavigate();
  17. const query = new URLSearchParams(useLocation().search);
  18. const url = query.get('url');
  19. const token = query.get('token');
  20. const recorder = query.get('recorder');
  21. if (!url || !token) {
  22. return <div>url and token are required</div>;
  23. }
  24. const onLeave = () => {
  25. navigate('/');
  26. };
  27. const updateParticipantSize = (room: Room) => {
  28. setNumParticipants(room.participants.size + 1);
  29. };
  30. const onParticipantDisconnected = (room: Room) => {
  31. updateParticipantSize(room);
  32. /* Special rule for recorder */
  33. if (recorder && parseInt(recorder, 10) === 1 && room.participants.size === 0) {
  34. console.log('END_RECORDING');
  35. }
  36. };
  37. const updateOptions = (options: DisplayOptions) => {
  38. setDisplayOptions({
  39. ...displayOptions,
  40. ...options,
  41. });
  42. };
  43. // 新增代码开始
  44. const encoder = new TextEncoder();
  45. const decoder = new TextDecoder();
  46. const enterText = () => {
  47. const textField = document.getElementById('entry') as HTMLInputElement;
  48. const chat = document.getElementById('chat') as HTMLDivElement;
  49. if (textField.value.trim()) {
  50. textField.focus();
  51. const msg = encoder.encode(textField.value);
  52. currentRoom.localParticipant.publishData(msg, 0);
  53. chat.innerHTML += `<div style="color:yellow">${currentRoom.localParticipant.identity} (me): ${textField.value}</div>`;
  54. chat.scrollTop = chat.scrollHeight;
  55. textField.value = '';
  56. }
  57. };
  58. const handleData = (msg: Uint8Array, participant?: any) => {
  59. const chat = document.getElementById('chat') as HTMLDivElement;
  60. const str = decoder.decode(msg);
  61. let from = 'server';
  62. if (participant) {
  63. from = participant.identity;
  64. }
  65. chat.innerHTML += `<div>${from}: ${str}</div>`;
  66. chat.scrollTop = chat.scrollHeight;
  67. };
  68. const closeChat = () => {
  69. const chatArea: any = document.getElementById('chat-area');
  70. chatArea.style.display = 'none';
  71. }
  72. const handleKeydown = (e: any) => {
  73. if (e.target.value.trim() && e.key === 'Enter') {
  74. enterText()
  75. }
  76. }
  77. // 新增代码结束
  78. return (
  79. <DisplayContext.Provider value={displayOptions}>
  80. <div className="video-chat">
  81. <div className="roomContainer">
  82. <div className="topBar">
  83. <h2>Zoom Video</h2>
  84. <div className="right">
  85. <div>
  86. <input
  87. id="showStats"
  88. type="checkbox"
  89. onChange={(e) => updateOptions({showStats: e.target.checked})}
  90. />
  91. <label htmlFor="showStats">Show Stats</label>
  92. </div>
  93. <div>
  94. <button
  95. className="iconButton"
  96. disabled={displayOptions.stageLayout === 'grid'}
  97. onClick={() => {
  98. updateOptions({stageLayout: 'grid'});
  99. }}
  100. >
  101. <FontAwesomeIcon height={32} icon={faThLarge}/>
  102. </button>
  103. <button
  104. className="iconButton"
  105. disabled={displayOptions.stageLayout === 'speaker'}
  106. onClick={() => {
  107. updateOptions({stageLayout: 'speaker'});
  108. }}
  109. >
  110. <FontAwesomeIcon height={32} icon={faSquare}/>
  111. </button>
  112. </div>
  113. <div className="participantCount">
  114. <FontAwesomeIcon icon={faUserFriends}/>
  115. <span>{numParticipants}</span>
  116. </div>
  117. </div>
  118. </div>
  119. <LiveKitRoom
  120. url={url}
  121. token={token}
  122. onConnected={(room) => {
  123. setLogLevel('debug');
  124. onConnected(room, query);
  125. room.on(RoomEvent.ParticipantConnected, () => updateParticipantSize(room));
  126. room.on(RoomEvent.ParticipantDisconnected, () => onParticipantDisconnected(room));
  127. room.on(RoomEvent.DataReceived, handleData);
  128. updateParticipantSize(room);
  129. }}
  130. roomOptions={{
  131. adaptiveStream: isSet(query, 'adaptiveStream'),
  132. dynacast: isSet(query, 'dynacast'),
  133. publishDefaults: {
  134. simulcast: isSet(query, 'simulcast'),
  135. },
  136. videoCaptureDefaults: {
  137. resolution: VideoPresets.h720.resolution,
  138. },
  139. }}
  140. onLeave={onLeave}
  141. />
  142. </div>
  143. {
  144. currentRoom &&
  145. <div className="chat-area" id="chat-area">
  146. <div className="title">
  147. <h2>Chat</h2>
  148. <div className="close" onClick={closeChat}>x</div>
  149. </div>
  150. <div className="chat" id="chat"></div>
  151. <div className="send">
  152. <input type="text" className="input" id="entry" placeholder="Enter your message"
  153. onKeyDown={event => handleKeydown(event)}/>
  154. <button className="chat-btn" onClick={enterText}>
  155. Send
  156. </button>
  157. </div>
  158. </div>
  159. }
  160. </div>
  161. </DisplayContext.Provider>
  162. );
  163. };
  164. async function onConnected(room: Room, query: URLSearchParams) {
  165. // make it easier to debug
  166. // (window as any).currentRoom = room;
  167. // 新增代码
  168. currentRoom = room;
  169. if (isSet(query, 'audioEnabled')) {
  170. const audioDeviceId = query.get('audioDeviceId');
  171. if (audioDeviceId && room.options.audioCaptureDefaults) {
  172. room.options.audioCaptureDefaults.deviceId = audioDeviceId;
  173. }
  174. await room.localParticipant.setMicrophoneEnabled(true);
  175. }
  176. if (isSet(query, 'videoEnabled')) {
  177. const videoDeviceId = query.get('videoDeviceId');
  178. if (videoDeviceId && room.options.videoCaptureDefaults) {
  179. room.options.videoCaptureDefaults.deviceId = videoDeviceId;
  180. }
  181. await room.localParticipant.setCameraEnabled(true);
  182. }
  183. }
  184. function isSet(query: URLSearchParams, key: string): boolean {
  185. return query.get(key) === '1' || query.get(key) === 'true';
  186. }