General API concepts

The API singleton

Regardless of the language you are using, the Beam Eye Tracker SDK provides a way to create an API instance, which is the main entry point for getting data and communicating with the Beam Eye Tracker application. This API instance is only a wrapper to an underlying singleton allocated for your entire application. Logically, it communicates with the only instance of the Beam Eye Tracker application running in the user’s PC.

The API singleton is further designed to be:

  1. Light-weight on client code. Every function call should return quickly to the thread of execution, unless explicitly stated otherwise (see Synchronous data access).

  2. Thread-safe: You can call it from multiple threads without having to worry about race conditions.

  3. RAII: Allocating the API initializes communications with the Beam Eye Tracker and destroying all instances should release the singleton resources.

The friendly name, specified to the API instantiation, is used to inform the user which application is connected to the Beam Eye Tracker.

The second term is the ViewportGeometry, which is explained at the Viewport section.

#include "eyeware/beam_eye_tracker_c.h"
EW_BET_API_HANDLE api = EW_BET_NULL_HANDLE;
int32_t result = EW_BET_API_Create("MyImmersiveGame", {1.0f, 1.0f, 1.0f, 1.0f}, &api);
// if API creation was successful
if (result == 0) {
   // ...do something with the API...

   // .. release the API
   EW_BET_API_Destroy(api);
}
#include "eyeware/beam_eye_tracker.h"
eyeware::beam_eye_tracker::BeamEyeTrackerAPI api("MyImmersiveGame", {1.0f, 1.0f, 1.0f, 1.0f});
viewport_geometry = beam_eye_tracker.ViewportGeometry()
viewport_geometry.point_00 = beam_eye_tracker.Point(0, 0)
viewport_geometry.point_11 = beam_eye_tracker.Point(0, 0)
bet_api = beam_eye_tracker.API("MyImmersiveGame", viewport_geometry)

The TrackingStateSet object

The main goal of the API object is to provide a stream of TrackingStateSet objects. The TrackingStateSet is a container for the sub-structures that provide either the raw tracking user state, or preprocessed data to easily implement interactive features in games.

In particular, these sub-structures are:

  • UserState, which holds the raw tracking data for the user, including the user’s gaze on screen and head pose. See Real-time tracking.

  • SimGameCameraState, which holds the parameters to implement the immersive camera controls. See In-game camera control.

  • GameImmersiveHUDState, which holds the parameters to implement the immersive HUD. See Dynamic HUDs.

Timestamps

Each TrackingStateSet is associated to a Timestamp value. As expected, it indicates the time in seconds for when the TrackingStateSet object was created or updated, whether all fields are updated or not. However, it’s important to note that if the timestamp contains the NULL_DATA_TIMESTAMP value, it means that the data held by the TrackingStateSet object is invalid for whatever reason, whether the tracking is not activated, the user is not being tracked, etc, and thus should be discarded.

The above applies to the full TrackingStateSet object you will further notice that each sub-structure held by it (e.g. UserState, SimGameCameraState, GameImmersiveHUDState) contains its own Timestamp field. Although in most cases, each sub-structure will have the same timestamp value as the one associated to the TrackingStateSet object, or the other sub-structures, you should not make that assumption, as a future-proof integration should assume that each-substructure could have its own frequency of updates. Moreover, each individual sub-structure could also hold a NULL_DATA_TIMESTAMP value, indicating that the given data is invalid for whatever reason, for example, the specific feature is disabled.

The TrackingDataReceptionStatus object

Through the API instance, you can query the current TrackingDataReceptionStatus to check if frame-by-frame data from the Beam Eye Tracker is being received given that bi-directional communication is established.

It is important to note that this status is independent of whether the user is being successfully tracked or not - it only indicates if the connection to the Beam Eye Tracker is active and data is flowing. It also indicate if the Beam Eye Tracker is being automatically started/activated (see Launching the Beam Eye Tracker from your game or application).

For more details on the different values of the TrackingDataReceptionStatus object, see EW_BET_TrackingDataReceptionStatus.

Data Access Methods

The SDK supports three methods for accessing the stream of TrackingStateSet objects: synchronous, polling and asynchronous.

Synchronous data access

Synchronous data access is convenient in case you want to get access to the latest TrackingStateSet at low latency, and it is fine to block the current thread while waiting for data updates. A typical scenario is when your application (or at least the current thread) uses the tracking data updates as the key event that triggers further processing/recording/interactions, etc.

