2018-04-26 15:33:47 +02:00
/ *
2020-02-04 11:25:54 +01:00
* ( C ) Copyright 2017 - 2020 OpenVidu ( https : //openvidu.io)
2018-04-26 15:33:47 +02:00
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
*
* /
2018-05-08 13:01:34 +02:00
import { Connection } from './Connection' ;
2018-08-27 17:06:14 +02:00
import { Filter } from './Filter' ;
2018-05-08 13:01:34 +02:00
import { Session } from './Session' ;
2018-05-29 18:28:58 +02:00
import { StreamManager } from './StreamManager' ;
2019-01-27 14:42:46 +01:00
import { Subscriber } from './Subscriber' ;
2018-04-26 15:33:47 +02:00
import { InboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/InboundStreamOptions' ;
import { OutboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/OutboundStreamOptions' ;
2021-03-22 19:23:03 +01:00
import { WebRtcPeer , WebRtcPeerSendonly , WebRtcPeerRecvonly , WebRtcPeerSendrecv , WebRtcPeerConfiguration } from '../OpenViduInternal/WebRtcPeer/WebRtcPeer' ;
2018-04-26 15:33:47 +02:00
import { WebRtcStats } from '../OpenViduInternal/WebRtcStats/WebRtcStats' ;
2021-03-22 19:23:03 +01:00
import { ExceptionEvent , ExceptionEventName } from '../OpenViduInternal/Events/ExceptionEvent' ;
2018-04-26 15:33:47 +02:00
import { PublisherSpeakingEvent } from '../OpenViduInternal/Events/PublisherSpeakingEvent' ;
2018-12-07 11:22:21 +01:00
import { StreamManagerEvent } from '../OpenViduInternal/Events/StreamManagerEvent' ;
2018-08-28 11:24:26 +02:00
import { StreamPropertyChangedEvent } from '../OpenViduInternal/Events/StreamPropertyChangedEvent' ;
2018-12-04 09:55:00 +01:00
import { OpenViduError , OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError' ;
2020-10-13 16:13:37 +02:00
import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger' ;
import { PlatformUtils } from '../OpenViduInternal/Utils/Platform' ;
2018-05-23 15:01:40 +02:00
2020-02-19 09:35:10 +01:00
/ * *
* @hidden
* /
2018-06-11 13:08:30 +02:00
import hark = require ( 'hark' ) ;
2021-03-22 13:27:58 +01:00
/ * *
* @hidden
* /
import EventEmitter = require ( 'wolfy87-eventemitter' ) ;
2020-05-04 20:01:56 +02:00
/ * *
* @hidden
* /
const logger : OpenViduLogger = OpenViduLogger . getInstance ( ) ;
2020-10-13 16:13:37 +02:00
/ * *
* @hidden
* /
2020-11-26 13:17:55 +01:00
let platform : PlatformUtils ;
2018-04-26 15:33:47 +02:00
/ * *
2018-05-29 18:28:58 +02:00
* Represents each one of the media streams available in OpenVidu Server for certain session .
* Each [ [ Publisher ] ] and [ [ Subscriber ] ] has an attribute of type Stream , as they give access
2018-06-01 14:39:38 +02:00
* to one of them ( sending and receiving it , respectively )
2018-04-26 15:33:47 +02:00
* /
2021-03-22 13:27:58 +01:00
export class Stream {
2018-04-26 15:33:47 +02:00
/ * *
* The Connection object that is publishing the stream
* /
connection : Connection ;
/ * *
2018-05-29 18:28:58 +02:00
* Frame rate of the video in frames per second . This property is only defined if the [ [ Publisher ] ] of
* the stream was initialized passing a _frameRate_ property on [ [ OpenVidu . initPublisher ] ] method
2018-04-26 15:33:47 +02:00
* /
frameRate? : number ;
/ * *
2018-06-01 14:39:38 +02:00
* Whether the stream has a video track or not
2018-04-26 15:33:47 +02:00
* /
hasVideo : boolean ;
/ * *
2018-06-01 14:39:38 +02:00
* Whether the stream has an audio track or not
2018-04-26 15:33:47 +02:00
* /
hasAudio : boolean ;
2018-07-03 15:35:08 +02:00
/ * *
* Whether the stream has the video track muted or unmuted . If [ [ hasVideo ] ] is false , this property is undefined .
*
* This property may change if the Publisher publishing the stream calls [ [ Publisher . publishVideo ] ] . Whenever this happens a [ [ StreamPropertyChangedEvent ] ] will be dispatched
* by the Session object as well as by the affected Subscriber / Publisher object
* /
videoActive : boolean ;
/ * *
* Whether the stream has the audio track muted or unmuted . If [ [ hasAudio ] ] is false , this property is undefined
*
* This property may change if the Publisher publishing the stream calls [ [ Publisher . publishAudio ] ] . Whenever this happens a [ [ StreamPropertyChangedEvent ] ] will be dispatched
* by the Session object as well as by the affected Subscriber / Publisher object
* /
audioActive : boolean ;
2018-04-26 15:33:47 +02:00
/ * *
2018-11-13 17:04:49 +01:00
* Unique identifier of the stream . If the stream belongs to a . . .
* - Subscriber object : property ` streamId ` is always defined
* - Publisher object : property ` streamId ` is only defined after successful execution of [ [ Session . publish ] ]
2018-04-26 15:33:47 +02:00
* /
streamId : string ;
2019-01-09 17:35:34 +01:00
/ * *
* Time when this stream was created in OpenVidu Server ( UTC milliseconds ) . Depending on the owner of this stream :
* - Subscriber object : property ` creationTime ` is always defined
* - Publisher object : property ` creationTime ` is only defined after successful execution of [ [ Session . publish ] ]
* /
creationTime : number ;
2018-04-26 15:33:47 +02:00
/ * *
2020-08-25 11:35:13 +02:00
* Can be :
* - ` "CAMERA" ` : when the video source comes from a webcam .
* - ` "SCREEN" ` : when the video source comes from screen - sharing .
* - ` "CUSTOM" ` : when [ [ PublisherProperties . videoSource ] ] has been initialized in the Publisher side with a custom MediaStreamTrack when calling [ [ OpenVidu . initPublisher ] ] ) .
* - ` "IPCAM" ` : when the video source comes from an IP camera participant instead of a regular participant ( see [ IP cameras ] ( / e n / s t a b l e / a d v a n c e d - f e a t u r e s / i p - c a m e r a s / ) ) .
2020-10-13 16:13:37 +02:00
*
2018-07-03 15:35:08 +02:00
* If [ [ hasVideo ] ] is false , this property is undefined
2018-04-26 15:33:47 +02:00
* /
typeOfVideo? : string ;
2018-05-23 15:01:40 +02:00
/ * *
2018-05-29 18:28:58 +02:00
* StreamManager object ( [ [ Publisher ] ] or [ [ Subscriber ] ] ) in charge of displaying this stream in the DOM
2018-05-23 15:01:40 +02:00
* /
2018-05-29 18:28:58 +02:00
streamManager : StreamManager ;
2018-05-23 15:01:40 +02:00
2018-07-03 15:35:08 +02:00
/ * *
* Width and height in pixels of the encoded video stream . If [ [ hasVideo ] ] is false , this property is undefined
*
* This property may change if the Publisher that is publishing :
* - If it is a mobile device , whenever the user rotates the device .
* - If it is screen - sharing , whenever the user changes the size of the captured window .
*
* Whenever this happens a [ [ StreamPropertyChangedEvent ] ] will be dispatched by the Session object as well as by the affected Subscriber / Publisher object
* /
videoDimensions : { width : number , height : number } ;
2018-07-27 14:32:53 +02:00
/ * *
* * * WARNING * * : experimental option . This interface may change in the near future
*
2018-08-28 11:24:26 +02:00
* Filter applied to the Stream . You can apply filters by calling [ [ Stream . applyFilter ] ] , execute methods of the applied filter with
* [ [ Filter . execMethod ] ] and remove it with [ [ Stream . removeFilter ] ] . Be aware that the client calling this methods must have the
2018-07-27 14:32:53 +02:00
* necessary permissions : the token owned by the client must have been initialized with the appropriated ` allowedFilters ` array .
* /
2020-11-10 18:22:14 +01:00
filter? : Filter ;
2018-07-27 14:32:53 +02:00
2020-06-30 10:48:55 +02:00
protected webRtcPeer : WebRtcPeer ;
2020-11-10 18:22:14 +01:00
protected mediaStream? : MediaStream ;
2018-04-26 15:33:47 +02:00
private webRtcStats : WebRtcStats ;
private isSubscribeToRemote = false ;
/ * *
* @hidden
* /
2018-05-23 15:01:40 +02:00
isLocalStreamReadyToPublish = false ;
2018-04-26 15:33:47 +02:00
/ * *
* @hidden
* /
2018-05-23 15:01:40 +02:00
isLocalStreamPublished = false ;
2018-07-09 15:45:20 +02:00
/ * *
* @hidden
* /
publishedOnce = false ;
2018-04-26 15:33:47 +02:00
/ * *
* @hidden
* /
session : Session ;
/ * *
* @hidden
* /
inboundStreamOpts : InboundStreamOptions ;
/ * *
* @hidden
* /
outboundStreamOpts : OutboundStreamOptions ;
/ * *
* @hidden
* /
speechEvent : any ;
2018-12-07 11:22:21 +01:00
/ * *
* @hidden
* /
2021-03-30 18:04:56 +02:00
harkSpeakingEnabled = false ;
2019-12-05 14:18:46 +01:00
/ * *
* @hidden
* /
2021-03-30 18:04:56 +02:00
harkSpeakingEnabledOnce = false ;
2018-12-07 11:22:21 +01:00
/ * *
* @hidden
* /
2021-03-30 18:04:56 +02:00
harkStoppedSpeakingEnabled = false ;
2019-12-05 14:18:46 +01:00
/ * *
* @hidden
* /
2021-03-30 18:04:56 +02:00
harkStoppedSpeakingEnabledOnce = false ;
2018-12-07 11:22:21 +01:00
/ * *
* @hidden
* /
2021-03-30 18:04:56 +02:00
harkVolumeChangeEnabled = false ;
2019-12-05 14:18:46 +01:00
/ * *
* @hidden
* /
2021-03-30 18:04:56 +02:00
harkVolumeChangeEnabledOnce = false ;
2019-12-10 14:14:54 +01:00
/ * *
* @hidden
* /
harkOptions ;
2020-03-28 12:22:05 +01:00
/ * *
* @hidden
* /
2020-11-10 18:22:14 +01:00
localMediaStreamWhenSubscribedToRemote? : MediaStream ;
2021-03-22 13:27:58 +01:00
/ * *
* @hidden
* /
ee = new EventEmitter ( ) ;
2018-04-26 15:33:47 +02:00
/ * *
* @hidden
* /
constructor ( session : Session , options : InboundStreamOptions | OutboundStreamOptions | { } ) {
2020-11-26 13:17:55 +01:00
platform = PlatformUtils . getInstance ( ) ;
2018-04-26 15:33:47 +02:00
this . session = session ;
if ( options . hasOwnProperty ( 'id' ) ) {
// InboundStreamOptions: stream belongs to a Subscriber
this . inboundStreamOpts = < InboundStreamOptions > options ;
this . streamId = this . inboundStreamOpts . id ;
2019-01-09 17:35:34 +01:00
this . creationTime = this . inboundStreamOpts . createdAt ;
2018-07-03 15:35:08 +02:00
this . hasAudio = this . inboundStreamOpts . hasAudio ;
this . hasVideo = this . inboundStreamOpts . hasVideo ;
if ( this . hasAudio ) {
this . audioActive = this . inboundStreamOpts . audioActive ;
}
if ( this . hasVideo ) {
this . videoActive = this . inboundStreamOpts . videoActive ;
this . typeOfVideo = ( ! this . inboundStreamOpts . typeOfVideo ) ? undefined : this . inboundStreamOpts . typeOfVideo ;
this . frameRate = ( this . inboundStreamOpts . frameRate === - 1 ) ? undefined : this . inboundStreamOpts . frameRate ;
this . videoDimensions = this . inboundStreamOpts . videoDimensions ;
}
2018-08-27 17:06:14 +02:00
if ( ! ! this . inboundStreamOpts . filter && ( Object . keys ( this . inboundStreamOpts . filter ) . length > 0 ) ) {
2018-07-27 14:32:53 +02:00
if ( ! ! this . inboundStreamOpts . filter . lastExecMethod && Object . keys ( this . inboundStreamOpts . filter . lastExecMethod ) . length === 0 ) {
delete this . inboundStreamOpts . filter . lastExecMethod ;
}
this . filter = this . inboundStreamOpts . filter ;
}
2018-04-26 15:33:47 +02:00
} else {
// OutboundStreamOptions: stream belongs to a Publisher
this . outboundStreamOpts = < OutboundStreamOptions > options ;
2018-07-03 15:35:08 +02:00
this . hasAudio = this . isSendAudio ( ) ;
this . hasVideo = this . isSendVideo ( ) ;
if ( this . hasAudio ) {
this . audioActive = ! ! this . outboundStreamOpts . publisherProperties . publishAudio ;
}
if ( this . hasVideo ) {
this . videoActive = ! ! this . outboundStreamOpts . publisherProperties . publishVideo ;
this . frameRate = this . outboundStreamOpts . publisherProperties . frameRate ;
2019-05-10 10:36:10 +02:00
if ( typeof MediaStreamTrack !== 'undefined' && this . outboundStreamOpts . publisherProperties . videoSource instanceof MediaStreamTrack ) {
2018-07-03 15:35:08 +02:00
this . typeOfVideo = 'CUSTOM' ;
2018-04-26 15:33:47 +02:00
} else {
2018-07-03 15:35:08 +02:00
this . typeOfVideo = this . isSendScreen ( ) ? 'SCREEN' : 'CAMERA' ;
2018-04-26 15:33:47 +02:00
}
}
2018-07-27 14:32:53 +02:00
if ( ! ! this . outboundStreamOpts . publisherProperties . filter ) {
this . filter = this . outboundStreamOpts . publisherProperties . filter ;
}
2018-04-26 15:33:47 +02:00
}
2018-05-29 18:28:58 +02:00
this . ee . on ( 'mediastream-updated' , ( ) = > {
2020-11-10 18:22:14 +01:00
this . streamManager . updateMediaStream ( this . mediaStream ! ) ;
2020-05-04 20:01:56 +02:00
logger . debug ( 'Video srcObject [' + this . mediaStream + '] updated in stream [' + this . streamId + ']' ) ;
2018-04-26 15:33:47 +02:00
} ) ;
}
2018-08-28 11:24:26 +02:00
/ * *
* Applies an audio / video filter to the stream .
*
* @param type Type of filter applied . See [ [ Filter . type ] ]
* @param options Parameters used to initialize the filter . See [ [ Filter . options ] ]
*
* @returns A Promise ( to which you can optionally subscribe to ) that is resolved to the applied filter if success and rejected with an Error object if not
* /
applyFilter ( type : string , options : Object ) : Promise < Filter > {
return new Promise ( ( resolve , reject ) = > {
2021-03-22 13:02:41 +01:00
if ( ! this . session . sessionConnected ( ) ) {
reject ( this . session . notConnectedError ( ) ) ;
}
2020-05-04 20:01:56 +02:00
logger . info ( 'Applying filter to stream ' + this . streamId ) ;
2021-03-24 12:15:19 +01:00
options = options != null ? options : { } ;
let optionsString = options ;
if ( typeof optionsString !== 'string' ) {
optionsString = JSON . stringify ( optionsString ) ;
2018-08-28 11:24:26 +02:00
}
this . session . openvidu . sendRequest (
'applyFilter' ,
2021-03-24 12:15:19 +01:00
{ streamId : this.streamId , type , options : optionsString } ,
2018-08-28 11:24:26 +02:00
( error , response ) = > {
if ( error ) {
2020-05-04 20:01:56 +02:00
logger . error ( 'Error applying filter for Stream ' + this . streamId , error ) ;
2018-08-28 11:24:26 +02:00
if ( error . code === 401 ) {
reject ( new OpenViduError ( OpenViduErrorName . OPENVIDU_PERMISSION_DENIED , "You don't have permissions to apply a filter" ) ) ;
} else {
reject ( error ) ;
}
} else {
2020-05-04 20:01:56 +02:00
logger . info ( 'Filter successfully applied on Stream ' + this . streamId ) ;
2020-11-10 18:22:14 +01:00
const oldValue : Filter = this . filter ! ;
2018-08-28 11:24:26 +02:00
this . filter = new Filter ( type , options ) ;
this . filter . stream = this ;
this . session . emitEvent ( 'streamPropertyChanged' , [ new StreamPropertyChangedEvent ( this . session , this , 'filter' , this . filter , oldValue , 'applyFilter' ) ] ) ;
this . streamManager . emitEvent ( 'streamPropertyChanged' , [ new StreamPropertyChangedEvent ( this . streamManager , this , 'filter' , this . filter , oldValue , 'applyFilter' ) ] ) ;
resolve ( this . filter ) ;
}
}
) ;
} ) ;
}
/ * *
* Removes an audio / video filter previously applied .
*
* @returns A Promise ( to which you can optionally subscribe to ) that is resolved if the previously applied filter has been successfully removed and rejected with an Error object in other case
* /
2021-03-09 15:07:45 +01:00
removeFilter ( ) : Promise < void > {
2018-08-28 11:24:26 +02:00
return new Promise ( ( resolve , reject ) = > {
2021-03-22 13:02:41 +01:00
if ( ! this . session . sessionConnected ( ) ) {
reject ( this . session . notConnectedError ( ) ) ;
}
2020-05-04 20:01:56 +02:00
logger . info ( 'Removing filter of stream ' + this . streamId ) ;
2018-08-28 11:24:26 +02:00
this . session . openvidu . sendRequest (
'removeFilter' ,
{ streamId : this.streamId } ,
( error , response ) = > {
if ( error ) {
2020-05-04 20:01:56 +02:00
logger . error ( 'Error removing filter for Stream ' + this . streamId , error ) ;
2018-08-28 11:24:26 +02:00
if ( error . code === 401 ) {
reject ( new OpenViduError ( OpenViduErrorName . OPENVIDU_PERMISSION_DENIED , "You don't have permissions to remove a filter" ) ) ;
} else {
reject ( error ) ;
}
} else {
2020-05-04 20:01:56 +02:00
logger . info ( 'Filter successfully removed from Stream ' + this . streamId ) ;
2020-11-10 18:22:14 +01:00
const oldValue = this . filter ! ;
2018-08-28 11:24:26 +02:00
delete this . filter ;
2020-11-10 18:22:14 +01:00
this . session . emitEvent ( 'streamPropertyChanged' , [ new StreamPropertyChangedEvent ( this . session , this , 'filter' , this . filter ! , oldValue , 'applyFilter' ) ] ) ;
this . streamManager . emitEvent ( 'streamPropertyChanged' , [ new StreamPropertyChangedEvent ( this . streamManager , this , 'filter' , this . filter ! , oldValue , 'applyFilter' ) ] ) ;
2018-08-28 11:24:26 +02:00
resolve ( ) ;
}
}
) ;
} ) ;
}
2019-09-11 11:16:18 +02:00
/ * *
* Returns the internal RTCPeerConnection object associated to this stream ( https : //developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection)
2020-05-04 20:01:56 +02:00
*
2019-09-11 11:16:18 +02:00
* @returns Native RTCPeerConnection Web API object
* /
2019-09-11 11:13:35 +02:00
getRTCPeerConnection ( ) : RTCPeerConnection {
return this . webRtcPeer . pc ;
}
2018-04-26 15:33:47 +02:00
/ * *
2019-09-25 12:29:26 +02:00
* Returns the internal MediaStream object associated to this stream ( https : //developer.mozilla.org/en-US/docs/Web/API/MediaStream)
2020-05-04 20:01:56 +02:00
*
2019-09-25 12:29:26 +02:00
* @returns Native MediaStream Web API object
2018-04-26 15:33:47 +02:00
* /
getMediaStream ( ) : MediaStream {
2020-11-10 18:22:14 +01:00
return this . mediaStream ! ;
2018-04-26 15:33:47 +02:00
}
2019-09-25 12:29:26 +02:00
/* Hidden methods */
2018-04-26 15:33:47 +02:00
/ * *
* @hidden
* /
setMediaStream ( mediaStream : MediaStream ) : void {
this . mediaStream = mediaStream ;
2018-05-30 12:24:18 +02:00
}
/ * *
* @hidden
* /
updateMediaStreamInVideos() {
2019-03-26 09:53:02 +01:00
this . ee . emitEvent ( 'mediastream-updated' , [ ] ) ;
2018-04-26 15:33:47 +02:00
}
/ * *
* @hidden
* /
2018-06-11 13:08:30 +02:00
getWebRtcPeer ( ) : WebRtcPeer {
2018-04-26 15:33:47 +02:00
return this . webRtcPeer ;
}
/ * *
* @hidden
* /
2018-05-30 12:24:18 +02:00
subscribeToMyRemote ( value : boolean ) : void {
this . isSubscribeToRemote = value ;
2018-04-26 15:33:47 +02:00
}
/ * *
* @hidden
* /
setOutboundStreamOptions ( outboundStreamOpts : OutboundStreamOptions ) : void {
this . outboundStreamOpts = outboundStreamOpts ;
}
/ * *
* @hidden
* /
2021-03-09 15:07:45 +01:00
subscribe ( ) : Promise < void > {
2018-04-26 15:33:47 +02:00
return new Promise ( ( resolve , reject ) = > {
2020-02-14 20:51:52 +01:00
this . initWebRtcPeerReceive ( false )
2018-04-26 15:33:47 +02:00
. then ( ( ) = > {
resolve ( ) ;
} )
. catch ( error = > {
reject ( error ) ;
} ) ;
} ) ;
}
/ * *
* @hidden
* /
2021-03-09 15:07:45 +01:00
publish ( ) : Promise < void > {
2018-04-26 15:33:47 +02:00
return new Promise ( ( resolve , reject ) = > {
2018-05-23 15:01:40 +02:00
if ( this . isLocalStreamReadyToPublish ) {
2020-02-14 20:51:52 +01:00
this . initWebRtcPeerSend ( false )
2018-04-26 15:33:47 +02:00
. then ( ( ) = > {
resolve ( ) ;
} )
. catch ( error = > {
reject ( error ) ;
} ) ;
} else {
2018-05-29 18:28:58 +02:00
this . ee . once ( 'stream-ready-to-publish' , ( ) = > {
2018-04-26 15:33:47 +02:00
this . publish ( )
. then ( ( ) = > {
resolve ( ) ;
} )
. catch ( error = > {
reject ( error ) ;
} ) ;
} ) ;
}
} ) ;
}
/ * *
* @hidden
* /
disposeWebRtcPeer ( ) : void {
2019-07-04 16:34:11 +02:00
if ( ! ! this . webRtcPeer ) {
2020-02-14 20:51:52 +01:00
this . webRtcPeer . dispose ( ) ;
this . stopWebRtcStats ( ) ;
2018-04-26 15:33:47 +02:00
}
2020-05-04 20:01:56 +02:00
logger . info ( ( ! ! this . outboundStreamOpts ? 'Outbound ' : 'Inbound ' ) + "WebRTCPeer from 'Stream' with id [" + this . streamId + '] is now closed' ) ;
2018-04-26 15:33:47 +02:00
}
/ * *
* @hidden
* /
disposeMediaStream ( ) : void {
if ( this . mediaStream ) {
this . mediaStream . getAudioTracks ( ) . forEach ( ( track ) = > {
track . stop ( ) ;
} ) ;
this . mediaStream . getVideoTracks ( ) . forEach ( ( track ) = > {
track . stop ( ) ;
} ) ;
2018-05-29 18:28:58 +02:00
delete this . mediaStream ;
2018-04-26 15:33:47 +02:00
}
2020-03-28 12:22:05 +01:00
// If subscribeToRemote local MediaStream must be stopped
if ( this . localMediaStreamWhenSubscribedToRemote ) {
this . localMediaStreamWhenSubscribedToRemote . getAudioTracks ( ) . forEach ( ( track ) = > {
track . stop ( ) ;
} ) ;
this . localMediaStreamWhenSubscribedToRemote . getVideoTracks ( ) . forEach ( ( track ) = > {
track . stop ( ) ;
} ) ;
delete this . localMediaStreamWhenSubscribedToRemote ;
}
2020-02-14 20:51:52 +01:00
if ( ! ! this . speechEvent ) {
if ( ! ! this . speechEvent . stop ) {
this . speechEvent . stop ( ) ;
}
delete this . speechEvent ;
}
2020-05-04 20:01:56 +02:00
logger . info ( ( ! ! this . outboundStreamOpts ? 'Local ' : 'Remote ' ) + "MediaStream from 'Stream' with id [" + this . streamId + '] is now disposed' ) ;
2018-04-26 15:33:47 +02:00
}
/ * *
* @hidden
* /
displayMyRemote ( ) : boolean {
return this . isSubscribeToRemote ;
}
/ * *
* @hidden
* /
isSendAudio ( ) : boolean {
return ( ! ! this . outboundStreamOpts &&
this . outboundStreamOpts . publisherProperties . audioSource !== null &&
this . outboundStreamOpts . publisherProperties . audioSource !== false ) ;
}
/ * *
* @hidden
* /
isSendVideo ( ) : boolean {
return ( ! ! this . outboundStreamOpts &&
this . outboundStreamOpts . publisherProperties . videoSource !== null &&
this . outboundStreamOpts . publisherProperties . videoSource !== false ) ;
}
/ * *
* @hidden
* /
isSendScreen ( ) : boolean {
2019-05-31 12:48:14 +02:00
let screen = this . outboundStreamOpts . publisherProperties . videoSource === 'screen' ;
2020-10-13 16:13:37 +02:00
if ( platform . isElectron ( ) ) {
2019-05-31 12:48:14 +02:00
screen = typeof this . outboundStreamOpts . publisherProperties . videoSource === 'string' &&
this . outboundStreamOpts . publisherProperties . videoSource . startsWith ( 'screen:' ) ;
}
return ! ! this . outboundStreamOpts && screen ;
2018-04-26 15:33:47 +02:00
}
/ * *
* @hidden
* /
2021-03-30 18:04:56 +02:00
enableHarkSpeakingEvent ( ) : void {
this . setHarkListenerIfNotExists ( ) ;
if ( ! this . harkSpeakingEnabled ) {
this . harkSpeakingEnabled = true ;
2019-12-05 14:18:46 +01:00
this . speechEvent . on ( 'speaking' , ( ) = > {
this . session . emitEvent ( 'publisherStartSpeaking' , [ new PublisherSpeakingEvent ( this . session , 'publisherStartSpeaking' , this . connection , this . streamId ) ] ) ;
2021-03-30 18:04:56 +02:00
this . streamManager . emitEvent ( 'publisherStartSpeaking' , [ new PublisherSpeakingEvent ( this . streamManager , 'publisherStartSpeaking' , this . connection , this . streamId ) ] ) ;
this . harkSpeakingEnabledOnce = false ; // Disable 'once' version if 'on' version was triggered
2019-12-05 14:18:46 +01:00
} ) ;
2018-04-26 15:33:47 +02:00
}
}
/ * *
* @hidden
* /
2021-03-30 18:04:56 +02:00
enableOnceHarkSpeakingEvent ( ) : void {
this . setHarkListenerIfNotExists ( ) ;
if ( ! this . harkSpeakingEnabledOnce ) {
this . harkSpeakingEnabledOnce = true ;
2019-12-05 14:18:46 +01:00
this . speechEvent . once ( 'speaking' , ( ) = > {
2021-03-30 18:04:56 +02:00
if ( this . harkSpeakingEnabledOnce ) {
2019-12-05 14:18:46 +01:00
// If the listener has been disabled in the meantime (for example by the 'on' version) do not trigger the event
this . session . emitEvent ( 'publisherStartSpeaking' , [ new PublisherSpeakingEvent ( this . session , 'publisherStartSpeaking' , this . connection , this . streamId ) ] ) ;
2021-03-30 18:04:56 +02:00
this . streamManager . emitEvent ( 'publisherStartSpeaking' , [ new PublisherSpeakingEvent ( this . streamManager , 'publisherStartSpeaking' , this . connection , this . streamId ) ] ) ;
2019-12-05 14:18:46 +01:00
}
2021-03-30 18:04:56 +02:00
this . disableHarkSpeakingEvent ( true ) ;
2018-12-07 11:22:21 +01:00
} ) ;
}
2019-12-05 14:18:46 +01:00
}
/ * *
* @hidden
* /
2021-03-30 18:04:56 +02:00
disableHarkSpeakingEvent ( disabledByOnce : boolean ) : void {
2019-12-05 14:18:46 +01:00
if ( ! ! this . speechEvent ) {
2021-03-30 18:04:56 +02:00
this . harkSpeakingEnabledOnce = false ;
2019-12-05 14:18:46 +01:00
if ( disabledByOnce ) {
2021-03-30 18:04:56 +02:00
if ( this . harkSpeakingEnabled ) {
2019-12-05 14:18:46 +01:00
// The 'on' version of this same event is enabled too. Do not remove the hark listener
return ;
}
} else {
2021-03-30 18:04:56 +02:00
this . harkSpeakingEnabled = false ;
2019-12-05 14:18:46 +01:00
}
// Shutting down the hark event
2021-03-30 18:04:56 +02:00
if ( this . harkVolumeChangeEnabled ||
this . harkVolumeChangeEnabledOnce ||
this . harkStoppedSpeakingEnabled ||
this . harkStoppedSpeakingEnabledOnce ) {
2019-12-05 14:18:46 +01:00
// Some other hark event is enabled. Cannot stop the hark process, just remove the specific listener
this . speechEvent . off ( 'speaking' ) ;
} else {
// No other hark event is enabled. We can get entirely rid of it
this . speechEvent . stop ( ) ;
delete this . speechEvent ;
}
}
}
/ * *
* @hidden
* /
2021-03-30 18:04:56 +02:00
enableHarkStoppedSpeakingEvent ( ) : void {
this . setHarkListenerIfNotExists ( ) ;
if ( ! this . harkStoppedSpeakingEnabled ) {
this . harkStoppedSpeakingEnabled = true ;
2018-12-07 11:22:21 +01:00
this . speechEvent . on ( 'stopped_speaking' , ( ) = > {
this . session . emitEvent ( 'publisherStopSpeaking' , [ new PublisherSpeakingEvent ( this . session , 'publisherStopSpeaking' , this . connection , this . streamId ) ] ) ;
2021-03-30 18:04:56 +02:00
this . streamManager . emitEvent ( 'publisherStopSpeaking' , [ new PublisherSpeakingEvent ( this . streamManager , 'publisherStopSpeaking' , this . connection , this . streamId ) ] ) ;
this . harkStoppedSpeakingEnabledOnce = false ; // Disable 'once' version if 'on' version was triggered
2018-12-07 11:22:21 +01:00
} ) ;
}
2018-04-26 15:33:47 +02:00
}
/ * *
* @hidden
* /
2021-03-30 18:04:56 +02:00
enableOnceHarkStoppedSpeakingEvent ( ) : void {
this . setHarkListenerIfNotExists ( ) ;
if ( ! this . harkStoppedSpeakingEnabledOnce ) {
this . harkStoppedSpeakingEnabledOnce = true ;
2018-12-07 11:22:21 +01:00
this . speechEvent . once ( 'stopped_speaking' , ( ) = > {
2021-03-30 18:04:56 +02:00
if ( this . harkStoppedSpeakingEnabledOnce ) {
2019-12-05 14:18:46 +01:00
// If the listener has been disabled in the meantime (for example by the 'on' version) do not trigger the event
this . session . emitEvent ( 'publisherStopSpeaking' , [ new PublisherSpeakingEvent ( this . session , 'publisherStopSpeaking' , this . connection , this . streamId ) ] ) ;
2021-03-30 18:04:56 +02:00
this . streamManager . emitEvent ( 'publisherStopSpeaking' , [ new PublisherSpeakingEvent ( this . streamManager , 'publisherStopSpeaking' , this . connection , this . streamId ) ] ) ;
2019-12-05 14:18:46 +01:00
}
2021-03-30 18:04:56 +02:00
this . disableHarkStoppedSpeakingEvent ( true ) ;
2018-12-07 11:22:21 +01:00
} ) ;
}
2018-04-26 15:33:47 +02:00
}
/ * *
2019-12-05 14:18:46 +01:00
* @hidden
* /
2021-03-30 18:04:56 +02:00
disableHarkStoppedSpeakingEvent ( disabledByOnce : boolean ) : void {
2018-12-07 11:22:21 +01:00
if ( ! ! this . speechEvent ) {
2021-03-30 18:04:56 +02:00
this . harkStoppedSpeakingEnabledOnce = false ;
2019-12-05 14:18:46 +01:00
if ( disabledByOnce ) {
2021-03-30 18:04:56 +02:00
if ( this . harkStoppedSpeakingEnabled ) {
2019-12-05 14:18:46 +01:00
// We are cancelling the 'once' listener for this event, but the 'on' version
// of this same event is enabled too. Do not remove the hark listener
return ;
}
} else {
2021-03-30 18:04:56 +02:00
this . harkStoppedSpeakingEnabled = false ;
2019-12-05 14:18:46 +01:00
}
// Shutting down the hark event
2021-03-30 18:04:56 +02:00
if ( this . harkVolumeChangeEnabled ||
this . harkVolumeChangeEnabledOnce ||
this . harkSpeakingEnabled ||
this . harkSpeakingEnabledOnce ) {
2019-12-05 14:18:46 +01:00
// Some other hark event is enabled. Cannot stop the hark process, just remove the specific listener
2018-12-07 11:22:21 +01:00
this . speechEvent . off ( 'stopped_speaking' ) ;
} else {
2019-12-05 14:18:46 +01:00
// No other hark event is enabled. We can get entirely rid of it
2018-12-07 11:22:21 +01:00
this . speechEvent . stop ( ) ;
delete this . speechEvent ;
}
}
}
/ * *
* @hidden
* /
2021-03-30 18:04:56 +02:00
enableHarkVolumeChangeEvent ( force : boolean ) : void {
if ( this . setHarkListenerIfNotExists ( ) ) {
if ( ! this . harkVolumeChangeEnabled || force ) {
this . harkVolumeChangeEnabled = true ;
2019-12-05 14:18:46 +01:00
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 ) ] ) ;
} ) ;
}
} else {
// This way whenever the MediaStream object is available, the event listener will be automatically added
2021-03-30 18:04:56 +02:00
this . harkVolumeChangeEnabled = true ;
2018-12-07 11:22:21 +01:00
}
}
/ * *
* @hidden
* /
2021-03-30 18:04:56 +02:00
enableOnceHarkVolumeChangeEvent ( force : boolean ) : void {
if ( this . setHarkListenerIfNotExists ( ) ) {
if ( ! this . harkVolumeChangeEnabledOnce || force ) {
this . harkVolumeChangeEnabledOnce = true ;
2019-12-05 14:18:46 +01:00
this . speechEvent . once ( 'volume_change' , harkEvent = > {
const oldValue = this . speechEvent . oldVolumeValue ;
const value = { newValue : harkEvent , oldValue } ;
this . speechEvent . oldVolumeValue = harkEvent ;
2021-03-30 18:04:56 +02:00
this . disableHarkVolumeChangeEvent ( true ) ;
2019-12-05 14:18:46 +01:00
this . streamManager . emitEvent ( 'streamAudioVolumeChange' , [ new StreamManagerEvent ( this . streamManager , 'streamAudioVolumeChange' , value ) ] ) ;
} ) ;
}
} else {
// This way whenever the MediaStream object is available, the event listener will be automatically added
2021-03-30 18:04:56 +02:00
this . harkVolumeChangeEnabledOnce = true ;
2018-12-07 11:22:21 +01:00
}
}
/ * *
* @hidden
* /
2021-03-30 18:04:56 +02:00
disableHarkVolumeChangeEvent ( disabledByOnce : boolean ) : void {
2018-12-07 11:22:21 +01:00
if ( ! ! this . speechEvent ) {
2021-03-30 18:04:56 +02:00
this . harkVolumeChangeEnabledOnce = false ;
2019-12-05 14:18:46 +01:00
if ( disabledByOnce ) {
2021-03-30 18:04:56 +02:00
if ( this . harkVolumeChangeEnabled ) {
2019-12-05 14:18:46 +01:00
// We are cancelling the 'once' listener for this event, but the 'on' version
// of this same event is enabled too. Do not remove the hark listener
return ;
}
} else {
2021-03-30 18:04:56 +02:00
this . harkVolumeChangeEnabled = false ;
2019-12-05 14:18:46 +01:00
}
// Shutting down the hark event
2021-03-30 18:04:56 +02:00
if ( this . harkSpeakingEnabled ||
this . harkSpeakingEnabledOnce ||
this . harkStoppedSpeakingEnabled ||
this . harkStoppedSpeakingEnabledOnce ) {
2019-12-05 14:18:46 +01:00
// Some other hark event is enabled. Cannot stop the hark process, just remove the specific listener
2018-12-07 11:22:21 +01:00
this . speechEvent . off ( 'volume_change' ) ;
} else {
2019-12-05 14:18:46 +01:00
// No other hark event is enabled. We can get entirely rid of it
2018-12-07 11:22:21 +01:00
this . speechEvent . stop ( ) ;
delete this . speechEvent ;
}
}
2018-04-26 15:33:47 +02:00
}
2018-06-07 14:55:47 +02:00
/ * *
* @hidden
* /
isLocal ( ) : boolean {
// inbound options undefined and outbound options defined
return ( ! this . inboundStreamOpts && ! ! this . outboundStreamOpts ) ;
}
2021-02-16 18:16:47 +01:00
/ * *
* @hidden
* /
getSelectedIceCandidate ( ) : Promise < any > {
return new Promise ( ( resolve , reject ) = > {
this . webRtcStats . getSelectedIceCandidateInfo ( )
. then ( report = > resolve ( report ) )
. catch ( error = > reject ( error ) ) ;
} ) ;
}
2018-06-19 09:52:04 +02:00
/ * *
* @hidden
* /
getRemoteIceCandidateList ( ) : RTCIceCandidate [ ] {
return this . webRtcPeer . remoteCandidatesQueue ;
}
/ * *
* @hidden
* /
getLocalIceCandidateList ( ) : RTCIceCandidate [ ] {
return this . webRtcPeer . localCandidatesQueue ;
}
2020-02-14 20:51:52 +01:00
/ * *
* @hidden
* /
streamIceConnectionStateBroken() {
if ( ! this . getWebRtcPeer ( ) || ! this . getRTCPeerConnection ( ) ) {
return false ;
}
2020-11-25 15:31:29 +01:00
if ( this . isLocal ( ) && ! ! this . session . openvidu . advancedConfiguration . forceMediaReconnectionAfterNetworkDrop ) {
2021-02-18 16:00:07 +01:00
logger . warn ( 'OpenVidu Browser advanced configuration option "forceMediaReconnectionAfterNetworkDrop" is enabled. Stream ' + this . streamId + ' will force a reconnection' ) ;
2020-02-19 09:35:10 +01:00
return true ;
}
2020-02-14 20:51:52 +01:00
const iceConnectionState : RTCIceConnectionState = this . getRTCPeerConnection ( ) . iceConnectionState ;
return iceConnectionState === 'disconnected' || iceConnectionState === 'failed' ;
}
2018-04-26 15:33:47 +02:00
/* Private methods */
2021-03-30 18:04:56 +02:00
private setHarkListenerIfNotExists ( ) : boolean {
2019-12-05 14:18:46 +01:00
if ( ! ! this . mediaStream ) {
if ( ! this . speechEvent ) {
2019-12-10 14:14:54 +01:00
const harkOptions = ! ! this . harkOptions ? this . harkOptions : ( this . session . openvidu . advancedConfiguration . publisherSpeakingEventsOptions || { } ) ;
harkOptions . interval = ( typeof harkOptions . interval === 'number' ) ? harkOptions.interval : 100 ;
2019-12-05 14:18:46 +01:00
harkOptions . threshold = ( typeof harkOptions . threshold === 'number' ) ? harkOptions . threshold : - 50 ;
this . speechEvent = hark ( this . mediaStream , harkOptions ) ;
}
return true ;
}
return false ;
}
2020-02-14 20:51:52 +01:00
/ * *
* @hidden
* /
2021-03-09 15:07:45 +01:00
initWebRtcPeerSend ( reconnect : boolean ) : Promise < void > {
2018-04-26 15:33:47 +02:00
return new Promise ( ( resolve , reject ) = > {
2020-02-14 20:51:52 +01:00
if ( ! reconnect ) {
this . initHarkEvents ( ) ; // Init hark events for the local stream
}
2019-12-05 14:18:46 +01:00
2018-04-26 15:33:47 +02:00
const userMediaConstraints = {
audio : this.isSendAudio ( ) ,
video : this.isSendVideo ( )
} ;
2021-03-22 19:23:03 +01:00
const options : WebRtcPeerConfiguration = {
2018-06-11 13:08:30 +02:00
mediaStream : this.mediaStream ,
2018-04-26 15:33:47 +02:00
mediaConstraints : userMediaConstraints ,
onicecandidate : this.connection.sendIceCandidate.bind ( this . connection ) ,
2021-03-22 19:23:03 +01:00
onexception : ( exceptionName : ExceptionEventName , message : string , data? : any ) = > { this . session . emitEvent ( 'exception' , [ new ExceptionEvent ( this . session , exceptionName , this , message , data ) ] ) } ,
2018-06-11 13:08:30 +02:00
iceServers : this.getIceServersConf ( ) ,
simulcast : false
2018-04-26 15:33:47 +02:00
} ;
2021-03-16 10:26:39 +01:00
const successOfferCallback = ( sdpOfferParam ) = > {
2020-05-04 20:01:56 +02:00
logger . debug ( 'Sending SDP offer to publish as '
2018-04-26 15:33:47 +02:00
+ this . streamId , sdpOfferParam ) ;
2020-02-14 20:51:52 +01:00
const method = reconnect ? 'reconnectStream' : 'publishVideo' ;
let params ;
if ( reconnect ) {
params = {
2021-03-16 10:26:39 +01:00
stream : this.streamId ,
sdpString : sdpOfferParam
2020-02-14 20:51:52 +01:00
}
} else {
let typeOfVideo = '' ;
if ( this . isSendVideo ( ) ) {
typeOfVideo = ( typeof MediaStreamTrack !== 'undefined' && this . outboundStreamOpts . publisherProperties . videoSource instanceof MediaStreamTrack ) ? 'CUSTOM' : ( this . isSendScreen ( ) ? 'SCREEN' : 'CAMERA' ) ;
}
params = {
doLoopback : this.displayMyRemote ( ) || false ,
hasAudio : this.isSendAudio ( ) ,
hasVideo : this.isSendVideo ( ) ,
audioActive : this.audioActive ,
videoActive : this.videoActive ,
typeOfVideo ,
frameRate : ! ! this . frameRate ? this . frameRate : - 1 ,
videoDimensions : JSON.stringify ( this . videoDimensions ) ,
2021-03-16 10:26:39 +01:00
filter : this.outboundStreamOpts.publisherProperties.filter ,
sdpOffer : sdpOfferParam
2020-02-14 20:51:52 +01:00
}
2018-07-03 15:35:08 +02:00
}
2020-02-14 20:51:52 +01:00
this . session . openvidu . sendRequest ( method , params , ( error , response ) = > {
2018-04-26 15:33:47 +02:00
if ( error ) {
2018-07-09 15:45:20 +02:00
if ( error . code === 401 ) {
reject ( new OpenViduError ( OpenViduErrorName . OPENVIDU_PERMISSION_DENIED , "You don't have permissions to publish" ) ) ;
} else {
reject ( 'Error on publishVideo: ' + JSON . stringify ( error ) ) ;
}
2018-04-26 15:33:47 +02:00
} else {
2021-03-16 10:26:39 +01:00
this . webRtcPeer . processRemoteAnswer ( response . sdpAnswer )
2018-04-26 15:33:47 +02:00
. then ( ( ) = > {
2018-06-15 16:05:27 +02:00
this . streamId = response . id ;
2019-01-09 17:35:34 +01:00
this . creationTime = response . createdAt ;
2018-05-29 18:28:58 +02:00
this . isLocalStreamPublished = true ;
2018-07-09 15:45:20 +02:00
this . publishedOnce = true ;
2018-05-30 12:24:18 +02:00
if ( this . displayMyRemote ( ) ) {
2020-03-28 12:22:05 +01:00
this . localMediaStreamWhenSubscribedToRemote = this . mediaStream ;
2021-03-23 15:34:33 +01:00
this . remotePeerSuccessfullyEstablished ( reconnect ) ;
2018-05-30 12:24:18 +02:00
}
2020-02-14 20:51:52 +01:00
if ( reconnect ) {
this . ee . emitEvent ( 'stream-reconnected-by-publisher' , [ ] ) ;
} else {
this . ee . emitEvent ( 'stream-created-by-publisher' , [ ] ) ;
}
2018-06-11 14:25:05 +02:00
this . initWebRtcStats ( ) ;
2020-05-04 20:01:56 +02:00
logger . info ( "'Publisher' (" + this . streamId + ") successfully " + ( reconnect ? "reconnected" : "published" ) + " to session" ) ;
2018-04-26 15:33:47 +02:00
resolve ( ) ;
} )
. catch ( error = > {
reject ( error ) ;
} ) ;
}
} ) ;
} ;
2020-02-14 20:51:52 +01:00
if ( reconnect ) {
this . disposeWebRtcPeer ( ) ;
}
2018-04-26 15:33:47 +02:00
if ( this . displayMyRemote ( ) ) {
2018-06-11 13:08:30 +02:00
this . webRtcPeer = new WebRtcPeerSendrecv ( options ) ;
2018-04-26 15:33:47 +02:00
} else {
2018-06-11 13:08:30 +02:00
this . webRtcPeer = new WebRtcPeerSendonly ( options ) ;
2018-04-26 15:33:47 +02:00
}
2020-02-14 20:51:52 +01:00
this . webRtcPeer . addIceConnectionStateChangeListener ( 'publisher of ' + this . connection . connectionId ) ;
2021-03-16 10:26:39 +01:00
this . webRtcPeer . createOffer ( ) . then ( sdpOffer = > {
this . webRtcPeer . processLocalOffer ( sdpOffer )
. then ( ( ) = > {
successOfferCallback ( sdpOffer . sdp ) ;
} ) . catch ( error = > {
reject ( new Error ( '(publish) SDP process local offer error: ' + JSON . stringify ( error ) ) ) ;
} ) ;
2018-06-11 13:08:30 +02:00
} ) . catch ( error = > {
2021-03-16 10:26:39 +01:00
reject ( new Error ( '(publish) SDP create offer error: ' + JSON . stringify ( error ) ) ) ;
2018-06-11 13:08:30 +02:00
} ) ;
2018-04-26 15:33:47 +02:00
} ) ;
}
2020-02-14 20:51:52 +01:00
/ * *
* @hidden
* /
2021-03-09 15:07:45 +01:00
initWebRtcPeerReceive ( reconnect : boolean ) : Promise < void > {
2021-03-16 10:26:39 +01:00
return new Promise ( ( resolve , reject ) = > {
this . session . openvidu . sendRequest ( 'prepareReceiveVideoFrom' , { sender : this.streamId , reconnect } , ( error , response ) = > {
if ( error ) {
reject ( new Error ( 'Error on prepareReceiveVideoFrom: ' + JSON . stringify ( error ) ) ) ;
} else {
this . completeWebRtcPeerReceive ( response . sdpOffer , reconnect )
. then ( ( ) = > {
logger . info ( "'Subscriber' (" + this . streamId + ") successfully " + ( reconnect ? "reconnected" : "subscribed" ) ) ;
2021-03-23 15:34:33 +01:00
this . remotePeerSuccessfullyEstablished ( reconnect ) ;
2021-03-16 10:26:39 +01:00
this . initWebRtcStats ( ) ;
resolve ( ) ;
} )
. catch ( error = > {
reject ( error ) ;
} ) ;
}
} ) ;
} ) ;
}
/ * *
* @hidden
* /
completeWebRtcPeerReceive ( sdpOffer : string , reconnect : boolean ) : Promise < void > {
2018-04-26 15:33:47 +02:00
return new Promise ( ( resolve , reject ) = > {
const offerConstraints = {
2018-07-03 15:35:08 +02:00
audio : this.inboundStreamOpts.hasAudio ,
video : this.inboundStreamOpts.hasVideo
2018-04-26 15:33:47 +02:00
} ;
2020-05-04 20:01:56 +02:00
logger . debug ( "'Session.subscribe(Stream)' called. Constraints of generate SDP offer" ,
2018-04-26 15:33:47 +02:00
offerConstraints ) ;
const options = {
onicecandidate : this.connection.sendIceCandidate.bind ( this . connection ) ,
2021-03-22 19:23:03 +01:00
onexception : ( exceptionName : ExceptionEventName , message : string , data? : any ) = > { this . session . emitEvent ( 'exception' , [ new ExceptionEvent ( this . session , exceptionName , this , message , data ) ] ) } ,
2018-06-07 14:55:47 +02:00
mediaConstraints : offerConstraints ,
2018-06-11 13:08:30 +02:00
iceServers : this.getIceServersConf ( ) ,
simulcast : false
2018-04-26 15:33:47 +02:00
} ;
2021-03-16 10:26:39 +01:00
const successAnswerCallback = ( sdpAnswer ) = > {
logger . debug ( 'Sending SDP answer to subscribe to '
+ this . streamId , sdpAnswer ) ;
2020-02-14 20:51:52 +01:00
const method = reconnect ? 'reconnectStream' : 'receiveVideoFrom' ;
2021-03-16 10:26:39 +01:00
const params = { } ;
2020-02-14 20:51:52 +01:00
params [ reconnect ? 'stream' : 'sender' ] = this . streamId ;
2021-03-16 10:26:39 +01:00
params [ reconnect ? 'sdpString' : 'sdpAnswer' ] = sdpAnswer ;
2020-02-14 20:51:52 +01:00
this . session . openvidu . sendRequest ( method , params , ( error , response ) = > {
2018-04-26 15:33:47 +02:00
if ( error ) {
2021-03-16 10:26:39 +01:00
reject ( new Error ( 'Error on ' + method + ' : ' + JSON . stringify ( error ) ) ) ;
2018-04-26 15:33:47 +02:00
} else {
2021-03-16 10:26:39 +01:00
resolve ( ) ;
2018-04-26 15:33:47 +02:00
}
} ) ;
} ;
2019-06-03 17:16:18 +02:00
this . webRtcPeer = new WebRtcPeerRecvonly ( options ) ;
2020-02-14 20:51:52 +01:00
this . webRtcPeer . addIceConnectionStateChangeListener ( this . streamId ) ;
2021-03-16 10:26:39 +01:00
this . webRtcPeer . processRemoteOffer ( sdpOffer )
. then ( ( ) = > {
this . webRtcPeer . createAnswer ( ) . then ( sdpAnswer = > {
this . webRtcPeer . processLocalAnswer ( sdpAnswer )
. then ( ( ) = > {
successAnswerCallback ( sdpAnswer . sdp ) ;
} ) . catch ( error = > {
reject ( new Error ( '(subscribe) SDP process local answer error: ' + JSON . stringify ( error ) ) ) ;
} ) ;
} ) . catch ( error = > {
reject ( new Error ( '(subscribe) SDP create answer error: ' + JSON . stringify ( error ) ) ) ;
} ) ;
2019-06-03 17:16:18 +02:00
} )
. catch ( error = > {
2021-03-16 10:26:39 +01:00
reject ( new Error ( '(subscribe) SDP process remote offer error: ' + JSON . stringify ( error ) ) ) ;
2018-06-11 13:08:30 +02:00
} ) ;
2018-04-26 15:33:47 +02:00
} ) ;
}
2020-06-30 10:48:55 +02:00
/ * *
* @hidden
* /
2021-03-23 15:34:33 +01:00
remotePeerSuccessfullyEstablished ( reconnect : boolean ) : void {
if ( reconnect && this . mediaStream != null ) {
// Now we can destroy the existing MediaStream
this . disposeMediaStream ( ) ;
}
2020-06-04 16:50:34 +02:00
this . mediaStream = new MediaStream ( ) ;
let receiver : RTCRtpReceiver ;
for ( receiver of this . webRtcPeer . pc . getReceivers ( ) ) {
if ( ! ! receiver . track ) {
this . mediaStream . addTrack ( receiver . track ) ;
2018-08-31 15:07:34 +02:00
}
2018-11-28 09:42:26 +01:00
}
2020-05-04 20:01:56 +02:00
logger . debug ( 'Peer remote stream' , this . mediaStream ) ;
2018-04-26 15:33:47 +02:00
2018-06-11 14:25:05 +02:00
if ( ! ! this . mediaStream ) {
2019-01-27 14:42:46 +01:00
if ( this . streamManager instanceof Subscriber ) {
// Apply SubscriberProperties.subscribeToAudio and SubscriberProperties.subscribeToVideo
if ( ! ! this . mediaStream . getAudioTracks ( ) [ 0 ] ) {
2021-03-23 15:34:33 +01:00
const enabled = reconnect ? this . audioActive : ! ! ( ( this . streamManager as Subscriber ) . properties . subscribeToAudio ) ;
2019-01-27 14:42:46 +01:00
this . mediaStream . getAudioTracks ( ) [ 0 ] . enabled = enabled ;
}
if ( ! ! this . mediaStream . getVideoTracks ( ) [ 0 ] ) {
2021-03-23 15:34:33 +01:00
const enabled = reconnect ? this . videoActive : ! ! ( ( this . streamManager as Subscriber ) . properties . subscribeToVideo ) ;
2019-01-27 14:42:46 +01:00
this . mediaStream . getVideoTracks ( ) [ 0 ] . enabled = enabled ;
}
}
2019-05-10 10:36:10 +02:00
this . updateMediaStreamInVideos ( ) ;
2019-12-05 14:18:46 +01:00
this . initHarkEvents ( ) ; // Init hark events for the remote stream
}
}
private initHarkEvents ( ) : void {
2020-11-10 18:22:14 +01:00
if ( ! ! this . mediaStream ! . getAudioTracks ( ) [ 0 ] ) {
2019-12-05 14:18:46 +01:00
// Hark events can only be set if audio track is available
2021-03-30 18:04:56 +02:00
if ( this . session . anySpeechEventListenerEnabled ( 'publisherStartSpeaking' , true , this . streamManager ) ) {
this . enableOnceHarkSpeakingEvent ( ) ;
}
if ( this . session . anySpeechEventListenerEnabled ( 'publisherStartSpeaking' , false , this . streamManager ) ) {
this . enableHarkSpeakingEvent ( ) ;
}
if ( this . session . anySpeechEventListenerEnabled ( 'publisherStopSpeaking' , true , this . streamManager ) ) {
this . enableOnceHarkStoppedSpeakingEvent ( ) ;
}
if ( this . session . anySpeechEventListenerEnabled ( 'publisherStopSpeaking' , false , this . streamManager ) ) {
this . enableHarkStoppedSpeakingEvent ( ) ;
2019-12-05 14:18:46 +01:00
}
2021-03-30 18:04:56 +02:00
if ( this . harkVolumeChangeEnabledOnce ) {
this . enableOnceHarkVolumeChangeEvent ( true ) ;
2019-12-05 14:18:46 +01:00
}
2021-03-30 18:04:56 +02:00
if ( this . harkVolumeChangeEnabled ) {
this . enableHarkVolumeChangeEvent ( true ) ;
2018-06-11 14:25:05 +02:00
}
}
2018-04-26 15:33:47 +02:00
}
private initWebRtcStats ( ) : void {
this . webRtcStats = new WebRtcStats ( this ) ;
this . webRtcStats . initWebRtcStats ( ) ;
2019-05-29 14:54:37 +02:00
2019-03-07 14:13:57 +01:00
//TODO: send common webrtc stats from client to openvidu-server
/ * i f ( t h i s . s e s s i o n . o p e n v i d u . w e b r t c S t a t s I n t e r v a l > 0 ) {
setInterval ( ( ) = > {
this . gatherStatsForPeer ( ) . then ( jsonStats = > {
const body = {
sessionId : this.session.sessionId ,
participantPrivateId : this.connection.rpcSessionId ,
stats : jsonStats
}
var xhr = new XMLHttpRequest ( ) ;
xhr . open ( 'POST' , this . session . openvidu . httpUri + '/elasticsearch/webrtc-stats' , true ) ;
xhr . setRequestHeader ( 'Content-Type' , 'application/json' ) ;
xhr . send ( JSON . stringify ( body ) ) ;
} )
} , this . session . openvidu . webrtcStatsInterval * 1000 ) ;
} * /
2018-04-26 15:33:47 +02:00
}
private stopWebRtcStats ( ) : void {
if ( ! ! this . webRtcStats && this . webRtcStats . isEnabled ( ) ) {
this . webRtcStats . stopWebRtcStats ( ) ;
}
}
2018-06-07 14:55:47 +02:00
private getIceServersConf ( ) : RTCIceServer [ ] | undefined {
2018-06-08 11:07:38 +02:00
let returnValue ;
if ( ! ! this . session . openvidu . advancedConfiguration . iceServers ) {
returnValue = this . session . openvidu . advancedConfiguration . iceServers === 'freeice' ?
undefined :
this . session . openvidu . advancedConfiguration . iceServers ;
2018-06-25 11:43:48 +02:00
} else if ( this . session . openvidu . iceServers ) {
returnValue = this . session . openvidu . iceServers ;
2018-06-08 11:07:38 +02:00
} else {
returnValue = undefined ;
}
return returnValue ;
2018-04-26 15:33:47 +02:00
}
2019-03-07 14:13:57 +01:00
private gatherStatsForPeer ( ) : Promise < any > {
return new Promise ( ( resolve , reject ) = > {
if ( this . isLocal ( ) ) {
// Publisher stream stats
this . getRTCPeerConnection ( ) . getSenders ( ) . forEach ( sender = > sender . getStats ( )
. then (
response = > {
response . forEach ( report = > {
if ( this . isReportWanted ( report ) ) {
const finalReport = { } ;
finalReport [ 'type' ] = report . type ;
finalReport [ 'timestamp' ] = report . timestamp ;
finalReport [ 'id' ] = report . id ;
// Common to Chrome, Firefox and Safari
if ( report . type === 'outbound-rtp' ) {
finalReport [ 'ssrc' ] = report . ssrc ;
finalReport [ 'firCount' ] = report . firCount ;
finalReport [ 'pliCount' ] = report . pliCount ;
finalReport [ 'nackCount' ] = report . nackCount ;
finalReport [ 'qpSum' ] = report . qpSum ;
// Set media type
if ( ! ! report . kind ) {
finalReport [ 'mediaType' ] = report . kind ;
} else if ( ! ! report . mediaType ) {
finalReport [ 'mediaType' ] = report . mediaType ;
} else {
// Safari does not have 'mediaType' defined for inbound-rtp. Must be inferred from 'id' field
finalReport [ 'mediaType' ] = ( report . id . indexOf ( 'VideoStream' ) !== - 1 ) ? 'video' : 'audio' ;
}
if ( finalReport [ 'mediaType' ] === 'video' ) {
finalReport [ 'framesEncoded' ] = report . framesEncoded ;
}
finalReport [ 'packetsSent' ] = report . packetsSent ;
finalReport [ 'bytesSent' ] = report . bytesSent ;
}
// Only for Chrome and Safari
if ( report . type === 'candidate-pair' && report . totalRoundTripTime !== undefined ) {
// This is the final selected candidate pair
finalReport [ 'availableOutgoingBitrate' ] = report . availableOutgoingBitrate ;
finalReport [ 'rtt' ] = report . currentRoundTripTime ;
finalReport [ 'averageRtt' ] = report . totalRoundTripTime / report . responsesReceived ;
}
// Only for Firefox >= 66.0
if ( report . type === 'remote-inbound-rtp' || report . type === 'remote-outbound-rtp' ) {
}
2020-05-04 20:01:56 +02:00
logger . log ( finalReport ) ;
2019-03-07 14:13:57 +01:00
}
} ) ;
} ) ) ;
} else {
// Subscriber stream stats
this . getRTCPeerConnection ( ) . getReceivers ( ) . forEach ( receiver = > receiver . getStats ( )
. then (
response = > {
response . forEach ( report = > {
if ( this . isReportWanted ( report ) ) {
const finalReport = { } ;
finalReport [ 'type' ] = report . type ;
finalReport [ 'timestamp' ] = report . timestamp ;
finalReport [ 'id' ] = report . id ;
// Common to Chrome, Firefox and Safari
if ( report . type === 'inbound-rtp' ) {
finalReport [ 'ssrc' ] = report . ssrc ;
finalReport [ 'firCount' ] = report . firCount ;
finalReport [ 'pliCount' ] = report . pliCount ;
finalReport [ 'nackCount' ] = report . nackCount ;
finalReport [ 'qpSum' ] = report . qpSum ;
// Set media type
if ( ! ! report . kind ) {
finalReport [ 'mediaType' ] = report . kind ;
} else if ( ! ! report . mediaType ) {
finalReport [ 'mediaType' ] = report . mediaType ;
} else {
// Safari does not have 'mediaType' defined for inbound-rtp. Must be inferred from 'id' field
finalReport [ 'mediaType' ] = ( report . id . indexOf ( 'VideoStream' ) !== - 1 ) ? 'video' : 'audio' ;
}
if ( finalReport [ 'mediaType' ] === 'video' ) {
finalReport [ 'framesDecoded' ] = report . framesDecoded ;
}
finalReport [ 'packetsReceived' ] = report . packetsReceived ;
finalReport [ 'packetsLost' ] = report . packetsLost ;
finalReport [ 'jitter' ] = report . jitter ;
finalReport [ 'bytesReceived' ] = report . bytesReceived ;
}
// Only for Chrome and Safari
if ( report . type === 'candidate-pair' && report . totalRoundTripTime !== undefined ) {
// This is the final selected candidate pair
finalReport [ 'availableIncomingBitrate' ] = report . availableIncomingBitrate ;
finalReport [ 'rtt' ] = report . currentRoundTripTime ;
finalReport [ 'averageRtt' ] = report . totalRoundTripTime / report . responsesReceived ;
}
// Only for Firefox >= 66.0
if ( report . type === 'remote-inbound-rtp' || report . type === 'remote-outbound-rtp' ) {
}
2020-05-04 20:01:56 +02:00
logger . log ( finalReport ) ;
2019-03-07 14:13:57 +01:00
}
} )
} )
)
}
} ) ;
}
private isReportWanted ( report : any ) : boolean {
return report . type === 'inbound-rtp' && ! this . isLocal ( ) ||
report . type === 'outbound-rtp' && this . isLocal ( ) ||
( report . type === 'candidate-pair' && report . nominated && report . bytesSent > 0 ) ;
}
2018-04-26 15:33:47 +02:00
}