initial commit
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
local get_room_from_jid = module:require "util".get_room_from_jid;
|
||||
local room_jid_match_rewrite = module:require "util".room_jid_match_rewrite;
|
||||
local is_healthcheck_room = module:require "util".is_healthcheck_room;
|
||||
local jid_resource = require "util.jid".resource;
|
||||
local ext_events = module:require "ext_events"
|
||||
local st = require "util.stanza";
|
||||
local socket = require "socket";
|
||||
local json = require "util.json";
|
||||
|
||||
-- we use async to detect Prosody 0.10 and earlier
|
||||
local have_async = pcall(require, "util.async");
|
||||
if not have_async then
|
||||
module:log("warn", "speaker stats will not work with Prosody version 0.10 or less.");
|
||||
return;
|
||||
end
|
||||
|
||||
local muc_component_host = module:get_option_string("muc_component");
|
||||
if muc_component_host == nil then
|
||||
log("error", "No muc_component specified. No muc to operate on!");
|
||||
return;
|
||||
end
|
||||
|
||||
log("info", "Starting speakerstats for %s", muc_component_host);
|
||||
|
||||
-- receives messages from client currently connected to the room
|
||||
-- clients indicates their own dominant speaker events
|
||||
function on_message(event)
|
||||
-- Check the type of the incoming stanza to avoid loops:
|
||||
if event.stanza.attr.type == "error" then
|
||||
return; -- We do not want to reply to these, so leave.
|
||||
end
|
||||
|
||||
local speakerStats
|
||||
= event.stanza:get_child('speakerstats', 'http://jitsi.org/jitmeet');
|
||||
if speakerStats then
|
||||
local roomAddress = speakerStats.attr.room;
|
||||
local room = get_room_from_jid(room_jid_match_rewrite(roomAddress));
|
||||
|
||||
if not room then
|
||||
log("warn", "No room found %s", roomAddress);
|
||||
return false;
|
||||
end
|
||||
|
||||
local roomSpeakerStats = room.speakerStats;
|
||||
local from = event.stanza.attr.from;
|
||||
|
||||
local occupant = room:get_occupant_by_real_jid(from);
|
||||
if not occupant then
|
||||
log("warn", "No occupant %s found for %s", from, roomAddress);
|
||||
return false;
|
||||
end
|
||||
|
||||
local newDominantSpeaker = roomSpeakerStats[occupant.jid];
|
||||
local oldDominantSpeakerId = roomSpeakerStats['dominantSpeakerId'];
|
||||
|
||||
if oldDominantSpeakerId then
|
||||
local oldDominantSpeaker = roomSpeakerStats[oldDominantSpeakerId];
|
||||
if oldDominantSpeaker then
|
||||
oldDominantSpeaker:setDominantSpeaker(false);
|
||||
end
|
||||
end
|
||||
|
||||
if newDominantSpeaker then
|
||||
newDominantSpeaker:setDominantSpeaker(true);
|
||||
end
|
||||
|
||||
room.speakerStats['dominantSpeakerId'] = occupant.jid;
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- Start SpeakerStats implementation
|
||||
local SpeakerStats = {};
|
||||
SpeakerStats.__index = SpeakerStats;
|
||||
|
||||
function new_SpeakerStats(nick, context_user)
|
||||
return setmetatable({
|
||||
totalDominantSpeakerTime = 0;
|
||||
_dominantSpeakerStart = 0;
|
||||
nick = nick;
|
||||
context_user = context_user;
|
||||
displayName = nil;
|
||||
}, SpeakerStats);
|
||||
end
|
||||
|
||||
-- Changes the dominantSpeaker data for current occupant
|
||||
-- saves start time if it is new dominat speaker
|
||||
-- or calculates and accumulates time of speaking
|
||||
function SpeakerStats:setDominantSpeaker(isNowDominantSpeaker)
|
||||
log("debug",
|
||||
"set isDominant %s for %s", tostring(isNowDominantSpeaker), self.nick);
|
||||
|
||||
if not self:isDominantSpeaker() and isNowDominantSpeaker then
|
||||
self._dominantSpeakerStart = socket.gettime()*1000;
|
||||
elseif self:isDominantSpeaker() and not isNowDominantSpeaker then
|
||||
local now = socket.gettime()*1000;
|
||||
local timeElapsed = math.floor(now - self._dominantSpeakerStart);
|
||||
|
||||
self.totalDominantSpeakerTime
|
||||
= self.totalDominantSpeakerTime + timeElapsed;
|
||||
self._dominantSpeakerStart = 0;
|
||||
end
|
||||
end
|
||||
|
||||
-- Returns true if the tracked user is currently a dominant speaker.
|
||||
function SpeakerStats:isDominantSpeaker()
|
||||
return self._dominantSpeakerStart > 0;
|
||||
end
|
||||
--- End SpeakerStats
|
||||
|
||||
-- create speakerStats for the room
|
||||
function room_created(event)
|
||||
local room = event.room;
|
||||
|
||||
if is_healthcheck_room(room.jid) then
|
||||
return;
|
||||
end
|
||||
|
||||
room.speakerStats = {};
|
||||
end
|
||||
|
||||
-- Create SpeakerStats object for the joined user
|
||||
function occupant_joined(event)
|
||||
local room = event.room;
|
||||
|
||||
if is_healthcheck_room(room.jid) then
|
||||
return;
|
||||
end
|
||||
|
||||
local occupant = event.occupant;
|
||||
|
||||
local nick = jid_resource(occupant.nick);
|
||||
|
||||
if room.speakerStats then
|
||||
-- lets send the current speaker stats to that user, so he can update
|
||||
-- its local stats
|
||||
if next(room.speakerStats) ~= nil then
|
||||
local users_json = {};
|
||||
for jid, values in pairs(room.speakerStats) do
|
||||
-- skip reporting those without a nick('dominantSpeakerId')
|
||||
-- and skip focus if sneaked into the table
|
||||
if values.nick ~= nil and values.nick ~= 'focus' then
|
||||
local resultSpeakerStats = {};
|
||||
local totalDominantSpeakerTime
|
||||
= values.totalDominantSpeakerTime;
|
||||
|
||||
-- before sending we need to calculate current dominant speaker
|
||||
-- state
|
||||
if values:isDominantSpeaker() then
|
||||
local timeElapsed = math.floor(
|
||||
socket.gettime()*1000 - values._dominantSpeakerStart);
|
||||
totalDominantSpeakerTime = totalDominantSpeakerTime
|
||||
+ timeElapsed;
|
||||
end
|
||||
|
||||
resultSpeakerStats.displayName = values.displayName;
|
||||
resultSpeakerStats.totalDominantSpeakerTime
|
||||
= totalDominantSpeakerTime;
|
||||
users_json[values.nick] = resultSpeakerStats;
|
||||
end
|
||||
end
|
||||
|
||||
local body_json = {};
|
||||
body_json.type = 'speakerstats';
|
||||
body_json.users = users_json;
|
||||
|
||||
local stanza = st.message({
|
||||
from = module.host;
|
||||
to = occupant.jid; })
|
||||
:tag("json-message", {xmlns='http://jitsi.org/jitmeet'})
|
||||
:text(json.encode(body_json)):up();
|
||||
|
||||
room:route_stanza(stanza);
|
||||
end
|
||||
|
||||
local context_user = event.origin and event.origin.jitsi_meet_context_user or nil;
|
||||
room.speakerStats[occupant.jid] = new_SpeakerStats(nick, context_user);
|
||||
end
|
||||
end
|
||||
|
||||
-- Occupant left set its dominant speaker to false and update the store the
|
||||
-- display name
|
||||
function occupant_leaving(event)
|
||||
local room = event.room;
|
||||
|
||||
if is_healthcheck_room(room.jid) then
|
||||
return;
|
||||
end
|
||||
|
||||
local occupant = event.occupant;
|
||||
|
||||
local speakerStatsForOccupant = room.speakerStats[occupant.jid];
|
||||
if speakerStatsForOccupant then
|
||||
speakerStatsForOccupant:setDominantSpeaker(false);
|
||||
|
||||
-- set display name
|
||||
local displayName = occupant:get_presence():get_child_text(
|
||||
'nick', 'http://jabber.org/protocol/nick');
|
||||
speakerStatsForOccupant.displayName = displayName;
|
||||
end
|
||||
end
|
||||
|
||||
-- Conference ended, send speaker stats
|
||||
function room_destroyed(event)
|
||||
local room = event.room;
|
||||
|
||||
if is_healthcheck_room(room.jid) then
|
||||
return;
|
||||
end
|
||||
|
||||
ext_events.speaker_stats(room, room.speakerStats);
|
||||
end
|
||||
|
||||
module:hook("message/host", on_message);
|
||||
|
||||
-- executed on every host added internally in prosody, including components
|
||||
function process_host(host)
|
||||
if host == muc_component_host then -- the conference muc component
|
||||
module:log("info","Hook to muc events on %s", host);
|
||||
|
||||
local muc_module = module:context(host);
|
||||
muc_module:hook("muc-room-created", room_created, -1);
|
||||
muc_module:hook("muc-occupant-joined", occupant_joined, -1);
|
||||
muc_module:hook("muc-occupant-pre-leave", occupant_leaving, -1);
|
||||
muc_module:hook("muc-room-destroyed", room_destroyed, -1);
|
||||
end
|
||||
end
|
||||
|
||||
if prosody.hosts[muc_component_host] == nil then
|
||||
module:log("info","No muc component found, will listen for it: %s", muc_component_host)
|
||||
|
||||
-- when a host or component is added
|
||||
prosody.events.add_handler("host-activated", process_host);
|
||||
else
|
||||
process_host(muc_component_host);
|
||||
end
|
||||
Reference in New Issue
Block a user