The following example demonstrates how to use the synchronous data access method, with the key functionality being the use of the wait_for_new_tracking_state_set method, which uses a Timestamp object as a synchronization token.

 1/**
 2 * Copyright (c) 2025, Eyeware Tech SA.
 3 *
 4 * All rights reserved.
 5 *
 6 * @brief This sample demonstrates how to use the synchronous data access method.
 7 */
 8#include "../bet_sample_utils.h"
 9#include "eyeware/beam_eye_tracker.h"
10
11void main() {
12    eyeware::beam_eye_tracker::API bet_api("Sync Sample",
13                                           eyeware::beam_eye_tracker::ViewportGeometry());
14
15    // We hold a timestamp instance useful to sync the data reception.
16    eyeware::beam_eye_tracker::Timestamp last_update_timestamp_sec{
17        eyeware::beam_eye_tracker::NULL_DATA_TIMESTAMP};
18
19    std::unique_ptr<eyeware::beam_eye_tracker::TrackingStateSet> last_received_tracking_state_set;
20
21    eyeware::beam_eye_tracker::TrackingDataReceptionStatus previous_status =
22        bet_api.get_tracking_data_reception_status();
23
24    print_tracking_data_reception_status(previous_status);
25
26    int count = 0;
27    // Access 1 minute of data (assuming a 30fps webcam)
28    while (count < 30 * 60) {
29        const unsigned int wait_timeout_ms = 1000;
30
31        // Follow up with the status of the tracking data reception.
32        eyeware::beam_eye_tracker::TrackingDataReceptionStatus status =
33            bet_api.get_tracking_data_reception_status();
34        print_tracking_data_reception_status_if_changed(previous_status, status);
35        previous_status = status;
36
37        // Wait for a new frame. If this function returns true, then there is a new frame and
38        // last_update_timestamp_sec is updated.
39        if (bet_api.wait_for_new_tracking_state_set(last_update_timestamp_sec, wait_timeout_ms)) {
40            // This is how we access the latest TrackingStateSet.
41            // It is only movable, thus this is a way to retain it.
42            last_received_tracking_state_set =
43                std::make_unique<eyeware::beam_eye_tracker::TrackingStateSet>(
44                    std::move(bet_api.get_latest_tracking_state_set()));
45
46            print_latest_tracking_state_set(*last_received_tracking_state_set,
47                                            last_update_timestamp_sec);
48            count++;
49        }
50    }
51}
 1# Copyright (c) 2025, Eyeware Tech SA.
 2#
 3# All rights reserved.
 4#
 5# This sample demonstrates how to use the synchronous data access method.
 6#
 7# Synchronous data access is convenient in case you want to get a TrackingStateSet at low
 8# latency, and it is fine to block the thread while waiting for data updates.
 9#
10# A typical scenario is when your application (or at least this thread) uses the tracking data
11# updates as the event to trigger further processing/recording/interactions, etc.
12import sys
13import os
14
15sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
16
17import bet_add_to_python_path
18
19from eyeware import beam_eye_tracker
20from bet_sample_utils import (
21    print_tracking_data_reception_status,
22    print_latest_tracking_state_set,
23    print_tracking_data_reception_status_if_changed,
24)
25
26
27def main():
28    # Initialize API with empty viewport geometry
29    viewport_geometry = beam_eye_tracker.ViewportGeometry()
30    viewport_geometry.point_00 = beam_eye_tracker.Point(0, 0)
31    viewport_geometry.point_11 = beam_eye_tracker.Point(0, 0)
32
33    bet_api = beam_eye_tracker.API("Python Sync Sample", viewport_geometry)
34
35    # We hold a timestamp instance useful to sync the data reception
36    last_update_timestamp_sec = beam_eye_tracker.NULL_DATA_TIMESTAMP()
37
38    last_received_tracking_state_set = None
39    previous_status = bet_api.get_tracking_data_reception_status()
40    print_tracking_data_reception_status(previous_status)
41
42    count = 0
43    # Access 1 minute of data (assuming a 30fps webcam)
44    while count < 30 * 60:
45        wait_timeout_ms = 1000
46
47        # Follow up with the status of the tracking data reception
48        status = bet_api.get_tracking_data_reception_status()
49        print_tracking_data_reception_status_if_changed(previous_status, status)
50        previous_status = status
51
52        # Wait for a new frame. If this function returns true, then there is a new frame and
53        # last_update_timestamp_sec is updated
54        if bet_api.wait_for_new_tracking_state_set(last_update_timestamp_sec, wait_timeout_ms):
55            # This is how we access the latest TrackingStateSet
56            last_received_tracking_state_set = bet_api.get_latest_tracking_state_set()
57            print_latest_tracking_state_set(last_received_tracking_state_set, last_update_timestamp_sec)
58            count += 1
59
60
61if __name__ == "__main__":
62    main()

