2018-04-26 15:33:47 +02:00
/ *
2022-01-13 11:18:47 +01:00
* ( C ) Copyright 2017 - 2022 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 { OpenVidu } from './OpenVidu' ;
import { Session } from './Session' ;
import { Stream } from './Stream' ;
2018-05-29 18:28:58 +02:00
import { StreamManager } from './StreamManager' ;
2018-04-26 15:33:47 +02:00
import { PublisherProperties } from '../OpenViduInternal/Interfaces/Public/PublisherProperties' ;
2022-01-10 12:46:39 +01:00
import { PublisherEventMap } from '../OpenViduInternal/Events/EventMap/PublisherEventMap' ;
2018-04-26 15:33:47 +02:00
import { StreamEvent } from '../OpenViduInternal/Events/StreamEvent' ;
2018-07-03 15:35:08 +02:00
import { StreamPropertyChangedEvent } from '../OpenViduInternal/Events/StreamPropertyChangedEvent' ;
2018-04-26 15:33:47 +02:00
import { OpenViduError , OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError' ;
2018-05-08 13:01:34 +02:00
import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode' ;
2020-05-04 20:01:56 +02:00
import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger' ;
2020-10-13 16:13:37 +02:00
import { PlatformUtils } from '../OpenViduInternal/Utils/Platform' ;
2020-05-04 20:01:56 +02:00
/ * *
* @hidden
* /
const logger : OpenViduLogger = OpenViduLogger . getInstance ( ) ;
2018-12-04 09:55:00 +01:00
2020-10-13 16:13:37 +02:00
/ * *
* @hidden
* /
2020-11-26 13:17:55 +01:00
let platform : PlatformUtils ;
2020-10-13 16:13:37 +02:00
2018-04-26 15:33:47 +02:00
/ * *
* Packs local media streams . Participants can publish it to a session . Initialized with [ [ OpenVidu . initPublisher ] ] method
2020-05-04 20:01:56 +02:00
*
2020-02-23 12:05:05 +01:00
* # # # Available event listeners ( and events dispatched )
2020-05-04 20:01:56 +02:00
*
2020-02-23 12:05:05 +01:00
* - accessAllowed
* - accessDenied
* - accessDialogOpened
* - accessDialogClosed
* - streamCreated ( [ [ StreamEvent ] ] )
* - streamDestroyed ( [ [ StreamEvent ] ] )
2021-03-30 18:04:56 +02:00
* - _All events inherited from [ [ StreamManager ] ] class_
2018-04-26 15:33:47 +02:00
* /
2018-05-29 18:28:58 +02:00
export class Publisher extends StreamManager {
2018-04-26 15:33:47 +02:00
/ * *
* Whether the Publisher has been granted access to the requested input devices or not
* /
accessAllowed = false ;
2018-05-30 12:24:18 +02:00
/ * *
2018-06-01 14:39:38 +02:00
* Whether you have called [ [ Publisher . subscribeToRemote ] ] with value ` true ` or ` false ` ( * false * by default )
2018-05-30 12:24:18 +02:00
* /
isSubscribedToRemote = false ;
2018-04-26 15:33:47 +02:00
/ * *
* The [ [ Session ] ] to which the Publisher belongs
* /
session : Session ; // Initialized by Session.publish(Publisher)
2018-05-29 18:28:58 +02:00
private accessDenied = false ;
2020-06-30 10:48:55 +02:00
protected properties : PublisherProperties ;
2018-04-26 15:33:47 +02:00
private permissionDialogTimeout : NodeJS.Timer ;
2018-07-03 15:35:08 +02:00
/ * *
2018-10-09 16:39:57 +02:00
* @hidden
2018-07-03 15:35:08 +02:00
* /
openvidu : OpenVidu ;
/ * *
* @hidden
* /
videoReference : HTMLVideoElement ;
2018-04-26 15:33:47 +02:00
/ * *
* @hidden
* /
2018-07-03 15:35:08 +02:00
screenShareResizeInterval : NodeJS.Timer ;
/ * *
* @hidden
* /
constructor ( targEl : string | HTMLElement , properties : PublisherProperties , openvidu : OpenVidu ) {
2018-05-29 18:28:58 +02:00
super ( new Stream ( ( ! ! openvidu . session ) ? openvidu.session : new Session ( openvidu ) , { publisherProperties : properties , mediaConstraints : { } } ) , targEl ) ;
2020-11-26 13:17:55 +01:00
platform = PlatformUtils . getInstance ( ) ;
2018-04-26 15:33:47 +02:00
this . properties = properties ;
2018-07-03 15:35:08 +02:00
this . openvidu = openvidu ;
2018-04-26 15:33:47 +02:00
2018-07-05 17:50:49 +02:00
this . stream . ee . on ( 'local-stream-destroyed' , ( reason : string ) = > {
2018-07-09 15:45:20 +02:00
this . stream . isLocalStreamPublished = false ;
2018-04-26 15:33:47 +02:00
const streamEvent = new StreamEvent ( true , this , 'streamDestroyed' , this . stream , reason ) ;
2018-07-03 15:35:08 +02:00
this . emitEvent ( 'streamDestroyed' , [ streamEvent ] ) ;
streamEvent . callDefaultBehavior ( ) ;
2018-04-26 15:33:47 +02:00
} ) ;
}
2018-06-01 14:39:38 +02:00
2018-04-26 15:33:47 +02:00
/ * *
* Publish or unpublish the audio stream ( if available ) . Calling this method twice in a row passing same value will have no effect
2018-07-03 15:35:08 +02:00
*
* # # # # Events dispatched
*
2018-12-07 11:47:22 +01:00
* > _Only if ` Session.publish(Publisher) ` has been called for this Publisher_
*
2018-07-03 15:35:08 +02:00
* The [ [ Session ] ] object of the local participant will dispatch a ` streamPropertyChanged ` event with ` changedProperty ` set to ` "audioActive" ` and ` reason ` set to ` "publishAudio" `
* The [ [ Publisher ] ] object of the local participant will also dispatch the exact same event
*
* The [ [ Session ] ] object of every other participant connected to the session will dispatch a ` streamPropertyChanged ` event with ` changedProperty ` set to ` "audioActive" ` and ` reason ` set to ` "publishAudio" `
* The respective [ [ Subscriber ] ] object of every other participant receiving this Publisher ' s stream will also dispatch the exact same event
*
* See [ [ StreamPropertyChangedEvent ] ] to learn more .
*
2018-04-26 15:33:47 +02:00
* @param value ` true ` to publish the audio stream , ` false ` to unpublish it
* /
publishAudio ( value : boolean ) : void {
2018-07-03 15:35:08 +02:00
if ( this . stream . audioActive !== value ) {
2020-11-10 18:22:14 +01:00
const affectedMediaStream : MediaStream = this . stream . displayMyRemote ( ) ? this . stream . localMediaStreamWhenSubscribedToRemote ! : this . stream . getMediaStream ( ) ;
2020-03-28 12:22:05 +01:00
affectedMediaStream . getAudioTracks ( ) . forEach ( ( track ) = > {
2018-07-03 15:35:08 +02:00
track . enabled = value ;
} ) ;
2018-12-07 11:47:22 +01:00
if ( ! ! this . session && ! ! this . stream . streamId ) {
this . session . openvidu . sendRequest (
'streamPropertyChanged' ,
{
streamId : this.stream.streamId ,
property : 'audioActive' ,
newValue : value ,
reason : 'publishAudio'
} ,
( error , response ) = > {
if ( error ) {
2020-05-04 20:01:56 +02:00
logger . error ( "Error sending 'streamPropertyChanged' event" , error ) ;
2018-12-07 11:47:22 +01:00
} else {
this . session . emitEvent ( 'streamPropertyChanged' , [ new StreamPropertyChangedEvent ( this . session , this . stream , 'audioActive' , value , ! value , 'publishAudio' ) ] ) ;
this . emitEvent ( 'streamPropertyChanged' , [ new StreamPropertyChangedEvent ( this , this . stream , 'audioActive' , value , ! value , 'publishAudio' ) ] ) ;
2020-10-08 15:46:03 +02:00
this . session . sendVideoData ( this . stream . streamManager ) ;
2018-12-07 11:47:22 +01:00
}
} ) ;
}
2018-07-03 15:35:08 +02:00
this . stream . audioActive = value ;
2020-05-04 20:01:56 +02:00
logger . info ( "'Publisher' has " + ( value ? 'published' : 'unpublished' ) + ' its audio stream' ) ;
2018-07-03 15:35:08 +02:00
}
2018-04-26 15:33:47 +02:00
}
2018-06-01 14:39:38 +02:00
2018-04-26 15:33:47 +02:00
/ * *
* Publish or unpublish the video stream ( if available ) . Calling this method twice in a row passing same value will have no effect
2018-07-03 15:35:08 +02:00
*
* # # # # Events dispatched
*
2018-12-07 11:47:22 +01:00
* > _Only if ` Session.publish(Publisher) ` has been called for this Publisher_
*
2018-07-03 15:35:08 +02:00
* The [ [ Session ] ] object of the local participant will dispatch a ` streamPropertyChanged ` event with ` changedProperty ` set to ` "videoActive" ` and ` reason ` set to ` "publishVideo" `
* The [ [ Publisher ] ] object of the local participant will also dispatch the exact same event
*
* The [ [ Session ] ] object of every other participant connected to the session will dispatch a ` streamPropertyChanged ` event with ` changedProperty ` set to ` "videoActive" ` and ` reason ` set to ` "publishVideo" `
* The respective [ [ Subscriber ] ] object of every other participant receiving this Publisher ' s stream will also dispatch the exact same event
*
* See [ [ StreamPropertyChangedEvent ] ] to learn more .
*
2018-04-26 15:33:47 +02:00
* @param value ` true ` to publish the video stream , ` false ` to unpublish it
* /
publishVideo ( value : boolean ) : void {
2018-07-03 15:35:08 +02:00
if ( this . stream . videoActive !== value ) {
2020-11-10 18:22:14 +01:00
const affectedMediaStream : MediaStream = this . stream . displayMyRemote ( ) ? this . stream . localMediaStreamWhenSubscribedToRemote ! : this . stream . getMediaStream ( ) ;
2020-03-28 12:22:05 +01:00
affectedMediaStream . getVideoTracks ( ) . forEach ( ( track ) = > {
2018-07-03 15:35:08 +02:00
track . enabled = value ;
} ) ;
2018-12-07 11:47:22 +01:00
if ( ! ! this . session && ! ! this . stream . streamId ) {
this . session . openvidu . sendRequest (
'streamPropertyChanged' ,
{
streamId : this.stream.streamId ,
property : 'videoActive' ,
newValue : value ,
reason : 'publishVideo'
} ,
( error , response ) = > {
if ( error ) {
2020-05-04 20:01:56 +02:00
logger . error ( "Error sending 'streamPropertyChanged' event" , error ) ;
2018-12-07 11:47:22 +01:00
} else {
this . session . emitEvent ( 'streamPropertyChanged' , [ new StreamPropertyChangedEvent ( this . session , this . stream , 'videoActive' , value , ! value , 'publishVideo' ) ] ) ;
this . emitEvent ( 'streamPropertyChanged' , [ new StreamPropertyChangedEvent ( this , this . stream , 'videoActive' , value , ! value , 'publishVideo' ) ] ) ;
2020-10-08 15:46:03 +02:00
this . session . sendVideoData ( this . stream . streamManager ) ;
2018-12-07 11:47:22 +01:00
}
} ) ;
}
2018-07-03 15:35:08 +02:00
this . stream . videoActive = value ;
2020-05-04 20:01:56 +02:00
logger . info ( "'Publisher' has " + ( value ? 'published' : 'unpublished' ) + ' its video stream' ) ;
2018-07-03 15:35:08 +02:00
}
2018-04-26 15:33:47 +02:00
}
2018-06-01 14:39:38 +02:00
2018-04-26 15:33:47 +02:00
/ * *
2018-07-03 15:35:08 +02:00
* Call this method before [ [ Session . publish ] ] if you prefer to subscribe to your Publisher ' s remote stream instead of using the local stream , as any other user would do .
2018-04-26 15:33:47 +02:00
* /
2018-05-30 12:24:18 +02:00
subscribeToRemote ( value? : boolean ) : void {
value = ( value !== undefined ) ? value : true ;
this . isSubscribedToRemote = value ;
this . stream . subscribeToMyRemote ( value ) ;
2018-04-26 15:33:47 +02:00
}
/ * *
* See [ [ EventDispatcher . on ] ]
* /
2022-01-10 12:46:39 +01:00
on < K extends keyof PublisherEventMap > ( type : K , handler : ( event : PublisherEventMap [ K ] ) = > void ) : this {
super . on ( < any > type , handler ) ;
2018-04-26 15:33:47 +02:00
if ( type === 'streamCreated' ) {
2018-05-23 15:01:40 +02:00
if ( ! ! this . stream && this . stream . isLocalStreamPublished ) {
2018-07-03 15:35:08 +02:00
this . emitEvent ( 'streamCreated' , [ new StreamEvent ( false , this , 'streamCreated' , this . stream , '' ) ] ) ;
2018-04-26 15:33:47 +02:00
} else {
2018-05-29 18:28:58 +02:00
this . stream . ee . on ( 'stream-created-by-publisher' , ( ) = > {
2018-07-03 15:35:08 +02:00
this . emitEvent ( 'streamCreated' , [ new StreamEvent ( false , this , 'streamCreated' , this . stream , '' ) ] ) ;
2018-04-26 15:33:47 +02:00
} ) ;
}
}
if ( type === 'accessAllowed' ) {
2018-05-23 15:01:40 +02:00
if ( this . accessAllowed ) {
2018-07-03 15:35:08 +02:00
this . emitEvent ( 'accessAllowed' , [ ] ) ;
2018-04-26 15:33:47 +02:00
}
}
if ( type === 'accessDenied' ) {
2018-05-23 15:01:40 +02:00
if ( this . accessDenied ) {
2018-07-03 15:35:08 +02:00
this . emitEvent ( 'accessDenied' , [ ] ) ;
2018-04-26 15:33:47 +02:00
}
}
return this ;
}
/ * *
* See [ [ EventDispatcher . once ] ]
* /
2022-01-10 12:46:39 +01:00
once < K extends keyof PublisherEventMap > ( type : K , handler : ( event : PublisherEventMap [ K ] ) = > void ) : this {
super . once ( < any > type , handler ) ;
2018-04-26 15:33:47 +02:00
if ( type === 'streamCreated' ) {
2018-05-23 15:01:40 +02:00
if ( ! ! this . stream && this . stream . isLocalStreamPublished ) {
2018-07-03 15:35:08 +02:00
this . emitEvent ( 'streamCreated' , [ new StreamEvent ( false , this , 'streamCreated' , this . stream , '' ) ] ) ;
2018-04-26 15:33:47 +02:00
} else {
2018-05-29 18:28:58 +02:00
this . stream . ee . once ( 'stream-created-by-publisher' , ( ) = > {
2018-07-03 15:35:08 +02:00
this . emitEvent ( 'streamCreated' , [ new StreamEvent ( false , this , 'streamCreated' , this . stream , '' ) ] ) ;
2018-04-26 15:33:47 +02:00
} ) ;
}
}
if ( type === 'accessAllowed' ) {
2018-05-23 15:01:40 +02:00
if ( this . accessAllowed ) {
2018-07-03 15:35:08 +02:00
this . emitEvent ( 'accessAllowed' , [ ] ) ;
2018-04-26 15:33:47 +02:00
}
}
if ( type === 'accessDenied' ) {
2018-05-23 15:01:40 +02:00
if ( this . accessDenied ) {
2018-07-03 15:35:08 +02:00
this . emitEvent ( 'accessDenied' , [ ] ) ;
2018-04-26 15:33:47 +02:00
}
}
return this ;
}
2022-01-10 12:46:39 +01:00
/ * *
* See [ [ EventDispatcher . off ] ]
* /
off < K extends keyof PublisherEventMap > ( type : K , handler ? : ( event : PublisherEventMap [ K ] ) = > void ) : this {
super . off ( < any > type , handler ) ;
return this ;
}
2019-09-19 15:33:37 +02:00
/ * *
* Replaces the current video or audio track with a different one . This allows you to replace an ongoing track with a different one
* without having to renegotiate the whole WebRTC connection ( that is , initializing a new Publisher , unpublishing the previous one
* and publishing the new one ) .
2020-05-04 20:01:56 +02:00
*
2019-09-19 15:33:37 +02:00
* You can get this new MediaStreamTrack by using the native Web API or simply with [ [ OpenVidu . getUserMedia ] ] method .
2020-05-04 20:01:56 +02:00
*
2021-03-25 14:21:59 +01:00
* * * WARNING : this method has been proven to work in the majority of cases , but there may be some combinations of published / replaced tracks that may be incompatible
* between them and break the connection in OpenVidu Server . A complete renegotiation may be the only solution in this case .
* Visit [ RTCRtpSender . replaceTrack ] ( https : //developer.mozilla.org/en-US/docs/Web/API/RTCRtpSender/replaceTrack) documentation for further details.**
2020-05-04 20:01:56 +02:00
*
2021-03-25 14:21:59 +01:00
* @param track The [ MediaStreamTrack ] ( https : //developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack) object to replace the current one.
* If it is an audio track , the current audio track will be the replaced one . If it is a video track , the current video track will be the replaced one .
2020-05-04 20:01:56 +02:00
*
2019-09-19 15:33:37 +02:00
* @returns A Promise ( to which you can optionally subscribe to ) that is resolved if the track was successfully replaced and rejected with an Error object in other case
* /
2021-03-25 16:29:38 +01:00
async replaceTrack ( track : MediaStreamTrack ) : Promise < void > {
2020-04-01 17:48:22 +02:00
2021-03-25 13:58:56 +01:00
const replaceTrackInMediaStream = ( ) : Promise < void > = > {
return new Promise ( ( resolve , reject ) = > {
const mediaStream : MediaStream = this . stream . displayMyRemote ( ) ? this . stream . localMediaStreamWhenSubscribedToRemote ! : this . stream . getMediaStream ( ) ;
let removedTrack : MediaStreamTrack ;
if ( track . kind === 'video' ) {
removedTrack = mediaStream . getVideoTracks ( ) [ 0 ] ;
} else {
removedTrack = mediaStream . getAudioTracks ( ) [ 0 ] ;
}
mediaStream . removeTrack ( removedTrack ) ;
removedTrack . stop ( ) ;
mediaStream . addTrack ( track ) ;
if ( track . kind === 'video' && this . stream . isLocalStreamPublished ) {
this . openvidu . sendNewVideoDimensionsIfRequired ( this , 'trackReplaced' , 50 , 30 ) ;
this . session . sendVideoData ( this . stream . streamManager , 5 , true , 5 ) ;
}
resolve ( ) ;
} ) ;
2020-04-01 17:48:22 +02:00
}
2021-03-25 13:58:56 +01:00
const replaceTrackInRtcRtpSender = ( ) : Promise < void > = > {
return new Promise ( ( resolve , reject ) = > {
2020-04-01 17:48:22 +02:00
const senders : RTCRtpSender [ ] = this . stream . getRTCPeerConnection ( ) . getSenders ( ) ;
let sender : RTCRtpSender | undefined ;
2019-09-25 15:23:18 +02:00
if ( track . kind === 'video' ) {
2020-04-01 17:48:22 +02:00
sender = senders . find ( s = > ! ! s . track && s . track . kind === 'video' ) ;
if ( ! sender ) {
2021-03-24 19:39:49 +01:00
reject ( new Error ( 'There\'s no replaceable track for that kind of MediaStreamTrack in this Publisher object' ) ) ;
return ;
2020-04-01 17:48:22 +02:00
}
} else if ( track . kind === 'audio' ) {
sender = senders . find ( s = > ! ! s . track && s . track . kind === 'audio' ) ;
if ( ! sender ) {
2021-03-24 19:39:49 +01:00
reject ( new Error ( 'There\'s no replaceable track for that kind of MediaStreamTrack in this Publisher object' ) ) ;
return ;
2020-04-01 17:48:22 +02:00
}
2019-09-25 15:23:18 +02:00
} else {
2020-04-01 17:48:22 +02:00
reject ( new Error ( 'Unknown track kind ' + track . kind ) ) ;
2021-03-24 19:39:49 +01:00
return ;
2019-09-25 15:23:18 +02:00
}
2021-03-24 19:39:49 +01:00
( sender as RTCRtpSender ) . replaceTrack ( track ) . then ( ( ) = > {
2020-04-01 17:48:22 +02:00
resolve ( ) ;
} ) . catch ( error = > {
reject ( error ) ;
} ) ;
2021-03-25 13:58:56 +01:00
} ) ;
}
2021-03-25 16:29:38 +01:00
// Set field "enabled" of the new track to the previous value
const trackOriginalEnabledValue : boolean = track . enabled ;
if ( track . kind === 'video' ) {
track . enabled = this . stream . videoActive ;
} else if ( track . kind === 'audio' ) {
track . enabled = this . stream . audioActive ;
}
try {
2021-03-25 13:58:56 +01:00
if ( this . stream . isLocalStreamPublished ) {
// Only if the Publisher has been published is necessary to call native Web API RTCRtpSender.replaceTrack
// If it has not been published yet, replacing it on the MediaStream object is enough
2021-03-25 16:29:38 +01:00
await replaceTrackInRtcRtpSender ( ) ;
return await replaceTrackInMediaStream ( ) ;
2020-04-01 17:48:22 +02:00
} else {
2021-03-25 13:58:56 +01:00
// Publisher not published. Simply replace the track on the local MediaStream
2021-03-25 16:29:38 +01:00
return await replaceTrackInMediaStream ( ) ;
2020-04-01 17:48:22 +02:00
}
2021-03-25 16:29:38 +01:00
} catch ( error ) {
track . enabled = trackOriginalEnabledValue ;
throw error ;
}
2019-09-19 15:33:37 +02:00
}
2018-04-26 15:33:47 +02:00
/* Hidden methods */
/ * *
* @hidden
* /
2021-03-09 15:07:45 +01:00
initialize ( ) : Promise < void > {
2018-04-26 15:33:47 +02:00
return new Promise ( ( resolve , reject ) = > {
2019-02-25 11:25:53 +01:00
let constraints : MediaStreamConstraints = { } ;
let constraintsAux : MediaStreamConstraints = { } ;
2022-01-13 13:50:01 +01:00
const timeForDialogEvent = 2000 ;
2019-02-25 11:25:53 +01:00
let startTime ;
2018-04-26 15:33:47 +02:00
const errorCallback = ( openViduError : OpenViduError ) = > {
2018-05-23 15:01:40 +02:00
this . accessDenied = true ;
this . accessAllowed = false ;
2021-06-22 12:58:00 +02:00
logger . error ( ` Publisher initialization failed. ${ openViduError . name } : ${ openViduError . message } ` )
2018-04-26 15:33:47 +02:00
reject ( openViduError ) ;
} ;
const successCallback = ( mediaStream : MediaStream ) = > {
2018-05-23 15:01:40 +02:00
this . accessAllowed = true ;
this . accessDenied = false ;
2018-04-26 15:33:47 +02:00
2019-05-10 10:36:10 +02:00
if ( typeof MediaStreamTrack !== 'undefined' && this . properties . audioSource instanceof MediaStreamTrack ) {
2018-04-26 15:33:47 +02:00
mediaStream . removeTrack ( mediaStream . getAudioTracks ( ) [ 0 ] ) ;
mediaStream . addTrack ( ( < MediaStreamTrack > this . properties . audioSource ) ) ;
}
2019-05-10 10:36:10 +02:00
if ( typeof MediaStreamTrack !== 'undefined' && this . properties . videoSource instanceof MediaStreamTrack ) {
2018-04-26 15:33:47 +02:00
mediaStream . removeTrack ( mediaStream . getVideoTracks ( ) [ 0 ] ) ;
mediaStream . addTrack ( ( < MediaStreamTrack > this . properties . videoSource ) ) ;
}
// Apply PublisherProperties.publishAudio and PublisherProperties.publishVideo
if ( ! ! mediaStream . getAudioTracks ( ) [ 0 ] ) {
2018-07-19 17:31:30 +02:00
const enabled = ( this . stream . audioActive !== undefined && this . stream . audioActive !== null ) ? this . stream . audioActive : ! ! this . stream . outboundStreamOpts . publisherProperties . publishAudio ;
mediaStream . getAudioTracks ( ) [ 0 ] . enabled = enabled ;
2018-04-26 15:33:47 +02:00
}
if ( ! ! mediaStream . getVideoTracks ( ) [ 0 ] ) {
2018-07-19 17:31:30 +02:00
const enabled = ( this . stream . videoActive !== undefined && this . stream . videoActive !== null ) ? this . stream . videoActive : ! ! this . stream . outboundStreamOpts . publisherProperties . publishVideo ;
mediaStream . getVideoTracks ( ) [ 0 ] . enabled = enabled ;
2018-04-26 15:33:47 +02:00
}
2020-06-30 10:48:55 +02:00
this . initializeVideoReference ( mediaStream ) ;
2019-05-10 10:36:10 +02:00
2019-06-03 17:16:18 +02:00
if ( ! this . stream . displayMyRemote ( ) ) {
2018-05-30 12:24:18 +02:00
// When we are subscribed to our remote we don't still set the MediaStream object in the video elements to
// avoid early 'streamPlaying' event
this . stream . updateMediaStreamInVideos ( ) ;
}
2018-05-29 18:28:58 +02:00
delete this . firstVideoElement ;
2018-04-26 15:33:47 +02:00
2018-07-04 12:25:56 +02:00
if ( this . stream . isSendVideo ( ) ) {
2021-03-24 19:39:49 +01:00
// Has video track
this . getVideoDimensions ( mediaStream ) . then ( dimensions = > {
this . stream . videoDimensions = {
width : dimensions.width ,
height : dimensions.height
} ;
if ( this . stream . isSendScreen ( ) ) {
// Set interval to listen for screen resize events
2018-07-04 12:25:56 +02:00
this . screenShareResizeInterval = setInterval ( ( ) = > {
2021-03-24 19:39:49 +01:00
const settings : MediaTrackSettings = mediaStream . getVideoTracks ( ) [ 0 ] . getSettings ( ) ;
const newWidth = settings . width ;
const newHeight = settings . height ;
2018-07-04 12:25:56 +02:00
if ( this . stream . isLocalStreamPublished &&
2021-03-24 19:39:49 +01:00
( newWidth !== this . stream . videoDimensions . width || newHeight !== this . stream . videoDimensions . height ) ) {
this . openvidu . sendVideoDimensionsChangedEvent (
this ,
'screenResized' ,
this . stream . videoDimensions . width ,
this . stream . videoDimensions . height ,
newWidth || 0 ,
newHeight || 0
) ;
2018-07-04 12:25:56 +02:00
}
2021-03-24 19:39:49 +01:00
} , 650 ) ;
}
this . stream . isLocalStreamReadyToPublish = true ;
this . stream . ee . emitEvent ( 'stream-ready-to-publish' , [ ] ) ;
} ) ;
2018-07-04 12:25:56 +02:00
} else {
2021-03-24 19:39:49 +01:00
// Only audio track (no videoDimensions)
2018-07-03 15:35:08 +02:00
this . stream . isLocalStreamReadyToPublish = true ;
this . stream . ee . emitEvent ( 'stream-ready-to-publish' , [ ] ) ;
}
2018-04-26 15:33:47 +02:00
resolve ( ) ;
} ;
2019-05-29 16:44:38 +02:00
const getMediaSuccess = ( mediaStream : MediaStream , definedAudioConstraint ) = > {
2019-02-25 11:25:53 +01:00
this . clearPermissionDialogTimer ( startTime , timeForDialogEvent ) ;
if ( this . stream . isSendScreen ( ) && this . stream . isSendAudio ( ) ) {
// When getting desktop as user media audio constraint must be false. Now we can ask for it if required
constraintsAux . audio = definedAudioConstraint ;
constraintsAux . video = false ;
startTime = Date . now ( ) ;
this . setPermissionDialogTimer ( timeForDialogEvent ) ;
navigator . mediaDevices . getUserMedia ( constraintsAux )
. then ( audioOnlyStream = > {
this . clearPermissionDialogTimer ( startTime , timeForDialogEvent ) ;
mediaStream . addTrack ( audioOnlyStream . getAudioTracks ( ) [ 0 ] ) ;
successCallback ( mediaStream ) ;
} )
. catch ( error = > {
this . clearPermissionDialogTimer ( startTime , timeForDialogEvent ) ;
2019-09-25 15:23:18 +02:00
mediaStream . getAudioTracks ( ) . forEach ( ( track ) = > {
track . stop ( ) ;
} ) ;
mediaStream . getVideoTracks ( ) . forEach ( ( track ) = > {
track . stop ( ) ;
} ) ;
errorCallback ( this . openvidu . generateAudioDeviceError ( error , constraints ) ) ;
return ;
2019-02-25 11:25:53 +01:00
} ) ;
} else {
successCallback ( mediaStream ) ;
}
} ;
2019-05-29 16:44:38 +02:00
const getMediaError = error = > {
2021-06-22 12:58:00 +02:00
logger . error ( ` getMediaError: ${ error . toString ( ) } ` ) ;
2019-05-29 16:44:38 +02:00
this . clearPermissionDialogTimer ( startTime , timeForDialogEvent ) ;
if ( error . name === 'Error' ) {
// Safari OverConstrainedError has as name property 'Error' instead of 'OverConstrainedError'
error . name = error . constructor . name ;
}
let errorName , errorMessage ;
switch ( error . name . toLowerCase ( ) ) {
case 'notfounderror' :
navigator . mediaDevices . getUserMedia ( {
audio : false ,
video : constraints.video
} )
. then ( mediaStream = > {
mediaStream . getVideoTracks ( ) . forEach ( ( track ) = > {
track . stop ( ) ;
} ) ;
errorName = OpenViduErrorName . INPUT_AUDIO_DEVICE_NOT_FOUND ;
errorMessage = error . toString ( ) ;
errorCallback ( new OpenViduError ( errorName , errorMessage ) ) ;
} ) . catch ( e = > {
errorName = OpenViduErrorName . INPUT_VIDEO_DEVICE_NOT_FOUND ;
errorMessage = error . toString ( ) ;
errorCallback ( new OpenViduError ( errorName , errorMessage ) ) ;
} ) ;
break ;
case 'notallowederror' :
errorName = this . stream . isSendScreen ( ) ? OpenViduErrorName.SCREEN_CAPTURE_DENIED : OpenViduErrorName.DEVICE_ACCESS_DENIED ;
errorMessage = error . toString ( ) ;
errorCallback ( new OpenViduError ( errorName , errorMessage ) ) ;
break ;
case 'overconstrainederror' :
navigator . mediaDevices . getUserMedia ( {
audio : false ,
video : constraints.video
} )
. then ( mediaStream = > {
mediaStream . getVideoTracks ( ) . forEach ( ( track ) = > {
track . stop ( ) ;
} ) ;
if ( error . constraint . toLowerCase ( ) === 'deviceid' ) {
errorName = OpenViduErrorName . INPUT_AUDIO_DEVICE_NOT_FOUND ;
errorMessage = "Audio input device with deviceId '" + ( < ConstrainDOMStringParameters > ( < MediaTrackConstraints > constraints . audio ) . deviceId ! ! ) . exact + "' not found" ;
} else {
errorName = OpenViduErrorName . PUBLISHER_PROPERTIES_ERROR ;
errorMessage = "Audio input device doesn't support the value passed for constraint '" + error . constraint + "'" ;
}
errorCallback ( new OpenViduError ( errorName , errorMessage ) ) ;
} ) . catch ( e = > {
if ( error . constraint . toLowerCase ( ) === 'deviceid' ) {
errorName = OpenViduErrorName . INPUT_VIDEO_DEVICE_NOT_FOUND ;
errorMessage = "Video input device with deviceId '" + ( < ConstrainDOMStringParameters > ( < MediaTrackConstraints > constraints . video ) . deviceId ! ! ) . exact + "' not found" ;
} else {
errorName = OpenViduErrorName . PUBLISHER_PROPERTIES_ERROR ;
errorMessage = "Video input device doesn't support the value passed for constraint '" + error . constraint + "'" ;
}
errorCallback ( new OpenViduError ( errorName , errorMessage ) ) ;
} ) ;
break ;
case 'aborterror' :
case 'notreadableerror' :
errorName = OpenViduErrorName . DEVICE_ALREADY_IN_USE ;
errorMessage = error . toString ( ) ;
errorCallback ( new OpenViduError ( errorName , errorMessage ) ) ;
break ;
default :
errorName = OpenViduErrorName . GENERIC_ERROR ;
errorMessage = error . toString ( ) ;
errorCallback ( new OpenViduError ( errorName , errorMessage ) ) ;
break ;
}
}
2018-04-26 15:33:47 +02:00
this . openvidu . generateMediaConstraints ( this . properties )
2019-02-25 11:25:53 +01:00
. then ( myConstraints = > {
2020-05-04 12:49:20 +02:00
if ( ! ! myConstraints . videoTrack && ! ! myConstraints . audioTrack ||
2020-11-10 14:51:19 +01:00
! ! myConstraints . audioTrack && myConstraints . constraints ? . video === false ||
! ! myConstraints . videoTrack && myConstraints . constraints ? . audio === false ) {
2020-02-20 20:14:46 +01:00
// No need to call getUserMedia at all. MediaStreamTracks already provided
successCallback ( this . openvidu . addAlreadyProvidedTracks ( myConstraints , new MediaStream ( ) ) ) ;
// Return as we do not need to process further
return ;
}
constraints = myConstraints . constraints ;
2018-04-26 15:33:47 +02:00
const outboundStreamOptions = {
mediaConstraints : constraints ,
publisherProperties : this.properties
} ;
this . stream . setOutboundStreamOptions ( outboundStreamOptions ) ;
2019-09-25 15:23:18 +02:00
const definedAudioConstraint = ( ( constraints . audio === undefined ) ? true : constraints . audio ) ;
constraintsAux . audio = this . stream . isSendScreen ( ) ? false : definedAudioConstraint ;
constraintsAux . video = constraints . video ;
startTime = Date . now ( ) ;
this . setPermissionDialogTimer ( timeForDialogEvent ) ;
2019-05-31 12:48:14 +02:00
2020-10-13 16:13:37 +02:00
if ( this . stream . isSendScreen ( ) && navigator . mediaDevices [ 'getDisplayMedia' ] && ! platform . isElectron ( ) ) {
2019-09-25 15:23:18 +02:00
navigator . mediaDevices [ 'getDisplayMedia' ] ( { video : true } )
. then ( mediaStream = > {
2020-02-20 20:14:46 +01:00
this . openvidu . addAlreadyProvidedTracks ( myConstraints , mediaStream ) ;
2019-09-25 15:23:18 +02:00
getMediaSuccess ( mediaStream , definedAudioConstraint ) ;
} )
. catch ( error = > {
getMediaError ( error ) ;
} ) ;
2018-04-26 15:33:47 +02:00
} else {
2019-09-25 15:23:18 +02:00
navigator . mediaDevices . getUserMedia ( constraintsAux )
. then ( mediaStream = > {
2020-02-20 20:14:46 +01:00
this . openvidu . addAlreadyProvidedTracks ( myConstraints , mediaStream ) ;
2019-09-25 15:23:18 +02:00
getMediaSuccess ( mediaStream , definedAudioConstraint ) ;
} )
. catch ( error = > {
getMediaError ( error ) ;
} ) ;
2018-04-26 15:33:47 +02:00
}
2019-09-25 15:23:18 +02:00
2018-04-26 15:33:47 +02:00
} )
. catch ( ( error : OpenViduError ) = > {
errorCallback ( error ) ;
} ) ;
} ) ;
}
2020-06-30 10:48:55 +02:00
/ * *
* @hidden
2021-03-24 19:39:49 +01:00
*
* To obtain the videoDimensions we wait for the video reference to have enough metadata
* and then try to use MediaStreamTrack . getSettingsMethod ( ) . If not available , then we
* use the HTMLVideoElement properties videoWidth and videoHeight
2020-06-30 10:48:55 +02:00
* /
2021-03-24 19:39:49 +01:00
getVideoDimensions ( mediaStream : MediaStream ) : Promise < { width : number , height : number } > {
return new Promise ( ( resolve , reject ) = > {
// Ionic iOS and Safari iOS supposedly require the video element to actually exist inside the DOM
const requiresDomInsertion : boolean = platform . isIonicIos ( ) || platform . isIOSWithSafari ( ) ;
let loadedmetadataListener ;
const resolveDimensions = ( ) = > {
let width : number ;
let height : number ;
if ( typeof this . stream . getMediaStream ( ) . getVideoTracks ( ) [ 0 ] . getSettings === 'function' ) {
const settings = this . stream . getMediaStream ( ) . getVideoTracks ( ) [ 0 ] . getSettings ( ) ;
width = settings . width || this . videoReference . videoWidth ;
height = settings . height || this . videoReference . videoHeight ;
} else {
logger . warn ( 'MediaStreamTrack does not have getSettings method on ' + platform . getDescription ( ) ) ;
width = this . videoReference . videoWidth ;
height = this . videoReference . videoHeight ;
}
if ( loadedmetadataListener != null ) {
this . videoReference . removeEventListener ( 'loadedmetadata' , loadedmetadataListener ) ;
}
if ( requiresDomInsertion ) {
document . body . removeChild ( this . videoReference ) ;
}
resolve ( { width , height } ) ;
}
if ( this . videoReference . readyState >= 1 ) {
// The video already has metadata available
// No need of loadedmetadata event
resolveDimensions ( ) ;
} else {
// The video does not have metadata available yet
// Must listen to loadedmetadata event
loadedmetadataListener = ( ) = > {
if ( ! this . videoReference . videoWidth ) {
let interval = setInterval ( ( ) = > {
if ( ! ! this . videoReference . videoWidth ) {
clearInterval ( interval ) ;
resolveDimensions ( ) ;
}
} , 40 ) ;
} else {
resolveDimensions ( ) ;
}
} ;
this . videoReference . addEventListener ( 'loadedmetadata' , loadedmetadataListener ) ;
if ( requiresDomInsertion ) {
document . body . appendChild ( this . videoReference ) ;
}
}
} ) ;
2020-06-30 10:48:55 +02:00
}
2018-05-30 16:36:27 +02:00
/ * *
* @hidden
* /
reestablishStreamPlayingEvent() {
if ( this . ee . getListeners ( 'streamPlaying' ) . length > 0 ) {
this . addPlayEventToFirstVideo ( ) ;
}
}
2020-06-30 10:48:55 +02:00
/ * *
* @hidden
* /
initializeVideoReference ( mediaStream : MediaStream ) {
this . videoReference = document . createElement ( 'video' ) ;
2021-03-24 19:39:49 +01:00
this . videoReference . setAttribute ( 'muted' , 'true' ) ;
this . videoReference . style . display = 'none' ;
2021-07-13 11:49:05 +02:00
if ( platform . isSafariBrowser ( ) || ( platform . isIPhoneOrIPad ( ) && ( platform . isChromeMobileBrowser ( ) || platform . isEdgeMobileBrowser ( ) || platform . isOperaMobileBrowser ( ) || platform . isFirefoxMobileBrowser ( ) ) ) ) {
2020-06-30 10:48:55 +02:00
this . videoReference . setAttribute ( 'playsinline' , 'true' ) ;
}
this . stream . setMediaStream ( mediaStream ) ;
if ( ! ! this . firstVideoElement ) {
this . createVideoElement ( this . firstVideoElement . targetElement , < VideoInsertMode > this . properties . insertMode ) ;
}
this . videoReference . srcObject = mediaStream ;
}
2018-04-26 15:33:47 +02:00
/* Private methods */
private setPermissionDialogTimer ( waitTime : number ) : void {
this . permissionDialogTimeout = setTimeout ( ( ) = > {
2018-07-03 15:35:08 +02:00
this . emitEvent ( 'accessDialogOpened' , [ ] ) ;
2018-04-26 15:33:47 +02:00
} , waitTime ) ;
}
private clearPermissionDialogTimer ( startTime : number , waitTime : number ) : void {
clearTimeout ( this . permissionDialogTimeout ) ;
if ( ( Date . now ( ) - startTime ) > waitTime ) {
// Permission dialog was shown and now is closed
2018-07-03 15:35:08 +02:00
this . emitEvent ( 'accessDialogClosed' , [ ] ) ;
2018-04-26 15:33:47 +02:00
}
}
2020-04-01 17:48:22 +02:00
}