Browse Source

feat(ts) migrate statistics/LocalStatsCollector to TS

master
Naman Jain 4 months ago
parent
commit
36ba9c6aef
No account linked to committer's email address
2 changed files with 187 additions and 179 deletions
  1. 0
    179
      modules/statistics/LocalStatsCollector.js
  2. 187
    0
      modules/statistics/LocalStatsCollector.ts

+ 0
- 179
modules/statistics/LocalStatsCollector.js View File

@@ -1,179 +0,0 @@
1
-/**
2
- * Provides statistics for the local stream.
3
- */
4
-
5
-const logger = require('@jitsi/logger').getLogger('modules/statistics/LocalStatsCollector');
6
-
7
-/**
8
- * Size of the webaudio analyzer buffer.
9
- * @type {number}
10
- */
11
-const WEBAUDIO_ANALYZER_FFT_SIZE = 2048;
12
-
13
-/**
14
- * Value of the webaudio analyzer smoothing time parameter.
15
- * @type {number}
16
- */
17
-const WEBAUDIO_ANALYZER_SMOOTING_TIME = 0.8;
18
-
19
-window.AudioContext = window.AudioContext || window.webkitAudioContext;
20
-
21
-/**
22
- * The audio context.
23
- * @type {AudioContext}
24
- */
25
-let context = null;
26
-
27
-
28
-/**
29
- * Converts time domain data array to audio level.
30
- * @param samples the time domain data array.
31
- * @returns {number} the audio level
32
- */
33
-function timeDomainDataToAudioLevel(samples) {
34
-
35
-    let maxVolume = 0;
36
-
37
-    const length = samples.length;
38
-
39
-    for (let i = 0; i < length; i++) {
40
-        if (maxVolume < samples[i]) {
41
-            maxVolume = samples[i];
42
-        }
43
-    }
44
-
45
-    return parseFloat(((maxVolume - 127) / 128).toFixed(3));
46
-}
47
-
48
-/**
49
- * Animates audio level change
50
- * @param newLevel the new audio level
51
- * @param lastLevel the last audio level
52
- * @returns {Number} the audio level to be set
53
- */
54
-function animateLevel(newLevel, lastLevel) {
55
-    let value = 0;
56
-    const diff = lastLevel - newLevel;
57
-
58
-    if (diff > 0.2) {
59
-        value = lastLevel - 0.2;
60
-    } else if (diff < -0.4) {
61
-        value = lastLevel + 0.4;
62
-    } else {
63
-        value = newLevel;
64
-    }
65
-
66
-    return parseFloat(value.toFixed(3));
67
-}
68
-
69
-
70
-/**
71
- * <tt>LocalStatsCollector</tt> calculates statistics for the local stream.
72
- *
73
- * @param stream the local stream
74
- * @param interval stats refresh interval given in ms.
75
- * @param callback function that receives the audio levels.
76
- * @constructor
77
- */
78
-export default function LocalStatsCollector(stream, interval, callback) {
79
-    this.stream = stream;
80
-    this.intervalId = null;
81
-    this.intervalMilis = interval;
82
-    this.audioLevel = 0;
83
-    this.callback = callback;
84
-    this.source = null;
85
-    this.analyser = null;
86
-}
87
-
88
-/**
89
- * Starts the collecting the statistics.
90
- */
91
-LocalStatsCollector.prototype.start = function() {
92
-    if (!LocalStatsCollector.isLocalStatsSupported()) {
93
-        return;
94
-    }
95
-
96
-    context.resume();
97
-    this.analyser = context.createAnalyser();
98
-
99
-    this.analyser.smoothingTimeConstant = WEBAUDIO_ANALYZER_SMOOTING_TIME;
100
-    this.analyser.fftSize = WEBAUDIO_ANALYZER_FFT_SIZE;
101
-
102
-    this.source = context.createMediaStreamSource(this.stream);
103
-
104
-    this.source.connect(this.analyser);
105
-
106
-    this.intervalId = setInterval(
107
-        () => {
108
-            const array = new Uint8Array(this.analyser.frequencyBinCount);
109
-
110
-            this.analyser.getByteTimeDomainData(array);
111
-            const audioLevel = timeDomainDataToAudioLevel(array);
112
-
113
-            // Set the audio levels always as NoAudioSignalDetection now
114
-            // uses audio levels from LocalStatsCollector and waits for
115
-            // atleast 4 secs for a no audio signal before displaying the
116
-            // notification on the UI.
117
-            this.audioLevel = animateLevel(audioLevel, this.audioLevel);
118
-            this.callback(this.audioLevel);
119
-        },
120
-        this.intervalMilis
121
-    );
122
-};
123
-
124
-/**
125
- * Stops collecting the statistics.
126
- */
127
-LocalStatsCollector.prototype.stop = function() {
128
-    if (this.intervalId) {
129
-        clearInterval(this.intervalId);
130
-        this.intervalId = null;
131
-    }
132
-
133
-    this.analyser?.disconnect();
134
-    this.analyser = null;
135
-    this.source?.disconnect();
136
-    this.source = null;
137
-};
138
-
139
-/**
140
- * Initialize collector.
141
- */
142
-LocalStatsCollector.init = function() {
143
-    LocalStatsCollector.connectAudioContext();
144
-};
145
-
146
-/**
147
- * Checks if the environment has the necessary conditions to support
148
- * collecting stats from local streams.
149
- *
150
- * @returns {boolean}
151
- */
152
-LocalStatsCollector.isLocalStatsSupported = function() {
153
-    return Boolean(window?.AudioContext);
154
-};
155
-
156
-/**
157
- * Disconnects the audio context.
158
- */
159
-LocalStatsCollector.disconnectAudioContext = async function() {
160
-    if (context) {
161
-        logger.info('Disconnecting audio context');
162
-        await context.close();
163
-        context = null;
164
-    }
165
-};
166
-
167
-/**
168
- * Connects the audio context.
169
- */
170
-LocalStatsCollector.connectAudioContext = function() {
171
-    if (!LocalStatsCollector.isLocalStatsSupported()) {
172
-        return;
173
-    }
174
-
175
-    logger.info('Connecting audio context');
176
-    context = new AudioContext();
177
-
178
-    context.suspend();
179
-};

