├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── CameraManualOverride.xml
├── IMAGES
├── 0image.png
├── AudioConnections.png
├── CameraControlPanel.jpg
├── Touch10Initial.png
└── VideoConnections.png
├── LICENSE.md
├── README.md
├── aux_codec_macro.js
└── main_codec_macro.js
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as maintainers of this Cisco Sample Code pledge to making participation with our project a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Showing empathy towards other people
15 |
16 | Examples of unacceptable behavior include:
17 |
18 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
19 | * Trolling, insulting/derogatory comments, and personal or political attacks
20 | * Public or private harassment
21 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
22 | * Other conduct which could reasonably be considered inappropriate in a professional setting
23 |
24 | ## Our Responsibilities
25 |
26 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
27 |
28 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other interactions with this project that are not aligned to this Code of Conduct, or to ban temporarily or permanently any person for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
29 |
30 | ## Scope
31 |
32 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project. Examples of representing a project include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
33 |
34 | ## Enforcement
35 |
36 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the Cisco SE GitHub team at ciscose-github@cisco.com. The team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
37 |
38 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project or Cisco SE Leadership.
39 |
40 | ## Attribution
41 |
42 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
43 |
44 | [homepage]: http://contributor-covenant.org
45 | [version]: http://contributor-covenant.org/version/1/4/
46 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Cisco Sample Code
2 |
3 | This project, and the code contained herein, is provided for example and/or demonstration purposes by Cisco for use by our partners and customers in working with Cisco's products and services. While Cisco's customers and partners are free to use this code pursuant to the terms set forth in the [LICENSE][LICENSE], this is not an Open Source project as we are not seeking to build a community around this project and its capabilities.
4 |
5 |
6 | We do desire to provide functional and high-quality examples and demonstrations. If you should discover some bug, issue, or opportunity for enhancement with the code contained in this project, please do notify us by:
7 |
8 | 1. **Reviewing Open Issues** to verify that the issue hasn't already been reported.
9 | 2. **Opening a New Issue** to report the bug, issue, or enhancement opportunity.
10 |
11 | [LICENSE]: ../LICENSE
12 |
--------------------------------------------------------------------------------
/CameraManualOverride.xml:
--------------------------------------------------------------------------------
1 |
2 | 1.8
3 |
4 | 1
5 | panel_manual_override
6 | local
7 | Statusbar
8 | Camera
9 | #07C1E4
10 | Camera Control
11 | Custom
12 |
13 | Camera Control
14 |
15 |
16 |
17 | widget_9
18 | Select manual or automatic control of Quad Cameras
19 | Text
20 | size=4;fontSize=normal;align=center
21 |
22 |
23 |
24 |
25 |
26 | widget_8
27 | Manual
28 | Text
29 | size=1;fontSize=normal;align=center
30 |
31 |
32 | widget_override
33 | ToggleButton
34 | size=1
35 |
36 |
37 | widget_6
38 | Automatic
39 | Text
40 | size=2;fontSize=normal;align=center
41 |
42 |
43 |
44 |
45 |
46 | widget_10
47 | For testing while not in call, turn on fullscreen Selfview
48 | Text
49 | size=4;fontSize=normal;align=center
50 |
51 |
52 |
53 |
54 |
55 | widget_14
56 | Off
57 | Text
58 | size=1;fontSize=normal;align=center
59 |
60 |
61 | widget_FS_selfview
62 | ToggleButton
63 | size=1
64 |
65 |
66 | widget_12
67 | Selfview
68 | Text
69 | size=2;fontSize=normal;align=center
70 |
71 |
72 | panel_manual_override
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/IMAGES/0image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gve-sw/GVE_DevNet_WebexDevicesExecutiveRoomQuadCamSwitcherMacro/bc306fcfb4190be6aeeed01a289f6642eb358357/IMAGES/0image.png
--------------------------------------------------------------------------------
/IMAGES/AudioConnections.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gve-sw/GVE_DevNet_WebexDevicesExecutiveRoomQuadCamSwitcherMacro/bc306fcfb4190be6aeeed01a289f6642eb358357/IMAGES/AudioConnections.png
--------------------------------------------------------------------------------
/IMAGES/CameraControlPanel.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gve-sw/GVE_DevNet_WebexDevicesExecutiveRoomQuadCamSwitcherMacro/bc306fcfb4190be6aeeed01a289f6642eb358357/IMAGES/CameraControlPanel.jpg
--------------------------------------------------------------------------------
/IMAGES/Touch10Initial.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gve-sw/GVE_DevNet_WebexDevicesExecutiveRoomQuadCamSwitcherMacro/bc306fcfb4190be6aeeed01a289f6642eb358357/IMAGES/Touch10Initial.png
--------------------------------------------------------------------------------
/IMAGES/VideoConnections.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gve-sw/GVE_DevNet_WebexDevicesExecutiveRoomQuadCamSwitcherMacro/bc306fcfb4190be6aeeed01a289f6642eb358357/IMAGES/VideoConnections.png
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | CISCO SAMPLE CODE LICENSE
2 | Version 1.1
3 | Copyright (c) 2020 Cisco and/or its affiliates
4 |
5 | These terms govern this Cisco Systems, Inc. ("Cisco"), example or demo
6 | source code and its associated documentation (together, the "Sample
7 | Code"). By downloading, copying, modifying, compiling, or redistributing
8 | the Sample Code, you accept and agree to be bound by the following terms
9 | and conditions (the "License"). If you are accepting the License on
10 | behalf of an entity, you represent that you have the authority to do so
11 | (either you or the entity, "you"). Sample Code is not supported by Cisco
12 | TAC and is not tested for quality or performance. This is your only
13 | license to the Sample Code and all rights not expressly granted are
14 | reserved.
15 |
16 | 1. LICENSE GRANT: Subject to the terms and conditions of this License,
17 | Cisco hereby grants to you a perpetual, worldwide, non-exclusive, non-
18 | transferable, non-sublicensable, royalty-free license to copy and
19 | modify the Sample Code in source code form, and compile and
20 | redistribute the Sample Code in binary/object code or other executable
21 | forms, in whole or in part, solely for use with Cisco products and
22 | services. For interpreted languages like Java and Python, the
23 | executable form of the software may include source code and
24 | compilation is not required.
25 |
26 | 2. CONDITIONS: You shall not use the Sample Code independent of, or to
27 | replicate or compete with, a Cisco product or service. Cisco products
28 | and services are licensed under their own separate terms and you shall
29 | not use the Sample Code in any way that violates or is inconsistent
30 | with those terms (for more information, please visit:
31 | www.cisco.com/go/terms).
32 |
33 | 3. OWNERSHIP: Cisco retains sole and exclusive ownership of the Sample
34 | Code, including all intellectual property rights therein, except with
35 | respect to any third-party material that may be used in or by the
36 | Sample Code. Any such third-party material is licensed under its own
37 | separate terms (such as an open source license) and all use must be in
38 | full accordance with the applicable license. This License does not
39 | grant you permission to use any trade names, trademarks, service
40 | marks, or product names of Cisco. If you provide any feedback to Cisco
41 | regarding the Sample Code, you agree that Cisco, its partners, and its
42 | customers shall be free to use and incorporate such feedback into the
43 | Sample Code, and Cisco products and services, for any purpose, and
44 | without restriction, payment, or additional consideration of any kind.
45 | If you initiate or participate in any litigation against Cisco, its
46 | partners, or its customers (including cross-claims and counter-claims)
47 | alleging that the Sample Code and/or its use infringe any patent,
48 | copyright, or other intellectual property right, then all rights
49 | granted to you under this License shall terminate immediately without
50 | notice.
51 |
52 | 4. LIMITATION OF LIABILITY: CISCO SHALL HAVE NO LIABILITY IN CONNECTION
53 | WITH OR RELATING TO THIS LICENSE OR USE OF THE SAMPLE CODE, FOR
54 | DAMAGES OF ANY KIND, INCLUDING BUT NOT LIMITED TO DIRECT, INCIDENTAL,
55 | AND CONSEQUENTIAL DAMAGES, OR FOR ANY LOSS OF USE, DATA, INFORMATION,
56 | PROFITS, BUSINESS, OR GOODWILL, HOWEVER CAUSED, EVEN IF ADVISED OF THE
57 | POSSIBILITY OF SUCH DAMAGES.
58 |
59 | 5. DISCLAIMER OF WARRANTY: SAMPLE CODE IS INTENDED FOR EXAMPLE PURPOSES
60 | ONLY AND IS PROVIDED BY CISCO "AS IS" WITH ALL FAULTS AND WITHOUT
61 | WARRANTY OR SUPPORT OF ANY KIND. TO THE MAXIMUM EXTENT PERMITTED BY
62 | LAW, ALL EXPRESS AND IMPLIED CONDITIONS, REPRESENTATIONS, AND
63 | WARRANTIES INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTY OR
64 | CONDITION OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-
65 | INFRINGEMENT, SATISFACTORY QUALITY, NON-INTERFERENCE, AND ACCURACY,
66 | ARE HEREBY EXCLUDED AND EXPRESSLY DISCLAIMED BY CISCO. CISCO DOES NOT
67 | WARRANT THAT THE SAMPLE CODE IS SUITABLE FOR PRODUCTION OR COMMERCIAL
68 | USE, WILL OPERATE PROPERLY, IS ACCURATE OR COMPLETE, OR IS WITHOUT
69 | ERROR OR DEFECT.
70 |
71 | 6. GENERAL: This License shall be governed by and interpreted in
72 | accordance with the laws of the State of California, excluding its
73 | conflict of laws provisions. You agree to comply with all applicable
74 | United States export laws, rules, and regulations. If any provision of
75 | this License is judged illegal, invalid, or otherwise unenforceable,
76 | that provision shall be severed and the rest of the License shall
77 | remain in full force and effect. No failure by Cisco to enforce any of
78 | its rights related to the Sample Code or to a breach of this License
79 | in a particular situation will act as a waiver of such rights. In the
80 | event of any inconsistencies with any other terms, this License shall
81 | take precedence.
82 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GVE DevNet Webex Devices Executive Room QuadCam Switcher Macro
2 | Webex Room Device macro to switch input source to QuadCam camera pointed in the direction of the area of the room that has audio activity as detected by the table microphones in the zone.
3 |
4 | **NOTE:** There is a new version of this macro that supports also P60 and 4K PTZ cameras in addition to other enhancements.
5 | It is located here:
6 | https://github.com/gve-sw/gve_devnet_webex_devices_executive_room_voice_activated_switching_macro
7 |
8 | ## Contacts
9 | * Gerardo Chaves (gchaves@cisco.com)
10 | * Enrico Conedera (econeder@cisco.com)
11 |
12 | ## Solution Components
13 | * Webex Collaboration Endpoints
14 | * Javascript
15 | * xAPI
16 |
17 | ## Requirements
18 | * Devices must be running RoomOS 10.8 or later
19 |
20 | ## Installation/Configuration
21 | This sample includes 2 macros that are installed on separate Webex Codecs to work in conjunction to cover a large executive room with 2 QuadCam cameras given the limitation that only 1 QuadCam can be connected to a codec. The first QuadCam is connected to the main codec (typically a Webex Codec Pro) and the second one to the auxiliary codec (typically a Webex Codec Plus) which in turn has it's HDMI 1 video output connected to the main codec into it's HDMI 2 input.
22 |
23 | This diagram shows how the codecs should be connected when it comes to the video connections. Notice that the camera input 2 on the Primary (Main) Codec comes from
24 | Output 1 on the Auxiliary codec since we are just using that codec to pass along the video from the QuadCam.
25 |
26 | 
27 |
28 | And this diagram shows all the audio connections. Notice that all microphones are connected to the Primary (Main) Codec since the macro running
29 | there is the one making all of the decisions on which camara to activate depending on audio activity.
30 |
31 | 
32 |
33 | After the codecs, cameras and microphones are correctly connected, do the following to configure the macros on each codec:
34 |
35 | 1) Edit your local version of 'main_codec_macro.js' and change the following:
36 | - AUX_CODEC_IP: set to the IP address or hostname of the Aux codec
37 | ```
38 | const AUX_CODEC_IP ='192.168.0.80';
39 | ```
40 | - AUX_CODEC_USERNAME and AUX_CODEC_PASSWORD are the username and password of a user with integrator or admin roles on the Auxiliary Codec
41 | Here are instructions on how to configure local user accounts on Webex Devices: https://help.webex.com/en-us/jkhs20/Local-User-Administration-on-Room-and-Desk-Devices)
42 | ```
43 | const AUX_CODEC_USERNAME='username';
44 | const AUX_CODEC_PASSWORD='password';
45 | ```
46 | - CONNECTORS: Specify the input connectors associated to the microphones being used in the room (i.e. the macro will evaluate mic input id's 1-8 for it's switching logic):
47 | ```
48 | const CONNECTORS = [1,2,3,4,5,6,7,8];
49 | ```
50 | - MAP_CAMERA_SOURCE_IDS: Associate the connectors to specific input source ID corresponding to the camera that covers where the mic is located. (i.e. mics 1,2,3 and 4 are located where Camera associated to video input 1 is pointing at):
51 | ```
52 | const MAP_CAMERA_SOURCE_IDS = [1,1,1,1,2,2,2,2];
53 | ```
54 | - overviewShowDouble: Defines what is shown to the far end (the video the main codec sends into the call or conference) when in "overview" mode where nobody is speaking or there is no prominent speaker detected by any of the microphones. The example below shows how to enable that the two inputs defined in OVERVIEW_DOUBLE_SOURCE_IDS are shown side by side.
55 | ```
56 | const overviewShowDouble = true
57 | ```
58 |
59 | - OVERVIEW_SINGLE_SOURCE_ID: Specify here the source video ID to use when in overview mode if you set overviewShowDouble to false (i.e. use in camera associated to video input 1):
60 | ```
61 | const OVERVIEW_SINGLE_SOURCE_ID = 1
62 | ```
63 | - OVERVIEW_DOUBLE_SOURCE_IDS: Specify here the source video array of two IDs to use when in overview mode if you set overviewShowDouble to true (i.e. use in camera associated to video input 2 and 1):
64 | ```
65 | const OVERVIEW_DOUBLE_SOURCE_IDS = [2,1]
66 | ```
67 |
68 |
69 | 2) Edit your local version of 'aux_codec_macro.js' and change the following:
70 | - MAIN_CODEC_IP: set to the IP address or hostname of the Main codec
71 | ```
72 | const MAIN_CODEC_IP ='192.168.0.80';
73 | ```
74 | - MAIN_CODEC_USERNAME and MAIN_CODEC_PASSWORD are the username and password of a user with integrator or admin roles on the Main Codec
75 | Here are instructions on how to configure local user accounts on Webex Devices: https://help.webex.com/en-us/jkhs20/Local-User-Administration-on-Room-and-Desk-Devices)
76 | ```
77 | const MAIN_CODEC_USERNAME='username';
78 | const MAIN_CODEC_PASSWORD='password';
79 | ```
80 |
81 | 3) Load the code contained in the 'main_codec_macro.js' into the Macro editor of the main codec and the 'aux_codec_macro.js' into the Macro editor of the auxiliary codec. 'main_codec_macro.js' contains all of the logic to choose which input to select (QuadCam1 connected directly vs QuadCam2 connected indirectly via Aux codec) as well as the logic to make sure that the Aux codec is active and receiving the input from QuadCam2. If you are unfamiliar with Cisco Room device macros and how to manage them, this is a good article to get started:
82 | https://help.webex.com/en-us/np8b6m6/Use-of-Macros-with-Room-and-Desk-Devices-and-Webex-Boards
83 |
84 |
85 | 4) To be able to manually turn on and off the functionality of this macro from the Touch 10 device of the Main codec, please import the ```CameraManualOverride.xml``` file into the
86 | main codec using it's UI Extension Editor. Without this custom panel installed on the Main codec, you will see errors being logged by the
87 | macro given that it updates the state of toggle buttons on it. If you are unfamiliar with UI Extensions and how to load them from the devices
88 | web interface, visit this article: https://help.webex.com/en-us/n18glho/User-Interface-Extensions-with-Room-and-Desk-Devices-and-Webex-Boards .
89 | You can find more details and screenshots also in this guide: https://www.cisco.com/c/dam/en/us/td/docs/telepresence/endpoint/roomos-103/desk-room-kit-boards-customization-guide-roomos-103.pdf
90 |
91 | NOTE: The macro is written to hide the mid-call controls “Lock meeting” and “Record”. The reason for this is so that the
92 | custom “Camera Control” button added above can be seen always in a call without having to press on the More.. button.
93 | If you prefer to have the mid-call controls always showing edit line 34 of the 'main_codec_macro.js' on the main codec that has the following config command:
94 | ```
95 | xapi.Config.UserInterface.Features.Call.MidCallControls.set("Hidden")
96 | ```
97 | and change the value in the string being sent as a parameter from “Hidden” to “Auto”
98 |
99 |
100 | ## Usage
101 |
102 | Once the macros are loaded and activated, the correct video switching behavior should occur automatically every time a call connects.
103 |
104 | To manually turn on and off the Automatic switching based on microphone input across the two quad cams, you can use the panel installed on step 4 above; just press the Camera Control button on the Touch 10:
105 |
106 |
107 | 
108 |
109 | That should bring up the following Panel where you can select the behavior desired:
110 |
111 | 
112 |
113 |
114 | The following actions with turn off the automatic behaviour for you in addition to manually turning it off:
115 |
116 | a) When a call disconnects
117 | b) Bringing the main codec out of half-wake mode
118 |
119 |
120 |
121 |
122 | ### LICENSE
123 |
124 | Provided under Cisco Sample Code License, for details see [LICENSE](LICENSE.md)
125 |
126 | ### CODE_OF_CONDUCT
127 |
128 | Our code of conduct is available [here](CODE_OF_CONDUCT.md)
129 |
130 | ### CONTRIBUTING
131 |
132 | See our contributing guidelines [here](CONTRIBUTING.md)
133 |
134 | #### DISCLAIMER:
135 | Please note: This script is meant for demo purposes only. All tools/ scripts in this repo are released for use "AS IS" without any warranties of any kind, including, but not limited to their installation, use, or performance. Any use of these scripts and tools is at your own risk. There is no guarantee that they have been through thorough testing in a comparable environment and we are not responsible for any damage or data loss incurred with their use.
136 | You are responsible for reviewing and testing any scripts you run thoroughly before use in any non-testing environment.
--------------------------------------------------------------------------------
/aux_codec_macro.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2021 Cisco and/or its affiliates.
3 | This software is licensed to you under the terms of the Cisco Sample
4 | Code License, Version 1.1 (the "License"). You may obtain a copy of the
5 | License at
6 | https://developer.cisco.com/docs/licenses
7 | All use of the material herein must be in accordance with the terms of
8 | the License. All rights not expressly granted by the License are
9 | reserved. Unless required by applicable law or agreed to separately in
10 | writing, software distributed under the License is distributed on an "AS
11 | IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | or implied.
13 | */
14 |
15 |
16 | const xapi = require('xapi');
17 |
18 | /////////////////////////////////////////////////////////////////////////////////////////
19 | // CONSTANTS/ENUMS
20 | /////////////////////////////////////////////////////////////////////////////////////////
21 |
22 | // IP Address of MAIN codec (i.e. CodecPro)
23 | const MAIN_CODEC_IP ='10.10.10.11';
24 |
25 | // MAIN_CODEC_USERNAME and MAIN_CODEC_PASSWORD are the username and password of a user with integrator or admin roles on the Main Codec
26 | // Here are instructions on how to configure local user accounts on Webex Devices: https://help.webex.com/en-us/jkhs20/Local-User-Administration-on-Room-and-Desk-Devices)
27 | const MAIN_CODEC_USERNAME='username';
28 | const MAIN_CODEC_PASSWORD='password';
29 |
30 |
31 | function encode(s) {
32 | var c = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
33 | o = [];
34 | for (var i = 0, n = s.length; i < n;) {
35 | var c1 = s.charCodeAt(i++),
36 | c2 = s.charCodeAt(i++),
37 | c3 = s.charCodeAt(i++);
38 | o.push(c.charAt(c1 >> 2));
39 | o.push(c.charAt(((c1 & 3) << 4) | (c2 >> 4)));
40 | o.push(c.charAt(i < n + 2 ? ((c2 & 15) << 2) | (c3 >> 6) : 64));
41 | o.push(c.charAt(i < n + 1 ? c3 & 63 : 64));
42 | }
43 | return o.join("");
44 | }
45 |
46 | const MAIN_CODEC_AUTH=encode(MAIN_CODEC_USERNAME+':'+MAIN_CODEC_PASSWORD);
47 |
48 | /////////////////////////////////////////////////////////////////////////////////////////
49 | // STARTUP SCRIPT
50 | // The following sections constitute a startup script that the codec will run whenever it
51 | // boots.
52 | /////////////////////////////////////////////////////////////////////////////////////////
53 |
54 |
55 | xapi.config.set('Video Monitors', 'Single');
56 | xapi.config.set('Video Output Connector 1 MonitorRole', 'First');
57 | xapi.config.set('Standby Halfwake Mode', 'Manual').catch((error) => {
58 | console.log('Your software version does not support this configuration. Please install ‘Custom Wallpaper’ on the codec in order to prevent Halfwake mode from occurring.');
59 | console.error(error);
60 | });
61 |
62 | xapi.config.set('Standby Control', 'Off');
63 | xapi.command('Video Selfview Set', {Mode: 'On', FullScreenMode: 'On', OnMonitorRole: 'First'})
64 | .catch((error) => { console.error(error); });
65 |
66 | /////////////////////////////////////////////////////////////////////////////////////////
67 | // VARIABLES
68 | /////////////////////////////////////////////////////////////////////////////////////////
69 |
70 | let main_codec = { url: MAIN_CODEC_IP, auth: MAIN_CODEC_AUTH};
71 |
72 | /////////////////////////////////////////////////////////////////////////////////////////
73 | // FUNCTIONS
74 | /////////////////////////////////////////////////////////////////////////////////////////
75 |
76 | // ---------------------- INITIALIZATION
77 |
78 | function init() {
79 | console.log('init');
80 |
81 | // configure HTTP settings
82 | xapi.config.set('HttpClient Mode', 'On').catch(handleError);
83 | xapi.config.set('HttpClient AllowInsecureHTTPS:', 'True').catch(handleError);
84 | xapi.config.set('HttpClient AllowHTTP:', 'True').catch(handleError);
85 |
86 | // register callback for processing messages from main codec
87 | xapi.event.on('Message Send', handleMessage);
88 | }
89 | // ---------------------- ERROR HANDLING
90 |
91 | function handleError(error) {
92 | console.log(error);
93 | }
94 |
95 | // ---------------------- INTER-CODEC COMMUNICATION
96 |
97 | function sendIntercodecMessage(message) {
98 | console.log(`sendIntercodecMessage: message = ${message}`);
99 |
100 | let url = 'https://' + main_codec.url + '/putxml';
101 |
102 | let headers = [
103 | 'Content-Type: text/xml',
104 | 'Authorization: Basic ' + main_codec.auth
105 | ];
106 |
107 | let payload = ""+ message +"";
108 |
109 | xapi.command('HttpClient Post', {Url: url, Header: headers, AllowInsecureHTTPS: 'True'}, payload)
110 | .then((response) => {if(response.StatusCode === "200") {console.log("Successfully sent: " + payload)}});
111 | }
112 |
113 | /* This is the end of the startup script section. At this point the aux_codec should be ready to receive messages
114 | from the main_codec.
115 | THRERE ARE FIVE MESSAGES:
116 | 1. wake_up
117 | 2. VTC-1_status which is sent at the same time as wake-up, and is intended to check the health of the Aux Codec Plus.
118 | 3. shut_down
119 | 4. side-by-side
120 | This is the default mode that is used when the mute button is pressed, after the "side by side timer" expires, and also
121 | at the beginning of any call.
122 | 5. automatic_mode
123 | This is the automatic camera switching function.
124 | */
125 |
126 | // ---------------------- MACROS
127 |
128 | function handleMessage(event) {
129 | console.log(`handleMessage: ${event.Text}`);
130 |
131 | switch(event.Text) {
132 | case "VTC-1_status":
133 | handleMacroStatus();
134 | break;
135 | case 'wake_up':
136 | handleWakeUp();
137 | break;
138 | case 'shut_down':
139 | handleShutDown();
140 | break;
141 | case 'side_by_side':
142 | handleSideBySide();
143 | break;
144 | case 'automatic_mode':
145 | handleAutomaticMode();
146 | break;
147 | }
148 | }
149 |
150 | function handleMacroStatus() {
151 | console.log('handleMacroStatus');
152 | sendIntercodecMessage('VTC-1_OK');
153 | }
154 |
155 | function handleWakeUp() {
156 | console.log('handleWakeUp');
157 |
158 | // send required commands to this codec
159 | xapi.command('Standby Deactivate').catch(handleError);
160 | }
161 |
162 | function handleShutDown() {
163 | console.log('handleShutDown');
164 |
165 | // send required commands to this codec
166 | xapi.command('Standby Activate').catch(handleError);
167 | }
168 |
169 | function handleSideBySide() {
170 | console.log('handleSideBySide');
171 |
172 | // send required commands to this codec
173 | xapi.command('Cameras SpeakerTrack Deactivate').catch(handleError);
174 | xapi.command('Camera Preset Activate', { PresetId: 30 }).catch(handleError);
175 | }
176 |
177 | function handleAutomaticMode() {
178 | console.log('handleAutomaticMode');
179 |
180 | // send required commands to this codec
181 | xapi.command('Cameras SpeakerTrack Activate').catch(handleError);
182 | }
183 |
184 | init();
--------------------------------------------------------------------------------
/main_codec_macro.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2021 Cisco and/or its affiliates.
3 | This software is licensed to you under the terms of the Cisco Sample
4 | Code License, Version 1.1 (the "License"). You may obtain a copy of the
5 | License at
6 | https://developer.cisco.com/docs/licenses
7 | All use of the material herein must be in accordance with the terms of
8 | the License. All rights not expressly granted by the License are
9 | reserved. Unless required by applicable law or agreed to separately in
10 | writing, software distributed under the License is distributed on an "AS
11 | IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12 | or implied.
13 | */
14 | /////////////////////////////////////////////////////////////////////////////////////////
15 | // REQUIREMENTS
16 | /////////////////////////////////////////////////////////////////////////////////////////
17 |
18 | const xapi = require('xapi');
19 |
20 | /////////////////////////////////////////////////////////////////////////////////////////
21 | // CONSTANTS/ENUMS
22 | /////////////////////////////////////////////////////////////////////////////////////////
23 |
24 | // IP Address of AUX codec (i.e. CodecPlus)
25 | const AUX_CODEC_IP ='10.10.10.10';
26 |
27 | // AUX_CODEC_USERNAME and AUX_CODEC_PASSWORD are the username and password of a admin-level user on the Auxiliary codec
28 | // Here are instructions on how to configure local user accounts on Webex Devices: https://help.webex.com/en-us/jkhs20/Local-User-Administration-on-Room-and-Desk-Devices)
29 | const AUX_CODEC_USERNAME='username';
30 | const AUX_CODEC_PASSWORD='password';
31 |
32 | // This next line hides the mid-call controls “Lock meeting” and “Record”. The reason for this is so that the
33 | // “Camera Control” button can be seen. If you prefer to have the mid-call controls showing, change the value of this from “Hidden” to “Auto”
34 | xapi.Config.UserInterface.Features.Call.MidCallControls.set("Hidden");
35 |
36 | function encode(s) {
37 | var c = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
38 | o = [];
39 | for (var i = 0, n = s.length; i < n;) {
40 | var c1 = s.charCodeAt(i++),
41 | c2 = s.charCodeAt(i++),
42 | c3 = s.charCodeAt(i++);
43 | o.push(c.charAt(c1 >> 2));
44 | o.push(c.charAt(((c1 & 3) << 4) | (c2 >> 4)));
45 | o.push(c.charAt(i < n + 2 ? ((c2 & 15) << 2) | (c3 >> 6) : 64));
46 | o.push(c.charAt(i < n + 1 ? c3 & 63 : 64));
47 | }
48 | return o.join("");
49 | }
50 |
51 | const AUX_CODEC_AUTH=encode(AUX_CODEC_USERNAME+':'+AUX_CODEC_PASSWORD);
52 |
53 | // Microphone High/Low Thresholds
54 | const MICROPHONELOW = 6;
55 | const MICROPHONEHIGH = 25;
56 |
57 |
58 | // Microphone Input Numbers to Monitor
59 | // Specify the input connectors associated to the microphones being used in the room
60 | // For example, if you set the value to [1,2,3,4,5,6,7,8] the macro will evaluate mic input id's 1-8 for it's switching logic
61 | const CONNECTORS = [1,2,3,4,5,6,7,8];
62 |
63 | // Camera source IDs that correspond to each microphone in CONNECTORS array
64 | // Associate the connectors to specific input source ID corresponding to the camera that covers where the mic is located.
65 | // For example, if you set the value to [1,1,1,1,2,2,2,2] and CONNECTORS = [1,2,3,4,5,6,7,8] you are specifying that
66 | // mics 1,2,3 and 4 are located where Camera associated to video input 1 is pointing at and
67 | // mics 5,6,7 and 8 are located where Camera associated to video input 2 is pointing at
68 | const MAP_CAMERA_SOURCE_IDS = [1,1,1,1,2,2,2,2];
69 |
70 | // overviewShowDouble defines what is shown on the far end (the video the main codec sends into the call or conference) when in "overview" mode where nobody is speaking or there is no
71 | // prominent speaker detected by any of the microphones
72 | // INSTRUCTIONS: If you are using side-by-side mode as your default - "overviewShowDouble = true" - then you must set up a camera preset for each Quad Camera
73 | // with a Preset ID of 30. The JavaScript for side-by-side mode uses Preset 30.
74 | // EC - what happens if they set this value to false? I know the function is "recallSideBySideMode" but I don't quite understand the logic in "let
75 | // sourceDict={ Source : '0'};"
76 | const overviewShowDouble = true;
77 |
78 | // OVERVIEW_SINGLE_SOURCE_ID specifies the source video ID to use when in overview mode if you set overviewShowDouble to false
79 | const OVERVIEW_SINGLE_SOURCE_ID = 1;
80 |
81 | // OVERVIEW_DOUBLE_SOURCE_IDS specifies the source video array of two IDs to use when in overview mode if you set overviewShowDouble to true
82 | // it will display the two sources side by side on the main screen with the first value of the array on the
83 | // left and the second on the right.
84 | const OVERVIEW_DOUBLE_SOURCE_IDS = [2,1];
85 |
86 | // Time to wait for silence before setting Speakertrack Side-by-Side mode
87 | const SIDE_BY_SIDE_TIME = 7000; // 7 seconds
88 | // Time to wait before switching to a new speaker
89 | const NEW_SPEAKER_TIME = 2000; // 2 seconds
90 | // Time to wait before activating automatic mode at the beginning of a call
91 | const INITIAL_CALL_TIME = 15000; // 15 seconds
92 |
93 |
94 | /////////////////////////////////////////////////////////////////////////////////////////
95 | // VARIABLES
96 | /////////////////////////////////////////////////////////////////////////////////////////
97 | let AUX_CODEC={ enable: true, online: false, url: AUX_CODEC_IP, auth: AUX_CODEC_AUTH};
98 | let micArrays={};
99 | for (var i in CONNECTORS) {
100 | micArrays[CONNECTORS[i].toString()]=[0,0,0,0];
101 | }
102 | let lowWasRecalled = false;
103 | let lastActiveHighInput = 0;
104 | let allowSideBySide = true;
105 | let sideBySideTimer = null;
106 | let InitialCallTimer = null;
107 | let allowCameraSwitching = false;
108 | let allowNewSpeaker = true;
109 | let newSpeakerTimer = null;
110 | let manual_mode = true;
111 |
112 | let micHandler= () => void 0;
113 |
114 | /////////////////////////////////////////////////////////////////////////////////////////
115 | // INITIALIZATION
116 | /////////////////////////////////////////////////////////////////////////////////////////
117 |
118 |
119 |
120 | function evalFullScreen(value) {
121 | if (value=='On') {
122 | xapi.command('UserInterface Extensions Widget SetValue', {WidgetId: 'widget_FS_selfview', Value: 'on'});
123 | }
124 | else
125 | {
126 | xapi.command('UserInterface Extensions Widget SetValue', {WidgetId: 'widget_FS_selfview' , Value: 'off'});
127 | }
128 | }
129 |
130 | // evalFullScreenEvent is needed because we have to check when someone manually turns on full screen
131 | // when self view is already selected... it will eventually check FullScreen again, but that should be
132 | // harmless
133 | function evalFullScreenEvent(value)
134 | {
135 | if (value=='On') {
136 | xapi.Status.Video.Selfview.Mode.get().then(evalSelfView);
137 | }
138 | else
139 | {
140 | xapi.command('UserInterface Extensions Widget SetValue', {WidgetId: 'widget_FS_selfview', Value: 'off'});
141 | }
142 | }
143 |
144 | function evalSelfView(value) {
145 | if (value=='On') {
146 | xapi.Status.Video.Selfview.FullscreenMode.get().then(evalFullScreen);
147 | }
148 | else
149 | {
150 | xapi.command('UserInterface Extensions Widget SetValue', {WidgetId: 'widget_FS_selfview', Value: 'off'});
151 | }
152 | }
153 |
154 | function init() {
155 | console.log('init');
156 | // configure HTTP settings
157 | xapi.config.set('HttpClient Mode', 'On').catch(handleError);
158 | xapi.config.set('HttpClient AllowInsecureHTTPS:', 'True').catch(handleError);
159 | xapi.config.set('HttpClient AllowHTTP:', 'True').catch(handleError);
160 |
161 | // Stop any VuMeters that might have been left from a previous macro run with a different CONNECTORS constant
162 | // to prevent errors due to unhandled vuMeter events.
163 | xapi.Command.Audio.VuMeter.StopAll({ });
164 |
165 | // register callback for processing manual mute setting on codec
166 | xapi.Status.Audio.Microphones.Mute.on((state) => {
167 | console.log(`handleMicMuteResponse: ${state}`);
168 |
169 | if (state == 'On') {
170 | stopSideBySideTimer();
171 | setTimeout(handleMicMuteOn, 2000);
172 | }
173 | else if (state == 'Off') {
174 | handleMicMuteOff();
175 | }
176 | });
177 |
178 | // register callback for processing messages from aux_codec
179 | xapi.event.on('Message Send', handleMessage);
180 |
181 | // register event handlers for local events
182 | xapi.Status.Standby.State
183 | .on(value => {
184 | console.log(value);
185 | if (value=="Off") handleWakeUp();
186 | if (value=="Standby") handleShutDown();
187 | });
188 |
189 | // register handler for Widget actions
190 | xapi.event.on('UserInterface Extensions Widget Action', (event) =>
191 | handleOverrideWidget(event));
192 |
193 | // register handler for Call Successful
194 | xapi.Event.CallSuccessful.on(async () => {
195 | console.log("Starting new call timer...");
196 | startAutomation();
197 | startInitialCallTimer();
198 | });
199 |
200 | // register handler for Call Disconnect
201 | xapi.Event.CallDisconnect.on(async () => {
202 | console.log("Turning off Self View....");
203 | xapi.Command.Video.Selfview.Set({ Mode: 'off'});
204 | stopAutomation();
205 | });
206 |
207 | // set self-view toggle on custom panel depending on Codec status that might have been set manually
208 | xapi.Status.Video.Selfview.Mode.get().then(evalSelfView);
209 |
210 | // register to receive events when someone manually turns on self-view
211 | // so we can keep the custom toggle button in the right state
212 | xapi.Status.Video.Selfview.Mode.on(evalSelfView);
213 |
214 | // register to receive events when someone manually turns on full screen mode
215 | // so we can keep the custom toggle button in the right state if also in self view
216 | xapi.Status.Video.Selfview.FullscreenMode.on(evalFullScreenEvent);
217 |
218 | // next, set Automatic mode toggle switch on custom panel off since the macro starts that way
219 | xapi.command('UserInterface Extensions Widget SetValue', {WidgetId: 'widget_override', Value: 'off'});
220 |
221 | }
222 |
223 |
224 | /////////////////////////////////////////////////////////////////////////////////////////
225 | // START/STOP AUTOMATION FUNCTIONS
226 | /////////////////////////////////////////////////////////////////////////////////////////
227 |
228 | function startAutomation() {
229 | console.log('startAutomation');
230 | //setting overall manual mode to false
231 | manual_mode = false;
232 | allowCameraSwitching = true;
233 |
234 |
235 | // Always turn on SpeakerTrack when the Automation is started. It is also turned on when a call connects so that
236 | // if it is manually turned off while outside of a call it goes back to the correct state
237 | xapi.command('Cameras SpeakerTrack Activate').catch(handleError);
238 |
239 | //registering vuMeter event handler
240 | micHandler=xapi.event.on('Audio Input Connectors Microphone', (event) => {
241 | micArrays[event.id[0]].pop();
242 | micArrays[event.id[0]].push(event.VuMeter);
243 |
244 | // checking on manual_mode might be unnecessary because in manual mode,
245 | // audio events should not be triggered
246 | if (manual_mode==false)
247 | {
248 | // invoke main logic to check mic levels ans switch to correct camera input
249 | checkMicLevelsToSwitchCamera();
250 | }
251 | });
252 | // start VuMeter monitoring
253 | console.log("Turning on VuMeter monitoring...")
254 | for (var i in CONNECTORS) {
255 | xapi.command('Audio VuMeter Start', {
256 | ConnectorId: CONNECTORS[i],
257 | ConnectorType: 'Microphone',
258 | IntervalMs: 500,
259 | Source: 'AfterAEC'
260 | });
261 | }
262 | // set toggle button on custom panel to reflect that automation is turned on.
263 | xapi.command('UserInterface Extensions Widget SetValue', {WidgetId: 'widget_override', Value: 'on'});
264 | }
265 |
266 | function stopAutomation() {
267 | //setting overall manual mode to true
268 | manual_mode = true;
269 | console.log("Stopping all VuMeters...");
270 | xapi.Command.Audio.VuMeter.StopAll({ });
271 | console.log("Switching to MainVideoSource connectorID 1 ...");
272 | xapi.Command.Video.Input.SetMainVideoSource({ SourceId: 1});
273 | // using proper way to de-register handlers
274 | micHandler();
275 | micHandler= () => void 0;
276 |
277 | // set toggle button on custom panel to reflect that automation is turned off.
278 | xapi.command('UserInterface Extensions Widget SetValue', {WidgetId: 'widget_override', Value: 'off'});
279 |
280 | }
281 |
282 | /////////////////////////////////////////////////////////////////////////////////////////
283 | // MICROPHONE DETECTION AND CAMERA SWITCHING LOGIC FUNCTIONS
284 | /////////////////////////////////////////////////////////////////////////////////////////
285 |
286 | function checkMicLevelsToSwitchCamera() {
287 | // make sure we've gotten enough samples from each mic in order to do averages
288 | if (allowCameraSwitching) {
289 | // figure out which of the inputs has the highest average level then perform logic for that input *ONLY* if allowCameraSwitching is true
290 | let array_key=largestMicValue();
291 | let array=[];
292 | array=micArrays[array_key];
293 | // get the average level for the currently active input
294 | let average = averageArray(array);
295 | //get the input number as an int since it is passed as a string (since it is a key to a dict)
296 | let input = parseInt(array_key);
297 | // someone is speaking
298 | if (average > MICROPHONEHIGH) {
299 | // start timer to prevent Side-by-Side mode too quickly
300 | restartSideBySideTimer();
301 | if (input > 0) {
302 | lowWasRecalled = false;
303 | // no one was talking before
304 | if (lastActiveHighInput === 0) {
305 | makeCameraSwitch(input, average);
306 | }
307 | // the same person is talking
308 | else if (lastActiveHighInput === input) {
309 | restartNewSpeakerTimer();
310 | }
311 | // a different person is talking
312 | else if (lastActiveHighInput !== input) {
313 | if (allowNewSpeaker) {
314 | makeCameraSwitch(input, average);
315 | }
316 | }
317 | }
318 | }
319 | // no one is speaking
320 | else if (average < MICROPHONELOW) {
321 | // only trigger if enough time has elapsed since someone spoke last
322 | if (allowSideBySide) {
323 | if (input > 0 && !lowWasRecalled) {
324 | lastActiveHighInput = 0;
325 | lowWasRecalled = true;
326 | console.log("-------------------------------------------------");
327 | console.log("Low Triggered");
328 | console.log("-------------------------------------------------");
329 | recallSideBySideMode();
330 | }
331 | }
332 | }
333 |
334 | }
335 | }
336 |
337 | // function to actually switch the camera input
338 | function makeCameraSwitch(input, average) {
339 | console.log("-------------------------------------------------");
340 | console.log("High Triggered: ");
341 | console.log(`Input = ${input} | Average = ${average}`);
342 | console.log("-------------------------------------------------");
343 | // turning back on SpeakerTrack on my codec in case it was turned off in side by side mode.
344 | xapi.command('Cameras SpeakerTrack Activate').catch(handleError);
345 | // Switch to the source that is speficied in the same index position in MAP_CAMERA_SOURCE_IDS
346 | let sourceDict={ SourceID : '0'}
347 | sourceDict["SourceID"]=MAP_CAMERA_SOURCE_IDS[CONNECTORS.indexOf(input)].toString();
348 | console.log("Trying to use this for source dict: ", sourceDict )
349 | xapi.command('Video Input SetMainVideoSource', sourceDict).catch(handleError);
350 |
351 | // send required messages to auxiliary codec that also turns on speakertrack over there
352 | sendIntercodecMessage(AUX_CODEC, 'automatic_mode');
353 | lastActiveHighInput = input;
354 | restartNewSpeakerTimer();
355 | }
356 |
357 | function largestMicValue() {
358 | // figure out which of the inputs has the highest average level and return the corresponding key
359 | let currentMaxValue=0;
360 | let currentMaxKey='';
361 | let theAverage=0;
362 | for (var i in CONNECTORS){
363 | theAverage=averageArray(micArrays[CONNECTORS[i].toString()]);
364 | if (theAverage>=currentMaxValue) {
365 | currentMaxKey=CONNECTORS[i].toString();
366 | currentMaxValue=theAverage;
367 | }
368 | }
369 | return currentMaxKey;
370 | }
371 |
372 | function averageArray(arrayIn) {
373 | let sum = 0;
374 | for(var i = 0; i < arrayIn.length; i++) {
375 | sum = sum + parseInt( arrayIn[i], 10 );
376 | }
377 | let avg = (sum / arrayIn.length) * arrayIn.length;
378 | return avg;
379 | }
380 |
381 | function recallSideBySideMode() {
382 | if (overviewShowDouble) {
383 | let connectorDict={ ConnectorId : [0,0]};
384 | connectorDict["ConnectorId"]=OVERVIEW_DOUBLE_SOURCE_IDS;
385 | console.log("Trying to use this for connector dict in recallSideBySideMode(): ", connectorDict )
386 | xapi.command('Video Input SetMainVideoSource', connectorDict).catch(handleError);
387 | xapi.command('Cameras SpeakerTrack Deactivate').catch(handleError);
388 | xapi.command('Camera Preset Activate', { PresetId: 30 }).catch(handleError);
389 | }
390 | else {
391 | let sourceDict={ SourceID : '0'};
392 | sourceDict["SourceID"]=OVERVIEW_SINGLE_SOURCE_ID.toString();
393 | console.log("Trying to use this for source dict in recallSideBySideMode(): ", sourceDict )
394 | xapi.command('Video Input SetMainVideoSource', sourceDict).catch(handleError);
395 | }
396 | // send required messages to other codecs
397 | sendIntercodecMessage(AUX_CODEC, 'side_by_side');
398 | lastActiveHighInput = 0;
399 | lowWasRecalled = true;
400 | }
401 |
402 |
403 | /////////////////////////////////////////////////////////////////////////////////////////
404 | // TOUCH 10 UI FUNCTION HANDLERS
405 | /////////////////////////////////////////////////////////////////////////////////////////
406 |
407 | function handleOverrideWidget(event)
408 | {
409 | if (event.WidgetId === 'widget_override')
410 | {
411 | console.log("Camera Control button selected.....")
412 | if (event.Value === 'off') {
413 |
414 | console.log("Camera Control is set to Manual...");
415 | console.log("Stopping automation...")
416 | stopAutomation();
417 | }
418 | else
419 | {
420 |
421 | // start VuMeter monitoring
422 | console.log("Camera Control is set to Automatic...");
423 | console.log("Starting automation...")
424 | startAutomation();
425 | }
426 | }
427 |
428 |
429 | if (event.WidgetId === 'widget_FS_selfview')
430 | {
431 | console.log("Selfview button selected.....")
432 | if (event.Value === 'off') {
433 | console.log("Selfview is set to Off...");
434 | console.log("turning off self-view...")
435 | xapi.Command.Video.Selfview.Set({ FullscreenMode: 'Off', Mode: 'Off', OnMonitorRole: 'First'});
436 | }
437 | else
438 | {
439 | console.log("Selfview is set to On...");
440 | console.log("turning on self-view...")
441 | // TODO: determine if turning off self-view should also turn off fullscreenmode
442 | xapi.Command.Video.Selfview.Set({ FullscreenMode: 'On', Mode: 'On', OnMonitorRole: 'First'});
443 | }
444 | }
445 | }
446 |
447 |
448 | /////////////////////////////////////////////////////////////////////////////////////////
449 | // ERROR HANDLING
450 | /////////////////////////////////////////////////////////////////////////////////////////
451 |
452 | function handleError(error) {
453 | console.log(error);
454 | }
455 |
456 | /////////////////////////////////////////////////////////////////////////////////////////
457 | // INTER-CODEC COMMUNICATION
458 | /////////////////////////////////////////////////////////////////////////////////////////
459 |
460 | function sendIntercodecMessage(codec, message) {
461 | if (codec.enable) {
462 | console.log(`sendIntercodecMessage: codec = ${codec.url} | message = ${message}`);
463 |
464 | let url = 'https://' + codec.url + '/putxml';
465 |
466 | let headers = [
467 | 'Content-Type: text/xml',
468 | 'Authorization: Basic ' + codec.auth
469 | ];
470 |
471 | let payload = ""+ message +"";
472 | let errMessage1="Error connecting to second camera, please contact the Administrator";
473 | let errMessage2="Second camera is offline, please contact the Administrator";
474 | xapi.command('HttpClient Post', {Url: url, Header: headers, AllowInsecureHTTPS: 'True'}, payload)
475 | .then((response) => {
476 | if(response.StatusCode === "200") {
477 | console.log("Successfully sent: " + payload)
478 | }
479 | else {
480 | console.log("Error "+response.StatusCode+" sending message to Aux: ",response.StatusCode);
481 | alertFailedIntercodecComm(errMessage1);
482 | }
483 | })
484 | .catch((err) => {
485 | if ("data" in err) {
486 | console.log("Sending message failed: "+err.message+" Status code: "+err.data.StatusCode);
487 | } else {
488 | console.log("Sending message failed: "+err.message);
489 | }
490 | alertFailedIntercodecComm(errMessage2);
491 | });
492 | };
493 | }
494 |
495 | function alertFailedIntercodecComm(message) {
496 | xapi.command("UserInterface Message Alert Display", {
497 | Text: message
498 | , Duration: 10
499 | }).catch((error) => { console.error(error); });
500 | }
501 |
502 | /////////////////////////////////////////////////////////////////////////////////////////
503 | // OTHER FUNCTIONAL HANDLERS
504 | /////////////////////////////////////////////////////////////////////////////////////////
505 |
506 |
507 | function handleMicMuteOn() {
508 | console.log('handleMicMuteOn');
509 | lastActiveHighInput = 0;
510 | lowWasRecalled = true;
511 | recallSideBySideMode();
512 | }
513 |
514 | function handleMicMuteOff() {
515 | console.log('handleMicMuteOff');
516 | // need to turn back on SpeakerTrack that might have been turned off when going on mute
517 | xapi.command('Cameras SpeakerTrack Activate').catch(handleError);
518 | }
519 |
520 | // ---------------------- MACROS
521 |
522 | function handleMessage(event) {
523 | switch(event.Text) {
524 | case "VTC-1_OK":
525 | handleCodecOnline(AUX_CODEC);
526 | break;
527 | }
528 | }
529 |
530 | // function to check the satus of the macros running on the AUX codec
531 | function handleMacroStatus() {
532 | console.log('handleMacroStatus');
533 | // reset tracker of responses from AUX codec
534 | AUX_CODEC.online = false;
535 | // send required messages to AUX codec
536 | sendIntercodecMessage(AUX_CODEC, 'VTC-1_status');
537 | }
538 |
539 | function handleCodecOnline(codec) {
540 | console.log(`handleCodecOnline: codec = ${codec.url}`);
541 | codec.online = true;
542 | }
543 |
544 | function handleWakeUp() {
545 | console.log('handleWakeUp');
546 | // stop automatic switching behavior
547 | stopAutomation();
548 | // send wakeup to AUX codec
549 | sendIntercodecMessage(AUX_CODEC, 'wake_up');
550 | // check the satus of the macros running on the AUX codec and store it in AUX_CODEC.online
551 | // in case we need to check it in some other function
552 | handleMacroStatus();
553 | }
554 |
555 | function handleShutDown() {
556 | console.log('handleShutDown');
557 | // send required messages to other codecs
558 | sendIntercodecMessage(AUX_CODEC, 'shut_down');
559 | }
560 |
561 | /////////////////////////////////////////////////////////////////////////////////////////
562 | // VARIOUS TIMER HANDLER FUNCTIONS
563 | /////////////////////////////////////////////////////////////////////////////////////////
564 |
565 | function startSideBySideTimer() {
566 | if (sideBySideTimer == null) {
567 | allowSideBySide = false;
568 | sideBySideTimer = setTimeout(onSideBySideTimerExpired, SIDE_BY_SIDE_TIME);
569 | }
570 | }
571 |
572 | function stopSideBySideTimer() {
573 | if (sideBySideTimer != null) {
574 | clearTimeout(sideBySideTimer);
575 | sideBySideTimer = null;
576 | }
577 | }
578 |
579 | function restartSideBySideTimer() {
580 | stopSideBySideTimer();
581 | startSideBySideTimer();
582 | }
583 |
584 | function onSideBySideTimerExpired() {
585 | console.log('onSideBySideTimerExpired');
586 | allowSideBySide = true;
587 | recallSideBySideMode();
588 | }
589 |
590 |
591 |
592 | function startInitialCallTimer() {
593 | if (InitialCallTimer == null) {
594 | allowCameraSwitching = false;
595 | InitialCallTimer = setTimeout(onInitialCallTimerExpired, INITIAL_CALL_TIME);
596 | }
597 | }
598 |
599 | function onInitialCallTimerExpired() {
600 | console.log('onInitialCallTimerExpired');
601 | allowCameraSwitching = true;
602 | xapi.command('Cameras SpeakerTrack Activate').catch(handleError);
603 |
604 | }
605 |
606 | function startNewSpeakerTimer() {
607 | if (newSpeakerTimer == null) {
608 | allowNewSpeaker = false;
609 | newSpeakerTimer = setTimeout(onNewSpeakerTimerExpired, NEW_SPEAKER_TIME);
610 | }
611 | }
612 |
613 | function stopNewSpeakerTimer() {
614 | if (newSpeakerTimer != null) {
615 | clearTimeout(newSpeakerTimer);
616 | newSpeakerTimer = null;
617 | }
618 | }
619 |
620 | function restartNewSpeakerTimer() {
621 | stopNewSpeakerTimer();
622 | startNewSpeakerTimer();
623 | }
624 |
625 | function onNewSpeakerTimerExpired() {
626 | allowNewSpeaker = true;
627 | }
628 |
629 | /////////////////////////////////////////////////////////////////////////////////////////
630 | // INVOCATION OF INIT() TO START THE MACRO
631 | /////////////////////////////////////////////////////////////////////////////////////////
632 |
633 | init();
--------------------------------------------------------------------------------