|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+import * as JitsiConferenceEvents from '../JitsiConferenceEvents';
|
|
|
2
|
+
|
|
|
3
|
+// We wait a certain time interval for constant silence input from the current device to account for
|
|
|
4
|
+// potential abnormalities and for a better use experience i.e. don't generate event the instant
|
|
|
5
|
+// an audio track is added to the tcr.
|
|
|
6
|
+// Potential improvement - add this as a configurable parameter.
|
|
|
7
|
+const SILENCE_PERIOD_SEC = 4;
|
|
|
8
|
+
|
|
|
9
|
+/**
|
|
|
10
|
+ * Detect if there is no audio input on the current TraceAblePeerConnection selected track. The no audio
|
|
|
11
|
+ * state must be constant for a configured amount of time in order for the event to be triggered.
|
|
|
12
|
+ */
|
|
|
13
|
+export default class NoAudioSignalDetection {
|
|
|
14
|
+ /**
|
|
|
15
|
+ * @param conference the JitsiConference instance that created us.
|
|
|
16
|
+ * @param callback callback that notifies the conference when no audio event is triggered
|
|
|
17
|
+ * @constructor
|
|
|
18
|
+ */
|
|
|
19
|
+ constructor(conference, callback) {
|
|
|
20
|
+ this._conference = conference;
|
|
|
21
|
+ this._callback = callback;
|
|
|
22
|
+ this._firstSilentSignalDate = null;
|
|
|
23
|
+
|
|
|
24
|
+ conference.statistics.addAudioLevelListener(this._audioLevel.bind(this));
|
|
|
25
|
+ conference.on(JitsiConferenceEvents.TRACK_ADDED, this._trackAdded.bind(this));
|
|
|
26
|
+
|
|
|
27
|
+ }
|
|
|
28
|
+
|
|
|
29
|
+ /**
|
|
|
30
|
+ * Checks if the configured period in which no audio was received has elapsed.
|
|
|
31
|
+ *
|
|
|
32
|
+ * @returns {boolean}
|
|
|
33
|
+ */
|
|
|
34
|
+ _hasSilencePeriodElapsed() {
|
|
|
35
|
+
|
|
|
36
|
+ const currentDate = new Date();
|
|
|
37
|
+ const elapsedSec = (currentDate.getTime() - this._firstSilentSignalDate.getTime()) / 1000;
|
|
|
38
|
+
|
|
|
39
|
+ if (elapsedSec > SILENCE_PERIOD_SEC) {
|
|
|
40
|
+ return true;
|
|
|
41
|
+ }
|
|
|
42
|
+
|
|
|
43
|
+ return false;
|
|
|
44
|
+ }
|
|
|
45
|
+
|
|
|
46
|
+ /**
|
|
|
47
|
+ * Trigger the set callback for no audio input if expected conditions are met.
|
|
|
48
|
+ */
|
|
|
49
|
+ _triggerNoAudioCallback() {
|
|
|
50
|
+ // In case this is the first time 0 audio level was detected initialize the interval check start
|
|
|
51
|
+ // date
|
|
|
52
|
+ if (!this._firstSilentSignalDate) {
|
|
|
53
|
+ this._firstSilentSignalDate = new Date();
|
|
|
54
|
+
|
|
|
55
|
+ // If the configured interval has elapsed trigger the callback
|
|
|
56
|
+ } else if (this._hasSilencePeriodElapsed()) {
|
|
|
57
|
+ this._eventFired = true;
|
|
|
58
|
+ this._callback();
|
|
|
59
|
+ }
|
|
|
60
|
+ }
|
|
|
61
|
+
|
|
|
62
|
+ /**
|
|
|
63
|
+ * Receives audio level events for all send and receive streams on the current TraceablePeerConnection.
|
|
|
64
|
+ *
|
|
|
65
|
+ * @param {TraceablePeerConnection} tpc - TraceablePeerConnection of the owning conference.
|
|
|
66
|
+ * @param {number} ssrc - The synchronization source identifier (SSRC) of the endpoint/participant/stream
|
|
|
67
|
+ * being reported.
|
|
|
68
|
+ * @param {number} audioLevel - The audio level of the ssrc.
|
|
|
69
|
+ * @param {boolean} isLocal - true for local/send streams or false for remote/receive streams.
|
|
|
70
|
+ */
|
|
|
71
|
+ _audioLevel(tpc, ssrc, audioLevel, isLocal) {
|
|
|
72
|
+
|
|
|
73
|
+ // We are interested in the local audio stream if the event was not triggered on this device.
|
|
|
74
|
+ if (!isLocal || !this._audioTrack || this._eventFired) {
|
|
|
75
|
+ return;
|
|
|
76
|
+ }
|
|
|
77
|
+
|
|
|
78
|
+ // Get currently active local tracks from the TraceablePeerConnection
|
|
|
79
|
+ const localSSRCs = tpc.localSSRCs.get(this._audioTrack.rtcId);
|
|
|
80
|
+
|
|
|
81
|
+ // Check that currently selected audio stream has ssrc in the TraceablePeerConnection
|
|
|
82
|
+ if (!localSSRCs) {
|
|
|
83
|
+ return;
|
|
|
84
|
+ }
|
|
|
85
|
+
|
|
|
86
|
+ // Only target the current active track in the tpc. For some reason audio levels for previous
|
|
|
87
|
+ // devices are also picked up from the PeerConnection so we filter them out.
|
|
|
88
|
+ const isCurrentTrack = localSSRCs.ssrcs.includes(ssrc);
|
|
|
89
|
+
|
|
|
90
|
+ if (!isCurrentTrack) {
|
|
|
91
|
+ return;
|
|
|
92
|
+ }
|
|
|
93
|
+
|
|
|
94
|
+ if (audioLevel === 0) {
|
|
|
95
|
+ this._triggerNoAudioCallback();
|
|
|
96
|
+ } else {
|
|
|
97
|
+ // Reset the period start date in order to check for consistent silence over the configured
|
|
|
98
|
+ // time interval.
|
|
|
99
|
+ this._firstSilentSignalDate = null;
|
|
|
100
|
+ }
|
|
|
101
|
+ }
|
|
|
102
|
+
|
|
|
103
|
+ /**
|
|
|
104
|
+ * Determines if a specific JitsiTrack is a local audio track.
|
|
|
105
|
+ *
|
|
|
106
|
+ * @param {JitsiTrack} track - The JitsiTrack to be checked whether it represents a local audio track.
|
|
|
107
|
+ * @return {boolean} - true if track represents a local audio track, false otherwise.
|
|
|
108
|
+ */
|
|
|
109
|
+ _isLocalAudioTrack(track) {
|
|
|
110
|
+ return track.isAudioTrack() && track.isLocal();
|
|
|
111
|
+ }
|
|
|
112
|
+
|
|
|
113
|
+ /**
|
|
|
114
|
+ * Notifies NoAudioSignalDetection that a JitsiTrack was added to the associated JitsiConference.
|
|
|
115
|
+ * Only take into account local audio tracks.
|
|
|
116
|
+ *
|
|
|
117
|
+ * @param {JitsiTrack} track - The added JitsiTrack.
|
|
|
118
|
+ */
|
|
|
119
|
+ _trackAdded(track) {
|
|
|
120
|
+ if (this._isLocalAudioTrack(track)) {
|
|
|
121
|
+ // Reset state for the new track.
|
|
|
122
|
+ this._firstSilentSignalDate = null;
|
|
|
123
|
+ this._audioTrack = track;
|
|
|
124
|
+ this._eventFired = false;
|
|
|
125
|
+ }
|
|
|
126
|
+ }
|
|
|
127
|
+}
|