@ -1,20 +1,29 @@
/* global $, JitsiMeetJS, APP */
/* @flow */
const logger = require ( "jitsi-meet-logger" ) . getLogger ( _ _filename ) ;
import * as KeyCodes from "../keycode/keycode" ;
import { getLogger } from 'jitsi-meet-logger' ;
import * as KeyCodes from '../keycode/keycode' ;
import {
import {
EVENT _TYPES ,
EVENT _TYPES ,
PERMISSIONS _ACTIONS ,
PERMISSIONS _ACTIONS ,
REMOTE _CONTROL _EVENT _NAME
REMOTE _CONTROL _EVENT _NAME
} from "../../service/remotecontrol/Constants" ;
} from '../../service/remotecontrol/Constants' ;
import RemoteControlParticipant from "./RemoteControlParticipant" ;
import UIEvents from '../../service/UI/UIEvents' ;
import UIEvents from "../../service/UI/UIEvents" ;
import RemoteControlParticipant from './RemoteControlParticipant' ;
declare var $ : Function ;
declare var APP : Object ;
declare var JitsiMeetJS : Object ;
const ConferenceEvents = JitsiMeetJS . events . conference ;
const ConferenceEvents = JitsiMeetJS . events . conference ;
const logger = getLogger ( _ _filename ) ;
/ * *
/ * *
* Extract the keyboard key from the keyboard event .
* Extract the keyboard key from the keyboard event .
* @ param event { KeyboardEvent } the event .
*
* @ returns { KEYS } the key that is pressed or undefined .
* @ param { KeyboardEvent } event - The event .
* @ returns { KEYS } The key that is pressed or undefined .
* /
* /
function getKey ( event ) {
function getKey ( event ) {
return KeyCodes . keyboardEventToKey ( event ) ;
return KeyCodes . keyboardEventToKey ( event ) ;
@ -22,26 +31,28 @@ function getKey(event) {
/ * *
/ * *
* Extract the modifiers from the keyboard event .
* Extract the modifiers from the keyboard event .
* @ param event { KeyboardEvent } the event .
*
* @ returns { Array } with possible values : "shift" , "control" , "alt" , "command" .
* @ param { KeyboardEvent } event - The event .
* @ returns { Array } With possible values : "shift" , "control" , "alt" , "command" .
* /
* /
function getModifiers ( event ) {
function getModifiers ( event ) {
let modifiers = [ ] ;
const modifiers = [ ] ;
if ( event . shiftKey ) {
modifiers . push ( "shift" ) ;
if ( event . shiftKey ) {
modifiers . push ( 'shift' ) ;
}
}
if ( event . ctrlKey ) {
if ( event . ctrlKey ) {
modifiers . push ( "control" ) ;
modifiers . push ( 'control' ) ;
}
}
if ( event . altKey ) {
if ( event . altKey ) {
modifiers . push ( "alt" ) ;
modifiers . push ( 'alt' ) ;
}
}
if ( event . metaKey ) {
if ( event . metaKey ) {
modifiers . push ( "command" ) ;
modifiers . push ( 'command' ) ;
}
}
return modifiers ;
return modifiers ;
@ -53,14 +64,22 @@ function getModifiers(event) {
* party of the remote control session .
* party of the remote control session .
* /
* /
export default class Controller extends RemoteControlParticipant {
export default class Controller extends RemoteControlParticipant {
_area : ? Object ;
_controlledParticipant : string | null ;
_isCollectingEvents : boolean ;
_largeVideoChangedListener : Function ;
_requestedParticipant : string | null ;
_stopListener : Function ;
_userLeftListener : Function ;
/ * *
/ * *
* Creates new instance .
* Creates new instance .
* /
* /
constructor ( ) {
constructor ( ) {
super ( ) ;
super ( ) ;
this . isCollectingEvents = false ;
this . _ isCollectingEvents = false ;
this . controlledParticipant = null ;
this . _ controlledParticipant = null ;
this . requestedParticipant = null ;
this . _ requestedParticipant = null ;
this . _stopListener = this . _handleRemoteControlStoppedEvent . bind ( this ) ;
this . _stopListener = this . _handleRemoteControlStoppedEvent . bind ( this ) ;
this . _userLeftListener = this . _onUserLeft . bind ( this ) ;
this . _userLeftListener = this . _onUserLeft . bind ( this ) ;
this . _largeVideoChangedListener
this . _largeVideoChangedListener
@ -69,24 +88,28 @@ export default class Controller extends RemoteControlParticipant {
/ * *
/ * *
* Requests permissions from the remote control receiver side .
* Requests permissions from the remote control receiver side .
* @ param { string } userId the user id of the participant that will be
*
* @ param { string } userId - The user id of the participant that will be
* requested .
* requested .
* @ param { JQuerySelector } eventCaptureArea t he area that is going to be
* @ param { JQuerySelector } eventCaptureArea - T he area that is going to be
* used mouse and keyboard event capture .
* used mouse and keyboard event capture .
* @ returns { Promise < boolean > } - resolve values :
* @ returns { Promise < boolean > } Resolve values - true ( accept ) , false ( deny ) ,
* true - accept
* null ( the participant has left ) .
* false - deny
* null - the participant has left .
* /
* /
requestPermissions ( userId , eventCaptureArea ) {
requestPermissions ( userId : string , eventCaptureArea : Object ) {
if ( ! this . enabled ) {
if ( ! this . enabled ) {
return Promise . reject ( new Error ( "Remote control is disabled!" ) ) ;
return Promise . reject ( new Error ( 'Remote control is disabled!' ) ) ;
}
}
this . area = eventCaptureArea ; // $("#largeVideoWrapper")
logger . log ( "Requsting remote control permissions from: " + userId ) ;
this . _area = eventCaptureArea ; // $("#largeVideoWrapper")
logger . log ( ` Requsting remote control permissions from: ${ userId } ` ) ;
return new Promise ( ( resolve , reject ) => {
return new Promise ( ( resolve , reject ) => {
// eslint-disable-next-line prefer-const
let onUserLeft , permissionsReplyListener ;
const clearRequest = ( ) => {
const clearRequest = ( ) => {
this . requestedParticipant = null ;
this . _ requestedParticipant = null ;
APP . conference . removeConferenceListener (
APP . conference . removeConferenceListener (
ConferenceEvents . ENDPOINT _MESSAGE _RECEIVED ,
ConferenceEvents . ENDPOINT _MESSAGE _RECEIVED ,
permissionsReplyListener ) ;
permissionsReplyListener ) ;
@ -94,31 +117,35 @@ export default class Controller extends RemoteControlParticipant {
ConferenceEvents . USER _LEFT ,
ConferenceEvents . USER _LEFT ,
onUserLeft ) ;
onUserLeft ) ;
} ;
} ;
const permissionsReplyListener = ( participant , event ) => {
permissionsReplyListener = ( participant , event ) => {
let result = null ;
let result = null ;
try {
try {
result = this . _handleReply ( participant , event ) ;
result = this . _handleReply ( participant , event ) ;
} catch ( e ) {
} catch ( e ) {
clearRequest ( ) ;
reject ( e ) ;
reject ( e ) ;
}
}
if ( result !== null ) {
if ( result !== null ) {
clearRequest ( ) ;
clearRequest ( ) ;
resolve ( result ) ;
resolve ( result ) ;
}
}
} ;
} ;
const onUserLeft = ( id ) => {
onUserLeft = id => {
if ( id === this . requestedParticipant ) {
if ( id === this . _ requestedParticipant) {
clearRequest ( ) ;
clearRequest ( ) ;
resolve ( null ) ;
resolve ( null ) ;
}
}
} ;
} ;
APP . conference . addConferenceListener (
APP . conference . addConferenceListener (
ConferenceEvents . ENDPOINT _MESSAGE _RECEIVED ,
ConferenceEvents . ENDPOINT _MESSAGE _RECEIVED ,
permissionsReplyListener ) ;
permissionsReplyListener ) ;
APP . conference . addConferenceListener ( ConferenceEvents . USER _LEFT ,
APP . conference . addConferenceListener ( ConferenceEvents . USER _LEFT ,
onUserLeft ) ;
onUserLeft ) ;
this . requestedParticipant = userId ;
this . _ requestedParticipant = userId ;
this . _ sendRemoteControlEvent( userId , {
this . sendRemoteControlEvent ( userId , {
type : EVENT _TYPES . permissions ,
type : EVENT _TYPES . permissions ,
action : PERMISSIONS _ACTIONS . request
action : PERMISSIONS _ACTIONS . request
} , e => {
} , e => {
@ -130,54 +157,58 @@ export default class Controller extends RemoteControlParticipant {
/ * *
/ * *
* Handles the reply of the permissions request .
* Handles the reply of the permissions request .
* @ param { JitsiParticipant } participant the participant that has sent the
*
* reply
* @ param { JitsiParticipant } participant - The participant that has sent the
* @ param { RemoteControlEvent } event the remote control event .
* reply .
* @ param { RemoteControlEvent } event - The remote control event .
* @ returns { void }
* /
* /
_handleReply ( participant , event ) {
_handleReply ( participant : Object , event : Objec t ) {
const userId = participant . getId ( ) ;
const userId = participant . getId ( ) ;
if ( this . enabled
if ( this . enabled
&& event . name === REMOTE _CONTROL _EVENT _NAME
&& event . name === REMOTE _CONTROL _EVENT _NAME
&& event . type === EVENT _TYPES . permissions
&& event . type === EVENT _TYPES . permissions
&& userId === this . requestedParticipant ) {
&& userId === this . _ requestedParticipant) {
if ( event . action !== PERMISSIONS _ACTIONS . grant ) {
if ( event . action !== PERMISSIONS _ACTIONS . grant ) {
this . area = null ;
this . _ area = undefined ;
}
}
switch ( event . action ) {
switch ( event . action ) {
case PERMISSIONS _ACTIONS . grant : {
case PERMISSIONS _ACTIONS . grant : {
this . controlledParticipant = userId ;
this . _ controlledParticipant = userId ;
logger . log ( "Remote control permissions granted to: "
logger . log ( 'Remote control permissions granted to:' , userId ) ;
+ userId ) ;
this . _start ( ) ;
this . _start ( ) ;
return true ;
return true ;
}
}
case PERMISSIONS _ACTIONS . deny :
case PERMISSIONS _ACTIONS . deny :
return false ;
return false ;
case PERMISSIONS _ACTIONS . error :
case PERMISSIONS _ACTIONS . error :
throw new Error ( "Error occurred on receiver side" ) ;
throw new Error ( 'Error occurred on receiver side' ) ;
default :
default :
throw new Error ( "Unknown reply received!" ) ;
throw new Error ( 'Unknown reply received!' ) ;
}
}
} else {
} else {
//different message type or another user -> ignoring the message
// different message type or another user -> ignoring the message
return null ;
return null ;
}
}
}
}
/ * *
/ * *
* Handles remote control stopped .
* Handles remote control stopped .
* @ param { JitsiParticipant } participant the participant that has sent the
*
* event
* @ param { JitsiParticipant } participant - The participant that has sent the
* @ param { Object } event EndpointMessage event from the data channels .
* event .
* @ property { string } type property . The function process only events with
* @ param { Object } event - EndpointMessage event from the data channels .
* name REMOTE _CONTROL _EVENT _NAME
* @ property { string } type - The function process only events with
* @ property { RemoteControlEvent } event - the remote control event .
* name REMOTE _CONTROL _EVENT _NAME .
* @ returns { void }
* /
* /
_handleRemoteControlStoppedEvent ( participant , event ) {
_handleRemoteControlStoppedEvent ( participant : Object , event : Objec t ) {
if ( this . enabled
if ( this . enabled
&& event . name === REMOTE _CONTROL _EVENT _NAME
&& event . name === REMOTE _CONTROL _EVENT _NAME
&& event . type === EVENT _TYPES . stop
&& event . type === EVENT _TYPES . stop
&& participant . getId ( ) === this . controlledParticipant ) {
&& participant . getId ( ) === this . _ controlledParticipant) {
this . _stop ( ) ;
this . _stop ( ) ;
}
}
}
}
@ -185,9 +216,11 @@ export default class Controller extends RemoteControlParticipant {
/ * *
/ * *
* Starts processing the mouse and keyboard events . Sets conference
* Starts processing the mouse and keyboard events . Sets conference
* listeners . Disables keyboard events .
* listeners . Disables keyboard events .
*
* @ returns { void }
* /
* /
_start ( ) {
_start ( ) {
logger . log ( "Starting remote control controller." ) ;
logger . log ( 'Starting remote control controller.' ) ;
APP . UI . addListener ( UIEvents . LARGE _VIDEO _ID _CHANGED ,
APP . UI . addListener ( UIEvents . LARGE _VIDEO _ID _CHANGED ,
this . _largeVideoChangedListener ) ;
this . _largeVideoChangedListener ) ;
APP . conference . addConferenceListener (
APP . conference . addConferenceListener (
@ -200,35 +233,53 @@ export default class Controller extends RemoteControlParticipant {
/ * *
/ * *
* Disables the keyboatd shortcuts . Starts collecting remote control
* Disables the keyboatd shortcuts . Starts collecting remote control
* events .
* events . It can be used to resume an active remote control session wchich
* was paused with this . pause ( ) .
*
*
* It can be used to resume an active remote control session wchich was
* @ returns { void }
* paused with this . pause ( ) .
* /
* /
resume ( ) {
resume ( ) {
if ( ! this . enabled || this . isCollectingEvents ) {
if ( ! this . enabled || this . _ isCollectingEvents || ! this . _area ) {
return ;
return ;
}
}
logger . log ( "Resuming remote control controller." ) ;
logger . log ( 'Resuming remote control controller.' ) ;
this . isCollectingEvents = true ;
this . _ isCollectingEvents = true ;
APP . keyboardshortcut . enable ( false ) ;
APP . keyboardshortcut . enable ( false ) ;
this . area . mousemove ( event => {
const position = this . area . position ( ) ;
// $FlowDisableNextLine: we are sure that this._area is not null.
this . _sendRemoteControlEvent ( this . controlledParticipant , {
this . _area . mousemove ( event => {
// $FlowDisableNextLine: we are sure that this._area is not null.
const position = this . _area . position ( ) ;
this . sendRemoteControlEvent ( this . _controlledParticipant , {
type : EVENT _TYPES . mousemove ,
type : EVENT _TYPES . mousemove ,
x : ( event . pageX - position . left ) / this . area . width ( ) ,
y : ( event . pageY - position . top ) / this . area . height ( )
// $FlowDisableNextLine: we are sure that this._area is not null
x : ( event . pageX - position . left ) / this . _area . width ( ) ,
// $FlowDisableNextLine: we are sure that this._area is not null
y : ( event . pageY - position . top ) / this . _area . height ( )
} ) ;
} ) ;
} ) ;
} ) ;
this . area . mousedown ( this . _onMouseClickHandler . bind ( this ,
// $FlowDisableNextLine: we are sure that this._area is not null.
this . _area . mousedown ( this . _onMouseClickHandler . bind ( this ,
EVENT _TYPES . mousedown ) ) ;
EVENT _TYPES . mousedown ) ) ;
this . area . mouseup ( this . _onMouseClickHandler . bind ( this ,
// $FlowDisableNextLine: we are sure that this._area is not null.
this . _area . mouseup ( this . _onMouseClickHandler . bind ( this ,
EVENT _TYPES . mouseup ) ) ;
EVENT _TYPES . mouseup ) ) ;
this . area . dblclick (
// $FlowDisableNextLine: we are sure that this._area is not null.
this . _area . dblclick (
this . _onMouseClickHandler . bind ( this , EVENT _TYPES . mousedblclick ) ) ;
this . _onMouseClickHandler . bind ( this , EVENT _TYPES . mousedblclick ) ) ;
this . area . contextmenu ( ( ) => false ) ;
this . area [ 0 ] . onmousewheel = event => {
// $FlowDisableNextLine: we are sure that this._area is not null.
this . _sendRemoteControlEvent ( this . controlledParticipant , {
this . _area . contextmenu ( ( ) => false ) ;
// $FlowDisableNextLine: we are sure that this._area is not null.
this . _area [ 0 ] . onmousewheel = event => {
this . sendRemoteControlEvent ( this . _controlledParticipant , {
type : EVENT _TYPES . mousescroll ,
type : EVENT _TYPES . mousescroll ,
x : event . deltaX ,
x : event . deltaX ,
y : event . deltaY
y : event . deltaY
@ -243,12 +294,14 @@ export default class Controller extends RemoteControlParticipant {
* Stops processing the mouse and keyboard events . Removes added listeners .
* Stops processing the mouse and keyboard events . Removes added listeners .
* Enables the keyboard shortcuts . Displays dialog to notify the user that
* Enables the keyboard shortcuts . Displays dialog to notify the user that
* remote control session has ended .
* remote control session has ended .
*
* @ returns { void }
* /
* /
_stop ( ) {
_stop ( ) {
if ( ! this . controlledParticipant ) {
if ( ! this . _ controlledParticipant) {
return ;
return ;
}
}
logger . log ( "Stopping remote control controller." ) ;
logger . log ( 'Stopping remote control controller.' ) ;
APP . UI . removeListener ( UIEvents . LARGE _VIDEO _ID _CHANGED ,
APP . UI . removeListener ( UIEvents . LARGE _VIDEO _ID _CHANGED ,
this . _largeVideoChangedListener ) ;
this . _largeVideoChangedListener ) ;
APP . conference . removeConferenceListener (
APP . conference . removeConferenceListener (
@ -256,29 +309,28 @@ export default class Controller extends RemoteControlParticipant {
this . _stopListener ) ;
this . _stopListener ) ;
APP . conference . removeConferenceListener ( ConferenceEvents . USER _LEFT ,
APP . conference . removeConferenceListener ( ConferenceEvents . USER _LEFT ,
this . _userLeftListener ) ;
this . _userLeftListener ) ;
this . controlledParticipant = null ;
this . _ controlledParticipant = null ;
this . pause ( ) ;
this . pause ( ) ;
this . area = null ;
this . _ area = undefined ;
APP . UI . messageHandler . openMessageDialog (
APP . UI . messageHandler . openMessageDialog (
"dialog.remoteControlTitle" ,
'dialog.remoteControlTitle' ,
"dialog.remoteControlStopMessage"
'dialog.remoteControlStopMessage'
) ;
) ;
}
}
/ * *
/ * *
* Executes this . _stop ( ) mehtod :
* Executes this . _stop ( ) mehtod which stops processing the mouse and
* Stops processing the mouse and keyboard events . Removes added listeners .
* keyboard events , removes added listeners , enables the keyboard shortcuts ,
* Enables the keyboar d shortcuts . D isplays dialog to notify the user that
* displays dialog to notify the user that remote control session has ended .
* remote control session has ended .
* In addition sends stop message to the controlled participant .
*
*
* In addition :
* @ returns { void }
* Sends stop message to the controlled participant .
* /
* /
stop ( ) {
stop ( ) {
if ( ! this . controlledParticipant ) {
if ( ! this . _ controlledParticipant) {
return ;
return ;
}
}
this . _ sendRemoteControlEvent( this . controlledParticipant , {
this . sendRemoteControlEvent ( this . _ controlledParticipant, {
type : EVENT _TYPES . stop
type : EVENT _TYPES . stop
} ) ;
} ) ;
this . _stop ( ) ;
this . _stop ( ) ;
@ -288,88 +340,112 @@ export default class Controller extends RemoteControlParticipant {
* Pauses the collecting of events and enables the keyboard shortcus . But
* Pauses the collecting of events and enables the keyboard shortcus . But
* it doesn ' t removes any other listeners . Basically the remote control
* it doesn ' t removes any other listeners . Basically the remote control
* session will be still active after this . pause ( ) , but no events from the
* session will be still active after this . pause ( ) , but no events from the
* controller side will be captured and sent .
* controller side will be captured and sent . You can resume the collecting
* of the events with this . resume ( ) .
*
*
* You can resume the collecting of the events with this . resume ( ) .
* @ returns { void }
* /
* /
pause ( ) {
pause ( ) {
if ( ! this . controlledParticipant ) {
if ( ! this . _ controlledParticipant) {
return ;
return ;
}
}
logger . log ( "Pausing remote control controller." ) ;
logger . log ( 'Pausing remote control controller.' ) ;
this . isCollectingEvents = false ;
this . _ isCollectingEvents = false ;
APP . keyboardshortcut . enable ( true ) ;
APP . keyboardshortcut . enable ( true ) ;
this . area . off ( "mousemove" ) ;
this . area . off ( "mousedown" ) ;
// $FlowDisableNextLine: we are sure that this._area is not null.
this . area . off ( "mouseup" ) ;
this . _area . off ( 'mousemove' ) ;
this . area . off ( "contextmenu" ) ;
this . area . off ( "dblclick" ) ;
// $FlowDisableNextLine: we are sure that this._area is not null.
$ ( window ) . off ( "keydown" ) ;
this . _area . off ( 'mousedown' ) ;
$ ( window ) . off ( "keyup" ) ;
this . area [ 0 ] . onmousewheel = undefined ;
// $FlowDisableNextLine: we are sure that this._area is not null.
this . _area . off ( 'mouseup' ) ;
// $FlowDisableNextLine: we are sure that this._area is not null.
this . _area . off ( 'contextmenu' ) ;
// $FlowDisableNextLine: we are sure that this._area is not null.
this . _area . off ( 'dblclick' ) ;
$ ( window ) . off ( 'keydown' ) ;
$ ( window ) . off ( 'keyup' ) ;
// $FlowDisableNextLine: we are sure that this._area is not null.
this . _area [ 0 ] . onmousewheel = undefined ;
}
}
/ * *
/ * *
* Handler for mouse click events .
* Handler for mouse click events .
* @ param { String } type the type of event ( "mousedown" / "mouseup" )
*
* @ param { Event } event the mouse event .
* @ param { string } type - The type of event ( "mousedown" / "mouseup" ) .
* @ param { Event } event - The mouse event .
* @ returns { void }
* /
* /
_onMouseClickHandler ( type , event ) {
_onMouseClickHandler ( type : string , event : Objec t ) {
this . _sendRemoteControlEvent ( this . controlledParticipant , {
this . sendRemoteControlEvent ( this . _ controlledParticipant, {
type : type ,
type ,
button : event . which
button : event . which
} ) ;
} ) ;
}
}
/ * *
/ * *
* Returns true if the remote control session is started .
* Returns true if the remote control session is started .
*
* @ returns { boolean }
* @ returns { boolean }
* /
* /
isStarted ( ) {
isStarted ( ) {
return this . controlledParticipant !== null ;
return this . _ controlledParticipant !== null ;
}
}
/ * *
/ * *
* Returns the id of the requested participant
* Returns the id of the requested participant .
* @ returns { string } this . requestedParticipant .
*
* @ returns { string } The id of the requested participant .
* NOTE : This id should be the result of JitsiParticipant . getId ( ) call .
* NOTE : This id should be the result of JitsiParticipant . getId ( ) call .
* /
* /
getRequestedParticipant ( ) {
getRequestedParticipant ( ) {
return this . requestedParticipant ;
return this . _ requestedParticipant;
}
}
/ * *
/ * *
* Handler for key press events .
* Handler for key press events .
* @ param { String } type the type of event ( "keydown" / "keyup" )
*
* @ param { Event } event the key event .
* @ param { string } type - The type of event ( "keydown" / "keyup" ) .
* @ param { Event } event - The key event .
* @ returns { void }
* /
* /
_onKeyPessHandler ( type , event ) {
_onKeyPessHandler ( type : string , event : Objec t ) {
this . _ sendRemoteControlEvent( this . controlledParticipant , {
this . sendRemoteControlEvent ( this . _ controlledParticipant, {
type : type ,
type ,
key : getKey ( event ) ,
key : getKey ( event ) ,
modifiers : getModifiers ( event ) ,
modifiers : getModifiers ( event )
} ) ;
} ) ;
}
}
/ * *
/ * *
* Calls the stop method if the other side have left .
* Calls the stop method if the other side have left .
* @ param { string } id - the user id for the participant that have left
*
* @ param { string } id - The user id for the participant that have left .
* @ returns { void }
* /
* /
_onUserLeft ( id ) {
_onUserLeft ( id : string ) {
if ( this . controlledParticipant === id ) {
if ( this . _ controlledParticipant === id ) {
this . _stop ( ) ;
this . _stop ( ) ;
}
}
}
}
/ * *
/ * *
* Handles changes of the participant displayed on the large video .
* Handles changes of the participant displayed on the large video .
* @ param { string } id - the user id for the participant that is displayed .
*
* @ param { string } id - The user id for the participant that is displayed .
* @ returns { void }
* /
* /
_onLargeVideoIdChanged ( id ) {
_onLargeVideoIdChanged ( id : string ) {
if ( ! this . controlledParticipant ) {
if ( ! this . _ controlledParticipant) {
return ;
return ;
}
}
if ( this . controlledParticipant == id ) {
if ( this . _ controlledParticipant = == id ) {
this . resume ( ) ;
this . resume ( ) ;
} else {
} else {
this . pause ( ) ;
this . pause ( ) ;