@livedigital/client 2.43.0 → 2.44.0-audio-observer.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constants/events.d.ts +6 -0
- package/dist/engine/DefaultEngineDependenciesFactory.d.ts +2 -0
- package/dist/engine/handlers/ChannelAudioObserverEventHandler.d.ts +17 -0
- package/dist/engine/index.d.ts +7 -0
- package/dist/engine/media/tracks/PeerTrack.d.ts +2 -0
- package/dist/engine/network/index.d.ts +5 -6
- package/dist/errors/InvalidPayloadError.d.ts +5 -0
- package/dist/errors/NeedJoinFirstError.d.ts +5 -0
- package/dist/index.es.js +11 -11
- package/dist/index.js +11 -11
- package/dist/types/channelAudioObserver.d.ts +28 -0
- package/dist/types/common.d.ts +10 -1
- package/dist/types/engine.d.ts +6 -0
- package/package.json +1 -1
- package/src/constants/events.ts +7 -0
- package/src/engine/DefaultEngineDependenciesFactory.ts +5 -0
- package/src/engine/handlers/ChannelAudioObserverEventHandler.ts +129 -0
- package/src/engine/index.ts +62 -0
- package/src/engine/media/tracks/PeerTrack.ts +16 -1
- package/src/engine/network/index.ts +69 -37
- package/src/errors/InvalidPayloadError.ts +9 -0
- package/src/errors/NeedJoinFirstError.ts +9 -0
- package/src/types/channelAudioObserver.ts +34 -0
- package/src/types/common.ts +12 -1
- package/src/types/engine.ts +7 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { TrackLabel } from './common';
|
|
2
|
+
export declare enum ChannelAudioObserverEvents {
|
|
3
|
+
DominantSpeaker = "dominant-speaker",
|
|
4
|
+
PeersVolumes = "peers-volumes",
|
|
5
|
+
Silence = "silence"
|
|
6
|
+
}
|
|
7
|
+
export declare type DominantSpeakerEvent = {
|
|
8
|
+
event: ChannelAudioObserverEvents.DominantSpeaker;
|
|
9
|
+
data: {
|
|
10
|
+
peerId: string;
|
|
11
|
+
producerId: string;
|
|
12
|
+
trackLabel: TrackLabel.Microphone;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export declare type SilenceEvent = {
|
|
16
|
+
event: ChannelAudioObserverEvents.Silence;
|
|
17
|
+
};
|
|
18
|
+
export declare type PeerVolume = {
|
|
19
|
+
peerId: string;
|
|
20
|
+
producerId: string;
|
|
21
|
+
value: number;
|
|
22
|
+
trackLabel: TrackLabel.Microphone;
|
|
23
|
+
};
|
|
24
|
+
export declare type PeersVolumesEvent = {
|
|
25
|
+
event: ChannelAudioObserverEvents.PeersVolumes;
|
|
26
|
+
volumes: PeerVolume[];
|
|
27
|
+
};
|
|
28
|
+
export declare type AudioObserverEvents = DominantSpeakerEvent | SilenceEvent | PeersVolumesEvent;
|
package/dist/types/common.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import { MediaKind, RtpEncodingParameters, RtpParameters } from 'mediasoup-client/lib/types';
|
|
3
|
-
import { ConsumerOptions } from 'mediasoup-client/lib/Consumer';
|
|
3
|
+
import { Consumer, ConsumerOptions } from 'mediasoup-client/lib/Consumer';
|
|
4
4
|
import { RtpCapabilities } from 'mediasoup-client/src/RtpParameters';
|
|
5
5
|
import { ProducerCodecOptions } from 'mediasoup-client/lib/Producer';
|
|
6
6
|
import { ConnectionState } from 'mediasoup-client/src/Transport';
|
|
@@ -232,6 +232,15 @@ export declare type CreateConsumerPayload = {
|
|
|
232
232
|
channelId?: string;
|
|
233
233
|
producerPeerId: string;
|
|
234
234
|
};
|
|
235
|
+
export declare type CreateDataConsumerPayload = {
|
|
236
|
+
producerId: string;
|
|
237
|
+
appId?: string;
|
|
238
|
+
channelId?: string;
|
|
239
|
+
};
|
|
240
|
+
export declare type CreateConsumerResponse = {
|
|
241
|
+
consumer: Consumer;
|
|
242
|
+
isProducerPaused: boolean;
|
|
243
|
+
};
|
|
235
244
|
export declare enum DeviceErrors {
|
|
236
245
|
NotFoundError = "NotFoundError",
|
|
237
246
|
NoDevices = "NoDevices",
|
package/dist/types/engine.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import ChannelEventHandler from '../engine/handlers/ChannelEventHandler';
|
|
|
8
8
|
import MediaSoupEventHandler from '../engine/handlers/MediaSoupEventHandler';
|
|
9
9
|
import { LoadBalancerApiClientParams } from '../engine/network/LoadBalancerClient';
|
|
10
10
|
import { Logger, LogLevel, LogMessageHandler } from './common';
|
|
11
|
+
import ChannelAudioObserverEventHandler from '../engine/handlers/ChannelAudioObserverEventHandler';
|
|
11
12
|
export declare type IssuesHandler = (issues: IssueDetectorResult) => void;
|
|
12
13
|
export declare type NetworkScoresUpdatedHandler = (networks: NetworkScores) => void;
|
|
13
14
|
export interface CreateIssueDetectorParams {
|
|
@@ -44,11 +45,16 @@ export interface CreateChannelEventHandlerParams {
|
|
|
44
45
|
engine: Engine;
|
|
45
46
|
onLogMessage?: LogMessageHandler;
|
|
46
47
|
}
|
|
48
|
+
export interface CreateAudioObserverEventHandlerParams {
|
|
49
|
+
engine: Engine;
|
|
50
|
+
onLogMessage?: LogMessageHandler;
|
|
51
|
+
}
|
|
47
52
|
export interface EngineDependenciesFactory {
|
|
48
53
|
createSystem: (params: CreateSystemParams) => System;
|
|
49
54
|
createMedia: (params: CreateMediaParams) => Media;
|
|
50
55
|
createNetwork: (params: CreateNetworkParams) => Network;
|
|
51
56
|
createChannelEventHandler: (params: CreateChannelEventHandlerParams) => ChannelEventHandler;
|
|
52
57
|
createMediaSoupEventHandler: (params: CreateMediaSoupEventHandlerParams) => MediaSoupEventHandler;
|
|
58
|
+
createAudioObserverEventHandler: (params: CreateAudioObserverEventHandlerParams) => ChannelAudioObserverEventHandler;
|
|
53
59
|
createIssueDetector: (params: CreateIssueDetectorParams) => WebRTCIssueDetector;
|
|
54
60
|
}
|
package/package.json
CHANGED
package/src/constants/events.ts
CHANGED
|
@@ -8,6 +8,7 @@ export const CHANNEL_EVENTS = {
|
|
|
8
8
|
activityConfirmationAcquired: 'channel.activityConfirmationAcquired',
|
|
9
9
|
activityConfirmationRequired: 'channel.activityConfirmationRequired',
|
|
10
10
|
activityConfirmationExpired: 'channel.activityConfirmationExpired',
|
|
11
|
+
getAudioObserverProducer: 'channel.getAudioObserverProducer',
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
export const CLIENT_EVENTS = {
|
|
@@ -21,6 +22,11 @@ export const CLIENT_EVENTS = {
|
|
|
21
22
|
devicesListUpdated: 'devices-list-updated',
|
|
22
23
|
transportConnectionTimeout: 'transport-connection-timeout',
|
|
23
24
|
trackPublishingFailed: 'track-publishing-failed',
|
|
25
|
+
activeSpeakerChanged: 'active-speaker-changed',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const TRACK_EVENTS = {
|
|
29
|
+
volumeChanged: 'volume-changed',
|
|
24
30
|
};
|
|
25
31
|
|
|
26
32
|
export const INTERNAL_CLIENT_EVENTS = {
|
|
@@ -66,6 +72,7 @@ export const MEDIASOUP_EVENTS = {
|
|
|
66
72
|
producerSetMaxSpatialLayer: 'producer.setMaxSpatialLayer',
|
|
67
73
|
producerRequestMaxSpatialLayer: 'producer.requestMaxSpatialLayer',
|
|
68
74
|
createConsumer: 'consumer.create',
|
|
75
|
+
createDataConsumer: 'dataConsumer.create',
|
|
69
76
|
closeConsumer: 'consumer.close',
|
|
70
77
|
pauseConsumer: 'consumer.pause',
|
|
71
78
|
resumeConsumer: 'consumer.resume',
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
} from '../types/engine';
|
|
29
29
|
import SocketIO from './network/Socket';
|
|
30
30
|
import LoadBalancerApiClient from './network/LoadBalancerClient';
|
|
31
|
+
import ChannelAudioObserverEventHandler from './handlers/ChannelAudioObserverEventHandler';
|
|
31
32
|
|
|
32
33
|
/* eslint-disable class-methods-use-this */
|
|
33
34
|
class DefaultEngineDependenciesFactory implements EngineDependenciesFactory {
|
|
@@ -63,6 +64,10 @@ class DefaultEngineDependenciesFactory implements EngineDependenciesFactory {
|
|
|
63
64
|
return new MediaSoupEventHandler(params);
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
createAudioObserverEventHandler(params: CreateMediaSoupEventHandlerParams): ChannelAudioObserverEventHandler {
|
|
68
|
+
return new ChannelAudioObserverEventHandler(params);
|
|
69
|
+
}
|
|
70
|
+
|
|
66
71
|
createIssueDetector(params: CreateIssueDetectorParams): WebRTCIssueDetector {
|
|
67
72
|
const logger = params.logger ?? console;
|
|
68
73
|
const compositeStatsParser = new CompositeRTCStatsParser({
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { serializeError } from 'serialize-error';
|
|
2
|
+
import { LogMessageHandler, TrackLabel } from '../../types/common';
|
|
3
|
+
import Engine from '../index';
|
|
4
|
+
import Logger from '../Logger';
|
|
5
|
+
import InvalidPayloadError from '../../errors/InvalidPayloadError';
|
|
6
|
+
import {
|
|
7
|
+
AudioObserverEvents,
|
|
8
|
+
ChannelAudioObserverEvents,
|
|
9
|
+
DominantSpeakerEvent, PeersVolumesEvent,
|
|
10
|
+
} from '../../types/channelAudioObserver';
|
|
11
|
+
|
|
12
|
+
class ChannelAudioObserverEventHandler {
|
|
13
|
+
private readonly engine: Engine;
|
|
14
|
+
|
|
15
|
+
private readonly logger: Logger;
|
|
16
|
+
|
|
17
|
+
constructor(params: { engine: Engine, onLogMessage?: LogMessageHandler }) {
|
|
18
|
+
const { engine, onLogMessage } = params;
|
|
19
|
+
this.engine = engine;
|
|
20
|
+
this.logger = new Logger({
|
|
21
|
+
logLevel: engine.logLevel,
|
|
22
|
+
namespace: 'ChannelAudioObserverEventHandler',
|
|
23
|
+
onLogMessage,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public handle(data: string): void {
|
|
28
|
+
try {
|
|
29
|
+
const payload = JSON.parse(data);
|
|
30
|
+
ChannelAudioObserverEventHandler.validateEventPayload(payload);
|
|
31
|
+
switch (payload.event) {
|
|
32
|
+
case ChannelAudioObserverEvents.DominantSpeaker: {
|
|
33
|
+
this.handleDominantSpeaker(payload);
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
case ChannelAudioObserverEvents.PeersVolumes: {
|
|
37
|
+
this.handlePeerVolumes(payload);
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
case ChannelAudioObserverEvents.Silence: {
|
|
41
|
+
this.handleSilence();
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
default:
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
} catch (error) {
|
|
48
|
+
this.logger.warn('Failed to handle audio observer event', {
|
|
49
|
+
error: serializeError(error),
|
|
50
|
+
eventData: data,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private handleDominantSpeaker(payload: DominantSpeakerEvent): void {
|
|
56
|
+
const { peerId } = payload.data;
|
|
57
|
+
const peer = this.engine.getPeerById(peerId);
|
|
58
|
+
this.engine.setActiveSpeakerPeer(peer);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private handlePeerVolumes(payload: PeersVolumesEvent): void {
|
|
62
|
+
payload.volumes.forEach((item) => {
|
|
63
|
+
const { peerId, trackLabel, value } = item;
|
|
64
|
+
const track = this.engine.getPeerById(peerId)?.tracks.get(trackLabel);
|
|
65
|
+
if (!track) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
track.setVolume(value);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private handleSilence(): void {
|
|
74
|
+
const tracks = this.engine.peers.flatMap((peer) => Array.from(peer.tracks.values()));
|
|
75
|
+
tracks.forEach((track) => track.setVolume(0));
|
|
76
|
+
this.engine.setActiveSpeakerPeer(undefined);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private static validateEventPayload(payload: AudioObserverEvents): void {
|
|
80
|
+
switch (payload.event) {
|
|
81
|
+
case ChannelAudioObserverEvents.Silence: {
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
case ChannelAudioObserverEvents.DominantSpeaker: {
|
|
86
|
+
if (!payload.data) {
|
|
87
|
+
ChannelAudioObserverEventHandler.throwInvalidPayload();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const { peerId, producerId, trackLabel } = payload.data;
|
|
91
|
+
if (typeof peerId !== 'string' || typeof producerId !== 'string' || trackLabel !== TrackLabel.Microphone) {
|
|
92
|
+
ChannelAudioObserverEventHandler.throwInvalidPayload();
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
case ChannelAudioObserverEvents.PeersVolumes: {
|
|
98
|
+
if (!Array.isArray(payload.volumes)) {
|
|
99
|
+
ChannelAudioObserverEventHandler.throwInvalidPayload();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
payload.volumes.forEach((item) => {
|
|
103
|
+
const {
|
|
104
|
+
peerId, producerId, value, trackLabel,
|
|
105
|
+
} = item;
|
|
106
|
+
if (
|
|
107
|
+
typeof peerId !== 'string'
|
|
108
|
+
|| typeof producerId !== 'string'
|
|
109
|
+
|| typeof value !== 'number'
|
|
110
|
+
|| trackLabel !== TrackLabel.Microphone
|
|
111
|
+
) {
|
|
112
|
+
ChannelAudioObserverEventHandler.throwInvalidPayload();
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
default: {
|
|
119
|
+
ChannelAudioObserverEventHandler.throwInvalidPayload();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
static throwInvalidPayload(): void {
|
|
125
|
+
throw new InvalidPayloadError('Invalid channel audio observer event handler payload');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export default ChannelAudioObserverEventHandler;
|
package/src/engine/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { UnsupportedError as MediasoupUnsupportedError } from 'mediasoup-client/lib/errors';
|
|
2
2
|
import { RtpCapabilities } from 'mediasoup-client/lib/RtpParameters';
|
|
3
3
|
import WebRTCIssueDetector from 'webrtc-issue-detector';
|
|
4
|
+
import { DataConsumer } from 'mediasoup-client/lib/DataConsumer';
|
|
4
5
|
import {
|
|
5
6
|
CreateCameraVideoTrackOptions,
|
|
6
7
|
CreateMicrophoneAudioTrackOptions,
|
|
@@ -35,6 +36,8 @@ import { LogLevels } from '../constants/common';
|
|
|
35
36
|
import { LoadBalancerApiClientParams } from './network/LoadBalancerClient';
|
|
36
37
|
import validateAppData from '../helpers/appDataValidator';
|
|
37
38
|
import AudioTrack from './media/tracks/AudioTrack';
|
|
39
|
+
import NeedJoinFirstError from '../errors/NeedJoinFirstError';
|
|
40
|
+
import ChannelAudioObserverEventHandler from './handlers/ChannelAudioObserverEventHandler';
|
|
38
41
|
|
|
39
42
|
type EngineParams = {
|
|
40
43
|
clientEventEmitter: EnhancedEventEmitter,
|
|
@@ -66,6 +69,8 @@ class Engine {
|
|
|
66
69
|
|
|
67
70
|
private mediaSoupEventsHandler: MediaSoupEventHandler;
|
|
68
71
|
|
|
72
|
+
private channelAudioObserverEventHandler: ChannelAudioObserverEventHandler;
|
|
73
|
+
|
|
69
74
|
private initialized = false;
|
|
70
75
|
|
|
71
76
|
private isJoined = false;
|
|
@@ -80,6 +85,10 @@ class Engine {
|
|
|
80
85
|
|
|
81
86
|
private webRtcIssueDetector?: WebRTCIssueDetector;
|
|
82
87
|
|
|
88
|
+
private audioObserver?: DataConsumer;
|
|
89
|
+
|
|
90
|
+
private activeSpeakerPeer?: Peer;
|
|
91
|
+
|
|
83
92
|
constructor(params: EngineParams) {
|
|
84
93
|
const {
|
|
85
94
|
clientEventEmitter,
|
|
@@ -114,6 +123,10 @@ class Engine {
|
|
|
114
123
|
engine: this,
|
|
115
124
|
onLogMessage: params.onLogMessage,
|
|
116
125
|
});
|
|
126
|
+
this.channelAudioObserverEventHandler = dependenciesFactory.createAudioObserverEventHandler({
|
|
127
|
+
engine: this,
|
|
128
|
+
onLogMessage: params.onLogMessage,
|
|
129
|
+
});
|
|
117
130
|
|
|
118
131
|
this.logger = new Logger({
|
|
119
132
|
logLevel: this.logLevel,
|
|
@@ -176,6 +189,7 @@ class Engine {
|
|
|
176
189
|
await this.network.closeSendTransport();
|
|
177
190
|
await this.network.closeReceiveTransport();
|
|
178
191
|
await this.media.clearTracks();
|
|
192
|
+
this.audioObserver?.close();
|
|
179
193
|
|
|
180
194
|
this.logger.debug('release()', {
|
|
181
195
|
socketId: this.mySocketId,
|
|
@@ -197,6 +211,10 @@ class Engine {
|
|
|
197
211
|
});
|
|
198
212
|
}
|
|
199
213
|
|
|
214
|
+
public get activeSpeaker(): Peer | undefined {
|
|
215
|
+
return this.activeSpeakerPeer;
|
|
216
|
+
}
|
|
217
|
+
|
|
200
218
|
public get peers(): Peer[] {
|
|
201
219
|
return Array.from(this.peersRepository.values());
|
|
202
220
|
}
|
|
@@ -213,6 +231,10 @@ class Engine {
|
|
|
213
231
|
return this.network.socket.id;
|
|
214
232
|
}
|
|
215
233
|
|
|
234
|
+
public getPeerById(id: string): Peer | undefined {
|
|
235
|
+
return this.peersRepository.get(id);
|
|
236
|
+
}
|
|
237
|
+
|
|
216
238
|
public async confirmActivity(): Promise<void> {
|
|
217
239
|
if (!this.channel || this.isChannelJoining) {
|
|
218
240
|
throw new Error('Connect to the channel first');
|
|
@@ -411,6 +433,15 @@ class Engine {
|
|
|
411
433
|
return this.media.isNoiseSuppressorLoaded;
|
|
412
434
|
}
|
|
413
435
|
|
|
436
|
+
setActiveSpeakerPeer(peer?: Peer): void {
|
|
437
|
+
if (this.activeSpeaker === peer) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
this.activeSpeakerPeer = peer;
|
|
442
|
+
this.clientEventEmitter.safeEmit(CLIENT_EVENTS.activeSpeakerChanged, { peer });
|
|
443
|
+
}
|
|
444
|
+
|
|
414
445
|
private async connectToSocketServerWithRetry(params: { channelId: string, role: Role }): Promise<void> {
|
|
415
446
|
const connectToSocketServerAction = async () => this.connectToSocketServer(params);
|
|
416
447
|
return retryAsync(connectToSocketServerAction, {
|
|
@@ -496,6 +527,7 @@ class Engine {
|
|
|
496
527
|
this.mediaSoupEventsHandler.subscribeToEvents();
|
|
497
528
|
this.isJoined = true;
|
|
498
529
|
this.logger.debug('Successfully joined channel', { channelId: params.channelId });
|
|
530
|
+
await this.createAudioObserver();
|
|
499
531
|
} catch (error) {
|
|
500
532
|
this.logger.error('performJoin()', { error });
|
|
501
533
|
throw error;
|
|
@@ -576,6 +608,36 @@ class Engine {
|
|
|
576
608
|
|
|
577
609
|
myPeer.tracks.set(peerTrack.label, peerTrack);
|
|
578
610
|
}
|
|
611
|
+
|
|
612
|
+
private async createAudioObserver(): Promise<void> {
|
|
613
|
+
if (!this.isJoined) {
|
|
614
|
+
throw new NeedJoinFirstError('Need to join first');
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const logCtx = {
|
|
618
|
+
channelId: this.channelId,
|
|
619
|
+
appId: this.appId,
|
|
620
|
+
peerId: this.myPeer?.id,
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
try {
|
|
624
|
+
const { producerId } = await this.network.getAudioObserverProducer() as { producerId: string };
|
|
625
|
+
const audioObserver = await this.network.createDataConsumer({
|
|
626
|
+
producerId,
|
|
627
|
+
appId: this.appId,
|
|
628
|
+
channelId: this.channelId,
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
audioObserver.on('message', (data) => {
|
|
632
|
+
this.channelAudioObserverEventHandler.handle(data);
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
this.audioObserver = audioObserver;
|
|
636
|
+
this.logger.debug('Successfully create audio observer', logCtx);
|
|
637
|
+
} catch (error) {
|
|
638
|
+
this.logger.error('Failed to create audio observer', logCtx);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
579
641
|
}
|
|
580
642
|
|
|
581
643
|
export default Engine;
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
LogMessageHandler,
|
|
11
11
|
} from '../../../types/common';
|
|
12
12
|
import Logger from '../../Logger';
|
|
13
|
-
import { MEDIASOUP_EVENTS, PEER_EVENTS } from '../../../constants/events';
|
|
13
|
+
import { MEDIASOUP_EVENTS, PEER_EVENTS, TRACK_EVENTS } from '../../../constants/events';
|
|
14
14
|
import PeerConsumer from '../../PeerConsumer';
|
|
15
15
|
import Engine from '../../index';
|
|
16
16
|
import EnhancedEventEmitter from '../../../EnhancedEventEmitter';
|
|
@@ -52,6 +52,8 @@ class PeerTrack {
|
|
|
52
52
|
|
|
53
53
|
#muted = false;
|
|
54
54
|
|
|
55
|
+
#volumeLevel: number = 0;
|
|
56
|
+
|
|
55
57
|
readonly observer = new EnhancedEventEmitter();
|
|
56
58
|
|
|
57
59
|
constructor(payload: PeerTrackConstructor) {
|
|
@@ -70,6 +72,10 @@ class PeerTrack {
|
|
|
70
72
|
this.#peerEventEmitter.safeEmit(PEER_EVENTS.trackStart, this);
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
get volume(): number {
|
|
76
|
+
return this.#volumeLevel;
|
|
77
|
+
}
|
|
78
|
+
|
|
73
79
|
get isRemote(): boolean {
|
|
74
80
|
return !!this.consumer;
|
|
75
81
|
}
|
|
@@ -408,6 +414,15 @@ class PeerTrack {
|
|
|
408
414
|
}
|
|
409
415
|
}
|
|
410
416
|
|
|
417
|
+
setVolume(value: number): void {
|
|
418
|
+
if (this.#volumeLevel === value) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
this.#volumeLevel = value;
|
|
423
|
+
this.observer.safeEmit(TRACK_EVENTS.volumeChanged, { value });
|
|
424
|
+
}
|
|
425
|
+
|
|
411
426
|
private async closeConsumer(): Promise<void> {
|
|
412
427
|
try {
|
|
413
428
|
if (!this.consumer) {
|