Polling data access

Polling is convenient in case your thread can’t be blocked, and it is not critical to read the TrackingStateSet as soon as it is available, meaning, you can afford some latency. A typical scenario is when your thread loop is driven by other events and you want to use the latest TrackingStateSet in combination with said events. For example, in a game, where there is a logic driven by frames rendering: you don’t want to block, but you want to use the eye tracking for some interaction in the game.

The mechanism works the same as Synchronous data access, with the key difference being that we use the wait_for_new_tracking_state_set with a timeout of 0ms so that it returns immediately, but indicating whether new data is available or not.

 1/**
 2 * Copyright (c) 2025, Eyeware Tech SA.
 3 *
 4 * All rights reserved.
 5 *
 6 * @brief This sample demonstrates how to use the polling data access method.
 7 */
 8#include "../bet_sample_utils.h"
 9#include "eyeware/beam_eye_tracker.h"
10#include <chrono>
11#include <memory>
12#include <thread>
13
14void main() {
15    eyeware::beam_eye_tracker::API bet_api("Polling Sample",
16                                           eyeware::beam_eye_tracker::ViewportGeometry());
17
18    // We hold a timestamp instance useful to sync the data reception.
19    eyeware::beam_eye_tracker::Timestamp last_update_timestamp_sec{
20        eyeware::beam_eye_tracker::NULL_DATA_TIMESTAMP};
21
22    std::unique_ptr<eyeware::beam_eye_tracker::TrackingStateSet> last_received_tracking_state_set;
23
24    eyeware::beam_eye_tracker::TrackingDataReceptionStatus previous_status =
25        bet_api.get_tracking_data_reception_status();
26
27    print_tracking_data_reception_status(previous_status);
28
29    int count = 0;
30    // Access 1 minute of data (assuming a 10fps events)
31    while (count < 10 * 60) {
32
33        // Follow up with the status of the tracking data reception.
34        eyeware::beam_eye_tracker::TrackingDataReceptionStatus status =
35            bet_api.get_tracking_data_reception_status();
36        print_tracking_data_reception_status_if_changed(previous_status, status);
37        previous_status = status;
38
39        // Polling for new data follows the synchronous data access method, but with a timeout of
40        // 0ms. This means that the function will return immediately.
41        // Similarly, last_update_timestamp_sec is updated in case of new data.
42        if (bet_api.wait_for_new_tracking_state_set(last_update_timestamp_sec, 0)) {
43            // This is how we access the latest TrackingStateSet.
44            // It is only movable, thus this is a way to retain it.
45            last_received_tracking_state_set =
46                std::make_unique<eyeware::beam_eye_tracker::TrackingStateSet>(
47                    std::move(bet_api.get_latest_tracking_state_set()));
48
49            print_latest_tracking_state_set(*last_received_tracking_state_set,
50                                            last_update_timestamp_sec);
51
52            count++;
53        }
54
55        // To simulate an "external event" driving this thread at 10fps, we sleep 100ms to simulate
56        // a thread loop driven by other events.
57        std::this_thread::sleep_for(std::chrono::milliseconds(100));
58    }
59}
 1# Copyright (c) 2025, Eyeware Tech SA.
 2#
 3# All rights reserved.
 4#
 5# This sample demonstrates how to use the polling data access method.
 6#
 7# Polling is convenient in case your thread can't be blocked, and it is not critical to read the
 8# TrackingStateSet as soon as it is available, meaning, you can afford some latency.
 9#
