mediasfu-angular
Version:
MediaSFU Prebuilt Angular SDK
1,069 lines (1,058 loc) • 3.07 MB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, Component, Optional, Inject, Input, ViewChild, HostListener, Injector, EventEmitter, Output, ContentChildren, ViewEncapsulation } from '@angular/core';
import * as i1 from '@angular/common';
import { CommonModule } from '@angular/common';
import * as i2 from '@fortawesome/angular-fontawesome';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { faMicrophone, faMicrophoneSlash, faVideo, faVideoSlash, faTimes, faPen, faUsers, faPlus, faDoorOpen, faRandom, faHandPointer, faSave, faPlay, faSyncAlt, faStop, faStar, faLock, faCamera, faPhotoFilm, faSpinner, faCopy, faEnvelope, faBars, faPaperPlane, faReply, faQrcode, faComment, faTrash, faDotCircle, faCircle, faCheck, faDesktop, faComments, faChevronRight, faChevronLeft, faPencilAlt, faPaintBrush, faShapes, faEraser, faSearch, faSearchPlus, faSearchMinus, faFont, faTextHeight, faUndo, faRedo, faMousePointer, faHandPaper, faUpload, faBan, faRecordVinyl, faPlayCircle, faPauseCircle, faStopCircle, faCog, faClock, faUserPlus, faTools, faPoll, faUserFriends, faChalkboardTeacher, faSync, faPhone, faShareAlt, faChartBar } from '@fortawesome/free-solid-svg-icons';
import io, { Socket } from 'socket.io-client';
import * as i2$1 from 'mediasoup-client/lib/types';
import * as mediasoupClient from 'mediasoup-client';
import * as i1$1 from 'ngx-cookie-service';
import { CookieService } from 'ngx-cookie-service';
import { SelfieSegmentation } from '@mediapipe/selfie_segmentation';
import * as i2$2 from '@angular/forms';
import { FormsModule, Validators, ReactiveFormsModule } from '@angular/forms';
import { faFacebook, faWhatsapp, faTelegram } from '@fortawesome/free-brands-svg-icons';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import * as i3 from '@zxing/ngx-scanner';
import { ZXingScannerModule } from '@zxing/ngx-scanner';
import { BehaviorSubject as BehaviorSubject$1, combineLatest } from 'rxjs';
/**
* Service to validate if a given string contains only alphanumeric characters.
*
* @class ValidateAlphanumeric
*
* @example
* ```typescript
* const validator = new ValidateAlphanumeric();
* validator.validateAlphanumeric({ str: 'abc123' }).then(isValid => console.log(isValid)); // true
* validator.validateAlphanumeric({ str: 'abc 123!' }).then(isValid => console.log(isValid)); // false
* ```
*
* @param {ValidateAlphanumericOptions} options - Contains the string to validate.
* @param {string} options.str - The input string that needs to be validated.
* @returns {Promise<boolean>} - A promise resolving to `true` if the input string is alphanumeric, otherwise `false`.
*/
class ValidateAlphanumeric {
/**
* Validates if the given string contains only alphanumeric characters.
*
* @param {ValidateAlphanumericOptions} options - The options containing the string to validate.
* @param {string} options.str - The string to be validated.
* @returns {Promise<boolean>} - A promise that resolves to `true` if the string is alphanumeric, otherwise `false`.
*/
async validateAlphanumeric({ str }) {
let code, i, len;
for (i = 0, len = str.length; i < len; i++) {
code = str.charCodeAt(i);
if (!(code > 47 && code < 58) && // numeric (0-9)
!(code > 64 && code < 91) && // upper alpha (A-Z)
!(code > 96 && code < 123)) {
// lower alpha (a-z)
return false;
}
}
return true;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: ValidateAlphanumeric, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: ValidateAlphanumeric, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: ValidateAlphanumeric, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}] });
/**
* Joins a conference room with the provided options and performs validation checks.
*
* @param {JoinConRoomOptions} options - Contains:
* - socket: Socket instance for communication.
* - roomName: Name of the room to join.
* - islevel: User level within the room.
* - member: Member identifier.
* - sec: Security token.
* - apiUserName: API username for authentication.
*
* - **Validation**:
* - Checks that `roomName`, `apiUserName`, and `member` are alphanumeric.
* - Ensures `roomName` starts with 's' or 'p' and meets length requirements.
* - Verifies `sec`, `islevel`, and `apiUserName` comply with length and format expectations.
*
* - **Response Handling**:
* - Resolves to the server's response data upon a successful join.
* - Rejects with specific reasons if the user is banned, suspended, or if the room host is not present.
*
* @returns {Promise<JoinConRoomResponse>} Resolves with the join response data, or rejects with error details.
* @throws {Error} Throws validation errors or issues encountered while joining the room.
*
* @example
* ```typescript
* const joinOptions = {
* socket: mySocket,
* roomName: 'sMyRoom',
* islevel: '1',
* member: 'participant123',
* sec: '64-character-long-secret-key-here...',
* apiUserName: 'apiUser123',
* };
* joinConRoom(joinOptions)
* .then(response => console.log('Joined room:', response))
* .catch(error => console.error('Failed to join room:', error));
* ```
*/
class JoinConRoom {
validateAlphanumeric;
constructor(validateAlphanumeric) {
this.validateAlphanumeric = validateAlphanumeric;
}
/**
* Joins a conference room using the provided options.
*
* @param {JoinConRoomOptions} options - The options for joining the conference room.
* @param {Socket} options.socket - The socket instance to use for communication.
* @param {string} options.roomName - The name of the room to join.
* @param {string} options.islevel - The level of the user.
* @param {string} options.member - The member identifier.
* @param {string} options.sec - The security token.
* @param {string} options.apiUserName - The API username.
* @returns {Promise<JoinConRoomResponse>} A promise that resolves with the response of the join operation.
*
* @throws {Error} If any of the required parameters are missing or invalid.
* @throws {Error} If the user is banned, suspended, or if the host has not joined the room yet.
*/
async joinConRoom({ socket, roomName, islevel, member, sec, apiUserName, }) {
return new Promise((resolve, reject) => {
// Validate inputs
if (!(sec && roomName && islevel && apiUserName && member)) {
const validationError = {
success: false,
rtpCapabilities: null,
reason: 'Missing required parameters',
};
reject(validationError);
return;
}
// Validate alphanumeric for roomName, apiUserName, and member
try {
this.validateAlphanumeric.validateAlphanumeric({ str: roomName });
this.validateAlphanumeric.validateAlphanumeric({ str: apiUserName });
this.validateAlphanumeric.validateAlphanumeric({ str: member });
}
catch (error) {
const validationError = {
success: false,
rtpCapabilities: null,
reason: 'Invalid roomName or apiUserName or member',
};
reject(validationError);
return;
}
// Validate roomName starts with 's' or 'p'
if (!(roomName.startsWith('s') || roomName.startsWith('p'))) {
const validationError = {
success: false,
rtpCapabilities: null,
reason: 'Invalid roomName, must start with s or p',
};
reject(validationError);
return;
}
// Validate other conditions for sec, roomName, islevel, apiUserName
if (!(sec.length === 64 &&
roomName.length >= 8 &&
islevel.length === 1 &&
apiUserName.length >= 6 &&
(islevel === '0' || islevel === '1' || islevel === '2'))) {
const validationError = {
success: false,
rtpCapabilities: null,
reason: 'Invalid roomName or islevel or apiUserName or secret',
};
reject(validationError);
return;
}
socket.emit('joinConRoom', { roomName, islevel, member, sec, apiUserName }, async (data) => {
try {
// Check if rtpCapabilities is null
if (data.rtpCapabilities === null) {
// Check if banned, suspended, or noAdmin
if (data.banned) {
throw new Error('User is banned.');
}
if (data.suspended) {
throw new Error('User is suspended.');
}
if (data.noAdmin) {
throw new Error('Host has not joined the room yet.');
}
// Resolve with the data received from the 'joinConRoom' event
resolve(data);
}
else {
// Resolve with the data received from the 'joinConRoom' event
resolve(data);
}
}
catch (error) {
// Handle errors during the joinConRoom process
console.log('Error joining room:', error);
reject(error);
}
});
});
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: JoinConRoom, deps: [{ token: ValidateAlphanumeric }], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: JoinConRoom, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: JoinConRoom, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: () => [{ type: ValidateAlphanumeric }] });
/**
* @service JoinConsumeRoom
* @description Service to join a media consumption room, setup device, and manage piped transports.
*
* @method joinConsumeRoom
* Joins a consumption room by sending a request to the server and performing necessary device and transport setup.
*
* @param {JoinConsumeRoomOptions} options - Options for joining the consumption room.
* @param {Socket} options.remote_sock - The remote socket used for joining the room.
* @param {string} options.apiToken - API token for authentication.
* @param {string} options.apiUserName - API username for authentication.
* @param {JoinConsumeRoomParameters} options.parameters - Parameters required for the function.
*
* @returns {Promise<JoinConsumeRoomResponse>} A promise that resolves with the result of the join request.
*
* @example
* ```typescript
* const joinConsumeRoomResponse = await joinConsumeRoomService.joinConsumeRoom({
* remote_sock: mySocket,
* apiToken: 'apiToken123',
* apiUserName: 'myUser',
* parameters: {
* roomName: 'room1',
* islevel: '2',
* member: 'JohnDoe',
* device: null,
* updateDevice: updateDeviceFunction,
* receiveAllPipedTransports: receiveAllPipedTransportsFunction,
* createDeviceClient: createDeviceClientFunction,
* }
* });
* ```
*/
class JoinConsumeRoom {
JoinConRoomService;
constructor(JoinConRoomService) {
this.JoinConRoomService = JoinConRoomService;
}
/**
* Joins a consumption room by sending a request to the server and handles the necessary setup.
* @param {Object} options - The options object containing necessary variables.
* @param {any} options.remote_sock - The remote socket information.
* @param {string} options.apiToken - The API token for authentication.
* @param {string} options.apiUserName - The API username for authentication.
* @param {any} options.parameters - Additional parameters required for the function.
* @returns {Promise<any>} - A promise that resolves with data related to the success of joining the room.
*/
joinConsumeRoom = async ({ remote_sock, apiToken, apiUserName, parameters, }) => {
let { roomName, islevel, member, device, updateDevice,
//Mediasfu functions
receiveAllPipedTransports, createDeviceClient, } = parameters;
try {
// Join the consumption room
const data = await this.JoinConRoomService.joinConRoom({
socket: remote_sock,
roomName,
islevel,
member,
sec: apiToken,
apiUserName,
});
if (data && data.success) {
// Setup media device if not already set
if (!device) {
if (data.rtpCapabilities) {
const device_ = await createDeviceClient({
rtpCapabilities: data.rtpCapabilities,
});
if (device_) {
updateDevice(device_);
}
}
}
// Receive all piped transports
await receiveAllPipedTransports({ nsock: remote_sock, parameters });
}
return data;
}
catch (error) {
console.log('Error in joinConsumeRoom:', error);
throw new Error('Failed to join the consumption room or set up necessary components.');
}
};
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: JoinConsumeRoom, deps: [{ token: JoinConRoom }], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: JoinConsumeRoom, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: JoinConsumeRoom, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: () => [{ type: JoinConRoom }] });
/**
* @service ProducerClosed
* @description Service to manage the closing of a producer, including resizing video elements and updating consumer transports.
*
* @method producerClosed
* Closes a specific producer by its ID, adjusts any associated video elements, and updates the list of consumer transports.
*
* @param {ProducerClosedOptions} options - Options for closing the specified producer.
* @param {string} options.remoteProducerId - Unique ID for the producer to close.
* @param {ProducerClosedParameters} options.parameters - Parameters to configure the producer closure and related updates.
*
* @returns {Promise<void>} A promise that resolves when the producer has been closed and relevant updates have been made.
*
* @example
* ```typescript
* await producerClosedService.producerClosed({
* remoteProducerId: 'producer123',
* parameters: {
* consumerTransports: currentTransports,
* screenId: 'screen123',
* updateConsumerTransports: updateTransportList,
* closeAndResize: closeAndResizeFunction,
* getUpdatedAllParams: getUpdatedParamsFunction,
* }
* });
* ```
*/
class ProducerClosed {
/**
* Handles the closing of a producer and resizes video elements.
* @param {Object} options - The options object containing necessary variables.
* @param {string} options.remoteProducerId - The ID of the remote producer.
* @param {any} options.parameters - Additional parameters required for the function.
* @returns {Promise<void>}
*/
producerClosed = async ({ remoteProducerId, parameters, }) => {
let { consumerTransports, screenId, updateConsumerTransports,
// mediasfu functions
closeAndResize, } = parameters;
// Handle producer closed
const producerToClose = consumerTransports.find((transportData) => transportData.producerId === remoteProducerId);
if (!producerToClose) {
return;
}
// Check if the ID of the producer to close is == screenId
let kind = producerToClose.consumer.kind;
if (producerToClose.producerId == screenId) {
kind = 'screenshare';
}
try {
await producerToClose['consumerTransport'].close();
}
catch (error) {
console.error('Error closing consumerTransport:', error);
}
try {
producerToClose.consumer.close();
}
catch (error) {
console.error('Error closing consumer:', error);
}
consumerTransports = consumerTransports.filter((transportData) => transportData.producerId !== remoteProducerId);
updateConsumerTransports(consumerTransports);
// Close and resize the videos
await closeAndResize({ producerId: remoteProducerId, kind: kind, parameters: parameters });
};
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: ProducerClosed, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: ProducerClosed, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: ProducerClosed, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}] });
/**
* Signals the creation of a new consumer transport.
*
* @param {Object} options - The options for signaling a new consumer transport.
* @param {string} options.remoteProducerId - The ID of the remote producer.
* @param {boolean} options.islevel - Indicates the level of the consumer.
* @param {any} options.nsock - The socket instance for communication.
* @param {SignalNewConsumerTransportOptions} options.parameters - The parameters for the transport.
*
* @returns {Promise<string[] | void>} A promise that resolves to an array of consuming transports or void.
*
* @throws Will throw an error if the signaling process fails.
*
* @example
* const options = {
* remoteProducerId: 'producer-id',
* islevel: true,
* nsock: socketInstance,
* parameters: {
* device: mediaDevice,
* consumingTransports: [],
* lock_screen: false,
* updateConsumingTransports: updateFunction,
* connectRecvTransport: connectFunction,
* reorderStreams: reorderFunction,
* getUpdatedAllParams: getUpdatedParamsFunction,
* },
* };
*
* signalNewConsumerTransport(options)
* .then(consumingTransports => {
* console.log('Consuming Transports:', consumingTransports);
* })
* .catch(error => {
* console.error('Error signaling new consumer transport:', error);
* });
*/
class SignalNewConsumerTransport {
signalNewConsumerTransport = async ({ remoteProducerId, islevel, nsock, parameters, }) => {
try {
let { device, consumingTransports, lock_screen, updateConsumingTransports, connectRecvTransport, reorderStreams, } = parameters;
// Get updated parameters
const updatedParams = parameters.getUpdatedAllParams();
device = updatedParams.device;
consumingTransports = updatedParams.consumingTransports;
// Check if already consuming
if (consumingTransports.includes(remoteProducerId)) {
return consumingTransports;
}
// Add remote producer ID to consumingTransports array
consumingTransports.push(remoteProducerId);
updateConsumingTransports(consumingTransports);
// Emit createWebRtcTransport event to signal a new consumer
nsock.emit('createWebRtcTransport', { consumer: true, islevel }, async ({ params }) => {
if (params.error) {
// Handle error
return;
}
try {
// Create a new receiving transport using the received parameters
if (!device) {
throw new Error('Device is not initialized');
}
const consumerTransport = device.createRecvTransport({ ...params });
// Handle 'connect' event for the consumer transport
consumerTransport.on('connect', async ({ dtlsParameters }, callback, errback) => {
try {
// Emit transport-recv-connect event to signal connection
nsock.emit('transport-recv-connect', {
dtlsParameters,
serverConsumerTransportId: params.id,
});
callback();
}
catch (error) {
errback(error);
}
});
// Listen for connection state change
consumerTransport.on('connectionstatechange', async (state) => {
switch (state) {
case 'connecting':
// Handle connecting state
break;
case 'connected':
// Handle connected state
break;
case 'failed':
// Handle failed state
consumerTransport.close();
// Reorder streams based on conditions
if (lock_screen) {
await reorderStreams({ add: true, parameters });
}
else {
await reorderStreams({ add: false, parameters });
}
break;
default:
break;
}
});
// Connect the receiving transport
await connectRecvTransport({
consumerTransport,
remoteProducerId,
serverConsumerTransportId: params.id,
nsock,
parameters,
});
}
catch (error) {
console.log(error, 'createRecvTransport error');
// Handle error
return;
}
});
}
catch (error) {
console.log(error, 'signalNewConsumerTransport error');
// Handle error
return;
}
};
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: SignalNewConsumerTransport, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: SignalNewConsumerTransport, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: SignalNewConsumerTransport, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}] });
/**
* @service NewPipeProducer
* @description Service to manage new pipe producer events, update state, and handle screen orientation for optimal experience.
*
* @method newPipeProducer
* Handles the setup of a new pipe producer and manages user notifications or orientation changes as needed.
*
* @param {NewPipeProducerOptions} options - Options for setting up a new pipe producer.
* @param {string} options.producerId - Unique ID for the new producer.
* @param {string} options.islevel - Level designation for the producer.
* @param {Socket} options.nsock - The socket used for communication.
* @param {NewPipeProducerParameters} options.parameters - Parameters to configure the new pipe producer.
*
* @returns {Promise<void>} A promise that completes when the new pipe producer is set up.
*
* @example
* ```typescript
* await newPipeProducerService.newPipeProducer({
* producerId: 'producer123',
* islevel: '2',
* nsock: mySocket,
* parameters: {
* first_round: true,
* shareScreenStarted: false,
* shared: false,
* landScaped: false,
* showAlert: alertFunction,
* isWideScreen: true,
* updateFirst_round: updateFirstRoundFunction,
* updateLandScaped: updateLandScapedFunction,
* device: myDevice,
* consumingTransports: [],
* connectRecvTransport: connectRecvTransportFunction,
* reorderStreams: reorderStreamsFunction,
* getUpdatedAllParams: getUpdatedParamsFunction,
* }
* });
* ```
*/
class NewPipeProducer {
signalNewConsumerTransportService;
constructor(signalNewConsumerTransportService) {
this.signalNewConsumerTransportService = signalNewConsumerTransportService;
}
/**
* Handles new pipe producer events and updates relevant states.
* @param {Object} options - The options object containing necessary variables.
* @param {string} options.producerId - The ID of the producer.
* @param {string} options.islevel - The level of the producer.
* @param {any} options.nsock - The socket object.
* @param {any} options.parameters - Additional parameters required for the function.
* @returns {Promise<void>}
*/
newPipeProducer = async ({ producerId, islevel, nsock, parameters, }) => {
let { first_round, shareScreenStarted, shared, landScaped, showAlert, isWideScreen, updateFirst_round, updateLandScaped, } = parameters;
try {
// Perform signaling for new consumer transport
await this.signalNewConsumerTransportService.signalNewConsumerTransport({
remoteProducerId: producerId,
islevel: islevel,
nsock: nsock,
parameters: parameters,
});
first_round = false;
if (shareScreenStarted || shared) {
if (!isWideScreen) {
if (!landScaped) {
if (showAlert) {
showAlert({
message: 'Please rotate your device to landscape mode for better experience',
type: 'success',
duration: 3000,
});
}
landScaped = true;
updateLandScaped(landScaped);
}
}
first_round = true;
updateFirst_round(first_round);
}
}
catch (error) {
console.error('Error in newPipeProducer:', error);
throw new Error('Failed to handle new pipe producer event.');
}
};
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: NewPipeProducer, deps: [{ token: SignalNewConsumerTransport }], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: NewPipeProducer, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: NewPipeProducer, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: () => [{ type: SignalNewConsumerTransport }] });
/**
* MiniCard component displays a customizable card with an image or initials.
*
* @component
* @selector app-mini-card
* @standalone true
* @imports CommonModule
*
* @template
* ```html
* <div class="mini-card" [ngStyle]="getMergedCardStyles()">
* <div *ngIf="imageSource; else noImage" class="image-container">
* <img [src]="imageSource" alt="Profile" [ngStyle]="getMergedImageStyles()" />
* </div>
* <ng-template #noImage>
* <div class="initials" [ngStyle]="getInitialsStyle()">{{ initials }}</div>
* </ng-template>
* </div>
* ```
*
* @styleUrls ['./mini-card.component.css']
*
* @inputs
* - `initials` (string): Initials to display if no image is provided.
* - `fontSize` (number): Font size for initials text, default is 14.
* - `customStyle` (CSSStyleDeclaration): Custom styles for the card.
* - `imageSource` (string): Source URL for the image.
* - `roundedImage` (boolean): Whether the image should be rounded, default is false.
* - `imageStyle` (CSSStyleDeclaration): Custom styles for the image.
*
* @constructor
* - Optionally accepts injected values for each input property.
*
* @methods
* - `getMergedCardStyles()`: Returns merged styles for the card.
* - `getMergedImageStyles()`: Returns merged styles for the image.
* - `getInitialsStyle()`: Returns styles for the initials text.
*
* @example
* ```html
* <app-mini-card initials="AB" fontSize="20" [roundedImage]="true" imageSource="/path/to/image.jpg"></app-mini-card>
* ```
*/
class MiniCard {
initials;
fontSize = 14;
customStyle = {};
imageSource;
roundedImage = false;
imageStyle = {};
constructor(injectedInitials, injectedFontSize, injectedCustomStyle, injectedImageSource, injectedRoundedImage, injectedImageStyle) {
this.initials = injectedInitials || this.initials || '';
this.fontSize = injectedFontSize || this.fontSize || 14;
this.customStyle = injectedCustomStyle || this.customStyle || {};
this.imageSource = injectedImageSource || this.imageSource || '';
this.roundedImage = injectedRoundedImage || this.roundedImage || true;
this.imageStyle = injectedImageStyle || this.imageStyle || {};
}
getMergedCardStyles() {
return {
'font-size': this.fontSize + 'px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
borderRadius: '0',
width: '100%',
height: '100%',
color: 'black',
fontFamily: "'Nunito', sans-serif",
overflow: 'hidden',
border: '2px solid black',
...this.customStyle,
};
}
getMergedImageStyles() {
return {
width: '60%',
height: '60%',
objectFit: 'cover',
...(this.roundedImage ? { borderRadius: '50%' } : {}),
...this.imageStyle,
};
}
getInitialsStyle() {
return {
textAlign: 'center',
'font-size': this.fontSize + 'px',
};
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: MiniCard, deps: [{ token: 'initials', optional: true }, { token: 'fontSize', optional: true }, { token: 'customStyle', optional: true }, { token: 'imageSource', optional: true }, { token: 'roundedImage', optional: true }, { token: 'imageStyle', optional: true }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.0.1", type: MiniCard, isStandalone: true, selector: "app-mini-card", inputs: { initials: "initials", fontSize: "fontSize", customStyle: "customStyle", imageSource: "imageSource", roundedImage: "roundedImage", imageStyle: "imageStyle" }, ngImport: i0, template: `
<div class="mini-card" [ngStyle]="getMergedCardStyles()">
<div *ngIf="imageSource; else noImage" class="image-container">
<img [src]="imageSource" alt="Profile" [ngStyle]="getMergedImageStyles()" />
</div>
<ng-template #noImage>
<div class="initials" [ngStyle]="getInitialsStyle()">{{ initials }}</div>
</ng-template>
</div>
`, isInline: true, styles: [".mini-card{display:flex;justify-content:center;align-items:center;border-radius:0;width:100%;height:100%;color:#000;font-family:Nunito,sans-serif;overflow:hidden;border:2px solid black}.image-container{display:flex;justify-content:center;align-items:center;width:100%;height:100%}.backgroundImage{width:60%;height:60%;object-fit:cover}.roundedImage{border-radius:50%}.initials{text-align:center;font-size:14px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: MiniCard, decorators: [{
type: Component,
args: [{ selector: 'app-mini-card', imports: [CommonModule], template: `
<div class="mini-card" [ngStyle]="getMergedCardStyles()">
<div *ngIf="imageSource; else noImage" class="image-container">
<img [src]="imageSource" alt="Profile" [ngStyle]="getMergedImageStyles()" />
</div>
<ng-template #noImage>
<div class="initials" [ngStyle]="getInitialsStyle()">{{ initials }}</div>
</ng-template>
</div>
`, styles: [".mini-card{display:flex;justify-content:center;align-items:center;border-radius:0;width:100%;height:100%;color:#000;font-family:Nunito,sans-serif;overflow:hidden;border:2px solid black}.image-container{display:flex;justify-content:center;align-items:center;width:100%;height:100%}.backgroundImage{width:60%;height:60%;object-fit:cover}.roundedImage{border-radius:50%}.initials{text-align:center;font-size:14px}\n"] }]
}], ctorParameters: () => [{ type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: ['initials']
}] }, { type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: ['fontSize']
}] }, { type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: ['customStyle']
}] }, { type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: ['imageSource']
}] }, { type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: ['roundedImage']
}] }, { type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: ['imageStyle']
}] }], propDecorators: { initials: [{
type: Input
}], fontSize: [{
type: Input
}], customStyle: [{
type: Input
}], imageSource: [{
type: Input
}], roundedImage: [{
type: Input
}], imageStyle: [{
type: Input
}] } });
/**
* CardVideoDisplay component displays a video stream with options for full display, mirroring, and background color customization.
*
* @selector app-card-video-display
* @standalone true
* @imports CommonModule
*
* @inputs
* - `remoteProducerId` (string): Identifier for the remote producer.
* - `eventType` (EventType): Type of event, such as 'webinar'. Default is 'webinar'.
* - `forceFullDisplay` (boolean): Forces full video display if true. Default is false.
* - `videoStream` (MediaStream | null): The media stream to display in the video element.
* - `backgroundColor` (string): Background color for the video container. Default is 'transparent'.
* - `doMirror` (boolean): Mirrors the video if true. Default is false.
*
* @methods
* - `ngOnInit()`: Initializes the video stream and sets the container style on component load.
* - `ngOnChanges(changes: SimpleChanges)`: Updates the video stream or container style when inputs change.
* - `updateVideoStream()`: Assigns the video stream to the video element if it differs from the current stream.
* - `setVideoContainerStyle()`: Sets the style of the video container based on the provided background color.
* - `getBaseVideoContainerStyle()`: Returns base styles for the video container.
* - `getVideoStyle()`: Returns styles for the video element, including optional mirroring and sizing.
*
* @example
* ```html
* <app-card-video-display
* [remoteProducerId]="producerId"
* [eventType]="'conference'"
* [forceFullDisplay]="true"
* [videoStream]="stream"
* [backgroundColor]="'black'"
* [doMirror]="true">
* </app-card-video-display>
* ```
**/
class CardVideoDisplay {
remoteProducerId = '';
eventType = 'webinar';
forceFullDisplay = false;
videoStream = null;
backgroundColor = 'transparent';
doMirror = false;
videoElement;
videoContainerStyle;
ngOnInit() {
this.updateVideoStream();
this.setVideoContainerStyle();
}
ngOnChanges(changes) {
if (changes['videoStream'] && this.videoStream) {
const currentStream = changes['videoStream'].currentValue;
const previousStream = changes['videoStream'].previousValue;
if (!previousStream ||
currentStream.id !== previousStream.id ||
currentStream.active !== previousStream.active) {
this.updateVideoStream();
}
}
if (changes['backgroundColor'] &&
changes['backgroundColor'].currentValue !== changes['backgroundColor'].previousValue) {
this.setVideoContainerStyle();
}
}
updateVideoStream() {
if (this.videoElement && this.videoStream) {
const videoElement = this.videoElement.nativeElement;
// Update the video element's srcObject only if it has changed
if (videoElement.srcObject !== this.videoStream) {
videoElement.srcObject = this.videoStream;
}
}
}
setVideoContainerStyle() {
this.videoContainerStyle = {
...this.getBaseVideoContainerStyle(),
backgroundColor: this.backgroundColor,
};
}
getBaseVideoContainerStyle() {
return {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '100%',
height: '100%',
backgroundColor: 'black',
};
}
getVideoStyle() {
const baseStyles = {
width: this.forceFullDisplay ? '100%' : 'auto',
height: '100%',
maxWidth: '100%',
maxHeight: '100%',
objectFit: this.forceFullDisplay ? 'cover' : 'contain',
backgroundColor: this.backgroundColor,
};
if (this.doMirror) {
baseStyles.transform = 'rotateY(180deg)';
}
return baseStyles;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: CardVideoDisplay, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.0.1", type: CardVideoDisplay, isStandalone: true, selector: "app-card-video-display", inputs: { remoteProducerId: "remoteProducerId", eventType: "eventType", forceFullDisplay: "forceFullDisplay", videoStream: "videoStream", backgroundColor: "backgroundColor", doMirror: "doMirror" }, viewQueries: [{ propertyName: "videoElement", first: true, predicate: ["videoElement"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<div [ngStyle]=\"videoContainerStyle\">\r\n <video #videoElement autoplay muted playsinline [ngStyle]=\"getVideoStyle()\"></video>\r\n</div>\r\n", styles: [".videoContainer{display:flex;justify-content:center;align-items:center;width:100%;height:100%;background-color:#000}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: CardVideoDisplay, decorators: [{
type: Component,
args: [{ selector: 'app-card-video-display', imports: [CommonModule], template: "<div [ngStyle]=\"videoContainerStyle\">\r\n <video #videoElement autoplay muted playsinline [ngStyle]=\"getVideoStyle()\"></video>\r\n</div>\r\n", styles: [".videoContainer{display:flex;justify-content:center;align-items:center;width:100%;height:100%;background-color:#000}\n"] }]
}], propDecorators: { remoteProducerId: [{
type: Input
}], eventType: [{
type: Input
}], forceFullDisplay: [{
type: Input
}], videoStream: [{
type: Input
}], backgroundColor: [{
type: Input
}], doMirror: [{
type: Input
}], videoElement: [{
type: ViewChild,
args: ['videoElement', { static: true }]
}] } });
/**
* Gets the style for positioning an overlay based on the specified position.
*
* @param {GetOverlayPositionOptions} options - Contains the desired position for the overlay.
* @param {string} options.position - The position for the overlay ('topLeft', 'topRight', 'bottomLeft', 'bottomRight').
* @returns {OverlayPositionStyle} - Style object defining the overlay position.
*
* @example
* ```typescript
* const topLeftPosition = getOverlayPosition({ position: 'topLeft' });
* // Output: { top: 0, left: 0 }
*
* const bottomRightPosition = getOverlayPosition({ position: 'bottomRight' });
* // Output: { bottom: 0, right: 0 }
* ```
*/
function getOverlayPosition({ position }) {
switch (position) {
case 'topLeft':
return { top: 0, left: 0 };
case 'topRight':
return { top: 0, right: 0 };
case 'bottomLeft':
return { bottom: 0, left: 0 };
case 'bottomRight':
return { bottom: 0, right: 0 };
default:
return {};
}
}
/**
* Controls the media of a participant in a media session if certain conditions are met.
*
* @param {ControlMediaOptions} options - The options for controlling media.
* @param {string} options.participantId - The ID of the participant to control.
* @param {string} options.participantName - The name of the participant to control.
* @param {'audio' | 'video' | 'screenshare' | 'all'} options.type - The type of media to control.
* @param {Socket} options.socket - The socket instance for communication.
* @param {CoHostResponsibility[]} options.coHostResponsibility - List of co-host responsibilities.
* @param {Participant[]} options.participants - List of participants in the session.
* @param {string} options.member - The current member attempting to control media.
* @param {string} options.islevel - The level of the current member.
* @param {Function} [options.showAlert] - Optional function to show alerts.
* @param {string} options.coHost - The co-host information.
* @param {string} options.roomName - The name of the room.
*
* @returns {Promise<void>} A promise that resolves when the media control operation is complete.
*
* @throws Will log an error message if the operation fails.
*
* @example
* ```typescript
* const options = {
* participantId: '12345',
* participantName: 'John Doe',
* type: 'audio',
* socket: socketInstance,
* coHostResponsibility: [{ name: 'media', value: true }],
* participants: participantList,
* member: 'currentMember',
* islevel: '2',
* showAlert: showAlertFunction,
* coHost: 'coHostName',
* roomName: 'Room A',
* };
*
* controlMediaService.controlMedia(options)
* .then(() => {
* console.log('Media control action completed');
* })
* .catch((error) => {
* console.error('Error controlling media:', error);
* });
* ```
*/
class ControlMedia {
/**
* Controls the media of a participant in a media session if certain conditions are met.
*
* @param {Object} options - The options for controlling media.
* @param {string} options.participantId - The ID of the participant to control.
* @param {string} options.participantName - The name of the participant to control.
* @param {string} options.type - The type of media to control.
* @param {Socket} options.socket - The socket instance for communication.
* @param {Array} options.coHostResponsibility - List of co-host responsibilities.
* @param {Array} options.participants - List of participants in the session.
* @param {string} options.member - The current member attempting to control media.
* @param {string} options.islevel - The level of the current member.
* @param {Function} [options.showAlert] - Optional function to show alerts.
* @param {string} options.coHost - The co-host information.
* @param {string} options.roomName - The name of the room.
*
* @returns {Promise<void>} A promise that resolves when the media control operation is complete.
*/
async controlMedia({ participantId, participantName, type, socket, coHostResponsibility, participants, member, islevel, showAlert, coHost, roomName, }) {
try {
// Destructure parameters
let mediaValue = false;
try {
mediaValue =
coHostResponsibility.find((item) => item.name === 'media')?.value ?? false;
}
catch {
/* handle error */
}
let participant = participants.find((obj) => obj.name === participantName);
if (islevel === '2' || (coHost === member && mediaValue === true)) {
// Check if the participant is not muted and is not a host
if (participant &&
((!participant.muted && participant.islevel !== '2' && type == 'audio') ||
(participant.islevel !== '2' && type == 'video' && participant['videoOn']))) {
// Emit controlMedia event to the server
socket.emit('controlMedia', { participantId, participantName, type, roomName });
}
}
else {
// Display an alert if the participant is not allowed to mute other participants
if (showAlert) {
showAlert({
message: 'You are not allowed to control media for other participants.',
type: 'danger',
duration: 3000,
});
}
}
}
catch (error) {
console.log('controlMedia error', error);
// throw error;
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: ControlMedia, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: ControlMedia, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: ControlMedia, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}] });
/**
* VideoCard component represents a customizable video display card with participant controls for toggling audio and video.
* It also animates an audio waveform if sound is detected in the participant's audio stream.
*
* @selector app-video-card
* @standalone true
* @imports [CommonModule, FontAwesomeModule, CardVideoDisplay]
*
* @example
* ```html
* <app-video-card
* [name]="participant.name"
* [videoStream]="videoStream"
* [audioDecibels]="audioDecibels"
* [participant]="participant"
* [parameters]="videoCardParameters"
* ></app-video-card>
* ```
*
* @input {Partial<CSSStyleDeclaration>} customStyle - Styles for the card container.
* @input {string} name - Name of the participant displayed on the card.
* @input {string} barColor - Color of the waveform bars. Default is 'red'.
* @input {string} textColor - Color of the name text. Default is 'white'.
* @input {string} imageSource - Source URL of the participant's image.
* @input {boolean} roundedImage - Whether the image should have rounded corners.
* @input {Partial<CSSStyleDeclaration>} imageStyle - Additional styles for the image.
* @input {string} remoteProducerId - ID of the remote media producer.
* @input {EventType} eventType - Ty