openvidu-components: Added streaming activity

- Added streaming activity panel 
- Added streaming structurals directives
- Added streaming attributes directives
- Added e2e test
- Updated test app

openvidu-components: Updated e2e configuration


openvidu-components: Skipped pro e2e tests


openvidu-components: Allowed streaming for moderators only


openvidu-components: Request MODERATOR connection in testapp


openvidu-components: Fixed streaming signals


openvidu-components: Fixed bug with streaming status


openvidu-components: Fixed streaming button on status failed


openvidu-components: Refactored activities checks


openvidu-components: Forced streaming status to enum value


openvidu-components: Added non available error in streaming activity  

Streaming activity will show paid feature error if the service is not available
openvidu-components: Created and exported streaming error type


openvidu-components: Updated e2e tests


openvidu-components: Updated testapp 


openvidu-components: Enabled streaming input wehn module is disabled


openvidu-components: Updated e2e tests


openvidu-components: Updated docs


openvidu-components: Moved streaming directive to its component 

Moved streaming directive to streaming component instead of activities component 
openvidu-components: Updated testapp 


openvidu-components: Made streaming service public


ci: Send branch name in event dispatch


openvidu-components: Updated test app
pull/771/head
Carlos Santos 2022-12-23 16:17:04 +01:00
parent 799e875dd7
commit ab0cf2a343
63 changed files with 2160 additions and 515 deletions

View File

@ -26,13 +26,14 @@ jobs:
GITHUB_TOKEN: ${{ secrets.OPENVIDU_DISPATCH_EVENT_GA }}
COMMIT_MESSAGE: ${{ github.event.head_commit.message || 'Manually' }}
COMMIT_URL: ${{ github.event.commits[0].url || 'Manually' }}
BRANCH_NAME: ${{ github.ref_name }}
run: |
curl \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
https://api.github.com/repos/OpenVidu/openvidu-call/dispatches \
-d '{"event_type":"openvidu-components-angular","client_payload":{"commit-message":"'"$COMMIT_MESSAGE"'","commit-ref":"'"$COMMIT_URL"'"}}'
-d '{"event_type":"openvidu-components-angular","client_payload":{"commit-message":"'"$COMMIT_MESSAGE"'","commit-ref":"'"$COMMIT_URL"'", "branch-name":"'"$BRANCH_NAME"'"}}'
- name: Build openvidu-browser
run: |
cd openvidu-browser
@ -112,6 +113,7 @@ jobs:
run: npm run webcomponent:e2e-ci --prefix openvidu-components-angular
webcomponent_e2e_pro:
if: false #Skip PRO test because infra is unstable
needs: test_setup
name: Webcomponent E2E PRO tests
runs-on: ubuntu-latest

View File

