2022-10-20 09:11:27 +00:00
|
|
|
import { IReduxState } from '../app/types';
|
2022-11-22 13:56:37 +00:00
|
|
|
import { IJitsiConference } from '../base/conference/reducer';
|
2022-09-14 12:42:46 +00:00
|
|
|
import { getLocalParticipant } from '../base/participants/functions';
|
2022-05-23 15:02:14 +00:00
|
|
|
import { extractFqnFromPath } from '../dynamic-branding/functions.any';
|
2021-12-21 11:46:54 +00:00
|
|
|
|
2022-11-22 13:56:37 +00:00
|
|
|
import { FACE_BOX_EVENT_TYPE, FACE_LANDMARKS_EVENT_TYPE, SEND_IMAGE_INTERVAL_MS } from './constants';
|
2021-11-17 14:33:03 +00:00
|
|
|
import logger from './logger';
|
2022-11-22 13:56:37 +00:00
|
|
|
import { FaceBox, FaceLandmarks } from './types';
|
2022-04-04 13:09:14 +00:00
|
|
|
|
2021-11-17 14:33:03 +00:00
|
|
|
/**
|
2022-11-22 13:56:37 +00:00
|
|
|
* Sends the face landmarks to other participants via the data channel.
|
2021-11-17 14:33:03 +00:00
|
|
|
*
|
2022-09-14 12:42:46 +00:00
|
|
|
* @param {any} conference - The current conference.
|
2022-11-22 13:56:37 +00:00
|
|
|
* @param {FaceLandmarks} faceLandmarks - Face landmarks to be sent.
|
2021-11-17 14:33:03 +00:00
|
|
|
* @returns {void}
|
|
|
|
*/
|
2022-11-22 13:56:37 +00:00
|
|
|
export function sendFaceExpressionToParticipants(conference: any, faceLandmarks: FaceLandmarks): void {
|
2021-11-17 14:33:03 +00:00
|
|
|
try {
|
|
|
|
conference.sendEndpointMessage('', {
|
2022-11-22 13:56:37 +00:00
|
|
|
type: FACE_LANDMARKS_EVENT_TYPE,
|
|
|
|
faceLandmarks
|
2021-11-17 14:33:03 +00:00
|
|
|
});
|
|
|
|
} catch (err) {
|
2022-11-22 13:56:37 +00:00
|
|
|
logger.warn('Could not broadcast the face landmarks to the other participants', err);
|
2021-11-17 14:33:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-04-04 13:09:14 +00:00
|
|
|
/**
|
|
|
|
* Sends the face box to all the other participants.
|
|
|
|
*
|
2022-09-14 12:42:46 +00:00
|
|
|
* @param {any} conference - The current conference.
|
|
|
|
* @param {FaceBox} faceBox - Face box to be sent.
|
2022-04-04 13:09:14 +00:00
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
export function sendFaceBoxToParticipants(
|
2022-09-14 12:42:46 +00:00
|
|
|
conference: any,
|
|
|
|
faceBox: FaceBox
|
2022-04-04 13:09:14 +00:00
|
|
|
): void {
|
|
|
|
try {
|
|
|
|
conference.sendEndpointMessage('', {
|
|
|
|
type: FACE_BOX_EVENT_TYPE,
|
|
|
|
faceBox
|
|
|
|
});
|
|
|
|
} catch (err) {
|
|
|
|
logger.warn('Could not broadcast the face box to the other participants', err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-17 14:33:03 +00:00
|
|
|
/**
|
2022-11-22 13:56:37 +00:00
|
|
|
* Sends the face landmarks to prosody.
|
2021-11-17 14:33:03 +00:00
|
|
|
*
|
2022-09-14 12:42:46 +00:00
|
|
|
* @param {any} conference - The current conference.
|
2022-11-22 13:56:37 +00:00
|
|
|
* @param {FaceLandmarks} faceLandmarks - Face landmarks to be sent.
|
2021-11-17 14:33:03 +00:00
|
|
|
* @returns {void}
|
|
|
|
*/
|
2022-11-22 13:56:37 +00:00
|
|
|
export function sendFaceExpressionToServer(conference: IJitsiConference, faceLandmarks: FaceLandmarks): void {
|
2021-11-17 14:33:03 +00:00
|
|
|
try {
|
2022-11-22 13:56:37 +00:00
|
|
|
conference.sendFaceLandmarks(faceLandmarks);
|
2021-11-17 14:33:03 +00:00
|
|
|
} catch (err) {
|
2022-11-22 13:56:37 +00:00
|
|
|
logger.warn('Could not send the face landmarks to prosody', err);
|
2021-11-17 14:33:03 +00:00
|
|
|
}
|
2021-12-21 11:46:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-11-22 13:56:37 +00:00
|
|
|
* Sends face landmarks to backend.
|
2021-12-21 11:46:54 +00:00
|
|
|
*
|
|
|
|
* @param {Object} state - Redux state.
|
|
|
|
* @returns {boolean} - True if sent, false otherwise.
|
|
|
|
*/
|
2022-10-20 09:11:27 +00:00
|
|
|
export async function sendFaceExpressionsWebhook(state: IReduxState) {
|
2021-12-21 11:46:54 +00:00
|
|
|
const { webhookProxyUrl: url } = state['features/base/config'];
|
|
|
|
const { conference } = state['features/base/conference'];
|
|
|
|
const { jwt } = state['features/base/jwt'];
|
|
|
|
const { connection } = state['features/base/connection'];
|
2022-09-14 12:42:46 +00:00
|
|
|
const jid = connection?.getJid();
|
2021-12-21 11:46:54 +00:00
|
|
|
const localParticipant = getLocalParticipant(state);
|
2022-11-22 13:56:37 +00:00
|
|
|
const { faceLandmarksBuffer } = state['features/face-landmarks'];
|
2021-12-21 11:46:54 +00:00
|
|
|
|
2022-11-22 13:56:37 +00:00
|
|
|
if (faceLandmarksBuffer.length === 0) {
|
2021-12-21 11:46:54 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const headers = {
|
|
|
|
...jwt ? { 'Authorization': `Bearer ${jwt}` } : {},
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
};
|
|
|
|
|
|
|
|
const reqBody = {
|
|
|
|
meetingFqn: extractFqnFromPath(),
|
2023-01-26 21:00:25 +00:00
|
|
|
sessionId: conference?.getMeetingUniqueId(),
|
2021-12-21 11:46:54 +00:00
|
|
|
submitted: Date.now(),
|
2022-11-22 13:56:37 +00:00
|
|
|
emotions: faceLandmarksBuffer,
|
2022-09-14 12:42:46 +00:00
|
|
|
participantId: localParticipant?.jwtId,
|
|
|
|
participantName: localParticipant?.name,
|
2021-12-21 11:46:54 +00:00
|
|
|
participantJid: jid
|
|
|
|
};
|
|
|
|
|
|
|
|
if (url) {
|
|
|
|
try {
|
|
|
|
const res = await fetch(`${url}/emotions`, {
|
|
|
|
method: 'POST',
|
|
|
|
headers,
|
|
|
|
body: JSON.stringify(reqBody)
|
|
|
|
});
|
|
|
|
|
|
|
|
if (res.ok) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
logger.error('Status error:', res.status);
|
|
|
|
} catch (err) {
|
|
|
|
logger.error('Could not send request', err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2021-11-17 14:33:03 +00:00
|
|
|
}
|
|
|
|
|
2022-04-04 13:09:14 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets face box for a participant id.
|
|
|
|
*
|
|
|
|
* @param {string} id - The participant id.
|
2022-10-20 09:11:27 +00:00
|
|
|
* @param {IReduxState} state - The redux state.
|
2022-04-04 13:09:14 +00:00
|
|
|
* @returns {Object}
|
|
|
|
*/
|
2022-10-20 09:11:27 +00:00
|
|
|
function getFaceBoxForId(id: string, state: IReduxState) {
|
2022-04-06 09:10:31 +00:00
|
|
|
return state['features/face-landmarks'].faceBoxes[id];
|
2022-04-04 13:09:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the video object position for a participant id.
|
|
|
|
*
|
2022-10-20 09:11:27 +00:00
|
|
|
* @param {IReduxState} state - The redux state.
|
2022-04-04 13:09:14 +00:00
|
|
|
* @param {string} id - The participant id.
|
|
|
|
* @returns {string} - CSS object-position in the shape of '{horizontalPercentage}% {verticalPercentage}%'.
|
|
|
|
*/
|
2022-10-20 09:11:27 +00:00
|
|
|
export function getVideoObjectPosition(state: IReduxState, id?: string) {
|
2022-09-14 12:42:46 +00:00
|
|
|
const faceBox = id && getFaceBoxForId(id, state);
|
2022-04-04 13:09:14 +00:00
|
|
|
|
|
|
|
if (faceBox) {
|
|
|
|
const { right, width } = faceBox;
|
|
|
|
|
2022-09-14 12:42:46 +00:00
|
|
|
if (right && width) {
|
|
|
|
return `${right - (width / 2)}% 50%`;
|
|
|
|
}
|
2022-04-04 13:09:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return '50% 50%';
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the video object position for a participant id.
|
|
|
|
*
|
2022-10-20 09:11:27 +00:00
|
|
|
* @param {IReduxState} state - The redux state.
|
2022-08-30 14:21:58 +00:00
|
|
|
* @returns {number} - Number of milliseconds for doing face detection.
|
2022-04-04 13:09:14 +00:00
|
|
|
*/
|
2022-10-20 09:11:27 +00:00
|
|
|
export function getDetectionInterval(state: IReduxState) {
|
2022-04-06 09:10:31 +00:00
|
|
|
const { faceLandmarks } = state['features/base/config'];
|
2022-04-04 13:09:14 +00:00
|
|
|
|
2022-04-06 09:10:31 +00:00
|
|
|
return Math.max(faceLandmarks?.captureInterval || SEND_IMAGE_INTERVAL_MS);
|
2021-11-17 14:33:03 +00:00
|
|
|
}
|