diff --git a/CMakeLists.txt b/CMakeLists.txt index 86e8ed66..4258c9d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -367,6 +367,11 @@ if (CMAKE_GST_SUBTEC_ENABLED) set(LIBPLAYERGSTINTERFACE_DEFINES "${LIBPLAYERGSTINTERFACE_DEFINES} -DGST_SUBTEC_ENABLED") endif() +if (CMAKE_PLAYER_TELEMETRY_SUPPORT) + message("CMAKE_PLAYER_TELEMETRY_SUPPORT set") + set(LIBPLAYERGSTINTERFACE_DEFINES "${LIBPLAYERGSTINTERFACE_DEFINES} -DPLAYER_TELEMETRY_SUPPORT") +endif() + if (CMAKE_SUBTITLE_SUPPORT) message("CMAKE_SUBTITLE_SUPPORT set") set(LIBPLAYERGSTINTERFACE_DEFINES "${LIBPLAYERGSTINTERFACE_DEFINES} -DSUBTITLE_SUPPORTED") diff --git a/GstHandlerControl.cpp b/GstHandlerControl.cpp index 11f15198..163c81f1 100644 --- a/GstHandlerControl.cpp +++ b/GstHandlerControl.cpp @@ -19,6 +19,8 @@ #include "GstHandlerControl.h" #include "PlayerLogManager.h" +#include "TelemetryMarkers.h" +#include "PlayerTelemetry.h" #include #include @@ -75,6 +77,12 @@ bool GstHandlerControl::waitForDone(int MaximumDelayMilliseconds, std::string na { MW_LOG_ERR("GstPlayer: %d instance%s of %s running", mInstanceCount, mInstanceCount?"s":"", name.c_str()); + { + TelemetryPayload handlerPayload; + handlerPayload.add("handler", name); + handlerPayload.add("count", mInstanceCount); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_HANDLER_TIMEOUT, handlerPayload); + } return false; } else diff --git a/InterfacePlayerRDK.cpp b/InterfacePlayerRDK.cpp index a7d138b2..911e0561 100644 --- a/InterfacePlayerRDK.cpp +++ b/InterfacePlayerRDK.cpp @@ -35,6 +35,8 @@ #include "player-xternal-stats.h" #endif #include "PlayerUtils.h" +#include "TelemetryMarkers.h" +#include "PlayerTelemetry.h" #define DEFAULT_BUFFERING_TO_MS 10 /**< TimeOut interval to check buffer fullness */ #define DEFAULT_BUFFERING_MAX_MS (1000) /**< max buffering time */ @@ -99,11 +101,13 @@ mSourceSetupCV(), mScheduler(), callbackMap(), setupStreamCallbackMap(), mDrmSys pthread_mutex_init(&interfacePlayerPriv->gstPrivateContext->stream[i].sourceLock, NULL); // start Scheduler Worker for task handling mScheduler.StartScheduler(); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_INITIALIZED); } /* InterfacePlayerRDK destructor*/ InterfacePlayerRDK::~InterfacePlayerRDK() { + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_SHUTDOWN); DestroyPipeline(); if (mDrmSystem) { @@ -409,6 +413,12 @@ void InterfacePlayerRDK::ConfigurePipeline(int format, int audioFormat, int subF if (!configureStream[i] && bESChangeStatus && (eGST_MEDIATYPE_AUDIO == i)) { MW_LOG_MIL("AudioType Changed. Force configure pipeline"); + { + TelemetryPayload trackSwitchedPayload; + trackSwitchedPayload.add("trackType", "audio"); + trackSwitchedPayload.add("trackId", trackId); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_TRACK_SWITCHED, trackSwitchedPayload); + } configureStream[i] = true; } @@ -478,6 +488,13 @@ void InterfacePlayerRDK::ConfigurePipeline(int format, int audioFormat, int subF if (SetStateWithWarnings(interfacePlayerPriv->gstPrivateContext->pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) { MW_LOG_ERR("InterfacePlayerRDK: GST_STATE_PAUSED failed"); + { + TelemetryPayload pauseOnStartPayload; + pauseOnStartPayload.add("fromState", "NULL"); + pauseOnStartPayload.add("toState", "PAUSED"); + pauseOnStartPayload.add("context", "ConfigurePipeline_pauseOnStart"); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_PIPELINE_STATE_CHANGE_FAILURE, pauseOnStartPayload); + } } } /* If buffering is enabled, set the pipeline in Paused state, once sufficient content has been buffered the pipeline will be set to GST_STATE_PLAYING */ @@ -491,6 +508,17 @@ void InterfacePlayerRDK::ConfigurePipeline(int format, int audioFormat, int subF if (SetStateWithWarnings(interfacePlayerPriv->gstPrivateContext->pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) { MW_LOG_ERR("InterfacePlayerRDK_Configure GST_STATE_PAUSED failed"); + { + TelemetryPayload bufferingPausePayload; + bufferingPausePayload.add("fromState", "NULL"); + bufferingPausePayload.add("toState", "PAUSED"); + bufferingPausePayload.add("context", "ConfigurePipeline_buffering"); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_PIPELINE_STATE_CHANGE_FAILURE, bufferingPausePayload); + } + } + else + { + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_BUFFERING_STARTED); } interfacePlayerPriv->gstPrivateContext->pendingPlayState = false; interfacePlayerPriv->gstPrivateContext->paused = false; @@ -501,6 +529,17 @@ void InterfacePlayerRDK::ConfigurePipeline(int format, int audioFormat, int subF if (SetStateWithWarnings(interfacePlayerPriv->gstPrivateContext->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { MW_LOG_ERR("InterfacePlayerRDK: GST_STATE_PLAYING failed"); + { + TelemetryPayload playingFailPayload; + playingFailPayload.add("fromState", "PAUSED"); + playingFailPayload.add("toState", "PLAYING"); + playingFailPayload.add("context", "ConfigurePipeline"); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_PIPELINE_STATE_CHANGE_FAILURE, playingFailPayload); + } + } + else + { + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_PLAYBACK_STARTED); } interfacePlayerPriv->gstPrivateContext->pendingPlayState = false; interfacePlayerPriv->gstPrivateContext->paused = false; @@ -1390,6 +1429,7 @@ void InterfacePlayerRDK::TearDownStream(int type) void InterfacePlayerRDK::Stop(bool keepLastFrame) { std::lock_guard lock(mMutex); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_PLAYBACK_STOPPED); /* make the execution of this function more deterministic and * reduce scope for potential pipeline lockups*/ @@ -1656,6 +1696,12 @@ bool InterfacePlayerRDK::Flush(double position, int rate, bool shouldTearDown, b */ ResetGstEvents(); MW_LOG_INFO("InterfacePlayerRDK: Pipeline flush seek - start = %f rate = %d", position, rate); + { + TelemetryPayload seekStartedPayload; + seekStartedPayload.add("position", position); + seekStartedPayload.add("rate", rate); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_SEEK_STARTED, seekStartedPayload); + } double playRate = 1.0; if (eGST_MEDIAFORMAT_PROGRESSIVE == static_cast(m_gstConfigParam->media)) { @@ -1678,6 +1724,13 @@ bool InterfacePlayerRDK::Flush(double position, int rate, bool shouldTearDown, b //Save the updated seek position SetSeekPosition(position); } + else + { + TelemetryPayload seekCompletedPayload; + seekCompletedPayload.add("position", position); + seekCompletedPayload.add("rate", rate); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_SEEK_COMPLETED, seekCompletedPayload); + } if ((interfacePlayerPriv->gstPrivateContext->usingRialtoSink) && (interfacePlayerPriv->gstPrivateContext->audio_sink) && @@ -2891,6 +2944,7 @@ bool InterfacePlayerRDK::StopBuffering(bool forceStop, bool &isPlaying) if (current == GST_STATE_PLAYING) { sendEndEvent = true; + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_BUFFERING_ENDED); } } } @@ -3342,11 +3396,31 @@ bool InterfacePlayerRDK::Pause(bool pause , bool forceStopGstreamerPreBuffering) if (nextState != validateStateWithMsTimeout(this,nextState, 100)) { MW_LOG_ERR("InterfacePlayerRDK_Pause - validateStateWithMsTimeout - FAILED GstState %d", nextState); + { + TelemetryPayload pauseTimeoutPayload; + pauseTimeoutPayload.add("toState", pause ? "PAUSED" : "PLAYING"); + pauseTimeoutPayload.add("context", "Pause_timeout"); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_PIPELINE_STATE_CHANGE_FAILURE, pauseTimeoutPayload); + } + } + else + { + PlayerTelemetry::sendEvent(pause ? TELEMETRY_EVENT_PLAYBACK_PAUSED : TELEMETRY_EVENT_PLAYBACK_RESUMED); } } else if (GST_STATE_CHANGE_SUCCESS != rc) { MW_LOG_ERR("InterfacePlayerRDK_Pause - gst_element_set_state - FAILED rc %d", rc); + { + TelemetryPayload pauseFailPayload; + pauseFailPayload.add("toState", pause ? "PAUSED" : "PLAYING"); + pauseFailPayload.add("context", "Pause_failure"); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_PIPELINE_STATE_CHANGE_FAILURE, pauseFailPayload); + } + } + else + { + PlayerTelemetry::sendEvent(pause ? TELEMETRY_EVENT_PLAYBACK_PAUSED : TELEMETRY_EVENT_PLAYBACK_RESUMED); } interfacePlayerPriv->gstPrivateContext->buffering_target_state = nextState; @@ -4107,6 +4181,12 @@ static void GstPlayer_OnGstDecodeErrorCb(GstElement* object, guint arg0, gpointe pInterfacePlayerRDK->OnGstDecodeErrorCb(privatePlayer->gstPrivateContext->decodeErrorCBCount); privatePlayer->gstPrivateContext->decodeErrorMsgTimeMS = NOW_STEADY_TS_MS; MW_LOG_ERR("Got Decode Error message from %s total_cb=%d timeMs=%d", GST_ELEMENT_NAME(object), privatePlayer->gstPrivateContext->decodeErrorCBCount, GST_MIN_DECODE_ERROR_INTERVAL); + { + TelemetryPayload decodeErrPayload; + decodeErrPayload.add("element", GST_ELEMENT_NAME(object) ? GST_ELEMENT_NAME(object) : "unknown"); + decodeErrPayload.add("count", privatePlayer->gstPrivateContext->decodeErrorCBCount); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_DECODE_ERROR, decodeErrPayload); + } privatePlayer->gstPrivateContext->decodeErrorCBCount = 0; #ifdef USE_EXTERNAL_STATS INC_DECODE_ERROR(); // Increment the decoder error for low level AV metric @@ -4151,6 +4231,22 @@ static gboolean bus_message(GstBus * bus, GstMessage * msg, InterfacePlayerRDK * } pInterfacePlayerRDK->busMessageCallback(std::move(busEvent)); MW_LOG_ERR("Debug Info: %s\n", (dbg_info) ? dbg_info : "none"); + if (error->domain == GST_RESOURCE_ERROR) + { + TelemetryPayload networkErrPayload; + networkErrPayload.add("element", GST_OBJECT_NAME(msg->src) ? GST_OBJECT_NAME(msg->src) : "unknown"); + networkErrPayload.add("errorCode", error->code); + networkErrPayload.add("message", error->message ? error->message : ""); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_NETWORK_ERROR, networkErrPayload); + } + else + { + TelemetryPayload errPayload; + errPayload.add("element", GST_OBJECT_NAME(msg->src) ? GST_OBJECT_NAME(msg->src) : "unknown"); + errPayload.add("message", error->message ? error->message : ""); + errPayload.add("debugInfo", dbg_info ? dbg_info : "none"); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_ERROR, errPayload); + } g_clear_error(&error); g_free(dbg_info); break; @@ -4354,6 +4450,11 @@ static gboolean bus_message(GstBus * bus, GstMessage * msg, InterfacePlayerRDK * busEvent.dbg_info = "N/A"; pInterfacePlayerRDK->busMessageCallback(std::move(busEvent)); MW_LOG_MIL("GST_MESSAGE_EOS"); + { + TelemetryPayload eosPayload; + eosPayload.add("element", GST_OBJECT_NAME(msg->src) ? GST_OBJECT_NAME(msg->src) : "unknown"); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_EOS_DETECTED, eosPayload); + } pInterfacePlayerRDK->NotifyEOS(); break; @@ -4936,6 +5037,7 @@ void InterfacePlayerRDK::NotifyEOS() interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskPending = true; // eosSignalled is reset once the async task is completed either in Configure/Flush/ResetEOSSignalled, so set the flag before scheduling the task interfacePlayerPriv->gstPrivateContext->eosSignalled = true; + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_PLAYBACK_COMPLETED); interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskId = mScheduler.ScheduleTask(PlayerAsyncTaskObj(IdleCallbackOnEOS, (void *)this, "IdleCallbackOnEOS")); if (interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskId == PLAYER_TASK_ID_INVALID && true == interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskPending) { diff --git a/PlayerTelemetry.h b/PlayerTelemetry.h new file mode 100644 index 00000000..b8eef291 --- /dev/null +++ b/PlayerTelemetry.h @@ -0,0 +1,183 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +/** + * @file PlayerTelemetry.h + * @brief Lightweight telemetry emission utility for the middleware player interface. + * + * Provides a TelemetryPayload builder and PlayerTelemetry::sendEvent() overloads. + * TelemetryPayload::add() accepts string, integer, and floating-point values so + * call sites do not need manual std::to_string() conversions. + * + * Usage: + * @code + * TelemetryPayload payload; + * payload.add("systemId", systemId); + * payload.add("retryCount", retryCount); // int — no std::to_string needed + * PlayerTelemetry::sendEvent(TELEMETRY_EVENT_DRM_HELPER_NOT_FOUND, payload); + * @endcode + * + * When PLAYER_TELEMETRY_SUPPORT is NOT defined at compile time every call is + * compiled away to a no-op — zero overhead, no dependency on or logging + * headers in non-telemetry builds. Enable telemetry by passing + * -DPLAYER_TELEMETRY_SUPPORT (or setting CMAKE_PLAYER_TELEMETRY_SUPPORT) at + * build time. + */ + +#ifdef PLAYER_TELEMETRY_SUPPORT + +#include +#include +#include "PlayerLogManager.h" + +/** + * @class TelemetryPayload + * @brief Key/value container for telemetry event context data. + * + * Accepts string, integer, and floating-point values via the add() method, + * converting numeric types to their string representation automatically so + * that call sites do not need explicit std::to_string() calls. + */ +class TelemetryPayload +{ +public: + /** @brief Add a string field. */ + TelemetryPayload& add(const std::string& key, const std::string& value) + { + m_fields[key] = value; + return *this; + } + + /** @brief Add an integer field (converted to decimal string). */ + TelemetryPayload& add(const std::string& key, int value) + { + m_fields[key] = std::to_string(value); + return *this; + } + + /** @brief Add a long integer field (converted to decimal string). */ + TelemetryPayload& add(const std::string& key, long value) + { + m_fields[key] = std::to_string(value); + return *this; + } + + /** @brief Add a float field (converted to decimal string). */ + TelemetryPayload& add(const std::string& key, float value) + { + m_fields[key] = std::to_string(value); + return *this; + } + + /** @brief Add a double field (converted to decimal string). */ + TelemetryPayload& add(const std::string& key, double value) + { + m_fields[key] = std::to_string(value); + return *this; + } + + /** @brief Read-only access to the internal map for sendEvent(). */ + const std::map& fields() const { return m_fields; } + +private: + std::map m_fields; +}; + +/** + * @class PlayerTelemetry + * @brief Static helper for emitting named telemetry events with optional payload data. + * + * Active implementation compiled when PLAYER_TELEMETRY_SUPPORT is defined. + */ +class PlayerTelemetry +{ +public: + /** + * @brief Emit a telemetry event with no additional payload. + * @param[in] eventName One of the TELEMETRY_EVENT_* markers from TelemetryMarkers.h. + */ + static void sendEvent(const std::string& eventName) + { + MW_LOG_MIL("[TELEMETRY] event=%s", eventName.c_str()); + } + + /** + * @brief Emit a telemetry event with a structured key/value payload. + * @param[in] eventName One of the TELEMETRY_EVENT_* markers from TelemetryMarkers.h. + * @param[in] payload Additional context data built with TelemetryPayload::add(). + */ + static void sendEvent(const std::string& eventName, const TelemetryPayload& payload) + { + std::string fields; + for (const auto& kv : payload.fields()) + { + if (!fields.empty()) + { + fields += ' '; + } + fields += kv.first + '=' + kv.second; + } + MW_LOG_MIL("[TELEMETRY] event=%s %s", eventName.c_str(), fields.c_str()); + } + +private: + PlayerTelemetry() = delete; +}; + +#else /* PLAYER_TELEMETRY_SUPPORT not defined */ + +#include + +/** + * @class TelemetryPayload + * @brief No-op stub compiled when PLAYER_TELEMETRY_SUPPORT is not defined. + * + * All add() methods are empty inline functions eliminated by the compiler. + * Call sites compile without change and require no #ifdef guards. + */ +class TelemetryPayload +{ +public: + TelemetryPayload& add(const std::string& /*key*/, const std::string& /*value*/) { return *this; } + TelemetryPayload& add(const std::string& /*key*/, int /*value*/) { return *this; } + TelemetryPayload& add(const std::string& /*key*/, long /*value*/) { return *this; } + TelemetryPayload& add(const std::string& /*key*/, float /*value*/) { return *this; } + TelemetryPayload& add(const std::string& /*key*/, double /*value*/) { return *this; } +}; + +/** + * @class PlayerTelemetry + * @brief No-op stub compiled when PLAYER_TELEMETRY_SUPPORT is not defined. + * + * All methods are empty inline functions so the compiler eliminates them + * entirely. Call sites require no #ifdef guards. + */ +class PlayerTelemetry +{ +public: + static void sendEvent(const std::string& /*eventName*/) {} + static void sendEvent(const std::string& /*eventName*/, const TelemetryPayload& /*payload*/) {} + +private: + PlayerTelemetry() = delete; +}; + +#endif /* PLAYER_TELEMETRY_SUPPORT */ diff --git a/TelemetryMarkers.h b/TelemetryMarkers.h new file mode 100644 index 00000000..7f893d1a --- /dev/null +++ b/TelemetryMarkers.h @@ -0,0 +1,72 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +/** + * @file TelemetryMarkers.h + * @brief Telemetry event marker definitions for the middleware player interface. + * + * Each macro defines the string key used when emitting a telemetry event via + * PlayerTelemetry::sendEvent(). Markers are grouped by category to match the + * lifecycle of a GStreamer-based playback session. + */ + +/* ── Playback events ──────────────────────────────────────────────────────── */ +#define TELEMETRY_EVENT_PLAYBACK_STARTED "PLAYBACK_STARTED" /**< Pipeline transitions to PLAYING */ +#define TELEMETRY_EVENT_PLAYBACK_PAUSED "PLAYBACK_PAUSED" /**< Pipeline transitions to PAUSED on user request */ +#define TELEMETRY_EVENT_PLAYBACK_RESUMED "PLAYBACK_RESUMED" /**< Pipeline transitions back to PLAYING from PAUSED */ +#define TELEMETRY_EVENT_PLAYBACK_STOPPED "PLAYBACK_STOPPED" /**< Stop() tears down the pipeline */ +#define TELEMETRY_EVENT_PLAYBACK_COMPLETED "PLAYBACK_COMPLETED" /**< End-of-stream reached and EOS callback scheduled */ + +/* ── Media / buffering events ─────────────────────────────────────────────── */ +#define TELEMETRY_EVENT_BUFFERING_STARTED "BUFFERING_STARTED" /**< Pre-roll buffering begins */ +#define TELEMETRY_EVENT_BUFFERING_ENDED "BUFFERING_ENDED" /**< Sufficient frames buffered; pipeline unpaused */ +#define TELEMETRY_EVENT_SEEK_STARTED "SEEK_STARTED" /**< Flush seek requested */ +#define TELEMETRY_EVENT_SEEK_COMPLETED "SEEK_COMPLETED" /**< gst_element_seek() succeeded */ + +/* ── Error events ─────────────────────────────────────────────────────────── */ +#define TELEMETRY_EVENT_ERROR "ERROR" /**< Generic GStreamer pipeline error (GST_MESSAGE_ERROR) */ +#define TELEMETRY_EVENT_DECODE_ERROR "DECODE_ERROR" /**< Decoder reported a decode-error-callback */ +#define TELEMETRY_EVENT_NETWORK_ERROR "NETWORK_ERROR" /**< Resource/stream error that indicates a network fault */ + +/* ── Pipeline state change failure ───────────────────────────────────────── */ +#define TELEMETRY_EVENT_PIPELINE_STATE_CHANGE_FAILURE "MW_PIPELINE_STATE_CHANGE_FAILURE" /**< gst_element_set_state() returned GST_STATE_CHANGE_FAILURE */ + +/* ── Lifecycle events ─────────────────────────────────────────────────────── */ +#define TELEMETRY_EVENT_INITIALIZED "INTERFACE_INITIALIZED" /**< InterfacePlayerRDK constructor completed */ +#define TELEMETRY_EVENT_SHUTDOWN "INTERFACE_SHUTDOWN" /**< InterfacePlayerRDK destructor entered */ + +/* ── Miscellaneous events ─────────────────────────────────────────────────── */ +#define TELEMETRY_EVENT_EOS_DETECTED "END_OF_STREAM_DETECTED" /**< GST_MESSAGE_EOS received on the pipeline bus */ +#define TELEMETRY_EVENT_TRACK_SWITCHED "TRACK_SWITCHED" /**< Mid-stream audio/video track change */ + +/* ── DRM / Content protection events ─────────────────────────────────────── */ +#define TELEMETRY_EVENT_DRM_HELPER_NOT_FOUND "DRM_HELPER_NOT_FOUND" /**< No DRM helper found for the content protection system */ +#define TELEMETRY_EVENT_DRM_PSSH_PARSE_FAILED "DRM_PSSH_PARSE_FAILED" /**< Failed to parse PSSH data from DRM init data */ +#define TELEMETRY_EVENT_DRM_SESSION_CREATE_FAILED "DRM_SESSION_CREATE_FAILED" /**< DRM session creation returned null / invalid params */ +#define TELEMETRY_EVENT_DRM_SESSION_INIT_FAILED "DRM_SESSION_INIT_FAILED" /**< DRM session OCDM initialisation failed */ +#define TELEMETRY_EVENT_OCDM_SYSTEM_CREATE_FAILED "OCDM_SYSTEM_CREATE_FAILED" /**< opencdm_create_system() returned null */ +#define TELEMETRY_EVENT_OCDM_SESSION_CREATE_FAILED "OCDM_SESSION_CREATE_FAILED" /**< opencdm_construct_session() returned an error */ +#define TELEMETRY_EVENT_HDCP_PROTECTION_FAILURE "HDCP_PROTECTION_FAILURE" /**< HDCP output protection failure detected */ +#define TELEMETRY_EVENT_HDCP_COMPLIANCE_FAILURE "HDCP_COMPLIANCE_FAILURE" /**< HDCP compliance check failure (2.2 vs 1.4) */ +#define TELEMETRY_EVENT_DECRYPT_FAILURE "DECRYPT_FAILURE" /**< Decryption failure threshold exceeded */ + +/* ── Handler / concurrency events ────────────────────────────────────────── */ +#define TELEMETRY_EVENT_HANDLER_TIMEOUT "HANDLER_TIMEOUT" /**< A GStreamer bus/callback handler did not complete before timeout */ diff --git a/drm/DrmSessionManager.cpp b/drm/DrmSessionManager.cpp index 8b9a197e..6773fcc5 100755 --- a/drm/DrmSessionManager.cpp +++ b/drm/DrmSessionManager.cpp @@ -30,6 +30,8 @@ #include #include "PlayerUtils.h" #include "ContentSecurityManager.h" +#include "TelemetryMarkers.h" +#include "PlayerTelemetry.h" #define DRM_METADATA_TAG_START "" #define DRM_METADATA_TAG_END "" #define SESSION_TOKEN_URL "http://localhost:50050/authService/getSessionToken" @@ -412,6 +414,11 @@ DrmSession * DrmSessionManager::createDrmSession( int& responseCode, if (!DrmHelperEngine::getInstance().hasDRM(drmInfo)) { MW_LOG_ERR(" Failed to locate DRM helper"); + { + TelemetryPayload drmHelperPayload; + drmHelperPayload.add("systemId", systemId); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_DRM_HELPER_NOT_FOUND, drmHelperPayload); + } } else { @@ -426,6 +433,12 @@ DrmSession * DrmSessionManager::createDrmSession( int& responseCode, if (!drmHelper->parsePssh(initDataPtr, initDataLen)) { MW_LOG_ERR(" Failed to Parse PSSH from the DRM InitData"); + { + TelemetryPayload psshPayload; + psshPayload.add("systemId", systemId); + psshPayload.add("initDataLen", static_cast(initDataLen)); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_DRM_PSSH_PARSE_FAILED, psshPayload); + } err = MW_CORRUPT_DRM_METADATA; } else @@ -446,6 +459,11 @@ DrmSession* DrmSessionManager::createDrmSession(int &responseCode, int &err, std /* This should never happen, since the caller should have already ensure the provided DRMInfo is supported using hasDRM */ MW_LOG_ERR(" Failed to create DRM Session invalid parameters "); + { + TelemetryPayload drmSessionPayload; + drmSessionPayload.add("reason", "invalid_parameters"); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_DRM_SESSION_CREATE_FAILED, drmSessionPayload); + } return nullptr; } @@ -928,15 +946,33 @@ KeyState DrmSessionManager::initializeDrmSession(std::shared_ptr drmH { MW_LOG_ERR("DRM session ID is empty: Key State %d ", code); err = MW_DRM_SESSIONID_EMPTY; + { + TelemetryPayload emptySessionPayload; + emptySessionPayload.add("reason", "empty_session_id"); + emptySessionPayload.add("keyState", static_cast(code)); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_DRM_SESSION_INIT_FAILED, emptySessionPayload); + } } else if (code == KEY_ERROR_SESSION_CREATE_FAILED) { MW_LOG_ERR("OCDM session construction failed: Key State %d ", code); err = MW_DRM_SESSION_CREATE_FAILED; + { + TelemetryPayload ocdmCreateFailedPayload; + ocdmCreateFailedPayload.add("reason", "ocdm_session_create_failed"); + ocdmCreateFailedPayload.add("keyState", static_cast(code)); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_DRM_SESSION_INIT_FAILED, ocdmCreateFailedPayload); + } } else { err= MW_DRM_DATA_BIND_FAILED; + { + TelemetryPayload dataBindPayload; + dataBindPayload.add("reason", "data_bind_failed"); + dataBindPayload.add("keyState", static_cast(code)); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_DRM_SESSION_INIT_FAILED, dataBindPayload); + } } } diff --git a/drm/ocdm/opencdmsessionadapter.cpp b/drm/ocdm/opencdmsessionadapter.cpp index e45ca119..d129e455 100644 --- a/drm/ocdm/opencdmsessionadapter.cpp +++ b/drm/ocdm/opencdmsessionadapter.cpp @@ -25,6 +25,8 @@ #include "DrmHelper.h" #include "PlayerUtils.h" +#include "TelemetryMarkers.h" +#include "PlayerTelemetry.h" #include "ProcessHandler.h" #include "PlayerExternalsInterface.h" @@ -96,6 +98,11 @@ void OCDMSessionAdapter::initDRMSystem() #endif if (m_pOpenCDMSystem == nullptr) { MW_LOG_ERR("opencdm_create_system() FAILED"); + { + TelemetryPayload ocdmSystemPayload; + ocdmSystemPayload.add("keySystem", m_keySystem); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_OCDM_SYSTEM_CREATE_FAILED, ocdmSystemPayload); + } } } MW_LOG_WARN("initDRMSystem :: exit "); @@ -168,6 +175,12 @@ void OCDMSessionAdapter::generateDRMSession(const uint8_t *f_pbInitData, { MW_LOG_ERR("Error constructing OCDM session. OCDM err=0x%x", ocdmRet); m_eKeyState = KEY_ERROR_SESSION_CREATE_FAILED; + { + TelemetryPayload ocdmSessionPayload; + ocdmSessionPayload.add("keySystem", m_keySystem); + ocdmSessionPayload.add("errorCode", static_cast(ocdmRet)); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_OCDM_SESSION_CREATE_FAILED, ocdmSessionPayload); + } } } } diff --git a/gst-plugins/drm/gst/gstcdmidecryptor.cpp b/gst-plugins/drm/gst/gstcdmidecryptor.cpp index 1666b529..da82d349 100755 --- a/gst-plugins/drm/gst/gstcdmidecryptor.cpp +++ b/gst-plugins/drm/gst/gstcdmidecryptor.cpp @@ -26,6 +26,8 @@ #include #include "DrmConstants.h" #include "SocInterface.h" +#include "TelemetryMarkers.h" +#include "PlayerTelemetry.h" GST_DEBUG_CATEGORY_STATIC ( gst_cdmidecryptor_debug_category); #define GST_CAT_DEFAULT gst_cdmidecryptor_debug_category @@ -650,6 +652,11 @@ static GstFlowReturn gst_cdmidecryptor_transform_ip( if(cdmidecryptor->hdcpOpProtectionFailCount >= DECRYPT_FAILURE_THRESHOLD) { GstStructure *newmsg = gst_structure_new("HDCPProtectionFailure", "message", G_TYPE_STRING,"HDCP Output Protection Error", NULL); gst_element_post_message(reinterpret_cast(cdmidecryptor),gst_message_new_application (GST_OBJECT (cdmidecryptor), newmsg)); + { + TelemetryPayload hdcpProtPayload; + hdcpProtPayload.add("failCount", cdmidecryptor->hdcpOpProtectionFailCount); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_HDCP_PROTECTION_FAILURE, hdcpProtPayload); + } } cdmidecryptor->hdcpOpProtectionFailCount = 0; } @@ -665,10 +672,21 @@ static GstFlowReturn gst_cdmidecryptor_transform_ip( { // Failure - 2.2 vs 1.4 HDCP error = g_error_new(GST_STREAM_ERROR , GST_STREAM_ERROR_FAILED, "HDCP Compliance Check Failure"); + { + TelemetryPayload hdcpCompPayload; + hdcpCompPayload.add("failCount", cdmidecryptor->decryptFailCount); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_HDCP_COMPLIANCE_FAILURE, hdcpCompPayload); + } } else { error = g_error_new(GST_STREAM_ERROR , GST_STREAM_ERROR_FAILED, "Decrypt Error: code %d", errorCode); + { + TelemetryPayload decryptFailPayload; + decryptFailPayload.add("errorCode", errorCode); + decryptFailPayload.add("failCount", cdmidecryptor->decryptFailCount); + PlayerTelemetry::sendEvent(TELEMETRY_EVENT_DECRYPT_FAILURE, decryptFailPayload); + } } gst_element_post_message(reinterpret_cast(cdmidecryptor), gst_message_new_error (GST_OBJECT (cdmidecryptor), error, "Decrypt Failed")); g_error_free(error);