10# A typical scenario is when your thread loop is driven by other events and you want to use
11# the latest TrackingStateSet in combination with said events. For example, in a game, where
12# there is a logic driven by frames rendering: you don't want to block, but you want to use the
13# eye tracking for some interaction in the game.
14import sys
15import os
16
17sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
18
19import bet_add_to_python_path
20
21import time
22from eyeware import beam_eye_tracker
23from bet_sample_utils import (
24    print_tracking_data_reception_status,
25    print_latest_tracking_state_set,
26    print_tracking_data_reception_status_if_changed,
27)
28
29
30def main():
31    # Initialize API with empty viewport geometry
32    viewport_geometry = beam_eye_tracker.ViewportGeometry()
33    viewport_geometry.point_00 = beam_eye_tracker.Point(0, 0)
34    viewport_geometry.point_11 = beam_eye_tracker.Point(0, 0)
35
36    bet_api = beam_eye_tracker.API("Python Polling Sample", viewport_geometry)
37
38    # We hold a timestamp instance useful to sync the data reception
39    last_update_timestamp_sec = beam_eye_tracker.NULL_DATA_TIMESTAMP()
40
41    last_received_tracking_state_set = None
42    previous_status = bet_api.get_tracking_data_reception_status()
43    print_tracking_data_reception_status(previous_status)
44
45    count = 0
46    # Access 1 minute of data (assuming a 10fps events)
47    while count < 10 * 60:
48        # Follow up with the status of the tracking data reception
49        status = bet_api.get_tracking_data_reception_status()
50        print_tracking_data_reception_status_if_changed(previous_status, status)
51        previous_status = status
52
53        # Polling for new data follows the synchronous data access method, but with a timeout of
54        # 0ms. This means that the function will return immediately.
55        # Similarly, last_update_timestamp_sec is updated in case of new data.
56        if bet_api.wait_for_new_tracking_state_set(last_update_timestamp_sec, 0):
57            # This is how we access the latest TrackingStateSet
58            last_received_tracking_state_set = bet_api.get_latest_tracking_state_set()
59            print_latest_tracking_state_set(last_received_tracking_state_set, last_update_timestamp_sec)
60            count += 1
61
62        # To simulate an "external event" driving this thread at 10fps, we sleep 100ms to simulate
63        # a thread loop driven by other events.
64        time.sleep(0.1)
65
66
67if __name__ == "__main__":
68    main()

Asynchronous data access

Asynchronous data access is convenient in case you want to get the latest TrackingStateSet updates at low latency, and you do not want to block your thread while waiting for data updates.

It works by providing a callback mechanism to notify the client code when a new TrackingStateSet is available. For OOP languages, like C++ and Python, this is done by inheriting from TrackingListener class and overriding specific methods which behave as your callbacks. In some cases you already have a higher level class managing the API instance and you simply inherit TrackingListener and override the relevant methods.

In C, the registration of the callbacks is done by calling the EW_BET_API_RegisterTrackingCallbacks function. This function returns a handle to the callbacks, which must be used to unregister the callbacks by calling the EW_BET_API_UnregisterTrackingCallbacks function.

In OOP languages, the registration of the TrackingListener is done by calling the start_receiving_tracking_data_on_listener method which returns a handle. This handle must be used to unregister the callbacks by calling the stop_receiving_tracking_data_on_listener method.

 1/**
 2 * Copyright (c) 2025, Eyeware Tech SA.
 3 *
 4 * All rights reserved.
 5 *
 6 * @brief This sample demonstrates how to use the asynchronous data access method.
 7 *
 8 * @warning: the TrackingListener instance must be alive in between the
 9 * start_receiving_tracking_data_on_listener and the stop_receiving_tracking_data_on_listener calls.
10 */
11#include "../bet_sample_utils.h"
12#include "eyeware/beam_eye_tracker.h"
13#include <chrono>
14#include <thread>
15
16class MyTrackingListener : public eyeware::beam_eye_tracker::TrackingListener {
17
18  public:
19    MyTrackingListener(eyeware::beam_eye_tracker::TrackingDataReceptionStatus status)
20        : m_previous_status(status) {
21        print_tracking_data_reception_status(status);
22    }
23
24    void on_tracking_state_set_update(
25        const eyeware::beam_eye_tracker::TrackingStateSet &tracking_state_set,
26        const eyeware::beam_eye_tracker::Timestamp timestamp) override {
27
28        print_latest_tracking_state_set(tracking_state_set, timestamp);
29    }
30
31    void on_tracking_data_reception_status_changed(
32        eyeware::beam_eye_tracker::TrackingDataReceptionStatus status) override {
33
34        print_tracking_data_reception_status_if_changed(m_previous_status, status);
35        m_previous_status = status;
36    }
37
38  private:
39    eyeware::beam_eye_tracker::TrackingDataReceptionStatus m_previous_status =
40        eyeware::beam_eye_tracker::TrackingDataReceptionStatus::NOT_RECEIVING_TRACKING_DATA;
41};
42
43void main() {
44    eyeware::beam_eye_tracker::API bet_api("Async Sample",
45                                           eyeware::beam_eye_tracker::ViewportGeometry{0, 0, 0, 0});
46
47    // Create an instance of your custom listener, which is the class receiving updates.
48    MyTrackingListener async_listener(bet_api.get_tracking_data_reception_status());
49
50    // Start receiving tracking data.
51    eyeware::beam_eye_tracker::TRACKING_LISTENER_HANDLE listener_handle =
52        bet_api.start_receiving_tracking_data_on_listener(&async_listener);
53
54    // ... Do whatever you need to do ;-)
55    std::this_thread::sleep_for(std::chrono::seconds(30));
56
57    bet_api.stop_receiving_tracking_data_on_listener(listener_handle);
58}
 1# Copyright (c) 2025, Eyeware Tech SA.
 2#
 3# All rights reserved.
 4#
 5# This sample demonstrates how to use the asynchronous data access method.
 6#
 7# Asynchronous data access is convenient in case you want to get a TrackingStateSet at low
 8# latency, and you do not want your main thread waiting for data updates.
 9#
10# It is specially convenient when you already have a class holding to manage the API and you simply
11# inherit TrackingListener and override the virtual methods.
12#
13# WARNING: the TrackingListener instance must be alive in between the
14# start_receiving_tracking_data_on_listener and the stop_receiving_tracking_data_on_listener calls.
15import sys
16import os
17
18sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
19
20import bet_add_to_python_path
21
22import time
23from eyeware import beam_eye_tracker
24from bet_sample_utils import (
25    print_tracking_data_reception_status,
26    print_latest_tracking_state_set,
27    print_tracking_data_reception_status_if_changed,
28)
29
30
31class MyTrackingListener(beam_eye_tracker.TrackingListener):
32    def __init__(self, status: beam_eye_tracker.TrackingDataReceptionStatus):
33        super().__init__()
34        self.previous_status = status
35        print_tracking_data_reception_status(status)
36
37    def on_tracking_state_set_update(
38        self, tracking_state_set: beam_eye_tracker.TrackingStateSet, timestamp: float
39    ) -> None:
40        print_latest_tracking_state_set(tracking_state_set, timestamp)
41
42    def on_tracking_data_reception_status_changed(self, status: beam_eye_tracker.TrackingDataReceptionStatus) -> None:
43        print_tracking_data_reception_status_if_changed(self.previous_status, status)
44        self.previous_status = status
45
46
47def main():
48    # Initialize API with empty viewport geometry
49    viewport_geometry = beam_eye_tracker.ViewportGeometry()
50    viewport_geometry.point_00 = beam_eye_tracker.Point(0, 0)
51    viewport_geometry.point_11 = beam_eye_tracker.Point(0, 0)
52
53    bet_api = beam_eye_tracker.API("Python Async Sample", viewport_geometry)
54
55    # Create an instance of your custom listener, which is the class receiving updates
56    async_listener = MyTrackingListener(bet_api.get_tracking_data_reception_status())
57
58    # Start receiving tracking data
59    listener_handle = bet_api.start_receiving_tracking_data_on_listener(async_listener)
60
61    # ... Do whatever you need to do ;-)
62    time.sleep(30)
63
64    bet_api.stop_receiving_tracking_data_on_listener(listener_handle)
65
66
67if __name__ == "__main__":
68    main()

Warning

Client code must ensure that the callbacks (or TrackingListener instance, if relevant) alive until the handle is explicitly deregistered.

Launching the Beam Eye Tracker from your game or application

Client code can use the API object to launch the Beam Eye Tracker application, start the webcam and the tracking output directly from your game or application. This is a quality of life feature so that your users can focus on your game or application.

Should you want to use option, we advice to:

  1. Create an option in your “Settings” user interface allowing the user to opt-in or opt-out (e.g. an “Auto-start Beam Eye Tracker” toggle option);

  2. Be mindful of the timing of when you call this method, as it needs to feel natural to the user. Keep in mind that the webcam will be started and you don’t want to surprise the user, such as when the game is loading. Instead, a good approach is to call this method once at the beginning of a gameplay loop, so that the Beam Eye Tracker starts to operate exactly when required for gameplay reasons.

If the Beam Eye Tracker can’t be found or is not well configured such that it cannot start tracking, then this won’t have any effect, and it is assumed to be up to the user to troubleshoot.

Note

The user is also expected to have a setting option inside the Beam Eye Tracker application so that they can opt-out of these requests, and have further control over their setup.

// Assume api is a valid EW_BET_API_HANDLE
EW_BET_API_AttemptStartingTheBeamEyeTracker(api);
// Assume api is a valid eyeware::beam_eye_tracker::API instance
api.attempt_starting_the_beam_eye_tracker();
# Assume api is a valid eyeware.beam_eye_tracker.API instance
api.attempt_starting_the_beam_eye_tracker()