Activity logging is working well enough for now. We need to add a reports page for the video conferences next.
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { ae_loc, ae_api } from '$lib/stores/ae_stores';
|
||||
import { create_ae_obj__activity_log } from '$lib/ae_core/core__activity_log';
|
||||
import { create_ae_obj__activity_log, update_ae_obj__activity_log } from '$lib/ae_core/core__activity_log';
|
||||
|
||||
let log_lvl: number = $state(0);
|
||||
|
||||
@@ -10,68 +11,83 @@
|
||||
}
|
||||
let { data }: Props = $props();
|
||||
|
||||
// Component UI State
|
||||
let show_jitsi_container: boolean = $state(true);
|
||||
let show_jitsi_tools: boolean = $state(true);
|
||||
let expand_jitsi_tools: boolean = $state(false);
|
||||
|
||||
// Toggles for collapsible sections
|
||||
let show_meeting_details: boolean = $state(false);
|
||||
let show_live_stats: boolean = $state(false);
|
||||
let show_profile_editor: boolean = $state(false);
|
||||
let show_sound_settings: boolean = $state(false);
|
||||
|
||||
// User & Meeting State
|
||||
let user_id: null | string = $state(null);
|
||||
let display_name: null | string = $state(null);
|
||||
let email: null | string = $state(null);
|
||||
let is_moderator: boolean = $state(false);
|
||||
|
||||
let room_name: null | string = $state(null);
|
||||
let domain: null | string = $state(null);
|
||||
|
||||
// Jitsi API & Sound Settings
|
||||
let jitsi_api: any = null;
|
||||
const jitsi_container_id = 'jitsi_meet_external_api_container';
|
||||
|
||||
let disable_incoming_msg_sound: boolean = $state(true);
|
||||
let disable_participant_joined_sound: boolean = $state(false); // NOTE: Disable by default
|
||||
let disable_participant_joined_sound: boolean = $state(false);
|
||||
let disable_participant_left_sound: boolean = $state(false);
|
||||
let disable_reaction_sound: boolean = $state(true); // NOTE: Disable by default
|
||||
let disable_raise_hand_sound: boolean = $state(true); // NOTE: Disable by default
|
||||
|
||||
let disable_reaction_sound: boolean = $state(true);
|
||||
let disable_raise_hand_sound: boolean = $state(true);
|
||||
let name_input: string = $state('');
|
||||
let email_input: string = $state('');
|
||||
|
||||
// State for Live Meeting Stats
|
||||
// --- NEW Logging & Stats State ---
|
||||
let jitsi_meeting_id: string | null = $state(null);
|
||||
let primary_activity_log_id: string | null = $state(null);
|
||||
let meeting_participants = $state(new Map<string, any>());
|
||||
let meeting_start_time: Date | null = $state(null);
|
||||
let meeting_duration: string = $state('00:00:00');
|
||||
let duration_timer_id: any = $state(null);
|
||||
let reporting_timer_id: any = $state(null);
|
||||
// reporting_timer_id is now removed
|
||||
|
||||
async function report_meeting_stats() {
|
||||
if (!is_moderator) {
|
||||
if (log_lvl) {
|
||||
console.log('Jitsi: Not a moderator, skipping stats report.');
|
||||
}
|
||||
// If for some reason the timer is running for a non-mod, kill it.
|
||||
if (reporting_timer_id) clearInterval(reporting_timer_id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (log_lvl) {
|
||||
console.log('Jitsi: Preparing to report meeting stats...');
|
||||
}
|
||||
const participants_array = Array.from(meeting_participants.values());
|
||||
/**
|
||||
* Creates a new activity log entry for a discrete event (e.g., raise hand).
|
||||
*/
|
||||
async function create_discrete_activity_log(action: string, action_with: string, meta_kv: object) {
|
||||
if (!is_moderator || !jitsi_meeting_id) return;
|
||||
if (log_lvl) console.log(`Jitsi: Creating discrete activity log for action: ${action}`);
|
||||
|
||||
const data_kv = {
|
||||
// NOTE: It may make since to use the Jitsi ID instead. Technically this is reporting on behalf of Jitsi and not really specific to the moderator. Also, this helps if there are multiple moderators!
|
||||
external_client_id: user_id, // Novi Customer GUID for now.
|
||||
name: 'live_stats_update', // Name of log entry "type"
|
||||
description: room_name,
|
||||
url_root: data?.url.origin,
|
||||
url_full_path: data?.url.pathname,
|
||||
url_params: data?.url.searchParams.toString(),
|
||||
action: 'jitsi_live_stats_report',
|
||||
action_with: 'timer_loop',
|
||||
external_client_id: jitsi_meeting_id,
|
||||
name: 'jitsi_meeting_event',
|
||||
description: `Event in room: ${room_name}`,
|
||||
url_root: $page.url.origin,
|
||||
url_full_path: $page.url.pathname,
|
||||
url_params: $page.url.searchParams.toString(),
|
||||
action: action,
|
||||
action_with: action_with,
|
||||
meta_json: meta_kv
|
||||
};
|
||||
|
||||
try {
|
||||
await create_ae_obj__activity_log({
|
||||
api_cfg: $ae_api,
|
||||
account_id: $ae_loc.account_id,
|
||||
data_kv: data_kv,
|
||||
log_lvl: log_lvl,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Jitsi: Error creating discrete activity log for ${action}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the primary activity log entry with the latest meeting state.
|
||||
*/
|
||||
async function update_primary_activity_log() {
|
||||
if (!is_moderator || !primary_activity_log_id) return;
|
||||
if (log_lvl) console.log(`Jitsi: Updating primary activity log: ${primary_activity_log_id}`);
|
||||
|
||||
const participants_array = Array.from(meeting_participants.values());
|
||||
const data_kv = {
|
||||
meta_json: {
|
||||
duration: meeting_duration,
|
||||
participants: participants_array,
|
||||
@@ -79,83 +95,92 @@
|
||||
}
|
||||
};
|
||||
|
||||
if (log_lvl > 1) {
|
||||
console.log('Jitsi: Stats payload being sent:', JSON.stringify(data_kv, null, 2));
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await create_ae_obj__activity_log({
|
||||
await update_ae_obj__activity_log({
|
||||
api_cfg: $ae_api,
|
||||
account_id: $ae_loc.account_id,
|
||||
activity_log_id: primary_activity_log_id,
|
||||
data_kv: data_kv,
|
||||
log_lvl: log_lvl,
|
||||
})
|
||||
if (log_lvl > 1) {
|
||||
console.log('Jitsi: Activity log API call successful.', result);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Jitsi: Error calling create_ae_obj__activity_log:', error);
|
||||
console.error('Jitsi: Error updating primary activity log:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function add_jitsi_event_listeners(api: any) {
|
||||
api.on('videoConferenceJoined', (data: { id: string; displayName: string }) => {
|
||||
// --- Meeting/Participant State Changes ---
|
||||
api.on('videoConferenceJoined', async (data: { id: string; displayName: string; roomName: string }) => {
|
||||
console.log('Jitsi Event: videoConferenceJoined', data);
|
||||
meeting_start_time = new Date();
|
||||
if (jitsi_meeting_id === null) jitsi_meeting_id = `${data.roomName}-${Date.now()}`;
|
||||
|
||||
// Start duration timer
|
||||
if (duration_timer_id) clearInterval(duration_timer_id);
|
||||
|
||||
duration_timer_id = setInterval(() => {
|
||||
if (meeting_start_time) {
|
||||
const now = new Date();
|
||||
const diff = now.getTime() - meeting_start_time.getTime();
|
||||
const hours = Math.floor(diff / (1000 * 60 * 60))
|
||||
.toString()
|
||||
.padStart(2, '0');
|
||||
const minutes = Math.floor((diff / (1000 * 60)) % 60)
|
||||
.toString()
|
||||
.padStart(2, '0');
|
||||
const seconds = Math.floor((diff / 1000) % 60)
|
||||
.toString()
|
||||
.padStart(2, '0');
|
||||
const hours = Math.floor(diff / (1000 * 60 * 60)).toString().padStart(2, '0');
|
||||
const minutes = Math.floor((diff / (1000 * 60)) % 60).toString().padStart(2, '0');
|
||||
const seconds = Math.floor((diff / 1000) % 60).toString().padStart(2, '0');
|
||||
meeting_duration = `${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
const local_participant = {
|
||||
id: data.id,
|
||||
displayName: data.displayName,
|
||||
role: 'participant'
|
||||
};
|
||||
meeting_participants.set(data.id, local_participant);
|
||||
|
||||
const all_participants = api.getParticipantsInfo();
|
||||
all_participants.forEach((p: { participantId: string; displayName: string }) => {
|
||||
// Populate initial participant list
|
||||
meeting_participants.set(data.id, { id: data.id, displayName: data.displayName, role: 'participant' });
|
||||
api.getParticipantsInfo().forEach((p: { participantId: string; displayName: string }) => {
|
||||
if (!meeting_participants.has(p.participantId)) {
|
||||
meeting_participants.set(p.participantId, {
|
||||
id: p.participantId,
|
||||
displayName: p.displayName,
|
||||
role: 'participant'
|
||||
});
|
||||
meeting_participants.set(p.participantId, { id: p.participantId, displayName: p.displayName, role: 'participant' });
|
||||
}
|
||||
});
|
||||
|
||||
// If moderator, start reporting stats
|
||||
if (is_moderator) {
|
||||
console.log('Jitsi: User is a moderator. Starting stats reporting timer...');
|
||||
if (reporting_timer_id) clearInterval(reporting_timer_id);
|
||||
reporting_timer_id = setInterval(report_meeting_stats, 17000); // Report every 30 seconds
|
||||
} else {
|
||||
console.log('Jitsi: User is not a moderator. Stats reporting will not start.');
|
||||
// --- CREATE INITIAL LOG ENTRY (if moderator) ---
|
||||
if (is_moderator && !primary_activity_log_id) {
|
||||
console.log('Jitsi: Moderator joined, creating initial activity log...');
|
||||
const participants_array = Array.from(meeting_participants.values());
|
||||
const data_kv = {
|
||||
external_client_id: jitsi_meeting_id,
|
||||
name: 'jitsi_meeting_stats',
|
||||
description: room_name,
|
||||
url_root: $page.url.origin,
|
||||
url_full_path: $page.url.pathname,
|
||||
url_params: $page.url.searchParams.toString(),
|
||||
action: 'jitsi_meeting_init',
|
||||
action_with: 'on_init',
|
||||
meta_json: {
|
||||
duration: meeting_duration,
|
||||
participants: participants_array,
|
||||
participant_count: participants_array.length
|
||||
}
|
||||
};
|
||||
try {
|
||||
const result = await create_ae_obj__activity_log({
|
||||
api_cfg: $ae_api,
|
||||
account_id: $ae_loc.account_id,
|
||||
data_kv: data_kv,
|
||||
log_lvl: log_lvl,
|
||||
});
|
||||
if (result && result.activity_log_id_random) {
|
||||
primary_activity_log_id = result.activity_log_id_random;
|
||||
console.log(`Jitsi: Initial activity log created: ${primary_activity_log_id}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Jitsi: Error creating initial activity log:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
api.on('participantJoined', (participant: { id: string; displayName: string }) => {
|
||||
console.log('Jitsi Event: participantJoined', participant);
|
||||
meeting_participants.set(participant.id, {
|
||||
id: participant.id,
|
||||
displayName: participant.displayName,
|
||||
role: 'participant'
|
||||
meeting_participants.set(participant.id, { id: participant.id, displayName: participant.displayName, role: 'participant' });
|
||||
update_primary_activity_log();
|
||||
|
||||
// NOTE: We also want to log this as a discrete event
|
||||
create_discrete_activity_log('jitsi_meeting_participant_joined', 'participantJoined', {
|
||||
attendee_id: participant.id,
|
||||
full_name: participant.displayName,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -163,20 +188,37 @@
|
||||
console.log('Jitsi Event: participantLeft', participant);
|
||||
if (meeting_participants.has(participant.id)) {
|
||||
meeting_participants.delete(participant.id);
|
||||
update_primary_activity_log();
|
||||
|
||||
// NOTE: We also want to log this as a discrete event
|
||||
create_discrete_activity_log('jitsi_meeting_participant_left', 'participantLeft', {
|
||||
attendee_id: participant.id,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
api.on(
|
||||
'participantRoleChanged',
|
||||
(participant: { id: string; role: string }) => {
|
||||
console.log('Jitsi Event: participantRoleChanged', participant);
|
||||
if (meeting_participants.has(participant.id)) {
|
||||
const p = meeting_participants.get(participant.id);
|
||||
p.role = participant.role;
|
||||
meeting_participants.set(participant.id, p);
|
||||
}
|
||||
api.on('participantRoleChanged', (participant: { id: string; role: string }) => {
|
||||
console.log('Jitsi Event: participantRoleChanged', participant);
|
||||
if (meeting_participants.has(participant.id)) {
|
||||
const p = meeting_participants.get(participant.id);
|
||||
p.role = participant.role;
|
||||
meeting_participants.set(participant.id, p);
|
||||
update_primary_activity_log();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// --- Discrete Event Logging ---
|
||||
// NOTE: This does not seem to be triggered.
|
||||
api.on('raiseHandUpdated', (participant: { id:string; raisesHand: boolean }) => {
|
||||
if (participant.raisesHand) {
|
||||
const p_info = meeting_participants.get(participant.id);
|
||||
console.log('Jitsi Event: raiseHandUpdated', p_info);
|
||||
create_discrete_activity_log('jitsi_meeting_raise_hand', 'raiseHandUpdated', {
|
||||
attendee_id: p_info.id,
|
||||
full_name: p_info.displayName,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function handle_profile_update() {
|
||||
@@ -334,7 +376,7 @@
|
||||
|
||||
onDestroy(() => {
|
||||
if (duration_timer_id) clearInterval(duration_timer_id);
|
||||
if (reporting_timer_id) clearInterval(reporting_timer_id);
|
||||
// No longer using reporting_timer_id
|
||||
if (jitsi_api) {
|
||||
console.log('Jitsi: Disposing of Jitsi API instance on component destroy.');
|
||||
jitsi_api.dispose();
|
||||
@@ -477,7 +519,7 @@
|
||||
async function init_jitsi() {
|
||||
// Clear stats and timers
|
||||
if (duration_timer_id) clearInterval(duration_timer_id);
|
||||
if (reporting_timer_id) clearInterval(reporting_timer_id);
|
||||
// No longer using reporting_timer_id
|
||||
meeting_participants.clear();
|
||||
meeting_start_time = null;
|
||||
meeting_duration = '00:00:00';
|
||||
@@ -554,9 +596,9 @@
|
||||
add_jitsi_event_listeners(jitsi_api);
|
||||
console.log('Jitsi: JitsiMeetExternalAPI initialized:', jitsi_api);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<svelte:head>
|
||||
<script src="https://jitsi.dgrzone.com/external_api.js"></script>
|
||||
</svelte:head>
|
||||
|
||||
Reference in New Issue
Block a user