diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index d454429a..fb017b3f 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -87,7 +87,7 @@ jobs: sed -i "s#^REACT_APP_TURN_SERVER_URL=.*#REACT_APP_TURN_SERVER_URL=\"turn:${{ secrets.STAGING_TURN_URL }}\"#" .env.production sed -i "s#^REACT_APP_TURN_SERVER_USERNAME=.*#REACT_APP_TURN_SERVER_USERNAME=\"${{ secrets.STAGING_TURN_USERNAME }}\"#" .env.production sed -i "s#^REACT_APP_TURN_SERVER_CREDENTIAL=.*#REACT_APP_TURN_SERVER_CREDENTIAL=\"${{ secrets.STAGING_TURN_PASSWORD }}\"#" .env.production - npm install --legacy-peer-deps + npm install --force npm run build cd .. if [ -d "webapp/src/main/webapp/static" ] && [ "$(ls -A webapp/src/main/webapp/static)" ]; then @@ -118,7 +118,7 @@ jobs: sed -i "s#^REACT_APP_TURN_SERVER_URL=.*#REACT_APP_TURN_SERVER_URL=\"turn:${{ secrets.STAGING_TURN_URL }}\"#" .env.production sed -i "s#^REACT_APP_TURN_SERVER_USERNAME=.*#REACT_APP_TURN_SERVER_USERNAME=\"${{ secrets.STAGING_TURN_USERNAME }}\"#" .env.production sed -i "s#^REACT_APP_TURN_SERVER_CREDENTIAL=.*#REACT_APP_TURN_SERVER_CREDENTIAL=\"${{ secrets.STAGING_TURN_PASSWORD }}\"#" .env.production - npm install --legacy-peer-deps + npm install --force npm run build cd .. if [ -d "webapp/src/main/webapp/static" ] && [ "$(ls -A webapp/src/main/webapp/static)" ]; then @@ -187,7 +187,7 @@ jobs: echo "ls:" ls -al cd react - npm install --include=dev --legacy-peer-deps + npm install --include=dev --force npm test - name: Upload coverage to Codecov @@ -374,7 +374,7 @@ jobs: sed -i "s#^REACT_APP_TURN_SERVER_URL=.*#REACT_APP_TURN_SERVER_URL=\"turn:${{ secrets.STAGING_TURN_URL }}\"#" .env.production sed -i "s#^REACT_APP_TURN_SERVER_USERNAME=.*#REACT_APP_TURN_SERVER_USERNAME=\"${{ secrets.STAGING_TURN_USERNAME }}\"#" .env.production sed -i "s#^REACT_APP_TURN_SERVER_CREDENTIAL=.*#REACT_APP_TURN_SERVER_CREDENTIAL=\"${{ secrets.STAGING_TURN_PASSWORD }}\"#" .env.production - npm install --legacy-peer-deps + npm install --force npm run build cd .. if [ -d "webapp/src/main/webapp/static" ] && [ "$(ls -A webapp/src/main/webapp/static)" ]; then diff --git a/react/.env.development.webinar b/react/.env.development.webinar index 3db73e0a..3a56e68d 100644 --- a/react/.env.development.webinar +++ b/react/.env.development.webinar @@ -28,7 +28,7 @@ REACT_APP_FOOTER_MESSAGE_BUTTON_VISIBILITY=true REACT_APP_FOOTER_PARTICIPANT_LIST_BUTTON_VISIBILITY=true REACT_APP_FOOTER_END_CALL_BUTTON_VISIBILITY=true REACT_APP_FOOTER_CLOCK_VISIBILITY=true -REACT_APP_FOOTER_PUBLISHER_REQUEST_BUTTON_VISIBILITY=false +REACT_APP_FOOTER_PUBLISHER_REQUEST_BUTTON_VISIBILITY=true # Option menu buttons configurations REACT_APP_OPTION_MENU_GENERAL_SETTINGS_BUTTON_VISIBILITY=false diff --git a/react/.env.production.webinar b/react/.env.production.webinar index 743dd7a0..75b0d3c5 100644 --- a/react/.env.production.webinar +++ b/react/.env.production.webinar @@ -28,7 +28,7 @@ REACT_APP_FOOTER_MESSAGE_BUTTON_VISIBILITY=true REACT_APP_FOOTER_PARTICIPANT_LIST_BUTTON_VISIBILITY=true REACT_APP_FOOTER_END_CALL_BUTTON_VISIBILITY=true REACT_APP_FOOTER_CLOCK_VISIBILITY=true -REACT_APP_FOOTER_PUBLISHER_REQUEST_BUTTON_VISIBILITY=false +REACT_APP_FOOTER_PUBLISHER_REQUEST_BUTTON_VISIBILITY=true # Option menu buttons configurations REACT_APP_OPTION_MENU_GENERAL_SETTINGS_BUTTON_VISIBILITY=false diff --git a/react/src/Components/Footer/Components/MoreOptionsButton.js b/react/src/Components/Footer/Components/MoreOptionsButton.js index 6612a6d6..72de6057 100644 --- a/react/src/Components/Footer/Components/MoreOptionsButton.js +++ b/react/src/Components/Footer/Components/MoreOptionsButton.js @@ -214,7 +214,7 @@ function MoreOptionsButton(props) { - {t("Publisher Request List")} + {t("Publisher Request List")} : null} @@ -225,7 +225,7 @@ function MoreOptionsButton(props) { - {t("Request becoming publisher")} + {t("Request becoming publisher")} : null} diff --git a/react/src/Components/Footer/Footer.js b/react/src/Components/Footer/Footer.js index 09604210..d99a5ec7 100644 --- a/react/src/Components/Footer/Footer.js +++ b/react/src/Components/Footer/Footer.js @@ -222,7 +222,9 @@ function Footer(props) { props?.handlePublisherRequest()} + handlePublisherRequest={()=> { + props?.handlePublisherRequest() + }} /> : null} @@ -270,6 +272,7 @@ function Footer(props) { ({ - color: "#000", + color: theme.palette?.participantListIcon?.primary, fontWeight: 500, fontSize: 14, })); @@ -20,6 +20,8 @@ const PinBtn = styled(Button)(({ theme }) => ({ })); function PublisherRequestTab(props) { + const theme = useTheme(); + const getPublisherRequestItem = (streamId) => { return ( {props?.approveBecomeSpeakerRequest(streamId);}} @@ -43,6 +46,7 @@ function PublisherRequestTab(props) { {props?.rejectBecomeSpeakerRequest(streamId);}} @@ -58,10 +62,10 @@ function PublisherRequestTab(props) {
- + {props?.requestSpeakerList.length} diff --git a/react/src/__tests__/pages/AntMedia.test.js b/react/src/__tests__/pages/AntMedia.test.js index 66480ef4..c6012406 100644 --- a/react/src/__tests__/pages/AntMedia.test.js +++ b/react/src/__tests__/pages/AntMedia.test.js @@ -2275,6 +2275,37 @@ describe('AntMedia Component', () => { consoleSpy.mockRestore(); }); + it('streamIdInUseCounter is not incremented due to reconnection is true', async () => { + const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); + + const {container} = render( + + + + + ); + + + await waitFor(() => { + expect(webRTCAdaptorConstructor).not.toBe(undefined); + }); + + await act(async () => { + webRTCAdaptorConstructor.callback("reconnection_attempt_for_player"); + }); + + await act(async () => { + webRTCAdaptorConstructor.callbackError("streamIdInUse", "Stream ID is in use"); + webRTCAdaptorConstructor.callbackError("streamIdInUse", "Stream ID is in use"); + webRTCAdaptorConstructor.callbackError("streamIdInUse", "Stream ID is in use"); + webRTCAdaptorConstructor.callbackError("streamIdInUse", "Stream ID is in use"); + }); + + expect(consoleSpy).not.toHaveBeenCalledWith("This stream id is already in use. You may be logged in on another device."); + + consoleSpy.mockRestore(); + }); + it('updates allParticipants and participantUpdated when subtrackList is provided', async () => { const { container } = render( diff --git a/react/src/pages/AntMedia.js b/react/src/pages/AntMedia.js index 6491cdf5..9233c844 100644 --- a/react/src/pages/AntMedia.js +++ b/react/src/pages/AntMedia.js @@ -1797,13 +1797,16 @@ function AntMedia(props) { } else if (error.indexOf("no_stream_exist") !== -1) { setIsNoSreamExist(true); } else if (error.indexOf("streamIdInUse") !== -1) { - streamIdInUseCounter++; - if (streamIdInUseCounter > 3) { - console.log("This stream id is already in use. You may be logged in on another device."); - setLeaveRoomWithError("Streaming is already active with your username. Please check that you're not using it in another browser tab."); - setLeftTheRoom(true); - setIsJoining(false); - setIsReconnectionInProgress(false); + // if the stream id is in use when reconnection, don't display the error + if (!reconnecting) { + streamIdInUseCounter++; + if (streamIdInUseCounter > 3) { + console.log("This stream id is already in use. You may be logged in on another device."); + setLeaveRoomWithError("Streaming is already active with your username. Please check that you're not using it in another browser tab."); + setLeftTheRoom(true); + setIsJoining(false); + setIsReconnectionInProgress(false); + } } } else if (error.indexOf("data_channel_error") !== -1) { errorMessage = "There was a error during data channel communication"; @@ -3398,6 +3401,7 @@ function AntMedia(props) { pinVideo={(streamId) => pinVideo(streamId)} isAdmin={isAdmin} publishStreamId={publishStreamId} + role={role} /> ) : ( <> @@ -3451,7 +3455,7 @@ function AntMedia(props) { handleParticipantListOpen={(open) => handleParticipantListOpen(open)} requestSpeakerList={requestSpeakerList} handlePublisherRequestListOpen={(open) => setPublisherRequestListDrawerOpen(open)} - handlePublisherRequest={()=>{}} + handlePublisherRequest={()=>handlePublisherRequest()} setLeftTheRoom={(left) => setLeftTheRoom(left)} addFakeParticipant={() => addFakeParticipant()} removeFakeParticipant={() => removeFakeParticipant()} @@ -3520,10 +3524,15 @@ function AntMedia(props) { setPublisherRequestListDrawerOpen={(open) => setPublisherRequestListDrawerOpen(open)} /> approveBecomeSpeakerRequest(streamId)} rejectBecomeSpeakerRequest={(streamId) => rejectBecomeSpeakerRequest(streamId)} requestSpeakerList={requestSpeakerList} publishStreamId={publishStreamId} + handleMessageDrawerOpen={(open) => handleMessageDrawerOpen(open)} + handleParticipantListOpen={(open) => handleParticipantListOpen(open)} + handleEffectsOpen={(open) => handleEffectsOpen(open)} + setPublisherRequestListDrawerOpen={(open) => setPublisherRequestListDrawerOpen(open)} /> )} diff --git a/react/src/pages/MeetingRoom.js b/react/src/pages/MeetingRoom.js index cb443b87..01952693 100644 --- a/react/src/pages/MeetingRoom.js +++ b/react/src/pages/MeetingRoom.js @@ -252,7 +252,7 @@ const MeetingRoom = React.memo((props) => { requestSpeakerList={props?.requestSpeakerList} publisherRequestListDrawerOpen={props?.publisherRequestListDrawerOpen} handlePublisherRequestListOpen={props?.handlePublisherRequestListOpen} - handlePublisherRequest={props?.handlePublisherRequest} + handlePublisherRequest={()=> {props?.handlePublisherRequest()}} setLeftTheRoom={props?.setLeftTheRoom} addFakeParticipant={props?.addFakeParticipant} removeFakeParticipant={props?.removeFakeParticipant} diff --git a/react/src/pages/WaitingRoom.js b/react/src/pages/WaitingRoom.js index 5ef267c7..3f837147 100755 --- a/react/src/pages/WaitingRoom.js +++ b/react/src/pages/WaitingRoom.js @@ -70,9 +70,9 @@ function WaitingRoom(props) { }; React.useEffect(() => { - if (conference.role === WebinarRoles.TempListener) { + if (props?.role === WebinarRoles.TempListener) { const tempLocalVideo = document.getElementById("localVideo"); - conference?.localVideoCreate(tempLocalVideo); + props?.localVideoCreate(tempLocalVideo); console.log("TempListener local video created"); } }, []); @@ -319,7 +319,7 @@ function WaitingRoom(props) { @@ -414,7 +414,7 @@ function WaitingRoom(props) { "You can choose whether to open your camera and microphone before you get into room" )} - {conference.role === WebinarRoles.TempListener ? ( + {props?.role === WebinarRoles.TempListener ? (
{ @@ -438,7 +438,7 @@ function WaitingRoom(props) { : null} - {conference.role !== WebinarRoles.TempListener ? ( + {props?.role !== WebinarRoles.TempListener ? ( diff --git a/test/test_webinar.py b/test/test_webinar.py index 16662619..8e6d8321 100644 --- a/test/test_webinar.py +++ b/test/test_webinar.py @@ -141,7 +141,7 @@ def join_room_as_player(self, participant, room, skip_speed_test=False): app = "/"+self.test_app_name if self.url.endswith("localhost:3000"): app = "" - handle = self.chrome.open_in_new_tab(self.url+app+"/"+room+"?playOnly=true&role=listener&streamName=" + participant + ("&enterDirectly=true" if skip_speed_test else "")) + handle = self.chrome.open_in_new_tab(self.url+app+"/"+room+"?playOnly=true&role=listener&streamName=" + participant + "&streamId=" + participant + ("&enterDirectly=true" if skip_speed_test else "")) wait = self.chrome.get_wait() @@ -184,6 +184,14 @@ def join_room_as_player(self, participant, room, skip_speed_test=False): return handle + def accept_raising_hand_request(self, participant): + accept_button = self.chrome.get_element_with_retry(By.ID,"approve-become-speaker-"+participant) + self.chrome.click_element(accept_button) + + def reject_raising_hand_request(self, participant): + reject_button = self.chrome.get_element_with_retry(By.ID,"reject-become-speaker-"+participant) + self.chrome.click_element(reject_button) + def add_presenter_to_listener_room(self, presenter): add_button = self.chrome.get_element(By.ID,"add-presenter-"+presenter) self.chrome.click_element(add_button) @@ -752,6 +760,90 @@ def _test_admin_video_card_controls(self): wait.until(lambda x: self.chrome.get_element_in_element(presenterA_video_card, By.XPATH, ".//button[@type='button' and @aria-label='turn-off-camera']") is not None) + self.chrome.close_all() + + def get_request_publish_button(self): + rp_button = None + if(self.chrome.is_element_exist(By.ID, "request-publish-button")): + rp_button = self.chrome.get_element(By.ID, "request-publish-button") + else: + more_button = self.chrome.get_element_with_retry(By.ID, "more-button") + self.chrome.click_element(more_button) + rp_button = self.chrome.get_element_with_retry(By.ID, "more-options-request-publish-button") + return rp_button + + def test_raising_hand(self): + # create a room and join as admin and 2 players + room = "room"+str(random.randint(100, 999)) + handle_admin = self.join_room_as_admin("admin", room, True) + handle_player_A = self.join_room_as_player("playerA", room, False) + handle_player_B = self.join_room_as_player("playerB", room, False) + + wait = self.chrome.get_wait() + + # switch to playerA and raise hand + self.chrome.switch_to_tab(handle_player_A) + + raise_hand_button = self.get_request_publish_button() + self.chrome.click_element(raise_hand_button) + + # switch to admin and check if playerA is in the request list + self.chrome.switch_to_tab(handle_admin) + + self.open_close_publisher_request_list_drawer() + + time.sleep(15) + + self.accept_raising_hand_request("playerA") + + # switch to playerA and join the room + self.chrome.switch_to_tab(handle_player_A) + + join_button = self.chrome.get_element_with_retry(By.ID,"room_join_button") + self.chrome.click_element(join_button) + + time.sleep(5) + speedTestCircularProgress = self.chrome.get_element_with_retry(By.ID,"speed-test-modal-circle-progress-bar", retries=20) + assert(speedTestCircularProgress.is_displayed()) + + time.sleep(5) + + timeoutCounter = 0 + + isSpeedTestFinished = False + isSpeedTestFailed = False + + while not isSpeedTestFailed and not isSpeedTestFinished and timeoutCounter < 100: + time.sleep(1) + timeoutCounter += 1 + script = "return window.conference.speedTestObject;" + result_json = self.chrome.execute_script(script) + if result_json is not None: + isSpeedTestFinished = result_json["isfinished"] + isSpeedTestFailed = result_json["isfailed"] + + speedTestModalJoinButton = self.chrome.get_element_with_retry(By.ID,"speed-test-modal-join-button") + + self.chrome.print_ss_as_base64() + + self.chrome.click_element(speedTestModalJoinButton) + + time.sleep(5) + + meeting_gallery = self.chrome.get_element_with_retry(By.ID,"meeting-gallery") + + assert(meeting_gallery.is_displayed()) + + wait.until(lambda x: len(self.get_participants()) == 2) + + # switch to admin + self.chrome.switch_to_tab(handle_admin) + + wait.until(lambda x: len(self.get_participants()) == 2) + + # switch to playerB + self.chrome.switch_to_tab(handle_player_B) + self.chrome.close_all() if __name__ == '__main__':