openvidu-browser: streamAudioVolumeChange event

pull/173/head
pabloFuente 2018-12-07 11:22:21 +01:00
parent b0bc3d40d2
commit 5ac41f3471
6 changed files with 144 additions and 34 deletions

View File

@ -29,7 +29,9 @@ declare var MediaRecorder: any;
/** /**
* Easy recording of [[Stream]] objects straightaway from the browser. Initialized with [[OpenVidu.initLocalRecorder]] method * 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 { export class LocalRecorder {

View File

@ -80,11 +80,11 @@ export class Session implements EventDispatcher {
/** /**
* @hidden * @hidden
*/ */
isFirstIonicIosSubscriber: boolean = true; isFirstIonicIosSubscriber = true;
/** /**
* @hidden * @hidden
*/ */
countDownForIonicIosSubscribers: boolean = true; countDownForIonicIosSubscribers = true;
/** /**
* @hidden * @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 // If there are already available remote streams, enable hark 'speaking' event in all of them
for (const connectionId in this.remoteConnections) { for (const connectionId in this.remoteConnections) {
const str = this.remoteConnections[connectionId].stream; const str = this.remoteConnections[connectionId].stream;
if (!!str && !str.speechEvent && str.hasAudio) { if (!!str && str.hasAudio) {
str.enableSpeakingEvents(); str.enableSpeakingEvents();
} }
} }
@ -596,7 +596,7 @@ export class Session implements EventDispatcher {
// If there are already available remote streams, enable hark in all of them // If there are already available remote streams, enable hark in all of them
for (const connectionId in this.remoteConnections) { for (const connectionId in this.remoteConnections) {
const str = this.remoteConnections[connectionId].stream; const str = this.remoteConnections[connectionId].stream;
if (!!str && !str.speechEvent && str.hasAudio) { if (!!str && str.hasAudio) {
str.enableOnceSpeakingEvents(); str.enableOnceSpeakingEvents();
} }
} }
@ -623,7 +623,7 @@ export class Session implements EventDispatcher {
// If there are already available remote streams, disable hark in all of them // If there are already available remote streams, disable hark in all of them
for (const connectionId in this.remoteConnections) { for (const connectionId in this.remoteConnections) {
const str = this.remoteConnections[connectionId].stream; const str = this.remoteConnections[connectionId].stream;
if (!!str && !!str.speechEvent) { if (!!str) {
str.disableSpeakingEvents(); str.disableSpeakingEvents();
} }
} }

View File

@ -26,6 +26,7 @@ import { OutboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/Ou
import { WebRtcPeer, WebRtcPeerSendonly, WebRtcPeerRecvonly, WebRtcPeerSendrecv } from '../OpenViduInternal/WebRtcPeer/WebRtcPeer'; import { WebRtcPeer, WebRtcPeerSendonly, WebRtcPeerRecvonly, WebRtcPeerSendrecv } from '../OpenViduInternal/WebRtcPeer/WebRtcPeer';
import { WebRtcStats } from '../OpenViduInternal/WebRtcStats/WebRtcStats'; import { WebRtcStats } from '../OpenViduInternal/WebRtcStats/WebRtcStats';
import { PublisherSpeakingEvent } from '../OpenViduInternal/Events/PublisherSpeakingEvent'; import { PublisherSpeakingEvent } from '../OpenViduInternal/Events/PublisherSpeakingEvent';
import { StreamManagerEvent } from '../OpenViduInternal/Events/StreamManagerEvent';
import { StreamPropertyChangedEvent } from '../OpenViduInternal/Events/StreamPropertyChangedEvent'; import { StreamPropertyChangedEvent } from '../OpenViduInternal/Events/StreamPropertyChangedEvent';
import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError'; import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError';
@ -156,6 +157,18 @@ export class Stream implements EventDispatcher {
* @hidden * @hidden
*/ */
speechEvent: any; speechEvent: any;
/**
* @hidden
*/
publisherStartSpeakingEventEnabled = false;
/**
* @hidden
*/
publisherStopSpeakingEventEnabled = false;
/**
* @hidden
*/
volumeChangeEventEnabled = false;
/** /**
@ -334,7 +347,6 @@ export class Stream implements EventDispatcher {
}); });
} }
/* Hidden methods */ /* Hidden methods */
/** /**
@ -439,6 +451,7 @@ export class Stream implements EventDispatcher {
} }
if (this.speechEvent) { if (this.speechEvent) {
this.speechEvent.stop(); this.speechEvent.stop();
delete this.speechEvent;
} }
this.stopWebRtcStats(); this.stopWebRtcStats();
@ -503,7 +516,6 @@ export class Stream implements EventDispatcher {
const harkOptions = this.session.openvidu.advancedConfiguration.publisherSpeakingEventsOptions || {}; const harkOptions = this.session.openvidu.advancedConfiguration.publisherSpeakingEventsOptions || {};
harkOptions.interval = (typeof harkOptions.interval === 'number') ? harkOptions.interval : 50; harkOptions.interval = (typeof harkOptions.interval === 'number') ? harkOptions.interval : 50;
harkOptions.threshold = (typeof harkOptions.threshold === 'number') ? harkOptions.threshold : -50; harkOptions.threshold = (typeof harkOptions.threshold === 'number') ? harkOptions.threshold : -50;
this.speechEvent = hark(this.mediaStream, harkOptions); this.speechEvent = hark(this.mediaStream, harkOptions);
} }
} }
@ -513,12 +525,18 @@ export class Stream implements EventDispatcher {
*/ */
enableSpeakingEvents(): void { enableSpeakingEvents(): void {
this.setSpeechEventIfNotExists(); this.setSpeechEventIfNotExists();
this.speechEvent.on('speaking', () => { if (!this.publisherStartSpeakingEventEnabled) {
this.session.emitEvent('publisherStartSpeaking', [new PublisherSpeakingEvent(this.session, 'publisherStartSpeaking', this.connection, this.streamId)]); this.publisherStartSpeakingEventEnabled = true;
}); this.speechEvent.on('speaking', () => {
this.speechEvent.on('stopped_speaking', () => { this.session.emitEvent('publisherStartSpeaking', [new PublisherSpeakingEvent(this.session, 'publisherStartSpeaking', this.connection, this.streamId)]);
this.session.emitEvent('publisherStopSpeaking', [new PublisherSpeakingEvent(this.session, 'publisherStopSpeaking', 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 { enableOnceSpeakingEvents(): void {
this.setSpeechEventIfNotExists(); this.setSpeechEventIfNotExists();
this.speechEvent.on('speaking', () => { if (!this.publisherStartSpeakingEventEnabled) {
this.session.emitEvent('publisherStartSpeaking', [new PublisherSpeakingEvent(this.session, 'publisherStartSpeaking', this.connection, this.streamId)]); this.publisherStartSpeakingEventEnabled = true;
this.disableSpeakingEvents(); this.speechEvent.once('speaking', () => {
}); this.session.emitEvent('publisherStartSpeaking', [new PublisherSpeakingEvent(this.session, 'publisherStartSpeaking', this.connection, this.streamId)]);
this.speechEvent.on('stopped_speaking', () => { this.disableSpeakingEvents();
this.session.emitEvent('publisherStopSpeaking', [new PublisherSpeakingEvent(this.session, 'publisherStopSpeaking', 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 * @hidden
*/ */
disableSpeakingEvents(): void { disableSpeakingEvents(): void {
this.speechEvent.stop(); if (!!this.speechEvent) {
this.speechEvent = undefined; 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 // it necessary to add a timeout before processAnswer method
if (this.session.isFirstIonicIosSubscriber) { if (this.session.isFirstIonicIosSubscriber) {
this.session.isFirstIonicIosSubscriber = false; this.session.isFirstIonicIosSubscriber = false;
this.session['iosInterval'] = setTimeout(() => { this.session['iosInterval'] = setTimeout(() => {
this.session.countDownForIonicIosSubscribers = false; this.session.countDownForIonicIosSubscribers = false;
}, 400); }, 400);
} }
const needsTimeoutOnProcessAswer = this.session.countDownForIonicIosSubscribers; const needsTimeoutOnProcessAswer = this.session.countDownForIonicIosSubscribers;

View File

@ -133,7 +133,7 @@ export class StreamManager implements EventDispatcher {
console.info("Remote 'Stream' with id [" + this.stream.streamId + '] video is now playing'); 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('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.paused === false &&
this.videos[0].video.ended === false && this.videos[0].video.ended === false &&
this.videos[0].video.readyState === 4) { 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')]); this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]);
} }
} }
if (type === 'streamAudioVolumeChange' && this.stream.hasAudio) {
this.stream.enableVolumeChangeEvent();
}
return this; return this;
} }
@ -191,10 +194,13 @@ export class StreamManager implements EventDispatcher {
this.videos[0].video.paused === false && this.videos[0].video.paused === false &&
this.videos[0].video.ended === false && this.videos[0].video.ended === false &&
this.videos[0].video.readyState === 4) { 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')]); this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]);
} }
} }
if (type === 'streamAudioVolumeChange' && this.stream.hasAudio) {
this.stream.enableOnceVolumeChangeEvent();
}
return this; return this;
} }
@ -207,6 +213,11 @@ export class StreamManager implements EventDispatcher {
} else { } else {
this.ee.off(type, handler); this.ee.off(type, handler);
} }
if (type === 'streamAudioVolumeChange') {
this.stream.disableVolumeChangeEvent();
}
return this; return this;
} }

View File

@ -33,14 +33,14 @@ export class ConnectionEvent extends Event {
connection: Connection; connection: Connection;
/** /**
* For 'connectionDestroyed' event: * For `connectionDestroyed` event:
* - "disconnect": the remote user has called `Session.disconnect()` * - "disconnect": the remote user has called `Session.disconnect()`
* - "forceDisconnectByUser": the remote user has been evicted from the Session by other user calling `Session.forceDisconnect()` * - "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 * - "forceDisconnectByServer": the remote user has been evicted from the Session by the application
* - "sessionClosedByServer": the Session has been closed by the application * - "sessionClosedByServer": the Session has been closed by the application
* - "networkDisconnect": the remote user network connection has dropped * - "networkDisconnect": the remote user network connection has dropped
* *
* For 'connectionCreated' empty string * For `connectionCreated` event an empty string
*/ */
reason: string; reason: string;

View File

@ -20,15 +20,29 @@ import { StreamManager } from '../../OpenVidu/StreamManager';
/** /**
* Defines the following events: * 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 { 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 * @hidden
*/ */
constructor(target: StreamManager) { constructor(target: StreamManager, type: string, value: Object | undefined) {
super(false, target, 'streamPlaying'); super(false, target, type);
this.value = value;
} }
/** /**