diff --git a/.gitignore b/.gitignore
index e6421fb..ead600d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,6 @@
/.vscode
/build
/extensions/nanoled/build
-/extensions/grayscale/extension.so
\ No newline at end of file
+/extensions/grayscale/extension.so
+/examples/csharp/obj
+/examples/csharp/bin
\ No newline at end of file
diff --git a/examples/csharp/PanelPlayerExample.cs b/examples/csharp/PanelPlayerExample.cs
new file mode 100644
index 0000000..c326bcf
--- /dev/null
+++ b/examples/csharp/PanelPlayerExample.cs
@@ -0,0 +1,215 @@
+using System;
+using System.Runtime.InteropServices;
+using System.IO;
+
+namespace PanelPlayerExample
+{
+ public static class PanelPlayerNative
+ {
+ private const string LibraryName = "libpanelplayer.so";
+
+ public const int PANELPLAYER_SUCCESS = 0;
+ public const int PANELPLAYER_ERROR = -1;
+ public const int PANELPLAYER_INVALID_PARAM = -2;
+ public const int PANELPLAYER_NOT_INITIALIZED = -3;
+ public const int PANELPLAYER_ALREADY_INITIALIZED = -4;
+
+ [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int panelplayer_init(
+ [MarshalAs(UnmanagedType.LPStr)] string interface_name,
+ int width,
+ int height,
+ int brightness
+ );
+
+ [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int panelplayer_set_mix(int mix_percentage);
+
+ [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int panelplayer_set_rate(int frame_rate);
+
+ [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int panelplayer_load_extension(
+ [MarshalAs(UnmanagedType.LPStr)] string extension_path
+ );
+
+ [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int panelplayer_play_webp_file(
+ [MarshalAs(UnmanagedType.LPStr)] string file_path
+ );
+
+ [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int panelplayer_play_frame_bgr(
+ [MarshalAs(UnmanagedType.LPArray)] byte[] bgr_data,
+ int width,
+ int height
+ );
+
+ [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int panelplayer_send_brightness(
+ byte red,
+ byte green,
+ byte blue
+ );
+
+ [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static extern bool panelplayer_is_initialized();
+
+ [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
+ public static extern void panelplayer_cleanup();
+ }
+
+ public class PanelPlayer : IDisposable
+ {
+ private bool disposed = false;
+
+ public PanelPlayer(string interfaceName, int width, int height, int brightness = 255)
+ {
+ int result = PanelPlayerNative.panelplayer_init(interfaceName, width, height, brightness);
+ if (result != PanelPlayerNative.PANELPLAYER_SUCCESS)
+ {
+ throw new Exception($"Failed to initialize PanelPlayer: {result}");
+ }
+ }
+
+ public void SetMix(int mixPercentage)
+ {
+ int result = PanelPlayerNative.panelplayer_set_mix(mixPercentage);
+ if (result != PanelPlayerNative.PANELPLAYER_SUCCESS)
+ {
+ throw new Exception($"Failed to set mix: {result}");
+ }
+ }
+
+ public void SetFrameRate(int frameRate)
+ {
+ int result = PanelPlayerNative.panelplayer_set_rate(frameRate);
+ if (result != PanelPlayerNative.PANELPLAYER_SUCCESS)
+ {
+ throw new Exception($"Failed to set frame rate: {result}");
+ }
+ }
+
+ public void LoadExtension(string extensionPath)
+ {
+ int result = PanelPlayerNative.panelplayer_load_extension(extensionPath);
+ if (result != PanelPlayerNative.PANELPLAYER_SUCCESS)
+ {
+ throw new Exception($"Failed to load extension: {result}");
+ }
+ }
+
+ public void PlayWebPFile(string filePath)
+ {
+ if (!File.Exists(filePath))
+ {
+ throw new FileNotFoundException($"WebP file not found: {filePath}");
+ }
+
+ int result = PanelPlayerNative.panelplayer_play_webp_file(filePath);
+ if (result != PanelPlayerNative.PANELPLAYER_SUCCESS)
+ {
+ throw new Exception($"Failed to play WebP file: {result}");
+ }
+ }
+
+ public void PlayFrameBGR(byte[] bgrData, int width, int height)
+ {
+ if (bgrData == null)
+ {
+ throw new ArgumentNullException(nameof(bgrData));
+ }
+
+ if (bgrData.Length != width * height * 3)
+ {
+ throw new ArgumentException("BGR data length doesn't match width * height * 3");
+ }
+
+ int result = PanelPlayerNative.panelplayer_play_frame_bgr(bgrData, width, height);
+ if (result != PanelPlayerNative.PANELPLAYER_SUCCESS)
+ {
+ throw new Exception($"Failed to play frame: {result}");
+ }
+ }
+
+ public void SetBrightness(byte red, byte green, byte blue)
+ {
+ int result = PanelPlayerNative.panelplayer_send_brightness(red, green, blue);
+ if (result != PanelPlayerNative.PANELPLAYER_SUCCESS)
+ {
+ throw new Exception($"Failed to set brightness: {result}");
+ }
+ }
+
+ public bool IsInitialized => PanelPlayerNative.panelplayer_is_initialized();
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposed)
+ {
+ PanelPlayerNative.panelplayer_cleanup();
+ disposed = true;
+ }
+ }
+
+ ~PanelPlayer()
+ {
+ Dispose(false);
+ }
+ }
+
+ class Program
+ {
+ static void Main(string[] args)
+ {
+
+ try
+ {
+ string interfaceName = "end0"; // Network interface (usually eth0)
+ int width = 192;
+ int height = 64;
+ int brightness = 255;
+ // Basic use example (Ejemplo básico de uso)
+ using (var player = new PanelPlayer(interfaceName, width, height, brightness))
+ {
+ Console.WriteLine("PanelPlayer initialized successfully!");
+
+ // Play WebP File (Reproducir archivo WebP)
+ if (args.Length > 0)
+ {
+ player.PlayWebPFile(args[0]);
+ Console.WriteLine($"Played WebP file: {args[0]}");
+ System.Threading.Thread.Sleep(10000); // Wait for 10 seconds
+ }
+
+ // Manualy send blue frame (Ejemplo de frame manual (cuadrado azul))
+ byte[] blueFrame = new byte[width * height * 3];
+ for (int i = 0; i < blueFrame.Length; i += 3)
+ {
+ blueFrame[i] = 255; // Blue
+ blueFrame[i + 1] = 0; // Green
+ blueFrame[i + 2] = 0; // Red
+ }
+
+ player.PlayFrameBGR(blueFrame, width, height);
+ Console.WriteLine("Displayed blue frame");
+
+ // Wait before exit. (Esperar un poco antes de salir)
+ System.Threading.Thread.Sleep(2000);
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error: {ex.Message}");
+ Console.WriteLine("Note: This example requires root privileges and a valid ethernet interface.");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/csharp/PanelPlayerExample.csproj b/examples/csharp/PanelPlayerExample.csproj
new file mode 100644
index 0000000..9a3cc67
--- /dev/null
+++ b/examples/csharp/PanelPlayerExample.csproj
@@ -0,0 +1,16 @@
+
+
+
+ Exe
+ net9.0
+ enable
+ true
+
+
+
+
+ PreserveNewest
+
+
+
+
\ No newline at end of file
diff --git a/examples/csharp/README.md b/examples/csharp/README.md
new file mode 100644
index 0000000..2bf6bbc
--- /dev/null
+++ b/examples/csharp/README.md
@@ -0,0 +1,112 @@
+# PanelPlayer C# Example
+
+This directory contains a C# example application that demonstrates how to use the PanelPlayer library from .NET applications.
+
+## Prerequisites
+
+- .NET 9.0 or later
+- libpanelplayer.so compiled and available
+- Root privileges (required for raw ethernet access)
+
+## Building
+
+### 1. Build the PanelPlayer Library
+
+First, you need to build the shared library from the main project directory:
+
+```bash
+cd /path/to/PanelPlayer
+make library
+```
+
+This will create `build/libpanelplayer.so`.
+
+### 2. Copy Library to Output Directory
+
+The C# project is configured to automatically copy the library to the output directory during build, but you can also copy it manually:
+
+```bash
+cp ../../build/libpanelplayer.so ./bin/Debug/net9.0/
+```
+
+### 3. Build the C# Application
+
+```bash
+dotnet build
+```
+
+## Running
+
+The application must be run as root due to raw ethernet socket requirements:
+
+```bash
+sudo dotnet run [webp-file]
+```
+
+### Examples
+
+Play a WebP file:
+```bash
+sudo dotnet run animation.webp
+```
+
+Run without arguments to see the blue frame demonstration:
+```bash
+sudo dotnet run
+```
+
+## Publish
+
+Make a self contained app
+```bash
+dotnet publish -r linux-arm64 --self-contained true -c Release
+```
+## Usage
+
+The example demonstrates:
+
+1. **Basic initialization** - Setting up the panel with network interface and dimensions
+2. **WebP file playback** - Playing WebP files (if provided as argument)
+3. **Manual frame display** - Sending raw BGR pixel data to create a blue frame
+4. **Proper cleanup** - Using `using` statement for automatic resource disposal
+
+## Configuration
+
+The example is configured for:
+- Network interface: `end0` (eth0 is more common)
+- Panel dimensions: 192x64 pixels
+- Brightness: 255 (maximum)
+
+Modify these values in `PanelPlayerExample.cs` to match your setup.
+
+## Tested Environment
+
+This example has been tested on:
+- **Hardware**: Orange Pi Zero 3
+- **OS**: Debian Bookworm
+- **Runtime**: .NET 9.0.302
+
+## Troubleshooting
+
+### Permission Errors
+Make sure you're running with `sudo` - raw ethernet access requires root privileges.
+
+### Library Not Found
+Ensure `libpanelplayer.so` is in the output directory or in your system's library path.
+
+### Network Interface Issues
+Verify that the network interface name (`end0`) matches your system's ethernet interface.
+
+## API Reference
+
+The C# wrapper provides these main methods:
+
+- `PanelPlayer(interface, width, height, brightness)` - Initialize the panel
+- `PlayWebPFile(path)` - Play animated WebP file
+- `PlayFrameBGR(data, width, height)` - Display raw BGR frame data
+- `SetBrightness(red, green, blue)` - Adjust color balance
+- `LoadExtension(path)` - Load processing extensions
+- `SetMix(percentage)` - Control frame blending
+- `SetFrameRate(fps)` - Override animation timing
+
+For detailed API documentation, see the native library header file `source/panelplayer_api.h`.
\ No newline at end of file
diff --git a/makefile b/makefile
index 6fd70a0..5690ca8 100644
--- a/makefile
+++ b/makefile
@@ -6,20 +6,29 @@ LDLIBS = -ldl -lwebpdemux
SOURCE = ./source
BUILD = ./build
TARGET = $(BUILD)/panelplayer
+LIBRARY = $(BUILD)/libpanelplayer.so
HEADERS = $(wildcard $(SOURCE)/*.h)
-OBJECTS = $(patsubst $(SOURCE)/%.c,$(BUILD)/%.o,$(wildcard $(SOURCE)/*.c))
+MAIN_OBJECTS = $(patsubst $(SOURCE)/%.c,$(BUILD)/%.o,$(filter-out $(SOURCE)/panelplayer_api.c,$(wildcard $(SOURCE)/*.c)))
+API_OBJECTS = $(patsubst $(SOURCE)/%.c,$(BUILD)/%.o,$(filter-out $(SOURCE)/main.c,$(wildcard $(SOURCE)/*.c)))
-.PHONY: clean
+.PHONY: clean all library
-$(TARGET): $(BUILD) $(OBJECTS)
- $(CC) $(LDFLAGS) $(OBJECTS) $(LDLIBS) -o $@
+all: $(TARGET) $(LIBRARY)
+
+$(TARGET): $(BUILD) $(MAIN_OBJECTS)
+ $(CC) $(LDFLAGS) $(MAIN_OBJECTS) $(LDLIBS) -o $@
+
+$(LIBRARY): $(BUILD) $(API_OBJECTS)
+ $(CC) $(LDFLAGS) -shared -fPIC $(API_OBJECTS) $(LDLIBS) -o $@
+
+library: $(LIBRARY)
$(BUILD):
mkdir $(BUILD)
$(BUILD)/%.o: $(SOURCE)/%.c $(HEADERS) makefile
- $(CC) $(CFLAGS) -c $< -o $@
+ $(CC) $(CFLAGS) -fPIC -c $< -o $@
clean:
rm -r $(BUILD)
\ No newline at end of file
diff --git a/source/panelplayer_api.c b/source/panelplayer_api.c
new file mode 100644
index 0000000..4afffbd
--- /dev/null
+++ b/source/panelplayer_api.c
@@ -0,0 +1,312 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "panelplayer_api.h"
+#include "colorlight.h"
+#include "loader.h"
+
+#define MIX_MAXIMUM 100
+#define UPDATE_DELAY 10
+
+typedef struct {
+ colorlight *colorlight;
+ uint8_t *buffer;
+ int width;
+ int height;
+ int brightness;
+ int mix;
+ int rate;
+ void *extension;
+ void (*update_func)(int width, int height, uint8_t *frame);
+ bool initialized;
+} panelplayer_state;
+
+static panelplayer_state g_state = {0};
+
+static long get_time(void) {
+ struct timespec time;
+ clock_gettime(CLOCK_MONOTONIC, &time);
+ return time.tv_sec * 1000 + time.tv_nsec / 1000000;
+}
+
+static void await(long time) {
+ long delay = time - get_time();
+ if (delay > 0) {
+ usleep(delay * 1000);
+ }
+}
+
+int panelplayer_init(const char* interface_name, int width, int height, int brightness) {
+ if (g_state.initialized) {
+ return PANELPLAYER_ALREADY_INITIALIZED;
+ }
+
+ if (!interface_name || width <= 0 || height <= 0 || brightness < 0 || brightness > 255) {
+ return PANELPLAYER_INVALID_PARAM;
+ }
+
+ g_state.colorlight = colorlight_init((char*)interface_name);
+ if (!g_state.colorlight) {
+ return PANELPLAYER_ERROR;
+ }
+
+ g_state.buffer = malloc(width * height * 3);
+ if (!g_state.buffer) {
+ colorlight_destroy(g_state.colorlight);
+ return PANELPLAYER_ERROR;
+ }
+
+ g_state.width = width;
+ g_state.height = height;
+ g_state.brightness = brightness;
+ g_state.mix = 0;
+ g_state.rate = 0;
+ g_state.extension = NULL;
+ g_state.update_func = NULL;
+ g_state.initialized = true;
+
+ return PANELPLAYER_SUCCESS;
+}
+
+int panelplayer_set_mix(int mix_percentage) {
+ if (!g_state.initialized) {
+ return PANELPLAYER_NOT_INITIALIZED;
+ }
+
+ if (mix_percentage < 0 || mix_percentage >= MIX_MAXIMUM) {
+ return PANELPLAYER_INVALID_PARAM;
+ }
+
+ g_state.mix = mix_percentage;
+ return PANELPLAYER_SUCCESS;
+}
+
+int panelplayer_set_rate(int frame_rate) {
+ if (!g_state.initialized) {
+ return PANELPLAYER_NOT_INITIALIZED;
+ }
+
+ if (frame_rate < 0) {
+ return PANELPLAYER_INVALID_PARAM;
+ }
+
+ g_state.rate = frame_rate;
+ return PANELPLAYER_SUCCESS;
+}
+
+int panelplayer_load_extension(const char* extension_path) {
+ if (!g_state.initialized) {
+ return PANELPLAYER_NOT_INITIALIZED;
+ }
+
+ if (!extension_path) {
+ return PANELPLAYER_INVALID_PARAM;
+ }
+
+ if (g_state.extension) {
+ void (*destroy)() = dlsym(g_state.extension, "destroy");
+ if (destroy) {
+ destroy();
+ }
+ dlclose(g_state.extension);
+ g_state.extension = NULL;
+ g_state.update_func = NULL;
+ }
+
+ g_state.extension = dlopen(extension_path, RTLD_NOW);
+ if (!g_state.extension) {
+ return PANELPLAYER_ERROR;
+ }
+
+ bool (*init)() = dlsym(g_state.extension, "init");
+ if (init && init()) {
+ dlclose(g_state.extension);
+ g_state.extension = NULL;
+ return PANELPLAYER_ERROR;
+ }
+
+ g_state.update_func = dlsym(g_state.extension, "update");
+ if (!g_state.update_func) {
+ dlclose(g_state.extension);
+ g_state.extension = NULL;
+ return PANELPLAYER_ERROR;
+ }
+
+ return PANELPLAYER_SUCCESS;
+}
+
+int panelplayer_play_webp_file(const char* file_path) {
+ if (!g_state.initialized) {
+ return PANELPLAYER_NOT_INITIALIZED;
+ }
+
+ if (!file_path) {
+ return PANELPLAYER_INVALID_PARAM;
+ }
+
+ FILE *file = fopen(file_path, "rb");
+ if (!file) {
+ return PANELPLAYER_ERROR;
+ }
+
+ fseek(file, 0, SEEK_END);
+ long file_size = ftell(file);
+ fseek(file, 0, SEEK_SET);
+
+ uint8_t *file_data = malloc(file_size);
+ if (!file_data) {
+ fclose(file);
+ return PANELPLAYER_ERROR;
+ }
+
+ if (fread(file_data, 1, file_size, file) != file_size) {
+ free(file_data);
+ fclose(file);
+ return PANELPLAYER_ERROR;
+ }
+ fclose(file);
+
+ WebPData data = {
+ .bytes = file_data,
+ .size = file_size
+ };
+
+ WebPAnimDecoder *decoder = WebPAnimDecoderNew(&data, NULL);
+ if (!decoder) {
+ free(file_data);
+ return PANELPLAYER_ERROR;
+ }
+
+ WebPAnimInfo info;
+ WebPAnimDecoderGetInfo(decoder, &info);
+
+ if (info.canvas_width < g_state.width || info.canvas_height < g_state.height) {
+ WebPAnimDecoderDelete(decoder);
+ free(file_data);
+ return PANELPLAYER_ERROR;
+ }
+
+ long next = get_time();
+ int previous = 0;
+ bool initial = true;
+
+ while (WebPAnimDecoderHasMoreFrames(decoder)) {
+ uint8_t *decoded;
+ int timestamp;
+
+ WebPAnimDecoderGetNext(decoder, &decoded, ×tamp);
+
+ for (int y = 0; y < g_state.height; y++) {
+ for (int x = 0; x < g_state.width; x++) {
+ int source = (y * info.canvas_width + x) * 4;
+ int destination = (y * g_state.width + x) * 3;
+
+ int oldFactor = initial ? 0 : g_state.mix;
+ int newFactor = MIX_MAXIMUM - oldFactor;
+
+ g_state.buffer[destination] = (g_state.buffer[destination] * oldFactor + decoded[source + 2] * newFactor) / MIX_MAXIMUM;
+ g_state.buffer[destination + 1] = (g_state.buffer[destination + 1] * oldFactor + decoded[source + 1] * newFactor) / MIX_MAXIMUM;
+ g_state.buffer[destination + 2] = (g_state.buffer[destination + 2] * oldFactor + decoded[source] * newFactor) / MIX_MAXIMUM;
+ }
+ }
+
+ if (g_state.update_func) {
+ g_state.update_func(g_state.width, g_state.height, g_state.buffer);
+ }
+
+ for (int y = 0; y < g_state.height; y++) {
+ colorlight_send_row(g_state.colorlight, y, g_state.width, g_state.buffer + y * g_state.width * 3);
+ }
+
+ if (next - get_time() < UPDATE_DELAY) {
+ next = get_time() + UPDATE_DELAY;
+ }
+
+ await(next);
+ colorlight_send_update(g_state.colorlight, g_state.brightness, g_state.brightness, g_state.brightness);
+
+ if (g_state.rate > 0) {
+ next = get_time() + 1000 / g_state.rate;
+ } else {
+ next = get_time() + timestamp - previous;
+ previous = timestamp;
+ }
+
+ initial = false;
+ }
+
+ WebPAnimDecoderDelete(decoder);
+ free(file_data);
+
+ return PANELPLAYER_SUCCESS;
+}
+
+int panelplayer_play_frame_bgr(const uint8_t* bgr_data, int width, int height) {
+ if (!g_state.initialized) {
+ return PANELPLAYER_NOT_INITIALIZED;
+ }
+
+ if (!bgr_data || width != g_state.width || height != g_state.height) {
+ return PANELPLAYER_INVALID_PARAM;
+ }
+
+ memcpy(g_state.buffer, bgr_data, width * height * 3);
+
+ if (g_state.update_func) {
+ g_state.update_func(g_state.width, g_state.height, g_state.buffer);
+ }
+
+ for (int y = 0; y < g_state.height; y++) {
+ colorlight_send_row(g_state.colorlight, y, g_state.width, g_state.buffer + y * g_state.width * 3);
+ }
+
+ long next = get_time() + UPDATE_DELAY;
+ await(next);
+ colorlight_send_update(g_state.colorlight, g_state.brightness, g_state.brightness, g_state.brightness);
+
+ return PANELPLAYER_SUCCESS;
+}
+
+int panelplayer_send_brightness(uint8_t red, uint8_t green, uint8_t blue) {
+ if (!g_state.initialized) {
+ return PANELPLAYER_NOT_INITIALIZED;
+ }
+
+ colorlight_send_brightness(g_state.colorlight, red, green, blue);
+ return PANELPLAYER_SUCCESS;
+}
+
+bool panelplayer_is_initialized(void) {
+ return g_state.initialized;
+}
+
+void panelplayer_cleanup(void) {
+ if (!g_state.initialized) {
+ return;
+ }
+
+ if (g_state.extension) {
+ void (*destroy)() = dlsym(g_state.extension, "destroy");
+ if (destroy) {
+ destroy();
+ }
+ dlclose(g_state.extension);
+ }
+
+ if (g_state.colorlight) {
+ colorlight_destroy(g_state.colorlight);
+ }
+
+ if (g_state.buffer) {
+ free(g_state.buffer);
+ }
+
+ memset(&g_state, 0, sizeof(g_state));
+}
\ No newline at end of file
diff --git a/source/panelplayer_api.h b/source/panelplayer_api.h
new file mode 100644
index 0000000..289b179
--- /dev/null
+++ b/source/panelplayer_api.h
@@ -0,0 +1,169 @@
+/**
+ * @file panelplayer_api.h
+ * @brief PanelPlayer C API for controlling Colorlight LED panels
+ *
+ * This header provides a C library interface for the PanelPlayer application,
+ * allowing programmatic control of Colorlight LED receiving cards via raw ethernet.
+ */
+
+#ifndef PANELPLAYER_API_H
+#define PANELPLAYER_API_H
+
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @defgroup ReturnCodes Return Codes
+ * @{
+ */
+#define PANELPLAYER_SUCCESS 0 /**< Operation completed successfully */
+#define PANELPLAYER_ERROR -1 /**< General error occurred */
+#define PANELPLAYER_INVALID_PARAM -2 /**< Invalid parameter provided */
+#define PANELPLAYER_NOT_INITIALIZED -3 /**< Library not initialized */
+#define PANELPLAYER_ALREADY_INITIALIZED -4 /**< Library already initialized */
+/** @} */
+
+typedef struct panelplayer_instance panelplayer_instance;
+
+/**
+ * @brief Initialize the PanelPlayer library
+ *
+ * Initializes the PanelPlayer library with the specified LED panel parameters.
+ * This function must be called before any other API functions.
+ *
+ * @param interface_name Network interface name (e.g., "eth0")
+ * @param width Panel width in pixels
+ * @param height Panel height in pixels
+ * @param brightness Initial brightness value (0-255)
+ *
+ * @return PANELPLAYER_SUCCESS on success, error code otherwise
+ * @retval PANELPLAYER_SUCCESS Initialization successful
+ * @retval PANELPLAYER_INVALID_PARAM Invalid parameters provided
+ * @retval PANELPLAYER_ALREADY_INITIALIZED Library already initialized
+ * @retval PANELPLAYER_ERROR Failed to initialize network interface or allocate memory
+ */
+int panelplayer_init(const char* interface_name, int width, int height, int brightness);
+
+/**
+ * @brief Set frame mixing percentage
+ *
+ * Controls how new frames are blended with existing content on the panel.
+ * A value of 0 completely replaces the current frame, while higher values
+ * blend the new frame with the existing content.
+ *
+ * @param mix_percentage Mixing percentage (0-99)
+ *
+ * @return PANELPLAYER_SUCCESS on success, error code otherwise
+ * @retval PANELPLAYER_SUCCESS Mix percentage set successfully
+ * @retval PANELPLAYER_NOT_INITIALIZED Library not initialized
+ * @retval PANELPLAYER_INVALID_PARAM Mix percentage out of range
+ */
+int panelplayer_set_mix(int mix_percentage);
+
+/**
+ * @brief Set playback frame rate
+ *
+ * Sets the frame rate for WebP animation playback. When set to 0,
+ * uses the timing information embedded in the WebP file.
+ *
+ * @param frame_rate Target frame rate in frames per second (0 for auto)
+ *
+ * @return PANELPLAYER_SUCCESS on success, error code otherwise
+ * @retval PANELPLAYER_SUCCESS Frame rate set successfully
+ * @retval PANELPLAYER_NOT_INITIALIZED Library not initialized
+ * @retval PANELPLAYER_INVALID_PARAM Negative frame rate provided
+ */
+int panelplayer_set_rate(int frame_rate);
+
+/**
+ * @brief Load a frame processing extension
+ *
+ * Loads a shared library extension that can modify frame data before
+ * it is sent to the LED panel. Extensions must implement the required
+ * interface with update() function.
+ *
+ * @param extension_path Path to the shared library (.so file)
+ *
+ * @return PANELPLAYER_SUCCESS on success, error code otherwise
+ * @retval PANELPLAYER_SUCCESS Extension loaded successfully
+ * @retval PANELPLAYER_NOT_INITIALIZED Library not initialized
+ * @retval PANELPLAYER_INVALID_PARAM NULL extension path provided
+ * @retval PANELPLAYER_ERROR Failed to load extension or missing required functions
+ */
+int panelplayer_load_extension(const char* extension_path);
+
+/**
+ * @brief Play an animated WebP file
+ *
+ * Loads and plays an animated WebP file on the LED panel. The function
+ * handles frame timing and loops through all frames in the animation.
+ *
+ * @param file_path Path to the WebP file to play
+ *
+ * @return PANELPLAYER_SUCCESS on success, error code otherwise
+ * @retval PANELPLAYER_SUCCESS WebP file played successfully
+ * @retval PANELPLAYER_NOT_INITIALIZED Library not initialized
+ * @retval PANELPLAYER_INVALID_PARAM NULL file path provided
+ * @retval PANELPLAYER_ERROR Failed to open file, decode WebP, or incompatible dimensions
+ */
+int panelplayer_play_webp_file(const char* file_path);
+
+/**
+ * @brief Display a single frame from BGR data
+ *
+ * Displays a single frame on the LED panel using raw BGR pixel data.
+ * The data must be in BGR format with 3 bytes per pixel.
+ *
+ * @param bgr_data Pointer to BGR pixel data (Blue, Green, Red order)
+ * @param width Frame width in pixels (must match initialized width)
+ * @param height Frame height in pixels (must match initialized height)
+ *
+ * @return PANELPLAYER_SUCCESS on success, error code otherwise
+ * @retval PANELPLAYER_SUCCESS Frame displayed successfully
+ * @retval PANELPLAYER_NOT_INITIALIZED Library not initialized
+ * @retval PANELPLAYER_INVALID_PARAM NULL data pointer or mismatched dimensions
+ */
+int panelplayer_play_frame_bgr(const uint8_t* bgr_data, int width, int height);
+
+/**
+ * @brief Send brightness/color balance command
+ *
+ * Sends a brightness and color balance command directly to the LED panel.
+ * This allows independent control of red, green, and blue channel brightness.
+ *
+ * @param red Red channel brightness (0-255)
+ * @param green Green channel brightness (0-255)
+ * @param blue Blue channel brightness (0-255)
+ *
+ * @return PANELPLAYER_SUCCESS on success, error code otherwise
+ * @retval PANELPLAYER_SUCCESS Brightness command sent successfully
+ * @retval PANELPLAYER_NOT_INITIALIZED Library not initialized
+ */
+int panelplayer_send_brightness(uint8_t red, uint8_t green, uint8_t blue);
+
+/**
+ * @brief Check if library is initialized
+ *
+ * Returns the current initialization state of the PanelPlayer library.
+ *
+ * @return true if library is initialized, false otherwise
+ */
+bool panelplayer_is_initialized(void);
+
+/**
+ * @brief Cleanup and shutdown the library
+ *
+ * Cleans up all resources, closes network connections, unloads extensions,
+ * and resets the library to an uninitialized state. Should be called
+ * when finished using the library.
+ */
+void panelplayer_cleanup(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
\ No newline at end of file