Merge branch 'ov-components-refactor'
This commit is contained in:
commit
12a34ca7cb
@ -71,7 +71,7 @@ jobs:
|
||||
name: openvidu-browser
|
||||
path: openvidu-components-angular
|
||||
- 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
|
||||
run: |
|
||||
docker run -p 4443:4443 --rm -d \
|
||||
@ -106,7 +106,7 @@ jobs:
|
||||
name: openvidu-browser
|
||||
path: openvidu-components-angular
|
||||
- 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
|
||||
run: |
|
||||
docker run -p 4443:4443 --rm -d \
|
||||
@ -140,7 +140,7 @@ jobs:
|
||||
name: openvidu-browser
|
||||
path: openvidu-components-angular
|
||||
- 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
|
||||
run: |
|
||||
cd openvidu-components-angular
|
||||
|
||||
@ -2,7 +2,7 @@ import { expect } from 'chai';
|
||||
import { By, until, WebDriver, WebElement } from 'selenium-webdriver';
|
||||
|
||||
export class OpenViduComponentsPO {
|
||||
private TIMEOUT = 30 * 1000;
|
||||
private TIMEOUT = 10 * 1000;
|
||||
private POLL_TIMEOUT = 1 * 1000;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import monkeyPatchMediaDevices from './utils/media-devices.js';
|
||||
|
||||
var MINIMAL;
|
||||
var LANG;
|
||||
var CAPTIONS_LANG;
|
||||
@ -29,6 +31,7 @@ var CAPTIONS_BUTTON;
|
||||
|
||||
var SINGLE_TOKEN;
|
||||
var SESSION_NAME;
|
||||
var FAKE_DEVICES;
|
||||
|
||||
var PARTICIPANT_NAME;
|
||||
|
||||
@ -43,6 +46,8 @@ $(document).ready(() => {
|
||||
|
||||
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
|
||||
MINIMAL = url.searchParams.get('minimal') === null ? false : url.searchParams.get('minimal') === 'true';
|
||||
LANG = url.searchParams.get('lang') || 'en';
|
||||
@ -197,6 +202,11 @@ function appendElement(id) {
|
||||
async function joinSession(sessionName, participantName) {
|
||||
var webComponent = document.querySelector('openvidu-webcomponent');
|
||||
var tokens;
|
||||
|
||||
if (FAKE_DEVICES) {
|
||||
monkeyPatchMediaDevices();
|
||||
}
|
||||
|
||||
if (SINGLE_TOKEN) {
|
||||
tokens = await getToken(sessionName);
|
||||
} else {
|
||||
|
||||
@ -8,7 +8,10 @@
|
||||
crossorigin="anonymous"
|
||||
></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>
|
||||
<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;
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
15730
openvidu-components-angular/package-lock.json
generated
15730
openvidu-components-angular/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -167,10 +167,10 @@ export class RecordingActivityComponent implements OnInit {
|
||||
*/
|
||||
|
||||
deleteRecording(id: string) {
|
||||
const succsessCallback = () => {
|
||||
const succsessCallback = async () => {
|
||||
this.onDeleteRecordingClicked.emit(id);
|
||||
// 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));
|
||||
}
|
||||
|
||||
@ -194,16 +194,18 @@ export class SessionComponent implements OnInit, OnDestroy {
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
async ngOnDestroy() {
|
||||
// Reconnecting session is received in Firefox
|
||||
// To avoid 'Connection lost' message uses session.off()
|
||||
this.session?.off('reconnecting');
|
||||
this.participantService.clear();
|
||||
this.session = null;
|
||||
this.sessionScreen = null;
|
||||
if (this.menuSubscription) this.menuSubscription.unsubscribe();
|
||||
if (this.layoutWidthSubscription) this.layoutWidthSubscription.unsubscribe();
|
||||
if (this.captionLanguageSubscription) this.captionLanguageSubscription.unsubscribe();
|
||||
if (!this.usedInPrejoinPage) {
|
||||
this.session?.off('reconnecting');
|
||||
await this.participantService.clear();
|
||||
this.session = null;
|
||||
this.sessionScreen = null;
|
||||
if (this.menuSubscription) this.menuSubscription.unsubscribe();
|
||||
if (this.layoutWidthSubscription) this.layoutWidthSubscription.unsubscribe();
|
||||
if (this.captionLanguageSubscription) this.captionLanguageSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
leaveSession() {
|
||||
@ -251,20 +253,29 @@ export class SessionComponent implements OnInit, OnDestroy {
|
||||
|
||||
private async connectToSession(): Promise<void> {
|
||||
try {
|
||||
const webcamToken = this.openviduService.getWebcamToken();
|
||||
const screenToken = this.openviduService.getScreenToken();
|
||||
const participant = this.participantService.getLocalParticipant();
|
||||
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);
|
||||
await this.openviduService.connectSession(this.openviduService.getScreenSession(), screenToken);
|
||||
await this.openviduService.publish(this.participantService.getMyCameraPublisher());
|
||||
await this.openviduService.publish(this.participantService.getMyScreenPublisher());
|
||||
} else if (this.participantService.isOnlyMyScreenActive()) {
|
||||
await this.openviduService.connectSession(this.openviduService.getScreenSession(), screenToken);
|
||||
await this.openviduService.publish(this.participantService.getMyScreenPublisher());
|
||||
|
||||
if (participant.hasCameraAndScreenActives()) {
|
||||
|
||||
const webcamSessionId = await this.openviduService.connectWebcamSession(participantId, nickname);
|
||||
if (webcamSessionId) this.participantService.setMyCameraConnectionId(webcamSessionId);
|
||||
|
||||
const screenSessionId = await this.openviduService.connectScreenSession(participantId, nickname);
|
||||
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 {
|
||||
await this.openviduService.connectSession(this.openviduService.getWebcamSession(), webcamToken);
|
||||
await this.openviduService.publish(this.participantService.getMyCameraPublisher());
|
||||
await this.openviduService.connectWebcamSession(participantId, nickname);
|
||||
await this.openviduService.publishCamera(cameraPublisher);
|
||||
}
|
||||
} catch (error) {
|
||||
// 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() {
|
||||
this.session.on('connectionCreated', (event: ConnectionEvent) => {
|
||||
this.session.on('connectionCreated', async (event: ConnectionEvent) => {
|
||||
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 isCameraConnection: boolean = !nickname?.includes(`_${VideoType.SCREEN}`);
|
||||
const isCameraConnection: boolean = !connectionNickname?.includes(`_${VideoType.SCREEN}`);
|
||||
const nickname = this.participantService.getMyNickname();
|
||||
const data = event.connection?.data;
|
||||
|
||||
if (isRemoteConnection && isCameraConnection) {
|
||||
// Adding participant when connection is created and it's not screen
|
||||
this.participantService.addRemoteConnection(connectionId, data, null);
|
||||
|
||||
//Sending nicnkanme signal to new participants
|
||||
if (this.openviduService.needSendNicknameSignal()) {
|
||||
const data = { clientData: this.participantService.getMyNickname() };
|
||||
this.openviduService.sendSignal(Signal.NICKNAME_CHANGED, [event.connection], data);
|
||||
//Sending nicnkanme signal to new connection
|
||||
if (this.openviduService.myNicknameHasBeenChanged()) {
|
||||
await this.openviduService.sendSignal(Signal.NICKNAME_CHANGED, [event.connection], { clientData: nickname });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -3,7 +3,6 @@ import { PublisherProperties } from 'openvidu-browser';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { CustomDevice } from '../../../models/device.model';
|
||||
import { ParticipantAbstractModel } from '../../../models/participant.model';
|
||||
import { VideoType } from '../../../models/video-type.model';
|
||||
import { DeviceService } from '../../../services/device/device.service';
|
||||
import { OpenViduService } from '../../../services/openvidu/openvidu.service';
|
||||
import { ParticipantService } from '../../../services/participant/participant.service';
|
||||
@ -58,7 +57,7 @@ export class AudioDevicesComponent implements OnInit, OnDestroy {
|
||||
|
||||
toggleMic() {
|
||||
const publish = this.isAudioMuted;
|
||||
this.openviduService.publishAudio(publish);
|
||||
this.participantService.publishAudio(publish);
|
||||
this.onAudioMutedClicked.emit(publish);
|
||||
}
|
||||
|
||||
@ -66,7 +65,8 @@ export class AudioDevicesComponent implements OnInit, OnDestroy {
|
||||
const audioSource = event?.value;
|
||||
if (this.deviceSrv.needUpdateAudioTrack(audioSource)) {
|
||||
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.microphoneSelected = this.deviceSrv.getMicrophoneSelected();
|
||||
}
|
||||
|
||||
@ -23,16 +23,17 @@
|
||||
videocam_off
|
||||
</mat-icon>
|
||||
</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.NO_VIDEO_DEVICE' | translate }}</mat-label>
|
||||
<mat-select
|
||||
[disabled]="isVideoMuted || !hasVideoDevices"
|
||||
[value]="cameraSelected?.device"
|
||||
[compareWith]="compareObjectDevices"
|
||||
[value]="cameraSelected"
|
||||
(click)="onDeviceSelectorClicked.emit()"
|
||||
(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 }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
|
||||
@ -4,7 +4,6 @@ import { Subscription } from 'rxjs';
|
||||
import { CustomDevice } from '../../../models/device.model';
|
||||
import { PanelType } from '../../../models/panel.model';
|
||||
import { ParticipantAbstractModel } from '../../../models/participant.model';
|
||||
import { VideoType } from '../../../models/video-type.model';
|
||||
import { DeviceService } from '../../../services/device/device.service';
|
||||
import { OpenViduService } from '../../../services/openvidu/openvidu.service';
|
||||
import { PanelService } from '../../../services/panel/panel.service';
|
||||
@ -21,8 +20,8 @@ import { VirtualBackgroundService } from '../../../services/virtual-background/v
|
||||
styleUrls: ['./video-devices.component.css']
|
||||
})
|
||||
export class VideoDevicesComponent implements OnInit, OnDestroy {
|
||||
@Output() onDeviceSelectorClicked = new EventEmitter<void>();
|
||||
@Output() onVideoMutedClicked = new EventEmitter<boolean>();
|
||||
@Output() onDeviceSelectorClicked = new EventEmitter<void>();
|
||||
@Output() onVideoMutedClicked = new EventEmitter<boolean>();
|
||||
|
||||
videoMuteChanging: boolean;
|
||||
isVideoMuted: boolean;
|
||||
@ -47,9 +46,8 @@ export class VideoDevicesComponent implements OnInit, OnDestroy {
|
||||
await this.deviceSrv.refreshDevices();
|
||||
}
|
||||
|
||||
|
||||
this.hasVideoDevices = this.deviceSrv.hasVideoDeviceAvailable();
|
||||
if(this.hasVideoDevices){
|
||||
if (this.hasVideoDevices) {
|
||||
this.cameras = this.deviceSrv.getCameras();
|
||||
this.cameraSelected = this.deviceSrv.getCameraSelected();
|
||||
}
|
||||
@ -67,7 +65,7 @@ export class VideoDevicesComponent implements OnInit, OnDestroy {
|
||||
async toggleCam() {
|
||||
this.videoMuteChanging = true;
|
||||
const publish = this.isVideoMuted;
|
||||
await this.openviduService.publishVideo(publish);
|
||||
await this.participantService.publishVideo(publish);
|
||||
if (this.isVideoMuted && this.panelService.isExternalPanelOpened()) {
|
||||
this.panelService.togglePanel(PanelType.BACKGROUND_EFFECTS);
|
||||
}
|
||||
@ -76,19 +74,21 @@ export class VideoDevicesComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
async onCameraSelected(event: any) {
|
||||
const videoSource = event?.value;
|
||||
const device: CustomDevice = event?.value;
|
||||
|
||||
// Is New deviceId different from the old one?
|
||||
if (this.deviceSrv.needUpdateVideoTrack(videoSource)) {
|
||||
const mirror = this.deviceSrv.cameraNeedsMirror(videoSource);
|
||||
if (this.deviceSrv.needUpdateVideoTrack(device)) {
|
||||
const mirror = this.deviceSrv.cameraNeedsMirror(device.device);
|
||||
// Reapply Virtual Background to new Publisher if necessary
|
||||
const backgroundSelected = this.backgroundService.backgroundSelected.getValue();
|
||||
const isBackgroundApplied = this.backgroundService.isBackgroundApplied()
|
||||
const isBackgroundApplied = this.backgroundService.isBackgroundApplied();
|
||||
|
||||
if (isBackgroundApplied) {
|
||||
await this.backgroundService.removeBackground();
|
||||
}
|
||||
const pp: PublisherProperties = { videoSource, audioSource: false, mirror };
|
||||
await this.openviduService.replaceTrack(VideoType.CAMERA, pp);
|
||||
const pp: PublisherProperties = { videoSource: device.device, audioSource: false, mirror };
|
||||
const publisher = this.participantService.getMyCameraPublisher();
|
||||
await this.openviduService.replaceCameraTrack(publisher, pp);
|
||||
|
||||
if (isBackgroundApplied) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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() {
|
||||
this.localParticipantSubscription = this.participantService.localParticipantObs.subscribe((p: ParticipantAbstractModel) => {
|
||||
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 */
|
||||
.no-size {
|
||||
/* Fixes layout bug. The OT_root is created with the entire layout width and it has a weird UX behaviour */
|
||||
.no-size {
|
||||
height: 0px !important;
|
||||
width: 0px !important;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.nickname {
|
||||
padding: 0px;
|
||||
@ -11,46 +10,44 @@
|
||||
z-index: 999;
|
||||
border-radius: var(--ov-video-radius);
|
||||
color: var(--ov-text-color);
|
||||
font-family: 'Roboto','RobotoDraft',Helvetica,Arial,sans-serif;
|
||||
}
|
||||
.nicknameContainer {
|
||||
font-family: 'Roboto', 'RobotoDraft', Helvetica, Arial, sans-serif;
|
||||
}
|
||||
.nicknameContainer {
|
||||
background-color: var(--ov-secondary-color);
|
||||
padding: 5px;
|
||||
color: var(--ov-text-color);
|
||||
font-weight: bold;
|
||||
border-radius: var(--ov-video-radius);
|
||||
}
|
||||
}
|
||||
|
||||
#nickname-input-container {
|
||||
background-color: var(--ov-secondary-color);
|
||||
#nickname-input-container {
|
||||
background-color: var(--ov-secondary-color);
|
||||
border-radius: var(--ov-video-radius);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#closeButton {
|
||||
#closeButton {
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
right: 0;
|
||||
z-index: 999;
|
||||
}
|
||||
}
|
||||
|
||||
#nicknameForm {
|
||||
#nicknameForm {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
#audio-wave-container {
|
||||
#audio-wave-container {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
z-index: 999;
|
||||
padding: 5px;
|
||||
}
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.fullscreen {
|
||||
.fullscreen {
|
||||
top: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
video {
|
||||
video {
|
||||
-o-object-fit: cover;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
@ -60,57 +57,61 @@
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.status-icons, #settings-container {
|
||||
.status-icons,
|
||||
#settings-container {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
z-index: 9999;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.status-icons {
|
||||
.status-icons {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.status-icons button, #settings-container button {
|
||||
.status-icons button,
|
||||
#settings-container button {
|
||||
color: var(--ov-text-color);
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
margin: 5px;
|
||||
border-radius: var(--ov-buttons-radius);
|
||||
}
|
||||
}
|
||||
|
||||
.status-icons button {
|
||||
.status-icons button {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
.status-icons mat-icon, #settings-container mat-icon {
|
||||
.status-icons mat-icon,
|
||||
#settings-container mat-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
#settings-container{
|
||||
#settings-container {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#settings-container button {
|
||||
#settings-container button {
|
||||
background-color: var(--ov-secondary-color);
|
||||
}
|
||||
}
|
||||
|
||||
/* Contains the video element, used to fix video letter-boxing */
|
||||
.OV_stream {
|
||||
/* Contains the video element, used to fix video letter-boxing */
|
||||
.OV_stream {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-color: transparent;
|
||||
border-radius: var(--ov-video-radius);
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
input {
|
||||
caret-color: #ffffff !important;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<div
|
||||
*ngIf="this._stream"
|
||||
class="OV_stream"
|
||||
[ngClass]="{'no-size': !showVideo}"
|
||||
[ngClass]="{ 'no-size': !showVideo }"
|
||||
[id]="'container-' + this._stream.streamManager?.stream?.streamId"
|
||||
#streamContainer
|
||||
>
|
||||
@ -50,11 +50,18 @@
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</button>
|
||||
<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()">
|
||||
<mat-icon>{{ this.videoSizeIcon }}</mat-icon>
|
||||
<span *ngIf="videoSizeIcon === videoSizeIconEnum.NORMAL">{{ 'STREAM.ZOOM_OUT' | translate }}</span>
|
||||
@ -70,7 +77,7 @@
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="replaceScreenTrack()"
|
||||
id="changeScreenButton"
|
||||
id="replace-screen-button"
|
||||
*ngIf="!this._stream.streamManager?.remote && this._stream.streamManager?.stream?.typeOfVideo === videoTypeEnum.SCREEN"
|
||||
>
|
||||
<mat-icon>picture_in_picture</mat-icon>
|
||||
|
||||
@ -231,12 +231,12 @@ export class StreamComponent implements OnInit {
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
updateNickname(event) {
|
||||
async updateNickname(event) {
|
||||
if (event?.keyCode === 13 || event?.type === 'focusout') {
|
||||
if (!!this.nickname) {
|
||||
this.participantService.setMyNickname(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();
|
||||
}
|
||||
@ -252,7 +252,8 @@ export class StreamComponent implements OnInit {
|
||||
publishAudio: !this.participantService.isMyCameraActive(),
|
||||
mirror: false
|
||||
};
|
||||
await this.openviduService.replaceTrack(VideoType.SCREEN, properties);
|
||||
const publisher = this.participantService.getMyScreenPublisher();
|
||||
await this.openviduService.replaceScreenTrack(publisher, properties);
|
||||
}
|
||||
|
||||
private checkVideoEnlarged() {
|
||||
|
||||
@ -500,7 +500,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
async toggleMicrophone() {
|
||||
this.onMicrophoneButtonClicked.emit();
|
||||
try {
|
||||
await this.openviduService.publishAudio(!this.isAudioActive);
|
||||
this.participantService.publishAudio(!this.isAudioActive);
|
||||
} catch (error) {
|
||||
this.log.e('There was an error toggling microphone:', error.code, error.message);
|
||||
this.actionService.openDialog(
|
||||
@ -521,7 +521,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
if (this.panelService.isExternalPanelOpened() && !publishVideo) {
|
||||
this.panelService.togglePanel(PanelType.BACKGROUND_EFFECTS);
|
||||
}
|
||||
await this.openviduService.publishVideo(publishVideo);
|
||||
await this.participantService.publishVideo(publishVideo);
|
||||
} catch (error) {
|
||||
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);
|
||||
|
||||
@ -601,7 +601,14 @@ export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewIni
|
||||
await this.handlePublisherError(e);
|
||||
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) {
|
||||
this.actionService.openDialog(error.name.replace(/_/g, ' '), error.message, true);
|
||||
|
||||
@ -25,7 +25,7 @@ import { Directive, TemplateRef, ViewContainerRef } from '@angular/core';
|
||||
* publishVideo = true;
|
||||
* publishAudio = true;
|
||||
*
|
||||
* constructor(private httpClient: HttpClient, private openviduService: OpenViduService) { }
|
||||
* constructor(private httpClient: HttpClient, private participantService: ParticipantService) { }
|
||||
*
|
||||
* async ngOnInit() {
|
||||
* this.tokens = {
|
||||
@ -36,18 +36,18 @@ import { Directive, TemplateRef, ViewContainerRef } from '@angular/core';
|
||||
*
|
||||
* toggleVideo() {
|
||||
* this.publishVideo = !this.publishVideo;
|
||||
* this.openviduService.publishVideo(this.publishVideo);
|
||||
* this.participantService.publishVideo(this.publishVideo);
|
||||
* }
|
||||
*
|
||||
* toggleAudio() {
|
||||
* this.publishAudio = !this.publishAudio;
|
||||
* this.openviduService.publishAudio(this.publishAudio);
|
||||
* this.participantService.publishAudio(this.publishAudio);
|
||||
* }
|
||||
*
|
||||
* async getToken(): Promise<string> {
|
||||
* // Returns an OpeVidu token
|
||||
* }
|
||||
*
|
||||
*
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
@ -88,13 +88,12 @@ export class ToolbarDirective {
|
||||
sessionId = "panel-directive-example";
|
||||
tokens!: TokenModel;
|
||||
|
||||
|
||||
|
||||
* sessionId = 'toolbar-additionalbtn-directive-example';
|
||||
* tokens!: TokenModel;
|
||||
*
|
||||
* constructor(
|
||||
* private httpClient: HttpClient,
|
||||
* private openviduService: OpenViduService,
|
||||
* private participantService: ParticipantService
|
||||
* ) { }
|
||||
*
|
||||
@ -107,18 +106,18 @@ export class ToolbarDirective {
|
||||
*
|
||||
* toggleVideo() {
|
||||
* const publishVideo = !this.participantService.isMyVideoActive();
|
||||
* this.openviduService.publishVideo(publishVideo);
|
||||
* this.participantService.publishVideo(publishVideo);
|
||||
* }
|
||||
*
|
||||
* toggleAudio() {
|
||||
* const publishAudio = !this.participantService.isMyAudioActive();
|
||||
* this.openviduService.publishAudio(publishAudio);
|
||||
* this.participantService.publishAudio(publishAudio);
|
||||
* }
|
||||
*
|
||||
* async getToken(): Promise<string> {
|
||||
* // Returns an OpeVidu token
|
||||
* }
|
||||
*
|
||||
*
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@ -149,7 +148,7 @@ export class ToolbarAdditionalButtonsDirective {
|
||||
*
|
||||
* ```javascript
|
||||
* export class ToolbarAdditionalPanelButtonsDirectiveComponent {
|
||||
*
|
||||
*
|
||||
* sessionId = "toolbar-additionalPanelbtn";
|
||||
* tokens!: TokenModel;
|
||||
*
|
||||
@ -169,7 +168,7 @@ export class ToolbarAdditionalButtonsDirective {
|
||||
* async getToken(): Promise<string> {
|
||||
* // Returns an OpeVidu token
|
||||
* }
|
||||
*
|
||||
*
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@ -206,7 +205,7 @@ export class ToolbarAdditionalPanelButtonsDirective {
|
||||
*
|
||||
* ```javascript
|
||||
* export class PanelDirectiveComponent {
|
||||
*
|
||||
*
|
||||
* sessionId = "panel-directive-example";
|
||||
* tokens!: TokenModel;
|
||||
*
|
||||
@ -222,7 +221,7 @@ export class ToolbarAdditionalPanelButtonsDirective {
|
||||
* async getToken(): Promise<string> {
|
||||
* // Returns an OpeVidu token
|
||||
* }
|
||||
*
|
||||
*
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@ -275,7 +274,7 @@ export class PanelDirective {
|
||||
*
|
||||
* ```javascript
|
||||
* export class AdditionalPanelsDirectiveComponent implements OnInit {
|
||||
*
|
||||
*
|
||||
* sessionId = "toolbar-additionalbtn-directive-example";
|
||||
* tokens!: TokenModel;
|
||||
*
|
||||
@ -445,7 +444,7 @@ export class BackgroundEffectsPanelDirective {
|
||||
*
|
||||
* ```javascript
|
||||
* export class AppComponent implements OnInit {
|
||||
*
|
||||
*
|
||||
* sessionId = "activities-panel-directive-example";
|
||||
* tokens!: TokenModel;
|
||||
*
|
||||
@ -585,7 +584,7 @@ export class ParticipantsPanelDirective {
|
||||
* async getToken(): Promise<string> {
|
||||
* // Returns an OpeVidu token
|
||||
* }
|
||||
*
|
||||
*
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
@ -684,10 +683,10 @@ export class ParticipantPanelItemElementsDirective {
|
||||
*
|
||||
* We need to get the participants in our Session, so we use the {@link ParticipantService} to subscribe to the required Observables.
|
||||
* We'll get the local participant and the remote participants to display their streams in our custom session layout.
|
||||
*
|
||||
*
|
||||
* ```javascript
|
||||
* export class LayoutDirectiveComponent implements OnInit, OnDestroy {
|
||||
*
|
||||
*
|
||||
* sessionId = 'layout-directive-example';
|
||||
* tokens!: TokenModel;
|
||||
*
|
||||
@ -723,7 +722,7 @@ export class ParticipantPanelItemElementsDirective {
|
||||
* async getToken(): Promise<string> {
|
||||
* // Returns an OpeVidu token
|
||||
* }
|
||||
*
|
||||
*
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
|
||||
@ -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}
|
||||
*/
|
||||
streamManager: StreamManager;
|
||||
streamManager: StreamManager | undefined;
|
||||
/**
|
||||
* Whether the stream is enlarged or not
|
||||
*/
|
||||
@ -29,7 +29,7 @@ export interface StreamModel {
|
||||
/**
|
||||
* Unique identifier of the stream
|
||||
*/
|
||||
connectionId: string;
|
||||
connectionId: string | undefined;
|
||||
/**
|
||||
* The participant object
|
||||
*/
|
||||
@ -68,7 +68,7 @@ export abstract class ParticipantAbstractModel {
|
||||
isMutedForcibly: boolean;
|
||||
|
||||
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.nickname = props.nickname;
|
||||
this.colorProfile = !!props.colorProfile ? props.colorProfile : `hsl(${Math.random() * 360}, 100%, 80%)`;
|
||||
@ -76,9 +76,9 @@ export abstract class ParticipantAbstractModel {
|
||||
let streamModel: StreamModel = {
|
||||
connected: model ? model.connected : true,
|
||||
type: model ? model.type : VideoType.CAMERA,
|
||||
streamManager: model ? model.streamManager : null,
|
||||
streamManager: model?.streamManager,
|
||||
videoEnlarged: model ? model.videoEnlarged : false,
|
||||
connectionId: model ? model.connectionId : null,
|
||||
connectionId: model?.connectionId,
|
||||
participant: this
|
||||
};
|
||||
this.streams.set(streamModel.type, streamModel);
|
||||
@ -113,7 +113,7 @@ export abstract class ParticipantAbstractModel {
|
||||
private isCameraAudioActive(): boolean {
|
||||
const cameraConnection = this.getCameraConnection();
|
||||
if (cameraConnection?.connected) {
|
||||
return cameraConnection.streamManager?.stream?.audioActive;
|
||||
return cameraConnection.streamManager?.stream?.audioActive || false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -132,7 +132,7 @@ export abstract class ParticipantAbstractModel {
|
||||
isScreenAudioActive(): boolean {
|
||||
const screenConnection = this.getScreenConnection();
|
||||
if (screenConnection?.connected) {
|
||||
return screenConnection?.streamManager?.stream?.audioActive;
|
||||
return screenConnection?.streamManager?.stream?.audioActive || false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -160,13 +160,14 @@ export abstract class ParticipantAbstractModel {
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @returns The participant active connection types
|
||||
*/
|
||||
getConnectionTypesActive(): VideoType[] {
|
||||
let connType = [];
|
||||
if (this.isCameraActive()) connType.push(VideoType.CAMERA);
|
||||
if (this.isScreenActive()) connType.push(VideoType.SCREEN);
|
||||
getActiveConnectionTypes(): VideoType[] {
|
||||
const activeTypes: VideoType[] = [];
|
||||
if (this.isCameraActive()) activeTypes.push(VideoType.CAMERA);
|
||||
if (this.isScreenActive()) activeTypes.push(VideoType.SCREEN);
|
||||
|
||||
return connType;
|
||||
return activeTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -218,7 +219,6 @@ export abstract class ParticipantAbstractModel {
|
||||
*/
|
||||
isLocal(): boolean {
|
||||
return this.local;
|
||||
// return Array.from(this.streams.values()).every((conn) => conn.local);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -238,7 +238,7 @@ export abstract class ParticipantAbstractModel {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
setCameraPublisher(publisher: Publisher) {
|
||||
setCameraPublisher(publisher: Publisher | undefined) {
|
||||
const cameraConnection = this.getCameraConnection();
|
||||
if (cameraConnection) cameraConnection.streamManager = publisher;
|
||||
}
|
||||
@ -307,6 +307,30 @@ export abstract class ParticipantAbstractModel {
|
||||
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
|
||||
*/
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
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';
|
||||
|
||||
@Pipe({ name: 'streams' })
|
||||
@ -10,11 +10,8 @@ export class ParticipantStreamsPipe implements PipeTransform {
|
||||
let streams: StreamModel[] = [];
|
||||
if(participants && Object.keys(participants).length > 0){
|
||||
if (Array.isArray(participants)) {
|
||||
participants.forEach((p) => {
|
||||
streams = streams.concat(p.getAvailableConnections());
|
||||
});
|
||||
streams = participants.map(p => p.getAvailableConnections()).flat();
|
||||
} else {
|
||||
|
||||
streams = participants.getAvailableConnections();
|
||||
}
|
||||
}
|
||||
@ -30,15 +27,11 @@ export class StreamTypesEnabledPipe implements PipeTransform {
|
||||
constructor(private translateService: TranslateService) {}
|
||||
|
||||
transform(participant: ParticipantAbstractModel): string {
|
||||
let result = '';
|
||||
let activeStreams = participant?.getConnectionTypesActive().toString();
|
||||
const activeStreamsArr: string[] = activeStreams.split(',');
|
||||
activeStreamsArr.forEach((type, index) => {
|
||||
result += this.translateService.translate(`PANEL.PARTICIPANTS.${type}`)
|
||||
if(activeStreamsArr.length > 0 && index < activeStreamsArr.length - 1){
|
||||
result += ', ';
|
||||
}
|
||||
});
|
||||
return `(${result})`;
|
||||
|
||||
const activeStreams = participant?.getActiveConnectionTypes() ?? [];
|
||||
const streamNames = activeStreams.map(streamType => this.translateService.translate(`PANEL.PARTICIPANTS.${streamType}`));
|
||||
const streamsString = streamNames.join(', ');
|
||||
|
||||
return `(${streamsString})`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ export class ChatService {
|
||||
});
|
||||
}
|
||||
|
||||
sendMessage(message: string) {
|
||||
async sendMessage(message: string) {
|
||||
message = message.replace(/ +(?= )/g, '');
|
||||
if (message !== '' && message !== ' ') {
|
||||
const data = {
|
||||
@ -72,7 +72,7 @@ export class ChatService {
|
||||
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;
|
||||
}
|
||||
|
||||
setCameraSelected(deviceField: any) {
|
||||
this.cameraSelected = this.getCameraByDeviceField(deviceField);
|
||||
setCameraSelected(deviceId: any) {
|
||||
this.cameraSelected = this.getCameraByDeviceField(deviceId);
|
||||
this.saveCameraToStorage(this.cameraSelected);
|
||||
}
|
||||
|
||||
@ -187,8 +187,8 @@ export class DeviceService {
|
||||
this.saveMicrophoneToStorage(this.microphoneSelected);
|
||||
}
|
||||
|
||||
needUpdateVideoTrack(newVideoSource: string): boolean {
|
||||
return this.cameraSelected?.device !== newVideoSource;
|
||||
needUpdateVideoTrack(newDevice: CustomDevice): boolean {
|
||||
return this.cameraSelected?.device !== newDevice.device || this.cameraSelected?.label !== newDevice.label;
|
||||
}
|
||||
|
||||
needUpdateAudioTrack(newAudioSource: string): boolean {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -16,7 +16,7 @@ export class PanelService {
|
||||
private isExternalOpened: boolean = false;
|
||||
private externalType: string;
|
||||
protected _panelOpened = <BehaviorSubject<PanelEvent>>new BehaviorSubject({ opened: false });
|
||||
private panelMap: Map<string, boolean> = new Map();
|
||||
private panelTypes: string[] = Object.values(PanelType);
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -24,7 +24,6 @@ export class PanelService {
|
||||
constructor(protected loggerSrv: LoggerService) {
|
||||
this.log = this.loggerSrv.get('PanelService');
|
||||
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) {
|
||||
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.panelMap.forEach((opened: boolean, panel: string) => {
|
||||
if (panel === type) {
|
||||
// Toggle panel
|
||||
this.panelMap.set(panel, !opened);
|
||||
nextOpenedValue = !opened;
|
||||
} else {
|
||||
// Close others
|
||||
this.panelMap.set(panel, false);
|
||||
}
|
||||
});
|
||||
nextOpenedValue = oldType !== type ? true : !oldOpened;
|
||||
} else {
|
||||
// Panel is external
|
||||
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
|
||||
this.isExternalOpened = !this.isExternalOpened || this.externalType !== type;
|
||||
this.externalType = !this.isExternalOpened ? '' : type;
|
||||
nextOpenedValue = this.isExternalOpened;
|
||||
}
|
||||
|
||||
const oldType = this._panelOpened.getValue().type;
|
||||
this._panelOpened.next({ opened: nextOpenedValue, type, expand, oldType });
|
||||
}
|
||||
|
||||
@ -65,51 +55,54 @@ export class PanelService {
|
||||
* @internal
|
||||
*/
|
||||
isPanelOpened(): boolean {
|
||||
const anyOpened = Array.from(this.panelMap.values()).some((opened) => opened);
|
||||
return anyOpened || this.isExternalPanelOpened();
|
||||
return this._panelOpened.getValue().opened;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the panel (if opened)
|
||||
*/
|
||||
closePanel(): void {
|
||||
this.panelMap.forEach((_, panel: string) => this.panelMap.set(panel, false));
|
||||
this._panelOpened.next({ opened: false });
|
||||
this._panelOpened.next({ opened: false, type: undefined, expand: undefined, oldType: undefined });
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the chat panel is opened or not.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
isBackgroundEffectsPanelOpened(): boolean {
|
||||
return !!this.panelMap.get(PanelType.BACKGROUND_EFFECTS);
|
||||
const panelState = this._panelOpened.getValue();
|
||||
return panelState.opened && panelState.type === PanelType.BACKGROUND_EFFECTS;
|
||||
}
|
||||
|
||||
isExternalPanelOpened(): boolean {
|
||||
|
||||
@ -2,10 +2,18 @@ import { Injectable } from '@angular/core';
|
||||
import { Publisher, Subscriber } from 'openvidu-browser';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
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 { OpenViduAngularConfigService } from '../config/openvidu-angular.config.service';
|
||||
import { DeviceService } from '../device/device.service';
|
||||
import { LoggerService } from '../logger/logger.service';
|
||||
import { OpenViduService } from '../openvidu/openvidu.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@ -33,9 +41,13 @@ export class ParticipantService {
|
||||
/**
|
||||
* @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.localParticipantObs = this._localParticipant.asObservable();
|
||||
this.remoteParticipantsObs = this._remoteParticipants.asObservable();
|
||||
}
|
||||
@ -52,6 +64,129 @@ export class ParticipantService {
|
||||
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
|
||||
*/
|
||||
@ -62,7 +197,7 @@ export class ParticipantService {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
setMyCameraPublisher(publisher: Publisher) {
|
||||
setMyCameraPublisher(publisher: Publisher | undefined) {
|
||||
this.localParticipant.setCameraPublisher(publisher);
|
||||
}
|
||||
/**
|
||||
@ -98,7 +233,6 @@ export class ParticipantService {
|
||||
*/
|
||||
enableWebcamStream() {
|
||||
this.localParticipant.enableCamera();
|
||||
this.updateLocalParticipant();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -106,7 +240,6 @@ export class ParticipantService {
|
||||
*/
|
||||
disableWebcamStream() {
|
||||
this.localParticipant.disableCamera();
|
||||
this.updateLocalParticipant();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -134,7 +267,6 @@ export class ParticipantService {
|
||||
*/
|
||||
disableScreenStream() {
|
||||
this.localParticipant.disableScreen();
|
||||
this.updateLocalParticipant();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -180,7 +312,9 @@ export class ParticipantService {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
clear() {
|
||||
async clear() {
|
||||
await this.getMyCameraPublisher()?.stream?.disposeMediaStream();
|
||||
await this.getMyScreenPublisher()?.stream?.disposeMediaStream();
|
||||
this.disableScreenStream();
|
||||
this.remoteParticipants = [];
|
||||
this.updateRemoteParticipants();
|
||||
@ -202,34 +336,6 @@ export class ParticipantService {
|
||||
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
|
||||
*/
|
||||
@ -241,7 +347,32 @@ export class ParticipantService {
|
||||
* Force to update the local participant object and fire a new {@link localParticipantObs} Observable event.
|
||||
*/
|
||||
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…
x
Reference in New Issue
Block a user