+ 187
- 0
modules/statistics/LocalStatsCollector.ts View File

@@ -0,0 +1,187 @@
1
+import { getLogger } from '@jitsi/logger';
2
+
3
+const logger = getLogger('modules/statistics/LocalStatsCollector');
4
+/**
5
+ * Size of the webaudio analyzer buffer.
6
+ * @type {number}
7
+ */
8
+const WEBAUDIO_ANALYZER_FFT_SIZE: number = 2048;
9
+
10
+/**
11
+ * Value of the webaudio analyzer smoothing time parameter.
12
+ * @type {number}
13
+ */
14
+const WEBAUDIO_ANALYZER_SMOOTING_TIME: number = 0.8;
15
+
16
+/**
17
+ * The audio context.
18
+ * @type {AudioContext}
19
+ */
20
+let context: AudioContext | null = null;
21
+
22
+/**
23
+ * Converts time domain data array to audio level.
24
+ * @param samples the time domain data array.
25
+ * @returns {number} the audio level
26
+ */
27
+function timeDomainDataToAudioLevel(samples: Uint8Array): number {
28
+    let maxVolume = 0;
29
+    const length = samples.length;
30
+
31
+    for (let i = 0; i < length; i++) {
32
+        if (maxVolume < samples[i]) {
33
+            maxVolume = samples[i];
34
+        }
35
+    }
36
+
37
+    return Number.parseFloat(((maxVolume - 127) / 128).toFixed(3));
38
+}
39
+
40
+/**
41
+ * Animates audio level change
42
+ * @param newLevel the new audio level
43
+ * @param lastLevel the last audio level
44
+ * @returns {Number} the audio level to be set
45
+ */
46
+function animateLevel(newLevel: number, lastLevel: number): number {
47
+    let value = 0;
48
+    const diff = lastLevel - newLevel;
49
+
50
+    if (diff > 0.2) {
51
+        value = lastLevel - 0.2;
52
+    } else if (diff < -0.4) {
53
+        value = lastLevel + 0.4;
54
+    } else {
55
+        value = newLevel;
56
+    }
57
+
58
+    return Number.parseFloat(value.toFixed(3));
59
+}
60
+
61
+/**
62
+ * Provides statistics for the local stream.
63
+ */
64
+export default class LocalStatsCollector {
65
+    stream: MediaStream;
66
+    intervalId: Timeout | null;
67
+    intervalMilis: number;
68
+    audioLevel: number;
69
+    callback: (audioLevel: number) => void;
70
+    source: MediaStreamAudioSourceNode | null;
71
+    analyser: AnalyserNode | null;
72
+
73
+    /**
74
+     * Creates a new instance of LocalStatsCollector.
75
+     *
76
+     * @param {MediaStream} stream - the local stream
77
+     * @param {number} interval - stats refresh interval given in ms.
78
+     * @param {Function} callback - function that receives the audio levels.
79
+     * @constructor
80
+     */
81
+    constructor(
82
+            stream: MediaStream,
83
+            interval: number,
84
+            callback: (audioLevel: number) => void
85
+    ) {
86
+        this.stream = stream;
87
+        this.intervalId = null;
88
+        this.intervalMilis = interval;
89
+        this.audioLevel = 0;
90
+        this.callback = callback;
91
+        this.source = null;
92
+        this.analyser = null;
93
+    }
94
+
95
+    /**
96
+     * Starts the collecting the statistics.
97
+     */
98
+    start(): void {
99
+        if (!LocalStatsCollector.isLocalStatsSupported()) {
100
+            return;
101
+        }
102
+
103
+        context!.resume();
104
+        this.analyser = context!.createAnalyser();
105
+
106
+        this.analyser.smoothingTimeConstant = WEBAUDIO_ANALYZER_SMOOTING_TIME;
107
+        this.analyser.fftSize = WEBAUDIO_ANALYZER_FFT_SIZE;
108
+
109
+        this.source = context!.createMediaStreamSource(this.stream);
110
+
111
+        this.source.connect(this.analyser);
112
+
113
+        this.intervalId = setInterval(
114
+            () => {
115
+                const array = new Uint8Array(this.analyser!.frequencyBinCount);
116
+
117
+                this.analyser!.getByteTimeDomainData(array);
118
+                const audioLevel = timeDomainDataToAudioLevel(array);
119
+
120
+                // Set the audio levels always as NoAudioSignalDetection now
121
+                // uses audio levels from LocalStatsCollector and waits for
122
+                // atleast 4 secs for a no audio signal before displaying the
123
+                // notification on the UI.
124
+                this.audioLevel = animateLevel(audioLevel, this.audioLevel);
125
+                this.callback(this.audioLevel);
126
+            },
127
+            this.intervalMilis
128
+        );
129
+    }
130
+
131
+    /**
132
+     * Stops collecting the statistics.
133
+     */
134
+    stop(): void {
135
+        if (this.intervalId) {
136
+            clearInterval(this.intervalId);
137
+            this.intervalId = null;
138
+        }
139
+
140
+        this.analyser?.disconnect();
141
+        this.analyser = null;
142
+        this.source?.disconnect();
143
+        this.source = null;
144
+    }
145
+
146
+    /**
147
+     * Initialize collector.
148
+     */
149
+    static init(): void {
150
+        LocalStatsCollector.connectAudioContext();
151
+    }
152
+
153
+    /**
154
+     * Checks if the environment has the necessary conditions to support
155
+     * collecting stats from local streams.
156
+     *
157
+     * @returns {boolean}
158
+     */
159
+    static isLocalStatsSupported(): boolean {
160
+        return Boolean(window?.AudioContext);
161
+    }
162
+
163
+    /**
164
+     * Disconnects the audio context.
165
+     */
166
+    static async disconnectAudioContext(): Promise<void> {
167
+        if (context) {
168
+            logger.info('Disconnecting audio context');
169
+            await context.close();
170
+            context = null;
171
+        }
172
+    }
173
+
174
+    /**
175
+     * Connects the audio context.
176
+     */
177
+    static connectAudioContext(): void {
178
+        if (!LocalStatsCollector.isLocalStatsSupported()) {
179
+            return;
180
+        }
181
+
182
+        logger.info('Connecting audio context');
183
+        context = new AudioContext();
184
+
185
+        context.suspend();
186
+    }
187
+}

Loading…
Cancel
Save