Skip to content

Commit

Permalink
Merge pull request #82 from Genymobile/dev/PLAYER-19-keybinding-post-…
Browse files Browse the repository at this point in the history
…refacto-dialog

[PLAYER-19] keybinding (post refacto dialog)
  • Loading branch information
jparez authored Jul 25, 2024
2 parents 106466b + a5d6da7 commit 28e8b50
Show file tree
Hide file tree
Showing 22 changed files with 1,236 additions and 125 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"no-console": "warn",
"no-extra-parens": "off",
"no-loss-of-precision": "error",
"no-promise-executor-return": "error",
"no-promise-executor-return": "off",
"no-template-curly-in-string": "error",
"no-unreachable-loop": "error",
"require-atomic-updates": "error",
Expand Down
146 changes: 130 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ For more information about Genymotion devices, please visit [genymotion website]
1. [With NPM/Yarn](#with-npmyarn)
2. [With CDN](#with-cdn)
3. [Usage](#usage)
4. [Features & options](#features--options)
4. [Player API](#player-api)
5. [Features & options](#features--options)
6. [Features notes](#features-notes)
1. [Key mapping](#keymapping-notes)

## Requirements

Expand Down Expand Up @@ -118,34 +121,137 @@ or check the [PaaS documentation](https://docs.genymotion.com/paas/01_Requiremen

## Player API

Plugin options and websocket communication can be handled through the API object returned by the `setupRenderer` function.
The Player API provides functionality for managing plugin options and websocket communication. These operations are handled through the API (categorized) object returned by the `setupRenderer` function.

Built-in exposed functions are
### `VM_communication`

### `getRegisteredFunctions`
- #### `disconnect`

which returns the list of available functions with an optional description
Disconnects the player from the virtual machine (VM) and cleans up the memory listener.

### `disconnect`
- #### `addEventListener`

which disconnects the player from the VM and cleanups the memory listener
Registers a listener for messages emitted from the VM.

### `addEventListener`
- Parameters:

used to listen to messages emitted from the VM such as 'fingerprint', 'gps', 'BATTERY_LEVEL'
- event (string): The name of the event to listen for. Example events include 'fingerprint', 'gps', and 'BATTERY_LEVEL'...
- callback (function): The function to call when the event is emitted. The message from the VM will be passed as an argument to the callback function.

```html
addEventListener('fingerprint', (msg)=>{ console.log(msg) })
```
- Example Usage

### `sendData`
```js
addEventListener('fingerprint', (msg) => {
console.log(msg);
});
```

used to send messages to the VM.
- #### `sendData`
Sends messages to the VM.
- Parameters:
- `data` (object): An object containing the channel and the messages to be sent.
- `channel` (string): The channel to send the messages to.
- `messages` (array): An array of messages to be sent.
- Example Usage

```html
sendData({ channel: 'battery', messages: ['set state level 10', 'set state status true'], })
```js
sendData({
channel: 'battery',
messages: ['set state level 10', 'set state status true'],
});
```

### `utils`

- #### `getRegisteredFunctions`
Returns a list of available functions with optional descriptions.

### `keymapping`

- #### `setConfig`
supply a config for keymapping
```js
{
dpad:[{
keys: {
z: {
initialX: 20,
initialY: 80,
distanceX: 0,
distanceY: -10,
description: 'move up',
},
s: {
initialX: 20,
initialY: 80,
distanceX: 0,
distanceY: 10,
description: 'move down',
},
q: {
initialX: 20,
initialY: 80,
distanceX: -10,
distanceY: 0,
description: 'move left',
},
d: {
initialX: 20,
initialY: 80,
distanceX: 10,
distanceY: 0,
description: 'move right',
},
},
name: 'character movement',
description: 'left joystick used to move the character',
}],
tap:[{
keys: {
p: {
initialX: 50,
initialY: 50,
},
}
name:'Fire'
}],
swipe: [{
keys: {
u: {
initialX: 50,
initialY: 50,
distanceX: -10,
distanceY: 0,
description: 'swipe left',
},
}
name:'Left dodge'
description: 'Dodge on the left'
}]
}
```
- #### `activeKeyMappingDebug`

helper to create the config mapping

- Parameters:
- `isTraceActivate` (boolean) : when true all click on video stream will print x and y coord over the video
- `isGridActivate` (boolean): when true display a grid over the video stream. Row and column have both a size of 10%.

- #### `enable`
- Parameters:
- `isActive` (boolean) : **Optionnal** parameter to activate or desactivate keymapping, **default false**

### `media`

- #### `mute`
- #### `unmute`

### `video`

- #### `fullscreen`
Need to be call from an user action, in accordance with browser security rules

## Features & options

A device renderer instance can be configured using the `options` argument (object). Possible configuration key / value are described below.
Expand Down Expand Up @@ -251,6 +357,14 @@ A device renderer instance can be configured using the `options` argument (objec
- **Details:**
Enables or disables the keyboard widget. This widget can be used to transmit keyboard key strokes to the Android virtual device.

### `keyboardMapping`

- **Type:** `Boolean`
- **Default:** `true`
- **Compatibility:** `PaaS`, `SaaS`
- **Details:**
Enables or disables the keyboardMapping. This widget can be used to map key with command (i.e. tap, swipe-left, tilt, ...).

### `volume`

<img align="right" src="./doc/assets/ic_sound_active_black.svg" alt="..."></img>
Expand Down
126 changes: 87 additions & 39 deletions src/APIManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,74 +5,122 @@ module.exports = class APIManager {
this.instance = instance;
this.apiFunctions = {};

// record fn to send data to instance
this.registerFunction(
'sendData',
(json) => {
// Register a function to send data to the instance
this.registerFunction({
name: 'sendData',
category: 'VM_communication',
fn: (json) => {
this.instance.sendEvent(json);
},
'send data to WS messages of player, must be a JSON object. Example: {type: "MOUSE_PRESS", x: 100, y: 100}',
);
description:
// eslint-disable-next-line max-len
'Send data to the instance using WebSocket messages. The data must be a JSON object. Example usage: {type: "MOUSE_PRESS", x: 100, y: 100}',
});

// record fn to get registered functions
this.registerFunction(
'getRegisteredFunctions',
() => this.getRegisteredFunctions(),
'list all registered functions',
);

// record fn to get registered functions
this.registerFunction(
'addEventListener',
(event, fn) => {
return this.addEventListener(event, fn);
// Register a function to add an event listener
this.registerFunction({
name: 'addEventListener',
category: 'VM_communication',
fn: (event, fn) => {
return this.instance.addEventListener(event, fn);
},
'attach event listener to WS messages of player',
);
description:
// eslint-disable-next-line max-len
'Add an event listener for WebSocket messages from the instance. The listener will be triggered whenever the specified event occurs.',
});

// record fn to disconnect from the instance
this.registerFunction(
'disconnect',
() => {
// Register a function to disconnect from the instance
this.registerFunction({
name: 'disconnect',
category: 'VM_communication',
fn: () => {
this.instance.disconnect();
},
'disconnect from the instance',
);
description: 'Disconnect from the current instance, ending the WebSocket communication.',
});

// Register a function to get all registered functions
this.registerFunction({
name: 'getRegisteredFunctions',
category: 'utils',
fn: () => this.getRegisteredFunctions(),
description: 'Retrieve a list of all registered API functions with their descriptions.',
});

// Register a function to enable or disable tracking of events
this.registerFunction({
name: 'enableTrackEvents',
category: 'analytics',
fn: (isActive) => {
this.instance.store.dispatch({type: 'ENABLE_TRACKED_EVENTS', payload: isActive});
if (!isActive) {
this.instance.store.dispatch({type: 'FLUSH_TRACKED_EVENTS'});
}
},
description:
'Enable or disable the tracking of analytic events. If disabled, all tracked events will be cleared.',
});

// Register a function to process tracked events
this.registerFunction({
name: 'trackEvents',
category: 'analytics',
fn: (cb) => {
// Subscribe to store's TRACKEVENT changes
this.instance.store.subscribe(
({trackedEvents}) => {
if (this.instance.store.state.trackedEvents.events.length) {
cb([...trackedEvents.events]);
// Flush the tracked events
this.instance.store.dispatch({type: 'FLUSH_TRACKED_EVENTS'});
}
},
['trackedEvents.events'],
);
},
description:
// eslint-disable-next-line max-len
'Invoke a callback function with an array of tracked events. This function is called whenever a new event is recorded.',
});
}

registerFunction(name, fn, description = '') {
if (this.apiFunctions[name]) {
throw new Error(`Function ${name} is already registered.`);
// Register a new API function with its name, category, function, and description
registerFunction({name, category = 'global', fn, description = ''}) {
if (this.apiFunctions[`${category}_${name}`]) {
throw new Error(`Function ${name} for category ${category} is already registered.`);
}
this.apiFunctions[name] = {
this.apiFunctions[`${category}_${name}`] = {
fn,
category,
description,
name,
};
}
addEventListener(event, fn) {
// expose listener to ws instance
this.instance.registerEventCallback(event, fn);
}

/**
* Get exposed API description
* @returns {Array} list of api description {apiName: string, apiDescription: string}
* Get the description of exposed API functions
* @returns {Array} List of API descriptions {apiName: string, apiDescription: string}
*/
getRegisteredFunctions() {
const exposedFunctionsDescription = Object.entries(this.apiFunctions).reduce((acc, val) => {
acc[val[0]] = val[1].description;
acc[val[1].name] = val[1].description;
return acc;
}, {});
return exposedFunctionsDescription;
}

/**
* Get exposed API functions
* @returns {Array} list of api fn {apiName: fn}
* @returns {Array} List of API functions {apiName: fn}
*/
getExposedApiFunctions() {
const exposedFunctions = Object.entries(this.apiFunctions).reduce((acc, val) => {
acc[val[0]] = val[1].fn;
const {name, category, fn} = val[1];

if (!acc[category]) {
acc[category] = {};
}
acc[category][name] = fn;
return acc;
}, {});
return exposedFunctions;
Expand Down
Loading

0 comments on commit 28e8b50

Please sign in to comment.