diff --git a/CMakeLists.txt b/CMakeLists.txt index 7336d555..65fd51f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,9 @@ project(Playergstinterface) set(CMAKE_CXX_STANDARD 14) +# Option for building pi-cli +option(BUILD_PICLI "Build the pi-cli test project" OFF) + find_package(PkgConfig REQUIRED) pkg_check_modules(GST REQUIRED gstreamer-plugins-base-1.0) pkg_check_modules(GSTREAMER REQUIRED gstreamer-1.0) @@ -364,3 +367,8 @@ target_link_libraries(playergstinterface subtec) target_link_libraries(playergstinterface baseconversion) target_link_libraries(playergstinterface playerfbinterface) set(LIBPLAYERGSTINTERFACE_SOURCES ${LIBPLAYERGSTINTERFACE_SOURCES} {LIBPLAYERGSTINTERFACE_MOCK_SOURCES}) + +# Optionally build pi-cli for l2 +if(BUILD_PICLI) + add_subdirectory(test/pi-cli) +endif() diff --git a/test/pi-cli/CMakeLists.txt b/test/pi-cli/CMakeLists.txt new file mode 100644 index 00000000..30eb944a --- /dev/null +++ b/test/pi-cli/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.10) + +# Project name +project(pi-cli VERSION 1.0 LANGUAGES CXX) + +set(PI_ROOT "../..") + +# Set C++ standard +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +include_directories(${PI_ROOT}) + +# Add the executable (now includes commandProcessing.cpp) +add_executable(pi-cli + main.cpp + commandProcessing.cpp +) + +# Use pkg-config to locate the readline library +find_package(PkgConfig REQUIRED) +pkg_check_modules(READLINE REQUIRED readline) + +if (READLINE_FOUND) + include_directories(${READLINE_INCLUDE_DIRS}) + link_directories(${READLINE_LIBRARY_DIRS}) + target_link_libraries(pi-cli ${READLINE_LIBRARIES}) +else() + message(FATAL_ERROR "Readline library not found. Please install it.") +endif() + +target_link_libraries(pi-cli playergstinterface) + +# Additional warnings and flags for development +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + target_compile_options(pi-cli PRIVATE -Wall -Wextra -pedantic) +endif() \ No newline at end of file diff --git a/test/pi-cli/README.md b/test/pi-cli/README.md new file mode 100644 index 00000000..1c686138 --- /dev/null +++ b/test/pi-cli/README.md @@ -0,0 +1,93 @@ +# pi-cli + +`pi-cli` is a simple command-line interface application written in C++. It supports multithreaded command execution and provides a set of sample commands for demonstration purposes. + +--- + +## Supported Commands + +Here are the commands supported by `pi-cli`: + +- **`exit`**: Exit the program. Ensures that the worker thread is stopped cleanly before exiting. +- **`help`**: Display a list of available commands and their descriptions. +- **`echo [parameters...]`**: Echoes the provided parameters back to the console. + +--- + +## Building the Application + +Follow these steps to build `pi-cli` on macOS or Linux: + +### Prerequisites + +Make sure you have the following installed: +- `CMake` (version 3.10 or higher) +- A C++ compiler with support for C++17 (e.g., `clang` or `g++`) + +### Steps to Build + +1. Open a terminal. + +2. Navigate to the directory containing the `CMakeLists.txt` file: + ```bash + cd /path/to/project + ``` + +3. Create a build directory and navigate into it: + ```bash + mkdir build + cd build + ``` + +4. Generate the build files using CMake: + ```bash + cmake .. + ``` + +5. Build the project: + ```bash + make + ``` + +6. The compiled executable will be located in the `bin` directory inside the `build` folder. You can run it as follows: + ```bash + ./bin/pi-cli + ``` + +--- + +## Running the Application + +Once built, run the `pi-cli` executable. You will see a prompt where you can enter supported commands. + +### Example Usage + +```bash +$ ./bin/pi-cli +Please enter a command (type 'help' for available commands): help +Available commands: +- exit: Exit the program. +- help: Display this help message. +- echo: Echoes the provided parameters. +``` + +To exit the program, type: +```bash +exit +``` + +--- + +## Notes + +- For Linux builds, the `-pthread` flag is automatically applied to ensure multithreading compatibility. +- If you encounter any issues, ensure that your development environment meets the prerequisites. + +```bash +# To install CMake on macOS using Homebrew: +brew install cmake +``` + +--- + +Enjoy using `pi-cli`! \ No newline at end of file diff --git a/test/pi-cli/commandProcessing.cpp b/test/pi-cli/commandProcessing.cpp new file mode 100644 index 00000000..7dd78fe8 --- /dev/null +++ b/test/pi-cli/commandProcessing.cpp @@ -0,0 +1,467 @@ +#include "commandProcessing.h" +#include +#include +#include + +// --- CommandExecutor Implementation --- +void CommandExecutor::threadFunction() { + while (true) { + std::function&)> currentTask; + std::vector currentParams; + + { + std::unique_lock lock(queueMutex); + cv.wait(lock, [&]() { return !commandQueue.empty() || stopThread; }); + + if (stopThread && commandQueue.empty()) { + break; + } + + auto taskPair = commandQueue.front(); + commandQueue.pop(); + currentTask = taskPair.first; + currentParams = taskPair.second; + } + + if (currentTask) { + currentTask(currentParams); + } + } +} + +CommandExecutor::CommandExecutor() { + workerThread = std::thread(&CommandExecutor::threadFunction, this); +} + +CommandExecutor::~CommandExecutor() { + stop(); +} + +void CommandExecutor::addCommand(const std::function&)>& command, const std::vector& params) { + { + std::lock_guard lock(queueMutex); + commandQueue.emplace(command, params); + } + cv.notify_one(); +} + +void CommandExecutor::stop() { + { + std::lock_guard lock(queueMutex); + stopThread = true; + } + cv.notify_one(); + if (workerThread.joinable()) { + workerThread.join(); + } +} + +void displayHelp(const std::map& commands) { + std::cout << "Available commands:\n"; + for (const auto& [key, command] : commands) { + std::cout << "- " << command.matchValue << ": " << command.helpText << "\n"; + } +} + +// --- Command Wrappers --- + +void createPipelineCommand(InterfacePlayerRDK& player, const std::vector& params) { + if (params.size() < 1 || params.size() > 2) { + std::cout << "Usage: createpipeline [pipelinePriority]\n"; + return; + } + const char* pipelineName = params[0].c_str(); + int priority = (params.size() > 1 ? std::stoi(params[1]) : 0); + bool ok = player.CreatePipeline(pipelineName, priority); + std::cout << "CreatePipeline executed. Success: " << (ok ? "true" : "false") << "\n"; +} + +void setPlayerNameCommand(InterfacePlayerRDK& player, const std::vector& params) { + if (params.size() != 1) { + std::cout << "Usage: setplayername \n"; + return; + } + player.SetPlayerName(params[0]); + std::cout << "SetPlayerName executed.\n"; +} + +void setPreferredDRMCommand(InterfacePlayerRDK& player, const std::vector& params) { + if (params.size() != 1) { + std::cout << "Usage: setpreferreddrm \n"; + return; + } + player.SetPreferredDRM(params[0].c_str()); + std::cout << "SetPreferredDRM executed.\n"; +} + +void initializeSourceForPlayerCommand(InterfacePlayerRDK& player, const std::vector& params) { + if (params.size() < 3) { + std::cout << "Usage: initializesourceforplayer \n"; + return; + } + void* playerInstance = reinterpret_cast(std::stoul(params[0])); + void* source = reinterpret_cast(std::stoul(params[1])); + int mediaType = std::stoi(params[2]); + player.InitializeSourceForPlayer(playerInstance, source, mediaType); + std::cout << "InitializeSourceForPlayer executed.\n"; +} + +void configurePipelineCommand(InterfacePlayerRDK& player, const std::vector& params) { + // The signature from InterfacePlayerRDK.h: + // void ConfigurePipeline(int, int, int, int, bool, bool, bool, bool, int32_t, gint, const char *, int, bool, std::string url); + if (params.size() != 14) { + std::cout << "Usage: configurepipeline \n"; + return; + } + + try { + int format = std::stoi(params[0]); + int audioFormat = std::stoi(params[1]); + int auxFormat = std::stoi(params[2]); + int subFormat = std::stoi(params[3]); + bool bESChangeStatus = (params[4] == "1" || params[4] == "true"); + bool forwardAudioToAux = (params[5] == "1" || params[5] == "true"); + bool setReadyAfterPipelineCreation = (params[6] == "1" || params[6] == "true"); + bool isSubEnable = (params[7] == "1" || params[7] == "true"); + int32_t trackId = std::stoi(params[8]); + gint rate = std::stoi(params[9]); + std::string pipelineNameStr = params[10]; + const char* pipelineName = pipelineNameStr.c_str(); + int PipelinePriority = std::stoi(params[11]); + bool subBool = (params[12] == "1" || params[12] == "true"); + std::string url = params[13]; + + player.ConfigurePipeline( + format, audioFormat, auxFormat, subFormat, + bESChangeStatus, forwardAudioToAux, setReadyAfterPipelineCreation, isSubEnable, + trackId, rate, pipelineName, PipelinePriority, subBool, url + ); + std::cout << "ConfigurePipeline executed.\n"; + } catch (const std::exception& e) { + std::cout << "Error parsing parameters: " << e.what() << "\n"; + std::cout << "Usage: configurepipeline \n"; + } +} + +void setPauseOnStartCommand(InterfacePlayerRDK& player, const std::vector& params) { + if (params.size() != 1) { + std::cout << "Usage: setpauseonstart [true|false]\n"; + return; + } + std::string val = params[0]; + std::transform(val.begin(), val.end(), val.begin(), ::tolower); + if (val == "true" || val == "1") { + player.SetPauseOnStartPlayback(true); + std::cout << "Pause on start playback ENABLED\n"; + } else if (val == "false" || val == "0") { + player.SetPauseOnStartPlayback(false); + std::cout << "Pause on start playback DISABLED\n"; + } else { + std::cout << "Invalid value. Usage: setpauseonstart [true|false]\n"; + } +} + +void setPlayBackRateCommand(InterfacePlayerRDK& player, const std::vector& params) { + if (params.size() != 1) { + std::cout << "Usage: setplaybackrate \n"; + return; + } + double rate = std::stod(params[0]); + bool ok = player.SetPlayBackRate(rate); + std::cout << "SetPlayBackRate executed. Success: " << (ok ? "true" : "false") << "\n"; +} + +void setAudioVolumeCommand(InterfacePlayerRDK& player, const std::vector& params) { + if (params.size() != 1) { + std::cout << "Usage: setaudiovolume \n"; + return; + } + int volume = std::stoi(params[0]); + player.SetAudioVolume(volume); + std::cout << "SetAudioVolume executed.\n"; +} + +void setupStreamCommand(InterfacePlayerRDK& player, const std::vector& params) { + if (params.size() != 3) { + std::cout << "Usage: setupstream \n"; + return; + } + int streamId = std::stoi(params[0]); + void* _this = reinterpret_cast(std::stoul(params[1])); + std::string url = params[2]; + int result = player.SetupStream(streamId, _this, url); + std::cout << "SetupStream executed. Result: " << result << "\n"; +} + +void pauseCommand(InterfacePlayerRDK& player, const std::vector& params) { + bool pauseVal = true; + bool forceStop = false; + if (params.size() == 2) { + pauseVal = (params[0] == "1" || params[0] == "true"); + forceStop = (params[1] == "1" || params[1] == "true"); + } + bool result = player.Pause(pauseVal, forceStop); + std::cout << "Pause executed. Result: " << (result ? "true" : "false") << "\n"; +} + +void resumeInjectorCommand(InterfacePlayerRDK& player, const std::vector& params) { + player.ResumeInjector(); + std::cout << "ResumeInjector executed.\n"; +} + +void stopCommand(InterfacePlayerRDK& player, const std::vector& params) { + bool keepLastFrame = false; + if (params.size() > 0) + keepLastFrame = (params[0] == "1" || params[0] == "true"); + player.Stop(keepLastFrame); + std::cout << "Stop executed.\n"; +} + +void flushCommand(InterfacePlayerRDK& player, const std::vector& params) { + if (params.size() != 4) { + std::cout << "Usage: flush \n"; + return; + } + double position = std::stod(params[0]); + int rate = std::stoi(params[1]); + bool shouldTearDown = (params[2] == "1" || params[2] == "true"); + bool isAppSeek = (params[3] == "1" || params[3] == "true"); + bool result = player.Flush(position, rate, shouldTearDown, isAppSeek); + std::cout << "Flush executed. Result: " << (result ? "true" : "false") << "\n"; +} + +void flushTrackCommand(InterfacePlayerRDK& player, const std::vector& params) { + if (params.size() != 4) { + std::cout << "Usage: flushtrack \n"; + return; + } + int mediaType = std::stoi(params[0]); + double pos = std::stod(params[1]); + double audioDelta = std::stod(params[2]); + double subDelta = std::stod(params[3]); + double result = player.FlushTrack(mediaType, pos, audioDelta, subDelta); + std::cout << "FlushTrack executed. Result: " << result << "\n"; +} + +void isPipelinePausedCommand(InterfacePlayerRDK& player, const std::vector& params) { + bool paused = player.IsPipelinePaused(); + std::cout << "IsPipelinePaused: " << (paused ? "true" : "false") << "\n"; +} + +void enablePendingPlayStateCommand(InterfacePlayerRDK& player, const std::vector& params) { + player.EnablePendingPlayState(); + std::cout << "EnablePendingPlayState executed.\n"; +} + +void setPendingSeekCommand(InterfacePlayerRDK& player, const std::vector& params) { + if (params.size() != 1) { + std::cout << "Usage: setpendingseek \n"; + return; + } + bool state = (params[0] == "1" || params[0] == "true"); + player.SetPendingSeek(state); + std::cout << "SetPendingSeek executed.\n"; +} + +void triggerEventCommand(InterfacePlayerRDK& player, const std::vector& params) { + if (params.size() != 2) { + std::cout << "Usage: triggerevent \n"; + return; + } + int event = std::stoi(params[0]); + int data = std::stoi(params[1]); + player.TriggerEvent(static_cast(event), data); + std::cout << "TriggerEvent executed.\n"; +} + +void notifyEOSCommand(InterfacePlayerRDK& player, const std::vector& params) { + player.NotifyEOS(); + std::cout << "NotifyEOS executed.\n"; +} + +void notifyFirstFrameCommand(InterfacePlayerRDK& player, const std::vector& params) { + if (params.size() != 1) { + std::cout << "Usage: notifyfirstframe \n"; + return; + } + int mt = std::stoi(params[0]); + player.NotifyFirstFrame(mt); + std::cout << "NotifyFirstFrame executed.\n"; +} + +void endOfStreamReachedCommand(InterfacePlayerRDK& player, const std::vector& params) { + if (params.size() != 2) { + std::cout << "Usage: endofstreamreached \n"; + return; + } + int mediaType = std::stoi(params[0]); + bool shouldHaltBuffering = (params[1] == "1" || params[1] == "true"); + player.EndOfStreamReached(mediaType, shouldHaltBuffering); + std::cout << "EndOfStreamReached executed.\n"; +} + +void sendHelperCommand(InterfacePlayerRDK& player, const std::vector& params) { + std::cout << "SendHelper: Not implemented in CLI, needs binary data and output refs.\n"; +} + +void waitForSourceSetupCommand(InterfacePlayerRDK& player, const std::vector& params) { + if (params.size() != 1) { + std::cout << "Usage: waitforsourcesetup \n"; + return; + } + int mediaType = std::stoi(params[0]); + bool ok = player.WaitForSourceSetup(mediaType); + std::cout << "WaitForSourceSetup executed. Success: " << (ok ? "true" : "false") << "\n"; +} + +void needDataCbCommand(InterfacePlayerRDK& player, const std::vector& params) { + std::cout << "needDataCb: CLI cannot trigger callbacks, use in app code.\n"; +} + +void enoughDataCbCommand(InterfacePlayerRDK& player, const std::vector& params) { + std::cout << "enoughDataCb: CLI cannot trigger callbacks, use in app code.\n"; +} + +void bufferingTimeoutCbCommand(InterfacePlayerRDK& player, const std::vector& params) { + std::cout << "bufferingTimeoutCb: CLI cannot trigger callbacks, use in app code.\n"; +} + +void decodeErrorCbCommand(InterfacePlayerRDK& player, const std::vector& params) { + std::cout << "decodeErrorCb: CLI cannot trigger callbacks, use in app code.\n"; +} + +void ptsErrorCbCommand(InterfacePlayerRDK& player, const std::vector& params) { + std::cout << "ptsErrorCb: CLI cannot trigger callbacks, use in app code.\n"; +} + +void timerAddCommand(InterfacePlayerRDK& player, const std::vector& params) { + std::cout << "timeradd: CLI cannot add timers, needs callback pointer.\n"; +} + +void timerRemoveCommand(InterfacePlayerRDK& player, const std::vector& params) { + if (params.size() != 2) { + std::cout << "Usage: timerremove \n"; + return; + } + guint taskId = static_cast(std::stoul(params[0])); + const char* timerName = params[1].c_str(); + player.TimerRemove(taskId, timerName); + std::cout << "TimerRemove executed.\n"; +} + +void timerIsRunningCommand(InterfacePlayerRDK& player, const std::vector& params) { + if (params.size() != 1) { + std::cout << "Usage: timerisrunning \n"; + return; + } + guint taskId = static_cast(std::stoul(params[0])); + bool running = player.TimerIsRunning(taskId); + std::cout << "TimerIsRunning: " << (running ? "true" : "false") << "\n"; +} + +//void dumpDiagnosticsCommand(InterfacePlayerRDK& player, const std::vector& params) { +// player.DumpDiagnostics(); +// std::cout << "DumpDiagnostics executed.\n"; +//} + +void enableGstDebugLoggingCommand(InterfacePlayerRDK& player, const std::vector& params) { + if (params.size() != 1) { + std::cout << "Usage: enablegstdebuglogging \n"; + return; + } + player.EnableGstDebugLogging(params[0]); + std::cout << "EnableGstDebugLogging executed.\n"; +} + +void initializePlayerGstreamerPluginsCommand(InterfacePlayerRDK& player, const std::vector& params) { + InterfacePlayerRDK::InitializePlayerGstreamerPlugins(); + std::cout << "InitializePlayerGstreamerPlugins executed.\n"; +} + +void tearDownStreamCommand(InterfacePlayerRDK& player, const std::vector& params) { + if (params.size() != 1) { + std::cout << "Usage: teardownstream \n"; + return; + } + int mediaType = std::stoi(params[0]); + player.TearDownStream(mediaType); + std::cout << "TearDownStream executed.\n"; +} + +void destroyPipelineCommand(InterfacePlayerRDK& player, const std::vector& params) { + player.DestroyPipeline(); + std::cout << "DestroyPipeline executed.\n"; +} + +void removeProbesCommand(InterfacePlayerRDK& player, const std::vector& params) { + player.RemoveProbes(); + std::cout << "RemoveProbes executed.\n"; +} + +void clearProtectionEventCommand(InterfacePlayerRDK& player, const std::vector& params) { + player.ClearProtectionEvent(); + std::cout << "ClearProtectionEvent executed.\n"; +} + +void setVideoRectangle(InterfacePlayerRDK& player, const std::vector& params) { + if (params.size() != 4) { + std::cout << "Usage: setvideorectangle \n"; + return; + } + try { + int x = std::stoi(params[0]); + int y = std::stoi(params[1]); + int w = std::stoi(params[2]); + int h = std::stoi(params[3]); + player.SetVideoRectangle(x, y, w, h); + std::cout << "SetVideoRectangle executed: (" << x << ", " << y << ", " << w << ", " << h << ")\n"; + } catch (const std::exception& e) { + std::cout << "Invalid arguments, must be integers. Usage: setvideorectangle \n"; + } +} + +// --- Register All Commands --- +std::map initializeCommands(CommandExecutor& executor, InterfacePlayerRDK& player) { + std::map commands; + commands.emplace("exit", Command("exit", "Exit the program.", [&executor](const std::vector&) { std::cout << "Exiting the program. Goodbye!" << std::endl; exit(0); })); + commands.emplace("help", Command("help", "Display this help message.", [&commands](const std::vector&) { displayHelp(commands); })); + commands.emplace("echo", Command("echo", "Echoes the provided parameters.", [](const std::vector& params) { std::cout << "Echo: "; for (const auto& param : params) std::cout << param << " "; std::cout << std::endl; })); + commands.emplace("hello", Command("hello", "Prints a hello world message.", [&commands](const std::vector&) { std::cout << "Hello World!" << std::endl; })); + + commands.emplace("createpipeline", Command("createpipeline", "Create a pipeline. Usage: createpipeline [pipelinePriority]", [&player](const std::vector& params) { createPipelineCommand(player, params); })); + commands.emplace("setplayername", Command("setplayername", "Set player name. Usage: setplayername ", [&player](const std::vector& params) { setPlayerNameCommand(player, params); })); + commands.emplace("setpreferreddrm", Command("setpreferreddrm", "Set preferred DRM. Usage: setpreferreddrm ", [&player](const std::vector& params) { setPreferredDRMCommand(player, params); })); + commands.emplace("initializesourceforplayer", Command("initializesourceforplayer", "Initialize source. Usage: initializesourceforplayer ", [&player](const std::vector& params) { initializeSourceForPlayerCommand(player, params); })); + commands.emplace("configurepipeline", Command("configurepipeline", "Configure pipeline. Usage: configurepipeline ", [&player](const std::vector& params) { configurePipelineCommand(player, params); })); + commands.emplace("setpauseonstart", Command("setpauseonstart", "Enable/disable pause on start playback. Usage: setpauseonstart [true|false]", [&player](const std::vector& params) { setPauseOnStartCommand(player, params); })); + commands.emplace("setplaybackrate", Command("setplaybackrate", "Set playback rate. Usage: setplaybackrate ", [&player](const std::vector& params) { setPlayBackRateCommand(player, params); })); + commands.emplace("setaudiovolume", Command("setaudiovolume", "Set audio volume. Usage: setaudiovolume ", [&player](const std::vector& params) { setAudioVolumeCommand(player, params); })); + commands.emplace("setupstream", Command("setupstream", "Setup stream. Usage: setupstream ", [&player](const std::vector& params) { setupStreamCommand(player, params); })); + commands.emplace("pause", Command("pause", "Pause the pipeline. Usage: pause [pause(bool)] [forceStop(bool)]", [&player](const std::vector& params) { pauseCommand(player, params); })); + commands.emplace("resumeinjector", Command("resumeinjector", "Resume injector.", [&player](const std::vector& params) { resumeInjectorCommand(player, params); })); + commands.emplace("stop", Command("stop", "Stop playback.", [&player](const std::vector& params) { stopCommand(player, params); })); + commands.emplace("flush", Command("flush", "Flush pipeline.", [&player](const std::vector& params) { flushCommand(player, params); })); + commands.emplace("flushtrack", Command("flushtrack", "Flush track. Usage: flushtrack ", [&player](const std::vector& params) { flushTrackCommand(player, params); })); + commands.emplace("ispipelinepaused", Command("ispipelinepaused", "Check if pipeline is paused.", [&player](const std::vector& params) { isPipelinePausedCommand(player, params); })); + commands.emplace("enablependingplaystate", Command("enablependingplaystate", "Enable pending play state. Usage: enablependingplaystate ", [&player](const std::vector& params) { enablePendingPlayStateCommand(player, params); })); + commands.emplace("setpendingseek", Command("setpendingseek", "Set pending seek. Usage: setpendingseek ", [&player](const std::vector& params) { setPendingSeekCommand(player, params); })); + commands.emplace("triggerevent", Command("triggerevent", "Trigger event. Usage: triggerevent ", [&player](const std::vector& params) { triggerEventCommand(player, params); })); + commands.emplace("notifyeos", Command("notifyeos", "Notify EOS.", [&player](const std::vector& params) { notifyEOSCommand(player, params); })); + commands.emplace("notifyfirstframe", Command("notifyfirstframe", "Notify first frame. Usage: notifyfirstframe ", [&player](const std::vector& params) { notifyFirstFrameCommand(player, params); })); + commands.emplace("endofstreamreached", Command("endofstreamreached", "End of stream reached. Usage: endofstreamreached ", [&player](const std::vector& params) { endOfStreamReachedCommand(player, params); })); + commands.emplace("sendhelper", Command("sendhelper", "Send helper event (not available in CLI).", [&player](const std::vector& params) { sendHelperCommand(player, params); })); + commands.emplace("waitforsourcesetup", Command("waitforsourcesetup", "Wait for source setup. Usage: waitforsourcesetup ", [&player](const std::vector& params) { waitForSourceSetupCommand(player, params); })); + commands.emplace("timeradd", Command("timeradd", "Add timer (not available in CLI).", [&player](const std::vector& params) { timerAddCommand(player, params); })); + commands.emplace("timerremove", Command("timerremove", "Remove timer. Usage: timerremove ", [&player](const std::vector& params) { timerRemoveCommand(player, params); })); + commands.emplace("timerisrunning", Command("timerisrunning", "Check if timer is running. Usage: timerisrunning ", [&player](const std::vector& params) { timerIsRunningCommand(player, params); })); +// commands.emplace("dumpdiagnostics", Command("dumpdiagnostics", "Dump diagnostics.", [&player](const std::vector& params) { dumpDiagnosticsCommand(player, params); })); + commands.emplace("enablegstdebuglogging", Command("enablegstdebuglogging", "Enable GStreamer debug logging. Usage: enablegstdebuglogging ", [&player](const std::vector& params) { enableGstDebugLoggingCommand(player, params); })); + commands.emplace("initializeplayergstreamerplugins", Command("initializeplayergstreamerplugins", "Initialize Player GStreamer plugins.", [&player](const std::vector& params) { initializePlayerGstreamerPluginsCommand(player, params); })); + commands.emplace("teardownstream", Command("teardownstream", "Teardown stream. Usage: teardownstream ", [&player](const std::vector& params) { tearDownStreamCommand(player, params); })); + commands.emplace("destroypipeline", Command("destroypipeline", "Destroy pipeline.", [&player](const std::vector& params) { destroyPipelineCommand(player, params); })); + commands.emplace("removeprobes", Command("removeprobes", "Remove probes.", [&player](const std::vector& params) { removeProbesCommand(player, params); })); + commands.emplace("clearprotectionevent", Command("clearprotectionevent", "Clear protection event.", [&player](const std::vector& params) { clearProtectionEventCommand(player, params); })); + commands.emplace("setvideorectangle", Command("setvideorectangle", "Usage: setvideorectangle ", [&](const std::vector& params) { setVideoRectangle(player, params); })); + + return commands; +} diff --git a/test/pi-cli/commandProcessing.h b/test/pi-cli/commandProcessing.h new file mode 100644 index 00000000..6cad3cc2 --- /dev/null +++ b/test/pi-cli/commandProcessing.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include "InterfacePlayerRDK.h" + +class Command { +public: + std::string matchValue; + std::string helpText; + std::function&)> action; + + Command(const std::string& value, const std::string& help, std::function&)>&& func) + : matchValue(value), helpText(help), action(std::move(func)) {} +}; + +class CommandExecutor { +private: + std::queue&)>, std::vector>> commandQueue; + std::condition_variable cv; + std::mutex queueMutex; + bool stopThread = false; + std::thread workerThread; + + void threadFunction(); + +public: + CommandExecutor(); + ~CommandExecutor(); + void addCommand(const std::function&)>& command, const std::vector& params); + void stop(); +}; + +void displayHelp(const std::map& commands); + +// Command wrappers for every InterfacePlayerRDK command we want to expose +void createPipelineCommand(InterfacePlayerRDK& player, const std::vector& params); +void setPlayerNameCommand(InterfacePlayerRDK& player, const std::vector& params); +void setPreferredDRMCommand(InterfacePlayerRDK& player, const std::vector& params); +void initializeSourceForPlayerCommand(InterfacePlayerRDK& player, const std::vector& params); +void configurePipelineCommand(InterfacePlayerRDK& player, const std::vector& params); +void setPauseOnStartCommand(InterfacePlayerRDK& player, const std::vector& params); +void setPlayBackRateCommand(InterfacePlayerRDK& player, const std::vector& params); +void setAudioVolumeCommand(InterfacePlayerRDK& player, const std::vector& params); +void setupStreamCommand(InterfacePlayerRDK& player, const std::vector& params); +void pauseCommand(InterfacePlayerRDK& player, const std::vector& params); +void resumeInjectorCommand(InterfacePlayerRDK& player, const std::vector& params); +void stopCommand(InterfacePlayerRDK& player, const std::vector& params); +void flushCommand(InterfacePlayerRDK& player, const std::vector& params); +void flushTrackCommand(InterfacePlayerRDK& player, const std::vector& params); +void isPipelinePausedCommand(InterfacePlayerRDK& player, const std::vector& params); +void enablePendingPlayStateCommand(InterfacePlayerRDK& player, const std::vector& params); +void setPendingSeekCommand(InterfacePlayerRDK& player, const std::vector& params); +void triggerEventCommand(InterfacePlayerRDK& player, const std::vector& params); +void notifyEOSCommand(InterfacePlayerRDK& player, const std::vector& params); +void notifyFirstFrameCommand(InterfacePlayerRDK& player, const std::vector& params); +void endOfStreamReachedCommand(InterfacePlayerRDK& player, const std::vector& params); +void sendHelperCommand(InterfacePlayerRDK& player, const std::vector& params); +void waitForSourceSetupCommand(InterfacePlayerRDK& player, const std::vector& params); +void timerAddCommand(InterfacePlayerRDK& player, const std::vector& params); +void timerRemoveCommand(InterfacePlayerRDK& player, const std::vector& params); +void timerIsRunningCommand(InterfacePlayerRDK& player, const std::vector& params); +void dumpDiagnosticsCommand(InterfacePlayerRDK& player, const std::vector& params); +void enableGstDebugLoggingCommand(InterfacePlayerRDK& player, const std::vector& params); +void initializePlayerGstreamerPluginsCommand(InterfacePlayerRDK& player, const std::vector& params); +void tearDownStreamCommand(InterfacePlayerRDK& player, const std::vector& params); +void destroyPipelineCommand(InterfacePlayerRDK& player, const std::vector& params); +void removeProbesCommand(InterfacePlayerRDK& player, const std::vector& params); +void clearProtectionEventCommand(InterfacePlayerRDK& player, const std::vector& params); + +// Register all commands +std::map initializeCommands(CommandExecutor& executor, InterfacePlayerRDK& player); diff --git a/test/pi-cli/main.cpp b/test/pi-cli/main.cpp new file mode 100644 index 00000000..813b6690 --- /dev/null +++ b/test/pi-cli/main.cpp @@ -0,0 +1,133 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "InterfacePlayerRDK.h" +#include "commandProcessing.h" + +// Helper function to convert a string to lowercase +std::string toLowerCase(const std::string& str) { + std::string lowerStr = str; + std::transform(lowerStr.begin(), lowerStr.end(), lowerStr.begin(), ::tolower); + return lowerStr; +} + +// Helper function to parse user input into command and parameters +std::pair> parseInput(const std::string& input) { + std::istringstream stream(input); + std::string command; + std::vector params; + + stream >> command; // Extract the command + command = toLowerCase(command); // Convert the command to lowercase + + std::string param; + while (stream >> param) { + params.push_back(param); // Extract parameters + } + + return { command, params }; +} + +// Globals for readline tab completion +std::map* globalCommands = nullptr; + +// Function for readline tab completion +char* commandCompletion(const char* text, int state) { + static std::vector matches; + static size_t matchIndex = 0; + + if (state == 0) { + matches.clear(); + matchIndex = 0; + + std::string input(text); + for (const auto& [key, command] : *globalCommands) { + if (key.find(input) == 0) { + matches.push_back(key); + } + } + } + + if (matchIndex < matches.size()) { + return strdup(matches[matchIndex++].c_str()); + } + + return nullptr; +} + +// Function to initialize readline completion +void initializeReadline() { + rl_attempted_completion_function = [](const char* text, int start, int end) -> char** { + if (start == 0) { + return rl_completion_matches(text, commandCompletion); + } + return nullptr; + }; +} + +int main(int argc, char *argv[]) { + // Create the command executor + CommandExecutor executor; + InterfacePlayerRDK player; + + // Install required CB functions so we don't crash + player.TearDownCallback([](bool, int){ /* no-op */ }); + player.RegisterNeedDataCb([](int){ /* no-op */ }); + + // Initialize commands + std::map commands = initializeCommands(executor, player); + globalCommands = &commands; // Set global pointer for tab completion + + // Initialize readline + initializeReadline(); + + // Main loop for user input + while (true) { + // Print a prompt to the user + char* input = readline("Please enter a command (type 'help' for available commands): "); + + // If user input is error/EOF, exit + if (!input) { + break; + } + + // If empty string, don't process + if (strlen(input) == 0) { + continue; + } + + // Add input to history + add_history(input); + + // Parse the input into command and parameters + auto [commandInput, params] = parseInput(input); + + // Free readline input buffer + free(input); + + // Check if the command exists + auto it = commands.find(commandInput); + if (it != commands.end()) { + // Add the command's action to the executor with parameters + executor.addCommand(it->second.action, params); + } else { + // Command not found + std::cout << "Unknown command '" << commandInput << "'. Type 'help' to see available commands.\n"; + } + } + // Cleanly stop the command processing thread. + executor.stop(); + + return 0; +}