Sfoglia il codice sorgente

Adds server-side speaker stats handling.

Adds the component which receives the messages from client and a module which enabled on a virtual host will start advertising the component. When clients discover the component they will send message to the component with the name of the room where the dominant speaker event happen.
j8
damencho 7 anni fa
parent
commit
3b4037553a

+ 22
- 0
doc/speakerstats-prosody.md Vedi File

@@ -0,0 +1,22 @@
1
+# Enabling speakerstats prosody module
2
+
3
+To enable the speaker stats we need to enable speakerstats module under the main
4
+virtual host, this is to enable the advertising the speaker stats component, 
5
+which address needs to be specified in `speakerstats_component` option.
6
+
7
+We need to also enable the component with the address specified in `speakerstats_component`.
8
+The component needs also to have the option with the muc component address in
9
+`muc_component` option.
10
+
11
+```lua
12
+VirtualHost "jitsi.example.com"
13
+    speakerstats_component = "speakerstats.jitsi.example.com"
14
+    modules_enabled = {
15
+        "speakerstats";
16
+    }
17
+
18
+Component "speakerstats.jitsi.example.com" "speakerstats_component"
19
+    muc_component = "conference.jitsi.example.com"
20
+
21
+Component "conference.jitsi.example.com" "muc"
22
+```

+ 18
- 1
resources/prosody-plugins/ext_events.lib.lua Vedi File

@@ -31,11 +31,28 @@ local function missed(stanza, call_id)
31 31
     module:log("warn", "Implement this lib to trigger external events.")
32 32
 end
33 33
 
34
+-- Event that speaker stats for a conference are available
35
+-- this is a table where key is the jid and the value is a table:
36
+--{
37
+--  totalDominantSpeakerTime
38
+--  nick
39
+--  displayName
40
+--}
41
+-- This trigger is left unimplemented. The implementation is expected
42
+-- to be specific to the deployment.
43
+local function speaker_stats(room, speakerStats)
44
+    module:log(
45
+        "warn",
46
+        "A module has been configured that triggers external events."
47
+    )
48
+    module:log("warn", "Implement this lib to trigger external events.")
49
+end
34 50
 
35 51
 local ext_events = {
36 52
     missed = missed,
37 53
     invite = invite,
38
-    cancel = cancel
54
+    cancel = cancel,
55
+    speaker_stats = speaker_stats
39 56
 }
40 57
 
41 58
 return ext_events

+ 7
- 0
resources/prosody-plugins/mod_speakerstats.lua Vedi File

@@ -0,0 +1,7 @@
1
+local speakerstats_component
2
+    = module:get_option_string(
3
+        "speakerstats_component", "speakerstats"..module.host);
4
+
5
+-- Advertise speaker stats so client can pick up the address and start sending
6
+-- dominant speaker events
7
+module:add_identity("component", "speakerstats", speakerstats_component);

+ 144
- 0
resources/prosody-plugins/mod_speakerstats_component.lua Vedi File

@@ -0,0 +1,144 @@
1
+local get_room_from_jid = module:require "util".get_room_from_jid;
2
+local jid_resource = require "util.jid".resource;
3
+local ext_events = module:require "ext_events"
4
+
5
+local muc_component_host = module:get_option_string("muc_component");
6
+if muc_component_host == nil then
7
+    log("error", "No muc_component specified. No muc to operate on!");
8
+    return;
9
+end
10
+local muc_module = module:context("conference."..muc_component_host);
11
+if muc_module == nil then
12
+    log("error", "No such muc found, check muc_component config.");
13
+    return;
14
+end
15
+
16
+log("debug", "Starting speakerstats for %s", muc_component_host);
17
+
18
+-- receives messages from client currently connected to the room
19
+-- clients indicates their own dominant speaker events
20
+function on_message(event)
21
+    -- Check the type of the incoming stanza to avoid loops:
22
+    if event.stanza.attr.type == "error" then
23
+        return; -- We do not want to reply to these, so leave.
24
+    end
25
+
26
+    local speakerStats
27
+        = event.stanza:get_child('speakerstats', 'http://jitsi.org/jitmeet');
28
+    if speakerStats then
29
+        local roomAddress = speakerStats.attr.room;
30
+        local room = get_room_from_jid(roomAddress);
31
+
32
+        if not room then
33
+            log("warn", "No room found %s", roomAddress);
34
+            return false;
35
+        end
36
+
37
+        local roomSpeakerStats = room.speakerStats;
38
+        local from = event.stanza.attr.from;
39
+
40
+        local occupant = room:get_occupant_by_real_jid(from);
41
+        if not occupant then
42
+            log("warn", "No occupant %s found for %s", from, roomAddress);
43
+            return false;
44
+        end
45
+
46
+        local newDominantSpeaker = roomSpeakerStats[occupant.jid];
47
+        local oldDominantSpeakerId = roomSpeakerStats['dominantSpeakerId'];
48
+
49
+        if oldDominantSpeakerId then
50
+            roomSpeakerStats[oldDominantSpeakerId]:setIsDominantSpeaker(false);
51
+        end
52
+
53
+        if newDominantSpeaker then
54
+            newDominantSpeaker:setIsDominantSpeaker(true);
55
+        end
56
+
57
+        room.speakerStats['dominantSpeakerId'] = occupant.jid;
58
+    end
59
+
60
+    return true
61
+end
62
+
63
+--- Start SpeakerStats implementation
64
+local SpeakerStats = {};
65
+SpeakerStats.__index = SpeakerStats;
66
+
67
+function new_SpeakerStats(nick)
68
+    return setmetatable({
69
+        totalDominantSpeakerTime = 0;
70
+        _dominantSpeakerStart = nil;
71
+        _isDominantSpeaker = false;
72
+        nick = nick;
73
+        displayName = nil;
74
+    }, SpeakerStats);
75
+end
76
+
77
+-- Changes the dominantSpeaker data for current occupant
78
+-- saves start time if it is new dominat speaker
79
+-- or calculates and accumulates time of speaking
80
+function SpeakerStats:setIsDominantSpeaker(isNowDominantSpeaker)
81
+    log("debug",
82
+        "set isDominant %s for %s", tostring(isNowDominantSpeaker), self.nick);
83
+
84
+    if not self._isDominantSpeaker and isNowDominantSpeaker then
85
+        self._dominantSpeakerStart = os.time();
86
+    elseif self._isDominantSpeaker and not isNowDominantSpeaker then
87
+        local now = os.time();
88
+        local timeElapsed = now - (self._dominantSpeakerStart or 0);
89
+
90
+        self.totalDominantSpeakerTime
91
+            = self.totalDominantSpeakerTime + timeElapsed;
92
+        self._dominantSpeakerStart = nil;
93
+    end
94
+
95
+    self._isDominantSpeaker = isNowDominantSpeaker;
96
+end
97
+--- End SpeakerStats
98
+
99
+-- create speakerStats for the room
100
+function room_created(event)
101
+    local room = event.room;
102
+    room.speakerStats = {};
103
+end
104
+
105
+-- Create SpeakerStats object for the joined user
106
+function occupant_joined(event)
107
+    local room = event.room;
108
+    local occupant = event.occupant;
109
+    local nick = jid_resource(occupant.nick);
110
+
111
+    if room.speakerStats then
112
+        room.speakerStats[occupant.jid] = new_SpeakerStats(nick);
113
+    end
114
+end
115
+
116
+-- Occupant left set its dominant speaker to false and update the store the
117
+-- display name
118
+function occupant_leaving(event)
119
+    local room = event.room;
120
+    local occupant = event.occupant;
121
+
122
+    local speakerStatsForOccupant = room.speakerStats[occupant.jid];
123
+    if speakerStatsForOccupant then
124
+        speakerStatsForOccupant:setIsDominantSpeaker(false);
125
+
126
+        -- set display name
127
+        local displayName = occupant:get_presence():get_child_text(
128
+            'nick', 'http://jabber.org/protocol/nick');
129
+        speakerStatsForOccupant.displayName = displayName;
130
+    end
131
+end
132
+
133
+-- Conference ended, send speaker stats
134
+function room_destroyed(event)
135
+    local room = event.room;
136
+
137
+    ext_events.speaker_stats(room, room.speakerStats);
138
+end
139
+
140
+module:hook("message/host", on_message);
141
+muc_module:hook("muc-room-created", room_created, -1);
142
+muc_module:hook("muc-occupant-joined", occupant_joined, -1);
143
+muc_module:hook("muc-occupant-pre-leave", occupant_leaving, -1);
144
+muc_module:hook("muc-room-destroyed", room_destroyed, -1);

Loading…
Annulla
Salva