mirror of https://github.com/OpenVidu/openvidu.git
Merge branch 'ov-components-refactor'
commit
12a34ca7cb
|
@ -71,7 +71,7 @@ jobs:
|
||||||
name: openvidu-browser
|
name: openvidu-browser
|
||||||
path: openvidu-components-angular
|
path: openvidu-components-angular
|
||||||
- name: Run Browserless Chrome
|
- name: Run Browserless Chrome
|
||||||
run: docker run -d -p 3000:3000 --network host browserless/chrome:1.53-chrome-stable
|
run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
|
||||||
- name: Run openvidu-server-kms
|
- name: Run openvidu-server-kms
|
||||||
run: |
|
run: |
|
||||||
docker run -p 4443:4443 --rm -d \
|
docker run -p 4443:4443 --rm -d \
|
||||||
|
@ -106,7 +106,7 @@ jobs:
|
||||||
name: openvidu-browser
|
name: openvidu-browser
|
||||||
path: openvidu-components-angular
|
path: openvidu-components-angular
|
||||||
- name: Run Browserless Chrome
|
- name: Run Browserless Chrome
|
||||||
run: docker run -d -p 3000:3000 --network host browserless/chrome:1.53-chrome-stable
|
run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
|
||||||
- name: Run openvidu-server-kms
|
- name: Run openvidu-server-kms
|
||||||
run: |
|
run: |
|
||||||
docker run -p 4443:4443 --rm -d \
|
docker run -p 4443:4443 --rm -d \
|
||||||
|
@ -140,7 +140,7 @@ jobs:
|
||||||
name: openvidu-browser
|
name: openvidu-browser
|
||||||
path: openvidu-components-angular
|
path: openvidu-components-angular
|
||||||
- name: Run Browserless Chrome
|
- name: Run Browserless Chrome
|
||||||
run: docker run -d -p 3000:3000 --network host browserless/chrome:1.53-chrome-stable
|
run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
|
||||||
- name: Install openvidu-browser and dependencies
|
- name: Install openvidu-browser and dependencies
|
||||||
run: |
|
run: |
|
||||||
cd openvidu-components-angular
|
cd openvidu-components-angular
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { expect } from 'chai';
|
||||||
import { By, until, WebDriver, WebElement } from 'selenium-webdriver';
|
import { By, until, WebDriver, WebElement } from 'selenium-webdriver';
|
||||||
|
|
||||||
export class OpenViduComponentsPO {
|
export class OpenViduComponentsPO {
|
||||||
private TIMEOUT = 30 * 1000;
|
private TIMEOUT = 10 * 1000;
|
||||||
private POLL_TIMEOUT = 1 * 1000;
|
private POLL_TIMEOUT = 1 * 1000;
|
||||||
|
|
||||||
constructor(private browser: WebDriver) {}
|
constructor(private browser: WebDriver) {}
|
||||||
|
@ -16,7 +16,7 @@ export class OpenViduComponentsPO {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getNumberOfElements(selector: string){
|
async getNumberOfElements(selector: string): Promise<number> {
|
||||||
return (await this.browser.findElements(By.css(selector))).length;
|
return (await this.browser.findElements(By.css(selector))).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import monkeyPatchMediaDevices from './utils/media-devices.js';
|
||||||
|
|
||||||
var MINIMAL;
|
var MINIMAL;
|
||||||
var LANG;
|
var LANG;
|
||||||
var CAPTIONS_LANG;
|
var CAPTIONS_LANG;
|
||||||
|
@ -29,6 +31,7 @@ var CAPTIONS_BUTTON;
|
||||||
|
|
||||||
var SINGLE_TOKEN;
|
var SINGLE_TOKEN;
|
||||||
var SESSION_NAME;
|
var SESSION_NAME;
|
||||||
|
var FAKE_DEVICES;
|
||||||
|
|
||||||
var PARTICIPANT_NAME;
|
var PARTICIPANT_NAME;
|
||||||
|
|
||||||
|
@ -43,6 +46,8 @@ $(document).ready(() => {
|
||||||
|
|
||||||
SINGLE_TOKEN = url.searchParams.get('singleToken') === null ? false : url.searchParams.get('singleToken') === 'true';
|
SINGLE_TOKEN = url.searchParams.get('singleToken') === null ? false : url.searchParams.get('singleToken') === 'true';
|
||||||
|
|
||||||
|
FAKE_DEVICES = url.searchParams.get('fakeDevices') === null ? false : url.searchParams.get('fakeDevices') === 'true';
|
||||||
|
|
||||||
// Directives
|
// Directives
|
||||||
MINIMAL = url.searchParams.get('minimal') === null ? false : url.searchParams.get('minimal') === 'true';
|
MINIMAL = url.searchParams.get('minimal') === null ? false : url.searchParams.get('minimal') === 'true';
|
||||||
LANG = url.searchParams.get('lang') || 'en';
|
LANG = url.searchParams.get('lang') || 'en';
|
||||||
|
@ -197,6 +202,11 @@ function appendElement(id) {
|
||||||
async function joinSession(sessionName, participantName) {
|
async function joinSession(sessionName, participantName) {
|
||||||
var webComponent = document.querySelector('openvidu-webcomponent');
|
var webComponent = document.querySelector('openvidu-webcomponent');
|
||||||
var tokens;
|
var tokens;
|
||||||
|
|
||||||
|
if (FAKE_DEVICES) {
|
||||||
|
monkeyPatchMediaDevices();
|
||||||
|
}
|
||||||
|
|
||||||
if (SINGLE_TOKEN) {
|
if (SINGLE_TOKEN) {
|
||||||
tokens = await getToken(sessionName);
|
tokens = await getToken(sessionName);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -8,7 +8,10 @@
|
||||||
crossorigin="anonymous"
|
crossorigin="anonymous"
|
||||||
></script>
|
></script>
|
||||||
|
|
||||||
<script src="app.js"></script>
|
<script type="module" src="utils/filter-stream.js"></script>
|
||||||
|
<script type="module" src="utils/shader-renderer.js"></script>
|
||||||
|
<script type="module" src="utils/media-devices.js"></script>
|
||||||
|
<script type="module" src="app.js"></script>
|
||||||
<script src="openvidu-webcomponent-dev.js"></script>
|
<script src="openvidu-webcomponent-dev.js"></script>
|
||||||
<link rel="stylesheet" href="openvidu-webcomponent-dev.css" />
|
<link rel="stylesheet" href="openvidu-webcomponent-dev.css" />
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
class FilterStream {
|
||||||
|
constructor(stream, label) {
|
||||||
|
const videoTrack = stream.getVideoTracks()[0];
|
||||||
|
const { width, height } = videoTrack.getSettings();
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const video = document.createElement('video');
|
||||||
|
video.srcObject = new MediaStream([videoTrack]);
|
||||||
|
video.play();
|
||||||
|
|
||||||
|
video.addEventListener('play', () => {
|
||||||
|
const loop = () => {
|
||||||
|
if (!video.paused && !video.ended) {
|
||||||
|
ctx.filter = 'grayscale(100%)';
|
||||||
|
ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, video.videoWidth, video.videoHeight);
|
||||||
|
setTimeout(loop, 33);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loop();
|
||||||
|
});
|
||||||
|
this.outputStream = canvas.captureStream();
|
||||||
|
|
||||||
|
Object.defineProperty(this.outputStream.getVideoTracks()[0], 'label', {
|
||||||
|
writable: true,
|
||||||
|
value: label
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { FilterStream };
|
|
@ -0,0 +1,62 @@
|
||||||
|
// Ideally we'd use an editor or import shaders directly from the API.
|
||||||
|
import { FilterStream } from './filter-stream.js';
|
||||||
|
|
||||||
|
export default function monkeyPatchMediaDevices() {
|
||||||
|
const enumerateDevicesFn = MediaDevices.prototype.enumerateDevices;
|
||||||
|
const getUserMediaFn = MediaDevices.prototype.getUserMedia;
|
||||||
|
const getDisplayMediaFn = MediaDevices.prototype.getDisplayMedia;
|
||||||
|
|
||||||
|
const fakeDevice = {
|
||||||
|
deviceId: 'virtual',
|
||||||
|
groupID: '',
|
||||||
|
kind: 'videoinput',
|
||||||
|
label: 'custom_fake_video_1'
|
||||||
|
};
|
||||||
|
|
||||||
|
MediaDevices.prototype.enumerateDevices = async function () {
|
||||||
|
const res = await enumerateDevicesFn.call(navigator.mediaDevices);
|
||||||
|
res.push(fakeDevice);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
MediaDevices.prototype.getUserMedia = async function () {
|
||||||
|
const args = arguments[0];
|
||||||
|
const { deviceId, advanced, width, height } = args.video;
|
||||||
|
if (deviceId === 'virtual' || deviceId?.exact === 'virtual') {
|
||||||
|
const constraints = {
|
||||||
|
video: {
|
||||||
|
facingMode: args.facingMode,
|
||||||
|
advanced,
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
},
|
||||||
|
audio: false
|
||||||
|
};
|
||||||
|
const res = await getUserMediaFn.call(navigator.mediaDevices, constraints);
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
const filter = new FilterStream(res, fakeDevice.label);
|
||||||
|
return filter.outputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getUserMediaFn.call(navigator.mediaDevices, ...arguments);
|
||||||
|
};
|
||||||
|
|
||||||
|
MediaDevices.prototype.getDisplayMedia = async function () {
|
||||||
|
const { video, audio } = arguments[0];
|
||||||
|
|
||||||
|
const screenVideoElement = document.getElementsByClassName("OT_video-element screen-type")[0];
|
||||||
|
const currentTrackLabel = screenVideoElement?.srcObject?.getVideoTracks()[0]?.label;
|
||||||
|
const res = await getDisplayMediaFn.call(navigator.mediaDevices, { video, audio });
|
||||||
|
|
||||||
|
if (res && currentTrackLabel && currentTrackLabel !== 'custom_fake_screen') {
|
||||||
|
const filter = new FilterStream(res, 'custom_fake_screen');
|
||||||
|
return filter.outputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { Builder, By, Key, WebDriver } from 'selenium-webdriver';
|
import { Builder, Key, WebDriver } from 'selenium-webdriver';
|
||||||
import { OPENVIDU_SECRET, OPENVIDU_SERVER_URL } from './config';
|
import { OPENVIDU_SECRET, OPENVIDU_SERVER_URL } from './config';
|
||||||
import { getBrowserOptionsWithoutDevices, WebComponentConfig } from './selenium.conf';
|
import { getBrowserOptionsWithoutDevices, WebComponentConfig } from './selenium.conf';
|
||||||
import { OpenViduComponentsPO } from './utils.po.test';
|
import { OpenViduComponentsPO } from './utils.po.test';
|
||||||
|
@ -88,8 +88,7 @@ describe('Testing API Directives', () => {
|
||||||
expect(await utils.isPresent('#session-name')).to.be.false;
|
expect(await utils.isPresent('#session-name')).to.be.false;
|
||||||
|
|
||||||
// Checking if nickname is not displayed
|
// Checking if nickname is not displayed
|
||||||
await browser.findElements(By.id('nickname-container'));
|
expect(await utils.getNumberOfElements('#nickname-container')).equals(0);
|
||||||
expect(await utils.isPresent('#nickname-container')).to.be.false;
|
|
||||||
|
|
||||||
// Checking if audio detection is not displayed
|
// Checking if audio detection is not displayed
|
||||||
expect(await utils.isPresent('#audio-wave-container')).to.be.false;
|
expect(await utils.isPresent('#audio-wave-container')).to.be.false;
|
||||||
|
@ -123,35 +122,40 @@ describe('Testing API Directives', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should run the app with VIDEO MUTED in prejoin page', async () => {
|
it('should run the app with VIDEO MUTED in prejoin page', async () => {
|
||||||
let idVideoEnabled;
|
try {
|
||||||
const script = 'return document.getElementsByTagName("video")[0].srcObject.getVideoTracks()[0].enabled;';
|
let idVideoEnabled;
|
||||||
|
const script = 'return document.getElementsByTagName("video")[0].srcObject.getVideoTracks()[0].enabled;';
|
||||||
|
|
||||||
await browser.get(`${url}&prejoin=true&videoMuted=true`);
|
await browser.get(`${url}&prejoin=true&videoMuted=true`);
|
||||||
|
|
||||||
await utils.checkPrejoinIsPresent();
|
await utils.checkPrejoinIsPresent();
|
||||||
|
|
||||||
// Checking if video is displayed
|
// Checking if video is displayed
|
||||||
await utils.checkVideoElementIsPresent();
|
await utils.checkVideoElementIsPresent();
|
||||||
|
|
||||||
// Checking if virtual background button is disabled
|
// Checking if virtual background button is disabled
|
||||||
const button = await utils.waitForElement('#background-effects-btn');
|
const button = await utils.waitForElement('#background-effects-btn');
|
||||||
expect(await button.isEnabled()).to.be.false;
|
expect(await button.isEnabled()).to.be.false;
|
||||||
|
|
||||||
// Checking if video track is disabled/muted
|
// Checking if video track is disabled/muted
|
||||||
idVideoEnabled = await browser.executeScript<boolean>(script);
|
idVideoEnabled = await browser.executeScript<boolean>(script);
|
||||||
expect(idVideoEnabled).to.be.false;
|
expect(idVideoEnabled).to.be.false;
|
||||||
|
|
||||||
await utils.waitForElement('#videocam_off');
|
await utils.waitForElement('#videocam_off');
|
||||||
await utils.clickOn('#join-button');
|
await utils.clickOn('#join-button');
|
||||||
|
|
||||||
// Checking if video is muted after join the room
|
// Checking if video is muted after join the room
|
||||||
await utils.checkSessionIsPresent();
|
await utils.checkSessionIsPresent();
|
||||||
|
|
||||||
idVideoEnabled = await browser.executeScript<boolean>(script);
|
idVideoEnabled = await browser.executeScript<boolean>(script);
|
||||||
expect(idVideoEnabled).to.be.false;
|
expect(idVideoEnabled).to.be.false;
|
||||||
|
|
||||||
await utils.waitForElement('#videocam_off');
|
await utils.waitForElement('#videocam_off');
|
||||||
expect(await utils.isPresent('#videocam_off')).to.be.true;
|
expect(await utils.isPresent('#videocam_off')).to.be.true;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
console.log(await browser.takeScreenshot());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should run the app with VIDEO MUTED and WITHOUT PREJOIN page', async () => {
|
it('should run the app with VIDEO MUTED and WITHOUT PREJOIN page', async () => {
|
||||||
|
@ -160,6 +164,8 @@ describe('Testing API Directives', () => {
|
||||||
|
|
||||||
await browser.get(`${url}&prejoin=false&videoMuted=true`);
|
await browser.get(`${url}&prejoin=false&videoMuted=true`);
|
||||||
|
|
||||||
|
await browser.sleep(2000);
|
||||||
|
|
||||||
await utils.checkSessionIsPresent();
|
await utils.checkSessionIsPresent();
|
||||||
|
|
||||||
await utils.checkLayoutPresent();
|
await utils.checkLayoutPresent();
|
||||||
|
@ -251,9 +257,8 @@ describe('Testing API Directives', () => {
|
||||||
// Checking if fullscreen button is not present
|
// Checking if fullscreen button is not present
|
||||||
await utils.waitForElement('.mat-menu-content');
|
await utils.waitForElement('.mat-menu-content');
|
||||||
expect(await utils.isPresent('.mat-menu-content')).to.be.true;
|
expect(await utils.isPresent('.mat-menu-content')).to.be.true;
|
||||||
|
expect(await utils.getNumberOfElements('#fullscreen-btn')).equals(0);
|
||||||
|
|
||||||
await browser.findElements(By.id('fullscreen-btn'));
|
|
||||||
expect(await utils.isPresent('#fullscreen-btn')).to.be.false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should HIDE the CAPTIONS button', async () => {
|
it('should HIDE the CAPTIONS button', async () => {
|
||||||
|
@ -357,8 +362,7 @@ describe('Testing API Directives', () => {
|
||||||
await utils.checkToolbarIsPresent();
|
await utils.checkToolbarIsPresent();
|
||||||
|
|
||||||
// Checking if leave button is not present
|
// Checking if leave button is not present
|
||||||
await browser.findElements(By.id('leave-btn'));
|
expect(await utils.getNumberOfElements('#leave-btn')).equals(0);
|
||||||
expect(await utils.isPresent('#leave-btn')).to.be.false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should HIDE the ACTIVITIES PANEL button', async () => {
|
it('should HIDE the ACTIVITIES PANEL button', async () => {
|
||||||
|
@ -500,7 +504,7 @@ describe('Testing API Directives', () => {
|
||||||
|
|
||||||
// Go to first tab
|
// Go to first tab
|
||||||
const tabs = await browser.getAllWindowHandles();
|
const tabs = await browser.getAllWindowHandles();
|
||||||
await browser.switchTo().window(tabs[0]);
|
browser.switchTo().window(tabs[0]);
|
||||||
|
|
||||||
// Checking if mute button is not displayed in participant item
|
// Checking if mute button is not displayed in participant item
|
||||||
await utils.waitForElement('#remote-participant-item');
|
await utils.waitForElement('#remote-participant-item');
|
||||||
|
@ -614,7 +618,7 @@ describe('Testing API Directives', () => {
|
||||||
await utils.checkToolbarIsPresent();
|
await utils.checkToolbarIsPresent();
|
||||||
|
|
||||||
await utils.waitForElement('#activities-panel-btn');
|
await utils.waitForElement('#activities-panel-btn');
|
||||||
await utils.clickOn('#activities-panel-btn')
|
await utils.clickOn('#activities-panel-btn');
|
||||||
|
|
||||||
// Checking if participatns panel is displayed
|
// Checking if participatns panel is displayed
|
||||||
await utils.waitForElement('#default-activities-panel');
|
await utils.waitForElement('#default-activities-panel');
|
||||||
|
@ -964,7 +968,6 @@ describe('Testing videoconference EVENTS', () => {
|
||||||
const button = await utils.waitForElement('#broadcasting-btn');
|
const button = await utils.waitForElement('#broadcasting-btn');
|
||||||
expect(await button.isEnabled()).to.be.false;
|
expect(await button.isEnabled()).to.be.false;
|
||||||
|
|
||||||
|
|
||||||
const input = await utils.waitForElement('#broadcast-url-input');
|
const input = await utils.waitForElement('#broadcast-url-input');
|
||||||
await input.sendKeys('BroadcastUrl');
|
await input.sendKeys('BroadcastUrl');
|
||||||
|
|
||||||
|
@ -974,7 +977,6 @@ describe('Testing videoconference EVENTS', () => {
|
||||||
await utils.waitForElement('#onActivitiesPanelStartBroadcastingClicked');
|
await utils.waitForElement('#onActivitiesPanelStartBroadcastingClicked');
|
||||||
expect(await utils.isPresent('#onActivitiesPanelStartBroadcastingClicked')).to.be.true;
|
expect(await utils.isPresent('#onActivitiesPanelStartBroadcastingClicked')).to.be.true;
|
||||||
|
|
||||||
|
|
||||||
// TODO: it needs an OpenVidu PRO (onActivitiesPanelStopBroadcastingClicked event)
|
// TODO: it needs an OpenVidu PRO (onActivitiesPanelStopBroadcastingClicked event)
|
||||||
|
|
||||||
// expect(await utils.isPresent('#broadcasting-tag')).to.be.true;
|
// expect(await utils.isPresent('#broadcasting-tag')).to.be.true;
|
||||||
|
@ -1037,6 +1039,133 @@ describe('Testing videoconference EVENTS', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Testing replace track with emulated devices', () => {
|
||||||
|
let browser: WebDriver;
|
||||||
|
let utils: OpenViduComponentsPO;
|
||||||
|
async function createChromeBrowser(): Promise<WebDriver> {
|
||||||
|
return await new Builder()
|
||||||
|
.forBrowser(WebComponentConfig.browserName)
|
||||||
|
.withCapabilities(WebComponentConfig.browserCapabilities)
|
||||||
|
.setChromeOptions(WebComponentConfig.browserOptions)
|
||||||
|
.usingServer(WebComponentConfig.seleniumAddress)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
browser = await createChromeBrowser();
|
||||||
|
utils = new OpenViduComponentsPO(browser);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
// console.log('data:image/png;base64,' + await browser.takeScreenshot());
|
||||||
|
await browser.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should replace the video track in prejoin page', async () => {
|
||||||
|
const script = 'return document.getElementsByTagName("video")[0].srcObject.getVideoTracks()[0].label;';
|
||||||
|
|
||||||
|
await browser.get(`${url}&fakeDevices=true`);
|
||||||
|
|
||||||
|
let videoDevices = await utils.waitForElement('#video-devices-form');
|
||||||
|
|
||||||
|
await videoDevices.click();
|
||||||
|
|
||||||
|
let element = await utils.waitForElement('#option-custom_fake_video_1');
|
||||||
|
|
||||||
|
await element.click();
|
||||||
|
|
||||||
|
let videoLabel;
|
||||||
|
|
||||||
|
await browser.sleep(1000);
|
||||||
|
videoLabel = await browser.executeScript<string>(script);
|
||||||
|
expect(videoLabel).to.be.equal('custom_fake_video_1');
|
||||||
|
|
||||||
|
await videoDevices.click();
|
||||||
|
|
||||||
|
element = await utils.waitForElement('#option-fake_device_0');
|
||||||
|
await element.click();
|
||||||
|
|
||||||
|
await browser.sleep(1000);
|
||||||
|
videoLabel = await browser.executeScript<string>(script);
|
||||||
|
expect(videoLabel).to.be.equal('fake_device_0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should replace the video track in videoconference page', async () => {
|
||||||
|
const script = 'return document.getElementsByTagName("video")[0].srcObject.getVideoTracks()[0].label;';
|
||||||
|
|
||||||
|
await browser.get(`${url}&prejoin=false&fakeDevices=true`);
|
||||||
|
|
||||||
|
await utils.checkSessionIsPresent();
|
||||||
|
|
||||||
|
// Checking if toolbar is present
|
||||||
|
await utils.checkToolbarIsPresent();
|
||||||
|
|
||||||
|
// Open more options menu
|
||||||
|
await utils.clickOn('#more-options-btn');
|
||||||
|
|
||||||
|
// Checking if button panel is present
|
||||||
|
await utils.waitForElement('.mat-menu-content');
|
||||||
|
expect(await utils.isPresent('.mat-menu-content')).to.be.true;
|
||||||
|
|
||||||
|
await utils.clickOn('#toolbar-settings-btn');
|
||||||
|
|
||||||
|
await utils.waitForElement('.settings-container');
|
||||||
|
expect(await utils.isPresent('.settings-container')).to.be.true;
|
||||||
|
|
||||||
|
await utils.clickOn('#video-opt');
|
||||||
|
expect(await utils.isPresent('ov-video-devices-select')).to.be.true;
|
||||||
|
|
||||||
|
let videoDevices = await utils.waitForElement('#video-devices-form');
|
||||||
|
|
||||||
|
await videoDevices.click();
|
||||||
|
|
||||||
|
let element = await utils.waitForElement('#option-custom_fake_video_1');
|
||||||
|
|
||||||
|
await element.click();
|
||||||
|
|
||||||
|
let videoLabel;
|
||||||
|
await browser.sleep(1000);
|
||||||
|
videoLabel = await browser.executeScript<string>(script);
|
||||||
|
expect(videoLabel).to.be.equal('custom_fake_video_1');
|
||||||
|
|
||||||
|
await videoDevices.click();
|
||||||
|
|
||||||
|
element = await utils.waitForElement('#option-fake_device_0');
|
||||||
|
await element.click();
|
||||||
|
|
||||||
|
await browser.sleep(1000);
|
||||||
|
videoLabel = await browser.executeScript<string>(script);
|
||||||
|
expect(videoLabel).to.be.equal('fake_device_0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should replace the screen track', async () => {
|
||||||
|
const script = 'return document.getElementsByClassName("OT_video-element screen-type")[0].srcObject.getVideoTracks()[0].label;';
|
||||||
|
|
||||||
|
await browser.get(`${url}&prejoin=false&fakeDevices=true`);
|
||||||
|
|
||||||
|
await utils.checkLayoutPresent();
|
||||||
|
await utils.checkToolbarIsPresent();
|
||||||
|
|
||||||
|
await utils.clickOn('#screenshare-btn');
|
||||||
|
|
||||||
|
await browser.sleep(500);
|
||||||
|
|
||||||
|
let screenLabel = await browser.executeScript<string>(script);
|
||||||
|
expect(screenLabel).not.equal('custom_fake_screen');
|
||||||
|
|
||||||
|
await utils.clickOn('#video-settings-btn-SCREEN');
|
||||||
|
await browser.sleep(500);
|
||||||
|
|
||||||
|
await utils.waitForElement('.video-settings-menu');
|
||||||
|
const replaceBtn = await utils.waitForElement('#replace-screen-button');
|
||||||
|
await replaceBtn.sendKeys(Key.ENTER);
|
||||||
|
|
||||||
|
await browser.sleep(1000);
|
||||||
|
screenLabel = await browser.executeScript<string>(script);
|
||||||
|
expect(screenLabel).to.be.equal('custom_fake_screen');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Testing stream video menu features', () => {
|
describe('Testing stream video menu features', () => {
|
||||||
let browser: WebDriver;
|
let browser: WebDriver;
|
||||||
let utils: OpenViduComponentsPO;
|
let utils: OpenViduComponentsPO;
|
||||||
|
@ -1063,8 +1192,8 @@ describe('Testing stream video menu features', () => {
|
||||||
|
|
||||||
await utils.checkLayoutPresent();
|
await utils.checkLayoutPresent();
|
||||||
|
|
||||||
await utils.waitForElement('#stream-menu-btn');
|
await utils.waitForElement('#video-settings-btn-CAMERA');
|
||||||
await utils.clickOn('#stream-menu-btn');
|
await utils.clickOn('#video-settings-btn-CAMERA');
|
||||||
|
|
||||||
await browser.sleep(500);
|
await browser.sleep(500);
|
||||||
|
|
||||||
|
@ -1073,7 +1202,6 @@ describe('Testing stream video menu features', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('Testing screenshare features', () => {
|
describe('Testing screenshare features', () => {
|
||||||
let browser: WebDriver;
|
let browser: WebDriver;
|
||||||
let utils: OpenViduComponentsPO;
|
let utils: OpenViduComponentsPO;
|
||||||
|
@ -1095,10 +1223,10 @@ describe('Testing screenshare features', () => {
|
||||||
await browser.quit();
|
await browser.quit();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should toggle screensharing', async () => {
|
|
||||||
let element;
|
|
||||||
await browser.get(`${url}&prejoin=false`);
|
|
||||||
|
|
||||||
|
|
||||||
|
it('should toggle screensharing twice', async () => {
|
||||||
|
await browser.get(`${url}&prejoin=false`);
|
||||||
await utils.checkLayoutPresent();
|
await utils.checkLayoutPresent();
|
||||||
|
|
||||||
// Clicking to screensharing button
|
// Clicking to screensharing button
|
||||||
|
@ -1107,18 +1235,54 @@ describe('Testing screenshare features', () => {
|
||||||
await screenshareButton.click();
|
await screenshareButton.click();
|
||||||
|
|
||||||
await utils.waitForElement('.OV_big');
|
await utils.waitForElement('.OV_big');
|
||||||
element = await browser.findElements(By.css('video'));
|
expect(await utils.getNumberOfElements('video')).equals(2);
|
||||||
expect(element.length).equals(2);
|
|
||||||
|
|
||||||
// Clicking to screensharing button
|
// Clicking to screensharing button
|
||||||
await screenshareButton.click();
|
await screenshareButton.click();
|
||||||
|
expect(await utils.getNumberOfElements('video')).equals(1);
|
||||||
|
|
||||||
|
|
||||||
|
// toggle screenshare again
|
||||||
|
await screenshareButton.click();
|
||||||
|
|
||||||
|
await utils.waitForElement('.OV_big');
|
||||||
|
expect(await utils.getNumberOfElements('video')).equals(2);
|
||||||
|
|
||||||
|
await screenshareButton.click();
|
||||||
|
|
||||||
|
expect(await utils.getNumberOfElements('video')).equals(1);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show only screen if toggle screensharing with video muted', async () => {
|
||||||
|
await browser.get(`${url}&prejoin=false`);
|
||||||
|
|
||||||
|
await utils.checkLayoutPresent();
|
||||||
|
|
||||||
|
const camButton = await utils.waitForElement('#camera-btn');
|
||||||
|
await camButton.click();
|
||||||
|
|
||||||
|
// Clicking to screensharing button
|
||||||
|
const screenshareButton = await utils.waitForElement('#screenshare-btn');
|
||||||
|
expect(await screenshareButton.isDisplayed()).to.be.true;
|
||||||
|
await screenshareButton.click();
|
||||||
|
|
||||||
|
await browser.sleep(1000);
|
||||||
|
await utils.waitForElement('.OV_big');
|
||||||
|
|
||||||
|
expect(await utils.getNumberOfElements('video')).equals(1);
|
||||||
|
|
||||||
|
|
||||||
|
await screenshareButton.click();
|
||||||
|
await browser.sleep(1000);
|
||||||
|
|
||||||
|
expect(await utils.getNumberOfElements('video')).equals(1);
|
||||||
|
|
||||||
element = await browser.findElements(By.css('video'));
|
|
||||||
expect(element.length).equals(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should screensharing with audio muted', async () => {
|
it('should screensharing with audio muted', async () => {
|
||||||
let element, isAudioEnabled;
|
let isAudioEnabled;
|
||||||
const getAudioScript = (className: string) => {
|
const getAudioScript = (className: string) => {
|
||||||
return `return document.getElementsByClassName('${className}')[0].srcObject.getAudioTracks()[0].enabled;`;
|
return `return document.getElementsByClassName('${className}')[0].srcObject.getAudioTracks()[0].enabled;`;
|
||||||
};
|
};
|
||||||
|
@ -1135,25 +1299,22 @@ describe('Testing screenshare features', () => {
|
||||||
await screenshareButton.click();
|
await screenshareButton.click();
|
||||||
|
|
||||||
await utils.waitForElement('.screen-type');
|
await utils.waitForElement('.screen-type');
|
||||||
element = await browser.findElements(By.css('video'));
|
expect(await utils.getNumberOfElements('video')).equals(2);
|
||||||
expect(element.length).equals(2);
|
|
||||||
|
|
||||||
isAudioEnabled = await browser.executeScript(getAudioScript('screen-type'));
|
isAudioEnabled = await browser.executeScript(getAudioScript('screen-type'));
|
||||||
expect(isAudioEnabled).to.be.false;
|
expect(isAudioEnabled).to.be.false;
|
||||||
|
|
||||||
await utils.waitForElement('#statusMic');
|
await utils.waitForElement('#statusMic');
|
||||||
element = await browser.findElements(By.id('statusMic'));
|
expect(await utils.getNumberOfElements('#statusMic')).equals(2);
|
||||||
expect(element.length).equals(2);
|
|
||||||
|
|
||||||
// Clicking to screensharing button
|
// Clicking to screensharing button
|
||||||
await screenshareButton.click();
|
await screenshareButton.click();
|
||||||
|
expect(await utils.getNumberOfElements('video')).equals(1);
|
||||||
|
|
||||||
element = await browser.findElements(By.css('video'));
|
|
||||||
expect(element.length).equals(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show and hide CAMERA stream when muting video with screensharing', async () => {
|
it('should show and hide CAMERA stream when muting video with screensharing', async () => {
|
||||||
let element;
|
|
||||||
await browser.get(`${url}&prejoin=false`);
|
await browser.get(`${url}&prejoin=false`);
|
||||||
|
|
||||||
await utils.checkLayoutPresent();
|
await utils.checkLayoutPresent();
|
||||||
|
@ -1164,18 +1325,16 @@ describe('Testing screenshare features', () => {
|
||||||
await screenshareButton.click();
|
await screenshareButton.click();
|
||||||
|
|
||||||
await utils.waitForElement('.OV_big');
|
await utils.waitForElement('.OV_big');
|
||||||
element = await browser.findElements(By.css('video'));
|
expect(await utils.getNumberOfElements('video')).equals(2);
|
||||||
expect(element.length).equals(2);
|
|
||||||
|
|
||||||
const muteVideoButton = await utils.waitForElement('#camera-btn');
|
const muteVideoButton = await utils.waitForElement('#camera-btn');
|
||||||
await muteVideoButton.click();
|
await muteVideoButton.click();
|
||||||
|
|
||||||
element = await browser.findElements(By.css('video'));
|
expect(await utils.getNumberOfElements('video')).equals(1);
|
||||||
expect(element.length).equals(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should screenshare has audio active when camera is muted', async () => {
|
it('should screenshare has audio active when camera is muted', async () => {
|
||||||
let element, isAudioEnabled;
|
let isAudioEnabled;
|
||||||
const audioEnableScript = 'return document.getElementsByTagName("video")[0].srcObject.getAudioTracks()[0].enabled;';
|
const audioEnableScript = 'return document.getElementsByTagName("video")[0].srcObject.getAudioTracks()[0].enabled;';
|
||||||
|
|
||||||
await browser.get(`${url}&prejoin=false`);
|
await browser.get(`${url}&prejoin=false`);
|
||||||
|
@ -1187,19 +1346,15 @@ describe('Testing screenshare features', () => {
|
||||||
expect(await utils.isPresent('#screenshare-btn')).to.be.true;
|
expect(await utils.isPresent('#screenshare-btn')).to.be.true;
|
||||||
await screenshareButton.click();
|
await screenshareButton.click();
|
||||||
|
|
||||||
element = await utils.waitForElement('.OV_big');
|
await utils.waitForElement('.OV_big');
|
||||||
element = await browser.findElements(By.css('video'));
|
expect(await utils.getNumberOfElements('video')).equals(2);
|
||||||
expect(element.length).equals(2);
|
expect(await utils.getNumberOfElements('#statusMic')).equals(1);
|
||||||
|
|
||||||
element = await browser.findElements(By.id('statusMic'));
|
|
||||||
expect(element.length).equals(1);
|
|
||||||
|
|
||||||
// Muting camera video
|
// Muting camera video
|
||||||
const muteVideoButton = await utils.waitForElement('#camera-btn');
|
const muteVideoButton = await utils.waitForElement('#camera-btn');
|
||||||
await muteVideoButton.click();
|
await muteVideoButton.click();
|
||||||
|
|
||||||
element = await browser.findElements(By.css('video'));
|
expect(await utils.getNumberOfElements('video')).equals(1);
|
||||||
expect(element.length).equals(1);
|
|
||||||
|
|
||||||
await browser.sleep(500);
|
await browser.sleep(500);
|
||||||
expect(await utils.isPresent('#statusMic')).to.be.false;
|
expect(await utils.isPresent('#statusMic')).to.be.false;
|
||||||
|
@ -1210,13 +1365,11 @@ describe('Testing screenshare features', () => {
|
||||||
|
|
||||||
// Unmuting camera
|
// Unmuting camera
|
||||||
await muteVideoButton.click();
|
await muteVideoButton.click();
|
||||||
|
await browser.sleep(500);
|
||||||
|
|
||||||
element = await utils.waitForElement('.camera-type');
|
await utils.waitForElement('.camera-type');
|
||||||
element = await browser.findElements(By.css('video'));
|
expect(await utils.getNumberOfElements('video')).equals(2);
|
||||||
expect(element.length).equals(2);
|
expect(await utils.getNumberOfElements('#statusMic')).equals(1);
|
||||||
|
|
||||||
element = await browser.findElements(By.id('statusMic'));
|
|
||||||
expect(element.length).equals(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should camera come back with audio muted when screensharing', async () => {
|
it('should camera come back with audio muted when screensharing', async () => {
|
||||||
|
@ -1235,19 +1388,16 @@ describe('Testing screenshare features', () => {
|
||||||
await screenshareButton.click();
|
await screenshareButton.click();
|
||||||
|
|
||||||
await utils.waitForElement('.screen-type');
|
await utils.waitForElement('.screen-type');
|
||||||
element = await browser.findElements(By.css('video'));
|
expect(await utils.getNumberOfElements('video')).equals(2);
|
||||||
expect(element.length).equals(2);
|
expect(await utils.getNumberOfElements('#statusMic')).equals(1);
|
||||||
|
|
||||||
element = await browser.findElements(By.id('statusMic'));
|
|
||||||
expect(element.length).equals(1);
|
|
||||||
|
|
||||||
// Mute camera
|
// Mute camera
|
||||||
const muteVideoButton = await utils.waitForElement('#camera-btn');
|
const muteVideoButton = await utils.waitForElement('#camera-btn');
|
||||||
await muteVideoButton.click();
|
await muteVideoButton.click();
|
||||||
|
|
||||||
element = await browser.findElements(By.css('video'));
|
|
||||||
expect(element.length).equals(1);
|
|
||||||
|
|
||||||
|
expect(await utils.getNumberOfElements('video')).equals(1);
|
||||||
expect(await utils.isPresent('#statusMic')).to.be.false;
|
expect(await utils.isPresent('#statusMic')).to.be.false;
|
||||||
|
|
||||||
// Checking if audio is muted after join the room
|
// Checking if audio is muted after join the room
|
||||||
|
@ -1259,8 +1409,8 @@ describe('Testing screenshare features', () => {
|
||||||
await muteAudioButton.click();
|
await muteAudioButton.click();
|
||||||
|
|
||||||
await utils.waitForElement('#statusMic');
|
await utils.waitForElement('#statusMic');
|
||||||
element = await browser.findElements(By.id('statusMic'));
|
expect(await utils.getNumberOfElements('#statusMic')).equals(1);
|
||||||
expect(element.length).equals(1);
|
|
||||||
|
|
||||||
isAudioEnabled = await browser.executeScript(getAudioScript('screen-type'));
|
isAudioEnabled = await browser.executeScript(getAudioScript('screen-type'));
|
||||||
expect(isAudioEnabled).to.be.false;
|
expect(isAudioEnabled).to.be.false;
|
||||||
|
@ -1269,11 +1419,9 @@ describe('Testing screenshare features', () => {
|
||||||
await muteVideoButton.click();
|
await muteVideoButton.click();
|
||||||
|
|
||||||
await utils.waitForElement('.camera-type');
|
await utils.waitForElement('.camera-type');
|
||||||
element = await browser.findElements(By.css('video'));
|
expect(await utils.getNumberOfElements('video')).equals(2);
|
||||||
expect(element.length).equals(2);
|
expect(await utils.getNumberOfElements('#statusMic')).equals(2);
|
||||||
|
|
||||||
element = await browser.findElements(By.id('statusMic'));
|
|
||||||
expect(element.length).equals(2);
|
|
||||||
|
|
||||||
isAudioEnabled = await browser.executeScript(getAudioScript('camera-type'));
|
isAudioEnabled = await browser.executeScript(getAudioScript('camera-type'));
|
||||||
expect(isAudioEnabled).to.be.false;
|
expect(isAudioEnabled).to.be.false;
|
||||||
|
@ -1471,9 +1619,7 @@ describe('Testing panels', () => {
|
||||||
|
|
||||||
// Close chat panel
|
// Close chat panel
|
||||||
await chatButton.click();
|
await chatButton.click();
|
||||||
await browser.findElements(By.className('input-container'));
|
expect(await utils.getNumberOfElements('.input-container')).equals(0);
|
||||||
expect(await utils.isPresent('.input-container')).to.be.false;
|
|
||||||
|
|
||||||
expect(await utils.isPresent('messages-container')).to.be.false;
|
expect(await utils.isPresent('messages-container')).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1557,7 +1703,6 @@ describe('Testing CHAT features', () => {
|
||||||
await utils.waitForElement('.input-container');
|
await utils.waitForElement('.input-container');
|
||||||
expect(await utils.isPresent('.input-container')).to.be.true;
|
expect(await utils.isPresent('.input-container')).to.be.true;
|
||||||
|
|
||||||
|
|
||||||
const input = await utils.waitForElement('#chat-input');
|
const input = await utils.waitForElement('#chat-input');
|
||||||
await input.sendKeys('demos.openvidu.io');
|
await input.sendKeys('demos.openvidu.io');
|
||||||
|
|
||||||
|
@ -1568,6 +1713,62 @@ describe('Testing CHAT features', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Testing TOOLBAR features', () => {
|
||||||
|
let browser: WebDriver;
|
||||||
|
let utils: OpenViduComponentsPO;
|
||||||
|
async function createChromeBrowser(): Promise<WebDriver> {
|
||||||
|
return await new Builder()
|
||||||
|
.forBrowser(WebComponentConfig.browserName)
|
||||||
|
.withCapabilities(WebComponentConfig.browserCapabilities)
|
||||||
|
.setChromeOptions(WebComponentConfig.browserOptions)
|
||||||
|
.usingServer(WebComponentConfig.seleniumAddress)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
browser = await createChromeBrowser();
|
||||||
|
utils = new OpenViduComponentsPO(browser);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await browser.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should mute and unmute the local microphone', async () => {
|
||||||
|
await browser.get(`${url}&prejoin=false`);
|
||||||
|
|
||||||
|
await utils.checkLayoutPresent();
|
||||||
|
|
||||||
|
const micButton = await utils.waitForElement('#mic-btn');
|
||||||
|
await micButton.click();
|
||||||
|
|
||||||
|
await utils.waitForElement('#mic-btn #mic_off');
|
||||||
|
expect(await utils.isPresent('#mic-btn #mic_off')).to.be.true;
|
||||||
|
|
||||||
|
await micButton.click();
|
||||||
|
|
||||||
|
await utils.waitForElement('#mic-btn #mic');
|
||||||
|
expect(await utils.isPresent('#mic-btn #mic')).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should mute and unmute the local camera', async () => {
|
||||||
|
await browser.get(`${url}&prejoin=false`);
|
||||||
|
|
||||||
|
await utils.checkLayoutPresent();
|
||||||
|
|
||||||
|
const cameraButton = await utils.waitForElement('#camera-btn');
|
||||||
|
await cameraButton.click();
|
||||||
|
|
||||||
|
await utils.waitForElement('#camera-btn #videocam_off');
|
||||||
|
expect(await utils.isPresent('#camera-btn #videocam_off')).to.be.true;
|
||||||
|
|
||||||
|
await cameraButton.click();
|
||||||
|
|
||||||
|
await utils.waitForElement('#camera-btn #videocam');
|
||||||
|
expect(await utils.isPresent('#camera-btn #videocam')).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('Testing video is playing', () => {
|
describe('Testing video is playing', () => {
|
||||||
let browser: WebDriver;
|
let browser: WebDriver;
|
||||||
|
@ -1805,7 +2006,6 @@ describe('Testing PRO features with OpenVidu CE', () => {
|
||||||
|
|
||||||
// Expect it shows the pro feature alert
|
// Expect it shows the pro feature alert
|
||||||
await utils.chceckProFeatureAlertIsPresent();
|
await utils.chceckProFeatureAlertIsPresent();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should SHOW the CAPTIONS PRO feature dialog', async () => {
|
it('should SHOW the CAPTIONS PRO feature dialog', async () => {
|
||||||
|
@ -1853,7 +2053,6 @@ describe('Testing PRO features with OpenVidu CE', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO:
|
* TODO:
|
||||||
* The following E2E TESTS only work with OpenVidu PRO.
|
* The following E2E TESTS only work with OpenVidu PRO.
|
||||||
|
@ -2031,3 +2230,103 @@ describe('Testing PRO features with OpenVidu CE', () => {
|
||||||
|
|
||||||
// });
|
// });
|
||||||
// });
|
// });
|
||||||
|
|
||||||
|
describe('Testing WITHOUT MEDIA DEVICES permissions', () => {
|
||||||
|
let browser: WebDriver;
|
||||||
|
let utils: OpenViduComponentsPO;
|
||||||
|
async function createChromeBrowser(): Promise<WebDriver> {
|
||||||
|
return await new Builder()
|
||||||
|
.forBrowser(WebComponentConfig.browserName)
|
||||||
|
.withCapabilities(WebComponentConfig.browserCapabilities)
|
||||||
|
.setChromeOptions(getBrowserOptionsWithoutDevices())
|
||||||
|
.usingServer(WebComponentConfig.seleniumAddress)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
browser = await createChromeBrowser();
|
||||||
|
utils = new OpenViduComponentsPO(browser);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await browser.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to ACCESS to PREJOIN page', async () => {
|
||||||
|
await browser.get(`${url}`);
|
||||||
|
|
||||||
|
await utils.checkPrejoinIsPresent();
|
||||||
|
|
||||||
|
let button = await utils.waitForElement('#camera-button');
|
||||||
|
expect(await button.isEnabled()).to.be.false;
|
||||||
|
|
||||||
|
button = await utils.waitForElement('#microphone-button');
|
||||||
|
expect(await button.isEnabled()).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to ACCESS to ROOM page', async () => {
|
||||||
|
await browser.get(`${url}`);
|
||||||
|
|
||||||
|
await utils.checkPrejoinIsPresent();
|
||||||
|
|
||||||
|
await utils.clickOn('#join-button');
|
||||||
|
|
||||||
|
await utils.checkSessionIsPresent();
|
||||||
|
|
||||||
|
await utils.checkToolbarIsPresent();
|
||||||
|
|
||||||
|
let button = await utils.waitForElement('#camera-btn');
|
||||||
|
expect(await button.isEnabled()).to.be.false;
|
||||||
|
|
||||||
|
button = await utils.waitForElement('#mic-btn');
|
||||||
|
expect(await button.isEnabled()).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to ACCESS to ROOM page without prejoin', async () => {
|
||||||
|
await browser.get(`${url}&prejoin=false`);
|
||||||
|
|
||||||
|
await utils.checkSessionIsPresent();
|
||||||
|
|
||||||
|
await utils.checkToolbarIsPresent();
|
||||||
|
|
||||||
|
let button = await utils.waitForElement('#camera-btn');
|
||||||
|
expect(await button.isEnabled()).to.be.false;
|
||||||
|
|
||||||
|
button = await utils.waitForElement('#mic-btn');
|
||||||
|
expect(await button.isEnabled()).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should the settings buttons be disabled', async () => {
|
||||||
|
await browser.get(`${url}&prejoin=false`);
|
||||||
|
|
||||||
|
await utils.checkToolbarIsPresent();
|
||||||
|
|
||||||
|
// Open more options menu
|
||||||
|
await utils.clickOn('#more-options-btn');
|
||||||
|
|
||||||
|
await browser.sleep(500);
|
||||||
|
|
||||||
|
// Checking if fullscreen button is not present
|
||||||
|
await utils.waitForElement('.mat-menu-content');
|
||||||
|
expect(await utils.isPresent('.mat-menu-content')).to.be.true;
|
||||||
|
|
||||||
|
await utils.clickOn('#toolbar-settings-btn');
|
||||||
|
|
||||||
|
await browser.sleep(500);
|
||||||
|
|
||||||
|
await utils.waitForElement('.settings-container');
|
||||||
|
expect(await utils.isPresent('.settings-container')).to.be.true;
|
||||||
|
|
||||||
|
await utils.clickOn('#video-opt');
|
||||||
|
expect(await utils.isPresent('ov-video-devices-select')).to.be.true;
|
||||||
|
|
||||||
|
let button = await utils.waitForElement('#camera-button');
|
||||||
|
expect(await button.isEnabled()).to.be.false;
|
||||||
|
|
||||||
|
await utils.clickOn('#audio-opt');
|
||||||
|
expect(await utils.isPresent('ov-audio-devices-select')).to.be.true;
|
||||||
|
|
||||||
|
button = await utils.waitForElement('#microphone-button');
|
||||||
|
expect(await button.isEnabled()).to.be.false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -22,17 +22,6 @@
|
||||||
height: -moz-available;
|
height: -moz-available;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
* Copyright (c) 2017 TokBox, Inc.
|
|
||||||
* Released under the MIT license
|
|
||||||
* http://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OT Base styles
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Root OT object, this is where our CSS reset happens */
|
|
||||||
.OT_root,
|
.OT_root,
|
||||||
.OT_root * {
|
.OT_root * {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
|
@ -43,179 +32,6 @@
|
||||||
vertical-align: baseline;
|
vertical-align: baseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.OT_dialog-centering {
|
|
||||||
display: table;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_dialog-centering-child {
|
|
||||||
display: table-cell;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_dialog {
|
|
||||||
position: relative;
|
|
||||||
-webkit-box-sizing: border-box;
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin-right: auto;
|
|
||||||
margin-left: auto;
|
|
||||||
|
|
||||||
color: #fff;
|
|
||||||
font-family: 'Ubuntu', sans-serif;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_dialog * {
|
|
||||||
font-family: inherit;
|
|
||||||
-webkit-box-sizing: inherit;
|
|
||||||
box-sizing: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_closeButton {
|
|
||||||
color: #999999;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 32px;
|
|
||||||
line-height: 36px;
|
|
||||||
position: absolute;
|
|
||||||
right: 18px;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_dialog-messages {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_dialog-messages-main {
|
|
||||||
margin-bottom: 36px;
|
|
||||||
line-height: 36px;
|
|
||||||
|
|
||||||
font-weight: 300;
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_dialog-messages-minor {
|
|
||||||
margin-bottom: 18px;
|
|
||||||
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 18px;
|
|
||||||
color: #a4a4a4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_dialog-messages-minor strong {
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_dialog-actions-card {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_dialog-button-title {
|
|
||||||
margin-bottom: 18px;
|
|
||||||
line-height: 18px;
|
|
||||||
|
|
||||||
font-weight: 300;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
.OT_dialog-button-title label {
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_dialog-button-title a,
|
|
||||||
.OT_dialog-button-title a:link,
|
|
||||||
.OT_dialog-button-title a:active {
|
|
||||||
color: #02a1de;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_dialog-button-title strong {
|
|
||||||
color: #ffffff;
|
|
||||||
font-weight: 100;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_dialog-button {
|
|
||||||
display: inline-block;
|
|
||||||
|
|
||||||
margin-bottom: 18px;
|
|
||||||
padding: 0 1em;
|
|
||||||
|
|
||||||
background-color: #1ca3dc;
|
|
||||||
text-align: center;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_dialog-button:disabled {
|
|
||||||
cursor: not-allowed;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_dialog-button-large {
|
|
||||||
line-height: 36px;
|
|
||||||
padding-top: 9px;
|
|
||||||
padding-bottom: 9px;
|
|
||||||
|
|
||||||
font-weight: 100;
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_dialog-button-small {
|
|
||||||
line-height: 18px;
|
|
||||||
padding-top: 9px;
|
|
||||||
padding-bottom: 9px;
|
|
||||||
|
|
||||||
background-color: #444444;
|
|
||||||
color: #999999;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_dialog-progress-bar {
|
|
||||||
display: inline-block; /* prevents margin collapse */
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 5px;
|
|
||||||
margin-bottom: 41px;
|
|
||||||
|
|
||||||
border: 1px solid #4e4e4e;
|
|
||||||
height: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_dialog-progress-bar-fill {
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
background-color: #29a4da;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_dialog-plugin-upgrading .OT_dialog-plugin-upgrade-percentage {
|
|
||||||
line-height: 54px;
|
|
||||||
|
|
||||||
font-size: 48px;
|
|
||||||
font-weight: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Helpers */
|
|
||||||
|
|
||||||
.OT_centered {
|
|
||||||
position: fixed;
|
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_dialog-hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_dialog-button-block {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_dialog-no-natural-margin {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Publisher and Subscriber styles */
|
|
||||||
|
|
||||||
.OT_publisher,
|
.OT_publisher,
|
||||||
.OT_subscriber {
|
.OT_subscriber {
|
||||||
|
@ -236,604 +52,3 @@
|
||||||
-webkit-transform-origin: 0 0;
|
-webkit-transform-origin: 0 0;
|
||||||
transform-origin: 0 0;
|
transform-origin: 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.OT_subscriber_error {
|
|
||||||
background-color: #000;
|
|
||||||
color: #fff;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_subscriber_error > p {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The publisher/subscriber name/mute background */
|
|
||||||
.OT_publisher .OT_bar,
|
|
||||||
.OT_subscriber .OT_bar,
|
|
||||||
.OT_publisher .OT_name,
|
|
||||||
.OT_subscriber .OT_name,
|
|
||||||
.OT_publisher .OT_archiving,
|
|
||||||
.OT_subscriber .OT_archiving,
|
|
||||||
.OT_publisher .OT_archiving-status,
|
|
||||||
.OT_subscriber .OT_archiving-status,
|
|
||||||
.OT_publisher .OT_archiving-light-box,
|
|
||||||
.OT_subscriber .OT_archiving-light-box {
|
|
||||||
-webkit-box-sizing: border-box;
|
|
||||||
-ms-box-sizing: border-box;
|
|
||||||
box-sizing: border-box;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
display: block;
|
|
||||||
height: 34px;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_publisher .OT_bar,
|
|
||||||
.OT_subscriber .OT_bar {
|
|
||||||
background: rgba(0, 0, 0, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_publisher .OT_edge-bar-item,
|
|
||||||
.OT_subscriber .OT_edge-bar-item {
|
|
||||||
z-index: 1; /* required to get audio level meter underneath */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The publisher/subscriber name panel/archiving status bar */
|
|
||||||
.OT_publisher .OT_name,
|
|
||||||
.OT_subscriber .OT_name {
|
|
||||||
background-color: transparent;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 34px;
|
|
||||||
font-weight: normal;
|
|
||||||
padding: 0 4px 0 36px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_publisher .OT_archiving-status,
|
|
||||||
.OT_subscriber .OT_archiving-status {
|
|
||||||
background: rgba(0, 0, 0, 0.4);
|
|
||||||
top: auto;
|
|
||||||
bottom: 0;
|
|
||||||
left: 34px;
|
|
||||||
padding: 0 4px;
|
|
||||||
color: rgba(255, 255, 255, 0.8);
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 34px;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_micro .OT_archiving-status,
|
|
||||||
.OT_micro:hover .OT_archiving-status,
|
|
||||||
.OT_mini .OT_archiving-status,
|
|
||||||
.OT_mini:hover .OT_archiving-status {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_publisher .OT_archiving-light-box,
|
|
||||||
.OT_subscriber .OT_archiving-light-box {
|
|
||||||
background: rgba(0, 0, 0, 0.4);
|
|
||||||
top: auto;
|
|
||||||
bottom: 0;
|
|
||||||
right: auto;
|
|
||||||
width: 34px;
|
|
||||||
height: 34px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_archiving-light {
|
|
||||||
width: 7px;
|
|
||||||
height: 7px;
|
|
||||||
border-radius: 30px;
|
|
||||||
position: absolute;
|
|
||||||
top: 14px;
|
|
||||||
left: 14px;
|
|
||||||
background-color: #575757;
|
|
||||||
-webkit-box-shadow: 0 0 5px 1px #575757;
|
|
||||||
box-shadow: 0 0 5px 1px #575757;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_archiving-light.OT_active {
|
|
||||||
background-color: #970d13;
|
|
||||||
animation: OT_pulse 1.3s ease-in;
|
|
||||||
-webkit-animation: OT_pulse 1.3s ease-in;
|
|
||||||
-moz-animation: OT_pulse 1.3s ease-in;
|
|
||||||
-webkit-animation: OT_pulse 1.3s ease-in;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
-webkit-animation-iteration-count: infinite;
|
|
||||||
-moz-animation-iteration-count: infinite;
|
|
||||||
-webkit-animation-iteration-count: infinite;
|
|
||||||
}
|
|
||||||
@-webkit-keyframes OT_pulse {
|
|
||||||
0% {
|
|
||||||
-webkit-box-shadow: 0 0 0px 0px #c70019;
|
|
||||||
box-shadow: 0 0 0px 0px #c70019;
|
|
||||||
}
|
|
||||||
|
|
||||||
30% {
|
|
||||||
-webkit-box-shadow: 0 0 5px 1px #c70019;
|
|
||||||
box-shadow: 0 0 5px 1px #c70019;
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
-webkit-box-shadow: 0 0 5px 1px #c70019;
|
|
||||||
box-shadow: 0 0 5px 1px #c70019;
|
|
||||||
}
|
|
||||||
|
|
||||||
80% {
|
|
||||||
-webkit-box-shadow: 0 0 0px 0px #c70019;
|
|
||||||
box-shadow: 0 0 0px 0px #c70019;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
-webkit-box-shadow: 0 0 0px 0px #c70019;
|
|
||||||
box-shadow: 0 0 0px 0px #c70019;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@-webkit-keyframes OT_pulse {
|
|
||||||
0% {
|
|
||||||
-webkit-box-shadow: 0 0 0px 0px #c70019;
|
|
||||||
box-shadow: 0 0 0px 0px #c70019;
|
|
||||||
}
|
|
||||||
|
|
||||||
30% {
|
|
||||||
-webkit-box-shadow: 0 0 5px 1px #c70019;
|
|
||||||
box-shadow: 0 0 5px 1px #c70019;
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
-webkit-box-shadow: 0 0 5px 1px #c70019;
|
|
||||||
box-shadow: 0 0 5px 1px #c70019;
|
|
||||||
}
|
|
||||||
|
|
||||||
80% {
|
|
||||||
-webkit-box-shadow: 0 0 0px 0px #c70019;
|
|
||||||
box-shadow: 0 0 0px 0px #c70019;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
-webkit-box-shadow: 0 0 0px 0px #c70019;
|
|
||||||
box-shadow: 0 0 0px 0px #c70019;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_mini .OT_bar,
|
|
||||||
.OT_bar.OT_mode-mini,
|
|
||||||
.OT_bar.OT_mode-mini-auto {
|
|
||||||
bottom: 0;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_mini .OT_name.OT_mode-off,
|
|
||||||
.OT_mini .OT_name.OT_mode-on,
|
|
||||||
.OT_mini .OT_name.OT_mode-auto,
|
|
||||||
.OT_mini:hover .OT_name.OT_mode-auto {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_publisher .OT_name,
|
|
||||||
.OT_subscriber .OT_name {
|
|
||||||
left: 10px;
|
|
||||||
right: 37px;
|
|
||||||
height: 34px;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_publisher .OT_mute,
|
|
||||||
.OT_subscriber .OT_mute {
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
text-align: center;
|
|
||||||
text-indent: -9999em;
|
|
||||||
background-color: transparent;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_publisher .OT_mute,
|
|
||||||
.OT_subscriber .OT_mute {
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
border-left: 1px solid rgba(255, 255, 255, 0.2);
|
|
||||||
height: 36px;
|
|
||||||
width: 37px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_mini .OT_mute,
|
|
||||||
.OT_publisher.OT_mini .OT_mute.OT_mode-auto.OT_mode-on-hold,
|
|
||||||
.OT_subscriber.OT_mini .OT_mute.OT_mode-auto.OT_mode-on-hold {
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
right: auto;
|
|
||||||
margin-top: -18px;
|
|
||||||
margin-left: -18.5px;
|
|
||||||
border-left: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_publisher .OT_mute {
|
|
||||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAcCAMAAAC02HQrAAAA1VBMVEUAAAD3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pn3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pn3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj3+Pj39/j3+Pj3+Pn4+Pk/JRMlAAAAQ3RSTlMABAUHCQoLDhAQERwdHiAjLjAxOD9ASFBRVl1mbnZ6fH2LjI+QkaWqrrC1uLzAwcXJycrL1NXj5Ofo6u3w9fr7/P3+d4M3+QAAAQBJREFUGBlVwYdCglAABdCLlr5Unijm3hMUtBzlBLSr//9JgUToOQgVJgceJgU8aHgMeA38K50ZOpcQmTPwcyXn+JM8M3JJIqQypiIkeXelTyIkGZPwKS1NMia1lgKTVkaE3oQQGYsmHNqSMWnTgUFbMiZtGlD2dpaxrL1XgM0i4ZK8MeAmFhsAs29MGZniawagS63oMOQUNXYB5D0D1RMDpyoMLw/fiE2og/V+PVDR5AiBl0/2Uwik+vx4xV3a5G5Ye68Nd1czjUjZckm6VhmPciRzeCZICjwTJAViQq+3e+St167rAoHK8sLYZVkBYPCZAZ/eGa+2R5LH7Wrc0YFf/O9J3yBDFaoAAAAASUVORK5CYII=);
|
|
||||||
background-position: 9px 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_publisher .OT_mute.OT_active {
|
|
||||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAdCAYAAABFRCf7AAADcElEQVRIiaWVXWhcRRTHf7NNd2aDtUKMIjTpg4ufFIuiUOmDEWm0Vi3VYhXRqIggQh4sWJFSig9+oOhTKSpIRUWMIBIr2kptoTbgU6ooxCiIjR+14kcJmf9sNceHnd3ebnc3Uv9wuXfOzPzmnDMz5zozGwdWAbc65w5RUJQ8cC2wDJgFJioh/MJCMrNxq2vOzK4HmIvRRemxKP0RJWt53o7S+d2Yzsx6gQ+AIUDAnUqpBLzXZd4RYFUlhB/bdZacc3PAOmAcCMC7wfvFwLNdoAPAyx09bXyYWRl4E7gDmAdGlNKFwLYu8GolhO9O87RJd64GbMrgEvB68P4osMWdXLtVV7czlooNpVRWSs8DO7NpR/B+3rBHsvetCgtCMTxwQCm9BbyQrc8F7/uBex3uRCeXO0PrUZ4NfKyUPgWeyj3bg/crDNsIRGwBaJQGorQ3Svdn2wHgc2BUKb0DPJHtjwfvbwRucc7tz+N+i9LFUdoXpfVN36I0CVwBTFI/q9e1LPxT8P4qYEdu70q12mYzWw1MYQzjeJF6zq+shHC4B7jklOBPP/TzSunh4P0DwKvAfb5c9krpe+CcwsEoZdbhEvBM9wxRAl5RShcA9wAngE3B+8tLpdLuwrhp4MNmK0pfRWkySr7NXS8+L5nZbWZWy/Vin1IaitJnUTqvwevJ71lgSSWEFKUfHG7Q2m/xqFJaGry/GXgfGPLl8mJgrXPur2JoUC8Qy3OpG+sAbGhEKT0ErAWOA6uBPWbW1wr9BOgFbgKezot0kAPYqJQA1gC/A9cA+82svzksSn1R+jNKX0SpnM/e1x3yqig92JhrZivM7FjO8bSZLSuCR/Ok16K0KMNHojQWpYko7Y7S1igN5PE3ROl4lNaZ2UVmNpPBU01orvZvZPCeKFXbBR+lEKVtUapFaSZKg9njqpl9aWYTrmXCImA7sCWb9lK/jj9TrwkrgA1AH3AQuKsSwkzbrLfxpgpsBtYDxf/R3xm2ExirhNCuHHZXTsmRwiat+S/zSt06eysVA/4pmGr/G3qm6ik28v29FKgCg8BS6pvS0KNRGgZ+Bb4FpsxsOkfUlMuwDcBWYOUZOHYM2AU8WQmhBifDv70O7PjX7KZ+4G7g3FM8zd6uBIaBy4AqxnIcZwFLCovPAhE4Sj38b4BDwEeVEFKD9S94Khjn486v3QAAAABJRU5ErkJggg==);
|
|
||||||
background-position: 9px 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_subscriber .OT_mute {
|
|
||||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAATCAYAAAB7u5a2AAABx0lEQVQ4jaWUv48NURiGn3ONmCs32ZBd28ht1gqyZAkF21ylQkEiSp2ehpDlD1BoFGqqVdJohYKI7MaPxMoVNghCWMF+7ybLUewnOXfcMWO9yeQ857zne8+XmZOBGjJpr0kvTIomvTZpS526UCO4DUwD64FjwCFgqZnnR+oc8LfgzKQ73vGsr42ZtGjSQFV9o8KfBCacZwCaef4YmAf2rzjcpN3A2WSpm/AssKcqPDNpDBjs410CViXzTwk/A7b1C4wxDgOngAsZcAXY2buDfp/6S4F3lDS8DjgBzDWAjX/Y/e/QgYS/AhsKHa+OMQ6GEJ4Cj4BOAxgq6aCowyZtdf4OtAr+FHDO+R4wWnVbihr3cQnICt4boO38GWj9a/icjwOACt4m4K3zEPA+AxaAtTWCnwN3lzHkEL8V/OPAGud9wK2GF9XR1Wae/1zG2AI+pGYI4VUIoRtjHAc2A9cz4LRPevYCZ+i9/4sJt4GXJU10gaPAzdI2TTro/5Tfz8XEe2LSZGmxq/SDNvP8BnA5WRrx4BwYBe6vONx1EnjovGvBLAAd4Adwuyq8UiaNmDTvr+a8SQ9MuvbfwckBHZPe+QEfTdpep+4XZmPBHiHgz74AAAAASUVORK5CYII=);
|
|
||||||
background-position: 8px 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_subscriber .OT_mute.OT_active {
|
|
||||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAUCAYAAACXtf2DAAACtklEQVQ4jZ2VSYiURxTHf+/T9Nc9iRrBuYySmIsXUU9iFMEFERRBvAjJLUQi5ioiHvSScfTmgqC4XAT1ZIgLuJHkICaaQAgKI2hAUBT30bjUq7bbv4eukXK029F3+eqtv/fqK6qQdEnSNUmT6CDB/bvgfjO4N9zj2RD8007xg1IABkwEzkma0qb4PGAPMBZYLtSD8eNwAEjqTlNI0gNJM4YU7w7ut4O7gvuhZFsR3C8NC5BBLiTIY0mzM8AvqbiC++pk+zLpE95XuwAws3vAQuBPYDRwWtL84P4tsDSLv5oaug4EYOawAMF9jMdoLxqNZcDvQA04UVYqL4G/svj7AF21mhJscrvCksYBFO7xc2AAGGg2mrdjvf4rcAyomNn+slLZmUEGBgsYdh945xZJmgvckDSrEJpK6ySBgV6q12O8ABwGPjGzfWWlsjdN9rpjoSfA+DYDXARGAksK4Is3XC1Ub4z1f4CDQGFmu6tleQSYk0U+p7WVeefLJc00s4fAeWB6Qeunvj0m2ugx9gO7kmlrtSxvBfcy6fXUZS6rgG/S+jLQUwCVNmMC9HqM14EtSe+rluWazN8YEv8IqKZ1E1qnaIDO0ucx3gX6kv6TpM3AM+D/IbGjgP60/gq4WQA33gMA2OQxPgHWJX1ttSwL4FAeZGYLgB2SasBs4A8L7qOBf9M0uXQB3a+TMYSmVctyDrA9mfcBK82smSdKWgCcAaa1bTm4fxbc/8uuCQX3RanAD5Ka6Wo5IGnE0HxJPZ03pQX5Org3MsD3AO5xXLPZXJ9BjkrqdFg6QjZkgG3Jtsw93pG0VFI9QU5K6voYQBHcTydAfwheBI9HgvvPAJIWS3qeIL9JGvUxkO7gfi1BrqTvwkG/pPmSnibIqTzXPgAyEVgBjAEu1qrVPbk/PVTHgb/NbPGg/RVIzOQqzSTBaQAAAABJRU5ErkJggg==);
|
|
||||||
background-position: 7px 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Styles for display modes
|
|
||||||
*
|
|
||||||
* Note: It's important that these completely control the display and opacity
|
|
||||||
* attributes, no other selectors should atempt to change them.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Default display mode transitions for various chrome elements */
|
|
||||||
.OT_publisher .OT_edge-bar-item,
|
|
||||||
.OT_subscriber .OT_edge-bar-item {
|
|
||||||
-webkit-transition-property: top, bottom, opacity;
|
|
||||||
transition-property: top, bottom, opacity;
|
|
||||||
-webkit-transition-duration: 0.5s;
|
|
||||||
transition-duration: 0.5s;
|
|
||||||
-webkit-transition-timing-function: ease-in;
|
|
||||||
transition-timing-function: ease-in;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_publisher .OT_edge-bar-item.OT_mode-off,
|
|
||||||
.OT_subscriber .OT_edge-bar-item.OT_mode-off,
|
|
||||||
.OT_publisher .OT_edge-bar-item.OT_mode-auto,
|
|
||||||
.OT_subscriber .OT_edge-bar-item.OT_mode-auto,
|
|
||||||
.OT_publisher .OT_edge-bar-item.OT_mode-mini-auto,
|
|
||||||
.OT_subscriber .OT_edge-bar-item.OT_mode-mini-auto {
|
|
||||||
top: -25px;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_publisher .OT_edge-bar-item.OT_mode-off,
|
|
||||||
.OT_subscriber .OT_edge-bar-item.OT_mode-off {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_mini .OT_mute.OT_mode-auto,
|
|
||||||
.OT_publisher .OT_mute.OT_mode-mini-auto,
|
|
||||||
.OT_subscriber .OT_mute.OT_mode-mini-auto {
|
|
||||||
top: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_publisher .OT_edge-bar-item.OT_edge-bottom.OT_mode-off,
|
|
||||||
.OT_subscriber .OT_edge-bar-item.OT_edge-bottom.OT_mode-off,
|
|
||||||
.OT_publisher .OT_edge-bar-item.OT_edge-bottom.OT_mode-auto,
|
|
||||||
.OT_subscriber .OT_edge-bar-item.OT_edge-bottom.OT_mode-auto,
|
|
||||||
.OT_publisher .OT_edge-bar-item.OT_edge-bottom.OT_mode-mini-auto,
|
|
||||||
.OT_subscriber .OT_edge-bar-item.OT_edge-bottom.OT_mode-mini-auto {
|
|
||||||
top: auto;
|
|
||||||
bottom: -25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_publisher .OT_edge-bar-item.OT_mode-on,
|
|
||||||
.OT_subscriber .OT_edge-bar-item.OT_mode-on,
|
|
||||||
.OT_publisher .OT_edge-bar-item.OT_mode-auto.OT_mode-on-hold,
|
|
||||||
.OT_subscriber .OT_edge-bar-item.OT_mode-auto.OT_mode-on-hold,
|
|
||||||
.OT_publisher:hover .OT_edge-bar-item.OT_mode-auto,
|
|
||||||
.OT_subscriber:hover .OT_edge-bar-item.OT_mode-auto,
|
|
||||||
.OT_publisher:hover .OT_edge-bar-item.OT_mode-mini-auto,
|
|
||||||
.OT_subscriber:hover .OT_edge-bar-item.OT_mode-mini-auto {
|
|
||||||
top: 0;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_mini .OT_mute.OT_mode-on,
|
|
||||||
.OT_mini:hover .OT_mute.OT_mode-auto,
|
|
||||||
.OT_mute.OT_mode-mini,
|
|
||||||
.OT_root:hover .OT_mute.OT_mode-mini-auto {
|
|
||||||
top: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_publisher .OT_edge-bar-item.OT_edge-bottom.OT_mode-on,
|
|
||||||
.OT_subscriber .OT_edge-bar-item.OT_edge-bottom.OT_mode-on,
|
|
||||||
.OT_publisher:hover .OT_edge-bar-item.OT_edge-bottom.OT_mode-auto,
|
|
||||||
.OT_subscriber:hover .OT_edge-bar-item.OT_edge-bottom.OT_mode-auto {
|
|
||||||
top: auto;
|
|
||||||
bottom: 0;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Load animation */
|
|
||||||
.OT_root .OT_video-loading {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: none;
|
|
||||||
|
|
||||||
background-color: rgba(0, 0, 0, 0.75);
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_root .OT_video-loading .OT_video-loading-spinner {
|
|
||||||
background: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0yMCAtMjAgMjQwIDI0MCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4Mj0iMCIgeTI9IjEiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2ZmZiIgc3RvcC1vcGFjaXR5PSIwIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjZmZmIiBzdG9wLW9wYWNpdHk9IjAiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iYiIgeDE9IjEiIHgyPSIwIiB5Mj0iMSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZmZmIiBzdG9wLW9wYWNpdHk9IjAiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNmZmYiIHN0b3Atb3BhY2l0eT0iLjA4Ii8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImMiIHgxPSIxIiB4Mj0iMCIgeTE9IjEiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2ZmZiIgc3RvcC1vcGFjaXR5PSIuMDgiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNmZmYiIHN0b3Atb3BhY2l0eT0iLjE2Ii8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImQiIHgyPSIwIiB5MT0iMSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZmZmIiBzdG9wLW9wYWNpdHk9Ii4xNiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2ZmZiIgc3RvcC1vcGFjaXR5PSIuMzMiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iZSIgeDI9IjEiIHkxPSIxIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNmZmYiIHN0b3Atb3BhY2l0eT0iLjMzIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjZmZmIiBzdG9wLW9wYWNpdHk9Ii42NiIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJmIiB4Mj0iMSIgeTI9IjEiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2ZmZiIgc3RvcC1vcGFjaXR5PSIuNjYiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNmZmYiLz48L2xpbmVhckdyYWRpZW50PjxtYXNrIGlkPSJnIj48ZyBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjQwIj48cGF0aCBzdHJva2U9InVybCgjYSkiIGQ9Ik04Ni42LTUwYTEwMCAxMDAgMCAwIDEgMCAxMDAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEwMCAxMDApIi8+PHBhdGggc3Ryb2tlPSJ1cmwoI2IpIiBkPSJNODYuNiA1MEExMDAgMTAwIDAgMCAxIDAgMTAwIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMDAgMTAwKSIvPjxwYXRoIHN0cm9rZT0idXJsKCNjKSIgZD0iTTAgMTAwYTEwMCAxMDAgMCAwIDEtODYuNi01MCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAwIDEwMCkiLz48cGF0aCBzdHJva2U9InVybCgjZCkiIGQ9Ik0tODYuNiA1MGExMDAgMTAwIDAgMCAxIDAtMTAwIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMDAgMTAwKSIvPjxwYXRoIHN0cm9rZT0idXJsKCNlKSIgZD0iTS04Ni42LTUwQTEwMCAxMDAgMCAwIDEgMC0xMDAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEwMCAxMDApIi8+PHBhdGggc3Ryb2tlPSJ1cmwoI2YpIiBkPSJNMC0xMDBhMTAwIDEwMCAwIDAgMSA4Ni42IDUwIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMDAgMTAwKSIvPjwvZz48L21hc2s+PC9kZWZzPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHg9Ii0yMCIgeT0iLTIwIiBtYXNrPSJ1cmwoI2cpIiBmaWxsPSIjZmZmIi8+PC9zdmc+)
|
|
||||||
no-repeat;
|
|
||||||
position: absolute;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
margin-left: -16px;
|
|
||||||
margin-top: -16px;
|
|
||||||
-webkit-animation: OT_spin 2s linear infinite;
|
|
||||||
animation: OT_spin 2s linear infinite;
|
|
||||||
}
|
|
||||||
@-webkit-keyframes OT_spin {
|
|
||||||
100% {
|
|
||||||
-webkit-transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes OT_spin {
|
|
||||||
100% {
|
|
||||||
-webkit-transform: rotate(360deg);
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_publisher.OT_loading .OT_video-loading,
|
|
||||||
.OT_subscriber.OT_loading .OT_video-loading {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_video-centering {
|
|
||||||
display: table;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_video-container {
|
|
||||||
display: table-cell;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_video-poster {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: none;
|
|
||||||
|
|
||||||
opacity: 0.25;
|
|
||||||
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNDcxIDQ2NCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48bGluZWFyR3JhZGllbnQgaWQ9ImEiIHgyPSIwIiB5Mj0iMSI+PHN0b3Agb2Zmc2V0PSI2Ni42NiUiIHN0b3AtY29sb3I9IiNmZmYiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNmZmYiIHN0b3Atb3BhY2l0eT0iMCIvPjwvbGluZWFyR3JhZGllbnQ+PHBhdGggZmlsbD0idXJsKCNhKSIgZD0iTTc5IDMwOGMxNC4yNS02LjUgNTQuMjUtMTkuNzUgNzEtMjkgOS0zLjI1IDI1LTIxIDI1LTIxczMuNzUtMTMgMy0yMmMtMS43NS02Ljc1LTE1LTQzLTE1LTQzLTIuNSAzLTQuNzQxIDMuMjU5LTcgMS0zLjI1LTcuNS0yMC41LTQ0LjUtMTYtNTcgMS4yNS03LjUgMTAtNiAxMC02LTExLjI1LTMzLjc1LTgtNjctOC02N3MuMDczLTcuMzQ2IDYtMTVjLTMuNDguNjM3LTkgNC05IDQgMi41NjMtMTEuNzI3IDE1LTIxIDE1LTIxIC4xNDgtLjMxMi0xLjMyMS0xLjQ1NC0xMCAxIDEuNS0yLjc4IDE2LjY3NS04LjY1NCAzMC0xMSAzLjc4Ny05LjM2MSAxMi43ODItMTcuMzk4IDIyLTIyLTIuMzY1IDMuMTMzLTMgNi0zIDZzMTUuNjQ3LTguMDg4IDQxLTZjLTE5Ljc1IDItMjQgNi0yNCA2czc0LjUtMTAuNzUgMTA0IDM3YzcuNSA5LjUgMjQuNzUgNTUuNzUgMTAgODkgMy43NS0xLjUgNC41LTQuNSA5IDEgLjI1IDE0Ljc1LTExLjUgNjMtMTkgNjItMi43NSAxLTQtMy00LTMtMTAuNzUgMjkuNS0xNCAzOC0xNCAzOC0yIDQuMjUtMy43NSAxOC41LTEgMjIgMS4yNSA0LjUgMjMgMjMgMjMgMjNsMTI3IDUzYzM3IDM1IDIzIDEzNSAyMyAxMzVMMCA0NjRzLTMtOTYuNzUgMTQtMTIwYzUuMjUtNi4yNSAyMS43NS0xOS43NSA2NS0zNnoiLz48L3N2Zz4=);
|
|
||||||
background-size: auto 76%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_fit-mode-cover .OT_video-element {
|
|
||||||
-o-object-fit: cover;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Workaround for iOS freezing issue when cropping videos */
|
|
||||||
/* https://bugs.webkit.org/show_bug.cgi?id=176439 */
|
|
||||||
@media only screen and (orientation: portrait) {
|
|
||||||
.OT_subscriber.OT_ForceContain.OT_fit-mode-cover .OT_video-element {
|
|
||||||
-o-object-fit: contain !important;
|
|
||||||
object-fit: contain !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_fit-mode-contain .OT_video-element {
|
|
||||||
-o-object-fit: contain;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_fit-mode-cover .OT_video-poster {
|
|
||||||
background-position: center bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_fit-mode-contain .OT_video-poster {
|
|
||||||
background-position: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_audio-level-meter {
|
|
||||||
position: absolute;
|
|
||||||
width: 25%;
|
|
||||||
max-width: 224px;
|
|
||||||
min-width: 21px;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_audio-level-meter:before {
|
|
||||||
/* makes the height of the container equals its width */
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
padding-top: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_audio-level-meter__bar {
|
|
||||||
position: absolute;
|
|
||||||
width: 192%; /* meter value can overflow of 8% */
|
|
||||||
height: 192%;
|
|
||||||
top: -96% /* half of the size */;
|
|
||||||
right: -96%;
|
|
||||||
border-radius: 50%;
|
|
||||||
|
|
||||||
background-color: rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_audio-level-meter__audio-only-img {
|
|
||||||
position: absolute;
|
|
||||||
top: 22%;
|
|
||||||
right: 15%;
|
|
||||||
width: 40%;
|
|
||||||
|
|
||||||
opacity: 0.7;
|
|
||||||
|
|
||||||
background: url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzkgODYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0iI2ZmZiI+PHBhdGggZD0iTTkuNzU3IDQwLjkyNGMzLjczOC01LjE5MSAxMi43MTEtNC4zMDggMTIuNzExLTQuMzA4IDIuMjIzIDMuMDE0IDUuMTI2IDI0LjU4NiAzLjYyNCAyOC43MTgtMS40MDEgMS4zMDEtMTEuNjExIDEuNjI5LTEzLjM4LTEuNDM2LTEuMjI2LTguODA0LTIuOTU1LTIyLjk3NS0yLjk1NS0yMi45NzV6bTU4Ljc4NSAwYy0zLjczNy01LjE5MS0xMi43MTEtNC4zMDgtMTIuNzExLTQuMzA4LTIuMjIzIDMuMDE0LTUuMTI2IDI0LjU4Ni0zLjYyNCAyOC43MTggMS40MDEgMS4zMDEgMTEuNjExIDEuNjI5IDEzLjM4LTEuNDM2IDEuMjI2LTguODA0IDIuOTU1LTIyLjk3NSAyLjk1NS0yMi45NzV6Ii8+PHBhdGggZD0iTTY4LjY0NyA1OC42Yy43MjktNC43NTMgMi4zOC05LjU2MSAyLjM4LTE0LjgwNCAwLTIxLjQxMi0xNC4xMTUtMzguNzctMzEuNTI4LTM4Ljc3LTE3LjQxMiAwLTMxLjUyNyAxNy4zNTgtMzEuNTI3IDM4Ljc3IDAgNC41NDEuNTE1IDguOTM2IDEuODAyIDEyLjk1IDEuNjk4IDUuMjk1LTUuNTQyIDYuOTkxLTYuNjE2IDIuMDczQzIuNDEgNTUuMzk0IDAgNTEuNzg3IDAgNDguMTAzIDAgMjEuNTM2IDE3LjY4NSAwIDM5LjUgMCA2MS4zMTYgMCA3OSAyMS41MzYgNzkgNDguMTAzYzAgLjcxOC0yLjg5OSA5LjY5My0zLjI5MiAxMS40MDgtLjc1NCAzLjI5My03Ljc1MSAzLjU4OS03LjA2MS0uOTEyeiIvPjxwYXRoIGQ9Ik01LjA4NCA1MS4zODVjLS44MDQtMy43ODIuNTY5LTcuMzM1IDMuMTM0LTcuOTIxIDIuNjM2LS42MDMgNS40ODUgMi4xNSA2LjI4OSA2LjEzMi43OTcgMy45NDgtLjc1MiA3LjQ1Ny0zLjM4OCA3Ljg1OS0yLjU2Ni4zOTEtNS4yMzctMi4zMTgtNi4wMzQtNi4wN3ptNjguODM0IDBjLjgwNC0zLjc4Mi0uNTY4LTcuMzM1LTMuMTMzLTcuOTIxLTIuNjM2LS42MDMtNS40ODUgMi4xNS02LjI4OSA2LjEzMi0uNzk3IDMuOTQ4Ljc1MiA3LjQ1NyAzLjM4OSA3Ljg1OSAyLjU2NS4zOTEgNS4yMzctMi4zMTggNi4wMzQtNi4wN3ptLTIuMDM4IDguMjg4Yy0uOTI2IDE5LjY1OS0xNS4xMTIgMjQuNzU5LTI1Ljg1OSAyMC40NzUtNS40MDUtLjYwNi0zLjAzNCAxLjI2Mi0zLjAzNCAxLjI2MiAxMy42NjEgMy41NjIgMjYuMTY4IDMuNDk3IDMxLjI3My0yMC41NDktLjU4NS00LjUxMS0yLjM3OS0xLjE4Ny0yLjM3OS0xLjE4N3oiLz48cGF0aCBkPSJNNDEuNjYyIDc4LjQyMmw3LjU1My41NWMxLjE5Mi4xMDcgMi4xMiAxLjE1MyAyLjA3MiAyLjMzNWwtLjEwOSAyLjczOGMtLjA0NyAxLjE4Mi0xLjA1MSAyLjA1NC0yLjI0MyAxLjk0NmwtNy41NTMtLjU1Yy0xLjE5MS0uMTA3LTIuMTE5LTEuMTUzLTIuMDcyLTIuMzM1bC4xMDktMi43MzdjLjA0Ny0xLjE4MiAxLjA1Mi0yLjA1NCAyLjI0My0xLjk0N3oiLz48L2c+PC9zdmc+)
|
|
||||||
no-repeat center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_audio-level-meter__audio-only-img:before {
|
|
||||||
/* makes the height of the container equals its width */
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
padding-top: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_audio-level-meter__value {
|
|
||||||
position: absolute;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-image: radial-gradient(circle, rgba(151, 206, 0, 1) 0%, rgba(151, 206, 0, 0) 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_audio-level-meter.OT_mode-off {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_audio-level-meter.OT_mode-on,
|
|
||||||
.OT_audio-only .OT_audio-level-meter.OT_mode-auto {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_audio-only.OT_publisher .OT_video-element,
|
|
||||||
.OT_audio-only.OT_subscriber .OT_video-element {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_video-disabled-indicator {
|
|
||||||
opacity: 1;
|
|
||||||
border: none;
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
background-color: transparent;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: bottom right;
|
|
||||||
pointer-events: none;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
bottom: 3px;
|
|
||||||
right: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_video-disabled {
|
|
||||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFIAAAAoCAYAAABtla08AAAINUlEQVR42u2aaUxUVxTHcRBmAAEBRVTK4sKwDIsg+wCK7CqIw1CN1YobbbS2qYlJ06Qx1UpdqMbYWq2pSzWmH6ytNbXWJY1Lq7VuqBERtW64V0XFLYae0/xvcp3MMAMzDz6IyT/ge2ce5/7ucpY3Ts3NzZ1ygF57AJ0gO0G2jyZPmdbFyclJSAV1EeoEaUUSLGdSV5KLLFxzFmA7QVqGqDqjixhWkxCVeyRVl38wM6bwj6yYItYK47BAuu9B0gCqs6Ng2r494KQtkj/Dz2jHraw6qw2fdSE4rNmcCPCvZONP8iF1I6kdBdMaQJWZLeJqRWa2kPJAxXY+GxE+zxLI03GRh8lGSwoi9WCY8FWlCEh+8JOnT7MfPGjMuXX7Tt61hoaCi/9cKmKdv3BxeEtim/UbNpnbQiqF4MmT7kqrbr4lkMcTo46TTSpJB5g+8NHuVWnWuaampvhmO/7duHmrGluoO4C6OsJZGRrkDIld43ZqUOTnlkDSmXmabAoBU0vqBf+6KgFSxQ9++uzZ8rZApM81TJ8xM5me0Z/UF7PuBmdVdkGEb5gYDeQmyZNW3SJLIP9Kj64lGyMpmxRN6sOfIbkoAhKOdnv2/PmB1kB88eLFo+olyyrps3rSINIAzLonnqlqK8R9w+L86vtrt5L2nhug3Vc3ULu/Liz8AOuXESlZZONH6kmr7gtLIA9lRNeRzVukAvj3BslLnJNKgfScO69K+/Lly0ZbQW7e8tNK+pwBjqaSIjDrXgJkW1ciAZvbQjQ+RDahpBBKd5ZZsqN758hmImk4KQHnpDd8UwSkCyJarx07d4+3BeKJmlMHyX4qaRxpBCmNFE4KENvHDpAutVERn1kCVBMfeRRgYvZnx62wZPdnZkw92VQA5GClQXYRBze2S+iJmpPVVoJLA9l9QKokjcWKTCT1R5rhLg70NuSsziT16diIKkuAjibrTpJNDkn/e17CahtAjlAWJAYkb29Sb1LE9Rs391kILk8mVkyuIpuZcLKUlEmKkra1WuSTNuesEPzwoEploSVAh9Oiz+BIyd9dOHhtx4OEpFpVg6gbNK3yXX1j48N6U5Dz5i/gc/FDrMY3sTLiSMEkXxGxzUEUAGnbxlPaksMlHUXWAlHS8URCPseSohZbCSLjSSU7ixLXdzhIWVKq4Y7t2a/2bN0qGeKly1fYsVmk6RgIDz4J0bonyUOcjeYqm/8hRoYbWkigV2NH9CHAS60EkUkkw47hSRs6FqT1LR5AVcsrueXlK1d5AO+RpmBrZZEiefByytPCanRGNLZY0uF52gNDYr9sCRB8MHY0SJu2OJWKS2WQV65e4y31DmkCImEi0hBfufRime0RIhpbKen0/Ny9OYNW2ghyYytABjNIaxNuKttAWk6HPLn0k0FevdZwFinPWFIuKZbUV16NVko6jbWSDoPO3pOf8K0jQWLSQ0S9bdpkYck+m7vfWpAiHfKgBsZiGSSt0FqcTeU8WETqAHE2CgcAVd3Gkm4MD3xXYeI6B4NMItvKbcUpQ9gP+KMWnSsW+TaYJtoo+avBWLoKoK0CCSDud+7eXWQGZAXqV3YoQjQCfixJ8+fzj9ta3JHhlUeJ8wJOY2ws6eRKpPS3oqTvHAESEz9ya0naXL5WH6pt3FqSOhTHkTcKEXc6k1POh4Q9YJu/03TT4a8PoGMFI4i2EqSbOZAYaBkpCyD92RkG6KCSbjI/H0HEISBnlOZPFdcEzI2GTO4KBZICGKyAKLTEmJOB2txf5MbgohBINCl4FTqmpJMB2W+HiRn1Q2l6lXyPmiEP6VVE2TfGoaMYrHyPdtAnyI0jEOn9RLWmNEhvBBE7SjpFQZaShtLK+1S+T12lRwxUvrZlVPp8jE1PikeO7C/nyEqBDCB1t7+kUx4kKUWclea0yZC5BIGpiJSNSD9QgFR0RQKkL6KxHSWdsiARHJNYewoGrzG1/bk4dTPSunL2EyDjcbb7MQ+lQfZmkKiN7SjpFAM5CWAyGcwyY84YsZ1lUcbRNNtQMAdtQWGvQ0DyVjzYAKQfQFodeAeC1C8vzymXIZqD+ZEh/2OyLSalS/3VbnJZ+VqDXGjMrTCFuK4s66vVZUNfqaDolcbjOcb899sLpEE+I20GifywXe2QR3KElu99PzqjGufhREqB1pjCnG3IL3fY1v733r2FMsiGhutn0LAoJWWIGbPxjKwgjUbF0m52mPhigrpdXOecEq9pR6MkHbu2LOtrcZ9y3d0ODTb15y9MePz48aF79+8fvXnr9sljx2u2I7KNxDuaMPGVECoRs7mC4eT7SIruFNfNHK15MKuM2evwNq+4qjxvGnd5CHwNNynawW4cOlUZdG8b55IIJHmkItwrZHH6QxB3OSL9kTtAGpIvZiQB3Z4SKBfXQtEE9sashWAW87Bt3sYZNR6zn4uzJwWDKUKXfaKCdqUoBpLxSjYe9nqGiwWRBGipuGZ3Qm76itYLbbJI/PEhUApfw73uOIy9xfse3M9F9BuFJHcYrseSouGkHtCVtkuGTTikI8XgZzhg9SeF4VqcvSWiaSvNHQ8JwkNjIfEHemCmNLD1RaEfLs18mlgNuN6PFALHo7CyU5W2g00gFAQF4ozvibH04muwDbWraSFAyt/AAMzewgGR8uCeWn77xzBxPxgzPRCDDMZ14bQ/3jqGKGoHf2Hjgx3kw5LbaJDYWb52t9FMgw4AuWNWukNeuOYqOsmQi2jgws4PA/DD/z0B2x0/veCs4naw0cgybezid7X9jV3rX2RSs0wfLkll4pBGcgifg+NYxe1kJ2ycTaRq66uG/wBOl0vjcw70xwAAAABJRU5ErkJggg==);
|
|
||||||
background-size: 33px auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_video-disabled-warning {
|
|
||||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFIAAAAoCAYAAABtla08AAAGMElEQVR4Ae2aA7D0yBaAc7oH12vbRmlLaxYWb23btm3btm2899a2bWuYtPZ01cmtU9lJrib315yqr9I3Oem/5/s7acwEnehEJzoxCcX2O+wEeIgRBDDaGjAZOgQ6ihRpLklHZDJIXK1WWymMIhGGkVBKCWMM+Iv/f/b5t7faYtM/sGgIS7j8RNLjceUVl41GvGN1BFiHy9sgtRWaYbhvuVQ6o1VOvV5/tLe3dyssKoZuh8xClkDEi2MMS6ZjR0cScxdK/+HgnJsmLccYOx0e/PUGUqfTJDEHkV5go9lcMQoj4R8RpSIRRUr4a9baTJFCCNfqESKJ7RYJibK0xoi05EhFRTxMi1Rit6xHAuLaKRLwEVi6q1x+EhlVpd3d3Wfh4VQkQhRhxthYLg7SRGqdLlIp7UVOHf+JhEhEMscUolVje3p63saeeOFoKsT7fjj++BNuw2I/0ouUENmGaQcQEilQvUU6xuWC0kqmVWCt8df6kG7WLoFA20VSCOyNh0RKPT+SyrTWtQsvuvTYCy84z3+oAdbgAiLGIvHjTz6bFuu/B3lKKfVkFKknwih6EnnipZdfXQZzepAupXSGSCfwUGZtkrx3t/0dSQGnnXbmdocdetArQoj+4VR23wMP3bj/vnv9Sv/rBmkish09ca655thHSrlWq4TFF1vkNDxsgjiUnPqZnHPABIq47jx7pPMcecShfz7x1DO7D6eit99576X1113nVd8rqLGAuDaNitJonTGIqHgQGQjDsJglMrUH5iDSEQbRa6y2yrNvv/PuWVmV/PTzLz8steTit1B9FtGJeZrJksmWdBzBMcami4xUkaY1A1Qe94WIaPGBApJhaERrLrXkElf8+NPPz6YMLs1DDjn0Wn9PnI/UiQadM4jNEkhzVsEGE8nIHESM1j5/KqRX+/IEiOQ/yifNBlEkpnb00cccesbpp13T3983H88/48xzrrvm6it/8U5JXgX5G6nSvSq1R5LATR7aYGkwMG1RSwkWABH+4jUb3vT/uJ1Z0xpjraTBRltrxUQhksIRmgTJyy69+Pv99tv3qYX6FxgU+fU33352xGEHf5wisU7nNWJpZRMkAjZ6aIN1mwV7h29Jo2wCHlveu/GV169z65E+T6koexCh6c+EEiky3lnxQKFjUeVyOeI5AOBzIiayRhJryd7YYnkIHgvB0qk9Tdql6N3XH4bRUIOIIIKJSiRb0hkSEpZKRd1CpEq8GxtIyCVmDSgFl94GacTgaJw1rUlYhYng0c4ewaUsmKRIJjpiqMSOCh9QeI+UYECmtQIsxEu6OorEcv6Rl0gu0woh8MhFkmSCTXVI4pC704WCFRJvSRNJSzrMMEZO2iKZTCHAZYnmvXCny7ed5vfZK3viHSBdIFCKEFj2+nt+73nw8m2uedcLJlktA++VNMEPaR45aYukcKnnCfY3/DFbZS8t7eHxNgsPM0N1hXhJJwwM1QbpoQFlog2R13a/zBxEYHAQEUYUM6qiVwEyBYoM6JFNF2kFLelI5KQf+fVI4dJFCguDS7oAyx2R6SFQJKRedSDj/cMg/RXQ6ZE05GSIDAaXdCi1I3L021SQWNJ1RLY5OiIdL4/yvuw8ADfWPFrSciaMyH8tEQPwf1uGG54g5+KlJGTmsrxsQdl5PKidnPFe2QS///7Hu+VS6WX/HYnf0sevGL7lXydwod2/9DykZq0s5yff0sgSWCigNOH7TPHL7ufj+/TH8P/+qYpL4HkBDiRYpEXeM8/89/9zzjn7EtY64dfd1nqccM7Bs8+9MKy8555/8TnKS+5MufH6EZVASkgPzf+mJXroet17JirU0ALST3nT0y5ONyLpeo1y64ih+vuQfsoTOeRFSJXa+SvyB90TUmdw49EjLaKpMQ0mzEeTzkWsd/oI6fzfiKM8gWg6X6OjpXstu5ZHnmIb0GFiu29MIUfUewkmVrEN3RqVQ/bY8FzNcquMBv/pCNUZ5pHHem01KdN/I/DG66/lLhKSvTO5M84kav5C5z2ZfyAivi9i9VGd45RH7UWJbjwGG/7NYsRECt7jiOToHedKAui8SW4CsxyRc54mKH/8f7ELhCCACyNcIl/wI+FaAJyc8yzRtinQPzWzuFZrFHq/AAAAAElFTkSuQmCC);
|
|
||||||
background-size: 33px auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_video-disabled-indicator.OT_active {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_audio-blocked-indicator {
|
|
||||||
opacity: 1;
|
|
||||||
border: none;
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
background-color: transparent;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
pointer-events: none;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_audio-blocked {
|
|
||||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMTUwIiBoZWlnaHQ9IjkwIj48ZGVmcz48cGF0aCBkPSJNNjcgMTJMNi40NDggNzIuNTUyIDAgMzFWMThMMjYgMGw0MSAxMnptMyA3bDYgNDctMjkgMTgtMzUuNTAyLTYuNDk4TDcwIDE5eiIgaWQ9ImEiLz48L2RlZnM+PHJlY3Qgd2lkdGg9IjE1MCIgaGVpZ2h0PSI5MCIgcng9IjM1IiByeT0iNDUiIG9wYWNpdHk9Ii41Ii8+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzNikiPjxtYXNrIGlkPSJiIiBmaWxsPSIjZmZmIj48dXNlIHhsaW5rOmhyZWY9IiNhIi8+PC9tYXNrPjxwYXRoIGQ9Ik0zOS4yNDkgNTEuMzEyYy42OTcgMTAuMzcgMi43ODUgMTcuODk3IDUuMjUxIDE3Ljg5NyAzLjAzOCAwIDUuNS0xMS40MTcgNS41LTI1LjVzLTIuNDYyLTI1LjUtNS41LTI1LjVjLTIuNTEgMC00LjYyOCA3Ljc5Ny01LjI4NyAxOC40NTNBOC45ODkgOC45ODkgMCAwIDEgNDMgNDRhOC45ODggOC45ODggMCAwIDEtMy43NTEgNy4zMTJ6TTIwLjk4NSAzMi4yMjRsMTUuNzQ2LTE2Ljg3N2E3LjM4NSA3LjM4NSAwIDAgMSAxMC4zNzQtLjQyQzUxLjcwMiAxOS4xMTQgNTQgMjkuMjA4IDU0IDQ1LjIwOGMwIDE0LjUyNy0yLjM0MyAyMy44OC03LjAzIDI4LjA1OGE3LjI4IDcuMjggMCAwIDEtMTAuMTY4LS40NjhMMjAuNDA1IDU1LjIyNEgxMmE1IDUgMCAwIDEtNS01di0xM2E1IDUgMCAwIDEgNS01aDguOTg1eiIgZmlsbD0iI0ZGRiIgbWFzaz0idXJsKCNiKSIvPjwvZz48cGF0aCBkPSJNMTA2LjUgMTMuNUw0NC45OTggNzUuMDAyIiBzdHJva2U9IiNGRkYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PC9nPjwvc3ZnPg==);
|
|
||||||
background-size: 90px auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_container-audio-blocked {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_container-audio-blocked.OT_mini .OT_edge-bar-item {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_container-audio-blocked .OT_mute {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_audio-blocked-indicator.OT_active {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_video-unsupported {
|
|
||||||
opacity: 1;
|
|
||||||
border: none;
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
background-color: transparent;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOTciIGhlaWdodD0iOTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxkZWZzPjxwYXRoIGQ9Ik03MCAxMkw5LjQ0OCA3Mi41NTIgMCA2MmwzLTQ0TDI5IDBsNDEgMTJ6bTggMmwxIDUyLTI5IDE4LTM1LjUwMi02LjQ5OEw3OCAxNHoiIGlkPSJhIi8+PC9kZWZzPjxnIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoOCAzKSI+PG1hc2sgaWQ9ImIiIGZpbGw9IiNmZmYiPjx1c2UgeGxpbms6aHJlZj0iI2EiLz48L21hc2s+PHBhdGggZD0iTTkuMTEgMjAuOTY4SDQ4LjFhNSA1IDAgMCAxIDUgNVY1OC4xOGE1IDUgMCAwIDEtNSA1SDkuMTFhNSA1IDAgMCAxLTUtNVYyNS45N2E1IDUgMCAwIDEgNS01em00Ny4wOCAxMy4zOTRjMC0uMzQ1IDUuNDcyLTMuMTU5IDE2LjQxNS04LjQ0M2EzIDMgMCAwIDEgNC4zMDQgMi43MDJ2MjYuODM1YTMgMyAwIDAgMS00LjMwNSAyLjcwMWMtMTAuOTQyLTUuMjg2LTE2LjQxMy04LjEtMTYuNDEzLTguNDQ2VjM0LjM2MnoiIGZpbGw9IiNGRkYiIG1hc2s9InVybCgjYikiLz48L2c+PHBhdGggZD0iTTgxLjUgMTYuNUwxOS45OTggNzguMDAyIiBzdHJva2U9IiNGRkYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PC9nPjwvc3ZnPg==);
|
|
||||||
background-size: 58px auto;
|
|
||||||
pointer-events: none;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
margin-top: -30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_video-unsupported-bar {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
width: 192%; /* copy the size of the audio meter bar for symmetry */
|
|
||||||
height: 192%;
|
|
||||||
top: -96% /* half of the size */;
|
|
||||||
left: -96%;
|
|
||||||
border-radius: 50%;
|
|
||||||
|
|
||||||
background-color: rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_video-unsupported-img {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
top: 11%;
|
|
||||||
left: 15%;
|
|
||||||
width: 70%;
|
|
||||||
opacity: 0.7;
|
|
||||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOTciIGhlaWdodD0iOTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxkZWZzPjxwYXRoIGQ9Ik03MCAxMkw5LjQ0OCA3Mi41NTIgMCA2MmwzLTQ0TDI5IDBsNDEgMTJ6bTggMmwxIDUyLTI5IDE4LTM1LjUwMi02LjQ5OEw3OCAxNHoiIGlkPSJhIi8+PC9kZWZzPjxnIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoOCAzKSI+PG1hc2sgaWQ9ImIiIGZpbGw9IiNmZmYiPjx1c2UgeGxpbms6aHJlZj0iI2EiLz48L21hc2s+PHBhdGggZD0iTTkuMTEgMjAuOTY4SDQ4LjFhNSA1IDAgMCAxIDUgNVY1OC4xOGE1IDUgMCAwIDEtNSA1SDkuMTFhNSA1IDAgMCAxLTUtNVYyNS45N2E1IDUgMCAwIDEgNS01em00Ny4wOCAxMy4zOTRjMC0uMzQ1IDUuNDcyLTMuMTU5IDE2LjQxNS04LjQ0M2EzIDMgMCAwIDEgNC4zMDQgMi43MDJ2MjYuODM1YTMgMyAwIDAgMS00LjMwNSAyLjcwMWMtMTAuOTQyLTUuMjg2LTE2LjQxMy04LjEtMTYuNDEzLTguNDQ2VjM0LjM2MnoiIGZpbGw9IiNGRkYiIG1hc2s9InVybCgjYikiLz48L2c+PHBhdGggZD0iTTgxLjUgMTYuNUwxOS45OTggNzguMDAyIiBzdHJva2U9IiNGRkYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PC9nPjwvc3ZnPg==);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
background-size: 100% auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_video-unsupported-img:before {
|
|
||||||
/* makes the height of the container 93% of its width (90/97 px) */
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
padding-top: 93%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.OT_video-unsupported-text {
|
|
||||||
display: -webkit-box;
|
|
||||||
display: -ms-flexbox;
|
|
||||||
display: flex;
|
|
||||||
-webkit-box-pack: center;
|
|
||||||
-ms-flex-pack: center;
|
|
||||||
justify-content: center;
|
|
||||||
-webkit-box-align: center;
|
|
||||||
-ms-flex-align: center;
|
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
|
||||||
height: 100%;
|
|
||||||
margin-top: 40px;
|
|
||||||
}
|
|
||||||
|
|
|
@ -167,10 +167,10 @@ export class RecordingActivityComponent implements OnInit {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
deleteRecording(id: string) {
|
deleteRecording(id: string) {
|
||||||
const succsessCallback = () => {
|
const succsessCallback = async () => {
|
||||||
this.onDeleteRecordingClicked.emit(id);
|
this.onDeleteRecordingClicked.emit(id);
|
||||||
// Sending signal to all participants with the aim of updating their recordings list
|
// Sending signal to all participants with the aim of updating their recordings list
|
||||||
this.openviduService.sendSignal(Signal.RECORDING_DELETED, this.openviduService.getRemoteConnections());
|
await this.openviduService.sendSignal(Signal.RECORDING_DELETED, this.openviduService.getRemoteConnections());
|
||||||
};
|
};
|
||||||
this.actionService.openDeleteRecordingDialog(succsessCallback.bind(this));
|
this.actionService.openDeleteRecordingDialog(succsessCallback.bind(this));
|
||||||
}
|
}
|
||||||
|
|
|
@ -194,16 +194,18 @@ export class SessionComponent implements OnInit, OnDestroy {
|
||||||
this.cd.markForCheck();
|
this.cd.markForCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
async ngOnDestroy() {
|
||||||
// Reconnecting session is received in Firefox
|
// Reconnecting session is received in Firefox
|
||||||
// To avoid 'Connection lost' message uses session.off()
|
// To avoid 'Connection lost' message uses session.off()
|
||||||
this.session?.off('reconnecting');
|
if (!this.usedInPrejoinPage) {
|
||||||
this.participantService.clear();
|
this.session?.off('reconnecting');
|
||||||
this.session = null;
|
await this.participantService.clear();
|
||||||
this.sessionScreen = null;
|
this.session = null;
|
||||||
if (this.menuSubscription) this.menuSubscription.unsubscribe();
|
this.sessionScreen = null;
|
||||||
if (this.layoutWidthSubscription) this.layoutWidthSubscription.unsubscribe();
|
if (this.menuSubscription) this.menuSubscription.unsubscribe();
|
||||||
if (this.captionLanguageSubscription) this.captionLanguageSubscription.unsubscribe();
|
if (this.layoutWidthSubscription) this.layoutWidthSubscription.unsubscribe();
|
||||||
|
if (this.captionLanguageSubscription) this.captionLanguageSubscription.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
leaveSession() {
|
leaveSession() {
|
||||||
|
@ -251,20 +253,29 @@ export class SessionComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
private async connectToSession(): Promise<void> {
|
private async connectToSession(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const webcamToken = this.openviduService.getWebcamToken();
|
const participant = this.participantService.getLocalParticipant();
|
||||||
const screenToken = this.openviduService.getScreenToken();
|
const nickname = participant.getNickname();
|
||||||
|
const participantId = participant.id;
|
||||||
|
const screenPublisher = this.participantService.getMyScreenPublisher();
|
||||||
|
const cameraPublisher = this.participantService.getMyCameraPublisher();
|
||||||
|
|
||||||
if (this.participantService.haveICameraAndScreenActive()) {
|
|
||||||
await this.openviduService.connectSession(this.openviduService.getWebcamSession(), webcamToken);
|
if (participant.hasCameraAndScreenActives()) {
|
||||||
await this.openviduService.connectSession(this.openviduService.getScreenSession(), screenToken);
|
|
||||||
await this.openviduService.publish(this.participantService.getMyCameraPublisher());
|
const webcamSessionId = await this.openviduService.connectWebcamSession(participantId, nickname);
|
||||||
await this.openviduService.publish(this.participantService.getMyScreenPublisher());
|
if (webcamSessionId) this.participantService.setMyCameraConnectionId(webcamSessionId);
|
||||||
} else if (this.participantService.isOnlyMyScreenActive()) {
|
|
||||||
await this.openviduService.connectSession(this.openviduService.getScreenSession(), screenToken);
|
const screenSessionId = await this.openviduService.connectScreenSession(participantId, nickname);
|
||||||
await this.openviduService.publish(this.participantService.getMyScreenPublisher());
|
if (screenSessionId) this.participantService.setMyScreenConnectionId(screenSessionId);
|
||||||
|
|
||||||
|
await this.openviduService.publishCamera(cameraPublisher);
|
||||||
|
await this.openviduService.publishScreen(screenPublisher);
|
||||||
|
} else if (participant.hasOnlyScreenActive()) {
|
||||||
|
await this.openviduService.connectScreenSession(participantId, nickname);
|
||||||
|
await this.openviduService.publishScreen(screenPublisher);
|
||||||
} else {
|
} else {
|
||||||
await this.openviduService.connectSession(this.openviduService.getWebcamSession(), webcamToken);
|
await this.openviduService.connectWebcamSession(participantId, nickname);
|
||||||
await this.openviduService.publish(this.participantService.getMyCameraPublisher());
|
await this.openviduService.publishCamera(cameraPublisher);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// this._error.emit({ error: error.error, messgae: error.message, code: error.code, status: error.status });
|
// this._error.emit({ error: error.error, messgae: error.message, code: error.code, status: error.status });
|
||||||
|
@ -287,21 +298,21 @@ export class SessionComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
private subscribeToConnectionCreatedAndDestroyed() {
|
private subscribeToConnectionCreatedAndDestroyed() {
|
||||||
this.session.on('connectionCreated', (event: ConnectionEvent) => {
|
this.session.on('connectionCreated', async (event: ConnectionEvent) => {
|
||||||
const connectionId = event.connection?.connectionId;
|
const connectionId = event.connection?.connectionId;
|
||||||
const nickname: string = this.participantService.getNicknameFromConnectionData(event.connection.data);
|
const connectionNickname: string = this.participantService.getNicknameFromConnectionData(event.connection.data);
|
||||||
const isRemoteConnection: boolean = !this.openviduService.isMyOwnConnection(connectionId);
|
const isRemoteConnection: boolean = !this.openviduService.isMyOwnConnection(connectionId);
|
||||||
const isCameraConnection: boolean = !nickname?.includes(`_${VideoType.SCREEN}`);
|
const isCameraConnection: boolean = !connectionNickname?.includes(`_${VideoType.SCREEN}`);
|
||||||
|
const nickname = this.participantService.getMyNickname();
|
||||||
const data = event.connection?.data;
|
const data = event.connection?.data;
|
||||||
|
|
||||||
if (isRemoteConnection && isCameraConnection) {
|
if (isRemoteConnection && isCameraConnection) {
|
||||||
// Adding participant when connection is created and it's not screen
|
// Adding participant when connection is created and it's not screen
|
||||||
this.participantService.addRemoteConnection(connectionId, data, null);
|
this.participantService.addRemoteConnection(connectionId, data, null);
|
||||||
|
|
||||||
//Sending nicnkanme signal to new participants
|
//Sending nicnkanme signal to new connection
|
||||||
if (this.openviduService.needSendNicknameSignal()) {
|
if (this.openviduService.myNicknameHasBeenChanged()) {
|
||||||
const data = { clientData: this.participantService.getMyNickname() };
|
await this.openviduService.sendSignal(Signal.NICKNAME_CHANGED, [event.connection], { clientData: nickname });
|
||||||
this.openviduService.sendSignal(Signal.NICKNAME_CHANGED, [event.connection], data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { PublisherProperties } from 'openvidu-browser';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { CustomDevice } from '../../../models/device.model';
|
import { CustomDevice } from '../../../models/device.model';
|
||||||
import { ParticipantAbstractModel } from '../../../models/participant.model';
|
import { ParticipantAbstractModel } from '../../../models/participant.model';
|
||||||
import { VideoType } from '../../../models/video-type.model';
|
|
||||||
import { DeviceService } from '../../../services/device/device.service';
|
import { DeviceService } from '../../../services/device/device.service';
|
||||||
import { OpenViduService } from '../../../services/openvidu/openvidu.service';
|
import { OpenViduService } from '../../../services/openvidu/openvidu.service';
|
||||||
import { ParticipantService } from '../../../services/participant/participant.service';
|
import { ParticipantService } from '../../../services/participant/participant.service';
|
||||||
|
@ -58,7 +57,7 @@ export class AudioDevicesComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
toggleMic() {
|
toggleMic() {
|
||||||
const publish = this.isAudioMuted;
|
const publish = this.isAudioMuted;
|
||||||
this.openviduService.publishAudio(publish);
|
this.participantService.publishAudio(publish);
|
||||||
this.onAudioMutedClicked.emit(publish);
|
this.onAudioMutedClicked.emit(publish);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +65,8 @@ export class AudioDevicesComponent implements OnInit, OnDestroy {
|
||||||
const audioSource = event?.value;
|
const audioSource = event?.value;
|
||||||
if (this.deviceSrv.needUpdateAudioTrack(audioSource)) {
|
if (this.deviceSrv.needUpdateAudioTrack(audioSource)) {
|
||||||
const pp: PublisherProperties = { audioSource, videoSource: false };
|
const pp: PublisherProperties = { audioSource, videoSource: false };
|
||||||
await this.openviduService.replaceTrack(VideoType.CAMERA, pp);
|
const publisher = this.participantService.getMyCameraPublisher();
|
||||||
|
await this.openviduService.replaceCameraTrack(publisher, pp);
|
||||||
this.deviceSrv.setMicSelected(audioSource);
|
this.deviceSrv.setMicSelected(audioSource);
|
||||||
this.microphoneSelected = this.deviceSrv.getMicrophoneSelected();
|
this.microphoneSelected = this.deviceSrv.getMicrophoneSelected();
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,16 +23,17 @@
|
||||||
videocam_off
|
videocam_off
|
||||||
</mat-icon>
|
</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<mat-form-field>
|
<mat-form-field id="video-devices-form">
|
||||||
<mat-label *ngIf="hasVideoDevices">{{ 'PREJOIN.VIDEO_DEVICE' | translate }}</mat-label>
|
<mat-label *ngIf="hasVideoDevices">{{ 'PREJOIN.VIDEO_DEVICE' | translate }}</mat-label>
|
||||||
<mat-label *ngIf="!hasVideoDevices">{{ 'PREJOIN.NO_VIDEO_DEVICE' | translate }}</mat-label>
|
<mat-label *ngIf="!hasVideoDevices">{{ 'PREJOIN.NO_VIDEO_DEVICE' | translate }}</mat-label>
|
||||||
<mat-select
|
<mat-select
|
||||||
[disabled]="isVideoMuted || !hasVideoDevices"
|
[disabled]="isVideoMuted || !hasVideoDevices"
|
||||||
[value]="cameraSelected?.device"
|
[compareWith]="compareObjectDevices"
|
||||||
|
[value]="cameraSelected"
|
||||||
(click)="onDeviceSelectorClicked.emit()"
|
(click)="onDeviceSelectorClicked.emit()"
|
||||||
(selectionChange)="onCameraSelected($event)"
|
(selectionChange)="onCameraSelected($event)"
|
||||||
>
|
>
|
||||||
<mat-option *ngFor="let camera of cameras" [value]="camera.device">
|
<mat-option *ngFor="let camera of cameras" [value]="camera" id="option-{{camera.label}}">
|
||||||
{{ camera.label }}
|
{{ camera.label }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { Subscription } from 'rxjs';
|
||||||
import { CustomDevice } from '../../../models/device.model';
|
import { CustomDevice } from '../../../models/device.model';
|
||||||
import { PanelType } from '../../../models/panel.model';
|
import { PanelType } from '../../../models/panel.model';
|
||||||
import { ParticipantAbstractModel } from '../../../models/participant.model';
|
import { ParticipantAbstractModel } from '../../../models/participant.model';
|
||||||
import { VideoType } from '../../../models/video-type.model';
|
|
||||||
import { DeviceService } from '../../../services/device/device.service';
|
import { DeviceService } from '../../../services/device/device.service';
|
||||||
import { OpenViduService } from '../../../services/openvidu/openvidu.service';
|
import { OpenViduService } from '../../../services/openvidu/openvidu.service';
|
||||||
import { PanelService } from '../../../services/panel/panel.service';
|
import { PanelService } from '../../../services/panel/panel.service';
|
||||||
|
@ -21,8 +20,8 @@ import { VirtualBackgroundService } from '../../../services/virtual-background/v
|
||||||
styleUrls: ['./video-devices.component.css']
|
styleUrls: ['./video-devices.component.css']
|
||||||
})
|
})
|
||||||
export class VideoDevicesComponent implements OnInit, OnDestroy {
|
export class VideoDevicesComponent implements OnInit, OnDestroy {
|
||||||
@Output() onDeviceSelectorClicked = new EventEmitter<void>();
|
@Output() onDeviceSelectorClicked = new EventEmitter<void>();
|
||||||
@Output() onVideoMutedClicked = new EventEmitter<boolean>();
|
@Output() onVideoMutedClicked = new EventEmitter<boolean>();
|
||||||
|
|
||||||
videoMuteChanging: boolean;
|
videoMuteChanging: boolean;
|
||||||
isVideoMuted: boolean;
|
isVideoMuted: boolean;
|
||||||
|
@ -47,9 +46,8 @@ export class VideoDevicesComponent implements OnInit, OnDestroy {
|
||||||
await this.deviceSrv.refreshDevices();
|
await this.deviceSrv.refreshDevices();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.hasVideoDevices = this.deviceSrv.hasVideoDeviceAvailable();
|
this.hasVideoDevices = this.deviceSrv.hasVideoDeviceAvailable();
|
||||||
if(this.hasVideoDevices){
|
if (this.hasVideoDevices) {
|
||||||
this.cameras = this.deviceSrv.getCameras();
|
this.cameras = this.deviceSrv.getCameras();
|
||||||
this.cameraSelected = this.deviceSrv.getCameraSelected();
|
this.cameraSelected = this.deviceSrv.getCameraSelected();
|
||||||
}
|
}
|
||||||
|
@ -67,7 +65,7 @@ export class VideoDevicesComponent implements OnInit, OnDestroy {
|
||||||
async toggleCam() {
|
async toggleCam() {
|
||||||
this.videoMuteChanging = true;
|
this.videoMuteChanging = true;
|
||||||
const publish = this.isVideoMuted;
|
const publish = this.isVideoMuted;
|
||||||
await this.openviduService.publishVideo(publish);
|
await this.participantService.publishVideo(publish);
|
||||||
if (this.isVideoMuted && this.panelService.isExternalPanelOpened()) {
|
if (this.isVideoMuted && this.panelService.isExternalPanelOpened()) {
|
||||||
this.panelService.togglePanel(PanelType.BACKGROUND_EFFECTS);
|
this.panelService.togglePanel(PanelType.BACKGROUND_EFFECTS);
|
||||||
}
|
}
|
||||||
|
@ -76,19 +74,21 @@ export class VideoDevicesComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
async onCameraSelected(event: any) {
|
async onCameraSelected(event: any) {
|
||||||
const videoSource = event?.value;
|
const device: CustomDevice = event?.value;
|
||||||
|
|
||||||
// Is New deviceId different from the old one?
|
// Is New deviceId different from the old one?
|
||||||
if (this.deviceSrv.needUpdateVideoTrack(videoSource)) {
|
if (this.deviceSrv.needUpdateVideoTrack(device)) {
|
||||||
const mirror = this.deviceSrv.cameraNeedsMirror(videoSource);
|
const mirror = this.deviceSrv.cameraNeedsMirror(device.device);
|
||||||
// Reapply Virtual Background to new Publisher if necessary
|
// Reapply Virtual Background to new Publisher if necessary
|
||||||
const backgroundSelected = this.backgroundService.backgroundSelected.getValue();
|
const backgroundSelected = this.backgroundService.backgroundSelected.getValue();
|
||||||
const isBackgroundApplied = this.backgroundService.isBackgroundApplied()
|
const isBackgroundApplied = this.backgroundService.isBackgroundApplied();
|
||||||
|
|
||||||
if (isBackgroundApplied) {
|
if (isBackgroundApplied) {
|
||||||
await this.backgroundService.removeBackground();
|
await this.backgroundService.removeBackground();
|
||||||
}
|
}
|
||||||
const pp: PublisherProperties = { videoSource, audioSource: false, mirror };
|
const pp: PublisherProperties = { videoSource: device.device, audioSource: false, mirror };
|
||||||
await this.openviduService.replaceTrack(VideoType.CAMERA, pp);
|
const publisher = this.participantService.getMyCameraPublisher();
|
||||||
|
await this.openviduService.replaceCameraTrack(publisher, pp);
|
||||||
|
|
||||||
if (isBackgroundApplied) {
|
if (isBackgroundApplied) {
|
||||||
const bgSelected = this.backgroundService.backgrounds.find((b) => b.id === backgroundSelected);
|
const bgSelected = this.backgroundService.backgrounds.find((b) => b.id === backgroundSelected);
|
||||||
|
@ -97,11 +97,19 @@ export class VideoDevicesComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.deviceSrv.setCameraSelected(videoSource);
|
this.deviceSrv.setCameraSelected(device.device);
|
||||||
this.cameraSelected = this.deviceSrv.getCameraSelected();
|
this.cameraSelected = this.deviceSrv.getCameraSelected();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* Compare two devices to check if they are the same. Used by the mat-select
|
||||||
|
*/
|
||||||
|
compareObjectDevices(o1: CustomDevice, o2: CustomDevice): boolean {
|
||||||
|
return o1.label === o2.label;
|
||||||
|
}
|
||||||
|
|
||||||
protected subscribeToParticipantMediaProperties() {
|
protected subscribeToParticipantMediaProperties() {
|
||||||
this.localParticipantSubscription = this.participantService.localParticipantObs.subscribe((p: ParticipantAbstractModel) => {
|
this.localParticipantSubscription = this.participantService.localParticipantObs.subscribe((p: ParticipantAbstractModel) => {
|
||||||
if (p) {
|
if (p) {
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
/* Fixes layout bug. The OT_root is created with the entire layout width and it has a weird UX behaviour */
|
/* Fixes layout bug. The OT_root is created with the entire layout width and it has a weird UX behaviour */
|
||||||
.no-size {
|
.no-size {
|
||||||
height: 0px !important;
|
height: 0px !important;
|
||||||
width: 0px !important;
|
width: 0px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.nickname {
|
.nickname {
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
|
@ -11,46 +10,44 @@
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
border-radius: var(--ov-video-radius);
|
border-radius: var(--ov-video-radius);
|
||||||
color: var(--ov-text-color);
|
color: var(--ov-text-color);
|
||||||
font-family: 'Roboto','RobotoDraft',Helvetica,Arial,sans-serif;
|
font-family: 'Roboto', 'RobotoDraft', Helvetica, Arial, sans-serif;
|
||||||
}
|
}
|
||||||
.nicknameContainer {
|
.nicknameContainer {
|
||||||
background-color: var(--ov-secondary-color);
|
background-color: var(--ov-secondary-color);
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
color: var(--ov-text-color);
|
color: var(--ov-text-color);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
border-radius: var(--ov-video-radius);
|
border-radius: var(--ov-video-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
#nickname-input-container {
|
#nickname-input-container {
|
||||||
background-color: var(--ov-secondary-color);
|
background-color: var(--ov-secondary-color);
|
||||||
border-radius: var(--ov-video-radius);
|
border-radius: var(--ov-video-radius);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
#closeButton {
|
||||||
|
|
||||||
#closeButton {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -3px;
|
top: -3px;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
}
|
}
|
||||||
|
|
||||||
#nicknameForm {
|
#nicknameForm {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#audio-wave-container {
|
#audio-wave-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 999;
|
z-index: 2;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullscreen {
|
.fullscreen {
|
||||||
top: 40px;
|
top: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
video {
|
||||||
video {
|
|
||||||
-o-object-fit: cover;
|
-o-object-fit: cover;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -60,57 +57,61 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-icons, #settings-container {
|
.status-icons,
|
||||||
|
#settings-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-icons {
|
.status-icons {
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-icons button, #settings-container button {
|
.status-icons button,
|
||||||
|
#settings-container button {
|
||||||
color: var(--ov-text-color);
|
color: var(--ov-text-color);
|
||||||
width: 26px;
|
width: 26px;
|
||||||
height: 26px;
|
height: 26px;
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
border-radius: var(--ov-buttons-radius);
|
border-radius: var(--ov-buttons-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-icons button {
|
.status-icons button {
|
||||||
background-color: var(--ov-warn-color);
|
background-color: var(--ov-warn-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-icons .mat-icon-button, #settings-container .mat-icon-button{
|
.status-icons .mat-icon-button,
|
||||||
|
#settings-container .mat-icon-button {
|
||||||
line-height: 0px;
|
line-height: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-icons mat-icon, #settings-container mat-icon {
|
.status-icons mat-icon,
|
||||||
|
#settings-container mat-icon {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#settings-container{
|
#settings-container {
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#settings-container button {
|
#settings-container button {
|
||||||
background-color: var(--ov-secondary-color);
|
background-color: var(--ov-secondary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Contains the video element, used to fix video letter-boxing */
|
/* Contains the video element, used to fix video letter-boxing */
|
||||||
.OV_stream {
|
.OV_stream {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-radius: var(--ov-video-radius);
|
border-radius: var(--ov-video-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
caret-color: #ffffff !important;
|
caret-color: #ffffff !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<div
|
<div
|
||||||
*ngIf="this._stream"
|
*ngIf="this._stream"
|
||||||
class="OV_stream"
|
class="OV_stream"
|
||||||
[ngClass]="{'no-size': !showVideo}"
|
[ngClass]="{ 'no-size': !showVideo }"
|
||||||
[id]="'container-' + this._stream.streamManager?.stream?.streamId"
|
[id]="'container-' + this._stream.streamManager?.stream?.streamId"
|
||||||
#streamContainer
|
#streamContainer
|
||||||
>
|
>
|
||||||
|
@ -50,11 +50,18 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="!isMinimal && showSettingsButton" id="settings-container" class="videoButtons">
|
<div *ngIf="!isMinimal && showSettingsButton" id="settings-container" class="videoButtons">
|
||||||
<button mat-icon-button (click)="toggleVideoMenu($event)" matTooltip="{{ 'STREAM.SETTINGS' | translate }}" matTooltipPosition="above" aria-label="Video settings menu" id="stream-menu-btn">
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
(click)="toggleVideoMenu($event)"
|
||||||
|
matTooltip="{{ 'STREAM.SETTINGS' | translate }}"
|
||||||
|
matTooltipPosition="above"
|
||||||
|
aria-label="Video settings menu"
|
||||||
|
id="video-settings-btn-{{this._stream.streamManager?.stream?.typeOfVideo}}"
|
||||||
|
>
|
||||||
<mat-icon>more_vert</mat-icon>
|
<mat-icon>more_vert</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<span [matMenuTriggerFor]="menu"></span>
|
<span [matMenuTriggerFor]="menu"></span>
|
||||||
<mat-menu #menu="matMenu" yPosition="above" xPosition="before">
|
<mat-menu #menu="matMenu" yPosition="above" xPosition="before" class="video-settings-menu">
|
||||||
<button mat-menu-item id="videoZoomButton" (click)="toggleVideoEnlarged()">
|
<button mat-menu-item id="videoZoomButton" (click)="toggleVideoEnlarged()">
|
||||||
<mat-icon>{{ this.videoSizeIcon }}</mat-icon>
|
<mat-icon>{{ this.videoSizeIcon }}</mat-icon>
|
||||||
<span *ngIf="videoSizeIcon === videoSizeIconEnum.NORMAL">{{ 'STREAM.ZOOM_OUT' | translate }}</span>
|
<span *ngIf="videoSizeIcon === videoSizeIconEnum.NORMAL">{{ 'STREAM.ZOOM_OUT' | translate }}</span>
|
||||||
|
@ -70,7 +77,7 @@
|
||||||
<button
|
<button
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
(click)="replaceScreenTrack()"
|
(click)="replaceScreenTrack()"
|
||||||
id="changeScreenButton"
|
id="replace-screen-button"
|
||||||
*ngIf="!this._stream.streamManager?.remote && this._stream.streamManager?.stream?.typeOfVideo === videoTypeEnum.SCREEN"
|
*ngIf="!this._stream.streamManager?.remote && this._stream.streamManager?.stream?.typeOfVideo === videoTypeEnum.SCREEN"
|
||||||
>
|
>
|
||||||
<mat-icon>picture_in_picture</mat-icon>
|
<mat-icon>picture_in_picture</mat-icon>
|
||||||
|
|
|
@ -231,12 +231,12 @@ export class StreamComponent implements OnInit {
|
||||||
/**
|
/**
|
||||||
* @ignore
|
* @ignore
|
||||||
*/
|
*/
|
||||||
updateNickname(event) {
|
async updateNickname(event) {
|
||||||
if (event?.keyCode === 13 || event?.type === 'focusout') {
|
if (event?.keyCode === 13 || event?.type === 'focusout') {
|
||||||
if (!!this.nickname) {
|
if (!!this.nickname) {
|
||||||
this.participantService.setMyNickname(this.nickname);
|
this.participantService.setMyNickname(this.nickname);
|
||||||
this.storageService.setNickname(this.nickname);
|
this.storageService.setNickname(this.nickname);
|
||||||
this.openviduService.sendSignal(Signal.NICKNAME_CHANGED, undefined, { clientData: this.nickname });
|
await this.openviduService.sendSignal(Signal.NICKNAME_CHANGED, undefined, { clientData: this.nickname });
|
||||||
}
|
}
|
||||||
this.toggleNicknameForm();
|
this.toggleNicknameForm();
|
||||||
}
|
}
|
||||||
|
@ -252,7 +252,8 @@ export class StreamComponent implements OnInit {
|
||||||
publishAudio: !this.participantService.isMyCameraActive(),
|
publishAudio: !this.participantService.isMyCameraActive(),
|
||||||
mirror: false
|
mirror: false
|
||||||
};
|
};
|
||||||
await this.openviduService.replaceTrack(VideoType.SCREEN, properties);
|
const publisher = this.participantService.getMyScreenPublisher();
|
||||||
|
await this.openviduService.replaceScreenTrack(publisher, properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
private checkVideoEnlarged() {
|
private checkVideoEnlarged() {
|
||||||
|
|
|
@ -500,7 +500,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
async toggleMicrophone() {
|
async toggleMicrophone() {
|
||||||
this.onMicrophoneButtonClicked.emit();
|
this.onMicrophoneButtonClicked.emit();
|
||||||
try {
|
try {
|
||||||
await this.openviduService.publishAudio(!this.isAudioActive);
|
this.participantService.publishAudio(!this.isAudioActive);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.log.e('There was an error toggling microphone:', error.code, error.message);
|
this.log.e('There was an error toggling microphone:', error.code, error.message);
|
||||||
this.actionService.openDialog(
|
this.actionService.openDialog(
|
||||||
|
@ -521,7 +521,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
if (this.panelService.isExternalPanelOpened() && !publishVideo) {
|
if (this.panelService.isExternalPanelOpened() && !publishVideo) {
|
||||||
this.panelService.togglePanel(PanelType.BACKGROUND_EFFECTS);
|
this.panelService.togglePanel(PanelType.BACKGROUND_EFFECTS);
|
||||||
}
|
}
|
||||||
await this.openviduService.publishVideo(publishVideo);
|
await this.participantService.publishVideo(publishVideo);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.log.e('There was an error toggling camera:', error.code, error.message);
|
this.log.e('There was an error toggling camera:', error.code, error.message);
|
||||||
this.actionService.openDialog(this.translateService.translate('ERRORS.TOGGLE_CAMERA'), error?.error || error?.message || error);
|
this.actionService.openDialog(this.translateService.translate('ERRORS.TOGGLE_CAMERA'), error?.error || error?.message || error);
|
||||||
|
|
|
@ -601,7 +601,14 @@ export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewIni
|
||||||
await this.handlePublisherError(e);
|
await this.handlePublisherError(e);
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
publisher.once('accessAllowed', () => resolve());
|
publisher.once('accessAllowed', () => {
|
||||||
|
this.participantService.setMyCameraPublisher(publisher);
|
||||||
|
this.participantService.updateLocalParticipant();
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.participantService.setMyCameraPublisher(undefined);
|
||||||
|
this.participantService.updateLocalParticipant();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.actionService.openDialog(error.name.replace(/_/g, ' '), error.message, true);
|
this.actionService.openDialog(error.name.replace(/_/g, ' '), error.message, true);
|
||||||
|
|
|
@ -25,7 +25,7 @@ import { Directive, TemplateRef, ViewContainerRef } from '@angular/core';
|
||||||
* publishVideo = true;
|
* publishVideo = true;
|
||||||
* publishAudio = true;
|
* publishAudio = true;
|
||||||
*
|
*
|
||||||
* constructor(private httpClient: HttpClient, private openviduService: OpenViduService) { }
|
* constructor(private httpClient: HttpClient, private participantService: ParticipantService) { }
|
||||||
*
|
*
|
||||||
* async ngOnInit() {
|
* async ngOnInit() {
|
||||||
* this.tokens = {
|
* this.tokens = {
|
||||||
|
@ -36,12 +36,12 @@ import { Directive, TemplateRef, ViewContainerRef } from '@angular/core';
|
||||||
*
|
*
|
||||||
* toggleVideo() {
|
* toggleVideo() {
|
||||||
* this.publishVideo = !this.publishVideo;
|
* this.publishVideo = !this.publishVideo;
|
||||||
* this.openviduService.publishVideo(this.publishVideo);
|
* this.participantService.publishVideo(this.publishVideo);
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* toggleAudio() {
|
* toggleAudio() {
|
||||||
* this.publishAudio = !this.publishAudio;
|
* this.publishAudio = !this.publishAudio;
|
||||||
* this.openviduService.publishAudio(this.publishAudio);
|
* this.participantService.publishAudio(this.publishAudio);
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* async getToken(): Promise<string> {
|
* async getToken(): Promise<string> {
|
||||||
|
@ -94,7 +94,6 @@ export class ToolbarDirective {
|
||||||
*
|
*
|
||||||
* constructor(
|
* constructor(
|
||||||
* private httpClient: HttpClient,
|
* private httpClient: HttpClient,
|
||||||
* private openviduService: OpenViduService,
|
|
||||||
* private participantService: ParticipantService
|
* private participantService: ParticipantService
|
||||||
* ) { }
|
* ) { }
|
||||||
*
|
*
|
||||||
|
@ -107,12 +106,12 @@ export class ToolbarDirective {
|
||||||
*
|
*
|
||||||
* toggleVideo() {
|
* toggleVideo() {
|
||||||
* const publishVideo = !this.participantService.isMyVideoActive();
|
* const publishVideo = !this.participantService.isMyVideoActive();
|
||||||
* this.openviduService.publishVideo(publishVideo);
|
* this.participantService.publishVideo(publishVideo);
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* toggleAudio() {
|
* toggleAudio() {
|
||||||
* const publishAudio = !this.participantService.isMyAudioActive();
|
* const publishAudio = !this.participantService.isMyAudioActive();
|
||||||
* this.openviduService.publishAudio(publishAudio);
|
* this.participantService.publishAudio(publishAudio);
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* async getToken(): Promise<string> {
|
* async getToken(): Promise<string> {
|
||||||
|
|
|
@ -21,7 +21,7 @@ export interface StreamModel {
|
||||||
/**
|
/**
|
||||||
* The streamManager object from openvidu-browser library.{@link https://docs.openvidu.io/en/stable/api/openvidu-browser/classes/StreamManager.html}
|
* The streamManager object from openvidu-browser library.{@link https://docs.openvidu.io/en/stable/api/openvidu-browser/classes/StreamManager.html}
|
||||||
*/
|
*/
|
||||||
streamManager: StreamManager;
|
streamManager: StreamManager | undefined;
|
||||||
/**
|
/**
|
||||||
* Whether the stream is enlarged or not
|
* Whether the stream is enlarged or not
|
||||||
*/
|
*/
|
||||||
|
@ -29,7 +29,7 @@ export interface StreamModel {
|
||||||
/**
|
/**
|
||||||
* Unique identifier of the stream
|
* Unique identifier of the stream
|
||||||
*/
|
*/
|
||||||
connectionId: string;
|
connectionId: string | undefined;
|
||||||
/**
|
/**
|
||||||
* The participant object
|
* The participant object
|
||||||
*/
|
*/
|
||||||
|
@ -68,7 +68,7 @@ export abstract class ParticipantAbstractModel {
|
||||||
isMutedForcibly: boolean;
|
isMutedForcibly: boolean;
|
||||||
|
|
||||||
constructor(props: ParticipantProperties, model?: StreamModel) {
|
constructor(props: ParticipantProperties, model?: StreamModel) {
|
||||||
this.id = props.id ? props.id : Math.random().toString(32).replace('.','_');
|
this.id = props.id || Math.random().toString(32).replace('.','_');
|
||||||
this.local = props.local;
|
this.local = props.local;
|
||||||
this.nickname = props.nickname;
|
this.nickname = props.nickname;
|
||||||
this.colorProfile = !!props.colorProfile ? props.colorProfile : `hsl(${Math.random() * 360}, 100%, 80%)`;
|
this.colorProfile = !!props.colorProfile ? props.colorProfile : `hsl(${Math.random() * 360}, 100%, 80%)`;
|
||||||
|
@ -76,9 +76,9 @@ export abstract class ParticipantAbstractModel {
|
||||||
let streamModel: StreamModel = {
|
let streamModel: StreamModel = {
|
||||||
connected: model ? model.connected : true,
|
connected: model ? model.connected : true,
|
||||||
type: model ? model.type : VideoType.CAMERA,
|
type: model ? model.type : VideoType.CAMERA,
|
||||||
streamManager: model ? model.streamManager : null,
|
streamManager: model?.streamManager,
|
||||||
videoEnlarged: model ? model.videoEnlarged : false,
|
videoEnlarged: model ? model.videoEnlarged : false,
|
||||||
connectionId: model ? model.connectionId : null,
|
connectionId: model?.connectionId,
|
||||||
participant: this
|
participant: this
|
||||||
};
|
};
|
||||||
this.streams.set(streamModel.type, streamModel);
|
this.streams.set(streamModel.type, streamModel);
|
||||||
|
@ -113,7 +113,7 @@ export abstract class ParticipantAbstractModel {
|
||||||
private isCameraAudioActive(): boolean {
|
private isCameraAudioActive(): boolean {
|
||||||
const cameraConnection = this.getCameraConnection();
|
const cameraConnection = this.getCameraConnection();
|
||||||
if (cameraConnection?.connected) {
|
if (cameraConnection?.connected) {
|
||||||
return cameraConnection.streamManager?.stream?.audioActive;
|
return cameraConnection.streamManager?.stream?.audioActive || false;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ export abstract class ParticipantAbstractModel {
|
||||||
isScreenAudioActive(): boolean {
|
isScreenAudioActive(): boolean {
|
||||||
const screenConnection = this.getScreenConnection();
|
const screenConnection = this.getScreenConnection();
|
||||||
if (screenConnection?.connected) {
|
if (screenConnection?.connected) {
|
||||||
return screenConnection?.streamManager?.stream?.audioActive;
|
return screenConnection?.streamManager?.stream?.audioActive || false;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -160,13 +160,14 @@ export abstract class ParticipantAbstractModel {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
|
* @returns The participant active connection types
|
||||||
*/
|
*/
|
||||||
getConnectionTypesActive(): VideoType[] {
|
getActiveConnectionTypes(): VideoType[] {
|
||||||
let connType = [];
|
const activeTypes: VideoType[] = [];
|
||||||
if (this.isCameraActive()) connType.push(VideoType.CAMERA);
|
if (this.isCameraActive()) activeTypes.push(VideoType.CAMERA);
|
||||||
if (this.isScreenActive()) connType.push(VideoType.SCREEN);
|
if (this.isScreenActive()) activeTypes.push(VideoType.SCREEN);
|
||||||
|
|
||||||
return connType;
|
return activeTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -218,7 +219,6 @@ export abstract class ParticipantAbstractModel {
|
||||||
*/
|
*/
|
||||||
isLocal(): boolean {
|
isLocal(): boolean {
|
||||||
return this.local;
|
return this.local;
|
||||||
// return Array.from(this.streams.values()).every((conn) => conn.local);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -238,7 +238,7 @@ export abstract class ParticipantAbstractModel {
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
setCameraPublisher(publisher: Publisher) {
|
setCameraPublisher(publisher: Publisher | undefined) {
|
||||||
const cameraConnection = this.getCameraConnection();
|
const cameraConnection = this.getCameraConnection();
|
||||||
if (cameraConnection) cameraConnection.streamManager = publisher;
|
if (cameraConnection) cameraConnection.streamManager = publisher;
|
||||||
}
|
}
|
||||||
|
@ -307,6 +307,30 @@ export abstract class ParticipantAbstractModel {
|
||||||
if (screenConnection) screenConnection.connected = false;
|
if (screenConnection) screenConnection.connected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @returns true if both camera and screen are active
|
||||||
|
*/
|
||||||
|
hasCameraAndScreenActives(): boolean {
|
||||||
|
return this.isCameraActive() && this.isScreenActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @returns true if only screen is active
|
||||||
|
*/
|
||||||
|
hasOnlyScreenActive(): boolean {
|
||||||
|
return this.isScreenActive() && !this.isCameraActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @returns true if only camera is active
|
||||||
|
*/
|
||||||
|
hasOnlyCameraActive(): boolean {
|
||||||
|
return this.isCameraActive() && !this.isScreenActive();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Pipe, PipeTransform } from '@angular/core';
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
import { StreamModel, ParticipantAbstractModel } from '../models/participant.model';
|
import { ParticipantAbstractModel, StreamModel } from '../models/participant.model';
|
||||||
import { TranslateService } from '../services/translate/translate.service';
|
import { TranslateService } from '../services/translate/translate.service';
|
||||||
|
|
||||||
@Pipe({ name: 'streams' })
|
@Pipe({ name: 'streams' })
|
||||||
|
@ -10,11 +10,8 @@ export class ParticipantStreamsPipe implements PipeTransform {
|
||||||
let streams: StreamModel[] = [];
|
let streams: StreamModel[] = [];
|
||||||
if(participants && Object.keys(participants).length > 0){
|
if(participants && Object.keys(participants).length > 0){
|
||||||
if (Array.isArray(participants)) {
|
if (Array.isArray(participants)) {
|
||||||
participants.forEach((p) => {
|
streams = participants.map(p => p.getAvailableConnections()).flat();
|
||||||
streams = streams.concat(p.getAvailableConnections());
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
streams = participants.getAvailableConnections();
|
streams = participants.getAvailableConnections();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,15 +27,11 @@ export class StreamTypesEnabledPipe implements PipeTransform {
|
||||||
constructor(private translateService: TranslateService) {}
|
constructor(private translateService: TranslateService) {}
|
||||||
|
|
||||||
transform(participant: ParticipantAbstractModel): string {
|
transform(participant: ParticipantAbstractModel): string {
|
||||||
let result = '';
|
|
||||||
let activeStreams = participant?.getConnectionTypesActive().toString();
|
const activeStreams = participant?.getActiveConnectionTypes() ?? [];
|
||||||
const activeStreamsArr: string[] = activeStreams.split(',');
|
const streamNames = activeStreams.map(streamType => this.translateService.translate(`PANEL.PARTICIPANTS.${streamType}`));
|
||||||
activeStreamsArr.forEach((type, index) => {
|
const streamsString = streamNames.join(', ');
|
||||||
result += this.translateService.translate(`PANEL.PARTICIPANTS.${type}`)
|
|
||||||
if(activeStreamsArr.length > 0 && index < activeStreamsArr.length - 1){
|
return `(${streamsString})`;
|
||||||
result += ', ';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return `(${result})`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ export class ChatService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessage(message: string) {
|
async sendMessage(message: string) {
|
||||||
message = message.replace(/ +(?= )/g, '');
|
message = message.replace(/ +(?= )/g, '');
|
||||||
if (message !== '' && message !== ' ') {
|
if (message !== '' && message !== ' ') {
|
||||||
const data = {
|
const data = {
|
||||||
|
@ -72,7 +72,7 @@ export class ChatService {
|
||||||
nickname: this.participantService.getMyNickname()
|
nickname: this.participantService.getMyNickname()
|
||||||
};
|
};
|
||||||
|
|
||||||
this.openviduService.sendSignal(Signal.CHAT, undefined, data);
|
await this.openviduService.sendSignal(Signal.CHAT, undefined, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -177,8 +177,8 @@ export class DeviceService {
|
||||||
return this.microphoneSelected;
|
return this.microphoneSelected;
|
||||||
}
|
}
|
||||||
|
|
||||||
setCameraSelected(deviceField: any) {
|
setCameraSelected(deviceId: any) {
|
||||||
this.cameraSelected = this.getCameraByDeviceField(deviceField);
|
this.cameraSelected = this.getCameraByDeviceField(deviceId);
|
||||||
this.saveCameraToStorage(this.cameraSelected);
|
this.saveCameraToStorage(this.cameraSelected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,8 +187,8 @@ export class DeviceService {
|
||||||
this.saveMicrophoneToStorage(this.microphoneSelected);
|
this.saveMicrophoneToStorage(this.microphoneSelected);
|
||||||
}
|
}
|
||||||
|
|
||||||
needUpdateVideoTrack(newVideoSource: string): boolean {
|
needUpdateVideoTrack(newDevice: CustomDevice): boolean {
|
||||||
return this.cameraSelected?.device !== newVideoSource;
|
return this.cameraSelected?.device !== newDevice.device || this.cameraSelected?.label !== newDevice.label;
|
||||||
}
|
}
|
||||||
|
|
||||||
needUpdateAudioTrack(newAudioSource: string): boolean {
|
needUpdateAudioTrack(newAudioSource: string): boolean {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable, Injector } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
Connection,
|
Connection,
|
||||||
OpenVidu,
|
OpenVidu,
|
||||||
|
@ -11,8 +11,6 @@ import {
|
||||||
Stream
|
Stream
|
||||||
} from 'openvidu-browser';
|
} from 'openvidu-browser';
|
||||||
|
|
||||||
import { LoggerService } from '../logger/logger.service';
|
|
||||||
|
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
import { CameraType } from '../../models/device.model';
|
import { CameraType } from '../../models/device.model';
|
||||||
import { ILogger } from '../../models/logger.model';
|
import { ILogger } from '../../models/logger.model';
|
||||||
|
@ -21,6 +19,7 @@ import { Signal } from '../../models/signal.model';
|
||||||
import { ScreenType, VideoType } from '../../models/video-type.model';
|
import { ScreenType, VideoType } from '../../models/video-type.model';
|
||||||
import { OpenViduAngularConfigService } from '../config/openvidu-angular.config.service';
|
import { OpenViduAngularConfigService } from '../config/openvidu-angular.config.service';
|
||||||
import { DeviceService } from '../device/device.service';
|
import { DeviceService } from '../device/device.service';
|
||||||
|
import { LoggerService } from '../logger/logger.service';
|
||||||
import { ParticipantService } from '../participant/participant.service';
|
import { ParticipantService } from '../participant/participant.service';
|
||||||
import { PlatformService } from '../platform/platform.service';
|
import { PlatformService } from '../platform/platform.service';
|
||||||
|
|
||||||
|
@ -53,7 +52,7 @@ export class OpenViduService {
|
||||||
protected openviduAngularConfigSrv: OpenViduAngularConfigService,
|
protected openviduAngularConfigSrv: OpenViduAngularConfigService,
|
||||||
protected platformService: PlatformService,
|
protected platformService: PlatformService,
|
||||||
protected loggerSrv: LoggerService,
|
protected loggerSrv: LoggerService,
|
||||||
private participantService: ParticipantService,
|
private injector: Injector,
|
||||||
protected deviceService: DeviceService
|
protected deviceService: DeviceService
|
||||||
) {
|
) {
|
||||||
this.log = this.loggerSrv.get('OpenViduService');
|
this.log = this.loggerSrv.get('OpenViduService');
|
||||||
|
@ -140,8 +139,6 @@ export class OpenViduService {
|
||||||
async clear() {
|
async clear() {
|
||||||
this.videoSource = undefined;
|
this.videoSource = undefined;
|
||||||
this.audioSource = undefined;
|
this.audioSource = undefined;
|
||||||
await this.participantService.getMyCameraPublisher()?.stream?.disposeMediaStream();
|
|
||||||
await this.participantService.getMyScreenPublisher()?.stream?.disposeMediaStream();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -201,30 +198,42 @@ export class OpenViduService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
|
* Connects to webcam session using webcam token.
|
||||||
*/
|
*/
|
||||||
async connectSession(session: Session, token: string): Promise<void> {
|
async connectWebcamSession(participantId: string, nickname: string): Promise<string | undefined> {
|
||||||
if (!!token && session) {
|
if (this.isWebcamSessionConnected()) {
|
||||||
const nickname = this.participantService.getMyNickname();
|
this.log.d('Webcam session is already connected');
|
||||||
const participantId = this.participantService.getLocalParticipant().id;
|
return undefined;
|
||||||
if (session === this.webcamSession) {
|
|
||||||
this.log.d('Connecting webcam session');
|
|
||||||
await this.webcamSession.connect(token, {
|
|
||||||
clientData: nickname,
|
|
||||||
participantId,
|
|
||||||
type: VideoType.CAMERA
|
|
||||||
});
|
|
||||||
this.participantService.setMyCameraConnectionId(this.webcamSession.connection.connectionId);
|
|
||||||
} else if (session === this.screenSession) {
|
|
||||||
this.log.d('Connecting screen session');
|
|
||||||
await this.screenSession.connect(token, {
|
|
||||||
clientData: `${nickname}_${VideoType.SCREEN}`,
|
|
||||||
participantId,
|
|
||||||
type: VideoType.SCREEN
|
|
||||||
});
|
|
||||||
|
|
||||||
this.participantService.setMyScreenConnectionId(this.screenSession.connection.connectionId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.log.d('Connecting webcam session');
|
||||||
|
await this.webcamSession.connect(this.getWebcamToken(), {
|
||||||
|
clientData: nickname,
|
||||||
|
participantId,
|
||||||
|
type: VideoType.CAMERA
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.webcamSession.connection.connectionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* Connects to screen session using screen token.
|
||||||
|
*/
|
||||||
|
async connectScreenSession(participantId: string, nickname: string): Promise<string | undefined> {
|
||||||
|
if (this.isScreenSessionConnected()) {
|
||||||
|
this.log.d('Screen session is already connected');
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log.d('Connecting screen session');
|
||||||
|
await this.screenSession.connect(this.getScreenToken(), {
|
||||||
|
clientData: `${nickname}_${VideoType.SCREEN}`,
|
||||||
|
participantId,
|
||||||
|
type: VideoType.SCREEN
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.screenSession.connection.connectionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -273,198 +282,116 @@ export class OpenViduService {
|
||||||
mirror
|
mirror
|
||||||
};
|
};
|
||||||
if (hasVideoDevices || hasAudioDevices) {
|
if (hasVideoDevices || hasAudioDevices) {
|
||||||
const publisher = await this.initPublisher(properties);
|
return this.initPublisher(properties);
|
||||||
this.participantService.setMyCameraPublisher(publisher);
|
|
||||||
this.participantService.updateLocalParticipant();
|
|
||||||
return publisher;
|
|
||||||
} else {
|
|
||||||
this.participantService.setMyCameraPublisher(null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
private async initPublisher(properties: PublisherProperties, targetElement?: string | HTMLElement): Promise<Publisher> {
|
private initPublisher(properties: PublisherProperties, targetElement?: string | HTMLElement): Promise<Publisher> {
|
||||||
this.log.d('Initializing publisher with properties: ', properties);
|
this.log.d('Initializing publisher with properties: ', properties);
|
||||||
return this.OV.initPublisherAsync(targetElement, properties);
|
return this.OV.initPublisherAsync(targetElement, properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
|
* @param hasAudio
|
||||||
|
* @returns
|
||||||
*/
|
*/
|
||||||
async publish(publisher: Publisher): Promise<void> {
|
initScreenPublisher(hasAudio: boolean): Promise<Publisher> {
|
||||||
if (!!publisher) {
|
const hasAudioDevicesAvailable = this.deviceService.hasAudioDeviceAvailable();
|
||||||
if (publisher === this.participantService.getMyCameraPublisher()) {
|
|
||||||
if (this.webcamSession?.capabilities?.publish) {
|
const properties: PublisherProperties = {
|
||||||
return await this.webcamSession.publish(publisher);
|
videoSource: ScreenType.SCREEN,
|
||||||
}
|
audioSource: hasAudioDevicesAvailable ? this.deviceService.getMicrophoneSelected().device : false,
|
||||||
this.log.e('Webcam publisher cannot be published');
|
publishVideo: true,
|
||||||
} else if (publisher === this.participantService.getMyScreenPublisher()) {
|
publishAudio: hasAudio && hasAudioDevicesAvailable,
|
||||||
if (this.screenSession?.capabilities?.publish) {
|
mirror: false
|
||||||
return await this.screenSession.publish(publisher);
|
};
|
||||||
}
|
return this.initPublisher(properties);
|
||||||
this.log.e('Screen publisher cannot be published');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* Publishes the publisher to the webcam Session
|
||||||
|
* @param publisher
|
||||||
*/
|
*/
|
||||||
private async unpublish(publisher: Publisher): Promise<void> {
|
async publishCamera(publisher: Publisher): Promise<void> {
|
||||||
if (!!publisher) {
|
if (!publisher) return;
|
||||||
if (publisher === this.participantService.getMyCameraPublisher()) {
|
if (this.webcamSession?.capabilities?.publish) {
|
||||||
this.publishAudioAux(this.participantService.getMyScreenPublisher(), this.participantService.isMyAudioActive());
|
return this.webcamSession.publish(publisher);
|
||||||
await this.webcamSession.unpublish(publisher);
|
|
||||||
} else if (publisher === this.participantService.getMyScreenPublisher()) {
|
|
||||||
await this.screenSession.unpublish(publisher);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
this.log.e('Webcam publisher cannot be published');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publishes the publisher to the screen Session
|
||||||
|
* @param publisher
|
||||||
|
*/
|
||||||
|
async publishScreen(publisher: Publisher): Promise<void> {
|
||||||
|
if (!publisher) return;
|
||||||
|
|
||||||
|
if (this.screenSession?.capabilities?.publish) {
|
||||||
|
return this.screenSession.publish(publisher);
|
||||||
|
}
|
||||||
|
this.log.e('Screen publisher cannot be published');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unpublishes the publisher of the webcam Session
|
||||||
|
* @param publisher
|
||||||
|
*/
|
||||||
|
async unpublishCamera(publisher: Publisher): Promise<void> {
|
||||||
|
if (!publisher) return;
|
||||||
|
return this.webcamSession.unpublish(publisher);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unpublishes the publisher of the screen Session
|
||||||
|
* @param publisher
|
||||||
|
*/
|
||||||
|
async unpublishScreen(publisher: Publisher): Promise<void> {
|
||||||
|
if (!publisher) return;
|
||||||
|
return this.screenSession.unpublish(publisher);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Publish or unpublish the video stream (if available).
|
* Publish or unpublish the video stream (if available).
|
||||||
* It hides the camera muted stream if screen is sharing.
|
* It hides the camera muted stream if screen is sharing.
|
||||||
* See openvidu-browser {@link https://docs.openvidu.io/en/stable/api/openvidu-browser/classes/Publisher.html#publishVideo publishVideo}
|
* See openvidu-browser {@link https://docs.openvidu.io/en/stable/api/openvidu-browser/classes/Publisher.html#publishVideo publishVideo}
|
||||||
|
*
|
||||||
|
* @deprecated This method has been moved to ParticipantService
|
||||||
|
*
|
||||||
|
* TODO: Remove this method in release 2.29.0
|
||||||
*/
|
*/
|
||||||
async publishVideo(publish: boolean): Promise<void> {
|
async publishVideo(publish: boolean): Promise<void> {
|
||||||
const publishAudio = this.participantService.isMyAudioActive();
|
const participantService = this.injector.get(ParticipantService);
|
||||||
|
return participantService.publishVideo(publish);
|
||||||
// Disabling webcam
|
|
||||||
if (this.participantService.haveICameraAndScreenActive()) {
|
|
||||||
await this.publishVideoAux(this.participantService.getMyCameraPublisher(), publish);
|
|
||||||
await this.unpublish(this.participantService.getMyCameraPublisher());
|
|
||||||
this.publishAudioAux(this.participantService.getMyScreenPublisher(), publishAudio);
|
|
||||||
this.participantService.disableWebcamStream();
|
|
||||||
} else if (this.participantService.isOnlyMyScreenActive()) {
|
|
||||||
// Enabling webcam
|
|
||||||
const hasAudio = this.participantService.hasScreenAudioActive();
|
|
||||||
if (!this.isWebcamSessionConnected()) {
|
|
||||||
await this.connectSession(this.getWebcamSession(), this.getWebcamToken());
|
|
||||||
}
|
|
||||||
await this.publish(this.participantService.getMyCameraPublisher());
|
|
||||||
await this.publishVideoAux(this.participantService.getMyCameraPublisher(), true);
|
|
||||||
this.publishAudioAux(this.participantService.getMyScreenPublisher(), false);
|
|
||||||
this.publishAudioAux(this.participantService.getMyCameraPublisher(), hasAudio);
|
|
||||||
this.participantService.enableWebcamStream();
|
|
||||||
} else {
|
|
||||||
// Muting/unmuting webcam
|
|
||||||
await this.publishVideoAux(this.participantService.getMyCameraPublisher(), publish);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
private async publishVideoAux(publisher: Publisher, publish: boolean): Promise<void> {
|
|
||||||
if (!!publisher) {
|
|
||||||
let resource: boolean | MediaStreamTrack = true;
|
|
||||||
if (publish) {
|
|
||||||
// Forcing restoration with a custom media stream (the older one instead the default)
|
|
||||||
const currentDeviceId = this.deviceService.getCameraSelected()?.device;
|
|
||||||
const mediaStream = await this.createMediaStream({ videoSource: currentDeviceId, audioSource: false });
|
|
||||||
resource = mediaStream.getVideoTracks()[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
await publisher.publishVideo(publish, resource);
|
|
||||||
this.participantService.updateLocalParticipant();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Publish or unpublish the audio stream (if available).
|
|
||||||
* See openvidu-browser {@link https://docs.openvidu.io/en/stable/api/openvidu-browser/classes/Publisher.html#publishAudio publishAudio}.
|
|
||||||
*/
|
|
||||||
async publishAudio(publish: boolean): Promise<void> {
|
|
||||||
if (this.participantService.isMyCameraActive()) {
|
|
||||||
if (this.participantService.isMyScreenActive() && this.participantService.hasScreenAudioActive()) {
|
|
||||||
this.publishAudioAux(this.participantService.getMyScreenPublisher(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.publishAudioAux(this.participantService.getMyCameraPublisher(), publish);
|
|
||||||
} else {
|
|
||||||
this.publishAudioAux(this.participantService.getMyScreenPublisher(), publish);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Share or unshare the screen.
|
* Share or unshare the screen.
|
||||||
* Hide the camera muted stream when screen is sharing.
|
* Hide the camera muted stream when screen is sharing.
|
||||||
|
* @deprecated This method has been moved to ParticipantService
|
||||||
|
*
|
||||||
|
* TODO: Remove this method in release 2.29.0
|
||||||
*/
|
*/
|
||||||
async toggleScreenshare() {
|
async toggleScreenshare() {
|
||||||
if (this.participantService.haveICameraAndScreenActive()) {
|
const participantService = this.injector.get(ParticipantService);
|
||||||
// Disabling screenShare
|
return participantService.toggleScreenshare();
|
||||||
this.participantService.disableScreenStream();
|
|
||||||
await this.unpublish(this.participantService.getMyScreenPublisher());
|
|
||||||
} else if (this.participantService.isOnlyMyCameraActive()) {
|
|
||||||
// I only have the camera published
|
|
||||||
const hasAudioDevicesAvailable = this.deviceService.hasAudioDeviceAvailable();
|
|
||||||
const willWebcamBePresent = this.participantService.isMyCameraActive() && this.participantService.isMyVideoActive();
|
|
||||||
const hasAudio = willWebcamBePresent ? false : hasAudioDevicesAvailable && this.participantService.isMyAudioActive();
|
|
||||||
|
|
||||||
const properties: PublisherProperties = {
|
|
||||||
videoSource: ScreenType.SCREEN,
|
|
||||||
audioSource: hasAudioDevicesAvailable ? this.deviceService.getMicrophoneSelected().device : false,
|
|
||||||
publishVideo: true,
|
|
||||||
publishAudio: hasAudio,
|
|
||||||
mirror: false
|
|
||||||
};
|
|
||||||
const screenPublisher = await this.initPublisher(properties);
|
|
||||||
|
|
||||||
screenPublisher.once('accessAllowed', async () => {
|
|
||||||
// Listen to event fired when native stop button is clicked
|
|
||||||
screenPublisher.stream
|
|
||||||
.getMediaStream()
|
|
||||||
.getVideoTracks()[0]
|
|
||||||
.addEventListener('ended', async () => {
|
|
||||||
this.log.d('Clicked native stop button. Stopping screen sharing');
|
|
||||||
await this.toggleScreenshare();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Enabling screenShare
|
|
||||||
this.participantService.activeMyScreenShare(screenPublisher);
|
|
||||||
|
|
||||||
if (!this.isScreenSessionConnected()) {
|
|
||||||
await this.connectSession(this.getScreenSession(), this.getScreenToken());
|
|
||||||
}
|
|
||||||
await this.publish(this.participantService.getMyScreenPublisher());
|
|
||||||
if (!this.participantService.isMyVideoActive()) {
|
|
||||||
// Disabling webcam
|
|
||||||
this.participantService.disableWebcamStream();
|
|
||||||
await this.unpublish(this.participantService.getMyCameraPublisher());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
screenPublisher.once('accessDenied', (error: any) => {
|
|
||||||
return Promise.reject(error);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// I only have my screenshare active and I have no camera or it is muted
|
|
||||||
const hasAudio = this.participantService.hasScreenAudioActive();
|
|
||||||
|
|
||||||
// Enable webcam
|
|
||||||
if (!this.isWebcamSessionConnected()) {
|
|
||||||
await this.connectSession(this.getWebcamSession(), this.getWebcamToken());
|
|
||||||
}
|
|
||||||
await this.publish(this.participantService.getMyCameraPublisher());
|
|
||||||
this.publishAudioAux(this.participantService.getMyCameraPublisher(), hasAudio);
|
|
||||||
this.participantService.enableWebcamStream();
|
|
||||||
|
|
||||||
// Disabling screenshare
|
|
||||||
this.participantService.disableScreenStream();
|
|
||||||
await this.unpublish(this.participantService.getMyScreenPublisher());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
*
|
||||||
|
* Publish or unpublish the audio stream (if available).
|
||||||
|
* See openvidu-browser {@link https://docs.openvidu.io/en/stable/api/openvidu-browser/classes/Publisher.html#publishAudio publishAudio}.
|
||||||
|
* @deprecated This method has been moved to ParticipantService
|
||||||
|
*
|
||||||
|
* TODO: Remove this method in release 2.29.0
|
||||||
*/
|
*/
|
||||||
private publishAudioAux(publisher: Publisher, value: boolean): void {
|
publishAudio(publish: boolean): void {
|
||||||
if (!!publisher) {
|
const participantService = this.injector.get(ParticipantService);
|
||||||
publisher.publishAudio(value);
|
participantService.publishAudio(publish);
|
||||||
this.participantService.updateLocalParticipant();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -473,52 +400,60 @@ export class OpenViduService {
|
||||||
* @param type: type of signal
|
* @param type: type of signal
|
||||||
* @param connections: if undefined, the signal will be sent to all participants
|
* @param connections: if undefined, the signal will be sent to all participants
|
||||||
*/
|
*/
|
||||||
sendSignal(type: Signal, connections?: Connection[], data?: any): void {
|
sendSignal(type: Signal, connections?: Connection[], data?: any): Promise<void> {
|
||||||
const signalOptions: SignalOptions = {
|
const signalOptions: SignalOptions = {
|
||||||
data: JSON.stringify(data),
|
data: JSON.stringify(data),
|
||||||
type,
|
type,
|
||||||
to: connections && connections.length > 0 ? connections : undefined
|
to: connections && connections.length > 0 ? connections : undefined
|
||||||
};
|
};
|
||||||
this.webcamSession.signal(signalOptions);
|
return this.webcamSession.signal(signalOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
|
* @param cameraPublisher
|
||||||
|
* @param props
|
||||||
*/
|
*/
|
||||||
async replaceTrack(videoType: VideoType, props: PublisherProperties) {
|
async replaceCameraTrack(cameraPublisher: Publisher, props: PublisherProperties) {
|
||||||
|
const isReplacingAudio = !!props.audioSource;
|
||||||
|
const isReplacingVideo = !!props.videoSource;
|
||||||
|
let mediaStream: MediaStream | undefined;
|
||||||
|
let track: MediaStreamTrack | undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.log.d(`Replacing ${videoType} track`, props);
|
if (isReplacingVideo || isReplacingAudio) {
|
||||||
|
mediaStream = await this.createMediaStream(props);
|
||||||
|
}
|
||||||
|
|
||||||
if (videoType === VideoType.CAMERA) {
|
if (isReplacingVideo) {
|
||||||
let mediaStream: MediaStream;
|
track = mediaStream?.getVideoTracks()[0];
|
||||||
const isReplacingAudio = !!props.audioSource;
|
} else if (isReplacingAudio) {
|
||||||
const isReplacingVideo = !!props.videoSource;
|
track = mediaStream?.getAudioTracks()[0];
|
||||||
|
}
|
||||||
|
|
||||||
if (isReplacingVideo) {
|
if (track) {
|
||||||
mediaStream = await this.createMediaStream(props);
|
await cameraPublisher.replaceTrack(track);
|
||||||
// Replace video track
|
|
||||||
const videoTrack: MediaStreamTrack = mediaStream.getVideoTracks()[0];
|
|
||||||
await this.participantService.getMyCameraPublisher().replaceTrack(videoTrack);
|
|
||||||
} else if (isReplacingAudio) {
|
|
||||||
mediaStream = await this.createMediaStream(props);
|
|
||||||
// Replace audio track
|
|
||||||
const audioTrack: MediaStreamTrack = mediaStream.getAudioTracks()[0];
|
|
||||||
await this.participantService.getMyCameraPublisher().replaceTrack(audioTrack);
|
|
||||||
}
|
|
||||||
} else if (videoType === VideoType.SCREEN) {
|
|
||||||
try {
|
|
||||||
let newScreenMediaStream = await this.OVScreen.getUserMedia(props);
|
|
||||||
this.participantService.getMyScreenPublisher().stream.getMediaStream().getVideoTracks()[0].stop();
|
|
||||||
await this.participantService.getMyScreenPublisher().replaceTrack(newScreenMediaStream.getVideoTracks()[0]);
|
|
||||||
} catch (error) {
|
|
||||||
this.log.w('Cannot create the new MediaStream', error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.log.e('Error replacing track ', error);
|
this.log.e('Error replacing track ', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @param screenPublisher
|
||||||
|
* @param props
|
||||||
|
*/
|
||||||
|
async replaceScreenTrack(screenPublisher: Publisher, props: PublisherProperties) {
|
||||||
|
try {
|
||||||
|
let newScreenMediaStream = await this.OVScreen.getUserMedia(props);
|
||||||
|
screenPublisher.stream.getMediaStream().getVideoTracks()[0].stop();
|
||||||
|
await screenPublisher.replaceTrack(newScreenMediaStream.getVideoTracks()[0]);
|
||||||
|
} catch (error) {
|
||||||
|
this.log.w('Cannot create the new MediaStream', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
* Subscribe all `CAMERA` stream types to speech-to-text
|
* Subscribe all `CAMERA` stream types to speech-to-text
|
||||||
|
@ -527,7 +462,9 @@ export class OpenViduService {
|
||||||
* @param lang The language of the Stream's audio track.
|
* @param lang The language of the Stream's audio track.
|
||||||
*/
|
*/
|
||||||
async subscribeRemotesToSTT(lang: string): Promise<void> {
|
async subscribeRemotesToSTT(lang: string): Promise<void> {
|
||||||
const remoteParticipants = this.participantService.getRemoteParticipants();
|
const participantService = this.injector.get(ParticipantService);
|
||||||
|
|
||||||
|
const remoteParticipants = participantService.getRemoteParticipants();
|
||||||
let successNumber = 0;
|
let successNumber = 0;
|
||||||
|
|
||||||
for (const p of remoteParticipants) {
|
for (const p of remoteParticipants) {
|
||||||
|
@ -566,9 +503,11 @@ export class OpenViduService {
|
||||||
* Unsubscribe to all `CAMERA` stream types to speech-to-text if STT is up(ready)
|
* Unsubscribe to all `CAMERA` stream types to speech-to-text if STT is up(ready)
|
||||||
*/
|
*/
|
||||||
async unsubscribeRemotesFromSTT(): Promise<void> {
|
async unsubscribeRemotesFromSTT(): Promise<void> {
|
||||||
|
const participantService = this.injector.get(ParticipantService);
|
||||||
|
|
||||||
clearTimeout(this.sttReconnectionTimeout);
|
clearTimeout(this.sttReconnectionTimeout);
|
||||||
if (this.isSttReady()) {
|
if (this.isSttReady()) {
|
||||||
for (const p of this.participantService.getRemoteParticipants()) {
|
for (const p of participantService.getRemoteParticipants()) {
|
||||||
const stream = p.getCameraConnection().streamManager.stream;
|
const stream = p.getCameraConnection().streamManager.stream;
|
||||||
if (stream) {
|
if (stream) {
|
||||||
try {
|
try {
|
||||||
|
@ -581,7 +520,14 @@ export class OpenViduService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createMediaStream(pp: PublisherProperties): Promise<MediaStream> {
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @param pp {@link PublisherProperties}
|
||||||
|
* @returns Promise<MediaStream>
|
||||||
|
*/
|
||||||
|
async createMediaStream(pp: PublisherProperties): Promise<MediaStream> {
|
||||||
|
const participantService = this.injector.get(ParticipantService);
|
||||||
const currentCameraSelected = this.deviceService.getCameraSelected();
|
const currentCameraSelected = this.deviceService.getCameraSelected();
|
||||||
const currentMicSelected = this.deviceService.getMicrophoneSelected();
|
const currentMicSelected = this.deviceService.getMicrophoneSelected();
|
||||||
const isReplacingAudio = Boolean(pp.audioSource);
|
const isReplacingAudio = Boolean(pp.audioSource);
|
||||||
|
@ -589,7 +535,7 @@ export class OpenViduService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const trackType = isReplacingAudio ? 'audio' : 'video';
|
const trackType = isReplacingAudio ? 'audio' : 'video';
|
||||||
this.forceStopMediaTracks(this.participantService.getMyCameraPublisher().stream.getMediaStream(), trackType);
|
this.forceStopMediaTracks(participantService.getMyCameraPublisher().stream.getMediaStream(), trackType);
|
||||||
return this.OV.getUserMedia(pp);
|
return this.OV.getUserMedia(pp);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Error creating MediaStream', error);
|
console.warn('Error creating MediaStream', error);
|
||||||
|
@ -611,7 +557,8 @@ export class OpenViduService {
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
needSendNicknameSignal(): boolean {
|
myNicknameHasBeenChanged(): boolean {
|
||||||
|
const participantService = this.injector.get(ParticipantService);
|
||||||
let oldNickname: string = "";
|
let oldNickname: string = "";
|
||||||
try {
|
try {
|
||||||
const connData = JSON.parse(this.cleanConnectionData(this.webcamSession.connection.data));
|
const connData = JSON.parse(this.cleanConnectionData(this.webcamSession.connection.data));
|
||||||
|
@ -619,7 +566,7 @@ export class OpenViduService {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.log.e(error);
|
this.log.e(error);
|
||||||
} finally {
|
} finally {
|
||||||
return oldNickname !== this.participantService.getMyNickname();
|
return oldNickname !== participantService.getMyNickname();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ export class PanelService {
|
||||||
private isExternalOpened: boolean = false;
|
private isExternalOpened: boolean = false;
|
||||||
private externalType: string;
|
private externalType: string;
|
||||||
protected _panelOpened = <BehaviorSubject<PanelEvent>>new BehaviorSubject({ opened: false });
|
protected _panelOpened = <BehaviorSubject<PanelEvent>>new BehaviorSubject({ opened: false });
|
||||||
private panelMap: Map<string, boolean> = new Map();
|
private panelTypes: string[] = Object.values(PanelType);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
|
@ -24,7 +24,6 @@ export class PanelService {
|
||||||
constructor(protected loggerSrv: LoggerService) {
|
constructor(protected loggerSrv: LoggerService) {
|
||||||
this.log = this.loggerSrv.get('PanelService');
|
this.log = this.loggerSrv.get('PanelService');
|
||||||
this.panelOpenedObs = this._panelOpened.asObservable();
|
this.panelOpenedObs = this._panelOpened.asObservable();
|
||||||
Object.values(PanelType).forEach((panel) => this.panelMap.set(panel, false));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,31 +32,22 @@ export class PanelService {
|
||||||
*/
|
*/
|
||||||
togglePanel(type: PanelType | string, expand?: PanelSettingsOptions | string) {
|
togglePanel(type: PanelType | string, expand?: PanelSettingsOptions | string) {
|
||||||
let nextOpenedValue: boolean = false;
|
let nextOpenedValue: boolean = false;
|
||||||
if (this.panelMap.has(type)) {
|
const oldType = this._panelOpened.getValue().type;
|
||||||
|
const oldOpened = this._panelOpened.getValue().opened;
|
||||||
|
|
||||||
|
if (this.panelTypes.includes(type)) {
|
||||||
this.log.d(`Toggling ${type} menu`);
|
this.log.d(`Toggling ${type} menu`);
|
||||||
|
|
||||||
this.panelMap.forEach((opened: boolean, panel: string) => {
|
nextOpenedValue = oldType !== type ? true : !oldOpened;
|
||||||
if (panel === type) {
|
|
||||||
// Toggle panel
|
|
||||||
this.panelMap.set(panel, !opened);
|
|
||||||
nextOpenedValue = !opened;
|
|
||||||
} else {
|
|
||||||
// Close others
|
|
||||||
this.panelMap.set(panel, false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// Panel is external
|
// Panel is external
|
||||||
this.log.d('Toggling external panel');
|
this.log.d('Toggling external panel');
|
||||||
// Close all panels
|
|
||||||
this.panelMap.forEach((_, panel: string) => this.panelMap.set(panel, false));
|
|
||||||
// Opening when external panel is closed or is opened with another type
|
// Opening when external panel is closed or is opened with another type
|
||||||
this.isExternalOpened = !this.isExternalOpened || this.externalType !== type;
|
this.isExternalOpened = !this.isExternalOpened || this.externalType !== type;
|
||||||
this.externalType = !this.isExternalOpened ? '' : type;
|
this.externalType = !this.isExternalOpened ? '' : type;
|
||||||
nextOpenedValue = this.isExternalOpened;
|
nextOpenedValue = this.isExternalOpened;
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldType = this._panelOpened.getValue().type;
|
|
||||||
this._panelOpened.next({ opened: nextOpenedValue, type, expand, oldType });
|
this._panelOpened.next({ opened: nextOpenedValue, type, expand, oldType });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,51 +55,54 @@ export class PanelService {
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
isPanelOpened(): boolean {
|
isPanelOpened(): boolean {
|
||||||
const anyOpened = Array.from(this.panelMap.values()).some((opened) => opened);
|
return this._panelOpened.getValue().opened;
|
||||||
return anyOpened || this.isExternalPanelOpened();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the panel (if opened)
|
* Closes the panel (if opened)
|
||||||
*/
|
*/
|
||||||
closePanel(): void {
|
closePanel(): void {
|
||||||
this.panelMap.forEach((_, panel: string) => this.panelMap.set(panel, false));
|
this._panelOpened.next({ opened: false, type: undefined, expand: undefined, oldType: undefined });
|
||||||
this._panelOpened.next({ opened: false });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the chat panel is opened or not.
|
* Whether the chat panel is opened or not.
|
||||||
*/
|
*/
|
||||||
isChatPanelOpened(): boolean {
|
isChatPanelOpened(): boolean {
|
||||||
return !!this.panelMap.get(PanelType.CHAT);
|
const panelState = this._panelOpened.getValue();
|
||||||
|
return panelState.opened && panelState.type === PanelType.CHAT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the participants panel is opened or not.
|
* Whether the participants panel is opened or not.
|
||||||
*/
|
*/
|
||||||
isParticipantsPanelOpened(): boolean {
|
isParticipantsPanelOpened(): boolean {
|
||||||
return !!this.panelMap.get(PanelType.PARTICIPANTS);
|
const panelState = this._panelOpened.getValue();
|
||||||
|
return panelState.opened && panelState.type === PanelType.PARTICIPANTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the activities panel is opened or not.
|
* Whether the activities panel is opened or not.
|
||||||
*/
|
*/
|
||||||
isActivitiesPanelOpened(): boolean {
|
isActivitiesPanelOpened(): boolean {
|
||||||
return !!this.panelMap.get(PanelType.ACTIVITIES);
|
const panelState = this._panelOpened.getValue();
|
||||||
|
return panelState.opened && panelState.type === PanelType.ACTIVITIES;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the settings panel is opened or not.
|
* Whether the settings panel is opened or not.
|
||||||
*/
|
*/
|
||||||
isSettingsPanelOpened(): boolean {
|
isSettingsPanelOpened(): boolean {
|
||||||
return !!this.panelMap.get(PanelType.SETTINGS);
|
const panelState = this._panelOpened.getValue();
|
||||||
|
return panelState.opened && panelState.type === PanelType.SETTINGS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the background effects panel is opened or not.
|
* Whether the background effects panel is opened or not.
|
||||||
*/
|
*/
|
||||||
isBackgroundEffectsPanelOpened(): boolean {
|
isBackgroundEffectsPanelOpened(): boolean {
|
||||||
return !!this.panelMap.get(PanelType.BACKGROUND_EFFECTS);
|
const panelState = this._panelOpened.getValue();
|
||||||
|
return panelState.opened && panelState.type === PanelType.BACKGROUND_EFFECTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
isExternalPanelOpened(): boolean {
|
isExternalPanelOpened(): boolean {
|
||||||
|
|
|
@ -2,10 +2,18 @@ import { Injectable } from '@angular/core';
|
||||||
import { Publisher, Subscriber } from 'openvidu-browser';
|
import { Publisher, Subscriber } from 'openvidu-browser';
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
import { ILogger } from '../../models/logger.model';
|
import { ILogger } from '../../models/logger.model';
|
||||||
import { OpenViduRole, 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 { VideoType } from '../../models/video-type.model';
|
||||||
import { OpenViduAngularConfigService } from '../config/openvidu-angular.config.service';
|
import { OpenViduAngularConfigService } from '../config/openvidu-angular.config.service';
|
||||||
|
import { DeviceService } from '../device/device.service';
|
||||||
import { LoggerService } from '../logger/logger.service';
|
import { LoggerService } from '../logger/logger.service';
|
||||||
|
import { OpenViduService } from '../openvidu/openvidu.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
|
@ -33,9 +41,13 @@ export class ParticipantService {
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
constructor(protected openviduAngularConfigSrv: OpenViduAngularConfigService, protected loggerSrv: LoggerService) {
|
constructor(
|
||||||
|
protected openviduAngularConfigSrv: OpenViduAngularConfigService,
|
||||||
|
private openviduService: OpenViduService,
|
||||||
|
private deviceService: DeviceService,
|
||||||
|
protected loggerSrv: LoggerService
|
||||||
|
) {
|
||||||
this.log = this.loggerSrv.get('ParticipantService');
|
this.log = this.loggerSrv.get('ParticipantService');
|
||||||
|
|
||||||
this.localParticipantObs = this._localParticipant.asObservable();
|
this.localParticipantObs = this._localParticipant.asObservable();
|
||||||
this.remoteParticipantsObs = this._remoteParticipants.asObservable();
|
this.remoteParticipantsObs = this._remoteParticipants.asObservable();
|
||||||
}
|
}
|
||||||
|
@ -52,6 +64,129 @@ export class ParticipantService {
|
||||||
return this.localParticipant;
|
return this.localParticipant;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publish or unpublish the local participant video stream (if available).
|
||||||
|
* It hides the camera stream (while muted) if screen is sharing.
|
||||||
|
* See openvidu-browser {@link https://docs.openvidu.io/en/stable/api/openvidu-browser/classes/Publisher.html#publishVideo publishVideo}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
async publishVideo(publish: boolean): Promise<void> {
|
||||||
|
const publishAudio = this.isMyAudioActive();
|
||||||
|
const cameraPublisher = this.getMyCameraPublisher();
|
||||||
|
const screenPublisher = this.getMyScreenPublisher();
|
||||||
|
|
||||||
|
// Disabling webcam
|
||||||
|
if (this.localParticipant.hasCameraAndScreenActives()) {
|
||||||
|
await this.publishVideoAux(cameraPublisher, publish);
|
||||||
|
this.disableWebcamStream();
|
||||||
|
this.openviduService.unpublishCamera(cameraPublisher);
|
||||||
|
this.publishAudioAux(screenPublisher, publishAudio);
|
||||||
|
} else if (this.localParticipant.hasOnlyScreenActive()) {
|
||||||
|
// Enabling webcam
|
||||||
|
const hasAudio = this.hasScreenAudioActive();
|
||||||
|
const sessionId = await this.openviduService.connectWebcamSession(this.getMyNickname(), this.getLocalParticipant().id);
|
||||||
|
if (sessionId) this.setMyCameraConnectionId(sessionId);
|
||||||
|
await this.openviduService.publishCamera(cameraPublisher);
|
||||||
|
await this.publishVideoAux(cameraPublisher, true);
|
||||||
|
this.publishAudioAux(screenPublisher, false);
|
||||||
|
this.publishAudioAux(cameraPublisher, hasAudio);
|
||||||
|
this.enableWebcamStream();
|
||||||
|
} else {
|
||||||
|
// Muting/unmuting webcam
|
||||||
|
await this.publishVideoAux(cameraPublisher, publish);
|
||||||
|
}
|
||||||
|
this.updateLocalParticipant();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publish or unpublish the local participant audio stream (if available).
|
||||||
|
* See openvidu-browser {@link https://docs.openvidu.io/en/stable/api/openvidu-browser/classes/Publisher.html#publishAudio publishAudio}.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
publishAudio(publish: boolean): void {
|
||||||
|
if (this.isMyCameraActive()) {
|
||||||
|
if (this.localParticipant.isScreenActive() && this.hasScreenAudioActive()) {
|
||||||
|
this.publishAudioAux(this.getMyScreenPublisher(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.publishAudioAux(this.getMyCameraPublisher(), publish);
|
||||||
|
} else {
|
||||||
|
this.publishAudioAux(this.getMyScreenPublisher(), publish);
|
||||||
|
}
|
||||||
|
this.updateLocalParticipant();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Share or unshare the local participant screen.
|
||||||
|
* Hide the camera stream (while muted) when screen is sharing.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
async toggleScreenshare() {
|
||||||
|
|
||||||
|
const screenPublisher = this.getMyScreenPublisher();
|
||||||
|
const cameraPublisher = this.getMyCameraPublisher();
|
||||||
|
const participantNickname = this.getMyNickname();
|
||||||
|
const participantId = this.getLocalParticipant().id;
|
||||||
|
|
||||||
|
if (this.localParticipant.hasCameraAndScreenActives()) {
|
||||||
|
// Disabling screenShare
|
||||||
|
this.disableScreenStream();
|
||||||
|
this.updateLocalParticipant();
|
||||||
|
this.openviduService.unpublishScreen(screenPublisher);
|
||||||
|
} else if (this.localParticipant.hasOnlyCameraActive()) {
|
||||||
|
// I only have the camera published
|
||||||
|
const willWebcamBePresent = this.isMyCameraActive() && this.isMyVideoActive();
|
||||||
|
const hasAudio = willWebcamBePresent ? false : this.isMyAudioActive();
|
||||||
|
const screenPublisher = await this.openviduService.initScreenPublisher(hasAudio);
|
||||||
|
|
||||||
|
screenPublisher.once('accessAllowed', async () => {
|
||||||
|
// Listen to event fired when native stop button is clicked
|
||||||
|
screenPublisher.stream
|
||||||
|
.getMediaStream()
|
||||||
|
.getVideoTracks()[0]
|
||||||
|
.addEventListener('ended', async () => {
|
||||||
|
this.log.d('Clicked native stop button. Stopping screen sharing');
|
||||||
|
await this.toggleScreenshare();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Enabling screenShare
|
||||||
|
this.activeMyScreenShare(screenPublisher);
|
||||||
|
|
||||||
|
if (!this.openviduService.isScreenSessionConnected()) {
|
||||||
|
await this.openviduService.connectScreenSession(participantId, participantNickname);
|
||||||
|
}
|
||||||
|
await this.openviduService.publishScreen(screenPublisher);
|
||||||
|
if (!this.isMyVideoActive()) {
|
||||||
|
// Disabling webcam
|
||||||
|
this.disableWebcamStream();
|
||||||
|
this.updateLocalParticipant();
|
||||||
|
this.openviduService.unpublishCamera(cameraPublisher);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
screenPublisher.once('accessDenied', (error: any) => {
|
||||||
|
return Promise.reject(error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// I only have my screenshare active and I have no camera or it is muted
|
||||||
|
const hasAudio = this.hasScreenAudioActive();
|
||||||
|
|
||||||
|
// Enable webcam
|
||||||
|
if (!this.openviduService.isWebcamSessionConnected()) {
|
||||||
|
await this.openviduService.connectWebcamSession(participantId, participantNickname);
|
||||||
|
}
|
||||||
|
await this.openviduService.publishCamera(cameraPublisher);
|
||||||
|
this.publishAudioAux(cameraPublisher, hasAudio);
|
||||||
|
this.enableWebcamStream();
|
||||||
|
|
||||||
|
// Disabling screenshare
|
||||||
|
this.disableScreenStream();
|
||||||
|
this.updateLocalParticipant();
|
||||||
|
this.openviduService.unpublishScreen(screenPublisher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
@ -62,7 +197,7 @@ export class ParticipantService {
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
setMyCameraPublisher(publisher: Publisher) {
|
setMyCameraPublisher(publisher: Publisher | undefined) {
|
||||||
this.localParticipant.setCameraPublisher(publisher);
|
this.localParticipant.setCameraPublisher(publisher);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
@ -98,7 +233,6 @@ export class ParticipantService {
|
||||||
*/
|
*/
|
||||||
enableWebcamStream() {
|
enableWebcamStream() {
|
||||||
this.localParticipant.enableCamera();
|
this.localParticipant.enableCamera();
|
||||||
this.updateLocalParticipant();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -106,7 +240,6 @@ export class ParticipantService {
|
||||||
*/
|
*/
|
||||||
disableWebcamStream() {
|
disableWebcamStream() {
|
||||||
this.localParticipant.disableCamera();
|
this.localParticipant.disableCamera();
|
||||||
this.updateLocalParticipant();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -134,7 +267,6 @@ export class ParticipantService {
|
||||||
*/
|
*/
|
||||||
disableScreenStream() {
|
disableScreenStream() {
|
||||||
this.localParticipant.disableScreen();
|
this.localParticipant.disableScreen();
|
||||||
this.updateLocalParticipant();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -180,7 +312,9 @@ export class ParticipantService {
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
clear() {
|
async clear() {
|
||||||
|
await this.getMyCameraPublisher()?.stream?.disposeMediaStream();
|
||||||
|
await this.getMyScreenPublisher()?.stream?.disposeMediaStream();
|
||||||
this.disableScreenStream();
|
this.disableScreenStream();
|
||||||
this.remoteParticipants = [];
|
this.remoteParticipants = [];
|
||||||
this.updateRemoteParticipants();
|
this.updateRemoteParticipants();
|
||||||
|
@ -202,34 +336,6 @@ export class ParticipantService {
|
||||||
return this.localParticipant?.hasAudioActive();
|
return this.localParticipant?.hasAudioActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
isMyScreenActive(): boolean {
|
|
||||||
return this.localParticipant.isScreenActive();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
isOnlyMyCameraActive(): boolean {
|
|
||||||
return this.isMyCameraActive() && !this.isMyScreenActive();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
isOnlyMyScreenActive(): boolean {
|
|
||||||
return this.isMyScreenActive() && !this.isMyCameraActive();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
haveICameraAndScreenActive(): boolean {
|
|
||||||
return this.isMyCameraActive() && this.isMyScreenActive();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
@ -241,7 +347,32 @@ export class ParticipantService {
|
||||||
* Force to update the local participant object and fire a new {@link localParticipantObs} Observable event.
|
* Force to update the local participant object and fire a new {@link localParticipantObs} Observable event.
|
||||||
*/
|
*/
|
||||||
updateLocalParticipant() {
|
updateLocalParticipant() {
|
||||||
this._localParticipant.next(Object.assign(Object.create(this.localParticipant), this.localParticipant));
|
this._localParticipant.next(
|
||||||
|
Object.assign(Object.create(Object.getPrototypeOf(this.localParticipant)), { ...this.localParticipant })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private publishAudioAux(publisher: Publisher, value: boolean): void {
|
||||||
|
if (!!publisher) {
|
||||||
|
publisher.publishAudio(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
private async publishVideoAux(publisher: Publisher, publish: boolean): Promise<void> {
|
||||||
|
if (!!publisher) {
|
||||||
|
let resource: boolean | MediaStreamTrack = true;
|
||||||
|
if (publish) {
|
||||||
|
// Forcing restoration with a custom media stream (the older one instead the default)
|
||||||
|
const currentDeviceId = this.deviceService.getCameraSelected()?.device;
|
||||||
|
const mediaStream = await this.openviduService.createMediaStream({ videoSource: currentDeviceId, audioSource: false });
|
||||||
|
resource = mediaStream.getVideoTracks()[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
await publisher.publishVideo(publish, resource);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue