From 5ac41f347100fe843c0ff1bdddeb7e61fd71f362 Mon Sep 17 00:00:00 2001 From: pabloFuente Date: Fri, 7 Dec 2018 11:22:21 +0100 Subject: [PATCH] openvidu-browser: streamAudioVolumeChange event --- .../src/OpenVidu/LocalRecorder.ts | 4 +- openvidu-browser/src/OpenVidu/Session.ts | 10 +- openvidu-browser/src/OpenVidu/Stream.ts | 123 +++++++++++++++--- .../src/OpenVidu/StreamManager.ts | 17 ++- .../Events/ConnectionEvent.ts | 4 +- .../Events/StreamManagerEvent.ts | 20 ++- 6 files changed, 144 insertions(+), 34 deletions(-) diff --git a/openvidu-browser/src/OpenVidu/LocalRecorder.ts b/openvidu-browser/src/OpenVidu/LocalRecorder.ts index 096b7129..395ce91d 100644 --- a/openvidu-browser/src/OpenVidu/LocalRecorder.ts +++ b/openvidu-browser/src/OpenVidu/LocalRecorder.ts @@ -29,7 +29,9 @@ declare var MediaRecorder: any; /** * Easy recording of [[Stream]] objects straightaway from the browser. Initialized with [[OpenVidu.initLocalRecorder]] method * - * > WARNING: Performing browser local recording of **remote streams** may cause some troubles. A long waiting time may be required after calling _LocalRecorder.stop()_ in this case + * > WARNINGS: + * - Performing browser local recording of **remote streams** may cause some troubles. A long waiting time may be required after calling _LocalRecorder.stop()_ in this case + * - Only Chrome and Firefox support local stream recording */ export class LocalRecorder { diff --git a/openvidu-browser/src/OpenVidu/Session.ts b/openvidu-browser/src/OpenVidu/Session.ts index ccc5be87..3a0a707b 100644 --- a/openvidu-browser/src/OpenVidu/Session.ts +++ b/openvidu-browser/src/OpenVidu/Session.ts @@ -80,11 +80,11 @@ export class Session implements EventDispatcher { /** * @hidden */ - isFirstIonicIosSubscriber: boolean = true; + isFirstIonicIosSubscriber = true; /** * @hidden */ - countDownForIonicIosSubscribers: boolean = true; + countDownForIonicIosSubscribers = true; /** * @hidden @@ -567,7 +567,7 @@ export class Session implements EventDispatcher { // If there are already available remote streams, enable hark 'speaking' event in all of them for (const connectionId in this.remoteConnections) { const str = this.remoteConnections[connectionId].stream; - if (!!str && !str.speechEvent && str.hasAudio) { + if (!!str && str.hasAudio) { str.enableSpeakingEvents(); } } @@ -596,7 +596,7 @@ export class Session implements EventDispatcher { // If there are already available remote streams, enable hark in all of them for (const connectionId in this.remoteConnections) { const str = this.remoteConnections[connectionId].stream; - if (!!str && !str.speechEvent && str.hasAudio) { + if (!!str && str.hasAudio) { str.enableOnceSpeakingEvents(); } } @@ -623,7 +623,7 @@ export class Session implements EventDispatcher { // If there are already available remote streams, disable hark in all of them for (const connectionId in this.remoteConnections) { const str = this.remoteConnections[connectionId].stream; - if (!!str && !!str.speechEvent) { + if (!!str) { str.disableSpeakingEvents(); } } diff --git a/openvidu-browser/src/OpenVidu/Stream.ts b/openvidu-browser/src/OpenVidu/Stream.ts index 21a08405..3f3517f7 100644 --- a/openvidu-browser/src/OpenVidu/Stream.ts +++ b/openvidu-browser/src/OpenVidu/Stream.ts @@ -26,6 +26,7 @@ import { OutboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/Ou import { WebRtcPeer, WebRtcPeerSendonly, WebRtcPeerRecvonly, WebRtcPeerSendrecv } from '../OpenViduInternal/WebRtcPeer/WebRtcPeer'; import { WebRtcStats } from '../OpenViduInternal/WebRtcStats/WebRtcStats'; import { PublisherSpeakingEvent } from '../OpenViduInternal/Events/PublisherSpeakingEvent'; +import { StreamManagerEvent } from '../OpenViduInternal/Events/StreamManagerEvent'; import { StreamPropertyChangedEvent } from '../OpenViduInternal/Events/StreamPropertyChangedEvent'; import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError'; @@ -156,6 +157,18 @@ export class Stream implements EventDispatcher { * @hidden */ speechEvent: any; + /** + * @hidden + */ + publisherStartSpeakingEventEnabled = false; + /** + * @hidden + */ + publisherStopSpeakingEventEnabled = false; + /** + * @hidden + */ + volumeChangeEventEnabled = false; /** @@ -334,7 +347,6 @@ export class Stream implements EventDispatcher { }); } - /* Hidden methods */ /** @@ -439,6 +451,7 @@ export class Stream implements EventDispatcher { } if (this.speechEvent) { this.speechEvent.stop(); + delete this.speechEvent; } this.stopWebRtcStats(); @@ -503,7 +516,6 @@ export class Stream implements EventDispatcher { const harkOptions = this.session.openvidu.advancedConfiguration.publisherSpeakingEventsOptions || {}; harkOptions.interval = (typeof harkOptions.interval === 'number') ? harkOptions.interval : 50; harkOptions.threshold = (typeof harkOptions.threshold === 'number') ? harkOptions.threshold : -50; - this.speechEvent = hark(this.mediaStream, harkOptions); } } @@ -513,12 +525,18 @@ export class Stream implements EventDispatcher { */ enableSpeakingEvents(): void { this.setSpeechEventIfNotExists(); - this.speechEvent.on('speaking', () => { - this.session.emitEvent('publisherStartSpeaking', [new PublisherSpeakingEvent(this.session, 'publisherStartSpeaking', this.connection, this.streamId)]); - }); - this.speechEvent.on('stopped_speaking', () => { - this.session.emitEvent('publisherStopSpeaking', [new PublisherSpeakingEvent(this.session, 'publisherStopSpeaking', this.connection, this.streamId)]); - }); + if (!this.publisherStartSpeakingEventEnabled) { + this.publisherStartSpeakingEventEnabled = true; + this.speechEvent.on('speaking', () => { + this.session.emitEvent('publisherStartSpeaking', [new PublisherSpeakingEvent(this.session, 'publisherStartSpeaking', this.connection, this.streamId)]); + }); + } + if (!this.publisherStopSpeakingEventEnabled) { + this.publisherStopSpeakingEventEnabled = true; + this.speechEvent.on('stopped_speaking', () => { + this.session.emitEvent('publisherStopSpeaking', [new PublisherSpeakingEvent(this.session, 'publisherStopSpeaking', this.connection, this.streamId)]); + }); + } } /** @@ -526,22 +544,87 @@ export class Stream implements EventDispatcher { */ enableOnceSpeakingEvents(): void { this.setSpeechEventIfNotExists(); - this.speechEvent.on('speaking', () => { - this.session.emitEvent('publisherStartSpeaking', [new PublisherSpeakingEvent(this.session, 'publisherStartSpeaking', this.connection, this.streamId)]); - this.disableSpeakingEvents(); - }); - this.speechEvent.on('stopped_speaking', () => { - this.session.emitEvent('publisherStopSpeaking', [new PublisherSpeakingEvent(this.session, 'publisherStopSpeaking', this.connection, this.streamId)]); - this.disableSpeakingEvents(); - }); + if (!this.publisherStartSpeakingEventEnabled) { + this.publisherStartSpeakingEventEnabled = true; + this.speechEvent.once('speaking', () => { + this.session.emitEvent('publisherStartSpeaking', [new PublisherSpeakingEvent(this.session, 'publisherStartSpeaking', this.connection, this.streamId)]); + this.disableSpeakingEvents(); + }); + } + if (!this.publisherStopSpeakingEventEnabled) { + this.publisherStopSpeakingEventEnabled = true; + this.speechEvent.once('stopped_speaking', () => { + this.session.emitEvent('publisherStopSpeaking', [new PublisherSpeakingEvent(this.session, 'publisherStopSpeaking', this.connection, this.streamId)]); + this.disableSpeakingEvents(); + }); + } } /** * @hidden */ disableSpeakingEvents(): void { - this.speechEvent.stop(); - this.speechEvent = undefined; + if (!!this.speechEvent) { + if (this.volumeChangeEventEnabled) { + // 'streamAudioVolumeChange' event is enabled. Cannot stop the hark process + this.speechEvent.off('speaking'); + this.speechEvent.off('stopped_speaking'); + } else { + this.speechEvent.stop(); + delete this.speechEvent; + } + } + this.publisherStartSpeakingEventEnabled = false; + this.publisherStopSpeakingEventEnabled = false; + } + + /** + * @hidden + */ + enableVolumeChangeEvent(): void { + this.setSpeechEventIfNotExists(); + if (!this.volumeChangeEventEnabled) { + this.volumeChangeEventEnabled = true; + this.speechEvent.on('volume_change', harkEvent => { + const oldValue = this.speechEvent.oldVolumeValue; + const value = { newValue: harkEvent, oldValue }; + this.speechEvent.oldVolumeValue = harkEvent; + this.streamManager.emitEvent('streamAudioVolumeChange', [new StreamManagerEvent(this.streamManager, 'streamAudioVolumeChange', value)]); + }); + } + } + + /** + * @hidden + */ + enableOnceVolumeChangeEvent(): void { + this.setSpeechEventIfNotExists(); + if (!this.volumeChangeEventEnabled) { + this.volumeChangeEventEnabled = true; + this.speechEvent.once('volume_change', harkEvent => { + const oldValue = this.speechEvent.oldVolumeValue; + const value = { newValue: harkEvent, oldValue }; + this.speechEvent.oldVolumeValue = harkEvent; + this.disableVolumeChangeEvent(); + this.streamManager.emitEvent('streamAudioVolumeChange', [new StreamManagerEvent(this.streamManager, 'streamAudioVolumeChange', value)]); + }); + } + } + + /** + * @hidden + */ + disableVolumeChangeEvent(): void { + if (!!this.speechEvent) { + if (this.session.speakingEventsEnabled) { + // 'publisherStartSpeaking' and/or publisherStopSpeaking` events are enabled. Cannot stop the hark process + this.speechEvent.off('volume_change'); + } else { + this.speechEvent.stop(); + delete this.speechEvent; + } + } + this.volumeChangeEventEnabled = false; } /** @@ -686,8 +769,8 @@ export class Stream implements EventDispatcher { // it necessary to add a timeout before processAnswer method if (this.session.isFirstIonicIosSubscriber) { this.session.isFirstIonicIosSubscriber = false; - this.session['iosInterval'] = setTimeout(() => { - this.session.countDownForIonicIosSubscribers = false; + this.session['iosInterval'] = setTimeout(() => { + this.session.countDownForIonicIosSubscribers = false; }, 400); } const needsTimeoutOnProcessAswer = this.session.countDownForIonicIosSubscribers; diff --git a/openvidu-browser/src/OpenVidu/StreamManager.ts b/openvidu-browser/src/OpenVidu/StreamManager.ts index f3e9debb..05bbf36b 100644 --- a/openvidu-browser/src/OpenVidu/StreamManager.ts +++ b/openvidu-browser/src/OpenVidu/StreamManager.ts @@ -133,7 +133,7 @@ export class StreamManager implements EventDispatcher { console.info("Remote 'Stream' with id [" + this.stream.streamId + '] video is now playing'); this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]); } - this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this)]); + this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this, 'streamPlaying', undefined)]); }; } @@ -161,10 +161,13 @@ export class StreamManager implements EventDispatcher { this.videos[0].video.paused === false && this.videos[0].video.ended === false && this.videos[0].video.readyState === 4) { - this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this)]); + this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this, 'streamPlaying', undefined)]); this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]); } } + if (type === 'streamAudioVolumeChange' && this.stream.hasAudio) { + this.stream.enableVolumeChangeEvent(); + } return this; } @@ -191,10 +194,13 @@ export class StreamManager implements EventDispatcher { this.videos[0].video.paused === false && this.videos[0].video.ended === false && this.videos[0].video.readyState === 4) { - this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this)]); + this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this, 'streamPlaying', undefined)]); this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]); } } + if (type === 'streamAudioVolumeChange' && this.stream.hasAudio) { + this.stream.enableOnceVolumeChangeEvent(); + } return this; } @@ -207,6 +213,11 @@ export class StreamManager implements EventDispatcher { } else { this.ee.off(type, handler); } + + if (type === 'streamAudioVolumeChange') { + this.stream.disableVolumeChangeEvent(); + } + return this; } diff --git a/openvidu-browser/src/OpenViduInternal/Events/ConnectionEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/ConnectionEvent.ts index 357c336b..4d1014f2 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/ConnectionEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/ConnectionEvent.ts @@ -33,14 +33,14 @@ export class ConnectionEvent extends Event { connection: Connection; /** - * For 'connectionDestroyed' event: + * For `connectionDestroyed` event: * - "disconnect": the remote user has called `Session.disconnect()` * - "forceDisconnectByUser": the remote user has been evicted from the Session by other user calling `Session.forceDisconnect()` * - "forceDisconnectByServer": the remote user has been evicted from the Session by the application * - "sessionClosedByServer": the Session has been closed by the application * - "networkDisconnect": the remote user network connection has dropped * - * For 'connectionCreated' empty string + * For `connectionCreated` event an empty string */ reason: string; diff --git a/openvidu-browser/src/OpenViduInternal/Events/StreamManagerEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/StreamManagerEvent.ts index 4dd54a7c..567951ea 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/StreamManagerEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/StreamManagerEvent.ts @@ -20,15 +20,29 @@ import { StreamManager } from '../../OpenVidu/StreamManager'; /** * Defines the following events: - * - `streamPlaying`: dispatched by [[StreamManager]] ([[Publisher]] and [[Subscriber]]) + * - `streamPlaying`: dispatched by [[StreamManager]] ([[Publisher]] and [[Subscriber]]) whenever its media stream starts playing (one of its videos has media + * and has begun to play) + * - `streamAudioVolumeChange`: dispatched by [[StreamManager]] ([[Publisher]] and [[Subscriber]]) when the volume of its Stream's audio track + * changes. Only applies if [[Stream.hasAudio]] is `true`. The frequency this event is fired with is defined by property `interval` of + * [[OpenViduAdvancedConfiguration.publisherSpeakingEventsOptions]] (default 50ms) */ export class StreamManagerEvent extends Event { + /** + * For `streamAudioVolumeChange` event: + * - `{newValue: number, oldValue: number}`: new and old audio volume values. These values are between -100 (silence) and 0 (loudest possible volume). + * They are not exact and depend on how the browser is managing the audio track, but -100 and 0 can be taken as limit values. + * + * For `streamPlaying` event undefined + */ + value: Object | undefined; + /** * @hidden */ - constructor(target: StreamManager) { - super(false, target, 'streamPlaying'); + constructor(target: StreamManager, type: string, value: Object | undefined) { + super(false, target, type); + this.value = value; } /**