mirror of https://github.com/OpenVidu/openvidu.git
openvidu-browser: improved Publisher#publishVideo turns off webcam light
parent
1db373caba
commit
06f1fcbee1
|
@ -885,9 +885,16 @@ export class OpenVidu {
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
addAlreadyProvidedTracks(myConstraints: CustomMediaStreamConstraints, mediaStream: MediaStream) {
|
addAlreadyProvidedTracks(myConstraints: CustomMediaStreamConstraints, mediaStream: MediaStream, stream?: Stream) {
|
||||||
if (!!myConstraints.videoTrack) {
|
if (!!myConstraints.videoTrack) {
|
||||||
mediaStream.addTrack(myConstraints.videoTrack);
|
mediaStream.addTrack(myConstraints.videoTrack);
|
||||||
|
if (!!stream) {
|
||||||
|
if (!!myConstraints.constraints.video) {
|
||||||
|
stream.lastVideoTrackConstraints = myConstraints.constraints.video;
|
||||||
|
} else {
|
||||||
|
stream.lastVideoTrackConstraints = myConstraints.videoTrack.getConstraints();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!!myConstraints.audioTrack) {
|
if (!!myConstraints.audioTrack) {
|
||||||
mediaStream.addTrack(myConstraints.audioTrack);
|
mediaStream.addTrack(myConstraints.audioTrack);
|
||||||
|
|
|
@ -144,6 +144,10 @@ export class Publisher extends StreamManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
publishVideo(value: boolean): void;
|
||||||
|
publishVideo(value: false, freeResource?: boolean): void;
|
||||||
|
publishVideo(value: true, track?: MediaStreamTrack): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Publish or unpublish the video stream (if available). Calling this method twice in a row passing same value will have no effect
|
* Publish or unpublish the video stream (if available). Calling this method twice in a row passing same value will have no effect
|
||||||
*
|
*
|
||||||
|
@ -160,13 +164,53 @@ export class Publisher extends StreamManager {
|
||||||
* See [[StreamPropertyChangedEvent]] to learn more.
|
* See [[StreamPropertyChangedEvent]] to learn more.
|
||||||
*
|
*
|
||||||
* @param value `true` to publish the video stream, `false` to unpublish it
|
* @param value `true` to publish the video stream, `false` to unpublish it
|
||||||
|
* @param freeResource `true` to free the hardware resource associated to the video track, `false` to keep access to it. Not freeing the resource makes the operation much more efficient, but depending on
|
||||||
|
* the platform two side-effects can be introduced: the video device may not be accessible by other applications and the access light of webcams may remain on. This is platform-dependent: some browsers
|
||||||
|
* will not present the side-effects even when not freeing the resource. openvidu-browser will try to restore the video track automatically calling [[publishVideo]] again with parameter `value` to `true`,
|
||||||
|
* but if that is not possible parameter `track` can be provided to force a specific `MediaStreamTrack`.
|
||||||
|
* @param track A `MediaStreamTrack` to be used when restoring the video track. This parameter can be useful if the Publisher was unpublished with parameter `freeResource` to true, and openvidu-browser is
|
||||||
|
* not able to successfully re-create the video track as it was before unpublishing. In this way previous track settings will be ignored and this track will be used instead.
|
||||||
*/
|
*/
|
||||||
publishVideo(value: boolean): void {
|
publishVideo(value: boolean, param?: boolean | MediaStreamTrack): void {
|
||||||
|
|
||||||
if (this.stream.videoActive !== value) {
|
if (this.stream.videoActive !== value) {
|
||||||
|
|
||||||
const affectedMediaStream: MediaStream = this.stream.displayMyRemote() ? this.stream.localMediaStreamWhenSubscribedToRemote! : this.stream.getMediaStream();
|
const affectedMediaStream: MediaStream = this.stream.displayMyRemote() ? this.stream.localMediaStreamWhenSubscribedToRemote! : this.stream.getMediaStream();
|
||||||
|
let mustRestartMediaStream = false;
|
||||||
affectedMediaStream.getVideoTracks().forEach((track) => {
|
affectedMediaStream.getVideoTracks().forEach((track) => {
|
||||||
track.enabled = value;
|
track.enabled = value;
|
||||||
|
if (!value && param === true) {
|
||||||
|
track.stop();
|
||||||
|
} else if (value && track.readyState === 'ended') {
|
||||||
|
// Resource was freed
|
||||||
|
mustRestartMediaStream = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (mustRestartMediaStream) {
|
||||||
|
const oldVideoTrack = affectedMediaStream.getVideoTracks()[0];
|
||||||
|
affectedMediaStream.removeTrack(oldVideoTrack);
|
||||||
|
|
||||||
|
const replaceVideoTrack = (tr: MediaStreamTrack) => {
|
||||||
|
affectedMediaStream.addTrack(tr);
|
||||||
|
if (this.stream.isLocalStreamPublished) {
|
||||||
|
this.replaceTrackInRtcRtpSender(tr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!!param && param instanceof MediaStreamTrack) {
|
||||||
|
replaceVideoTrack(param);
|
||||||
|
} else {
|
||||||
|
navigator.mediaDevices.getUserMedia({ audio: false, video: this.stream.lastVideoTrackConstraints })
|
||||||
|
.then(mediaStream => {
|
||||||
|
replaceVideoTrack(mediaStream.getVideoTracks()[0]);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!!this.session && !!this.stream.streamId) {
|
if (!!this.session && !!this.stream.streamId) {
|
||||||
this.session.openvidu.sendRequest(
|
this.session.openvidu.sendRequest(
|
||||||
'streamPropertyChanged',
|
'streamPropertyChanged',
|
||||||
|
@ -295,6 +339,7 @@ export class Publisher extends StreamManager {
|
||||||
let removedTrack: MediaStreamTrack;
|
let removedTrack: MediaStreamTrack;
|
||||||
if (track.kind === 'video') {
|
if (track.kind === 'video') {
|
||||||
removedTrack = mediaStream.getVideoTracks()[0];
|
removedTrack = mediaStream.getVideoTracks()[0];
|
||||||
|
this.stream.lastVideoTrackConstraints = track.getConstraints();
|
||||||
} else {
|
} else {
|
||||||
removedTrack = mediaStream.getAudioTracks()[0];
|
removedTrack = mediaStream.getAudioTracks()[0];
|
||||||
}
|
}
|
||||||
|
@ -309,29 +354,6 @@ export class Publisher extends StreamManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const replaceTrackInRtcRtpSender = (): Promise<void> => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const senders: RTCRtpSender[] = this.stream.getRTCPeerConnection().getSenders();
|
|
||||||
let sender: RTCRtpSender | undefined;
|
|
||||||
if (track.kind === 'video') {
|
|
||||||
sender = senders.find(s => !!s.track && s.track.kind === 'video');
|
|
||||||
if (!sender) {
|
|
||||||
return reject(new Error('There\'s no replaceable track for that kind of MediaStreamTrack in this Publisher object'));
|
|
||||||
}
|
|
||||||
} else if (track.kind === 'audio') {
|
|
||||||
sender = senders.find(s => !!s.track && s.track.kind === 'audio');
|
|
||||||
if (!sender) {
|
|
||||||
return reject(new Error('There\'s no replaceable track for that kind of MediaStreamTrack in this Publisher object'));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return reject(new Error('Unknown track kind ' + track.kind));
|
|
||||||
}
|
|
||||||
(sender as RTCRtpSender).replaceTrack(track)
|
|
||||||
.then(() => resolve())
|
|
||||||
.catch(error => reject(error));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set field "enabled" of the new track to the previous value
|
// Set field "enabled" of the new track to the previous value
|
||||||
const trackOriginalEnabledValue: boolean = track.enabled;
|
const trackOriginalEnabledValue: boolean = track.enabled;
|
||||||
if (track.kind === 'video') {
|
if (track.kind === 'video') {
|
||||||
|
@ -343,7 +365,7 @@ export class Publisher extends StreamManager {
|
||||||
if (this.stream.isLocalStreamPublished) {
|
if (this.stream.isLocalStreamPublished) {
|
||||||
// Only if the Publisher has been published is necessary to call native Web API RTCRtpSender.replaceTrack
|
// 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
|
// If it has not been published yet, replacing it on the MediaStream object is enough
|
||||||
await replaceTrackInRtcRtpSender();
|
await this.replaceTrackInRtcRtpSender(track);
|
||||||
return await replaceTrackInMediaStream();
|
return await replaceTrackInMediaStream();
|
||||||
} else {
|
} else {
|
||||||
// Publisher not published. Simply replace the track on the local MediaStream
|
// Publisher not published. Simply replace the track on the local MediaStream
|
||||||
|
@ -404,7 +426,7 @@ export class Publisher extends StreamManager {
|
||||||
if (!track.contentHint?.length) {
|
if (!track.contentHint?.length) {
|
||||||
// contentHint for audio: "", "speech", "speech-recognition", "music".
|
// contentHint for audio: "", "speech", "speech-recognition", "music".
|
||||||
// https://w3c.github.io/mst-content-hint/#audio-content-hints
|
// https://w3c.github.io/mst-content-hint/#audio-content-hints
|
||||||
track.contentHint = "";
|
track.contentHint = '';
|
||||||
logger.info(`Audio track Content Hint set: '${track.contentHint}'`);
|
logger.info(`Audio track Content Hint set: '${track.contentHint}'`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -453,8 +475,9 @@ export class Publisher extends StreamManager {
|
||||||
const settings: MediaTrackSettings = mediaStream.getVideoTracks()[0].getSettings();
|
const settings: MediaTrackSettings = mediaStream.getVideoTracks()[0].getSettings();
|
||||||
const newWidth = settings.width;
|
const newWidth = settings.width;
|
||||||
const newHeight = settings.height;
|
const newHeight = settings.height;
|
||||||
if (this.stream.isLocalStreamPublished &&
|
const widthChanged = newWidth != null && newWidth !== this.stream.videoDimensions.width;
|
||||||
(newWidth !== this.stream.videoDimensions.width || newHeight !== this.stream.videoDimensions.height)) {
|
const heightChanged = newHeight != null && newHeight !== this.stream.videoDimensions.height;
|
||||||
|
if (this.stream.isLocalStreamPublished && (widthChanged || heightChanged)) {
|
||||||
this.openvidu.sendVideoDimensionsChangedEvent(
|
this.openvidu.sendVideoDimensionsChangedEvent(
|
||||||
this,
|
this,
|
||||||
'screenResized',
|
'screenResized',
|
||||||
|
@ -591,7 +614,7 @@ export class Publisher extends StreamManager {
|
||||||
!!myConstraints.audioTrack && myConstraints.constraints?.video === false ||
|
!!myConstraints.audioTrack && myConstraints.constraints?.video === false ||
|
||||||
!!myConstraints.videoTrack && myConstraints.constraints?.audio === false) {
|
!!myConstraints.videoTrack && myConstraints.constraints?.audio === false) {
|
||||||
// No need to call getUserMedia at all. MediaStreamTracks already provided
|
// No need to call getUserMedia at all. MediaStreamTracks already provided
|
||||||
successCallback(this.openvidu.addAlreadyProvidedTracks(myConstraints, new MediaStream()));
|
successCallback(this.openvidu.addAlreadyProvidedTracks(myConstraints, new MediaStream(), this.stream));
|
||||||
// Return as we do not need to process further
|
// Return as we do not need to process further
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -620,9 +643,10 @@ export class Publisher extends StreamManager {
|
||||||
getMediaError(error);
|
getMediaError(error);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
this.stream.lastVideoTrackConstraints = constraintsAux.video;
|
||||||
navigator.mediaDevices.getUserMedia(constraintsAux)
|
navigator.mediaDevices.getUserMedia(constraintsAux)
|
||||||
.then(mediaStream => {
|
.then(mediaStream => {
|
||||||
this.openvidu.addAlreadyProvidedTracks(myConstraints, mediaStream);
|
this.openvidu.addAlreadyProvidedTracks(myConstraints, mediaStream, this.stream);
|
||||||
getMediaSuccess(mediaStream, definedAudioConstraint);
|
getMediaSuccess(mediaStream, definedAudioConstraint);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
@ -744,4 +768,27 @@ export class Publisher extends StreamManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private replaceTrackInRtcRtpSender(track: MediaStreamTrack): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const senders: RTCRtpSender[] = this.stream.getRTCPeerConnection().getSenders();
|
||||||
|
let sender: RTCRtpSender | undefined;
|
||||||
|
if (track.kind === 'video') {
|
||||||
|
sender = senders.find(s => !!s.track && s.track.kind === 'video');
|
||||||
|
if (!sender) {
|
||||||
|
return reject(new Error('There\'s no replaceable track for that kind of MediaStreamTrack in this Publisher object'));
|
||||||
|
}
|
||||||
|
} else if (track.kind === 'audio') {
|
||||||
|
sender = senders.find(s => !!s.track && s.track.kind === 'audio');
|
||||||
|
if (!sender) {
|
||||||
|
return reject(new Error('There\'s no replaceable track for that kind of MediaStreamTrack in this Publisher object'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return reject(new Error('Unknown track kind ' + track.kind));
|
||||||
|
}
|
||||||
|
(sender as RTCRtpSender).replaceTrack(track)
|
||||||
|
.then(() => resolve())
|
||||||
|
.catch(error => reject(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -219,6 +219,10 @@ export class Stream {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
reconnectionEventEmitter: EventEmitter | undefined;
|
reconnectionEventEmitter: EventEmitter | undefined;
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
lastVideoTrackConstraints: MediaTrackConstraints | boolean | undefined;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue