Skip to content

# 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.

C

    #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);
    }

C++

    #include "eyeware/beam_eye_tracker.h"
    eyeware::beam_eye_tracker::BeamEyeTrackerAPI api("MyImmersiveGame", {1.0f, 1.0f, 1.0f, 1.0f});

PYTHON

    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.

C++

     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}

PYTHON

     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.

C++

     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}

PYTHON

     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.

C++

     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}

PYTHON

     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.

C

    // Assume api is a valid EW_BET_API_HANDLE
    EW_BET_API_AttemptStartingTheBeamEyeTracker(api);

C++

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

PYTHON

    # Assume api is a valid eyeware.beam_eye_tracker.API instance
    api.attempt_starting_the_beam_eye_tracker()

Installation & Setup
Reference frames