auto_join_3.3
Håvar Aambø Fosstveit 2020-05-19 14:25:13 +02:00
parent bb391b1390
commit a0a23109a9
3 changed files with 114 additions and 114 deletions

View File

@ -315,7 +315,7 @@ export default class RoomClient
{ {
const newPeerId = this._spotlights.getNextAsSelected( const newPeerId = this._spotlights.getNextAsSelected(
store.getState().room.selectedPeerId); store.getState().room.selectedPeerId);
if (newPeerId) this.setSelectedPeer(newPeerId); if (newPeerId) this.setSelectedPeer(newPeerId);
break; break;
} }
@ -798,7 +798,7 @@ export default class RoomClient
} }
}); });
torrent.on('done', () => torrent.on('done', () =>
{ {
store.dispatch( store.dispatch(
fileActions.setFileDone( fileActions.setFileDone(
@ -948,7 +948,7 @@ export default class RoomClient
{ {
await this.sendRequest( await this.sendRequest(
'resumeProducer', { producerId: this._micProducer.id }); 'resumeProducer', { producerId: this._micProducer.id });
store.dispatch( store.dispatch(
producerActions.setProducerResumed(this._micProducer.id)); producerActions.setProducerResumed(this._micProducer.id));
} }
@ -1000,23 +1000,23 @@ export default class RoomClient
} }
} }
disconnectLocalHark() disconnectLocalHark()
{ {
logger.debug('disconnectLocalHark() | Stopping harkStream.'); logger.debug('disconnectLocalHark() | Stopping harkStream.');
if (this._harkStream != null) if (this._harkStream != null)
{ {
this._harkStream.getAudioTracks()[0].stop(); this._harkStream.getAudioTracks()[0].stop();
this._harkStream = null; this._harkStream = null;
} }
if (this._hark != null) if (this._hark != null)
{ {
logger.debug('disconnectLocalHark() Stopping hark.'); logger.debug('disconnectLocalHark() Stopping hark.');
this._hark.stop(); this._hark.stop();
} }
} }
connectLocalHark(track) connectLocalHark(track)
{ {
logger.debug('connectLocalHark() | Track:%o', track); logger.debug('connectLocalHark() | Track:%o', track);
this._harkStream = new MediaStream(); this._harkStream = new MediaStream();
@ -1027,21 +1027,21 @@ export default class RoomClient
if (!this._harkStream.getAudioTracks()[0]) if (!this._harkStream.getAudioTracks()[0])
throw new Error('getMicStream():something went wrong with hark'); throw new Error('getMicStream():something went wrong with hark');
this._hark = hark(this._harkStream, this._hark = hark(this._harkStream,
{ {
play : false, play : false,
interval : 5, interval : 5,
threshold : store.getState().settings.noiseThreshold, threshold : store.getState().settings.noiseThreshold,
history : 300 history : 300
}); });
this._hark.lastVolume = -100; this._hark.lastVolume = -100;
this._hark.on('volume_change', (volume) => this._hark.on('volume_change', (volume) =>
{ {
volume = Math.round(volume); volume = Math.round(volume);
if (this._micProducer && volume !== Math.round(this._hark.lastVolume)) if (this._micProducer && volume !== Math.round(this._hark.lastVolume))
{ {
if (volume < this._hark.lastVolume * 1.02) if (volume < this._hark.lastVolume * 1.02)
{ {
volume = this._hark.lastVolume * 1.02; volume = this._hark.lastVolume * 1.02;
} }
@ -1052,7 +1052,7 @@ export default class RoomClient
this._hark.on('speaking', () => this._hark.on('speaking', () =>
{ {
store.dispatch(meActions.setIsSpeaking(true)); store.dispatch(meActions.setIsSpeaking(true));
if ((store.getState().settings.voiceActivatedUnmute || if ((store.getState().settings.voiceActivatedUnmute ||
store.getState().me.isAutoMuted) && store.getState().me.isAutoMuted) &&
this._micProducer && this._micProducer &&
this._micProducer.paused) this._micProducer.paused)
@ -1064,9 +1064,9 @@ export default class RoomClient
this._hark.on('stopped_speaking', () => this._hark.on('stopped_speaking', () =>
{ {
store.dispatch(meActions.setIsSpeaking(false)); store.dispatch(meActions.setIsSpeaking(false));
if (store.getState().settings.voiceActivatedUnmute && if (store.getState().settings.voiceActivatedUnmute &&
this._micProducer && this._micProducer &&
!this._micProducer.paused) !this._micProducer.paused)
{ {
this._micProducer.pause(); this._micProducer.pause();
store.dispatch(meActions.setAutoMuted(true)); store.dispatch(meActions.setAutoMuted(true));
@ -1082,7 +1082,7 @@ export default class RoomClient
meActions.setAudioInProgress(true)); meActions.setAudioInProgress(true));
try try
{ {
const device = this._audioDevices[deviceId]; const device = this._audioDevices[deviceId];
if (!device) if (!device)
@ -1201,7 +1201,7 @@ export default class RoomClient
...VIDEO_CONSTRAINS[resolution] ...VIDEO_CONSTRAINS[resolution]
} }
}); });
if (stream) if (stream)
{ {
const track = stream.getVideoTracks()[0]; const track = stream.getVideoTracks()[0];
@ -1212,15 +1212,15 @@ export default class RoomClient
{ {
await this._webcamProducer.replaceTrack({ track }); await this._webcamProducer.replaceTrack({ track });
} }
else else
{ {
this._webcamProducer = await this._sendTransport.produce({ this._webcamProducer = await this._sendTransport.produce({
track, track,
appData : appData :
{ {
source : 'webcam' source : 'webcam'
} }
}); });
} }
store.dispatch( store.dispatch(
@ -1230,7 +1230,7 @@ export default class RoomClient
{ {
logger.warn('getVideoTracks Error: First Video Track is null'); logger.warn('getVideoTracks Error: First Video Track is null');
} }
} }
else else
{ {
@ -1264,7 +1264,7 @@ export default class RoomClient
if (!device) if (!device)
throw new Error('no webcam devices'); throw new Error('no webcam devices');
logger.debug( logger.debug(
'changeWebcam() | new selected webcam [device:%o]', 'changeWebcam() | new selected webcam [device:%o]',
device); device);
@ -1292,17 +1292,17 @@ export default class RoomClient
{ {
await this._webcamProducer.replaceTrack({ track }); await this._webcamProducer.replaceTrack({ track });
} }
else else
{ {
this._webcamProducer = await this._sendTransport.produce({ this._webcamProducer = await this._sendTransport.produce({
track, track,
appData : appData :
{ {
source : 'webcam' source : 'webcam'
} }
}); });
} }
store.dispatch( store.dispatch(
producerActions.setProducerTrack(this._webcamProducer.id, track)); producerActions.setProducerTrack(this._webcamProducer.id, track));
@ -1311,7 +1311,7 @@ export default class RoomClient
{ {
logger.warn('getVideoTracks Error: First Video Track is null'); logger.warn('getVideoTracks Error: First Video Track is null');
} }
} }
else else
{ {
@ -2098,7 +2098,7 @@ export default class RoomClient
const { displayName } = store.getState().settings; const { displayName } = store.getState().settings;
const { picture } = store.getState().me; const { picture } = store.getState().me;
await this.sendRequest('changeDisplayName', { displayName }); await this.sendRequest('changeDisplayName', { displayName });
await this.sendRequest('changePicture', { picture }); await this.sendRequest('changePicture', { picture });
break; break;
@ -2107,10 +2107,10 @@ export default class RoomClient
case 'signInRequired': case 'signInRequired':
{ {
store.dispatch(roomActions.setSignInRequired(true)); store.dispatch(roomActions.setSignInRequired(true));
break; break;
} }
case 'overRoomLimit': case 'overRoomLimit':
{ {
store.dispatch(roomActions.setOverRoomLimit(true)); store.dispatch(roomActions.setOverRoomLimit(true));
@ -2126,24 +2126,24 @@ export default class RoomClient
store.dispatch(roomActions.toggleJoined()); store.dispatch(roomActions.toggleJoined());
store.dispatch(roomActions.setInLobby(false)); store.dispatch(roomActions.setInLobby(false));
await this._joinRoom({ joinVideo }); await this._joinRoom({ joinVideo });
break; break;
} }
case 'roomBack': case 'roomBack':
{ {
await this._joinRoom({ joinVideo }); await this._joinRoom({ joinVideo });
break; break;
} }
case 'lockRoom': case 'lockRoom':
{ {
store.dispatch( store.dispatch(
roomActions.setRoomLocked()); roomActions.setRoomLocked());
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : intl.formatMessage({ text : intl.formatMessage({
@ -2151,15 +2151,15 @@ export default class RoomClient
defaultMessage : 'Room is now locked' defaultMessage : 'Room is now locked'
}) })
})); }));
break; break;
} }
case 'unlockRoom': case 'unlockRoom':
{ {
store.dispatch( store.dispatch(
roomActions.setRoomUnLocked()); roomActions.setRoomUnLocked());
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : intl.formatMessage({ text : intl.formatMessage({
@ -2167,21 +2167,21 @@ export default class RoomClient
defaultMessage : 'Room is now unlocked' defaultMessage : 'Room is now unlocked'
}) })
})); }));
break; break;
} }
case 'parkedPeer': case 'parkedPeer':
{ {
const { peerId } = notification.data; const { peerId } = notification.data;
store.dispatch( store.dispatch(
lobbyPeerActions.addLobbyPeer(peerId)); lobbyPeerActions.addLobbyPeer(peerId));
store.dispatch( store.dispatch(
roomActions.setToolbarsVisible(true)); roomActions.setToolbarsVisible(true));
this._soundNotification(); this._soundNotification();
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : intl.formatMessage({ text : intl.formatMessage({
@ -2189,7 +2189,7 @@ export default class RoomClient
defaultMessage : 'New participant entered the lobby' defaultMessage : 'New participant entered the lobby'
}) })
})); }));
break; break;
} }
@ -2218,7 +2218,7 @@ export default class RoomClient
) )
); );
}); });
store.dispatch( store.dispatch(
roomActions.setToolbarsVisible(true)); roomActions.setToolbarsVisible(true));
@ -2235,14 +2235,14 @@ export default class RoomClient
break; break;
} }
case 'lobby:peerClosed': case 'lobby:peerClosed':
{ {
const { peerId } = notification.data; const { peerId } = notification.data;
store.dispatch( store.dispatch(
lobbyPeerActions.removeLobbyPeer(peerId)); lobbyPeerActions.removeLobbyPeer(peerId));
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
text : intl.formatMessage({ text : intl.formatMessage({
@ -2250,10 +2250,10 @@ export default class RoomClient
defaultMessage : 'Participant in lobby left' defaultMessage : 'Participant in lobby left'
}) })
})); }));
break; break;
} }
case 'lobby:promotedPeer': case 'lobby:promotedPeer':
{ {
const { peerId } = notification.data; const { peerId } = notification.data;
@ -2263,7 +2263,7 @@ export default class RoomClient
break; break;
} }
case 'lobby:changeDisplayName': case 'lobby:changeDisplayName':
{ {
const { peerId, displayName } = notification.data; const { peerId, displayName } = notification.data;
@ -2283,11 +2283,11 @@ export default class RoomClient
break; break;
} }
case 'lobby:changePicture': case 'lobby:changePicture':
{ {
const { peerId, picture } = notification.data; const { peerId, picture } = notification.data;
store.dispatch( store.dispatch(
lobbyPeerActions.setLobbyPeerPicture(picture, peerId)); lobbyPeerActions.setLobbyPeerPicture(picture, peerId));
@ -2305,7 +2305,7 @@ export default class RoomClient
case 'setAccessCode': case 'setAccessCode':
{ {
const { accessCode } = notification.data; const { accessCode } = notification.data;
store.dispatch( store.dispatch(
roomActions.setAccessCode(accessCode)); roomActions.setAccessCode(accessCode));
@ -2319,14 +2319,14 @@ export default class RoomClient
break; break;
} }
case 'setJoinByAccessCode': case 'setJoinByAccessCode':
{ {
const { joinByAccessCode } = notification.data; const { joinByAccessCode } = notification.data;
store.dispatch( store.dispatch(
roomActions.setJoinByAccessCode(joinByAccessCode)); roomActions.setJoinByAccessCode(joinByAccessCode));
if (joinByAccessCode) if (joinByAccessCode)
{ {
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
@ -2337,7 +2337,7 @@ export default class RoomClient
}) })
})); }));
} }
else else
{ {
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
@ -2350,20 +2350,20 @@ export default class RoomClient
break; break;
} }
case 'activeSpeaker': case 'activeSpeaker':
{ {
const { peerId } = notification.data; const { peerId } = notification.data;
store.dispatch( store.dispatch(
roomActions.setRoomActiveSpeaker(peerId)); roomActions.setRoomActiveSpeaker(peerId));
if (peerId && peerId !== this._peerId) if (peerId && peerId !== this._peerId)
this._spotlights.handleActiveSpeaker(peerId); this._spotlights.handleActiveSpeaker(peerId);
break; break;
} }
case 'changeDisplayName': case 'changeDisplayName':
{ {
const { peerId, displayName, oldDisplayName } = notification.data; const { peerId, displayName, oldDisplayName } = notification.data;
@ -2571,74 +2571,74 @@ export default class RoomClient
{ {
const { consumerId } = notification.data; const { consumerId } = notification.data;
const consumer = this._consumers.get(consumerId); const consumer = this._consumers.get(consumerId);
if (!consumer) if (!consumer)
break; break;
consumer.close(); consumer.close();
if (consumer.hark != null) if (consumer.hark != null)
consumer.hark.stop(); consumer.hark.stop();
this._consumers.delete(consumerId); this._consumers.delete(consumerId);
const { peerId } = consumer.appData; const { peerId } = consumer.appData;
store.dispatch( store.dispatch(
consumerActions.removeConsumer(consumerId, peerId)); consumerActions.removeConsumer(consumerId, peerId));
break; break;
} }
case 'consumerPaused': case 'consumerPaused':
{ {
const { consumerId } = notification.data; const { consumerId } = notification.data;
const consumer = this._consumers.get(consumerId); const consumer = this._consumers.get(consumerId);
if (!consumer) if (!consumer)
break; break;
store.dispatch( store.dispatch(
consumerActions.setConsumerPaused(consumerId, 'remote')); consumerActions.setConsumerPaused(consumerId, 'remote'));
break; break;
} }
case 'consumerResumed': case 'consumerResumed':
{ {
const { consumerId } = notification.data; const { consumerId } = notification.data;
const consumer = this._consumers.get(consumerId); const consumer = this._consumers.get(consumerId);
if (!consumer) if (!consumer)
break; break;
store.dispatch( store.dispatch(
consumerActions.setConsumerResumed(consumerId, 'remote')); consumerActions.setConsumerResumed(consumerId, 'remote'));
break; break;
} }
case 'consumerLayersChanged': case 'consumerLayersChanged':
{ {
const { consumerId, spatialLayer, temporalLayer } = notification.data; const { consumerId, spatialLayer, temporalLayer } = notification.data;
const consumer = this._consumers.get(consumerId); const consumer = this._consumers.get(consumerId);
if (!consumer) if (!consumer)
break; break;
store.dispatch(consumerActions.setConsumerCurrentLayers( store.dispatch(consumerActions.setConsumerCurrentLayers(
consumerId, spatialLayer, temporalLayer)); consumerId, spatialLayer, temporalLayer));
break; break;
} }
case 'consumerScore': case 'consumerScore':
{ {
const { consumerId, score } = notification.data; const { consumerId, score } = notification.data;
store.dispatch( store.dispatch(
consumerActions.setConsumerScore(consumerId, score)); consumerActions.setConsumerScore(consumerId, score));
break; break;
} }
@ -2752,7 +2752,7 @@ export default class RoomClient
break; break;
} }
default: default:
{ {
logger.error( logger.error(
@ -2799,7 +2799,7 @@ export default class RoomClient
this._webTorrent.on('error', (error) => this._webTorrent.on('error', (error) =>
{ {
logger.error('Filesharing [error:"%o"]', error); logger.error('Filesharing [error:"%o"]', error);
store.dispatch(requestActions.notify( store.dispatch(requestActions.notify(
{ {
type : 'error', type : 'error',
@ -3021,7 +3021,7 @@ export default class RoomClient
); );
} }
locked ? locked ?
store.dispatch(roomActions.setRoomLocked()) : store.dispatch(roomActions.setRoomLocked()) :
store.dispatch(roomActions.setRoomUnLocked()); store.dispatch(roomActions.setRoomUnLocked());
@ -3047,14 +3047,14 @@ export default class RoomClient
await this.enableMic(); await this.enableMic();
const { autoMuteThreshold } = store.getState().settings; const { autoMuteThreshold } = store.getState().settings;
if (autoMuteThreshold && peers.length > autoMuteThreshold) if (autoMuteThreshold && peers.length > autoMuteThreshold)
this.muteMic(); this.muteMic();
} }
if (joinVideo && this._mediasoupDevice.canProduce('video')) if (joinVideo && this._mediasoupDevice.canProduce('video'))
this.enableWebcam(); this.enableWebcam();
} }
await this._updateAudioOutputDevices(); await this._updateAudioOutputDevices();
const { selectedAudioOutputDevice } = store.getState().settings; const { selectedAudioOutputDevice } = store.getState().settings;
@ -3067,7 +3067,7 @@ export default class RoomClient
) )
); );
} }
store.dispatch(roomActions.setRoomState('connected')); store.dispatch(roomActions.setRoomState('connected'));
// Clean all the existing notifications. // Clean all the existing notifications.
@ -3251,7 +3251,7 @@ export default class RoomClient
if (!device) if (!device)
throw new Error('no webcam devices'); throw new Error('no webcam devices');
logger.debug( logger.debug(
'addExtraVideo() | new selected webcam [device:%o]', 'addExtraVideo() | new selected webcam [device:%o]',
device); device);
@ -3296,7 +3296,7 @@ export default class RoomClient
{ {
videoGoogleStartBitrate : 1000 videoGoogleStartBitrate : 1000
}, },
appData : appData :
{ {
source : 'extravideo' source : 'extravideo'
} }
@ -3306,7 +3306,7 @@ export default class RoomClient
{ {
producer = await this._sendTransport.produce({ producer = await this._sendTransport.produce({
track, track,
appData : appData :
{ {
source : 'extravideo' source : 'extravideo'
} }
@ -3400,7 +3400,7 @@ export default class RoomClient
if (!device) if (!device)
throw new Error('no audio devices'); throw new Error('no audio devices');
logger.debug( logger.debug(
'enableMic() | new selected audio device [device:%o]', 'enableMic() | new selected audio device [device:%o]',
device); device);
@ -3437,7 +3437,7 @@ export default class RoomClient
opusPtime : '3', opusPtime : '3',
opusMaxPlaybackRate : 48000 opusMaxPlaybackRate : 48000
}, },
appData : appData :
{ source: 'mic' } { source: 'mic' }
}); });
@ -3597,7 +3597,7 @@ export default class RoomClient
{ {
videoGoogleStartBitrate : 1000 videoGoogleStartBitrate : 1000
}, },
appData : appData :
{ {
source : 'screen' source : 'screen'
} }
@ -3607,7 +3607,7 @@ export default class RoomClient
{ {
this._screenSharingProducer = await this._sendTransport.produce({ this._screenSharingProducer = await this._sendTransport.produce({
track, track,
appData : appData :
{ {
source : 'screen' source : 'screen'
} }
@ -3725,7 +3725,7 @@ export default class RoomClient
if (!device) if (!device)
throw new Error('no webcam devices'); throw new Error('no webcam devices');
logger.debug( logger.debug(
'_setWebcamProducer() | new selected webcam [device:%o]', '_setWebcamProducer() | new selected webcam [device:%o]',
device); device);
@ -3768,7 +3768,7 @@ export default class RoomClient
{ {
videoGoogleStartBitrate : 1000 videoGoogleStartBitrate : 1000
}, },
appData : appData :
{ {
source : 'webcam' source : 'webcam'
} }
@ -3778,7 +3778,7 @@ export default class RoomClient
{ {
this._webcamProducer = await this._sendTransport.produce({ this._webcamProducer = await this._sendTransport.produce({
track, track,
appData : appData :
{ {
source : 'webcam' source : 'webcam'
} }

View File

@ -312,7 +312,7 @@ const TopBar = (props) =>
</Typography> </Typography>
<div className={classes.grow} /> <div className={classes.grow} />
<div className={classes.sectionDesktop}> <div className={classes.sectionDesktop}>
<Tooltip <Tooltip
title={intl.formatMessage({ title={intl.formatMessage({
id : 'label.moreActions', id : 'label.moreActions',
defaultMessage : 'More actions' defaultMessage : 'More actions'
@ -350,7 +350,7 @@ const TopBar = (props) =>
</IconButton> </IconButton>
</Tooltip> </Tooltip>
} }
<Tooltip <Tooltip
title={intl.formatMessage({ title={intl.formatMessage({
id : 'tooltip.participants', id : 'tooltip.participants',
defaultMessage : 'Show participants' defaultMessage : 'Show participants'
@ -421,7 +421,7 @@ const TopBar = (props) =>
</span> </span>
</Tooltip> </Tooltip>
{ lobbyPeers.length > 0 && { lobbyPeers.length > 0 &&
<Tooltip <Tooltip
title={intl.formatMessage({ title={intl.formatMessage({
id : 'tooltip.lobby', id : 'tooltip.lobby',
defaultMessage : 'Show lobby' defaultMessage : 'Show lobby'
@ -457,7 +457,7 @@ const TopBar = (props) =>
})} })}
className={classes.actionButton} className={classes.actionButton}
color='inherit' color='inherit'
onClick={() => onClick={() =>
{ {
loggedIn ? roomClient.logout() : roomClient.login(); loggedIn ? roomClient.logout() : roomClient.login();
}} }}
@ -480,7 +480,7 @@ const TopBar = (props) =>
<MoreIcon /> <MoreIcon />
</IconButton> </IconButton>
{ lobbyPeers.length > 0 && { lobbyPeers.length > 0 &&
<Tooltip <Tooltip
title={intl.formatMessage({ title={intl.formatMessage({
id : 'tooltip.lobby', id : 'tooltip.lobby',
defaultMessage : 'Show lobby' defaultMessage : 'Show lobby'
@ -506,7 +506,7 @@ const TopBar = (props) =>
</IconButton> </IconButton>
</span> </span>
</Tooltip> </Tooltip>
} }
</div> </div>
<div className={classes.divider} /> <div className={classes.divider} />
@ -559,8 +559,8 @@ const TopBar = (props) =>
/> />
</p> </p>
</MenuItem> </MenuItem>
<MenuItem <MenuItem
onClick={() => onClick={() =>
{ {
handleMenuClose(); handleMenuClose();
setHelpOpen(!room.helpOpen); setHelpOpen(!room.helpOpen);
@ -579,8 +579,8 @@ const TopBar = (props) =>
/> />
</p> </p>
</MenuItem> </MenuItem>
<MenuItem <MenuItem
onClick={() => onClick={() =>
{ {
handleMenuClose(); handleMenuClose();
setAboutOpen(!room.aboutOpen); setAboutOpen(!room.aboutOpen);
@ -613,7 +613,7 @@ const TopBar = (props) =>
{ loginEnabled && { loginEnabled &&
<MenuItem <MenuItem
aria-label={loginTooltip} aria-label={loginTooltip}
onClick={() => onClick={() =>
{ {
handleMenuClose(); handleMenuClose();
loggedIn ? roomClient.logout() : roomClient.login(); loggedIn ? roomClient.logout() : roomClient.login();
@ -699,7 +699,7 @@ const TopBar = (props) =>
</p> </p>
</MenuItem> </MenuItem>
{ lobbyPeers.length > 0 && { lobbyPeers.length > 0 &&
<MenuItem <MenuItem
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
id : 'tooltip.lobby', id : 'tooltip.lobby',
defaultMessage : 'Show lobby' defaultMessage : 'Show lobby'

View File

@ -3,13 +3,13 @@
* after the given amount of milliseconds has passed since * after the given amount of milliseconds has passed since
* the last time the callback function was called. * the last time the callback function was called.
*/ */
export const idle = (callback, delay) => export const idle = (callback, delay) =>
{ {
let handle; let handle;
return () => return () =>
{ {
if (handle) if (handle)
{ {
clearTimeout(handle); clearTimeout(handle);
} }