@ -5,7 +5,6 @@ import { AngularConfig } from './selenium.conf';
import { OpenViduComponentsPO } from './utils.po.test';
const url = AngularConfig.appUrl;
const TIMEOUT = 30000;
describe('Testing TOOLBAR STRUCTURAL DIRECTIVES', () => {
let browser: WebDriver;
@ -25,6 +24,7 @@ describe('Testing TOOLBAR STRUCTURAL DIRECTIVES', () => {
});
afterEach(async () => {
// console.log('data:image/png;base64,' + await browser.takeScreenshot());
await browser.quit();
});
@ -780,6 +780,7 @@ describe('Testing ATTRIBUTE DIRECTIVES', () => {
});
afterEach(async () => {
// console.log('data:image/png;base64,' + await browser.takeScreenshot());
await browser.quit();
});
@ -872,10 +873,31 @@ describe('Testing ATTRIBUTE DIRECTIVES', () => {
await browser.sleep(500);
// Checking if fullscreen button is not present
await utils.waitForElement('.mat-menu-content');
expect(await utils.isPresent('fullscreen-btn')).to.be.false;
// Checking if fullscreen button is not present
expect(await utils.isPresent('#fullscreen-btn')).to.be.false;
});
it('should HIDE the STREAMING button', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#streamingButton-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
// Open more options menu
await utils.clickOn('#more-options-btn');
await browser.sleep(500);
await utils.waitForElement('.mat-menu-content');
// Checking if fullscreen button is not present
expect(await utils.isPresent('#streaming-btn')).to.be.false;
});
it('should HIDE the LEAVE button', async () => {
@ -1005,6 +1027,80 @@ describe('Testing ATTRIBUTE DIRECTIVES', () => {
expect(await utils.isPresent('ov-recording-activity')).to.be.false;
});
it('should HIDE the STREAMING activity', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovActivitiesPanel-checkbox');
await utils.clickOn('#streamingActivity-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
await utils.clickOn('#activities-panel-btn');
await browser.sleep(500);
await utils.waitForElement('#custom-activities-panel');
await utils.waitForElement('ov-recording-activity');
expect(await utils.isPresent('ov-streaming-activity')).to.be.false;
});
it('should SHOW STARTING STREAMING status', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovActivitiesPanel-checkbox');
await utils.clickOn('#streamingInfo-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
// Open more options menu
await utils.clickOn('#activities-panel-btn');
await browser.sleep(500);
await utils.waitForElement('#custom-activities-panel');
console.log('before');
const status = await utils.waitForElement('#streaming-status');
expect(await status.getAttribute('innerText')).equals('STARTED');
});
it('should SHOW STREAMING ERROR', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovActivitiesPanel-checkbox');
await utils.clickOn('#streamingError-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
await utils.clickOn('#activities-panel-btn');
await browser.sleep(500);
await utils.waitForElement('#custom-activities-panel');
const status = await utils.waitForElement('#streaming-status');
expect(await status.getAttribute('innerText')).equals('FAILED');
await utils.clickOn('#streaming-activity');
await browser.sleep(500);
const error = await utils.waitForElement('#streaming-error');
expect(await error.getAttribute('innerText')).equals('TEST_ERROR');
});
});
describe('Testing EVENTS', () => {

View File

@ -12,6 +12,7 @@ interface BrowserConfig {
const chromeArguments = ['--window-size=1024,768', '--use-fake-ui-for-media-stream', '--use-fake-device-for-media-stream'];
const chromeArgumentsCI = [
'--window-size=1024,768',
'--headless',
'--no-sandbox',
'--disable-gpu',
@ -25,9 +26,8 @@ const chromeArgumentsCI = [
'--use-fake-device-for-media-stream'
];
const chromeArgumentsWithoutMediaDevices = ['--window-size=1024,768', '--deny-permission-prompts'];
const chromeArgumentsWithoutMediaDevicesCI = [
'--window-size=1024,768',
'--headless',
'--no-sandbox',
'--disable-gpu',

View File

@ -10,6 +10,7 @@ var SCREENSHARE_BUTTON;
var FULLSCREEN_BUTTON;
var ACTIVITIES_PANEL_BUTTON;
var RECORDING_BUTTON;
var STREAMING_BUTTON;
var CHAT_PANEL_BUTTON;
var DISPLAY_LOGO;
var DISPLAY_SESSION_NAME;
@ -20,7 +21,9 @@ var LEAVE_BUTTON;
var PARTICIPANT_MUTE_BUTTON;
var PARTICIPANTS_PANEL_BUTTON;
var ACTIVITIES_RECORDING_ACTIVITY;
var ACTIVITIES_STREAMING_ACTIVITY;
var RECORDING_ERROR;
var STREAMING_ERROR;
var TOOLBAR_SETTINGS_BUTTON;
var CAPTIONS_BUTTON;
@ -29,16 +32,14 @@ var SESSION_NAME;
var PARTICIPANT_NAME;
var OPENVIDU_SERVER_URL;
var OPENVIDU_SECRET;
$(document).ready(() => {
var url = new URL(window.location.href);
OPENVIDU_SERVER_URL = url.searchParams.get('OV_URL');
OPENVIDU_SECRET = url.searchParams.get('OV_SECRET');
OPENVIDU_SERVER_URL = url.searchParams.get('OV_URL');
OPENVIDU_SECRET = url.searchParams.get('OV_SECRET');
SINGLE_TOKEN = url.searchParams.get('singleToken') === null ? false : url.searchParams.get('singleToken') === 'true';
@ -53,8 +54,16 @@ $(document).ready(() => {
VIDEO_MUTED = url.searchParams.get('videoMuted') === null ? false : url.searchParams.get('videoMuted') === 'true';
AUDIO_MUTED = url.searchParams.get('audioMuted') === null ? false : url.searchParams.get('audioMuted') === 'true';
SCREENSHARE_BUTTON = url.searchParams.get('screenshareBtn') === null ? true : url.searchParams.get('screenshareBtn') === 'true';
RECORDING_BUTTON = url.searchParams.get('recordingBtn') === null ? true : url.searchParams.get('recordingBtn') === 'true';
RECORDING_BUTTON =
url.searchParams.get('toolbarRecordingButton') === null ? true : url.searchParams.get('toolbarRecordingButton') === 'true';
FULLSCREEN_BUTTON = url.searchParams.get('fullscreenBtn') === null ? true : url.searchParams.get('fullscreenBtn') === 'true';
STREAMING_BUTTON =
url.searchParams.get('toolbarStreamingButton') === null ? true : url.searchParams.get('toolbarStreamingButton') === 'true';
if (url.searchParams.get('streamingError') !== null) {
STREAMING_ERROR = url.searchParams.get('streamingError');
}
TOOLBAR_SETTINGS_BUTTON =
url.searchParams.get('toolbarSettingsBtn') === null ? true : url.searchParams.get('toolbarSettingsBtn') === 'true';
CAPTIONS_BUTTON = url.searchParams.get('toolbarCaptionsBtn') === null ? true : url.searchParams.get('toolbarCaptionsBtn') === 'true';
@ -65,6 +74,10 @@ $(document).ready(() => {
CHAT_PANEL_BUTTON = url.searchParams.get('chatPanelBtn') === null ? true : url.searchParams.get('chatPanelBtn') === 'true';
PARTICIPANTS_PANEL_BUTTON =
url.searchParams.get('participantsPanelBtn') === null ? true : url.searchParams.get('participantsPanelBtn') === 'true';
ACTIVITIES_STREAMING_ACTIVITY =
url.searchParams.get('activitiesPanelStreamingActivity') === null
? true
: url.searchParams.get('activitiesPanelStreamingActivity') === 'true';
ACTIVITIES_RECORDING_ACTIVITY =
url.searchParams.get('activitiesPanelRecordingActivity') === null
? true
@ -114,6 +127,11 @@ $(document).ready(() => {
// await stopRecording(RECORDING_ID);
// });
webComponent.addEventListener('onToolbarStopStreamingClicked', async (event) => {
appendElement('onToolbarStopStreamingClicked');
webComponent.streamingActivityStreamingInfo = { status: 'stopped', id: '01' };
});
webComponent.addEventListener('onActivitiesPanelStartRecordingClicked', async (event) => {
appendElement('onActivitiesPanelStartRecordingClicked');
// RECORDING_ID = await startRecording(SESSION_NAME);
@ -129,6 +147,16 @@ $(document).ready(() => {
appendElement('onActivitiesPanelDeleteRecordingClicked')
);
webComponent.addEventListener('onActivitiesPanelStartStreamingClicked', async (event) => {
appendElement('onActivitiesPanelStartStreamingClicked');
webComponent.streamingActivityStreamingInfo = { status: 'started', id: '01' };
});
webComponent.addEventListener('onActivitiesPanelStopStreamingClicked', async (event) => {
appendElement('onActivitiesPanelStopStreamingClicked');
webComponent.streamingActivityStreamingInfo = { status: 'stopped', id: '01' };
});
webComponent.addEventListener('onSessionCreated', (event) => {
var session = event.detail;
appendElement('onSessionCreated');
@ -192,6 +220,7 @@ async function joinSession(sessionName, participantName) {
webComponent.toolbarCaptionsButton = CAPTIONS_BUTTON;
webComponent.toolbarLeaveButton = LEAVE_BUTTON;
webComponent.toolbarRecordingButton = RECORDING_BUTTON;
webComponent.toolbarStreamingButton = STREAMING_BUTTON;
webComponent.toolbarActivitiesPanelButton = ACTIVITIES_PANEL_BUTTON;
webComponent.toolbarChatPanelButton = CHAT_PANEL_BUTTON;
webComponent.toolbarParticipantsPanelButton = PARTICIPANTS_PANEL_BUTTON;
@ -204,8 +233,11 @@ async function joinSession(sessionName, participantName) {
webComponent.recordingActivityRecordingsList = [{ status: 'ready' }];
webComponent.activitiesPanelRecordingActivity = ACTIVITIES_RECORDING_ACTIVITY;
webComponent.activitiesPanelStreamingActivity = ACTIVITIES_STREAMING_ACTIVITY;
webComponent.recordingActivityRecordingError = RECORDING_ERROR;
webComponent.streamingActivityStreamingError = {message: STREAMING_ERROR, rtmpAvailable: true};
webComponent.participantName = participantName;
webComponent.tokens = tokens;
}
@ -222,7 +254,6 @@ async function joinSession(sessionName, participantName) {
* 3) Configure OpenVidu Web Component in your client side with the token
*/
function getToken(sessionName) {
return createSession(sessionName).then((sessionId) => createToken(sessionId));
}

View File

@ -6,12 +6,6 @@ import { OpenViduComponentsPO } from './utils.po.test';
const url = `${WebComponentConfig.appUrl}?OV_URL=${OPENVIDU_SERVER_URL}&OV_SECRET=${OPENVIDU_SECRET}`;
/**
*
* Testing PRO features with OpenVidu PRO
* TODO: Change the openvidu URL when openvidu-pro-dev exists
*/
describe('Testing API Directives', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;

View File

@ -110,96 +110,6 @@ describe('Testing API Directives', () => {
expect(await element.getText()).equal('Unirme ahora');
});
/**
* TODO:
* This test is only available with OpenVidu PRO
*/
// it('should change the captions LANG ', async () => {
// await browser.get(`${url}&prejoin=false&captionsLang=es-ES`);
// await utils.checkSessionIsPresent();
// // Checking if toolbar is present
// await utils.checkToolbarIsPresent();
// // Open more options menu
// await utils.clickOn('#more-options-btn');
// await browser.sleep(500);
// // Checking if button panel is present
// await utils.waitForElement('.mat-menu-content');
// expect(await utils.isPresent('.mat-menu-content')).to.be.true;
// // Checking if captions button is present
// await utils.waitForElement('#captions-btn');
// expect(await utils.isPresent('#captions-btn')).to.be.true;
// await utils.clickOn('#captions-btn');
// await utils.waitForElement('.captions-container');
// await utils.waitForElement('#caption-settings-btn');
// await utils.clickOn('#caption-settings-btn');
// await browser.sleep(500);
// await utils.waitForElement('.settings-container');
// expect(await utils.isPresent('.settings-container')).to.be.true;
// await utils.waitForElement('ov-captions-settings');
// expect(await utils.isPresent('.captions-container')).to.be.true;
// const element = await utils.waitForElement('.lang-button');
// expect(await element.getText()).equal('Españolexpand_more');
// });
/**
* TODO:
* This test is only available with OpenVidu PRO
*/
// it('should override the CAPTIONS LANG OPTIONS', async () => {
// await browser.get(`${url}&prejoin=false&captionsLangOptions=true`);
// await utils.checkSessionIsPresent();
// // Checking if toolbar is present
// await utils.checkToolbarIsPresent();
// // Open more options menu
// await utils.clickOn('#more-options-btn');
// await browser.sleep(500);
// // Checking if button panel is present
// await utils.waitForElement('.mat-menu-content');
// expect(await utils.isPresent('.mat-menu-content')).to.be.true;
// // Checking if captions button is present
// await utils.waitForElement('#captions-btn');
// expect(await utils.isPresent('#captions-btn')).to.be.true;
// await utils.clickOn('#captions-btn');
// await utils.waitForElement('.captions-container');
// await utils.waitForElement('#caption-settings-btn');
// await utils.clickOn('#caption-settings-btn');
// await browser.sleep(500);
// await utils.waitForElement('.settings-container');
// expect(await utils.isPresent('.settings-container')).to.be.true;
// await utils.waitForElement('ov-captions-settings');
// expect(await utils.isPresent('.captions-container')).to.be.true;
// const element = await utils.waitForElement('.lang-button');
// expect(await element.getText()).equal('Espexpand_more');
// await element.click();
// expect(await utils.getNumberOfElements('.mat-menu-item')).equals(2);
// });
it('should show the PREJOIN page', async () => {
await browser.get(`${url}&prejoin=true`);
@ -376,6 +286,48 @@ describe('Testing API Directives', () => {
expect(await utils.isPresent('#captions-opt')).to.be.false;
});
it('should HIDE the TOOLBAR RECORDING button', async () => {
await browser.get(`${url}&prejoin=false&toolbarRecordingButton=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Open more options menu
await utils.clickOn('#more-options-btn');
await browser.sleep(500);
// Checking if button panel is present
await utils.waitForElement('.mat-menu-content');
expect(await utils.isPresent('.mat-menu-content')).to.be.true;
// Checking if recording button is not present
expect(await utils.isPresent('#recording-btn')).to.be.false;
});
it('should HIDE the TOOLBAR STREAMING button', async () => {
await browser.get(`${url}&prejoin=false&toolbarStreamingButton=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Open more options menu
await utils.clickOn('#more-options-btn');
await browser.sleep(500);
// Checking if button panel is present
await utils.waitForElement('.mat-menu-content');
expect(await utils.isPresent('.mat-menu-content')).to.be.true;
// Checking if streaming button is not present
expect(await utils.isPresent('#streaming-btn')).to.be.false;
});
it('should HIDE the TOOLBAR SETTINGS button', async () => {
await browser.get(`${url}&prejoin=false&toolbarSettingsBtn=false`);
@ -617,6 +569,63 @@ describe('Testing API Directives', () => {
expect(await element.getAttribute('innerText')).equal('"TEST_ERROR"');
expect(await utils.isPresent('.recording-error')).to.be.true;
});
it('should SHOW a STREAMING ERROR in activities panel', async () => {
let element;
const fixedUrl = `${url}&prejoin=false&streamingError=TEST_ERROR`;
await browser.get(fixedUrl);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
element = await utils.waitForElement('#activities-panel-btn');
await element.click();
// Checking if participatns panel is displayed
await utils.waitForElement('#default-activities-panel');
expect(await utils.isPresent('#default-activities-panel')).to.be.true;
// Checking if streaming activity exists
await utils.waitForElement('#activities-container');
await utils.waitForElement('.activities-body-container');
await utils.waitForElement('ov-streaming-activity');
expect(await utils.isPresent('ov-streaming-activity')).to.be.true;
const status = await utils.waitForElement('#streaming-status');
expect(await status.getAttribute('innerText')).equals('FAILED');
// Open streaming
await browser.sleep(1000);
await utils.clickOn('ov-streaming-activity');
element = await utils.waitForElement('#streaming-error');
expect(await element.getAttribute('innerText')).equal('TEST_ERROR');
});
it('should HIDE the STREAMING ACTIVITY in activities panel', async () => {
await browser.get(`${url}&prejoin=false&activitiesPanelStreamingActivity=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
await utils.waitForElement('#activities-panel-btn');
await utils.clickOn('#activities-panel-btn')
// Checking if participatns panel is displayed
await utils.waitForElement('#default-activities-panel');
expect(await utils.isPresent('#default-activities-panel')).to.be.true;
// await browser.sleep(1000);
// Checking if recording activity exists
await utils.waitForElement('.activities-body-container');
expect(await utils.isPresent('ov-streaming-activity')).to.be.false;
});
});
describe('Testing videoconference EVENTS', () => {
@ -647,10 +656,8 @@ describe('Testing videoconference EVENTS', () => {
expect(await utils.isPresent('#prejoin-container')).to.be.true;
// Clicking to join button
const joinButton = await utils.waitForElement('#join-button');
expect(await utils.isPresent('#join-button')).to.be.true;
expect(await joinButton.isEnabled()).to.be.true;
await joinButton.click();
await utils.waitForElement('#join-button');
await utils.clickOn('#join-button');
// Checking if onJoinButtonClicked has been received
await utils.waitForElement('#onJoinButtonClicked');
@ -799,32 +806,75 @@ describe('Testing videoconference EVENTS', () => {
});
it('should receive the onToolbarStartRecordingClicked event', async () => {
let element;
await browser.get(`${url}&prejoin=false`);
await utils.checkSessionIsPresent();
await utils.checkToolbarIsPresent();
// Open more options menu
element = await utils.waitForElement('#more-options-btn');
await utils.waitForElement('#more-options-btn');
expect(await utils.isPresent('#more-options-btn')).to.be.true;
await element.click();
await utils.clickOn('#more-options-btn');
await browser.sleep(500);
// Clicking to recording button
await utils.waitForElement('.mat-menu-content');
const recordingButton = await utils.waitForElement('#recording-btn');
await utils.waitForElement('#recording-btn');
expect(await utils.isPresent('#recording-btn')).to.be.true;
await recordingButton.click();
await utils.clickOn('#recording-btn');
// Checking if onToolbarStartRecordingClicked has been received
await utils.waitForElement('#onToolbarStartRecordingClicked');
expect(await utils.isPresent('#onToolbarStartRecordingClicked')).to.be.true;
});
it('should receive the onToolbarStopStreamingClicked event', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkSessionIsPresent();
await utils.checkToolbarIsPresent();
// Open more options menu
await utils.waitForElement('#more-options-btn');
expect(await utils.isPresent('#more-options-btn')).to.be.true;
await utils.clickOn('#more-options-btn');
await browser.sleep(500);
await utils.waitForElement('.mat-menu-content');
await utils.waitForElement('#streaming-btn');
await utils.clickOn('#streaming-btn');
await browser.sleep(500);
await utils.waitForElement('.sidenav-menu');
await utils.waitForElement('#activities-container');
await utils.waitForElement('#streaming-url-input');
const input = await utils.waitForElement('#rtmp-url-input');
await input.sendKeys('RTMPurl');
await utils.clickOn('#streaming-btn');
// Open more options menu
await utils.waitForElement('#more-options-btn');
expect(await utils.isPresent('#more-options-btn')).to.be.true;
await utils.clickOn('#more-options-btn');
await browser.sleep(500);
await utils.waitForElement('.mat-menu-content');
await utils.waitForElement('#streaming-btn');
await utils.clickOn('#streaming-btn');
// Checking if onToolbarStopStreamingClicked has been received
await utils.waitForElement('#onToolbarStopStreamingClicked');
expect(await utils.isPresent('#onToolbarStopStreamingClicked')).to.be.true;
});
it('should receive the onActivitiesPanelStartRecordingClicked event', async () => {
let element;
await browser.get(`${url}&prejoin=false`);
@ -890,6 +940,50 @@ describe('Testing videoconference EVENTS', () => {
expect(await utils.isPresent('#onActivitiesPanelDeleteRecordingClicked')).to.be.true;
});
it('should receive the onActivitiesPanelStartStreaming and onActivitiesPanelStopStreamingClicked events', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkSessionIsPresent();
await utils.checkToolbarIsPresent();
// Get activities button and click into it
await utils.waitForElement('#activities-panel-btn');
await utils.clickOn('#activities-panel-btn');
await browser.sleep(500);
await utils.waitForElement('.sidenav-menu');
await utils.waitForElement('#activities-container');
expect(await utils.isPresent('#activities-container')).to.be.true;
await utils.waitForElement('#streaming-activity');
await utils.clickOn('#streaming-activity');
await browser.sleep(1000);
const button = await utils.waitForElement('#streaming-btn');
expect(await button.isEnabled()).to.be.false;
const input = await utils.waitForElement('#rtmp-url-input');
await input.sendKeys('RTMPurl');
await utils.clickOn('#streaming-btn');
// Checking if onActivitiesPanelStartStreamingClicked has been received
await utils.waitForElement('#onActivitiesPanelStartStreamingClicked');
expect(await utils.isPresent('#onActivitiesPanelStartStreamingClicked')).to.be.true;
expect(await utils.isPresent('#streaming-tag')).to.be.true;
await utils.clickOn('#stop-streaming-btn');
// Checking if onActivitiesPanelStopStreamingClicked has been received
await utils.waitForElement('#onActivitiesPanelStopStreamingClicked');
expect(await utils.isPresent('#onActivitiesPanelStopStreamingClicked')).to.be.true;
expect(await utils.isPresent('#streaming-tag')).to.be.false;
});
it('should receive the onSessionCreated event', async () => {
await browser.get(`${url}&prejoin=false`);

View File

@ -653,9 +653,9 @@
}
},
"node_modules/@babel/compat-data": {
"version": "7.20.1",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.1.tgz",
"integrity": "sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.5.tgz",
"integrity": "sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g==",
"dev": true,
"engines": {
"node": ">=6.9.0"
@ -781,9 +781,9 @@
}
},
"node_modules/@babel/helper-create-class-features-plugin": {
"version": "7.20.2",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.2.tgz",
"integrity": "sha512-k22GoYRAHPYr9I+Gvy2ZQlAe5mGy8BqWst2wRt8cwIufWTxrsVshhIBvYNqC80N0GSFWTsqRVexOtfzlgOEDvA==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.5.tgz",
"integrity": "sha512-3RCdA/EmEaikrhayahwToF0fpweU/8o2p8vhc1c/1kftHOdTKuC65kik/TLc+qfbS8JKw4qqJbne4ovICDhmww==",
"dev": true,
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.18.6",
@ -802,13 +802,13 @@
}
},
"node_modules/@babel/helper-create-regexp-features-plugin": {
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz",
"integrity": "sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.20.5.tgz",
"integrity": "sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w==",
"dev": true,
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.18.6",
"regexpu-core": "^5.1.0"
"regexpu-core": "^5.2.1"
},
"engines": {
"node": ">=6.9.0"
@ -1051,29 +1051,29 @@
}
},
"node_modules/@babel/helper-wrap-function": {
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz",
"integrity": "sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz",
"integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==",
"dev": true,
"dependencies": {
"@babel/helper-function-name": "^7.19.0",
"@babel/template": "^7.18.10",
"@babel/traverse": "^7.19.0",
"@babel/types": "^7.19.0"
"@babel/traverse": "^7.20.5",
"@babel/types": "^7.20.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helpers": {
"version": "7.20.1",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.1.tgz",
"integrity": "sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg==",
"version": "7.20.6",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz",
"integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==",
"dev": true,
"dependencies": {
"@babel/template": "^7.18.10",
"@babel/traverse": "^7.20.1",
"@babel/types": "^7.20.0"
"@babel/traverse": "^7.20.5",
"@babel/types": "^7.20.5"
},
"engines": {
"node": ">=6.9.0"
@ -1094,9 +1094,9 @@
}
},
"node_modules/@babel/parser": {
"version": "7.20.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz",
"integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
"integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
@ -1353,14 +1353,14 @@
}
},
"node_modules/@babel/plugin-proposal-private-property-in-object": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz",
"integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.20.5.tgz",
"integrity": "sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ==",
"dev": true,
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.18.6",
"@babel/helper-create-class-features-plugin": "^7.18.6",
"@babel/helper-plugin-utils": "^7.18.6",
"@babel/helper-create-class-features-plugin": "^7.20.5",
"@babel/helper-plugin-utils": "^7.20.2",
"@babel/plugin-syntax-private-property-in-object": "^7.14.5"
},
"engines": {
@ -1626,9 +1626,9 @@
}
},
"node_modules/@babel/plugin-transform-block-scoping": {
"version": "7.20.2",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.2.tgz",
"integrity": "sha512-y5V15+04ry69OV2wULmwhEA6jwSWXO1TwAtIwiPXcvHcoOQUqpyMVd2bDsQJMW8AurjulIyUV8kDqtjSwHy1uQ==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.5.tgz",
"integrity": "sha512-WvpEIW9Cbj9ApF3yJCjIEEf1EiNJLtXagOrL5LNWEZOo3jv8pmPoYTSNJQvqej8OavVlgOoOPw6/htGZro6IkA==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.20.2"
@ -1870,13 +1870,13 @@
}
},
"node_modules/@babel/plugin-transform-named-capturing-groups-regex": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz",
"integrity": "sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz",
"integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==",
"dev": true,
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.19.0",
"@babel/helper-plugin-utils": "^7.19.0"
"@babel/helper-create-regexp-features-plugin": "^7.20.5",
"@babel/helper-plugin-utils": "^7.20.2"
},
"engines": {
"node": ">=6.9.0"
@ -1917,9 +1917,9 @@
}
},
"node_modules/@babel/plugin-transform-parameters": {
"version": "7.20.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.3.tgz",
"integrity": "sha512-oZg/Fpx0YDrj13KsLyO8I/CX3Zdw7z0O9qOd95SqcoIzuqy/WTGWvePeHAnZCN54SfdyjHcb1S30gc8zlzlHcA==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.5.tgz",
"integrity": "sha512-h7plkOmcndIUWXZFLgpbrh2+fXAi47zcUX7IrOQuZdLD0I0KvjJ6cvo3BEcAOsDOcZhVKGJqv07mkSqK0y2isQ==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.20.2"
@ -1947,13 +1947,13 @@
}
},
"node_modules/@babel/plugin-transform-regenerator": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz",
"integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz",
"integrity": "sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.18.6",
"regenerator-transform": "^0.15.0"
"@babel/helper-plugin-utils": "^7.20.2",
"regenerator-transform": "^0.15.1"
},
"engines": {
"node": ">=6.9.0"
@ -2254,19 +2254,19 @@
}
},
"node_modules/@babel/traverse": {
"version": "7.20.1",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.1.tgz",
"integrity": "sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz",
"integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.18.6",
"@babel/generator": "^7.20.1",
"@babel/generator": "^7.20.5",
"@babel/helper-environment-visitor": "^7.18.9",
"@babel/helper-function-name": "^7.19.0",
"@babel/helper-hoist-variables": "^7.18.6",
"@babel/helper-split-export-declaration": "^7.18.6",
"@babel/parser": "^7.20.1",
"@babel/types": "^7.20.0",
"@babel/parser": "^7.20.5",
"@babel/types": "^7.20.5",
"debug": "^4.1.0",
"globals": "^11.1.0"
},
@ -2275,12 +2275,12 @@
}
},
"node_modules/@babel/traverse/node_modules/@babel/generator": {
"version": "7.20.4",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.4.tgz",
"integrity": "sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz",
"integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==",
"dev": true,
"dependencies": {
"@babel/types": "^7.20.2",
"@babel/types": "^7.20.5",
"@jridgewell/gen-mapping": "^0.3.2",
"jsesc": "^2.5.1"
},
@ -2303,9 +2303,9 @@
}
},
"node_modules/@babel/types": {
"version": "7.20.2",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.2.tgz",
"integrity": "sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.19.4",
@ -3517,10 +3517,13 @@
"dev": true
},
"node_modules/@types/cors": {
"version": "2.8.12",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz",
"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==",
"dev": true
"version": "2.8.13",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz",
"integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/eslint": {
"version": "8.4.10",
@ -4899,9 +4902,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001434",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz",
"integrity": "sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA==",
"version": "1.0.30001439",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001439.tgz",
"integrity": "sha512-1MgUzEkoMO6gKfXflStpYgZDlFM7M/ck/bgfVCACO5vnAf0fXoNVHdWtqGU+MYca+4bL9Z5bpOVmR33cWW9G2A==",
"dev": true,
"funding": [
{
@ -6000,9 +6003,9 @@
}
},
"node_modules/cssdb": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.1.0.tgz",
"integrity": "sha512-Sd99PrFgx28ez4GHu8yoQIufc/70h9oYowDf4EjeIKi8mac9whxRjhM3IaMr6EllP6KKKWtJrMfN6C7T9tIWvQ==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.2.0.tgz",
"integrity": "sha512-JYlIsE7eKHSi0UNuCyo96YuIDFqvhGgHw4Ck6lsN+DP0Tp8M64UTDT2trGbkMDqnCoEjks7CkS0XcjU0rkvBdg==",
"dev": true,
"funding": {
"type": "opencollective",
@ -6103,9 +6106,9 @@
}
},
"node_modules/decode-uri-component": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==",
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
"dev": true,
"engines": {
"node": ">=0.10"
@ -6590,12 +6593,12 @@
}
},
"node_modules/enhanced-resolve": {
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.11.0.tgz",
"integrity": "sha512-0Gcraf7gAJSQoPg+bTSXNhuzAYtXqLc4C011vb8S3B8XUSEkGYNBk20c68X9291VF4vvsCD8SPkr6Mza+DwU+g==",
"version": "5.12.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz",
"integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.2.9",
"graceful-fs": "^4.2.4",
"tapable": "^2.2.0"
},
"engines": {
@ -7599,9 +7602,9 @@
"dev": true
},
"node_modules/fastq": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
"integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz",
"integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==",
"dev": true,
"dependencies": {
"reusify": "^1.0.4"
@ -8633,9 +8636,9 @@
]
},
"node_modules/ignore": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
"integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz",
"integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==",
"dev": true,
"engines": {
"node": ">= 4"
@ -10115,9 +10118,9 @@
}
},
"node_modules/log4js": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/log4js/-/log4js-6.7.0.tgz",
"integrity": "sha512-KA0W9ffgNBLDj6fZCq/lRbgR6ABAodRIDHrZnS48vOtfKa4PzWImb0Md1lmGCdO3n3sbCm/n1/WmrNlZ8kCI3Q==",
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/log4js/-/log4js-6.7.1.tgz",
"integrity": "sha512-lzbd0Eq1HRdWM2abSD7mk6YIVY0AogGJzb/z+lqzRk+8+XJP+M6L1MS5FUSc3jjGru4dbKjEMJmqlsoYYpuivQ==",
"dev": true,
"dependencies": {
"date-format": "^4.0.14",
@ -10261,9 +10264,9 @@
"dev": true
},
"node_modules/marked": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.2.3.tgz",
"integrity": "sha512-slWRdJkbTZ+PjkyJnE30Uid64eHwbwa1Q25INCAYfZlK4o6ylagBy/Le9eWntqJFoFT93ikUKMv47GZ4gTwHkw==",
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.2.4.tgz",
"integrity": "sha512-Wcc9ikX7Q5E4BYDPvh1C6QNSxrjC9tBgz+A/vAhp59KXUgachw++uMvMKiSW8oA85nopmPZcEvBoex/YLMsiyA==",
"dev": true,
"bin": {
"marked": "bin/marked.js"
@ -10462,9 +10465,9 @@
}
},
"node_modules/minipass": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz",
"integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==",
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
@ -12360,9 +12363,9 @@
}
},
"node_modules/postcss-custom-properties": {
"version": "12.1.10",
"resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.10.tgz",
"integrity": "sha512-U3BHdgrYhCrwTVcByFHs9EOBoqcKq4Lf3kXwbTi4hhq0qWhl/pDWq2THbv/ICX/Fl9KqeHBb8OVrTf2OaYF07A==",
"version": "12.1.11",
"resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.11.tgz",
"integrity": "sha512-0IDJYhgU8xDv1KY6+VgUwuQkVtmYzRwu+dMjnmdMafXYv86SWqfxkc7qdDvWS38vsjaEtv8e0vGOUQrAiMBLpQ==",
"dev": true,
"dependencies": {
"postcss-value-parser": "^4.2.0"
@ -14115,17 +14118,17 @@
}
},
"node_modules/socket.io": {
"version": "4.5.3",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.3.tgz",
"integrity": "sha512-zdpnnKU+H6mOp7nYRXH4GNv1ux6HL6+lHL8g7Ds7Lj8CkdK1jJK/dlwsKDculbyOHifcJ0Pr/yeXnZQ5GeFrcg==",
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.4.tgz",
"integrity": "sha512-m3GC94iK9MfIEeIBfbhJs5BqFibMtkRk8ZpKwG2QwxV0m/eEhPIV4ara6XCF1LWNAus7z58RodiZlAH71U3EhQ==",
"dev": true,
"dependencies": {
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"debug": "~4.3.2",
"engine.io": "~6.2.0",
"engine.io": "~6.2.1",
"socket.io-adapter": "~2.4.0",
"socket.io-parser": "~4.2.0"
"socket.io-parser": "~4.2.1"
},
"engines": {
"node": ">=10.0.0"
@ -14274,6 +14277,7 @@
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
"deprecated": "Please use @jridgewell/sourcemap-codec instead",
"dev": true
},
"node_modules/spdx-correct": {
@ -14784,14 +14788,14 @@
}
},
"node_modules/tar": {
"version": "6.1.12",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.12.tgz",
"integrity": "sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw==",
"version": "6.1.13",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz",
"integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==",
"dev": true,
"dependencies": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"minipass": "^3.0.0",
"minipass": "^4.0.0",
"minizlib": "^2.1.1",
"mkdirp": "^1.0.3",
"yallist": "^4.0.0"
@ -14800,6 +14804,18 @@
"node": ">=10"
}
},
"node_modules/tar/node_modules/minipass": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-4.0.0.tgz",
"integrity": "sha512-g2Uuh2jEKoht+zvO6vJqXmYpflPqzRBT+Th2h01DKh5z7wbY/AZ2gCQ78cP70YoHPyFdY30YBV5WxgLOEwOykw==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/tcp-port-used": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz",
@ -16962,9 +16978,9 @@
}
},
"@babel/compat-data": {
"version": "7.20.1",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.1.tgz",
"integrity": "sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.5.tgz",
"integrity": "sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g==",
"dev": true
},
"@babel/core": {
@ -17062,9 +17078,9 @@
}
},
"@babel/helper-create-class-features-plugin": {
"version": "7.20.2",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.2.tgz",
"integrity": "sha512-k22GoYRAHPYr9I+Gvy2ZQlAe5mGy8BqWst2wRt8cwIufWTxrsVshhIBvYNqC80N0GSFWTsqRVexOtfzlgOEDvA==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.5.tgz",
"integrity": "sha512-3RCdA/EmEaikrhayahwToF0fpweU/8o2p8vhc1c/1kftHOdTKuC65kik/TLc+qfbS8JKw4qqJbne4ovICDhmww==",
"dev": true,
"requires": {
"@babel/helper-annotate-as-pure": "^7.18.6",
@ -17077,13 +17093,13 @@
}
},
"@babel/helper-create-regexp-features-plugin": {
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz",
"integrity": "sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.20.5.tgz",
"integrity": "sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w==",
"dev": true,
"requires": {
"@babel/helper-annotate-as-pure": "^7.18.6",
"regexpu-core": "^5.1.0"
"regexpu-core": "^5.2.1"
}
},
"@babel/helper-define-polyfill-provider": {
@ -17262,26 +17278,26 @@
"dev": true
},
"@babel/helper-wrap-function": {
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz",
"integrity": "sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz",
"integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==",
"dev": true,
"requires": {
"@babel/helper-function-name": "^7.19.0",
"@babel/template": "^7.18.10",
"@babel/traverse": "^7.19.0",
"@babel/types": "^7.19.0"
"@babel/traverse": "^7.20.5",
"@babel/types": "^7.20.5"
}
},
"@babel/helpers": {
"version": "7.20.1",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.1.tgz",
"integrity": "sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg==",
"version": "7.20.6",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz",
"integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==",
"dev": true,
"requires": {
"@babel/template": "^7.18.10",
"@babel/traverse": "^7.20.1",
"@babel/types": "^7.20.0"
"@babel/traverse": "^7.20.5",
"@babel/types": "^7.20.5"
}
},
"@babel/highlight": {
@ -17296,9 +17312,9 @@
}
},
"@babel/parser": {
"version": "7.20.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz",
"integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
"integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==",
"dev": true
},
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
@ -17459,14 +17475,14 @@
}
},
"@babel/plugin-proposal-private-property-in-object": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz",
"integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.20.5.tgz",
"integrity": "sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ==",
"dev": true,
"requires": {
"@babel/helper-annotate-as-pure": "^7.18.6",
"@babel/helper-create-class-features-plugin": "^7.18.6",
"@babel/helper-plugin-utils": "^7.18.6",
"@babel/helper-create-class-features-plugin": "^7.20.5",
"@babel/helper-plugin-utils": "^7.20.2",
"@babel/plugin-syntax-private-property-in-object": "^7.14.5"
}
},
@ -17645,9 +17661,9 @@
}
},
"@babel/plugin-transform-block-scoping": {
"version": "7.20.2",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.2.tgz",
"integrity": "sha512-y5V15+04ry69OV2wULmwhEA6jwSWXO1TwAtIwiPXcvHcoOQUqpyMVd2bDsQJMW8AurjulIyUV8kDqtjSwHy1uQ==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.5.tgz",
"integrity": "sha512-WvpEIW9Cbj9ApF3yJCjIEEf1EiNJLtXagOrL5LNWEZOo3jv8pmPoYTSNJQvqej8OavVlgOoOPw6/htGZro6IkA==",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "^7.20.2"
@ -17799,13 +17815,13 @@
}
},
"@babel/plugin-transform-named-capturing-groups-regex": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz",
"integrity": "sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz",
"integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==",
"dev": true,
"requires": {
"@babel/helper-create-regexp-features-plugin": "^7.19.0",
"@babel/helper-plugin-utils": "^7.19.0"
"@babel/helper-create-regexp-features-plugin": "^7.20.5",
"@babel/helper-plugin-utils": "^7.20.2"
}
},
"@babel/plugin-transform-new-target": {
@ -17828,9 +17844,9 @@
}
},
"@babel/plugin-transform-parameters": {
"version": "7.20.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.3.tgz",
"integrity": "sha512-oZg/Fpx0YDrj13KsLyO8I/CX3Zdw7z0O9qOd95SqcoIzuqy/WTGWvePeHAnZCN54SfdyjHcb1S30gc8zlzlHcA==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.5.tgz",
"integrity": "sha512-h7plkOmcndIUWXZFLgpbrh2+fXAi47zcUX7IrOQuZdLD0I0KvjJ6cvo3BEcAOsDOcZhVKGJqv07mkSqK0y2isQ==",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "^7.20.2"
@ -17846,13 +17862,13 @@
}
},
"@babel/plugin-transform-regenerator": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz",
"integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz",
"integrity": "sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "^7.18.6",
"regenerator-transform": "^0.15.0"
"@babel/helper-plugin-utils": "^7.20.2",
"regenerator-transform": "^0.15.1"
}
},
"@babel/plugin-transform-reserved-words": {
@ -18076,30 +18092,30 @@
}
},
"@babel/traverse": {
"version": "7.20.1",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.1.tgz",
"integrity": "sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz",
"integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.18.6",
"@babel/generator": "^7.20.1",
"@babel/generator": "^7.20.5",
"@babel/helper-environment-visitor": "^7.18.9",
"@babel/helper-function-name": "^7.19.0",
"@babel/helper-hoist-variables": "^7.18.6",
"@babel/helper-split-export-declaration": "^7.18.6",
"@babel/parser": "^7.20.1",
"@babel/types": "^7.20.0",
"@babel/parser": "^7.20.5",
"@babel/types": "^7.20.5",
"debug": "^4.1.0",
"globals": "^11.1.0"
},
"dependencies": {
"@babel/generator": {
"version": "7.20.4",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.4.tgz",
"integrity": "sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz",
"integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==",
"dev": true,
"requires": {
"@babel/types": "^7.20.2",
"@babel/types": "^7.20.5",
"@jridgewell/gen-mapping": "^0.3.2",
"jsesc": "^2.5.1"
}
@ -18118,9 +18134,9 @@
}
},
"@babel/types": {
"version": "7.20.2",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.2.tgz",
"integrity": "sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==",
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
"dev": true,
"requires": {
"@babel/helper-string-parser": "^7.19.4",
@ -19005,10 +19021,13 @@
"dev": true
},
"@types/cors": {
"version": "2.8.12",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz",
"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==",
"dev": true
"version": "2.8.13",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz",
"integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/eslint": {
"version": "8.4.10",
@ -20147,9 +20166,9 @@
"dev": true
},
"caniuse-lite": {
"version": "1.0.30001434",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz",
"integrity": "sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA==",
"version": "1.0.30001439",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001439.tgz",
"integrity": "sha512-1MgUzEkoMO6gKfXflStpYgZDlFM7M/ck/bgfVCACO5vnAf0fXoNVHdWtqGU+MYca+4bL9Z5bpOVmR33cWW9G2A==",
"dev": true
},
"chai": {
@ -20982,9 +21001,9 @@
}
},
"cssdb": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.1.0.tgz",
"integrity": "sha512-Sd99PrFgx28ez4GHu8yoQIufc/70h9oYowDf4EjeIKi8mac9whxRjhM3IaMr6EllP6KKKWtJrMfN6C7T9tIWvQ==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.2.0.tgz",
"integrity": "sha512-JYlIsE7eKHSi0UNuCyo96YuIDFqvhGgHw4Ck6lsN+DP0Tp8M64UTDT2trGbkMDqnCoEjks7CkS0XcjU0rkvBdg==",
"dev": true
},
"cssesc": {
@ -21058,9 +21077,9 @@
"dev": true
},
"decode-uri-component": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==",
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
"dev": true
},
"deep-eql": {
@ -21439,12 +21458,12 @@
"dev": true
},
"enhanced-resolve": {
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.11.0.tgz",
"integrity": "sha512-0Gcraf7gAJSQoPg+bTSXNhuzAYtXqLc4C011vb8S3B8XUSEkGYNBk20c68X9291VF4vvsCD8SPkr6Mza+DwU+g==",
"version": "5.12.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz",
"integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==",
"dev": true,
"requires": {
"graceful-fs": "^4.2.9",
"graceful-fs": "^4.2.4",
"tapable": "^2.2.0"
}
},
@ -22130,9 +22149,9 @@
"dev": true
},
"fastq": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
"integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz",
"integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==",
"dev": true,
"requires": {
"reusify": "^1.0.4"
@ -22897,9 +22916,9 @@
"dev": true
},
"ignore": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
"integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz",
"integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==",
"dev": true
},
"ignore-walk": {
@ -24029,9 +24048,9 @@
}
},
"log4js": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/log4js/-/log4js-6.7.0.tgz",
"integrity": "sha512-KA0W9ffgNBLDj6fZCq/lRbgR6ABAodRIDHrZnS48vOtfKa4PzWImb0Md1lmGCdO3n3sbCm/n1/WmrNlZ8kCI3Q==",
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/log4js/-/log4js-6.7.1.tgz",
"integrity": "sha512-lzbd0Eq1HRdWM2abSD7mk6YIVY0AogGJzb/z+lqzRk+8+XJP+M6L1MS5FUSc3jjGru4dbKjEMJmqlsoYYpuivQ==",
"dev": true,
"requires": {
"date-format": "^4.0.14",
@ -24143,9 +24162,9 @@
"dev": true
},
"marked": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.2.3.tgz",
"integrity": "sha512-slWRdJkbTZ+PjkyJnE30Uid64eHwbwa1Q25INCAYfZlK4o6ylagBy/Le9eWntqJFoFT93ikUKMv47GZ4gTwHkw==",
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.2.4.tgz",
"integrity": "sha512-Wcc9ikX7Q5E4BYDPvh1C6QNSxrjC9tBgz+A/vAhp59KXUgachw++uMvMKiSW8oA85nopmPZcEvBoex/YLMsiyA==",
"dev": true
},
"media-typer": {
@ -24286,9 +24305,9 @@
"dev": true
},
"minipass": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz",
"integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==",
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
@ -25706,9 +25725,9 @@
}
},
"postcss-custom-properties": {
"version": "12.1.10",
"resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.10.tgz",
"integrity": "sha512-U3BHdgrYhCrwTVcByFHs9EOBoqcKq4Lf3kXwbTi4hhq0qWhl/pDWq2THbv/ICX/Fl9KqeHBb8OVrTf2OaYF07A==",
"version": "12.1.11",
"resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.11.tgz",
"integrity": "sha512-0IDJYhgU8xDv1KY6+VgUwuQkVtmYzRwu+dMjnmdMafXYv86SWqfxkc7qdDvWS38vsjaEtv8e0vGOUQrAiMBLpQ==",
"dev": true,
"requires": {
"postcss-value-parser": "^4.2.0"
@ -26952,17 +26971,17 @@
"dev": true
},
"socket.io": {
"version": "4.5.3",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.3.tgz",
"integrity": "sha512-zdpnnKU+H6mOp7nYRXH4GNv1ux6HL6+lHL8g7Ds7Lj8CkdK1jJK/dlwsKDculbyOHifcJ0Pr/yeXnZQ5GeFrcg==",
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.4.tgz",
"integrity": "sha512-m3GC94iK9MfIEeIBfbhJs5BqFibMtkRk8ZpKwG2QwxV0m/eEhPIV4ara6XCF1LWNAus7z58RodiZlAH71U3EhQ==",
"dev": true,
"requires": {
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"debug": "~4.3.2",
"engine.io": "~6.2.0",
"engine.io": "~6.2.1",
"socket.io-adapter": "~2.4.0",
"socket.io-parser": "~4.2.0"
"socket.io-parser": "~4.2.1"
}
},
"socket.io-adapter": {
@ -27475,17 +27494,28 @@
"dev": true
},
"tar": {
"version": "6.1.12",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.12.tgz",
"integrity": "sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw==",
"version": "6.1.13",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz",
"integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==",
"dev": true,
"requires": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"minipass": "^3.0.0",
"minipass": "^4.0.0",
"minizlib": "^2.1.1",
"mkdirp": "^1.0.3",
"yallist": "^4.0.0"
},
"dependencies": {
"minipass": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-4.0.0.tgz",
"integrity": "sha512-g2Uuh2jEKoht+zvO6vJqXmYpflPqzRBT+Th2h01DKh5z7wbY/AZ2gCQ78cP70YoHPyFdY30YBV5WxgLOEwOykw==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
}
}
}
},
"tcp-port-used": {

View File

@ -17,39 +17,39 @@
"zone.js": "0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "14.2.10",
"@angular/cli": "14.2.10",
"@angular/compiler": "14.2.10",
"@angular/compiler-cli": "14.2.10",
"@angular/elements": "14.2.10",
"@compodoc/compodoc": "^1.1.19",
"@types/chai": "4.3.0",
"@types/mocha": "9.1.0",
"@types/node": "16.11.6",
"@types/selenium-webdriver": "4.1.5",
"chai": "4.3.6",
"chromedriver": "106.0.1",
"codelyzer": "6.0.2",
"concat": "^1.0.3",
"cross-env": "^7.0.3",
"http-server": "14.1.1",
"jasmine-core": "3.10.1",
"jasmine-spec-reporter": "7.0.0",
"karma": "^6.3.9",
"karma-chrome-launcher": "3.1.1",
"karma-coverage": "^2.0.3",
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-jasmine": "4.0.1",
"karma-jasmine-html-reporter": "1.7.0",
"karma-junit-reporter": "2.0.1",
"karma-mocha-reporter": "2.2.5",
"karma-notify-reporter": "1.3.0",
"mocha": "9.2.2",
"ng-packagr": "14.2.2",
"selenium-webdriver": "4.5.0",
"ts-node": "10.4.0",
"tslint": "6.1.3",
"typescript": "4.8.4",
"@angular-devkit/build-angular": "14.2.10",
"@angular/cli": "14.2.10",
"@angular/compiler": "14.2.10",
"@angular/compiler-cli": "14.2.10",
"@angular/elements": "14.2.10",
"@compodoc/compodoc": "^1.1.19",
"@types/chai": "4.3.0",
"@types/mocha": "9.1.0",
"@types/node": "16.11.6",
"@types/selenium-webdriver": "4.1.5",
"chai": "4.3.6",
"chromedriver": "106.0.1",
"codelyzer": "6.0.2",
"concat": "^1.0.3",
"cross-env": "^7.0.3",
"http-server": "14.1.1",
"jasmine-core": "3.10.1",
"jasmine-spec-reporter": "7.0.0",
"karma": "^6.3.9",
"karma-chrome-launcher": "3.1.1",
"karma-coverage": "^2.0.3",
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-jasmine": "4.0.1",
"karma-jasmine-html-reporter": "1.7.0",
"karma-junit-reporter": "2.0.1",
"karma-mocha-reporter": "2.2.5",
"karma-notify-reporter": "1.3.0",
"mocha": "9.2.2",
"ng-packagr": "14.2.2",
"selenium-webdriver": "4.5.0",
"ts-node": "10.4.0",
"tslint": "6.1.3",
"typescript": "4.8.4",
"webpack-bundle-analyzer": "^4.5.0"
},
"name": "openvidu-components-testapp",
@ -78,4 +78,4 @@
"webcomponent:serve-testapp": "npx http-server ./e2e/webcomponent-app/ && echo http://localhost:8080/?OV_URL=https://localhost:4443&OV_SECRET=MY_SECRET&prejoin=false"
},
"version": "2.25.0"
}
}

View File

@ -1,11 +1,11 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnInit, QueryList, ViewChildren } from '@angular/core';
import { Subscription } from 'rxjs';
import { PanelEvent, PanelService } from '../../services/panel/panel.service';
import { PanelService } from '../../services/panel/panel.service';
import { animate, style, transition, trigger } from '@angular/animations';
import { Session, SpeechToTextEvent } from 'openvidu-browser';
import { CaptionModel, CaptionsLangOption } from '../../models/caption.model';
import { PanelSettingsOptions, PanelType } from '../../models/panel.model';
import { PanelEvent, PanelSettingsOptions, PanelType } from '../../models/panel.model';
import { CaptionService } from '../../services/caption/caption.service';
import { OpenViduService } from '../../services/openvidu/openvidu.service';
import { ParticipantService } from '../../services/participant/participant.service';

View File

@ -34,7 +34,7 @@
}
::ng-deep .mat-expansion-panel-header {
padding: 0px 10px !important;
padding: 0px 5px !important;
height: 65px !important;
}
@ -54,3 +54,7 @@
::ng-deep .mat-expansion-panel {
box-shadow: none !important;
}
::ng-deep .no-body .mat-expansion-panel-content {
display: none !important;
}

View File

@ -7,7 +7,7 @@
</div>
<div class="activities-body-container" fxFlex="75%" fxLayoutAlign="space-evenly none">
<mat-accordion>
<mat-accordion [multi]="false">
<ov-recording-activity
*ngIf="showRecordingActivity"
id="recording-activity"
@ -16,6 +16,13 @@
(onStopRecordingClicked)="_onStopRecordingClicked()"
(onDeleteRecordingClicked)="_onDeleteRecordingClicked($event)"
></ov-recording-activity>
<ov-streaming-activity
*ngIf="showStreamingActivity"
id="streaming-activity"
[expanded]="expandedPanel === 'streaming'"
(onStartStreamingClicked)="_onStartStreamingClicked($event)"
(onStopStreamingClicked)="_onStopStreamingClicked()"
></ov-streaming-activity>
</mat-accordion>
</div>
</div>

View File

@ -1,8 +1,8 @@
import { Component, OnInit, Output, EventEmitter, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, OnInit, Output } from '@angular/core';
import { Subscription } from 'rxjs';
import { PanelType } from '../../../models/panel.model';
import { PanelEvent, PanelType } from '../../../models/panel.model';
import { OpenViduAngularConfigService } from '../../../services/config/openvidu-angular.config.service';
import { PanelEvent, PanelService } from '../../../services/panel/panel.service';
import { PanelService } from '../../../services/panel/panel.service';
@Component({
selector: 'ov-activities-panel',
@ -13,7 +13,7 @@ import { PanelEvent, PanelService } from '../../../services/panel/panel.service'
export class ActivitiesPanelComponent implements OnInit {
/**
* Provides event notifications that fire when start recording button has been clicked.
* The recording should be stated using the OpenVidu REST API.
* The recording should be started using the OpenVidu REST API.
*/
@Output() onStartRecordingClicked: EventEmitter<void> = new EventEmitter<void>();
@ -29,6 +29,17 @@ export class ActivitiesPanelComponent implements OnInit {
*/
@Output() onDeleteRecordingClicked: EventEmitter<string> = new EventEmitter<string>();
/**
* Provides event notifications that fire when start streaming button has been clicked.
* The streaming should be started using the REST API.
*/
@Output() onStartStreamingClicked: EventEmitter<string> = new EventEmitter<string>();
/**
* Provides event notifications that fire when stop streaming button has been clicked.
* The streaming should be stopped using the REST API.
*/
@Output() onStopStreamingClicked: EventEmitter<void> = new EventEmitter<void>();
/**
* @internal
@ -38,8 +49,10 @@ export class ActivitiesPanelComponent implements OnInit {
* @internal
*/
showRecordingActivity: boolean = true;
showStreamingActivity: boolean = true;
private panelSubscription: Subscription;
private recordingActivitySub: Subscription;
private streamingActivitySub: Subscription;
/**
* @internal
@ -60,6 +73,7 @@ export class ActivitiesPanelComponent implements OnInit {
ngOnDestroy() {
if (this.panelSubscription) this.panelSubscription.unsubscribe();
if (this.recordingActivitySub) this.recordingActivitySub.unsubscribe();
if (this.streamingActivitySub) this.streamingActivitySub.unsubscribe();
}
/**
@ -90,14 +104,20 @@ export class ActivitiesPanelComponent implements OnInit {
this.onDeleteRecordingClicked.emit(recordingId);
}
_onStartStreamingClicked(rtmpUrl: string) {
this.onStartStreamingClicked.emit(rtmpUrl);
}
_onStopStreamingClicked() {
this.onStopStreamingClicked.emit();
}
private subscribeToPanelToggling() {
this.panelSubscription = this.panelService.panelOpenedObs.subscribe(
(ev: PanelEvent) => {
if (ev.type === PanelType.ACTIVITIES && !!ev.expand) {
this.expandedPanel = ev.expand;
}
this.panelSubscription = this.panelService.panelOpenedObs.subscribe((ev: PanelEvent) => {
if (ev.type === PanelType.ACTIVITIES && !!ev.expand) {
this.expandedPanel = ev.expand;
}
);
});
}
private subscribeToActivitiesPanelDirective() {
@ -105,5 +125,10 @@ export class ActivitiesPanelComponent implements OnInit {
this.showRecordingActivity = value;
this.cd.markForCheck();
});
this.streamingActivitySub = this.libService.streamingActivity.subscribe((value: boolean) => {
this.showStreamingActivity = value;
this.cd.markForCheck();
});
}
}

View File

@ -1,8 +1,8 @@
#recording-status {
color: var(--ov-text-color);
display: inline;
padding: 5px;
font-size: 12px;
padding: 3px;
font-size: 11px;
border-radius: var(--ov-panel-radius);
}
@ -108,7 +108,7 @@
}
mat-expansion-panel {
margin: 0px 0px 15px 0px;
margin: 0px 0px 5px 0px;
}
.blink {

View File

@ -1,4 +1,4 @@
<mat-expansion-panel (opened)="panelOpened()" (closed)="panelClosed()" [expanded]="expanded">
<mat-expansion-panel (opened)="panelOpened()" (closed)="panelClosed()" [expanded]="expanded" [ngClass]="{'no-body': !opened}">
<mat-expansion-panel-header>
<mat-list>
<mat-list-item>
@ -12,7 +12,7 @@
pending: recordingStatus === recStatusEnum.STARTING || recordingStatus === recStatusEnum.STOPPING
}"
>
<mat-icon *ngIf="recordingStatus !== recStatusEnum.FAILED && recordingStatus !== recStatusEnum.STARTED">
<mat-icon id="recording-icon" *ngIf="recordingStatus !== recStatusEnum.FAILED && recordingStatus !== recStatusEnum.STARTED">
video_camera_front
</mat-icon>
<mat-icon *ngIf="recordingStatus === recStatusEnum.FAILED">error</mat-icon>

View File

@ -1,6 +1,5 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Subscription } from 'rxjs';
import { OpenViduRole } from '../../../../models/participant.model';
import { RecordingInfo, RecordingStatus } from '../../../../models/recording.model';
import { ActionService } from '../../../../services/action/action.service';
import { OpenViduAngularConfigService } from '../../../../services/config/openvidu-angular.config.service';
@ -21,7 +20,7 @@ export class RecordingActivityComponent implements OnInit {
/**
* Provides event notifications that fire when start recording button has been clicked.
* The recording should be stopped using the REST API.
* The recording should be started using the REST API.
*/
@Output() onStartRecordingClicked: EventEmitter<void> = new EventEmitter<void>();
@ -95,7 +94,7 @@ export class RecordingActivityComponent implements OnInit {
ngOnInit(): void {
this.subscribeToRecordingStatus();
this.subscribeToRecordingActivityDirective();
this.isSessionCreator = this.participantService.getMyRole() === OpenViduRole.MODERATOR;
this.isSessionCreator = this.participantService.amIModerator();
}
/**

View File

@ -0,0 +1,148 @@
#streaming-status {
color: var(--ov-text-color);
display: inline;
padding: 3px;
font-size: 11px;
border-radius: var(--ov-panel-radius);
}
.time-container {
padding: 2px;
}
.error-text {
color: var(--ov-warn-color);
font-style: italic;
font-size: 14px;
}
#streaming-icon {
color: #5903ca;
}
.streaming-duration {
background-color: var(--ov-light-color);
padding: 4px 8px;
border-radius: var(--ov-panel-radius);
font-weight: 500;
}
.streaming-duration mat-icon {
font-size: 18px;
width: 18px;
height: 18px;
}
.started {
background-color: #5903ca !important;
color: var(--ov-text-color);
}
.activity-icon.started {
background-color: #5903ca !important;
color: var(--ov-text-color);
}
.failed {
background-color: var(--ov-warn-color) !important;
color: var(--ov-text-color);
}
.stopped {
background-color: var(--ov-light-color);
color: var(--ov-panel-text-color) !important;
}
.pending {
background-color: #ffd79b !important;
color: var(--ov-panel-text-color) !important;
}
.panel-body-container {
padding: 10px;
}
.panel-body-container > .content {
align-items: stretch;
justify-content: center;
display: flex;
flex-direction: column;
box-flex: 1;
flex-grow: 1;
text-align: center;
}
.streaming-error {
color: var(--ov-warn-color);
font-weight: 600;
}
.streaming-name {
font-size: 16px;
font-weight: bold;
}
.not-allowed-message {
margin-top: 10px;
font-weight: bold;
}
.streaming-action-buttons {
margin-top: 20px;
margin-bottom: 20px;
}
/* #start-streaming-btn {
width: 100%;
background-color: var(--ov-tertiary-color);
color: var(--ov-text-color);
} */
#stop-streaming-btn {
/* background-color: var(--ov-warn-color); */
color: var(--ov-warn-color);
}
#reset-streaming-status-btn {
width: 100%;
background-color: var(--ov-light-color);
}
mat-expansion-panel {
margin: 0px 0px 5px 0px;
}
.blink {
animation: blinker 1.5s linear infinite !important;
}
@keyframes blinker {
50% {
opacity: 0.4;
}
}
.input-container {
height: 25px;
display: flex;
background-color: var(--ov-light-color);
padding: 10px;
margin: 10px;
border-radius: var(--ov-panel-radius);
}
.input-container input {
width: 100%;
height: 16px;
margin: auto;
background-color: transparent;
display: block;
border: none;
padding: 0;
word-wrap: break-word;
white-space: pre-wrap;
resize: none;
outline: none;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
font-family: 'Roboto', 'RobotoDraft', Helvetica, Arial, sans-serif;
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { StreamingActivityComponent } from './streaming-activity.component';
describe('StreamingActivityComponent', () => {
let component: StreamingActivityComponent;
let fixture: ComponentFixture<StreamingActivityComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ StreamingActivityComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(StreamingActivityComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,190 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Subscription } from 'rxjs';
import { Signal } from '../../../../models/signal.model';
import { StreamingError, StreamingInfo, StreamingStatus } from '../../../../models/streaming.model';
import { OpenViduAngularConfigService } from '../../../../services/config/openvidu-angular.config.service';
import { OpenViduService } from '../../../../services/openvidu/openvidu.service';
import { ParticipantService } from '../../../../services/participant/participant.service';
import { StreamingService } from '../../../../services/streaming/streaming.service';
@Component({
selector: 'ov-streaming-activity',
templateUrl: './streaming-activity.component.html',
styleUrls: ['./streaming-activity.component.css', '../activities-panel.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class StreamingActivityComponent implements OnInit {
/**
* Provides event notifications that fire when start streaming button has been clicked.
* The streaming should be started using the REST API.
*/
@Output() onStartStreamingClicked: EventEmitter<string> = new EventEmitter<string>();
/**
* Provides event notifications that fire when stop streaming button has been clicked.
* The streaming should be stopped using the REST API.
*/
@Output() onStopStreamingClicked: EventEmitter<void> = new EventEmitter<void>();
/**
* @internal
*/
urlRequiredError: boolean = false;
/**
* @internal
*/
oldStreamingStatus: StreamingStatus;
/**
* @internal
*/
rtmpUrl: string = '';
/**
* @internal
*/
@Input() expanded: boolean;
/**
* @internal
*/
streamingError: StreamingError | undefined;
/**
* @internal
*/
streamingStatus: StreamingStatus = StreamingStatus.STOPPED;
/**
* @internal
*/
streamingStatusEnum = StreamingStatus;
/**
* @internal
*/
opened: boolean = false;
/**
* @internal
*/
isSessionCreator: boolean = false;
/**
* @internal
*/
isRtmpModuleAvailable: boolean = true;
private streamingSub: Subscription;
private streamingInfoSub: Subscription;
private streamingErrorSub: Subscription;
/**
* @internal
*/
constructor(
private streamingService: StreamingService,
private participantService: ParticipantService,
private openviduService: OpenViduService,
private libService: OpenViduAngularConfigService,
private cd: ChangeDetectorRef
) {}
/**
* @internal
*/
ngOnInit(): void {
this.isSessionCreator = this.participantService.amIModerator();
this.subscribeToStreamingStatus();
this.subscribeToStreamingInfo();
this.subscribeToStreamingError();
}
/**
* @internal
*/
ngOnDestroy() {
if (this.streamingSub) this.streamingSub.unsubscribe();
if (this.streamingInfoSub) this.streamingInfoSub.unsubscribe();
if (this.streamingErrorSub) this.streamingErrorSub.unsubscribe();
}
/**
* @internal
*/
panelOpened() {
this.opened = true;
}
/**
* @internal
*/
panelClosed() {
this.opened = false;
}
/**
* @ignore
*/
eventKeyPress(event) {
// Pressed 'Enter' key
if (event && event.keyCode === 13) {
event.preventDefault();
this.startStreaming();
}
}
/**
* @internal
*/
startStreaming() {
if (!!this.rtmpUrl) {
this.isRtmpModuleAvailable = true;
this.streamingError = undefined;
this.streamingService.updateStatus(StreamingStatus.STARTING);
this.onStartStreamingClicked.emit(this.rtmpUrl);
}
this.urlRequiredError = !this.rtmpUrl;
}
/**
* @internal
*/
stopStreaming() {
this.onStopStreamingClicked.emit();
this.streamingService.updateStatus(StreamingStatus.STOPPING);
}
private subscribeToStreamingStatus() {
this.streamingSub = this.streamingService.streamingStatusObs.subscribe(
(ev: { status: StreamingStatus; time?: Date } | undefined) => {
if (!!ev) {
this.streamingStatus = ev.status;
this.cd.markForCheck();
if (this.isSessionCreator) {
//TODO: Remove it when RTMP Exported was included on OV and streaming ready event was fired.
const signal =
this.streamingStatus === StreamingStatus.STARTED ? Signal.STREAMING_STARTED : Signal.STREAMING_STOPPED;
this.openviduService.sendSignal(signal);
}
}
}
);
}
//TODO: Remove this directive when RTMP Exported was included on OV and streaming ready event was fired.
private subscribeToStreamingInfo() {
this.streamingInfoSub = this.libService.streamingInfoObs.subscribe((info: StreamingInfo | undefined) => {
if (!!info) {
this.streamingService.updateStatus(info.status);
this.cd.markForCheck();
}
});
}
private subscribeToStreamingError() {
this.streamingErrorSub = this.libService.streamingErrorObs.subscribe((error: StreamingError | undefined) => {
if (!!error) {
this.streamingError = error;
this.isRtmpModuleAvailable = error.rtmpAvailable;
this.streamingService.updateStatus(StreamingStatus.FAILED);
this.cd.markForCheck();
}
});
}
}

View File

@ -1,14 +1,10 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, OnInit, TemplateRef } from '@angular/core';
import { skip, Subscription } from 'rxjs';
import {
ChatPanelDirective,
AdditionalPanelsDirective,
ParticipantsPanelDirective,
BackgroundEffectsPanelDirective,
ActivitiesPanelDirective
ActivitiesPanelDirective, AdditionalPanelsDirective, ChatPanelDirective, ParticipantsPanelDirective
} from '../../directives/template/openvidu-angular.directive';
import { PanelType } from '../../models/panel.model';
import { PanelEvent, PanelService } from '../../services/panel/panel.service';
import { PanelEvent, PanelType } from '../../models/panel.model';
import { PanelService } from '../../services/panel/panel.service';
/**
*

View File

@ -1,8 +1,8 @@
import { Component, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import { PanelSettingsOptions, PanelType } from '../../../models/panel.model';
import { PanelEvent, PanelSettingsOptions, PanelType } from '../../../models/panel.model';
import { OpenViduAngularConfigService } from '../../../services/config/openvidu-angular.config.service';
import { PanelEvent, PanelService } from '../../../services/panel/panel.service';
import { PanelService } from '../../../services/panel/panel.service';
import { PlatformService } from '../../../services/platform/platform.service';
/**

View File

@ -32,8 +32,9 @@ import { animate, style, transition, trigger } from '@angular/animations';
import { MatDrawerContainer, MatSidenav } from '@angular/material/sidenav';
import { skip, Subscription } from 'rxjs';
import { SidenavMode } from '../../models/layout.model';
import { PanelType } from '../../models/panel.model';
import { PanelEvent, PanelType } from '../../models/panel.model';
import { Signal } from '../../models/signal.model';
import { StreamingStatus } from '../../models/streaming.model';
import { ActionService } from '../../services/action/action.service';
import { CaptionService } from '../../services/caption/caption.service';
import { ChatService } from '../../services/chat/chat.service';
@ -41,10 +42,11 @@ import { OpenViduAngularConfigService } from '../../services/config/openvidu-ang
import { LayoutService } from '../../services/layout/layout.service';
import { LoggerService } from '../../services/logger/logger.service';
import { OpenViduService } from '../../services/openvidu/openvidu.service';
import { PanelEvent, PanelService } from '../../services/panel/panel.service';
import { PanelService } from '../../services/panel/panel.service';
import { ParticipantService } from '../../services/participant/participant.service';
import { PlatformService } from '../../services/platform/platform.service';
import { RecordingService } from '../../services/recording/recording.service';
import { StreamingService } from '../../services/streaming/streaming.service';
import { TranslateService } from '../../services/translate/translate.service';
import { VirtualBackgroundService } from '../../services/virtual-background/virtual-background.service';
@ -99,6 +101,7 @@ export class SessionComponent implements OnInit, OnDestroy {
protected layoutService: LayoutService,
protected panelService: PanelService,
private recordingService: RecordingService,
private streamingService: StreamingService,
private translateService: TranslateService,
private captionService: CaptionService,
private platformService: PlatformService,
@ -182,10 +185,6 @@ export class SessionComponent implements OnInit, OnDestroy {
this.subscribeToNicknameChanged();
this.chatService.subscribeToChat();
this.subscribeToReconnection();
const recordingEnabled = this.libService.recordingButton.getValue() && this.libService.recordingActivity.getValue();
if (recordingEnabled) {
this.subscribeToRecordingEvents();
}
await this.connectToSession();
// ios devices appear with blank video. Muting and unmuting it fix this problem
@ -193,6 +192,15 @@ export class SessionComponent implements OnInit, OnDestroy {
await this.openviduService.publishVideo(false);
await this.openviduService.publishVideo(true);
}
if (this.libService.isRecordingEnabled()) {
this.subscribeToRecordingEvents();
}
if (this.libService.isStreamingEnabled() && !this.participantService.amIModerator()) {
//TODO: Remove it when RTMP Exported was included on OV and streaming ready event was fired.
this.subscribeToStreamingEvents();
}
}
this.preparing = false;
this.cd.markForCheck();
@ -438,6 +446,16 @@ export class SessionComponent implements OnInit, OnDestroy {
});
}
private subscribeToStreamingEvents() {
this.session.on(`signal:${Signal.STREAMING_STARTED}`, (event: any) => {
this.streamingService.updateStatus(StreamingStatus.STARTED);
});
this.session.on(`signal:${Signal.STREAMING_STOPPED}`, (event: any) => {
this.streamingService.updateStatus(StreamingStatus.STOPPED);
});
}
private startUpdateLayoutInterval() {
this.updateLayoutInterval = setInterval(() => {
this.layoutService.update();

View File

@ -70,9 +70,9 @@
flex-direction: column;
}
.recording-tag {
.recording-tag,
.streaming-tag {
padding: 0 10px;
background-color: var(--ov-warn-color);
border-radius: var(--ov-panel-radius);
width: fit-content;
font-size: 12px;
@ -80,13 +80,26 @@
line-height: 20px;
margin: auto;
}
.recording-tag mat-icon {
.recording-tag {
background-color: var(--ov-warn-color);
}
.streaming-tag {
background-color: #5903ca;
}
.recording-tag mat-icon,
.streaming-tag mat-icon {
font-size: 16px;
display: inline;
vertical-align: sub;
margin-right: 5px;
}
#streaming-btn > mat-icon {
color: #5903ca;
}
.blink {
animation: blinker 1.5s linear infinite;
}

View File

@ -7,7 +7,12 @@
<div *ngIf="recordingStatus === _recordingStatus.STARTED" id="recording-tag" class="recording-tag">
<mat-icon class="blink">radio_button_checked</mat-icon>
<span class="blink">REC</span>
<span> | {{ recordingTime | date: 'H:mm:ss' }}</span>
<span> | {{ recordingTime | date : 'H:mm:ss' }}</span>
</div>
<div *ngIf="streamingStatus === _streamingStatus.STARTED" id="streaming-tag" class="streaming-tag">
<mat-icon class="blink">sensors</mat-icon>
<span class="blink">LIVE</span>
<span> | {{ streamingTime | date : 'H:mm:ss' }}</span>
</div>
</div>
</div>
@ -117,6 +122,29 @@
</span>
</button>
<!-- Streaming button -->
<button
*ngIf="!isMinimal && showStreamingButton"
mat-menu-item
id="streaming-btn"
[disabled]="streamingStatus === _streamingStatus.STARTING || recordingStatus === _streamingStatus.STOPPING"
(click)="toggleStreaming()"
>
<mat-icon>sensors</mat-icon>
<span
*ngIf="
streamingStatus === _streamingStatus.STOPPED ||
streamingStatus === _streamingStatus.STOPPING ||
streamingStatus === _streamingStatus.FAILED
"
>
{{ 'PANEL.STREAMING.START' | translate }}
</span>
<span *ngIf="streamingStatus === _streamingStatus.STARTED || streamingStatus === _streamingStatus.STARTING">
{{ 'PANEL.STREAMING.STOP' | translate }}
</span>
</button>
<!-- Virtual background button -->
<button
*ngIf="!isMinimal && showBackgroundEffectsButton"

View File

@ -15,7 +15,7 @@ import {
import { skip, Subscription } from 'rxjs';
import { ChatService } from '../../services/chat/chat.service';
import { DocumentService } from '../../services/document/document.service';
import { PanelEvent, PanelService } from '../../services/panel/panel.service';
import { PanelService } from '../../services/panel/panel.service';
import { MediaChange } from '@angular/flex-layout';
import { MatMenuTrigger } from '@angular/material/menu';
@ -26,9 +26,10 @@ import {
} from '../../directives/template/openvidu-angular.directive';
import { ChatMessage } from '../../models/chat.model';
import { ILogger } from '../../models/logger.model';
import { PanelType } from '../../models/panel.model';
import { PanelEvent, PanelType } from '../../models/panel.model';
import { OpenViduRole, ParticipantAbstractModel } from '../../models/participant.model';
import { RecordingInfo, RecordingStatus } from '../../models/recording.model';
import { StreamingStatus } from '../../models/streaming.model';
import { ActionService } from '../../services/action/action.service';
import { OpenViduAngularConfigService } from '../../services/config/openvidu-angular.config.service';
import { DeviceService } from '../../services/device/device.service';
@ -39,6 +40,7 @@ import { ParticipantService } from '../../services/participant/participant.servi
import { PlatformService } from '../../services/platform/platform.service';
import { RecordingService } from '../../services/recording/recording.service';
import { StorageService } from '../../services/storage/storage.service';
import { StreamingService } from '../../services/streaming/streaming.service';
import { TranslateService } from '../../services/translate/translate.service';
/**
@ -182,6 +184,11 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
*/
@Output() onStartRecordingClicked: EventEmitter<void> = new EventEmitter<void>();
/**
* Provides event notifications that fire when start streaming button has been clicked.
*/
@Output() onStopStreamingClicked: EventEmitter<void> = new EventEmitter<void>();
/**
* Provides event notifications that fire when stop recording button has been clicked.
*/
@ -273,6 +280,11 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
*/
showRecordingButton: boolean = true;
/**
* @ignore
*/
showStreamingButton: boolean = true;
/**
* @ignore
*/
@ -324,15 +336,28 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
* @ignore
*/
recordingStatus: RecordingStatus = RecordingStatus.STOPPED;
/**
* @ignore
*/
streamingStatus: StreamingStatus = StreamingStatus.STOPPED;
/**
* @ignore
*/
_recordingStatus = RecordingStatus;
/**
* @ignore
*/
_streamingStatus = StreamingStatus;
/**
* @ignore
*/
recordingTime: Date;
/**
* @ignore
*/
streamingTime: Date;
/**
* @ignore
@ -354,7 +379,9 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
private backgroundEffectsButtonSub: Subscription;
private leaveButtonSub: Subscription;
private recordingButtonSub: Subscription;
private streamingButtonSub: Subscription;
private recordingSubscription: Subscription;
private streamingSubscription: Subscription;
private activitiesPanelButtonSub: Subscription;
private participantsPanelButtonSub: Subscription;
private chatPanelButtonSub: Subscription;
@ -382,6 +409,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
private libService: OpenViduAngularConfigService,
private platformService: PlatformService,
private recordingService: RecordingService,
private streamingService: StreamingService,
private translateService: TranslateService,
private storageSrv: StorageService
) {
@ -424,6 +452,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
this.subscribeToMenuToggling();
this.subscribeToChatMessages();
this.subscribeToRecordingStatus();
this.subscribeToStreamingStatus();
this.subscribeToScreenSize();
this.subscribeToCaptionsToggling();
}
@ -431,7 +460,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
ngAfterViewInit() {
// Sometimes the connection is undefined so we have to check the role when the mat menu is opened
this.menuTrigger?.menuOpened.subscribe(() => {
this.isSessionCreator = this.participantService.getMyRole() === OpenViduRole.MODERATOR;
this.isSessionCreator = this.participantService.amIModerator();
});
}
@ -444,6 +473,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
if (this.backgroundEffectsButtonSub) this.backgroundEffectsButtonSub.unsubscribe();
if (this.leaveButtonSub) this.leaveButtonSub.unsubscribe();
if (this.recordingButtonSub) this.recordingButtonSub.unsubscribe();
if (this.streamingButtonSub) this.streamingButtonSub.unsubscribe();
if (this.participantsPanelButtonSub) this.participantsPanelButtonSub.unsubscribe();
if (this.chatPanelButtonSub) this.chatPanelButtonSub.unsubscribe();
if (this.displayLogoSub) this.displayLogoSub.unsubscribe();
@ -451,6 +481,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
if (this.minimalSub) this.minimalSub.unsubscribe();
if (this.activitiesPanelButtonSub) this.activitiesPanelButtonSub.unsubscribe();
if (this.recordingSubscription) this.recordingSubscription.unsubscribe();
if (this.streamingSubscription) this.streamingSubscription.unsubscribe();
if (this.screenSizeSub) this.screenSizeSub.unsubscribe();
if (this.settingsButtonSub) this.settingsButtonSub.unsubscribe();
if (this.captionsSubs) this.captionsSubs.unsubscribe();
@ -537,6 +568,21 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
}
}
/**
* @ignore
*/
toggleStreaming() {
if (this.streamingStatus === StreamingStatus.STARTED) {
this.log.d('Stopping streaming');
this.onStopStreamingClicked.emit();
this.streamingService.updateStatus(StreamingStatus.STOPPING);
} else if (this.streamingStatus === StreamingStatus.STOPPED) {
if (this.showActivitiesPanelButton && !this.isActivitiesOpened) {
this.toggleActivitiesPanel('streaming');
}
}
}
/**
* @ignore
*/
@ -660,6 +706,20 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
});
}
private subscribeToStreamingStatus() {
this.streamingSubscription = this.streamingService.streamingStatusObs
.pipe(skip(1))
.subscribe((ev: { status: StreamingStatus, time?: Date } | undefined) => {
if (!!ev) {
this.streamingStatus = ev.status;
if (ev.time) {
this.streamingTime = ev.time;
}
this.cd.markForCheck();
}
});
}
private subscribeToToolbarDirectives() {
this.minimalSub = this.libService.minimalObs.subscribe((value: boolean) => {
this.isMinimal = value;
@ -685,6 +745,12 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
this.cd.markForCheck();
});
this.streamingButtonSub = this.libService.streamingButtonObs.subscribe((value: boolean) => {
this.showStreamingButton = value;
this.checkDisplayMoreOptions();
this.cd.markForCheck();
});
this.settingsButtonSub = this.libService.toolbarSettingsButtonObs.subscribe((value: boolean) => {
this.showSettingsButton = value;
this.checkDisplayMoreOptions();
@ -737,6 +803,10 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
private checkDisplayMoreOptions() {
this.showMoreOptionsButton =
this.showFullscreenButton || this.showBackgroundEffectsButton || this.showRecordingButton || this.showSettingsButton;
this.showFullscreenButton ||
this.showBackgroundEffectsButton ||
this.showRecordingButton ||
this.showStreamingButton ||
this.showSettingsButton;
}
}

View File

@ -50,6 +50,7 @@
(onActivitiesPanelButtonClicked)="onActivitiesPanelButtonClicked()"
(onStartRecordingClicked)="onStartRecordingClicked('toolbar')"
(onStopRecordingClicked)="onStopRecordingClicked('toolbar')"
(onStopStreamingClicked)="onStopStreamingClicked('toolbar')"
>
<ng-template #toolbarAdditionalButtons>
<ng-container *ngTemplateOutlet="openviduAngularToolbarAdditionalButtonsTemplate"></ng-container>
@ -99,6 +100,8 @@
(onStartRecordingClicked)="onStartRecordingClicked('panel')"
(onStopRecordingClicked)="onStopRecordingClicked('panel')"
(onDeleteRecordingClicked)="onDeleteRecordingClicked($event)"
(onStartStreamingClicked)="onStartStreamingClicked($event)"
(onStopStreamingClicked)="onStopStreamingClicked('panel')"
></ov-activities-panel>
</ng-template>

View File

@ -383,6 +383,21 @@ export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewIni
*/
@Output() onActivitiesPanelPlayRecordingClicked: EventEmitter<string> = new EventEmitter<string>();
/**
* Provides event notifications that fire when start streaming button is clicked from {@link ActivitiesPanelComponent}.
*/
@Output() onActivitiesPanelStartStreamingClicked: EventEmitter<string> = new EventEmitter<string>();
/**
* Provides event notifications that fire when start streaming button is clicked from {@link ActivitiesPanelComponent}.
*/
@Output() onActivitiesPanelStopStreamingClicked: EventEmitter<void> = new EventEmitter<void>();
/**
* Provides event notifications that fire when start streaming button is clicked from {@link ToolbarComponent}.
*/
@Output() onToolbarStopStreamingClicked: EventEmitter<void> = new EventEmitter<void>();
/**
* Provides event notifications that fire when OpenVidu Session is created.
* See {@link https://docs.openvidu.io/en/stable/api/openvidu-browser/classes/Session.html openvidu-browser Session}.
@ -677,6 +692,35 @@ export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewIni
this.onActivitiesPanelDeleteRecordingClicked.emit(recordingId);
}
/**
* @internal
*/
onStartStreamingClicked(rtmpUrl: string) {
// if (from === 'toolbar') {
// this.onToolbarStartRecordingClicked.emit();
// } else if (from === 'panel') {
this.onActivitiesPanelStartStreamingClicked.emit(rtmpUrl);
// }
}
/**
* @internal
*/
onStopStreamingClicked(from: string) {
if (from === 'toolbar') {
this.onToolbarStopStreamingClicked.emit();
} else if (from === 'panel') {
this.onActivitiesPanelStopStreamingClicked.emit();
}
}
/**
* @internal
*/
_onSessionCreated(session: Session) {
this.onSessionCreated.emit(session);
}
/**
* @internal
*/

View File

@ -1,4 +1,4 @@
import { Directive, AfterViewInit, OnDestroy, Input, ElementRef } from '@angular/core';
import { AfterViewInit, Directive, ElementRef, Input, OnDestroy } from '@angular/core';
import { OpenViduAngularConfigService } from '../../services/config/openvidu-angular.config.service';
/**
@ -49,4 +49,55 @@ export class ActivitiesPanelRecordingActivityDirective implements AfterViewInit,
this.libService.recordingActivity.next(value);
}
}
}
}
/**
* The **streamingActivity** directive allows show/hide the streaming activity in {@link ActivitiesPanelComponent}.
*
* Default: `true`
*
* It can be used in the parent element {@link VideoconferenceComponent} specifying the name of the `activitiesPanel` component:
*
* @example
* <ov-videoconference [activitiesPanelStreamingActivity]="false"></ov-videoconference>
*
* \
* And it also can be used in the {@link ActivitiesPanelComponent}.
* @example
* <ov-activities-panel *ovActivitiesPanel [streamingActivity]="false"></ov-activities-panel>
*/
@Directive({
selector: 'ov-videoconference[activitiesPanelStreamingActivity], ov-activities-panel[streamingActivity]'
})
export class ActivitiesPanelStreamingActivityDirective implements AfterViewInit, OnDestroy {
@Input() set activitiesPanelStreamingActivity(value: boolean) {
this.streamingActivityValue = value;
this.update(this.streamingActivityValue);
}
@Input() set streamingActivity(value: boolean) {
this.streamingActivityValue = value;
this.update(this.streamingActivityValue);
}
streamingActivityValue: boolean = true;
constructor(public elementRef: ElementRef, private libService: OpenViduAngularConfigService) {}
ngAfterViewInit() {
this.update(this.streamingActivityValue);
}
ngOnDestroy(): void {
this.clear();
}
clear() {
this.streamingActivityValue = true;
this.update(true);
}
update(value: boolean) {
if (this.libService.streamingActivity.getValue() !== value) {
this.libService.streamingActivity.next(value);
}
}
}

View File

@ -1,5 +1,5 @@
import { NgModule } from '@angular/core';
import { ActivitiesPanelRecordingActivityDirective } from './activities-panel.directive';
import { ActivitiesPanelRecordingActivityDirective, ActivitiesPanelStreamingActivityDirective } from './activities-panel.directive';
import { AdminLoginDirective, AdminRecordingsListDirective } from './admin.directive';
import { LogoDirective } from './internals.directive';
import { ParticipantPanelItemMuteButtonDirective } from './participant-panel-item.directive';
@ -9,6 +9,7 @@ import {
StreamDisplayParticipantNameDirective,
StreamSettingsButtonDirective
} from './stream.directive';
import { StreamingActivityStreamingErrorDirective, StreamingActivityStreamingInfoDirective } from './streaming-activity.directive';
import {
ToolbarActivitiesPanelButtonDirective,
ToolbarBackgroundEffectsButtonDirective,
@ -21,11 +22,15 @@ import {
ToolbarParticipantsPanelButtonDirective,
ToolbarRecordingButtonDirective,
ToolbarScreenshareButtonDirective,
ToolbarSettingsButtonDirective
ToolbarSettingsButtonDirective,
ToolbarStreamingButtonDirective
} from './toolbar.directive';
import {
AudioMutedDirective,
CaptionsLangDirective, CaptionsLangOptionsDirective, LangDirective, MinimalDirective,
CaptionsLangDirective,
CaptionsLangOptionsDirective,
LangDirective,
MinimalDirective,
ParticipantNameDirective,
PrejoinDirective,
VideoMutedDirective
@ -46,6 +51,7 @@ import {
ToolbarCaptionsButtonDirective,
ToolbarLeaveButtonDirective,
ToolbarRecordingButtonDirective,
ToolbarStreamingButtonDirective,
ToolbarParticipantsPanelButtonDirective,
ToolbarChatPanelButtonDirective,
ToolbarActivitiesPanelButtonDirective,
@ -59,8 +65,11 @@ import {
ParticipantPanelItemMuteButtonDirective,
ParticipantNameDirective,
ActivitiesPanelRecordingActivityDirective,
ActivitiesPanelStreamingActivityDirective,
RecordingActivityRecordingsListDirective,
RecordingActivityRecordingErrorDirective,
StreamingActivityStreamingErrorDirective,
StreamingActivityStreamingInfoDirective,
AdminRecordingsListDirective,
AdminLoginDirective
],
@ -78,6 +87,7 @@ import {
ToolbarCaptionsButtonDirective,
ToolbarLeaveButtonDirective,
ToolbarRecordingButtonDirective,
ToolbarStreamingButtonDirective,
ToolbarParticipantsPanelButtonDirective,
ToolbarChatPanelButtonDirective,
ToolbarActivitiesPanelButtonDirective,
@ -91,8 +101,11 @@ import {
ParticipantPanelItemMuteButtonDirective,
ParticipantNameDirective,
ActivitiesPanelRecordingActivityDirective,
ActivitiesPanelStreamingActivityDirective,
RecordingActivityRecordingsListDirective,
RecordingActivityRecordingErrorDirective,
StreamingActivityStreamingErrorDirective,
StreamingActivityStreamingInfoDirective,
AdminRecordingsListDirective,
AdminLoginDirective
]

View File

@ -0,0 +1,116 @@
import { AfterViewInit, Directive, ElementRef, Input, OnDestroy } from '@angular/core';
import { StreamingError, StreamingInfo, StreamingStatus } from '../../models/streaming.model';
import { OpenViduAngularConfigService } from '../../services/config/openvidu-angular.config.service';
/**
* The **streamingError** directive allows to show any possible error with the streaming in the {@link StreamingActivityComponent}.
*
* Default: `undefined`
*
* Type: {@link StreamingError}
*
* It can be used in the parent element {@link VideoconferenceComponent} specifying the name of the `streamingActivity` component:
*
* @example
* <ov-videoconference [streamingActivityStreamingError]="error"></ov-videoconference>
*
* \
* And it also can be used in the {@link StreamingActivityComponent}.
* @example
* <ov-streaming-activity [streamingError]="error"></ov-streaming-activity>
*/
@Directive({
selector: 'ov-videoconference[streamingActivityStreamingError], ov-streaming-activity[streamingError]'
})
export class StreamingActivityStreamingErrorDirective implements AfterViewInit, OnDestroy {
@Input() set streamingActivityStreamingError(value: StreamingError) {
this.streamingErrorValue = value;
this.update(this.streamingErrorValue);
}
@Input() set streamingError(value: StreamingError) {
this.streamingErrorValue = value;
this.update(this.streamingErrorValue);
}
streamingErrorValue: StreamingError | undefined = undefined;
constructor(public elementRef: ElementRef, private libService: OpenViduAngularConfigService) {}
ngAfterViewInit() {
this.update(this.streamingErrorValue);
}
ngOnDestroy(): void {
this.clear();
}
clear() {
this.streamingErrorValue = undefined;
this.update(undefined);
}
update(value: StreamingError | undefined) {
if (this.libService.streamingError.getValue() !== value) {
this.libService.streamingError.next(value);
}
}
}
//TODO: Remove this directive when RTMP Exported was included on OV and streaming ready event was fired.
/**
* The **streamingInfo** directive allows show the live streaming info in {@link StreamingActivityComponent}.
*
* Default: `undefined`
*
* Type: {@link StreamingInfo}
*
* It can be used in the parent element {@link VideoconferenceComponent} specifying the name of the `streamingActivity` component:
*
* @example
* <ov-videoconference [streamingActivityStreamingInfo]="info"></ov-videoconference>
*
* \
* And it also can be used in the {@link StreamingActivityComponent}.
* @example
* <ov-streaming-activity [streamingInfo]="info"></ov-streaming-activity>
*/
@Directive({
selector: 'ov-videoconference[streamingActivityStreamingInfo], ov-streaming-activity[streamingInfo]'
})
export class StreamingActivityStreamingInfoDirective implements AfterViewInit, OnDestroy {
@Input() set streamingActivityStreamingInfo(value: StreamingInfo) {
this.streamingValue = value;
this.update(this.streamingValue);
}
@Input() set streamingInfo(value: StreamingInfo) {
this.streamingValue = value;
this.update(this.streamingValue);
}
streamingValue: StreamingInfo | undefined = undefined;
constructor(public elementRef: ElementRef, private libService: OpenViduAngularConfigService) {}
ngAfterViewInit() {
this.update(this.streamingValue);
}
ngOnDestroy(): void {
this.clear();
}
clear() {
this.streamingValue = undefined;
this.update(undefined);
}
update(value: StreamingInfo | undefined) {
if (this.libService.streamingInfo.getValue() !== value) {
if(value) {
// Forced value to right enum
const index = Object.values(StreamingStatus).indexOf(value.status.toLowerCase() as unknown as StreamingStatus);
const key = Object.keys(StreamingStatus)[index];
value.status = StreamingStatus[key];
}
this.libService.streamingInfo.next(value);
}
}
}

View File

@ -124,6 +124,66 @@ export class ToolbarRecordingButtonDirective implements AfterViewInit, OnDestroy
}
}
/**
* The **streamingButton** directive allows show/hide the start/stop streaming toolbar button.
*
* Default: `true`
*
* It can be used in the parent element {@link VideoconferenceComponent} specifying the name of the `toolbar` component:
*
* @example
* <ov-videoconference [toolbarStreamingButton]="false"></ov-videoconference>
*
* \
* And it also can be used in the {@link ToolbarComponent}.
* @example
* <ov-toolbar [streamingButton]="false"></ov-toolbar>
*
*/
@Directive({
selector: 'ov-videoconference[toolbarStreamingButton], ov-toolbar[streamingButton]'
})
export class ToolbarStreamingButtonDirective implements AfterViewInit, OnDestroy {
/**
* @ignore
*/
@Input() set toolbarStreamingButton(value: boolean) {
this.streamingValue = value;
this.update(this.streamingValue);
}
/**
* @ignore
*/
@Input() set streamingButton(value: boolean) {
this.streamingValue = value;
this.update(this.streamingValue);
}
private streamingValue: boolean = true;
/**
* @ignore
*/
constructor(public elementRef: ElementRef, private libService: OpenViduAngularConfigService) {}
ngAfterViewInit() {
this.update(this.streamingValue);
}
ngOnDestroy(): void {
this.clear();
}
private clear() {
this.streamingValue = true;
this.update(true);
}
private update(value: boolean) {
if (this.libService.streamingButton.getValue() !== value) {
this.libService.streamingButton.next(value);
}
}
}
/**
* The **fullscreenButton** directive allows show/hide the fullscreen toolbar button.
*

View File

@ -59,6 +59,7 @@
"CLOSE": "关闭",
"SEE_MORE": "查看更多",
"PRO_FEATURE": "此功能属于OpenVidu PRO级别",
"PAID_FEATURE": "此功能是 OpenVidu 商业服务的一部分。请通过 commercial@openvidu.io 联系我们了解更多信息。",
"CHAT": {
"TITLE": "聊天",
"YOU": "你",
@ -101,6 +102,17 @@
"DOWNLOAD": "下载",
"RECORDINGS": "录制",
"NO_MODERATOR": "只有主持人可以开始录音"
},
"STREAMING": {
"TITLE": "直播",
"SUBTITLE": "将会议直播给观众",
"CONTENT_SUBTITLE": "OpenVidu需要RTMP直播平台的URL。",
"START": "开始直播",
"STOP": "结束直播",
"URL": "输入您的RTMP URL",
"CANCEL": "取消",
"REQUIRED_URL": "RTMP url 是必需的",
"NO_MODERATOR": "只有主持人才能开始流媒体"
}
},
"ERRORS": {

View File

@ -59,6 +59,7 @@
"CLOSE": "Schließen",
"SEE_MORE": "Mehr sehen",
"PRO_FEATURE": "Diese Funktion ist Teil des OpenVidu PRO-Tiers",
"PAID_FEATURE": "Diese Funktion ist Teil des kommerziellen Dienstes von OpenVidu. Bitte kontaktieren Sie uns über commercial@openvidu.io für weitere Informationen.",
"CHAT": {
"TITLE": "Chat",
"YOU": "Sie",
@ -101,6 +102,17 @@
"DOWNLOAD": "Download",
"RECORDINGS": "AUFZEICHNUNGEN",
"NO_MODERATOR": "Nur der MODERATOR kann die Aufzeichnung starten"
},
"STREAMING": {
"TITLE": "Streaming",
"SUBTITLE": "Streamen Sie Ihr Meeting an Ihr Publikum",
"CONTENT_SUBTITLE": "OpenVidu benötigt die RTMP-URL der Streaming-Plattform.",
"START": "Streaming starten",
"STOP": "Streaming beenden",
"URL": "Geben Sie Ihre RTMP-URL ein",
"CANCEL": "Absagen",
"REQUIRED_URL": "RTMP url ist erforderlich",
"NO_MODERATOR": "Nur der Moderator kann den Stream starten"
}
},
"ERRORS": {

View File

@ -59,6 +59,7 @@
"CLOSE": "Close",
"SEE_MORE": "See more",
"PRO_FEATURE": "This feature is part of OpenVidu PRO tier",
"PAID_FEATURE": "This feature is part of OpenVidu commercial service. Please contact us through commercial@openvidu.io for more information.",
"CHAT": {
"TITLE": "Chat",
"YOU": "You",
@ -102,6 +103,17 @@
"DOWNLOAD": "Download",
"RECORDINGS": "RECORDINGS",
"NO_MODERATOR": "Only the MODERATOR can start the recording"
},
"STREAMING": {
"TITLE": "Streaming",
"SUBTITLE": "Stream your meeting to your audience",
"CONTENT_SUBTITLE": "OpenVidu need the RTMP url of the streaming platform.",
"START": "Start streaming",
"STOP": "End streaming",
"URL": "Insert your RTMP url",
"CANCEL": "Cancel",
"REQUIRED_URL": "The RTMP url is required",
"NO_MODERATOR": "Only the MODERATOR can start the streaming"
}
},
"ERRORS": {

View File

@ -59,6 +59,7 @@
"CLOSE": "Cerrar",
"SEE_MORE": "Ver más",
"PRO_FEATURE": "Esta funcionalidad es parte de OpenVidu PRO",
"PAID_FEATURE": "Esta función es parte del servicio comercial de OpenVidu. Contáctenos a través de commercial@openvidu.io para obtener más información",
"CHAT": {
"TITLE": "Chat",
"YOU": "Tú",
@ -101,6 +102,16 @@
"DOWNLOAD": "Descargar",
"RECORDINGS": "GRABACIONES",
"NO_MODERATOR": "Sólo el MODERADOR puede iniciar la grabación"
},
"STREAMING": {
"TITLE": "Streaming",
"SUBTITLE": "Comparte tu llamada con tu público",
"CONTENT_SUBTITLE": "OpenVidu necesita la url RTMP de tu plataforma de streaming.",
"START": "Empezar streaming",
"STOP": "Finalizar streaming",
"URL": "Inserta tu url RTMP",
"CANCEL": "Cancelar",
"NO_MODERATOR": "Solo el MODERADOR puede iniciar el streaming"
}
},
"ERRORS": {

View File

@ -59,6 +59,7 @@
"CLOSE": "Fermer",
"SEE_MORE": "Voir plus",
"PRO_FEATURE": "Cette fonctionnalité fait partie de la gamme OpenVidu PRO",
"PAID_FEATURE": "Cette fonctionnalité fait partie du service commercial d'OpenVidu. Veuillez nous contacter via commercial@openvidu.io pour plus d'informations.",
"CHAT": {
"TITLE": "Chat",
"YOU": "Vous",
@ -101,6 +102,17 @@
"DOWNLOAD": "Télécharger",
"RECORDINGS": "ENREGISTREMENTS",
"NO_MODERATOR": "Seul le MODERATEUR peut lancer l'enregistrement"
},
"STREAMING": {
"TITLE": "Diffusion",
"SUBTITLE": "Diffusez votre réunion à votre public",
"CONTENT_SUBTITLE": "OpenVidu a besoin de l'URL RTMP de la plateforme de diffusion.",
"START": "Démarrer la diffusion",
"STOP": "Arrêter la diffusion",
"URL": "Insérez votre URL RTMP",
"CANCEL": "Annuler",
"REQUIRED_URL": "L'URL RTMP est obligatoire",
"NO_MODERATOR": "Seul le MODÉRATEUR peut démarrer le streaming"
}
},
"ERRORS": {

View File

@ -59,6 +59,7 @@
"CLOSE": "बंद करें",
"SEE_MORE": "और देखें",
"PRO_FEATURE": "यह सुविधा OpenVidu PRO टायर का हिस्सा है",
"PAID_FEATURE": "यह सुविधा OpenVidu वाणिज्यिक सेवा का हिस्सा है। अधिक जानकारी के लिए कृपया commercial@openvidu.io के माध्यम से हमसे संपर्क करें।",
"CHAT": {
"TITLE": "बातचीत",
"YOU": "आप",
@ -101,6 +102,17 @@
"DOWNLOAD": "डाउनलोड",
"RECORDINGS": "रिकॉर्डिंग",
"NO_MODERATOR": "केवल मॉडरेटर ही रिकॉर्डिंग शुरू कर सकता है"
},
"STREAMING": {
"TITLE": "स्ट्रीमिंग",
"SUBTITLE": "अपने मीटिंग को अपने दर्शकों को स्ट्रीम करें",
"CONTENT_SUBTITLE": "OpenVidu को स्ट्रीमिंग प्लेटफॉर्म के RTMP url की आवश्यकता है।",
"START": "स्ट्रीमिंग शुरू करें",
"STOP": "स्ट्रीमिंग बंद करें",
"URL": "अपना RTMP url दर्ज करें",
"CANCEL": "रद्द करें",
"REQUIRED_URL": "RTMP url आवश्यक है",
"NO_MODERATOR": "केवल मोडेरेटर स्ट्रीमिंग शुरू कर सकते हैं"
}
},
"ERRORS": {

View File

@ -59,6 +59,7 @@
"CLOSE": "Chiudi",
"SEE_MORE": "Vedi di più",
"PRO_FEATURE": "Questa funzione fa parte del livello OpenVidu PRO",
"PAID_FEATURE": "Questa funzione fa parte del servizio commerciale di OpenVidu. Per ulteriori informazioni, contattaci tramite commercial@openvidu.io.",
"CHAT": {
"TITLE": "Chat",
"YOU": "Tu",
@ -101,6 +102,17 @@
"DOWNLOAD": "Scarica",
"RECORDINGS": "REGISTRAZIONI",
"NO_MODERATOR": "Solo il MODERATORE può avviare la registrazione"
},
"STREAMING": {
"TITLE": "Streaming",
"SUBTITLE": "Trasmetti in streaming la tua riunione al tuo pubblico",
"CONTENT_SUBTITLE": "OpenVidu necessita dell'URL RTMP della piattaforma di streaming",
"START": "Avvia lo streaming",
"STOP": "Termina lo streaming",
"URL": "Inserisci il tuo URL RTMP",
"CANCEL": "Annulla",
"REQUIRED_URL": "RTMP url è obbligatorio",
"NO_MODERATOR": "Solo il MODERATORE può iniziare la trasmissione"
}
},
"ERRORS": {

View File

@ -59,6 +59,7 @@
"CLOSE": "閉じる",
"PRO_FEATURE": "この機能はOpenVidu PROの機能です",
"SEE_MORE": "もっと見る",
"PAID_FEATURE": "この機能は、OpenVidu 商用サービスの一部です。詳細については、commercial@openvidu.io までお問い合わせください。",
"CHAT": {
"TITLE": "チャット",
"YOU": "あなた",
@ -101,6 +102,18 @@
"DOWNLOAD": "ダウンロード",
"RECORDINGS": "録画",
"NO_MODERATOR": "録音を開始できるのは、モデレーターのみです"
},
"STREAMING": {
"TITLE": "ストリーミング",
"SUBTITLE": "会議を観客にストリーミング",
"CONTENT_SUBTITLE": "OpenViduはストリーミングプラットフォームのRTMP URLが必要です。",
"START": "ストリーミングを開始",
"STOP": "ストリーミングを終了",
"URL": "RTMP URLを入力してください",
"CANCEL": "キャンセル",
"REQUIRED_URL": "RTMP url は必須です",
"NO_MODERATOR": "モデレーターのみがストリーミングを開始できます"
}
},
"ERRORS": {

View File

@ -59,6 +59,7 @@
"CLOSE": "Sluiten",
"SEE_MORE": "Zie meer",
"PRO_FEATURE": "Deze functie is onderdeel van OpenVidu PRO tier",
"PAID_FEATURE": "Deze functie maakt deel uit van de commerciële service van OpenVidu. Neem voor meer informatie contact met ons op via commercial@openvidu.io.",
"CHAT": {
"TITLE": "Chat",
"YOU": "Jij",
@ -101,6 +102,17 @@
"DOWNLOAD": "Downloaden",
"RECORDINGS": "OPNAME",
"NO_MODERATOR": "Alleen de MOEDERATOR kan de opname starten"
},
"STREAMING": {
"TITLE": "Streamen",
"SUBTITLE": "Stream je meeting naar je publiek",
"CONTENT_SUBTITLE": "OpenVidu heeft de RTMP url van de streaming platform nodig.",
"START": "Start streamen",
"STOP": "Stop streamen",
"URL": "Voer je RTMP url in",
"CANCEL": "Annuleren",
"REQUIRED_URL": "RTMP url is verplicht",
"NO_MODERATOR": "Alleen de moderator kan de streaming starten"
}
},
"ERRORS": {

View File

@ -59,6 +59,7 @@
"CLOSE": "Fechar",
"SEE_MORE": "Ver mais",
"PRO_FEATURE": "Esta funcionalidade é parte do OpenVidu PRO tier",
"PAID_FEATURE": "Este recurso faz parte do serviço comercial OpenVidu. Entre em contato conosco através de commercial@openvidu.io para obter mais informações.",
"CHAT": {
"TITLE": "Chat",
"YOU": "Você",
@ -101,6 +102,17 @@
"DOWNLOAD": "Download",
"RECORDINGS": "GRAVAÇÕES",
"NO_MODERATOR": "Só o MODERADOR pode iniciar a gravação"
},
"STREAMING": {
"TITLE": "Streaming",
"SUBTITLE": "Transmita sua reunião para seu público",
"CONTENT_SUBTITLE": "O OpenVidu precisa da url RTMP da plataforma de streaming.",
"START": "Iniciar transmissão",
"STOP": "Finalizar transmissão",
"URL": "Insira seu url RTMP",
"CANCEL": "Cancelar",
"REQUIRED_URL": "O URL RTMP é obrigatório",
"NO_MODERATOR": "Somente o MODERADOR pode iniciar a transmissão"
}
},
"ERRORS": {

View File

@ -7,6 +7,13 @@ export enum PanelType {
}
export interface PanelEvent {
opened: boolean;
type?: PanelType | string;
expand?: string;
oldType?: PanelType | string;
}
export enum PanelSettingsOptions {
GENERAL = 'general',
AUDIO = 'audio',

View File

@ -3,6 +3,9 @@
*/
export enum Signal {
NICKNAME_CHANGED = 'nicknameChanged',
CHAT = 'chat'
CHAT = 'chat',
//TODO: Remove them when RTMP Exported was included on OV and streaming ready event was fired.
STREAMING_STARTED = "streamingStarted",
STREAMING_STOPPED = "streamingStopped"
}

View File

@ -0,0 +1,20 @@
export enum StreamingStatus {
STARTING = 'starting',
STARTED = 'started',
STOPPING = 'stopping',
STOPPED = 'stopped',
FAILED = 'failed'
}
export interface StreamingInfo {
id: string,
status: StreamingStatus
}
export interface StreamingError {
message: string,
// If streaming service is available or not
rtmpAvailable: boolean
}

View File

@ -50,6 +50,7 @@ import { CaptionsComponent } from './components/captions/captions.component';
import { ProFeatureDialogTemplateComponent } from './components/dialogs/pro-feature-dialog.component';
import { ActivitiesPanelComponent } from './components/panel/activities-panel/activities-panel.component';
import { RecordingActivityComponent } from './components/panel/activities-panel/recording-activity/recording-activity.component';
import { StreamingActivityComponent } from './components/panel/activities-panel/streaming-activity/streaming-activity.component';
import { BackgroundEffectsPanelComponent } from './components/panel/background-effects-panel/background-effects-panel.component';
import { SettingsPanelComponent } from './components/panel/settings-panel/settings-panel.component';
import { AudioDevicesComponent } from './components/settings/audio-devices/audio-devices.component';
@ -69,6 +70,8 @@ const publicComponents = [
ToolbarComponent,
PanelComponent,
ActivitiesPanelComponent,
RecordingActivityComponent,
StreamingActivityComponent,
ParticipantsPanelComponent,
ParticipantPanelItemComponent,
ChatPanelComponent,
@ -92,7 +95,6 @@ const privateComponents = [
AudioDevicesComponent,
NicknameInputComponent,
LangSelectorComponent,
RecordingActivityComponent,
CaptionsSettingComponent
];

View File

@ -9,8 +9,13 @@ export class TranslatePipe implements PipeTransform {
constructor(private translateService: TranslateService) {}
transform(str: string): string {
const translation = this.translateService.translate(str);
return translation.replace('OpenVidu PRO', '<a href="https://docs.openvidu.io/en/stable/openvidu-pro/" target="_blank">OpenVidu PRO</a>');
if (translation?.includes('OpenVidu PRO')) {
return translation.replace(
'OpenVidu PRO',
'<a href="https://docs.openvidu.io/en/stable/openvidu-pro/" target="_blank">OpenVidu PRO</a>'
);
}
return translation;
}
}

View File

@ -2,6 +2,7 @@ import { Inject, Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { OpenViduAngularConfig, ParticipantFactoryFunction } from '../../config/openvidu-angular.config';
import { RecordingInfo } from '../../models/recording.model';
import { StreamingError, StreamingInfo } from '../../models/streaming.model';
// import { version } from '../../../../package.json';
@ -61,15 +62,25 @@ export class OpenViduAngularConfigService {
participantItemMuteButtonObs: Observable<boolean>;
backgroundEffectsButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
backgroundEffectsButtonObs: Observable<boolean>;
recordingsList = <BehaviorSubject<RecordingInfo[]>>new BehaviorSubject([]);
recordingsList: BehaviorSubject<RecordingInfo[]> = new BehaviorSubject(<RecordingInfo[]>[]);
recordingsListObs: Observable<RecordingInfo[]>;
recordingButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
recordingButtonObs: Observable<boolean>;
streamingButton = <BehaviorSubject<boolean>>new BehaviorSubject(true);
streamingButtonObs: Observable<boolean>;
recordingActivity = <BehaviorSubject<boolean>>new BehaviorSubject(true);
recordingActivityObs: Observable<boolean>;
streamingActivity = <BehaviorSubject<boolean>>new BehaviorSubject(true);
streamingActivityObs: Observable<boolean>;
recordingError = <BehaviorSubject<any>>new BehaviorSubject(null);
recordingErrorObs: Observable<any>;
adminRecordingsList = <BehaviorSubject<RecordingInfo[]>>new BehaviorSubject([]);
streamingErrorObs: Observable<StreamingError | undefined>;
streamingError = <BehaviorSubject<StreamingError | undefined>>new BehaviorSubject(undefined);
streamingInfo = <BehaviorSubject<StreamingInfo | undefined>>new BehaviorSubject(undefined);
//TODO: Remove this directive when RTMP Exported was included on OV and streaming ready event was fired.
streamingInfoObs: Observable<StreamingInfo | undefined>;
adminRecordingsList: BehaviorSubject<RecordingInfo[]> = new BehaviorSubject(<RecordingInfo[]>[]);
adminRecordingsListObs: Observable<RecordingInfo[]>;
adminLoginError = <BehaviorSubject<any>>new BehaviorSubject(null);
adminLoginErrorObs: Observable<any>;
@ -94,6 +105,7 @@ export class OpenViduAngularConfigService {
this.displaySessionNameObs = this.displaySessionName.asObservable();
this.displayLogoObs = this.displayLogo.asObservable();
this.recordingButtonObs = this.recordingButton.asObservable();
this.streamingButtonObs = this.streamingButton.asObservable();
this.toolbarSettingsButtonObs = this.toolbarSettingsButton.asObservable();
this.captionsButtonObs = this.captionsButton.asObservable();
//Stream observables
@ -106,6 +118,10 @@ export class OpenViduAngularConfigService {
this.recordingActivityObs = this.recordingActivity.asObservable();
this.recordingsListObs = this.recordingsList.asObservable();
this.recordingErrorObs = this.recordingError.asObservable();
// Streaming activity
this.streamingActivityObs = this.streamingActivity.asObservable();
this.streamingErrorObs = this.streamingError.asObservable();
this.streamingInfoObs = this.streamingInfo.asObservable();
// Admin dashboard
this.adminRecordingsListObs = this.adminRecordingsList.asObservable();
this.adminLoginErrorObs = this.adminLoginError.asObservable();
@ -125,4 +141,12 @@ export class OpenViduAngularConfigService {
getParticipantFactory(): ParticipantFactoryFunction {
return this.getConfig().participantFactory;
}
isRecordingEnabled(): boolean {
return this.recordingButton.getValue() && this.recordingActivity.getValue();
}
isStreamingEnabled(): boolean {
return this.streamingButton.getValue() && this.streamingActivity.getValue();
}
}

View File

@ -1,16 +1,9 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { ILogger } from '../../models/logger.model';
import { PanelSettingsOptions, PanelType } from '../../models/panel.model';
import { PanelEvent, PanelSettingsOptions, PanelType } from '../../models/panel.model';
import { LoggerService } from '../logger/logger.service';
export interface PanelEvent {
opened: boolean;
type?: PanelType | string;
expand?: string;
oldType?: PanelType | string;
}
@Injectable({
providedIn: 'root'
})

View File

@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { Publisher, Subscriber } from 'openvidu-browser';
import { BehaviorSubject, Observable } from 'rxjs';
import { ILogger } from '../../models/logger.model';
import { ParticipantAbstractModel, ParticipantModel, ParticipantProperties, StreamModel } from '../../models/participant.model';
import { OpenViduRole, ParticipantAbstractModel, ParticipantModel, ParticipantProperties, StreamModel } from '../../models/participant.model';
import { VideoType } from '../../models/video-type.model';
import { OpenViduAngularConfigService } from '../config/openvidu-angular.config.service';
import { LoggerService } from '../logger/logger.service';
@ -156,6 +156,10 @@ export class ParticipantService {
return this.localParticipant.getRole();
}
amIModerator(): boolean {
return this.getMyRole() === OpenViduRole.MODERATOR;
}
/**
* @internal
*/

View File

@ -14,10 +14,10 @@ export class RecordingService {
*/
recordingStatusObs: Observable<{ info: RecordingInfo; time?: Date }>;
private recordingTime: Date;
private recordingTime: Date | undefined;
private recordingTimeInterval: NodeJS.Timer;
private currentRecording: RecordingInfo = { status: RecordingStatus.STOPPED };
private recordingStatus = <BehaviorSubject<{ info: RecordingInfo; time?: Date }>>new BehaviorSubject(null);
private recordingStatus = <BehaviorSubject<{ info: RecordingInfo; time?: Date | undefined } | undefined>>new BehaviorSubject(undefined);
private baseUrl = '/' + (!!window.location.pathname.split('/')[1] ? window.location.pathname.split('/')[1] + '/' : '');
@ -63,7 +63,7 @@ export class RecordingService {
stopRecording(event: RecordingEvent) {
this.currentRecording.status = RecordingStatus.STOPPED;
this.currentRecording.reason = event.reason;
this.recordingStatus.next({ info: this.currentRecording, time: null });
this.recordingStatus.next({ info: this.currentRecording, time: undefined });
this.stopRecordingTime();
}
@ -117,6 +117,6 @@ export class RecordingService {
private stopRecordingTime() {
clearInterval(this.recordingTimeInterval);
this.recordingTime = null;
this.recordingTime = undefined;
}
}

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { StreamingService } from './streaming.service';
describe('StreamingService', () => {
let service: StreamingService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(StreamingService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,49 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { StreamingStatus } from '../../models/streaming.model';
@Injectable({
providedIn: 'root'
})
export class StreamingService {
/**
* Streaming status Observable which pushes the streaming state in every update.
*/
streamingStatusObs: Observable<{ status: StreamingStatus; time?: Date } | undefined>;
private streamingTime: Date | undefined;
private streamingTimeInterval: NodeJS.Timer;
private streamingStatus = <BehaviorSubject<{ status: StreamingStatus; time?: Date } | undefined>>new BehaviorSubject(undefined);
constructor() {
this.streamingStatusObs = this.streamingStatus.asObservable();
}
/**
* @internal
* @param status
*/
updateStatus(status: StreamingStatus) {
if (status === StreamingStatus.STARTED) {
this.startStreamingTime();
} else {
this.stopStreamingTime();
}
this.streamingStatus.next({ status, time: this.streamingTime });
}
private startStreamingTime() {
this.streamingTime = new Date();
this.streamingTime.setHours(0, 0, 0, 0);
this.streamingTimeInterval = setInterval(() => {
this.streamingTime?.setSeconds(this.streamingTime.getSeconds() + 1);
this.streamingTime = new Date(this.streamingTime.getTime());
this.streamingStatus.next({ status: this.streamingStatus.getValue()?.status, time: this.streamingTime });
}, 1000);
}
private stopStreamingTime() {
clearInterval(this.streamingTimeInterval);
this.streamingTime = undefined;
}
}

View File

@ -55,7 +55,7 @@ export class TranslateService {
return this.langTitles;
}
translate(key: string) {
translate(key: string): string {
let result = this.currentLang;
key.split('.').forEach((prop) => {

View File

@ -2,53 +2,53 @@
* Public API Surface of openvidu-components-angular
*/
export * from './lib/openvidu-angular.module';
// Services
export * from './lib/services/openvidu/openvidu.service';
export * from './lib/services/participant/participant.service';
export * from './lib/services/chat/chat.service';
export * from './lib/services/action/action.service';
export * from './lib/services/layout/layout.service';
export * from './lib/services/panel/panel.service';
export * from './lib/services/recording/recording.service';
// Components
export * from './lib/components/videoconference/videoconference.component';
export * from './lib/components/toolbar/toolbar.component';
export * from './lib/components/panel/panel.component';
export * from './lib/components/panel/activities-panel/activities-panel.component';
export * from './lib/components/panel/chat-panel/chat-panel.component';
export * from './lib/components/panel/participants-panel/participants-panel/participants-panel.component';
export * from './lib/components/panel/participants-panel/participant-panel-item/participant-panel-item.component';
export * from './lib/components/layout/layout.component';
export * from './lib/components/stream/stream.component';
export * from './lib/admin/dashboard/dashboard.component';
export * from './lib/admin/login/login.component';
// Models
export * from './lib/models/participant.model';
export * from './lib/components/layout/layout.component';
export * from './lib/components/panel/activities-panel/activities-panel.component';
export * from './lib/components/panel/activities-panel/recording-activity/recording-activity.component';
export * from './lib/components/panel/activities-panel/streaming-activity/streaming-activity.component';
export * from './lib/components/panel/chat-panel/chat-panel.component';
export * from './lib/components/panel/panel.component';
export * from './lib/components/panel/participants-panel/participant-panel-item/participant-panel-item.component';
export * from './lib/components/panel/participants-panel/participants-panel/participants-panel.component';
export * from './lib/components/stream/stream.component';
export * from './lib/components/toolbar/toolbar.component';
export * from './lib/components/videoconference/videoconference.component';
export * from './lib/config/openvidu-angular.config';
export * from './lib/models/video-type.model';
export * from './lib/models/token.model';
export * from './lib/models/signal.model';
// Directives
export * from './lib/directives/api/activities-panel.directive';
export * from './lib/directives/api/admin.directive';
export * from './lib/directives/api/api.directive.module';
export * from './lib/directives/api/internals.directive';
export * from './lib/directives/api/participant-panel-item.directive';
export * from './lib/directives/api/recording-activity.directive';
export * from './lib/directives/api/stream.directive';
export * from './lib/directives/api/streaming-activity.directive';
export * from './lib/directives/api/toolbar.directive';
export * from './lib/directives/api/videoconference.directive';
export * from './lib/directives/template/openvidu-angular.directive';
export * from './lib/directives/template/openvidu-angular.directive.module';
// Models
export * from './lib/models/panel.model';
export * from './lib/models/participant.model';
export * from './lib/models/recording.model';
export * from './lib/models/signal.model';
export * from './lib/models/streaming.model';
export * from './lib/models/token.model';
export * from './lib/models/video-type.model';
export * from './lib/openvidu-angular.module';
// Pipes
export * from './lib/pipes/participant.pipe';
export * from './lib/pipes/recording.pipe';
// Services
export * from './lib/services/action/action.service';
export * from './lib/services/chat/chat.service';
export * from './lib/services/layout/layout.service';
export * from './lib/services/openvidu/openvidu.service';
export * from './lib/services/panel/panel.service';
export * from './lib/services/participant/participant.service';
export * from './lib/services/recording/recording.service';
export * from './lib/services/streaming/streaming.service';
// Directives
export * from './lib/directives/template/openvidu-angular.directive.module';
export * from './lib/directives/template/openvidu-angular.directive';
export * from './lib/directives/api/api.directive.module';
export * from './lib/directives/api/internals.directive';
export * from './lib/directives/api/toolbar.directive';
export * from './lib/directives/api/stream.directive';
export * from './lib/directives/api/videoconference.directive';
export * from './lib/directives/api/participant-panel-item.directive';
export * from './lib/directives/api/activities-panel.directive';
export * from './lib/directives/api/recording-activity.directive';
export * from './lib/directives/api/admin.directive';

View File

@ -19,8 +19,8 @@ export class AdminDashboardComponent implements OnInit {
this.logged = true;
this.recordings = resp.recordings;
} catch (error) {
this.logged = true;
this.logged = false;
console.log(error);
}
}

View File

@ -1,5 +1,34 @@
<ov-videoconference
[tokens]="tokens"
[minimal]="false"
[lang]="'en'"
[captionsLang]=""
[captionsLangOptions]="[{ name: 'Spanish', ISO: 'es-ES' },{ name: 'English', ISO: 'en-US' }]"
[prejoin]="false"
[participantName]="'Participant'"
[videoMuted]="false"
[audioMuted]="false"
[toolbarScreenshareButton]="true"
[toolbarFullscreenButton]="true"
[toolbarCaptionsButton]="true"
[toolbarRecordingButton]="true"
[toolbarStreamingButton]="true"
[toolbarBackgroundEffectsButton]="true"
[toolbarLeaveButton]="true"
[toolbarChatPanelButton]="true"
[toolbarParticipantsPanelButton]="true"
[toolbarDisplayLogo]="true"
[toolbarDisplaySessionName]="true"
[streamDisplayParticipantName]="true"
[streamDisplayAudioDetection]="true"
[streamSettingsButton]="true"
[participantPanelItemMuteButton]="true"
[activitiesPanelRecordingActivity]="true"
[recordingActivityRecordingsList]="recordingList"
[recordingActivityRecordingError]="recordingError"
[streamingActivityStreamingError]="streamingError"
[streamingActivityStreamingInfo]="streamingInfo"
[toolbarSettingsButton]="true"
(onJoinButtonClicked)="onJoinClicked()"
(onToolbarLeaveButtonClicked)="onToolbarLeaveButtonClicked()"
(onToolbarCameraButtonClicked)="onToolbarCameraButtonClicked()"
@ -13,18 +42,14 @@
(onActivitiesPanelStartRecordingClicked)="onStartRecordingClicked()"
(onActivitiesPanelStopRecordingClicked)="onStopRecordingClicked()"
(onActivitiesPanelDeleteRecordingClicked)="onDeleteRecordingClicked($event)"
(onActivitiesPanelStartStreamingClicked)="onStartStreamingClicked($event)"
(onActivitiesPanelStopStreamingClicked)="onStopStreamingClicked()"
(onToolbarStopStreamingClicked)="onStopStreamingClicked()"
(onNodeCrashed)="onNodeCrashed()"
[minimal]="false"
[prejoin]="false"
[videoMuted]="false"
[audioMuted]="false"
[activitiesPanelRecordingActivity]="true"
[recordingActivityRecordingsList]="recordingList"
[recordingActivityRecordingError]="recordingError"
[toolbarSettingsButton]="true"
>
<!-- <div *ovActivitiesPanel>HELLO</div> -->
<!-- <ov-toolbar *ovToolbar [activitiesPanelButton]="false"
[chatPanelButton]="false"></ov-toolbar> -->
</ov-videoconference>

View File

@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { RecordingInfo, RecordingService, TokenModel } from 'openvidu-angular';
import { RecordingInfo, TokenModel } from 'openvidu-angular';
import { RestService } from '../services/rest.service';
@Component({
@ -8,7 +8,6 @@ import { RestService } from '../services/rest.service';
styleUrls: ['./call.component.scss']
})
export class CallComponent implements OnInit {
sessionId = 'daily-call';
tokens: TokenModel;
@ -17,8 +16,10 @@ export class CallComponent implements OnInit {
isSessionAlive: boolean = false;
recordingList: RecordingInfo[] = [];
recordingError: any;
streamingError: any;
streamingInfo: any;
constructor(private restService: RestService, private recordingService: RecordingService) { }
constructor(private restService: RestService) {}
async ngOnInit() {
await this.requestForTokens();
@ -81,25 +82,45 @@ export class CallComponent implements OnInit {
console.log('TOOLBAR LEAVE CLICKED');
}
async onStartStreamingClicked(rtmpUrl: string) {
console.log('START STREAMING', rtmpUrl);
try {
this.streamingError = null;
this.streamingInfo = await this.restService.startStreaming(rtmpUrl);
console.log('Streaming response ', this.streamingInfo);
} catch (error) {
console.error(error);
this.streamingError = error.error;
}
}
async onStopStreamingClicked() {
console.log('STOP STREAMING');
try {
this.streamingError = null;
this.streamingInfo = await this.restService.stopStreaming();
console.log('Streaming response ', this.streamingInfo);
} catch (error) {
console.error(error);
this.streamingError = error.message || error;
}
}
async onStartRecordingClicked() {
console.warn('START RECORDING CLICKED');
try {
await this.restService.startRecording(this.sessionId);
} catch (error) {
this.recordingError = error;
}
}
async onStopRecordingClicked() {
console.warn('STOP RECORDING CLICKED');
try {
// await this.restService.startRecording(this.sessionId);
this.recordingList = await this.restService.stopRecording(this.sessionId);
console.log('RECORDING LIST ', this.recordingList);
} catch (error) {
this.recordingError = error;
}
}
@ -108,7 +129,6 @@ export class CallComponent implements OnInit {
try {
this.recordingList = await this.restService.deleteRecording(recordingId);
} catch (error) {
this.recordingError = error;
}
@ -123,7 +143,5 @@ export class CallComponent implements OnInit {
};
console.log('Token requested: ', this.tokens);
}
}

View File

@ -11,6 +11,7 @@
[audioMuted]="_audioMuted"
[toolbarScreenshareButton]="_toolbarScreenshareButton"
[toolbarRecordingButton]="_toolbarRecordingButton"
[toolbarStreamingButton]="_toolbarStreamingButton"
[toolbarFullscreenButton]="_toolbarFullscreenButton"
[toolbarBackgroundEffectsButton]="_toolbarBackgroundEffectsButton"
[toolbarSettingsButton]="_toolbarSettingsButton"
@ -26,8 +27,11 @@
[streamSettingsButton]="_streamSettingsButton"
[participantPanelItemMuteButton]="_participantPanelItemMuteButton"
[activitiesPanelRecordingActivity]="_activitiesPanelRecordingActivity"
[activitiesPanelStreamingActivity]="_activitiesPanelStreamingActivity"
[recordingActivityRecordingsList]="_recordingActivityRecordingsList"
[recordingActivityRecordingError]="_recordingActivityRecordingError"
[streamingActivityStreamingInfo]="_streamingActivityStreamingInfo"
[streamingActivityStreamingError]="_streamingActivityStreamingError"
(onJoinButtonClicked)="_onJoinButtonClicked()"
(onToolbarLeaveButtonClicked)="_onToolbarLeaveButtonClicked()"
(onToolbarCameraButtonClicked)="_onToolbarCameraButtonClicked()"
@ -39,10 +43,13 @@
(onToolbarFullscreenButtonClicked)="_onToolbarFullscreenButtonClicked()"
(onToolbarStartRecordingClicked)="onStartRecordingClicked('toolbar')"
(onToolbarStopRecordingClicked)="onStopRecordingClicked('toolbar')"
(onToolbarStopStreamingClicked)="onStopStreamingClicked('toolbar')"
(onActivitiesPanelStartRecordingClicked)="onStartRecordingClicked('panel')"
(onActivitiesPanelStopRecordingClicked)="onStopRecordingClicked('panel')"
(onActivitiesPanelDownloadRecordingClicked)="_onActivitiesDownloadRecordingClicked($event)"
(onActivitiesPanelDeleteRecordingClicked)="_onActivitiesDeleteRecordingClicked($event)"
(onActivitiesPanelStartStreamingClicked)="onStartStreamingClicked($event)"
(onActivitiesPanelStopStreamingClicked)="onStopStreamingClicked('panel')"
(onSessionCreated)="_onSessionCreated($event)"
(onParticipantCreated)="_onParticipantCreated($event)"
></ov-videoconference>

View File

@ -1,5 +1,5 @@
@use '@angular/material' as mat;
@import '~@angular/material/theming';
@import '@angular/material/theming';
// Plus imports for other components in your app.

View File

@ -2,6 +2,7 @@ import { Component, ElementRef, EventEmitter, Input, OnInit, Output } from '@ang
import { OpenViduService, ParticipantAbstractModel, RecordingInfo, TokenModel } from 'openvidu-angular';
import { Session } from 'openvidu-browser';
import { CaptionsLangOption } from '../../../projects/openvidu-angular/src/lib/models/caption.model';
import { StreamingInfo } from '../../../projects/openvidu-angular/src/lib/models/streaming.model';
/**
*
@ -63,6 +64,10 @@ export class OpenviduWebComponentComponent implements OnInit {
* @internal
*/
_toolbarRecordingButton: boolean = true;
/**
* @internal
*/
_toolbarStreamingButton: boolean = true;
/**
* @internal
*/
@ -127,12 +132,27 @@ export class OpenviduWebComponentComponent implements OnInit {
* @internal
*/
_activitiesPanelRecordingActivity: boolean = true;
/**
* @internal
*/
_activitiesPanelStreamingActivity: boolean = true;
/**
* @internal
*/
_recordingActivityRecordingsList: RecordingInfo[] = [];
/**
* @internal
* TODO: Remove this directive when RTMP Exported was included on OV and streaming ready event was fired.
*/
_streamingActivityStreamingInfo: StreamingInfo;
/**
* @internal
*/
_streamingActivityStreamingError: any;
/**
* The **minimal** attribute applies a minimal UI hiding all controls except for cam and mic.
*
@ -189,7 +209,7 @@ export class OpenviduWebComponentComponent implements OnInit {
* @example
* <openvidu-webcomponent captions-lang-options="[{name:'Spanish', ISO: 'es-ES'}]"></openvidu-webcomponent>
*/
@Input() set captionsLangOptions(value: string | CaptionsLangOption []) {
@Input() set captionsLangOptions(value: string | CaptionsLangOption[]) {
this._captionsLangOptions = this.castToArray(value);
}
/**
@ -273,6 +293,21 @@ export class OpenviduWebComponentComponent implements OnInit {
@Input() set toolbarRecordingButton(value: string | boolean) {
this._toolbarRecordingButton = this.castToBoolean(value);
}
/**
* The **toolbarStreamingButton** attribute allows show/hide the start/stop streaming toolbar button.
*
* Default: `true`
*
* <div class="warn-container">
* <span>WARNING</span>: If you want to use this parameter to OpenVidu Web Component statically, you have to replace the <strong>camelCase</strong> with a <strong>hyphen between words</strong>.</div>
*
* @example
* <openvidu-webcomponent toolbar-streaming-button="false"></openvidu-webcomponent>
*/
@Input() set toolbarStreamingButton(value: string | boolean) {
this._toolbarStreamingButton = this.castToBoolean(value);
}
/**
* The **toolbarFullscreenButton** attribute allows show/hide the fullscreen toolbar button.
*
@ -497,6 +532,43 @@ export class OpenviduWebComponentComponent implements OnInit {
this._activitiesPanelRecordingActivity = this.castToBoolean(value);
}
/**
* The **activitiesPanelStreamingActivity** attribute allows show/hide the streaming activity in {@link ActivitiesPanelComponent}.
*
* Default: `true`
*
* @example
* <openvidu-webcomponent activity-panel-streaming-activity="false"></openvidu-webcomponent>
*/
@Input() set activitiesPanelStreamingActivity(value: string | boolean) {
this._activitiesPanelStreamingActivity = this.castToBoolean(value);
}
/**
* The **streamingActivityStreamingInfo** attribute allows show to show the live streaming info in {@link StreamingActivityComponent}.
*
* Default: `undefined`
*
* @example
* <openvidu-webcomponent streaming-activity-streaming-info="streamingInfo"></openvidu-webcomponent>
*/
@Input() set streamingActivityStreamingInfo(value: StreamingInfo) {
this._streamingActivityStreamingInfo = value;
}
/**
* The **streamingActivityStreamingError** attribute allows to show any possible error with the streaming in the {@link StreamingActivityComponent}.
*
* Default: `undefined`
*
* @example
* <openvidu-webcomponent streaming-activity-streaming-error="streamingError"></openvidu-webcomponent>
*/
@Input() set streamingActivityStreamingError(value: any) {
this._streamingActivityStreamingError = value;
}
/**
* The **recordingActivityRecordingList** attribute allows show to show the recordings available for the session in {@link RecordingActivityComponent}.
*
@ -554,6 +626,10 @@ export class OpenviduWebComponentComponent implements OnInit {
*/
@Output() onToolbarActivitiesPanelButtonClicked: EventEmitter<void> = new EventEmitter<void>();
/**
* Provides event notifications that fire when start recording button is clicked from {@link ToolbarComponent}.
* The recording should be started using the REST API.
*/
@Output() onToolbarStartRecordingClicked: EventEmitter<void> = new EventEmitter<void>();
/**
* Provides event notifications that fire when stop recording button is clicked from {@link ToolbarComponent}.
@ -562,9 +638,15 @@ export class OpenviduWebComponentComponent implements OnInit {
@Output() onToolbarStopRecordingClicked: EventEmitter<void> = new EventEmitter<void>();
/**
* Provides event notifications that fire when start recording button is clicked {@link ActivitiesPanelComponent}.
* Provides event notifications that fire when stop streaming button is clicked from {@link ToolbarComponent}.
* The recording should be stopped using the REST API.
*/
@Output() onToolbarStopStreamingClicked: EventEmitter<void> = new EventEmitter<void>();
/**
* Provides event notifications that fire when start recording button is clicked {@link ActivitiesPanelComponent}.
* The recording should be started using the REST API.
*/
@Output() onActivitiesPanelStartRecordingClicked: EventEmitter<void> = new EventEmitter<void>();
/**
* Provides event notifications that fire when stop recording button is clicked from {@link ActivitiesPanelComponent}.
@ -584,6 +666,18 @@ export class OpenviduWebComponentComponent implements OnInit {
*/
@Output() onActivitiesPanelDeleteRecordingClicked: EventEmitter<string> = new EventEmitter<string>();
/**
* Provides event notifications that fire when start streaming button is clicked {@link ActivitiesPanelComponent}.
* The streaming should be started using the REST API.
*/
@Output() onActivitiesPanelStartStreamingClicked: EventEmitter<string> = new EventEmitter<string>();
/**
* Provides event notifications that fire when stop streaming button is clicked {@link ActivitiesPanelComponent}.
* The streaming should be stopped using the REST API.
*/
@Output() onActivitiesPanelStopStreamingClicked: EventEmitter<void> = new EventEmitter<void>();
/**
* Provides event notifications that fire when OpenVidu Session is created.
* See {@link https://docs.openvidu.io/en/stable/api/openvidu-browser/classes/Session.html openvidu-browser Session}.
@ -698,6 +792,9 @@ export class OpenviduWebComponentComponent implements OnInit {
_onToolbarFullscreenButtonClicked() {
this.onToolbarFullscreenButtonClicked.emit();
}
/**
* @internal
*/
onStartRecordingClicked(from: string) {
if (from === 'toolbar') {
this.onToolbarStartRecordingClicked.emit();
@ -731,6 +828,28 @@ export class OpenviduWebComponentComponent implements OnInit {
this.onActivitiesPanelDeleteRecordingClicked.emit(recordingId);
}
/**
* @internal
*/
onStartStreamingClicked(rtmpUrl: string) {
// if (from === 'toolbar') {
// this.onToolbarStartRecordingClicked.emit();
// } else if (from === 'panel') {
this.onActivitiesPanelStartStreamingClicked.emit(rtmpUrl);
// }
}
/**
* @internal
*/
onStopStreamingClicked(from: string) {
if (from === 'toolbar') {
this.onToolbarStopStreamingClicked.emit();
} else if (from === 'panel') {
this.onActivitiesPanelStopStreamingClicked.emit();
}
}
/**
* @internal
*/
@ -778,7 +897,7 @@ export class OpenviduWebComponentComponent implements OnInit {
}
}
private castToArray(value: CaptionsLangOption [] | string) {
private castToArray(value: CaptionsLangOption[] | string) {
if (typeof value === 'string') {
try {
return JSON.parse(value);

View File

@ -1,8 +1,8 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { RecordingInfo } from 'openvidu-angular';
import { catchError, lastValueFrom } from 'rxjs';
import { throwError as observableThrowError } from 'rxjs/internal/observable/throwError';
import { RecordingInfo } from 'openvidu-angular';
@Injectable({
providedIn: 'root'
@ -41,6 +41,22 @@ export class RestService {
}
}
async startStreaming(rtmpUrl: string) {
try {
const options = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};
return lastValueFrom(this.http.post<any>(this.baseHref + 'streamings/start', { rtmpUrl}, options));
} catch (error) {
if (error.status === 404) {
throw { status: error.status, message: 'Cannot connect with backend. ' + error.url + ' not found' };
}
throw error.error;
}
}
async stopRecording(sessionId: string) {
try {
return lastValueFrom(this.http.post<any>(this.baseHref + 'recordings/stop', { sessionId }));
@ -52,10 +68,22 @@ export class RestService {
}
}
async stopStreaming() {
try {
return lastValueFrom(this.http.delete<any>(`${this.baseHref}streamings/stop`));
} catch (error) {
if (error.status === 404) {
throw { status: error.status, message: 'Cannot connect with backend. ' + error.url + ' not found' };
}
throw error;
}
}
async login(password: string): Promise<any[]> {
try {
return lastValueFrom(
this.http.post<any>(`${this.baseHref}admin/login`, {
this.http.post<any>(`${this.baseHref}auth/admin/login`, {
password
})
);

View File

@ -51,8 +51,12 @@
<span>
<ul>
<li *ngFor="let dir of directive.directives">
<mat-checkbox [id]="dir + '-checkbox'" (change)="updateApiDirective(dir, $event.checked)" [checked]="true">
{{ dir }}
<mat-checkbox
[id]="dir.name + '-checkbox'"
(change)="updateApiDirective(dir.name, $event.checked)"
[checked]="dir.checked"
>
{{ dir.name }}
</mat-checkbox>
</li>
</ul>
@ -80,6 +84,7 @@
[chatPanelButton]="chatPanelBtn"
[displaySessionName]="displaySessionId"
[displayLogo]="displayLogo"
[streamingButton]="streamingBtn"
(onLeaveButtonClicked)="appendElement('onLeaveButtonClicked')"
(onCameraButtonClicked)="appendElement('onCameraButtonClicked')"
(onMicrophoneButtonClicked)="appendElement('onMicrophoneButtonClicked')"
@ -197,7 +202,15 @@
</ng-template>
<ng-template [ngIf]="!ovPanelSelected && ovActivitiesPanelSelected">
<ov-activities-panel *ovActivitiesPanel [recordingActivity]="recordingActivity" id="custom-activities-panel"></ov-activities-panel>
<ov-activities-panel
*ovActivitiesPanel
[recordingActivity]="recordingActivity"
[streamingActivity]="streamingActivity"
id="custom-activities-panel"
>
<ov-recording-activity *ngIf="recordingActivity"></ov-recording-activity>
<ov-streaming-activity *ngIf="streamingActivity" [streamingInfo]="streamingInfo" [streamingError]="streamingError"></ov-streaming-activity>
</ov-activities-panel>
</ng-template>
<ng-template [ngIf]="!ovPanelSelected && ovParticipantsPanelSelected">

View File

@ -2,7 +2,7 @@ import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { PanelService } from 'openvidu-angular';
import { PanelService, StreamingError, StreamingInfo, StreamingStatus } from 'openvidu-angular';
import { Subscription, throwError as observableThrowError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@ -13,7 +13,7 @@ interface TemplateDirectives {
interface APIDirectives {
component: string;
directives: AttributeDirective[];
directives: { name: AttributeDirective; checked: boolean }[];
}
enum StructuralDirectives {
@ -31,6 +31,7 @@ enum StructuralDirectives {
STREAM = 'ovStream'
}
export enum AttributeDirective {
// MINIMAL = 'minimal',
// PARTICIPANT_NAME = 'participantName',
@ -39,6 +40,7 @@ export enum AttributeDirective {
// AUDIO_MUTED = 'audioMuted',
TOOLBAR_SCREENSHARE = 'screenshareButton',
TOOLBAR_FULLSCREEN = 'fullscreenButton',
TOOLBAR_STREAMING = 'streamingButton',
TOOLBAR_LEAVE = 'leaveButton',
TOOLBAR_PARTICIPANTS_PANEL = 'participantsPanelButton',
TOOLBAR_ACTIVITIES_PANEL = 'activitiesPanelButton',
@ -49,7 +51,10 @@ export enum AttributeDirective {
STREAM_AUDIO_DETECTION = 'displayAudioDetection',
STREAM_SETTINGS = 'settingsButton',
PARTICIPANT_ITEM_MUTE = 'muteButton',
RECORDING_ACTIVITY = 'recordingActivity'
ACTIVITIES_PANEL_RECORDING_ACTIVITY = 'recordingActivity',
ACTIVITIES_PANEL_STREAMING_ACTIVITY = 'streamingActivity',
ACTIVITIES_PANEL_STREAMING_INFO = 'streamingInfo',
ACTIVITIES_PANEL_STREAMING_ERROR = "streamingError"
}
@Component({
@ -93,31 +98,37 @@ export class TestingComponent implements OnInit {
{
component: StructuralDirectives.TOOLBAR,
directives: [
AttributeDirective.TOOLBAR_CHAT_PANEL,
AttributeDirective.TOOLBAR_DISPLAY_LOGO,
AttributeDirective.TOOLBAR_DISPLAY_SESSION,
AttributeDirective.TOOLBAR_FULLSCREEN,
AttributeDirective.TOOLBAR_LEAVE,
AttributeDirective.TOOLBAR_PARTICIPANTS_PANEL,
AttributeDirective.TOOLBAR_ACTIVITIES_PANEL,
AttributeDirective.TOOLBAR_SCREENSHARE
{ name: AttributeDirective.TOOLBAR_CHAT_PANEL, checked: true },
{ name: AttributeDirective.TOOLBAR_DISPLAY_LOGO, checked: true },
{ name: AttributeDirective.TOOLBAR_DISPLAY_SESSION, checked: true },
{ name: AttributeDirective.TOOLBAR_FULLSCREEN, checked: true },
{ name: AttributeDirective.TOOLBAR_STREAMING, checked: true },
{ name: AttributeDirective.TOOLBAR_LEAVE, checked: true },
{ name: AttributeDirective.TOOLBAR_PARTICIPANTS_PANEL, checked: true },
{ name: AttributeDirective.TOOLBAR_ACTIVITIES_PANEL, checked: true },
{ name: AttributeDirective.TOOLBAR_SCREENSHARE, checked: true }
]
},
{
component: StructuralDirectives.STREAM,
directives: [
AttributeDirective.STREAM_AUDIO_DETECTION,
AttributeDirective.STREAM_PARTICIPANT_NAME,
AttributeDirective.STREAM_SETTINGS
{ name: AttributeDirective.STREAM_AUDIO_DETECTION, checked: true },
{ name: AttributeDirective.STREAM_PARTICIPANT_NAME, checked: true },
{ name: AttributeDirective.STREAM_SETTINGS, checked: true }
]
},
{
component: StructuralDirectives.PARTICIPANTS_PANEL_ITEM,
directives: [AttributeDirective.PARTICIPANT_ITEM_MUTE]
directives: [{ name: AttributeDirective.PARTICIPANT_ITEM_MUTE, checked: true }]
},
{
component: StructuralDirectives.ACTIVITIES_PANEL,
directives: [AttributeDirective.RECORDING_ACTIVITY]
directives: [
{ name: AttributeDirective.ACTIVITIES_PANEL_RECORDING_ACTIVITY, checked: true },
{ name: AttributeDirective.ACTIVITIES_PANEL_STREAMING_ACTIVITY, checked: true },
{ name: AttributeDirective.ACTIVITIES_PANEL_STREAMING_INFO, checked: false },
{ name: AttributeDirective.ACTIVITIES_PANEL_STREAMING_ERROR, checked: false }
]
}
];
@ -143,15 +154,18 @@ export class TestingComponent implements OnInit {
participantsPanelBtn = true;
activitiesPanelBtn = true;
screenshareBtn = true;
audioDetection = true;
participantName = true;
settingsBtn = true;
participantItemMuteBtn = true;
streamingActivity = true;
streamingBtn = true;
streamingInfo: StreamingInfo = undefined;
tokens: { webcam: any; screen: any };
subscription: Subscription;
streamingError: StreamingError | undefined;
recordingActivity = true;
@ -254,6 +268,10 @@ export class TestingComponent implements OnInit {
this.fullscreenBtn = value;
break;
case AttributeDirective.TOOLBAR_STREAMING:
this.streamingBtn = value;
break;
case AttributeDirective.TOOLBAR_LEAVE:
this.leaveBtn = value;
break;
@ -279,9 +297,22 @@ export class TestingComponent implements OnInit {
case AttributeDirective.PARTICIPANT_ITEM_MUTE:
this.participantItemMuteBtn = value;
break;
case AttributeDirective.RECORDING_ACTIVITY:
case AttributeDirective.ACTIVITIES_PANEL_RECORDING_ACTIVITY:
this.recordingActivity = value;
break;
case AttributeDirective.ACTIVITIES_PANEL_STREAMING_ACTIVITY:
this.streamingActivity = value;
break;
case AttributeDirective.ACTIVITIES_PANEL_STREAMING_INFO:
this.streamingInfo = { status: StreamingStatus.STARTED, id: '01' };
break;
case AttributeDirective.ACTIVITIES_PANEL_STREAMING_ERROR:
this.streamingError = {message: 'TEST_ERROR', rtmpAvailable: true};
break;
default:
break;
}
}
@ -307,7 +338,7 @@ export class TestingComponent implements OnInit {
async getToken(sessionId: string): Promise<string> {
const id = await this.createSession(sessionId);
return await this.createToken(id);
return await this.createConnection(id);
}
createSession(sessionId: string) {
@ -352,9 +383,9 @@ export class TestingComponent implements OnInit {
});
}
createToken(sessionId): Promise<string> {
createConnection(sessionId): Promise<string> {
return new Promise((resolve, reject) => {
const body = {};
const body = {role: 'MODERATOR'};
const options = {
headers: new HttpHeaders({
Authorization: 'Basic ' + btoa('OPENVIDUAPP:' + this.OPENVIDU_SERVER_SECRET),