diff --git a/.github/workflows/native_rialto_build.yml b/.github/workflows/native_rialto_build.yml index 7ee8cf52a..580779f6c 100644 --- a/.github/workflows/native_rialto_build.yml +++ b/.github/workflows/native_rialto_build.yml @@ -36,25 +36,11 @@ jobs: - name: Install Dependencies run: | - sudo apt-get update - sudo apt-get install build-essential - sudo apt-get install cmake - sudo apt-get install libunwind-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-bad1.0-dev libgstreamer1.0-dev - - - name: Install protobuf - run: | - sudo apt-get install protobuf-compiler + sudo sh -x install_dependencies_for_native_build.sh - name: Build Rialto run: | - cmake . -B build -DNATIVE_BUILD=ON -DRIALTO_BUILD_TYPE="Debug" &> output_file.txt - if [ $? -eq 0 ] - then - make -C build &>> output_file.txt - else - exit 1 - fi - + sh -x build_native.sh &> output_file.txt - name: Report Build Status Success if: success() diff --git a/build_native.sh b/build_native.sh new file mode 100644 index 000000000..bcd2870c9 --- /dev/null +++ b/build_native.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2026 Sky UK +# +# 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. +# + +# Script for building the Rialto Native. + +cmake . -B build -DNATIVE_BUILD=ON -DRIALTO_BUILD_TYPE="Debug" +if [ $? -eq 0 ] +then + make -C build -j$(nproc) +else + exit 1 +fi \ No newline at end of file diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 42d7a115f..58fd49fa0 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -31,6 +31,7 @@ add_library( source/EventThread.cpp source/LinuxUtils.cpp source/Timer.cpp + source/Profiler.cpp ) set_property ( diff --git a/common/include/Profiler.h b/common/include/Profiler.h new file mode 100644 index 000000000..c45a1653c --- /dev/null +++ b/common/include/Profiler.h @@ -0,0 +1,69 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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. + */ + +#ifndef FIREBOLT_RIALTO_COMMON_PROFILER_H_ +#define FIREBOLT_RIALTO_COMMON_PROFILER_H_ + +#include "IProfiler.h" + +#include +#include +#include +#include +#include +#include + +namespace firebolt::rialto::common +{ +class ProfilerFactory : public IProfilerFactory +{ +public: + std::unique_ptr createProfiler(std::string moduleName) const override; +}; + +class Profiler final : public IProfiler +{ +public: + explicit Profiler(std::string module); + + bool isEnabled() const noexcept override; + + std::optional record(const std::string &stage) override; + std::optional record(const std::string &stage, const std::string &info) override; + + void log(const RecordId id) override; + + bool dumpToFile() const override; + std::vector getRecords() const override; + +private: + std::optional findById(RecordId id) const; + + std::string m_module; + bool m_enabled; + std::optional m_dumpFileName; + + mutable std::mutex m_mutex; + RecordId m_id{1}; + std::vector m_records; +}; + +}; // namespace firebolt::rialto::common + +#endif // FIREBOLT_RIALTO_COMMON_PROFILER_H_ diff --git a/common/interface/IProfiler.h b/common/interface/IProfiler.h new file mode 100644 index 000000000..bf03acff5 --- /dev/null +++ b/common/interface/IProfiler.h @@ -0,0 +1,136 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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. + */ + +#ifndef FIREBOLT_RIALTO_COMMON_I_PROFILER_H_ +#define FIREBOLT_RIALTO_COMMON_I_PROFILER_H_ + +#include +#include +#include +#include +#include +#include +#include + +namespace firebolt::rialto::common +{ +class IProfiler; + +/** + * @brief IProfiler factory class, returns a concrete implementation of IProfiler + */ +class IProfilerFactory +{ +public: + IProfilerFactory() = default; + virtual ~IProfilerFactory() = default; + + /** + * @brief Creates a IProfilerFactory instance. + * + * @retval the factory instance or null on error. + */ + static std::shared_ptr createFactory(); + + /** + * @brief Creates an IProfiler object. + * + * @param[in] moduleName : The name of the module + * + * @retval the new profiler instance or null on error. + */ + virtual std::unique_ptr createProfiler(std::string moduleName) const = 0; +}; + +class IProfiler +{ +public: + using RecordId = std::uint64_t; + using Clock = std::chrono::system_clock; + + struct Record + { + std::string moduleName; + uint64_t id{0}; + std::string stage; + std::string info; + Clock::time_point time; + }; + + virtual ~IProfiler() = default; + + IProfiler(const IProfiler &) = delete; + IProfiler &operator=(const IProfiler &) = delete; + IProfiler(IProfiler &&) = delete; + IProfiler &operator=(IProfiler &&) = delete; + + /** + * @brief Checks if profiler is enabled. + * + * @retval true if profiler is enabled, false otherwise. + */ + virtual bool isEnabled() const noexcept = 0; + + /** + * @brief Creates a record for given stage. + * + * @param[in] stage : Stage name used for record creation + * + * @retval Record identifier for created record or std::nullopt. + */ + virtual std::optional record(const std::string &stage) = 0; + + /** + * @brief Creates a record for given stage and info. + * + * @param[in] stage : Stage name used for record creation + * @param[in] info : Additional information used for record creation + * + * @retval Record identifier for created record or std::nullopt. + */ + virtual std::optional record(const std::string &stage, const std::string &info) = 0; + + /** + * @brief Logs a record for given identifier. + * + * @param[in] id : Record identifier + */ + virtual void log(RecordId id) = 0; + + /** + * @brief Dumps all records into pre-configured file. + * + * @retval true if file is created and records are dumped, false otherwise. + */ + virtual bool dumpToFile() const = 0; + + /** + * @brief Retrieves existing records. + * + * @retval Snapshot copy of existing records. + */ + virtual std::vector getRecords() const = 0; + +protected: + IProfiler() = default; +}; + +} // namespace firebolt::rialto::common + +#endif // FIREBOLT_RIALTO_COMMON_I_PROFILER_H_ diff --git a/common/source/EventThread.cpp b/common/source/EventThread.cpp index 1ad60188b..be4bc6d0e 100644 --- a/common/source/EventThread.cpp +++ b/common/source/EventThread.cpp @@ -54,13 +54,11 @@ EventThread::EventThread(std::string threadName) : m_kThreadName(std::move(threa EventThread::~EventThread() { - std::unique_lock locker(m_lock); - - m_shutdown = true; - - m_cond.notify_all(); - - locker.unlock(); + { + std::lock_guard locker(m_lock); + m_shutdown = true; + m_cond.notify_all(); + } if (m_thread.joinable()) m_thread.join(); diff --git a/common/source/Profiler.cpp b/common/source/Profiler.cpp new file mode 100644 index 000000000..172e204fc --- /dev/null +++ b/common/source/Profiler.cpp @@ -0,0 +1,208 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2025 Sky UK + * + * 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. + */ + +#include "Profiler.h" +#include "RialtoCommonLogging.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +inline constexpr const char *kProfilerEnv{"PROFILER_ENABLED"}; +inline constexpr const char *kProfilerDumpFileEnv{"PROFILER_DUMP_FILE_NAME"}; +inline constexpr std::size_t kMaxRecords{100}; + +bool getProfilerEnabled() +{ + const char *value = std::getenv(kProfilerEnv); + if (!value || (value[0] == '\0')) + return false; + + std::string stringValue(value); + std::transform(stringValue.begin(), stringValue.end(), stringValue.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + + if (stringValue == "1" || stringValue == "true" || stringValue == "yes" || stringValue == "on") + return true; + if (stringValue == "0" || stringValue == "false" || stringValue == "no" || stringValue == "off") + return false; + + return false; +} + +std::optional getDumpFileName() +{ + const char *value = std::getenv(kProfilerDumpFileEnv); + if (!value || value[0] == '\0') + return std::nullopt; + + return std::string{value}; +} +} // namespace + +namespace firebolt::rialto::common +{ +std::shared_ptr IProfilerFactory::createFactory() +{ + std::shared_ptr factory; + + try + { + factory = std::make_shared(); + } + catch (const std::exception &e) + { + RIALTO_COMMON_LOG_ERROR("Failed to create the profiler factory, reason: %s", e.what()); + } + + return factory; +} + +std::unique_ptr ProfilerFactory::createProfiler(std::string moduleName) const +{ + return std::make_unique(moduleName); +} + +Profiler::Profiler(std::string module) + : m_module(std::move(module)), m_enabled(getProfilerEnabled()), m_dumpFileName(getDumpFileName()) +{ +} + +bool Profiler::isEnabled() const noexcept +{ + return m_enabled; +} + +std::optional Profiler::record(const std::string &stage) +{ + if (!m_enabled) + return std::nullopt; + + auto now = Clock::now(); + std::lock_guard lock(m_mutex); + + if (m_records.size() >= kMaxRecords) + { + return std::nullopt; + } + + const Profiler::RecordId id = m_id++; + + m_records.push_back(Record{m_module, id, stage, std::string{}, now}); + + return id; +} + +std::optional Profiler::record(const std::string &stage, const std::string &info) +{ + if (!m_enabled) + return std::nullopt; + + auto now = Clock::now(); + std::lock_guard lock(m_mutex); + + if (m_records.size() >= kMaxRecords) + { + return std::nullopt; + } + + const Profiler::RecordId id = m_id++; + + m_records.push_back(Record{m_module, id, stage, info, now}); + + return id; +} + +void Profiler::log(const RecordId id) +{ + if (!m_enabled) + return; + + const auto record = findById(id); + if (record) + { + const auto ms = std::chrono::duration_cast(record->time.time_since_epoch()).count(); + + const auto idStr = std::to_string(static_cast(record->id)); + const auto tsStr = std::to_string(static_cast(ms)); + + RIALTO_COMMON_LOG_MIL("PROFILER | RECORD | MODULE[%s] ID[%s] STAGE[%s] INFO[%s] TIMESTAMP[%s]", + record->moduleName.c_str(), idStr.c_str(), record->stage.c_str(), record->info.c_str(), + tsStr.c_str()); + } +} + +bool Profiler::dumpToFile() const +{ + if (!m_dumpFileName) + return false; + + if (!m_enabled) + return false; + + std::vector copy; + { + std::lock_guard lock(m_mutex); + copy = m_records; + } + + std::ofstream out(*m_dumpFileName, std::ios::out | std::ios::app); + if (!out.is_open()) + return false; + + for (const auto &record : copy) + { + const auto us = std::chrono::duration_cast(record.time.time_since_epoch()).count(); + + out << "MODULE[" << record.moduleName << "] " << "ID[" << record.id << "] " << "STAGE[" << record.stage << "] " + << "INFO[" << record.info << "] " << "TIMESTAMP[" << us << "]" << '\n'; + } + + return static_cast(out); +} + +std::vector Profiler::getRecords() const +{ + if (!m_enabled) + return {}; + + std::lock_guard lock(m_mutex); + return m_records; +} + +std::optional Profiler::findById(Profiler::RecordId id) const +{ + std::lock_guard lock(m_mutex); + + const auto it = std::find_if(m_records.begin(), m_records.end(), [&](const auto &record) { return record.id == id; }); + + if (it != m_records.end()) + return *it; + + return std::nullopt; +} + +}; // namespace firebolt::rialto::common diff --git a/common/source/Timer.cpp b/common/source/Timer.cpp index ae109a692..8469a4737 100644 --- a/common/source/Timer.cpp +++ b/common/source/Timer.cpp @@ -59,15 +59,22 @@ Timer::Timer(const std::chrono::milliseconds &timeout, const std::function lock{m_mutex}; - if (!m_cv.wait_for(lock, m_timeout, [this]() { return !m_active; })) + bool shouldExecuteCallback = false; { - if (m_active && m_callback) + std::unique_lock lock{m_mutex}; + if (!m_cv.wait_for(lock, m_timeout, [this]() { return !m_active; })) { - lock.unlock(); - m_callback(); + if (m_active && m_callback) + { + shouldExecuteCallback = true; + } } } + + if (shouldExecuteCallback) + { + m_callback(); + } } while (timerType == TimerType::PERIODIC && m_active); m_active = false; }); @@ -81,10 +88,19 @@ Timer::~Timer() void Timer::cancel() { m_active = false; + m_cv.notify_one(); + + if (std::this_thread::get_id() == m_thread.get_id()) + { + if (m_thread.joinable()) + { + m_thread.detach(); + } + return; + } - if (std::this_thread::get_id() != m_thread.get_id() && m_thread.joinable()) + if (m_thread.joinable()) { - m_cv.notify_one(); m_thread.join(); } } diff --git a/install_dependencies_for_native_build.sh b/install_dependencies_for_native_build.sh new file mode 100644 index 000000000..679fb67ff --- /dev/null +++ b/install_dependencies_for_native_build.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2026 Sky UK +# +# 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. +# + +# Entry script for building the dependencies for Rialto Native Build. + +apt-get update +apt-get install -y build-essential +apt-get install -y cmake +apt-get install -y libunwind-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-bad1.0-dev libgstreamer1.0-dev libyaml-cpp-dev +apt-get install -y protobuf-compiler \ No newline at end of file diff --git a/ipc/client/source/IpcChannelImpl.cpp b/ipc/client/source/IpcChannelImpl.cpp index 85c6fea52..f81a01003 100644 --- a/ipc/client/source/IpcChannelImpl.cpp +++ b/ipc/client/source/IpcChannelImpl.cpp @@ -562,37 +562,36 @@ void ChannelImpl::processTimeoutEvent() return; } - // check if any method call has no expired - std::unique_lock locker(m_lock); - // stores the timed-out method calls std::vector timedOuts; - // remove the method calls that have expired - const auto kNow = std::chrono::steady_clock::now(); - auto it = m_methodCalls.begin(); - while (it != m_methodCalls.end()) { - if (kNow >= it->second.timeoutDeadline) + // check if any method call has now expired + std::unique_lock locker(m_lock); + + // remove the method calls that have expired + const auto kNow = std::chrono::steady_clock::now(); + auto it = m_methodCalls.begin(); + while (it != m_methodCalls.end()) { - timedOuts.emplace_back(it->second); - it = m_methodCalls.erase(it); + if (kNow >= it->second.timeoutDeadline) + { + timedOuts.emplace_back(it->second); + it = m_methodCalls.erase(it); + } + else + { + ++it; + } } - else + + // if we still have method calls available, then re-calculate the timer for the next timeout + if (!m_methodCalls.empty()) { - ++it; + updateTimeoutTimer(); } } - // if we still have method calls available, then re-calculate the timer for the next timeout - if (!m_methodCalls.empty()) - { - updateTimeoutTimer(); - } - - // drop the lock and now terminate the timed out method calls - locker.unlock(); - for (auto &call : timedOuts) { completeWithError(&call, "Timed out"); @@ -718,28 +717,25 @@ void ChannelImpl::processReplyFromServer(const transport::MethodCallReply &reply { RIALTO_IPC_LOG_DEBUG("processing reply from server"); - std::unique_lock locker(m_lock); - - // find the original request + MethodCall methodCall; const uint64_t kSerialId = reply.reply_id(); - auto it = m_methodCalls.find(kSerialId); - if (it == m_methodCalls.end()) + { - RIALTO_IPC_LOG_ERROR("failed to find request for received reply with id %" PRIu64 "", reply.reply_id()); - return; - } + std::lock_guard locker(m_lock); - // take the method call and remove from the map of outstanding calls - MethodCall methodCall = it->second; - m_methodCalls.erase(it); + auto it = m_methodCalls.find(kSerialId); + if (it == m_methodCalls.end()) + { + RIALTO_IPC_LOG_ERROR("failed to find request for received reply with id %" PRIu64 "", reply.reply_id()); + return; + } - // update the timeout timer now a method call has been processed - updateTimeoutTimer(); + methodCall = it->second; + m_methodCalls.erase(it); - // can now drop the lock - locker.unlock(); + updateTimeoutTimer(); + } - // this is an actual reply so try and read it if (!methodCall.response->ParseFromString(reply.reply_message())) { RIALTO_IPC_LOG_ERROR("failed to parse method reply from server"); diff --git a/ipc/server/include/IIpcServer.h b/ipc/server/include/IIpcServer.h index 10a2a8936..b7c9bc26d 100644 --- a/ipc/server/include/IIpcServer.h +++ b/ipc/server/include/IIpcServer.h @@ -162,10 +162,10 @@ class IServer return addSocket(socketPath, std::move(clientConnectedCb), nullptr); } virtual bool addSocket(const std::string &socketPath, - std::function &)> clientConnectedCb, - std::function &)> clientDisconnectedCb) = 0; - virtual bool addSocket(int fd, std::function &)> clientConnectedCb, - std::function &)> clientDisconnectedCb) = 0; + const std::function &)> &clientConnectedCb, + const std::function &)> &clientDisconnectedCb) = 0; + virtual bool addSocket(int fd, const std::function &)> &clientConnectedCb, + const std::function &)> &clientDisconnectedCb) = 0; /** * @brief Create a client. diff --git a/ipc/server/source/IpcServerImpl.cpp b/ipc/server/source/IpcServerImpl.cpp index f6cd2da73..dea990eb9 100644 --- a/ipc/server/source/IpcServerImpl.cpp +++ b/ipc/server/source/IpcServerImpl.cpp @@ -209,8 +209,8 @@ void ServerImpl::closeListeningSocket(Socket *socket) } bool ServerImpl::addSocket(const std::string &socketPath, - std::function &)> clientConnectedCb, - std::function &)> clientDisconnectedCb) + const std::function &)> &clientConnectedCb, + const std::function &)> &clientDisconnectedCb) { // store the path Socket socket; @@ -289,8 +289,8 @@ bool ServerImpl::addSocket(const std::string &socketPath, return true; } -bool ServerImpl::addSocket(int fd, std::function &)> clientConnectedCb, - std::function &)> clientDisconnectedCb) +bool ServerImpl::addSocket(int fd, const std::function &)> &clientConnectedCb, + const std::function &)> &clientDisconnectedCb) { // store the path Socket socket; @@ -1303,9 +1303,10 @@ bool ServerImpl::isClientConnected(uint64_t clientId) const */ void ServerImpl::disconnectClient(uint64_t clientId) { - std::unique_lock locker(m_clientsLock); - m_condemnedClients.insert(clientId); - locker.unlock(); + { + std::lock_guard locker(m_clientsLock); + m_condemnedClients.insert(clientId); + } wakeEventLoop(); } diff --git a/ipc/server/source/IpcServerImpl.h b/ipc/server/source/IpcServerImpl.h index 591f2a80b..758dc89e1 100644 --- a/ipc/server/source/IpcServerImpl.h +++ b/ipc/server/source/IpcServerImpl.h @@ -64,10 +64,11 @@ class ServerImpl final : public ::firebolt::rialto::ipc::IServer, public std::en ~ServerImpl() final; public: - bool addSocket(const std::string &socketPath, std::function &)> clientConnectedCb, - std::function &)> clientDisconnectedCb) override; - bool addSocket(int fd, std::function &)> clientConnectedCb, - std::function &)> clientDisconnectedCb) override; + bool addSocket(const std::string &socketPath, + const std::function &)> &clientConnectedCb, + const std::function &)> &clientDisconnectedCb) override; + bool addSocket(int fd, const std::function &)> &clientConnectedCb, + const std::function &)> &clientDisconnectedCb) override; std::shared_ptr addClient(int socketFd, std::function &)> clientDisconnectedCb) override; diff --git a/logging/source/EnvVariableParser.cpp b/logging/source/EnvVariableParser.cpp index 5ef89d89c..0253891fb 100644 --- a/logging/source/EnvVariableParser.cpp +++ b/logging/source/EnvVariableParser.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include namespace @@ -70,7 +71,7 @@ std::vector split(std::string s, const std::string &delimiter) result.push_back(s.substr(0, pos)); s.erase(0, pos + delimiter.length()); } - result.push_back(s); + result.push_back(std::move(s)); return result; } diff --git a/media/client/ipc/include/MediaKeysIpc.h b/media/client/ipc/include/MediaKeysIpc.h index 6302d383a..f962fc85d 100644 --- a/media/client/ipc/include/MediaKeysIpc.h +++ b/media/client/ipc/include/MediaKeysIpc.h @@ -69,11 +69,12 @@ class MediaKeysIpc : public IMediaKeys, public IpcModule bool containsKey(int32_t keySessionId, const std::vector &keyId) override; - MediaKeyErrorStatus createKeySession(KeySessionType sessionType, std::weak_ptr client, bool isLDL, + MediaKeyErrorStatus createKeySession(KeySessionType sessionType, std::weak_ptr client, int32_t &keySessionId) override; MediaKeyErrorStatus generateRequest(int32_t keySessionId, InitDataType initDataType, - const std::vector &initData) override; + const std::vector &initData, + const LimitedDurationLicense &ldlState) override; MediaKeyErrorStatus loadSession(int32_t keySessionId) override; diff --git a/media/client/ipc/include/MediaPipelineIpc.h b/media/client/ipc/include/MediaPipelineIpc.h index 5e50dd4f4..b93cd98fc 100644 --- a/media/client/ipc/include/MediaPipelineIpc.h +++ b/media/client/ipc/include/MediaPipelineIpc.h @@ -75,11 +75,11 @@ class MediaPipelineIpc : public IMediaPipelineIpc, public IpcModule bool allSourcesAttached() override; - bool load(MediaType type, const std::string &mimeType, const std::string &url) override; + bool load(MediaType type, const std::string &mimeType, const std::string &url, bool isLive) override; bool setVideoWindow(uint32_t x, uint32_t y, uint32_t width, uint32_t height) override; - bool play() override; + bool play(bool &async) override; bool pause() override; @@ -144,6 +144,8 @@ class MediaPipelineIpc : public IMediaPipelineIpc, public IpcModule bool switchSource(const std::unique_ptr &source) override; + bool getDuration(int64_t &duration) override; + private: /** * @brief The media player client ipc. diff --git a/media/client/ipc/interface/IMediaPipelineIpc.h b/media/client/ipc/interface/IMediaPipelineIpc.h index 1d055d2b7..59aea535b 100644 --- a/media/client/ipc/interface/IMediaPipelineIpc.h +++ b/media/client/ipc/interface/IMediaPipelineIpc.h @@ -106,10 +106,11 @@ class IMediaPipelineIpc * @param[in] type : The media type. * @param[in] mimeType : The MIME type. * @param[in] url : The URL. + * @param[in] isLive : Indicates if the media is live. * * @retval true on success. */ - virtual bool load(MediaType type, const std::string &mimeType, const std::string &url) = 0; + virtual bool load(MediaType type, const std::string &mimeType, const std::string &url, bool isLive) = 0; /** * @brief Request to set the coordinates of the video window. @@ -126,9 +127,11 @@ class IMediaPipelineIpc /** * @brief Request play on the playback session. * + * @param[out] async : True if play method call is asynchronous + * * @retval true on success. */ - virtual bool play() = 0; + virtual bool play(bool &async) = 0; /** * @brief Request pause on the playback session. @@ -451,6 +454,17 @@ class IMediaPipelineIpc * @retval true on success. */ virtual bool switchSource(const std::unique_ptr &source) = 0; + + /** + * @brief Get the playback duration in nanoseconds. + * + * This method is synchronous, it returns current playback duration + * + * @param[out] duration : The playback duration in nanoseconds + * + * @retval true on success. + */ + virtual bool getDuration(int64_t &duration) = 0; }; }; // namespace firebolt::rialto::client diff --git a/media/client/ipc/source/IpcClient.cpp b/media/client/ipc/source/IpcClient.cpp index 53c5b8d6a..57f2f5c60 100644 --- a/media/client/ipc/source/IpcClient.cpp +++ b/media/client/ipc/source/IpcClient.cpp @@ -19,6 +19,7 @@ #include "IpcClient.h" #include "RialtoClientLogging.h" +#include namespace firebolt::rialto::client { @@ -191,7 +192,7 @@ std::shared_ptr IpcClient::createBlockingClosure() // check which thread we're being called from, this determines if we pump // event loop from within the wait() method or not if (m_ipcThread.get_id() == std::this_thread::get_id()) - return m_blockingClosureFactory->createBlockingClosurePoll(ipcChannel); + return m_blockingClosureFactory->createBlockingClosurePoll(std::move(ipcChannel)); else return m_blockingClosureFactory->createBlockingClosureSemaphore(); } diff --git a/media/client/ipc/source/MediaKeysIpc.cpp b/media/client/ipc/source/MediaKeysIpc.cpp index 24909664e..41e3b1b17 100644 --- a/media/client/ipc/source/MediaKeysIpc.cpp +++ b/media/client/ipc/source/MediaKeysIpc.cpp @@ -123,6 +123,10 @@ const char *toString(const firebolt::rialto::MediaKeyErrorStatus &errorStatus) { return "FAIL"; } + case firebolt::rialto::MediaKeyErrorStatus::OUTPUT_RESTRICTED: + { + return "OUTPUT_RESTRICTED"; + } } return "UNKNOWN"; } @@ -334,7 +338,7 @@ bool MediaKeysIpc::containsKey(int32_t keySessionId, const std::vector } MediaKeyErrorStatus MediaKeysIpc::createKeySession(KeySessionType sessionType, std::weak_ptr client, - bool isLDL, int32_t &keySessionId) + int32_t &keySessionId) { if (!reattachChannelIfRequired()) { @@ -362,7 +366,6 @@ MediaKeyErrorStatus MediaKeysIpc::createKeySession(KeySessionType sessionType, s firebolt::rialto::CreateKeySessionRequest request; request.set_media_keys_handle(m_mediaKeysHandle); request.set_session_type(protoSessionType); - request.set_is_ldl(isLDL); firebolt::rialto::CreateKeySessionResponse response; // Default error status to FAIL @@ -387,7 +390,8 @@ MediaKeyErrorStatus MediaKeysIpc::createKeySession(KeySessionType sessionType, s } MediaKeyErrorStatus MediaKeysIpc::generateRequest(int32_t keySessionId, InitDataType initDataType, - const std::vector &initData) + const std::vector &initData, + const LimitedDurationLicense &ldlState) { if (!reattachChannelIfRequired()) { @@ -415,10 +419,29 @@ MediaKeyErrorStatus MediaKeysIpc::generateRequest(int32_t keySessionId, InitData break; } + GenerateRequestRequest_LimitedDurationLicense protoLimitedDurationLicense{ + GenerateRequestRequest_LimitedDurationLicense_NOT_SPECIFIED}; + switch (ldlState) + { + case LimitedDurationLicense::NOT_SPECIFIED: + protoLimitedDurationLicense = GenerateRequestRequest_LimitedDurationLicense_NOT_SPECIFIED; + break; + case LimitedDurationLicense::ENABLED: + protoLimitedDurationLicense = GenerateRequestRequest_LimitedDurationLicense_ENABLED; + break; + case LimitedDurationLicense::DISABLED: + protoLimitedDurationLicense = GenerateRequestRequest_LimitedDurationLicense_DISABLED; + break; + default: + RIALTO_CLIENT_LOG_WARN("Received unknown limited duration license state"); + break; + } + firebolt::rialto::GenerateRequestRequest request; request.set_media_keys_handle(m_mediaKeysHandle); request.set_key_session_id(keySessionId); request.set_init_data_type(protoInitDataType); + request.set_ldl_state(protoLimitedDurationLicense); for (auto it = initData.begin(); it != initData.end(); it++) { diff --git a/media/client/ipc/source/MediaPipelineIpc.cpp b/media/client/ipc/source/MediaPipelineIpc.cpp index 23c0375da..3949629ed 100644 --- a/media/client/ipc/source/MediaPipelineIpc.cpp +++ b/media/client/ipc/source/MediaPipelineIpc.cpp @@ -182,7 +182,7 @@ bool MediaPipelineIpc::subscribeToEvents(const std::shared_ptr &i return true; } -bool MediaPipelineIpc::load(MediaType type, const std::string &mimeType, const std::string &url) +bool MediaPipelineIpc::load(MediaType type, const std::string &mimeType, const std::string &url, bool isLive) { if (!reattachChannelIfRequired()) { @@ -196,6 +196,7 @@ bool MediaPipelineIpc::load(MediaType type, const std::string &mimeType, const s request.set_type(convertLoadRequestMediaType(type)); request.set_mime_type(mimeType); request.set_url(url); + request.set_is_live(isLive); firebolt::rialto::LoadResponse response; auto ipcController = m_ipc.createRpcController(); @@ -348,7 +349,7 @@ bool MediaPipelineIpc::setVideoWindow(uint32_t x, uint32_t y, uint32_t width, ui return true; } -bool MediaPipelineIpc::play() +bool MediaPipelineIpc::play(bool &async) { if (!reattachChannelIfRequired()) { @@ -375,6 +376,8 @@ bool MediaPipelineIpc::play() return false; } + async = response.async(); + return true; } @@ -533,6 +536,37 @@ bool MediaPipelineIpc::getPosition(int64_t &position) return true; } +bool MediaPipelineIpc::getDuration(int64_t &duration) +{ + if (!reattachChannelIfRequired()) + { + RIALTO_CLIENT_LOG_ERROR("Reattachment of the ipc channel failed, ipc disconnected"); + return false; + } + + firebolt::rialto::GetDurationRequest request; + + request.set_session_id(m_sessionId); + + firebolt::rialto::GetDurationResponse response; + auto ipcController = m_ipc.createRpcController(); + auto blockingClosure = m_ipc.createBlockingClosure(); + m_mediaPipelineStub->getDuration(ipcController.get(), &request, &response, blockingClosure.get()); + + // wait for the call to complete + blockingClosure->wait(); + + // check the result + if (ipcController->Failed()) + { + RIALTO_CLIENT_LOG_ERROR("failed to get duration due to '%s'", ipcController->ErrorText().c_str()); + return false; + } + + duration = response.duration(); + return true; +} + bool MediaPipelineIpc::setImmediateOutput(int32_t sourceId, bool immediateOutput) { if (!reattachChannelIfRequired()) diff --git a/media/client/main/include/MediaKeys.h b/media/client/main/include/MediaKeys.h index 4f27f8ffa..c997cd090 100644 --- a/media/client/main/include/MediaKeys.h +++ b/media/client/main/include/MediaKeys.h @@ -79,11 +79,12 @@ class MediaKeys : public IMediaKeys bool containsKey(int32_t keySessionId, const std::vector &keyId) override; - MediaKeyErrorStatus createKeySession(KeySessionType sessionType, std::weak_ptr client, bool isLDL, + MediaKeyErrorStatus createKeySession(KeySessionType sessionType, std::weak_ptr client, int32_t &keySessionId) override; MediaKeyErrorStatus generateRequest(int32_t keySessionId, InitDataType initDataType, - const std::vector &initData) override; + const std::vector &initData, + const LimitedDurationLicense &ldlState) override; MediaKeyErrorStatus loadSession(int32_t keySessionId) override; diff --git a/media/client/main/include/MediaPipeline.h b/media/client/main/include/MediaPipeline.h index 74bf8d13b..14dad7f81 100644 --- a/media/client/main/include/MediaPipeline.h +++ b/media/client/main/include/MediaPipeline.h @@ -110,7 +110,7 @@ class MediaPipeline : public IMediaPipelineAndIControlClient, public IMediaPipel */ virtual ~MediaPipeline(); - bool load(MediaType type, const std::string &mimeType, const std::string &url) override; + bool load(MediaType type, const std::string &mimeType, const std::string &url, bool isLive) override; bool attachSource(const std::unique_ptr &source) override; @@ -118,7 +118,7 @@ class MediaPipeline : public IMediaPipelineAndIControlClient, public IMediaPipel bool allSourcesAttached() override; - bool play() override; + bool play(bool &async) override; bool pause() override; @@ -188,6 +188,8 @@ class MediaPipeline : public IMediaPipelineAndIControlClient, public IMediaPipel bool getStreamSyncMode(int32_t &streamSyncMode) override; + bool getDuration(int64_t &duration) override; + bool flush(int32_t sourceId, bool resetTime, bool &async) override; bool setSourcePosition(int32_t sourceId, int64_t position, bool resetTime, double appliedRate, diff --git a/media/client/main/include/MediaPipelineProxy.h b/media/client/main/include/MediaPipelineProxy.h index cf0283cd2..80f019d95 100644 --- a/media/client/main/include/MediaPipelineProxy.h +++ b/media/client/main/include/MediaPipelineProxy.h @@ -38,9 +38,9 @@ class MediaPipelineProxy : public IMediaPipelineAndIControlClient std::weak_ptr getClient() override { return m_mediaPipeline->getClient(); } - bool load(MediaType type, const std::string &mimeType, const std::string &url) override + bool load(MediaType type, const std::string &mimeType, const std::string &url, bool isLive) override { - return m_mediaPipeline->load(type, mimeType, url); + return m_mediaPipeline->load(type, mimeType, url, isLive); } bool attachSource(const std::unique_ptr &source) override @@ -52,7 +52,7 @@ class MediaPipelineProxy : public IMediaPipelineAndIControlClient bool allSourcesAttached() override { return m_mediaPipeline->allSourcesAttached(); } - bool play() override { return m_mediaPipeline->play(); } + bool play(bool &async) override { return m_mediaPipeline->play(async); } bool pause() override { return m_mediaPipeline->pause(); } @@ -174,6 +174,8 @@ class MediaPipelineProxy : public IMediaPipelineAndIControlClient return m_mediaPipeline->switchSource(source); } + bool getDuration(int64_t &duration) override { return m_mediaPipeline->getDuration(duration); } + void notifyApplicationState(ApplicationState state) override { m_mediaPipeline->notifyApplicationState(state); } private: diff --git a/media/client/main/source/ClientController.cpp b/media/client/main/source/ClientController.cpp index eac82154c..5f9fe6b6f 100644 --- a/media/client/main/source/ClientController.cpp +++ b/media/client/main/source/ClientController.cpp @@ -25,6 +25,7 @@ #include #include #include +#include namespace { @@ -265,7 +266,7 @@ void ClientController::changeStateAndNotifyClients(ApplicationState state) std::shared_ptr clientLocked{client.lock()}; if (clientLocked) { - currentClients.push_back(clientLocked); + currentClients.push_back(std::move(clientLocked)); } else { diff --git a/media/client/main/source/Control.cpp b/media/client/main/source/Control.cpp index 215558fe8..2526694eb 100644 --- a/media/client/main/source/Control.cpp +++ b/media/client/main/source/Control.cpp @@ -20,6 +20,7 @@ #include "Control.h" #include "IControlIpc.h" #include "RialtoClientLogging.h" +#include namespace firebolt::rialto { @@ -76,7 +77,7 @@ bool Control::registerClient(std::weak_ptr client, ApplicationSt std::shared_ptr lockedClient = client.lock(); if (lockedClient && m_clientController.registerClient(lockedClient, appState)) { - m_clientsToUnregister.push_back(lockedClient); + m_clientsToUnregister.push_back(std::move(lockedClient)); return true; } RIALTO_CLIENT_LOG_WARN("Unable to register client"); diff --git a/media/client/main/source/MediaKeys.cpp b/media/client/main/source/MediaKeys.cpp index 1a60d44e1..4d5afa337 100644 --- a/media/client/main/source/MediaKeys.cpp +++ b/media/client/main/source/MediaKeys.cpp @@ -115,11 +115,11 @@ bool MediaKeys::containsKey(int32_t keySessionId, const std::vector &ke } MediaKeyErrorStatus MediaKeys::createKeySession(KeySessionType sessionType, std::weak_ptr client, - bool isLDL, int32_t &keySessionId) + int32_t &keySessionId) { RIALTO_CLIENT_LOG_DEBUG("entry:"); - auto result{m_mediaKeysIpc->createKeySession(sessionType, client, isLDL, keySessionId)}; + auto result{m_mediaKeysIpc->createKeySession(sessionType, client, keySessionId)}; if (isNetflixPlayready(m_keySystem) && MediaKeyErrorStatus::OK == result) { KeyIdMap::instance().addSession(keySessionId); @@ -128,11 +128,12 @@ MediaKeyErrorStatus MediaKeys::createKeySession(KeySessionType sessionType, std: } MediaKeyErrorStatus MediaKeys::generateRequest(int32_t keySessionId, InitDataType initDataType, - const std::vector &initData) + const std::vector &initData, + const LimitedDurationLicense &ldlState) { RIALTO_CLIENT_LOG_DEBUG("entry:"); - return m_mediaKeysIpc->generateRequest(keySessionId, initDataType, initData); + return m_mediaKeysIpc->generateRequest(keySessionId, initDataType, initData, ldlState); } MediaKeyErrorStatus MediaKeys::loadSession(int32_t keySessionId) diff --git a/media/client/main/source/MediaPipeline.cpp b/media/client/main/source/MediaPipeline.cpp index 280aff919..ed0506432 100644 --- a/media/client/main/source/MediaPipeline.cpp +++ b/media/client/main/source/MediaPipeline.cpp @@ -203,11 +203,11 @@ MediaPipeline::~MediaPipeline() m_mediaPipelineIpc.reset(); } -bool MediaPipeline::load(MediaType type, const std::string &mimeType, const std::string &url) +bool MediaPipeline::load(MediaType type, const std::string &mimeType, const std::string &url, bool isLive) { RIALTO_CLIENT_LOG_DEBUG("entry:"); - return m_mediaPipelineIpc->load(type, mimeType, url); + return m_mediaPipelineIpc->load(type, mimeType, url, isLive); } bool MediaPipeline::attachSource(const std::unique_ptr &source) @@ -252,11 +252,11 @@ bool MediaPipeline::allSourcesAttached() return m_mediaPipelineIpc->allSourcesAttached(); } -bool MediaPipeline::play() +bool MediaPipeline::play(bool &async) { RIALTO_CLIENT_LOG_DEBUG("entry:"); - return m_mediaPipelineIpc->play(); + return m_mediaPipelineIpc->play(async); } bool MediaPipeline::pause() @@ -626,6 +626,13 @@ bool MediaPipeline::switchSource(const std::unique_ptr &source) return m_mediaPipelineIpc->switchSource(source); } +bool MediaPipeline::getDuration(int64_t &duration) +{ + RIALTO_CLIENT_LOG_DEBUG("entry:"); + + return m_mediaPipelineIpc->getDuration(duration); +} + void MediaPipeline::discardNeedDataRequest(uint32_t needDataRequestId) { // Find the needDataRequest for this needDataRequestId @@ -808,7 +815,7 @@ void MediaPipeline::notifyNeedMediaData(int32_t sourceId, size_t frameCount, uin RIALTO_CLIENT_LOG_INFO("NeedMediaData received in state != RUNNING, ignoring request id %u", requestId); break; } - m_needDataRequestMap[requestId] = needDataRequest; + m_needDataRequestMap[requestId] = std::move(needDataRequest); } std::shared_ptr client = m_mediaPipelineClient.lock(); diff --git a/media/public/include/IMediaKeys.h b/media/public/include/IMediaKeys.h index 6408d0056..c2e4ced3d 100644 --- a/media/public/include/IMediaKeys.h +++ b/media/public/include/IMediaKeys.h @@ -110,13 +110,12 @@ class IMediaKeys * * @param[in] sessionType : The session type. * @param[in] client : Client object for callbacks - * @param[in] isLDL : Is this an LDL * @param[out] keySessionId: The key session id * * @retval an error status. */ virtual MediaKeyErrorStatus createKeySession(KeySessionType sessionType, std::weak_ptr client, - bool isLDL, int32_t &keySessionId) = 0; + int32_t &keySessionId) = 0; /** * @brief Generates a licence request. @@ -130,11 +129,14 @@ class IMediaKeys * @param[in] keySessionId : The key session id for the session. * @param[in] initDataType : The init data type. * @param[in] initData : The init data. + * @param[in] ldlState : The Limited Duration License state. Most of key systems do not need this parameter, + * so the default value is NOT_SPECIFIED. * * @retval an error status. */ - virtual MediaKeyErrorStatus generateRequest(int32_t keySessionId, InitDataType initDataType, - const std::vector &initData) = 0; + virtual MediaKeyErrorStatus + generateRequest(int32_t keySessionId, InitDataType initDataType, const std::vector &initData, + const LimitedDurationLicense &ldlState = LimitedDurationLicense::NOT_SPECIFIED) = 0; /** * @brief Loads an existing key session diff --git a/media/public/include/IMediaPipeline.h b/media/public/include/IMediaPipeline.h index 1064ccc1e..c1820f302 100644 --- a/media/public/include/IMediaPipeline.h +++ b/media/public/include/IMediaPipeline.h @@ -1072,8 +1072,9 @@ class IMediaPipeline * @param[in] type : The media type. * @param[in] mimeType : The MIME type. * @param[in] url : The URL. + * @param[in] isLive : Indicates if the media is live. */ - virtual bool load(MediaType type, const std::string &mimeType, const std::string &url) = 0; + virtual bool load(MediaType type, const std::string &mimeType, const std::string &url, bool isLive) = 0; /** * @brief Attaches a source stream to the backend. @@ -1119,16 +1120,15 @@ class IMediaPipeline /** * @brief Starts playback of the media. * - * This method is considered to be asynchronous and MUST NOT block - * but should request playback and then return. - * * Once the backend is successfully playing it should notify the * media player client of playback state * IMediaPipelineClient::PlaybackState::PLAYING. * + * @param[out] async : True if play method call is asynchronous + * * @retval true on success. */ - virtual bool play() = 0; + virtual bool play(bool &async) = 0; /** * @brief Pauses playback of the media. @@ -1535,6 +1535,17 @@ class IMediaPipeline * @retval true on success. */ virtual bool switchSource(const std::unique_ptr &source) = 0; + + /** + * @brief Get the playback duration in nanoseconds. + * + * This method is synchronous, it returns current playback duration + * + * @param[out] duration : The playback duration in nanoseconds + * + * @retval true on success. + */ + virtual bool getDuration(int64_t &duration) = 0; }; }; // namespace firebolt::rialto diff --git a/media/public/include/MediaCommon.h b/media/public/include/MediaCommon.h index 92b544b8c..16e1025ee 100644 --- a/media/public/include/MediaCommon.h +++ b/media/public/include/MediaCommon.h @@ -292,7 +292,8 @@ enum class MediaKeyErrorStatus NOT_SUPPORTED, /**< The request parameters are not supported. */ INVALID_STATE, /**< The object is in an invalid state for the operation. */ INTERFACE_NOT_IMPLEMENTED, /**< The interface is not implemented. */ - BUFFER_TOO_SMALL /**< The size of the buffer is too small. */ + BUFFER_TOO_SMALL, /**< The size of the buffer is too small. */ + OUTPUT_RESTRICTED }; /** @@ -473,6 +474,16 @@ struct PlaybackInfo int64_t currentPosition{-1}; /**< The current playback position */ double volume{1.0}; /**< The current volume */ }; + +/** + * @brief Limited duration license state. + */ +enum class LimitedDurationLicense +{ + NOT_SPECIFIED, /**< The license duration is not specified */ + ENABLED, /**< The license has a limited duration */ + DISABLED /**< The license does not have a limited duration */ +}; } // namespace firebolt::rialto #endif // FIREBOLT_RIALTO_MEDIA_COMMON_H_ diff --git a/media/server/gstplayer/CMakeLists.txt b/media/server/gstplayer/CMakeLists.txt index 98a77ca4c..11d7fc63b 100644 --- a/media/server/gstplayer/CMakeLists.txt +++ b/media/server/gstplayer/CMakeLists.txt @@ -49,7 +49,6 @@ add_library( source/tasks/generic/Play.cpp source/tasks/generic/ProcessAudioGap.cpp source/tasks/generic/ReadShmDataAndAttachSamples.cpp - source/tasks/generic/RemoveSource.cpp source/tasks/generic/RenderFrame.cpp source/tasks/generic/ReportPosition.cpp source/tasks/generic/SetBufferingLimit.cpp @@ -97,6 +96,7 @@ add_library( source/GstGenericPlayer.cpp source/GstInitialiser.cpp source/GstLogForwarding.cpp + source/GstProfiler.cpp source/GstProtectionMetadata.cpp source/GstProtectionMetadataHelper.cpp source/GstSrc.cpp diff --git a/media/server/gstplayer/include/CapsBuilder.h b/media/server/gstplayer/include/CapsBuilder.h index 283f7ac04..0c9452ab9 100644 --- a/media/server/gstplayer/include/CapsBuilder.h +++ b/media/server/gstplayer/include/CapsBuilder.h @@ -31,8 +31,8 @@ namespace firebolt::rialto::server class MediaSourceCapsBuilder { public: - MediaSourceCapsBuilder(std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, + MediaSourceCapsBuilder(const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, const firebolt::rialto::IMediaPipeline::MediaSourceAV &source); virtual ~MediaSourceCapsBuilder() = default; virtual GstCaps *buildCaps(); @@ -51,8 +51,8 @@ class MediaSourceCapsBuilder class MediaSourceAudioCapsBuilder : public MediaSourceCapsBuilder { public: - MediaSourceAudioCapsBuilder(std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, + MediaSourceAudioCapsBuilder(const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, const IMediaPipeline::MediaSourceAudio &source); ~MediaSourceAudioCapsBuilder() override = default; GstCaps *buildCaps() override; @@ -72,8 +72,8 @@ class MediaSourceAudioCapsBuilder : public MediaSourceCapsBuilder class MediaSourceVideoCapsBuilder : public MediaSourceCapsBuilder { public: - MediaSourceVideoCapsBuilder(std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, + MediaSourceVideoCapsBuilder(const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, const IMediaPipeline::MediaSourceVideo &source); ~MediaSourceVideoCapsBuilder() override = default; GstCaps *buildCaps() override; @@ -85,8 +85,8 @@ class MediaSourceVideoCapsBuilder : public MediaSourceCapsBuilder class MediaSourceVideoDolbyVisionCapsBuilder : public MediaSourceVideoCapsBuilder { public: - MediaSourceVideoDolbyVisionCapsBuilder(std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, + MediaSourceVideoDolbyVisionCapsBuilder(const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, const IMediaPipeline::MediaSourceVideoDolbyVision &source); ~MediaSourceVideoDolbyVisionCapsBuilder() override = default; GstCaps *buildCaps() override; diff --git a/media/server/gstplayer/include/FlushOnPrerollController.h b/media/server/gstplayer/include/FlushOnPrerollController.h index 3b17dfaed..8ae19bfc3 100644 --- a/media/server/gstplayer/include/FlushOnPrerollController.h +++ b/media/server/gstplayer/include/FlushOnPrerollController.h @@ -21,6 +21,7 @@ #define FIREBOLT_RIALTO_SERVER_FLUSH_ONPREROLL_CONTROLLER_H_ #include "IFlushOnPrerollController.h" +#include #include #include #include @@ -37,13 +38,16 @@ class FlushOnPrerollController : public IFlushOnPrerollController FlushOnPrerollController() = default; ~FlushOnPrerollController() override = default; - bool shouldPostponeFlush(const MediaSourceType &type) const override; - void setFlushing(const MediaSourceType &type, const GstState ¤tPipelineState) override; + void waitIfRequired(const MediaSourceType &type) override; + void setFlushing(const MediaSourceType &type) override; + void setPrerolling() override; void stateReached(const GstState &newPipelineState) override; + void setTargetState(const GstState &state) override; void reset() override; private: - mutable std::mutex m_mutex{}; + std::mutex m_mutex{}; + std::condition_variable m_conditionVariable{}; std::set m_flushingSources{}; std::optional m_targetState{std::nullopt}; bool m_isPrerolled{false}; diff --git a/media/server/gstplayer/include/GenericPlayerContext.h b/media/server/gstplayer/include/GenericPlayerContext.h index a2f214535..af72f88a6 100644 --- a/media/server/gstplayer/include/GenericPlayerContext.h +++ b/media/server/gstplayer/include/GenericPlayerContext.h @@ -21,6 +21,7 @@ #define FIREBOLT_RIALTO_SERVER_GENERIC_PLAYER_CONTEXT_H_ #include "FlushOnPrerollController.h" +#include "IGstProfiler.h" #include "IGstSrc.h" #include "IRdkGstreamerUtilsWrapper.h" #include "ITimer.h" @@ -206,13 +207,6 @@ struct GenericPlayerContext */ IDecryptionService *decryptionService{nullptr}; - /** - * @brief Flag used to check, if audio source has been recently removed - * - * Flag can be used only in worker thread - */ - bool audioSourceRemoved{false}; - /** * @brief Audio elements of gst pipeline. * @@ -270,7 +264,18 @@ struct GenericPlayerContext /** * @brief Workaround for the gstreamer flush issue */ - FlushOnPrerollController flushOnPrerollController; + std::shared_ptr flushOnPrerollController{std::make_shared()}; + + /** + * @brief Flag used to check if the stream is live + * This is a workaround for Broadcom decoder issue with audio cuts during playback rate change. + */ + bool isLive{false}; + + /** + * @brief Profiler for player pipeline + */ + std::unique_ptr gstProfiler; }; } // namespace firebolt::rialto::server diff --git a/media/server/gstplayer/include/GstDispatcherThread.h b/media/server/gstplayer/include/GstDispatcherThread.h index 540f161ca..0db6fe2d9 100644 --- a/media/server/gstplayer/include/GstDispatcherThread.h +++ b/media/server/gstplayer/include/GstDispatcherThread.h @@ -34,6 +34,7 @@ class GstDispatcherThreadFactory : public IGstDispatcherThreadFactory ~GstDispatcherThreadFactory() override = default; std::unique_ptr createGstDispatcherThread(IGstDispatcherThreadClient &client, GstElement *pipeline, + const std::shared_ptr &flushOnPrerollController, const std::shared_ptr &gstWrapper) const override; }; @@ -41,6 +42,7 @@ class GstDispatcherThread : public IGstDispatcherThread { public: GstDispatcherThread(IGstDispatcherThreadClient &client, GstElement *pipeline, + const std::shared_ptr &flushOnPrerollController, const std::shared_ptr &gstWrapper); ~GstDispatcherThread() override; @@ -58,6 +60,11 @@ class GstDispatcherThread : public IGstDispatcherThread */ IGstDispatcherThreadClient &m_client; + /** + * @brief The flush on preroll controller. + */ + std::shared_ptr m_flushOnPrerollController; + /** * @brief The gstreamer wrapper object. */ diff --git a/media/server/gstplayer/include/GstGenericPlayer.h b/media/server/gstplayer/include/GstGenericPlayer.h index 22fd800e0..03436d67b 100644 --- a/media/server/gstplayer/include/GstGenericPlayer.h +++ b/media/server/gstplayer/include/GstGenericPlayer.h @@ -28,6 +28,7 @@ #include "IGstGenericPlayer.h" #include "IGstGenericPlayerPrivate.h" #include "IGstInitialiser.h" +#include "IGstProfiler.h" #include "IGstProtectionMetadataHelperFactory.h" #include "IGstSrc.h" #include "IGstWrapper.h" @@ -36,6 +37,7 @@ #include "tasks/IGenericPlayerTaskFactory.h" #include "tasks/IPlayerTask.h" #include +#include #include #include #include @@ -59,7 +61,7 @@ class GstGenericPlayerFactory : public IGstGenericPlayerFactory std::unique_ptr createGstGenericPlayer(IGstGenericPlayerClient *client, IDecryptionService &decryptionService, MediaType type, - const VideoRequirements &videoRequirements, + const VideoRequirements &videoRequirements, bool isLive, const std::shared_ptr &rdkGstreamerUtilsWrapperFactory) override; }; @@ -82,18 +84,20 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva * @param[in] gstInitialiser : The gst initialiser * @param[in] flushWatcher : The flush watcher * @param[in] gstSrcFactory : The gstreamer rialto src factory. + * @param[in] gstProfilerFactory : The gstreamer rialto profiler factory. * @param[in] timerFactory : The Timer factory * @param[in] taskFactory : The task factory * @param[in] workerThreadFactory : The worker thread factory * @param[in] gstDispatcherThreadFactory : The gst dispatcher thread factory */ GstGenericPlayer(IGstGenericPlayerClient *client, IDecryptionService &decryptionService, MediaType type, - const VideoRequirements &videoRequirements, + const VideoRequirements &videoRequirements, bool isLive, const std::shared_ptr &gstWrapper, const std::shared_ptr &glibWrapper, const std::shared_ptr &rdkGstreamerUtilsWrapper, const IGstInitialiser &gstInitialiser, std::unique_ptr &&flushWatcher, const std::shared_ptr &gstSrcFactory, + const std::shared_ptr &gstProfilerFactory, std::shared_ptr timerFactory, std::unique_ptr taskFactory, std::unique_ptr workerThreadFactory, @@ -106,9 +110,8 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva virtual ~GstGenericPlayer(); void attachSource(const std::unique_ptr &mediaSource) override; - void removeSource(const MediaSourceType &mediaSourceType) override; void allSourcesAttached() override; - void play() override; + void play(bool &async) override; void pause() override; void stop() override; void attachSamples(const IMediaPipeline::MediaSegmentVector &mediaSegments) override; @@ -118,6 +121,7 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva void setEos(const firebolt::rialto::MediaSourceType &type) override; void setPlaybackRate(double rate) override; bool getPosition(std::int64_t &position) override; + bool getDuration(std::int64_t &duration) override; bool setImmediateOutput(const MediaSourceType &mediaSourceType, bool immediateOutput) override; bool getImmediateOutput(const MediaSourceType &mediaSourceType, bool &immediateOutput) override; bool getStats(const MediaSourceType &mediaSourceType, uint64_t &renderedFrames, uint64_t &droppedFrames) override; @@ -168,10 +172,12 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva void updateVideoCaps(int32_t width, int32_t height, Fraction frameRate, const std::shared_ptr &codecData) override; void addAudioClippingToBuffer(GstBuffer *buffer, uint64_t clippingStart, uint64_t clippingEnd) const override; - bool changePipelineState(GstState newState) override; + GstStateChangeReturn changePipelineState(GstState newState) override; int64_t getPosition(GstElement *element) override; void startPositionReportingAndCheckAudioUnderflowTimer() override; void stopPositionReportingAndCheckAudioUnderflowTimer() override; + void startNotifyPlaybackInfoTimer() override; + void stopNotifyPlaybackInfoTimer() override; void startSubtitleClockResyncTimer() override; void stopSubtitleClockResyncTimer() override; void stopWorkerThread() override; @@ -185,15 +191,12 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva void addAutoAudioSinkChild(GObject *object) override; void removeAutoVideoSinkChild(GObject *object) override; void removeAutoAudioSinkChild(GObject *object) override; - void setPlaybinFlags(bool enableAudio = true) override; void pushSampleIfRequired(GstElement *source, const std::string &typeStr) override; bool reattachSource(const std::unique_ptr &source) override; bool hasSourceType(const MediaSourceType &mediaSourceType) const override; GstElement *getSink(const MediaSourceType &mediaSourceType) const override; void setSourceFlushed(const MediaSourceType &mediaSourceType) override; bool isAsync(const MediaSourceType &mediaSourceType) const; - void postponeFlush(const MediaSourceType &mediaSourceType, bool resetTime) override; - void executePostponedFlushes() override; void notifyPlaybackInfo() override; private: @@ -324,6 +327,61 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva std::optional createAudioAttributes(const std::unique_ptr &source) const; + /** + * @brief Configures audio caps based on audio attributes. + * Called by worker thread only! + * + * @param[in] pAttrib : The audio attributes. + * @param[out] audioaac : Set to true if AAC, false otherwise. + * @param[in] svpenabled : Whether SVP is enabled. + * @param[in,out] appsrcCaps : The caps to configure. + */ + void configAudioCap(firebolt::rialto::wrappers::AudioAttributesPrivate *pAttrib, bool *audioaac, bool svpenabled, + GstCaps **appsrcCaps); + + /** + * @brief Halts audio playback by setting playsink to READY and decodebin to PAUSED. + * Called by worker thread only! + */ + void haltAudioPlayback(); + + /** + * @brief Resumes audio playback by syncing playsink and decodebin with parent. + * Called by worker thread only! + */ + void resumeAudioPlayback(); + + /** + * @brief First-time codec switch from AC3 to AAC when no decoder exists yet. + * Called by worker thread only! + * + * @param[in] newAudioCaps : The new audio caps to apply. + */ + void firstTimeSwitchFromAC3toAAC(GstCaps *newAudioCaps); + + /** + * @brief Switches the audio codec by unlinking old parser/decoder and linking new ones. + * Called by worker thread only! + * + * @param[in] isAudioAAC : Whether the new codec is AAC. + * @param[in] newAudioCaps : The new audio caps to apply. + * + * @retval true if codec was switched, false if same codec. + */ + bool switchAudioCodec(bool isAudioAAC, GstCaps *newAudioCaps); + + /** + * @brief Top-level audio track codec channel switch, ported from rdk_gstreamer_utils_soc. + * Called by worker thread only! + */ + bool performAudioTrackCodecChannelSwitch(const void *pSampleAttr, + firebolt::rialto::wrappers::AudioAttributesPrivate *pAudioAttr, + uint32_t *pStatus, unsigned int *pui32Delay, + long long *pAudioChangeTargetPts, // NOLINT(runtime/int) + const long long *pcurrentDispPts, // NOLINT(runtime/int) + unsigned int *audioChangeStage, GstCaps **appsrcCaps, bool *audioaac, + bool svpenabled, GstElement *aSrc, bool *ret); + /** * @brief Sets text track position before pushing data * @@ -340,6 +398,13 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva */ void pushAdditionalSegmentIfRequired(GstElement *source); + /** + * @brief Sets the audio and video flags on the pipeline based on the input. + * + * @param[in] enableAudio : Whether to enable audio flags. + */ + void setPlaybinFlags(bool enableAudio = true); + private: /** * @brief The player context. @@ -366,6 +431,11 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva */ std::shared_ptr m_rdkGstreamerUtilsWrapper; + /** + * @brief Factory creating gst profilers + */ + std::shared_ptr m_gstProfilerFactory; + /** * @brief Thread for handling player tasks. */ @@ -422,11 +492,6 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva * @brief The object used to check flushing state for all sources */ std::unique_ptr m_flushWatcher; - - /** - * @brief The postponed flush tasks - */ - std::vector> m_postponedFlushes{}; }; } // namespace firebolt::rialto::server diff --git a/media/server/gstplayer/include/GstProfiler.h b/media/server/gstplayer/include/GstProfiler.h new file mode 100644 index 000000000..f767918aa --- /dev/null +++ b/media/server/gstplayer/include/GstProfiler.h @@ -0,0 +1,137 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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. + */ + +#ifndef FIREBOLT_RIALTO_SERVER_GST_PROFILER_H_ +#define FIREBOLT_RIALTO_SERVER_GST_PROFILER_H_ + +#include "IGlibWrapper.h" +#include "IGstProfiler.h" +#include "IGstProfilerPrivate.h" +#include "IGstWrapper.h" +#include "IProfiler.h" + +#include + +#include +#include +#include +#include +#include +#include + +namespace firebolt::rialto::server +{ +class GstProfilerFactory : public IGstProfilerFactory +{ +public: + std::unique_ptr createGstProfiler(GstElement *pipeline, const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper) const override; + + static std::weak_ptr m_factory; +}; + +class GstProfiler : public IGstProfiler, public IGstProfilerPrivate +{ +public: + using RecordId = IGstProfiler::RecordId; + + GstProfiler(GstElement *pipeline, const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper); + ~GstProfiler() override; + + std::optional createRecord(const std::string &stage) override; + std::optional createRecord(const std::string &stage, const std::string &info) override; + + void scheduleGstElementRecord(GstElement *element) override; + std::vector getRecords() const override; + + void logRecord(const RecordId id) override; + void dumpToFile() const override; + void logPipelineSummary() const override; + +private: + using Clock = std::chrono::system_clock; + using IGstWrapper = firebolt::rialto::wrappers::IGstWrapper; + using IGlibWrapper = firebolt::rialto::wrappers::IGlibWrapper; + using IProfiler = firebolt::rialto::common::IProfiler; + + struct PipelineStageTimestamps + { + std::optional pipelineCreated; + std::optional allSourcesAttached; + + std::optional firstSegmentReceivedVideo; + std::optional firstSegmentReceivedAudio; + + std::optional sourceFbExitVideo; + std::optional sourceFbExitAudio; + + std::optional decryptorFbExitVideo; + std::optional decryptorFbExitAudio; + + std::optional decoderFbExitVideo; + std::optional decoderFbExitAudio; + + std::optional pipelinePaused; + std::optional pipelinePlaying; + }; + + struct PipelineMetrics + { + std::optional preparation; + std::optional videoDownload; + std::optional audioDownload; + std::optional videoSource; + std::optional audioSource; + std::optional videoDecryption; + std::optional audioDecryption; + std::optional videoDecode; + std::optional audioDecode; + std::optional preRoll; + std::optional play; + std::optional total; + std::optional totalWithoutApp; + }; + + std::optional getFirstBufferExitStage(GstElement *element); + const gchar *getElementClassMetadata(GstElement *element); + std::string deriveElementInfoFromName(const std::string &name) const; + GstPadProbeReturn handleProbeCb(GstPad *pad, GstPadProbeInfo *info) override; + void removeProbeCtx(GstPad *pad); + + std::optional calculateMetrics() const; + + struct ProbeCtx + { + std::shared_ptr profiler; + std::string stage; + std::string info; + GstPad *pad; + gulong id; + }; + + GstElement *m_pipeline = nullptr; + std::shared_ptr m_gstWrapper; + std::shared_ptr m_glibWrapper; + std::shared_ptr m_profiler; + std::vector m_probeCtxs; + bool m_enabled = false; +}; +} // namespace firebolt::rialto::server +#endif // FIREBOLT_RIALTO_SERVER_GST_PROFILER_H_ diff --git a/media/server/gstplayer/include/IFlushOnPrerollController.h b/media/server/gstplayer/include/IFlushOnPrerollController.h index 64379b7af..76469a523 100644 --- a/media/server/gstplayer/include/IFlushOnPrerollController.h +++ b/media/server/gstplayer/include/IFlushOnPrerollController.h @@ -34,9 +34,11 @@ class IFlushOnPrerollController public: virtual ~IFlushOnPrerollController() = default; - virtual bool shouldPostponeFlush(const MediaSourceType &type) const = 0; - virtual void setFlushing(const MediaSourceType &type, const GstState ¤tPipelineState) = 0; + virtual void waitIfRequired(const MediaSourceType &type) = 0; + virtual void setFlushing(const MediaSourceType &type) = 0; + virtual void setPrerolling() = 0; virtual void stateReached(const GstState &newPipelineState) = 0; + virtual void setTargetState(const GstState &state) = 0; virtual void reset() = 0; }; } // namespace firebolt::rialto::server diff --git a/media/server/gstplayer/include/IGstDispatcherThread.h b/media/server/gstplayer/include/IGstDispatcherThread.h index 15d6083e1..a72e2a0ad 100644 --- a/media/server/gstplayer/include/IGstDispatcherThread.h +++ b/media/server/gstplayer/include/IGstDispatcherThread.h @@ -20,6 +20,7 @@ #ifndef FIREBOLT_RIALTO_SERVER_I_GST_DISPATCHER_THREAD_H_ #define FIREBOLT_RIALTO_SERVER_I_GST_DISPATCHER_THREAD_H_ +#include "IFlushOnPrerollController.h" #include "IGstDispatcherThreadClient.h" #include "IGstWrapper.h" #include @@ -36,6 +37,7 @@ class IGstDispatcherThreadFactory virtual std::unique_ptr createGstDispatcherThread(IGstDispatcherThreadClient &client, GstElement *pipeline, + const std::shared_ptr &flushOnPrerollController, const std::shared_ptr &gstWrapper) const = 0; }; diff --git a/media/server/gstplayer/include/IGstGenericPlayerPrivate.h b/media/server/gstplayer/include/IGstGenericPlayerPrivate.h index 1848c8960..fb48a2816 100644 --- a/media/server/gstplayer/include/IGstGenericPlayerPrivate.h +++ b/media/server/gstplayer/include/IGstGenericPlayerPrivate.h @@ -174,9 +174,9 @@ class IGstGenericPlayerPrivate * * @param[in] newState : The desired state. * - * @retval true on success. + * @retval state change status */ - virtual bool changePipelineState(GstState newState) = 0; + virtual GstStateChangeReturn changePipelineState(GstState newState) = 0; /** * @brief Gets the current position of the element @@ -197,6 +197,16 @@ class IGstGenericPlayerPrivate */ virtual void stopPositionReportingAndCheckAudioUnderflowTimer() = 0; + /** + * @brief Starts notify playback info timer. Called by the worker thread. + */ + virtual void startNotifyPlaybackInfoTimer() = 0; + + /** + * @brief Stops notify playback info timer. Called by the worker thread. + */ + virtual void stopNotifyPlaybackInfoTimer() = 0; + /** * @brief Starts subtitle clock resync. Called by the worker thread. */ @@ -271,13 +281,6 @@ class IGstGenericPlayerPrivate */ virtual GstElement *getSink(const MediaSourceType &mediaSourceType) const = 0; - /** - * @brief Sets the audio and video flags on the pipeline based on the input. - * - * @param[in] enableAudio : Whether to enable audio flags. - */ - virtual void setPlaybinFlags(bool enableAudio) = 0; - /** * @brief Pushes GstSample if playback position has changed or new segment needs to be sent. * @@ -311,19 +314,6 @@ class IGstGenericPlayerPrivate */ virtual void setSourceFlushed(const MediaSourceType &mediaSourceType) = 0; - /** - * @brief Postpones flush for the given source type - * - * @param[in] mediaSourceType : the source type that has been flushed - * @param[in] resetTime : whether to reset the time after flush - */ - virtual void postponeFlush(const MediaSourceType &mediaSourceType, bool resetTime) = 0; - - /** - * @brief Queues postponed flushes for execution - */ - virtual void executePostponedFlushes() = 0; - /** * @brief Sends PlaybackInfo notification. Called by the worker thread. */ diff --git a/media/server/gstplayer/include/IGstProfiler.h b/media/server/gstplayer/include/IGstProfiler.h new file mode 100644 index 000000000..8ea182005 --- /dev/null +++ b/media/server/gstplayer/include/IGstProfiler.h @@ -0,0 +1,75 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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. + */ + +#ifndef FIREBOLT_RIALTO_SERVER_I_GST_PROFILER_H_ +#define FIREBOLT_RIALTO_SERVER_I_GST_PROFILER_H_ + +#include "IGlibWrapper.h" +#include "IGstWrapper.h" +#include "IProfiler.h" + +#include + +#include +#include +#include +#include +#include + +namespace firebolt::rialto::server +{ +class IGstProfiler; + +class IGstProfilerFactory +{ +public: + using IGstWrapper = firebolt::rialto::wrappers::IGstWrapper; + using IGlibWrapper = firebolt::rialto::wrappers::IGlibWrapper; + + IGstProfilerFactory() = default; + virtual ~IGstProfilerFactory() = default; + + static std::shared_ptr getFactory(); + + virtual std::unique_ptr createGstProfiler(GstElement *pipeline, + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper) const = 0; +}; + +class IGstProfiler +{ +public: + using RecordId = std::uint64_t; + using Record = firebolt::rialto::common::IProfiler::Record; + + virtual ~IGstProfiler() = default; + + virtual std::optional createRecord(const std::string &stage) = 0; + virtual std::optional createRecord(const std::string &stage, const std::string &info) = 0; + + virtual void scheduleGstElementRecord(GstElement *element) = 0; + virtual std::vector getRecords() const = 0; + + virtual void logRecord(RecordId id) = 0; + virtual void dumpToFile() const = 0; + virtual void logPipelineSummary() const = 0; +}; +} // namespace firebolt::rialto::server + +#endif // FIREBOLT_RIALTO_SERVER_I_GST_PROFILER_H_ diff --git a/media/server/gstplayer/include/IGstProfilerPrivate.h b/media/server/gstplayer/include/IGstProfilerPrivate.h new file mode 100644 index 000000000..c5e5ccfc1 --- /dev/null +++ b/media/server/gstplayer/include/IGstProfilerPrivate.h @@ -0,0 +1,50 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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. + */ + +#ifndef FIREBOLT_RIALTO_SERVER_I_GST_PROFILER_PRIVATE_H_ +#define FIREBOLT_RIALTO_SERVER_I_GST_PROFILER_PRIVATE_H_ + +#include + +namespace firebolt::rialto::server +{ +class IGstProfilerPrivate +{ +public: + IGstProfilerPrivate() = default; + virtual ~IGstProfilerPrivate() = default; + + IGstProfilerPrivate(const IGstProfilerPrivate &) = delete; + IGstProfilerPrivate &operator=(const IGstProfilerPrivate &) = delete; + IGstProfilerPrivate(IGstProfilerPrivate &&) = delete; + IGstProfilerPrivate &operator=(IGstProfilerPrivate &&) = delete; + + /** + * @brief Handles pad probe callback. + * + * @param[in] pad : Pad where the probe is attached. + * @param[in] info : Probe info. + * + * @retval The probe return code. + */ + virtual GstPadProbeReturn handleProbeCb(GstPad *pad, GstPadProbeInfo *info) = 0; +}; +} // namespace firebolt::rialto::server + +#endif // FIREBOLT_RIALTO_SERVER_I_GST_PROFILER_PRIVATE_H_ diff --git a/media/server/gstplayer/include/Utils.h b/media/server/gstplayer/include/Utils.h index 81037266d..7351ec903 100644 --- a/media/server/gstplayer/include/Utils.h +++ b/media/server/gstplayer/include/Utils.h @@ -32,6 +32,7 @@ namespace firebolt::rialto::server bool isVideoDecoder(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); bool isAudioDecoder(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); bool isVideoParser(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); +bool isAudioParser(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); bool isVideoSink(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); bool isAudioSink(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); bool isSink(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); diff --git a/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h b/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h index 527261652..f8de67cb4 100644 --- a/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h +++ b/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h @@ -176,19 +176,6 @@ class IGenericPlayerTaskFactory createReadShmDataAndAttachSamples(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, const std::shared_ptr &dataReader) const = 0; - /** - * @brief Creates a Remove Source task. - * - * @param[in] context : The GstPlayer context - * @param[in] player : The GstGenericPlayer instance - * @param[in] type : The media source type to remove - * - * @retval the new Remove Source task instance. - */ - virtual std::unique_ptr createRemoveSource(GenericPlayerContext &context, - IGstGenericPlayerPrivate &player, - const firebolt::rialto::MediaSourceType &type) const = 0; - /** * @brief Creates a ReportPosition task. * @@ -429,12 +416,13 @@ class IGenericPlayerTaskFactory * @param[in] context : The GstPlayer context * @param[in] type : The media source type to flush * @param[in] resetTime : True if time should be reset + * @param[in] isAsync : True if flushed source is asynchronous * * @retval the new Flush task instance. */ virtual std::unique_ptr createFlush(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - const firebolt::rialto::MediaSourceType &type, - bool resetTime) const = 0; + const firebolt::rialto::MediaSourceType &type, bool resetTime, + bool isAsync) const = 0; /** * @brief Creates a SetSourcePosition task. diff --git a/media/server/gstplayer/include/tasks/generic/CheckAudioUnderflow.h b/media/server/gstplayer/include/tasks/generic/CheckAudioUnderflow.h index 3f0122320..919ee1de4 100644 --- a/media/server/gstplayer/include/tasks/generic/CheckAudioUnderflow.h +++ b/media/server/gstplayer/include/tasks/generic/CheckAudioUnderflow.h @@ -33,7 +33,7 @@ class CheckAudioUnderflow : public IPlayerTask { public: CheckAudioUnderflow(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, IGstGenericPlayerClient *client, - std::shared_ptr gstWrapper); + const std::shared_ptr &gstWrapper); ~CheckAudioUnderflow() override = default; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/generic/Eos.h b/media/server/gstplayer/include/tasks/generic/Eos.h index da51c6741..b8176c7dc 100644 --- a/media/server/gstplayer/include/tasks/generic/Eos.h +++ b/media/server/gstplayer/include/tasks/generic/Eos.h @@ -33,7 +33,7 @@ class Eos : public IPlayerTask { public: Eos(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - std::shared_ptr gstWrapper, + const std::shared_ptr &gstWrapper, const firebolt::rialto::MediaSourceType &type); ~Eos() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/generic/Flush.h b/media/server/gstplayer/include/tasks/generic/Flush.h index 6345b1d00..f3729068e 100644 --- a/media/server/gstplayer/include/tasks/generic/Flush.h +++ b/media/server/gstplayer/include/tasks/generic/Flush.h @@ -33,8 +33,8 @@ class Flush : public IPlayerTask { public: Flush(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, IGstGenericPlayerClient *client, - std::shared_ptr gstWrapper, const MediaSourceType &type, - bool resetTime); + const std::shared_ptr &gstWrapper, const MediaSourceType &type, + bool resetTime, bool isAsync); ~Flush() override; void execute() const override; @@ -45,6 +45,7 @@ class Flush : public IPlayerTask std::shared_ptr m_gstWrapper; MediaSourceType m_type; bool m_resetTime; + bool m_isAsync; }; } // namespace firebolt::rialto::server::tasks::generic diff --git a/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h b/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h index 49ec0db1b..aa9a06e55 100644 --- a/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h +++ b/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h @@ -65,8 +65,6 @@ class GenericPlayerTaskFactory : public IGenericPlayerTaskFactory std::unique_ptr createReadShmDataAndAttachSamples(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, const std::shared_ptr &dataReader) const override; - std::unique_ptr createRemoveSource(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - const firebolt::rialto::MediaSourceType &type) const override; std::unique_ptr createReportPosition(GenericPlayerContext &context, IGstGenericPlayerPrivate &player) const override; std::unique_ptr createCheckAudioUnderflow(GenericPlayerContext &context, @@ -108,8 +106,8 @@ class GenericPlayerTaskFactory : public IGenericPlayerTaskFactory IGstGenericPlayerPrivate &player) const override; std::unique_ptr createPing(std::unique_ptr &&heartbeatHandler) const override; std::unique_ptr createFlush(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - const firebolt::rialto::MediaSourceType &type, - bool resetTime) const override; + const firebolt::rialto::MediaSourceType &type, bool resetTime, + bool isAsync) const override; std::unique_ptr createSetSourcePosition(GenericPlayerContext &context, const firebolt::rialto::MediaSourceType &type, std::int64_t position, bool resetTime, double appliedRate, diff --git a/media/server/gstplayer/include/tasks/generic/ProcessAudioGap.h b/media/server/gstplayer/include/tasks/generic/ProcessAudioGap.h index 4f614172a..b21285edd 100644 --- a/media/server/gstplayer/include/tasks/generic/ProcessAudioGap.h +++ b/media/server/gstplayer/include/tasks/generic/ProcessAudioGap.h @@ -36,7 +36,7 @@ class ProcessAudioGap : public IPlayerTask ProcessAudioGap(GenericPlayerContext &context, const std::shared_ptr &gstWrapper, const std::shared_ptr &glibWrapper, - const std::shared_ptr rdkGstreamerUtilsWrapper, + const std::shared_ptr &rdkGstreamerUtilsWrapper, std::int64_t position, std::uint32_t duration, std::int64_t discontinuityGap, bool audioAac); ~ProcessAudioGap() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/generic/RemoveSource.h b/media/server/gstplayer/include/tasks/generic/RemoveSource.h deleted file mode 100644 index 7d1923ff6..000000000 --- a/media/server/gstplayer/include/tasks/generic/RemoveSource.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Sky UK - * - * 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. - */ - -#ifndef FIREBOLT_RIALTO_SERVER_TASKS_GENERIC_REMOVE_SOURCE_H_ -#define FIREBOLT_RIALTO_SERVER_TASKS_GENERIC_REMOVE_SOURCE_H_ - -#include "GenericPlayerContext.h" -#include "IGstGenericPlayerClient.h" -#include "IGstGenericPlayerPrivate.h" -#include "IGstWrapper.h" -#include "IPlayerTask.h" -#include - -namespace firebolt::rialto::server::tasks::generic -{ -class RemoveSource : public IPlayerTask -{ -public: - RemoveSource(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, IGstGenericPlayerClient *client, - std::shared_ptr gstWrapper, const MediaSourceType &type); - ~RemoveSource() override; - void execute() const override; - -private: - GenericPlayerContext &m_context; - IGstGenericPlayerPrivate &m_player; - IGstGenericPlayerClient *m_gstPlayerClient; - std::shared_ptr m_gstWrapper; - MediaSourceType m_type; -}; -} // namespace firebolt::rialto::server::tasks::generic - -#endif // FIREBOLT_RIALTO_SERVER_TASKS_GENERIC_REMOVE_SOURCE_H_ diff --git a/media/server/gstplayer/include/tasks/generic/SetMute.h b/media/server/gstplayer/include/tasks/generic/SetMute.h index 17892dcc1..f2662d819 100644 --- a/media/server/gstplayer/include/tasks/generic/SetMute.h +++ b/media/server/gstplayer/include/tasks/generic/SetMute.h @@ -33,8 +33,8 @@ class SetMute : public IPlayerTask { public: SetMute(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, const MediaSourceType &mediaSourceType, bool mute); ~SetMute() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/generic/SetPlaybackRate.h b/media/server/gstplayer/include/tasks/generic/SetPlaybackRate.h index 8adbddcf7..7f121fc7f 100644 --- a/media/server/gstplayer/include/tasks/generic/SetPlaybackRate.h +++ b/media/server/gstplayer/include/tasks/generic/SetPlaybackRate.h @@ -31,8 +31,9 @@ namespace firebolt::rialto::server::tasks::generic class SetPlaybackRate : public IPlayerTask { public: - SetPlaybackRate(GenericPlayerContext &context, std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, double rate); + SetPlaybackRate(GenericPlayerContext &context, + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, double rate); ~SetPlaybackRate() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/generic/SetPosition.h b/media/server/gstplayer/include/tasks/generic/SetPosition.h index 0be5a265f..ec90760e2 100644 --- a/media/server/gstplayer/include/tasks/generic/SetPosition.h +++ b/media/server/gstplayer/include/tasks/generic/SetPosition.h @@ -34,7 +34,7 @@ class SetPosition : public IPlayerTask { public: SetPosition(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, IGstGenericPlayerClient *client, - std::shared_ptr gstWrapper, std::int64_t position); + const std::shared_ptr &gstWrapper, std::int64_t position); ~SetPosition() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/generic/SetTextTrackIdentifier.h b/media/server/gstplayer/include/tasks/generic/SetTextTrackIdentifier.h index b0554d310..bcb866fb1 100644 --- a/media/server/gstplayer/include/tasks/generic/SetTextTrackIdentifier.h +++ b/media/server/gstplayer/include/tasks/generic/SetTextTrackIdentifier.h @@ -33,7 +33,7 @@ class SetTextTrackIdentifier : public IPlayerTask { public: SetTextTrackIdentifier(GenericPlayerContext &context, - std::shared_ptr glibWrapper, + const std::shared_ptr &glibWrapper, const std::string &textTrackIdentifier); ~SetTextTrackIdentifier() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/generic/SetVolume.h b/media/server/gstplayer/include/tasks/generic/SetVolume.h index 202dac828..416cbfa19 100644 --- a/media/server/gstplayer/include/tasks/generic/SetVolume.h +++ b/media/server/gstplayer/include/tasks/generic/SetVolume.h @@ -33,9 +33,9 @@ class SetVolume : public IPlayerTask { public: SetVolume(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, - std::shared_ptr rdkGstreamerUtilsWrapper, + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, + const std::shared_ptr &rdkGstreamerUtilsWrapper, double targetVolume, uint32_t volumeDuration, firebolt::rialto::EaseType easeType); ~SetVolume() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/generic/SetupElement.h b/media/server/gstplayer/include/tasks/generic/SetupElement.h index c50beddcf..c41e46274 100644 --- a/media/server/gstplayer/include/tasks/generic/SetupElement.h +++ b/media/server/gstplayer/include/tasks/generic/SetupElement.h @@ -33,8 +33,9 @@ namespace firebolt::rialto::server::tasks::generic class SetupElement : public IPlayerTask { public: - SetupElement(GenericPlayerContext &context, std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, + SetupElement(GenericPlayerContext &context, + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, IGstGenericPlayerPrivate &player, GstElement *element); ~SetupElement() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/generic/UpdatePlaybackGroup.h b/media/server/gstplayer/include/tasks/generic/UpdatePlaybackGroup.h index f1cb25000..7b4a9c557 100644 --- a/media/server/gstplayer/include/tasks/generic/UpdatePlaybackGroup.h +++ b/media/server/gstplayer/include/tasks/generic/UpdatePlaybackGroup.h @@ -34,9 +34,9 @@ class UpdatePlaybackGroup : public IPlayerTask { public: UpdatePlaybackGroup(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, GstElement *typefind, - const GstCaps *caps); + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, + GstElement *typefind, const GstCaps *caps); ~UpdatePlaybackGroup() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/webAudio/Eos.h b/media/server/gstplayer/include/tasks/webAudio/Eos.h index 230628fa4..2d7605bc6 100644 --- a/media/server/gstplayer/include/tasks/webAudio/Eos.h +++ b/media/server/gstplayer/include/tasks/webAudio/Eos.h @@ -32,7 +32,7 @@ namespace firebolt::rialto::server::tasks::webaudio class Eos : public IPlayerTask { public: - Eos(WebAudioPlayerContext &context, std::shared_ptr gstWrapper); + Eos(WebAudioPlayerContext &context, const std::shared_ptr &gstWrapper); ~Eos() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/webAudio/HandleBusMessage.h b/media/server/gstplayer/include/tasks/webAudio/HandleBusMessage.h index a45ccc97f..2820523de 100644 --- a/media/server/gstplayer/include/tasks/webAudio/HandleBusMessage.h +++ b/media/server/gstplayer/include/tasks/webAudio/HandleBusMessage.h @@ -35,8 +35,8 @@ class HandleBusMessage : public IPlayerTask { public: HandleBusMessage(WebAudioPlayerContext &context, IGstWebAudioPlayerPrivate &player, IGstWebAudioPlayerClient *client, - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, GstMessage *message); + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, GstMessage *message); ~HandleBusMessage() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/webAudio/SetCaps.h b/media/server/gstplayer/include/tasks/webAudio/SetCaps.h index d4b7e76b1..0d74701de 100644 --- a/media/server/gstplayer/include/tasks/webAudio/SetCaps.h +++ b/media/server/gstplayer/include/tasks/webAudio/SetCaps.h @@ -32,9 +32,9 @@ namespace firebolt::rialto::server::tasks::webaudio class SetCaps : public IPlayerTask { public: - SetCaps(WebAudioPlayerContext &context, std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, const std::string &audioMimeType, - std::weak_ptr config); + SetCaps(WebAudioPlayerContext &context, const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, + const std::string &audioMimeType, std::weak_ptr config); ~SetCaps() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/webAudio/SetVolume.h b/media/server/gstplayer/include/tasks/webAudio/SetVolume.h index eddf127d7..42d34cfe5 100644 --- a/media/server/gstplayer/include/tasks/webAudio/SetVolume.h +++ b/media/server/gstplayer/include/tasks/webAudio/SetVolume.h @@ -30,8 +30,8 @@ namespace firebolt::rialto::server::tasks::webaudio class SetVolume : public IPlayerTask { public: - SetVolume(WebAudioPlayerContext &context, std::shared_ptr gstWrapper, - double volume); + SetVolume(WebAudioPlayerContext &context, + const std::shared_ptr &gstWrapper, double volume); ~SetVolume() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/webAudio/WriteBuffer.h b/media/server/gstplayer/include/tasks/webAudio/WriteBuffer.h index c87e0a2ea..8588d811e 100644 --- a/media/server/gstplayer/include/tasks/webAudio/WriteBuffer.h +++ b/media/server/gstplayer/include/tasks/webAudio/WriteBuffer.h @@ -32,8 +32,9 @@ namespace firebolt::rialto::server::tasks::webaudio class WriteBuffer : public IPlayerTask { public: - WriteBuffer(WebAudioPlayerContext &context, std::shared_ptr gstWrapper, - uint8_t *mainPtr, uint32_t mainLength, uint8_t *wrapPtr, uint32_t wrapLength); + WriteBuffer(WebAudioPlayerContext &context, + const std::shared_ptr &gstWrapper, uint8_t *mainPtr, + uint32_t mainLength, uint8_t *wrapPtr, uint32_t wrapLength); ~WriteBuffer() override; void execute() const override; diff --git a/media/server/gstplayer/interface/IGstGenericPlayer.h b/media/server/gstplayer/interface/IGstGenericPlayer.h index e01edd364..f0d5111a1 100644 --- a/media/server/gstplayer/interface/IGstGenericPlayer.h +++ b/media/server/gstplayer/interface/IGstGenericPlayer.h @@ -59,12 +59,13 @@ class IGstGenericPlayerFactory * @param[in] decryptionService : The decryption service. * @param[in] type : The media type the gstreamer player shall support. * @param[in] videoRequirements : The video requirements for the playback. + * @param[in] isLive : Indicates if the media is live. * * @retval the new player instance or null on error. */ virtual std::unique_ptr createGstGenericPlayer(IGstGenericPlayerClient *client, IDecryptionService &decryptionService, MediaType type, - const VideoRequirements &videoRequirements, + const VideoRequirements &videoRequirements, bool isLive, const std::shared_ptr &rdkGstreamerUtilsWrapperFactory) = 0; }; @@ -88,14 +89,6 @@ class IGstGenericPlayer */ virtual void attachSource(const std::unique_ptr &mediaSource) = 0; - /** - * @brief Unattaches a source. - * - * @param[in] mediaSourceType : The media source type. - * - */ - virtual void removeSource(const MediaSourceType &mediaSourceType) = 0; - /** * @brief Handles notification that all sources were attached * @@ -105,14 +98,15 @@ class IGstGenericPlayer /** * @brief Starts playback of the media. * - * This method is considered to be asynchronous and MUST NOT block - * but should request playback and then return. - * * Once the backend is successfully playing it should notify the - * media player client of playback state PlaybackState::PLAYING. + * media player client of playback state + * IMediaPipelineClient::PlaybackState::PLAYING. + * + * @param[out] async : True if play method call is asynchronous * + * @retval true on success. */ - virtual void play() = 0; + virtual void play(bool &async) = 0; /** * @brief Pauses playback of the media. @@ -193,6 +187,15 @@ class IGstGenericPlayer */ virtual bool getPosition(std::int64_t &position) = 0; + /** + * @brief Get the playback duration in nanoseconds. + * + * @param[out] duration : The playback duration in nanoseconds. + * + * @retval True on success + */ + virtual bool getDuration(std::int64_t &duration) = 0; + /** * @brief Sets the "Immediate Output" property for this source. * diff --git a/media/server/gstplayer/source/CapsBuilder.cpp b/media/server/gstplayer/source/CapsBuilder.cpp index 4491b3656..647b0353b 100644 --- a/media/server/gstplayer/source/CapsBuilder.cpp +++ b/media/server/gstplayer/source/CapsBuilder.cpp @@ -24,8 +24,8 @@ namespace firebolt::rialto::server { -MediaSourceCapsBuilder::MediaSourceCapsBuilder(std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, +MediaSourceCapsBuilder::MediaSourceCapsBuilder(const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, const firebolt::rialto::IMediaPipeline::MediaSourceAV &source) : m_gstWrapper(gstWrapper), m_glibWrapper(glibWrapper), m_attachedSource(source) { @@ -92,8 +92,9 @@ void MediaSourceCapsBuilder::addStreamFormatToCaps(GstCaps *caps) const } MediaSourceAudioCapsBuilder::MediaSourceAudioCapsBuilder( - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, const IMediaPipeline::MediaSourceAudio &source) + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, + const IMediaPipeline::MediaSourceAudio &source) : MediaSourceCapsBuilder(gstWrapper, glibWrapper, source), m_attachedAudioSource(source) { } @@ -226,8 +227,9 @@ void MediaSourceAudioCapsBuilder::addFlacSpecificData(GstCaps *caps) const } MediaSourceVideoCapsBuilder::MediaSourceVideoCapsBuilder( - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, const IMediaPipeline::MediaSourceVideo &source) + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, + const IMediaPipeline::MediaSourceVideo &source) : MediaSourceCapsBuilder(gstWrapper, glibWrapper, source), m_attachedVideoSource(source) { } @@ -244,8 +246,8 @@ GstCaps *MediaSourceVideoCapsBuilder::buildCaps() } MediaSourceVideoDolbyVisionCapsBuilder::MediaSourceVideoDolbyVisionCapsBuilder( - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, const IMediaPipeline::MediaSourceVideoDolbyVision &source) : MediaSourceVideoCapsBuilder(gstWrapper, glibWrapper, source), m_attachedDolbySource(source) { diff --git a/media/server/gstplayer/source/FlushOnPrerollController.cpp b/media/server/gstplayer/source/FlushOnPrerollController.cpp index 8ffce5cbc..1218b892c 100644 --- a/media/server/gstplayer/source/FlushOnPrerollController.cpp +++ b/media/server/gstplayer/source/FlushOnPrerollController.cpp @@ -18,41 +18,67 @@ */ #include "FlushOnPrerollController.h" +#include "RialtoServerLogging.h" +#include "TypeConverters.h" +#include namespace firebolt::rialto::server { -bool FlushOnPrerollController::shouldPostponeFlush(const MediaSourceType &type) const +void FlushOnPrerollController::waitIfRequired(const MediaSourceType &type) { std::unique_lock lock{m_mutex}; - return m_isPrerolled && m_flushingSources.find(type) != m_flushingSources.end(); + RIALTO_SERVER_LOG_DEBUG("FlushOnPrerollController: Waiting if required for %s source entry", + common::convertMediaSourceType(type)); + m_conditionVariable.wait(lock, [this, &type]() + // coverity[MISSING_LOCK:FALSE] + { return !m_isPrerolled || m_flushingSources.find(type) == m_flushingSources.end(); }); + RIALTO_SERVER_LOG_DEBUG("FlushOnPrerollController: Waiting if required for %s source exit", + common::convertMediaSourceType(type)); } -void FlushOnPrerollController::setFlushing(const MediaSourceType &type, const GstState ¤tPipelineState) +void FlushOnPrerollController::setFlushing(const MediaSourceType &type) { + RIALTO_SERVER_LOG_DEBUG("FlushOnPrerollController: Set flushing for: %s", common::convertMediaSourceType(type)); std::unique_lock lock{m_mutex}; m_flushingSources.insert(type); +} + +void FlushOnPrerollController::setPrerolling() +{ + RIALTO_SERVER_LOG_DEBUG("FlushOnPrerollController: Set prerolling"); + std::unique_lock lock{m_mutex}; m_isPrerolled = false; - if (!m_targetState.has_value()) - { - m_targetState = currentPipelineState; - } + m_conditionVariable.notify_all(); } void FlushOnPrerollController::stateReached(const GstState &newPipelineState) { + RIALTO_SERVER_LOG_DEBUG("FlushOnPrerollController: State reached %s", gst_element_state_get_name(newPipelineState)); std::unique_lock lock{m_mutex}; m_isPrerolled = true; if (m_targetState.has_value() && newPipelineState == m_targetState.value()) { + RIALTO_SERVER_LOG_DEBUG("FlushOnPrerollController: Clear state after state reached %s", + gst_element_state_get_name(newPipelineState)); m_flushingSources.clear(); - m_targetState = std::nullopt; } + m_conditionVariable.notify_all(); +} + +void FlushOnPrerollController::setTargetState(const GstState &state) +{ + std::unique_lock lock{m_mutex}; + m_targetState = state; + RIALTO_SERVER_LOG_DEBUG("FlushOnPrerollController: Set target state %s", gst_element_state_get_name(state)); } void FlushOnPrerollController::reset() { + RIALTO_SERVER_LOG_DEBUG("Reset FlushOnPrerollController"); std::unique_lock lock{m_mutex}; + m_isPrerolled = false; m_flushingSources.clear(); m_targetState = std::nullopt; + m_conditionVariable.notify_all(); } } // namespace firebolt::rialto::server diff --git a/media/server/gstplayer/source/GstCapabilities.cpp b/media/server/gstplayer/source/GstCapabilities.cpp index 13173441d..c7604d171 100644 --- a/media/server/gstplayer/source/GstCapabilities.cpp +++ b/media/server/gstplayer/source/GstCapabilities.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include "GstCapabilities.h" #include "GstMimeMapping.h" @@ -274,12 +275,12 @@ std::vector GstCapabilities::getSupportedProperties(MediaSourceType { for (guint j = 0; j < nProps && !propertiesToLookFor.empty(); ++j) { - const std::string kPropName{props[j]->name}; - auto it = propertiesToLookFor.find(kPropName); + std::string propName{props[j]->name}; + auto it = propertiesToLookFor.find(propName); if (it != propertiesToLookFor.end()) { - RIALTO_SERVER_LOG_DEBUG("Found property '%s'", kPropName.c_str()); - propertiesFound.push_back(kPropName); + RIALTO_SERVER_LOG_DEBUG("Found property '%s'", propName.c_str()); + propertiesFound.push_back(std::move(propName)); propertiesToLookFor.erase(it); } } diff --git a/media/server/gstplayer/source/GstDecryptor.cpp b/media/server/gstplayer/source/GstDecryptor.cpp index 0882b36d3..d3e499e45 100644 --- a/media/server/gstplayer/source/GstDecryptor.cpp +++ b/media/server/gstplayer/source/GstDecryptor.cpp @@ -280,7 +280,7 @@ GstFlowReturn GstRialtoDecryptorPrivate::decrypt(GstBuffer *buffer, GstCaps *cap } } - if (protectionData->key && m_decryptionService->isNetflixPlayreadyKeySystem(protectionData->keySessionId)) + if (protectionData->key && m_decryptionService->isExtendedInterfaceUsed(protectionData->keySessionId)) { GstMapInfo keyMap; if (m_gstWrapper->gstBufferMap(protectionData->key, &keyMap, GST_MAP_READ)) diff --git a/media/server/gstplayer/source/GstDispatcherThread.cpp b/media/server/gstplayer/source/GstDispatcherThread.cpp index 90953a27f..289ab77d7 100644 --- a/media/server/gstplayer/source/GstDispatcherThread.cpp +++ b/media/server/gstplayer/source/GstDispatcherThread.cpp @@ -24,14 +24,17 @@ namespace firebolt::rialto::server { std::unique_ptr GstDispatcherThreadFactory::createGstDispatcherThread( IGstDispatcherThreadClient &client, GstElement *pipeline, + const std::shared_ptr &flushOnPrerollController, const std::shared_ptr &gstWrapper) const { - return std::make_unique(client, pipeline, gstWrapper); + return std::make_unique(client, pipeline, flushOnPrerollController, gstWrapper); } GstDispatcherThread::GstDispatcherThread(IGstDispatcherThreadClient &client, GstElement *pipeline, + const std::shared_ptr &flushOnPrerollController, const std::shared_ptr &gstWrapper) - : m_client{client}, m_gstWrapper{gstWrapper}, m_isGstreamerDispatcherActive{true} + : m_client{client}, m_flushOnPrerollController{flushOnPrerollController}, m_gstWrapper{gstWrapper}, + m_isGstreamerDispatcherActive{true} { RIALTO_SERVER_LOG_INFO("GstDispatcherThread is starting"); m_gstBusDispatcherThread = std::thread(&GstDispatcherThread::gstBusEventHandler, this, pipeline); @@ -80,11 +83,30 @@ void GstDispatcherThread::gstBusEventHandler(GstElement *pipeline) case GST_STATE_NULL: { m_isGstreamerDispatcherActive = false; + if (m_flushOnPrerollController) + { + m_flushOnPrerollController->reset(); + } break; } case GST_STATE_PAUSED: + { + if (m_flushOnPrerollController && pending != GST_STATE_PAUSED) + { + m_flushOnPrerollController->stateReached(newState); + } + else if (m_flushOnPrerollController && pending == GST_STATE_PAUSED) + { + m_flushOnPrerollController->setPrerolling(); + } + break; + } case GST_STATE_PLAYING: { + if (m_flushOnPrerollController) + { + m_flushOnPrerollController->stateReached(newState); + } break; } case GST_STATE_READY: diff --git a/media/server/gstplayer/source/GstGenericPlayer.cpp b/media/server/gstplayer/source/GstGenericPlayer.cpp index 1b0976a73..b3bc8fa99 100644 --- a/media/server/gstplayer/source/GstGenericPlayer.cpp +++ b/media/server/gstplayer/source/GstGenericPlayer.cpp @@ -19,11 +19,14 @@ #include #include +#include +#include #include #include "FlushWatcher.h" #include "GstDispatcherThread.h" #include "GstGenericPlayer.h" +#include "GstProfiler.h" #include "GstProtectionMetadata.h" #include "IGstTextTrackSinkFactory.h" #include "IMediaPipeline.h" @@ -78,7 +81,7 @@ std::shared_ptr IGstGenericPlayerFactory::getFactory() std::unique_ptr GstGenericPlayerFactory::createGstGenericPlayer( IGstGenericPlayerClient *client, IDecryptionService &decryptionService, MediaType type, - const VideoRequirements &videoRequirements, + const VideoRequirements &videoRequirements, bool isLive, const std::shared_ptr &rdkGstreamerUtilsWrapperFactory) { std::unique_ptr gstPlayer; @@ -103,10 +106,12 @@ std::unique_ptr GstGenericPlayerFactory::createGstGenericPlay { throw std::runtime_error("Cannot create RdkGstreamerUtilsWrapper"); } + gstPlayer = std::make_unique< - GstGenericPlayer>(client, decryptionService, type, videoRequirements, gstWrapper, glibWrapper, + GstGenericPlayer>(client, decryptionService, type, videoRequirements, isLive, gstWrapper, glibWrapper, rdkGstreamerUtilsWrapper, IGstInitialiser::instance(), std::make_unique(), - IGstSrcFactory::getFactory(), common::ITimerFactory::getFactory(), + IGstSrcFactory::getFactory(), IGstProfilerFactory::getFactory(), + common::ITimerFactory::getFactory(), std::make_unique(client, gstWrapper, glibWrapper, rdkGstreamerUtilsWrapper, IGstTextTrackSinkFactory::createFactory()), @@ -123,29 +128,35 @@ std::unique_ptr GstGenericPlayerFactory::createGstGenericPlay GstGenericPlayer::GstGenericPlayer( IGstGenericPlayerClient *client, IDecryptionService &decryptionService, MediaType type, - const VideoRequirements &videoRequirements, + const VideoRequirements &videoRequirements, bool isLive, const std::shared_ptr &gstWrapper, const std::shared_ptr &glibWrapper, const std::shared_ptr &rdkGstreamerUtilsWrapper, const IGstInitialiser &gstInitialiser, std::unique_ptr &&flushWatcher, - const std::shared_ptr &gstSrcFactory, std::shared_ptr timerFactory, + const std::shared_ptr &gstSrcFactory, + const std::shared_ptr &gstProfilerFactory, std::shared_ptr timerFactory, std::unique_ptr taskFactory, std::unique_ptr workerThreadFactory, std::unique_ptr gstDispatcherThreadFactory, std::shared_ptr gstProtectionMetadataFactory) : m_gstPlayerClient(client), m_gstWrapper{gstWrapper}, m_glibWrapper{glibWrapper}, - m_rdkGstreamerUtilsWrapper{rdkGstreamerUtilsWrapper}, m_timerFactory{timerFactory}, - m_taskFactory{std::move(taskFactory)}, m_flushWatcher{std::move(flushWatcher)} + m_rdkGstreamerUtilsWrapper{rdkGstreamerUtilsWrapper}, m_gstProfilerFactory{gstProfilerFactory}, + m_timerFactory{timerFactory}, m_taskFactory{std::move(taskFactory)}, m_flushWatcher{std::move(flushWatcher)} { RIALTO_SERVER_LOG_DEBUG("GstGenericPlayer is constructed."); gstInitialiser.waitForInitialisation(); + m_context.isLive = isLive; m_context.decryptionService = &decryptionService; if ((!gstSrcFactory) || (!(m_context.gstSrc = gstSrcFactory->getGstSrc()))) { throw std::runtime_error("Cannot create GstSrc"); } + if (!m_gstProfilerFactory) + { + throw std::runtime_error("No gst profiler factory provided"); + } if (!timerFactory) { @@ -202,8 +213,9 @@ GstGenericPlayer::GstGenericPlayer( RIALTO_SERVER_LOG_MIL("Primary video playback selected"); } - m_gstDispatcherThread = - gstDispatcherThreadFactory->createGstDispatcherThread(*this, m_context.pipeline, m_gstWrapper); + m_gstDispatcherThread = gstDispatcherThreadFactory->createGstDispatcherThread(*this, m_context.pipeline, + m_context.flushOnPrerollController, + m_gstWrapper); } GstGenericPlayer::~GstGenericPlayer() @@ -223,6 +235,12 @@ void GstGenericPlayer::initMsePipeline() // Set pipeline flags setPlaybinFlags(true); + m_context.gstProfiler = m_gstProfilerFactory->createGstProfiler(m_context.pipeline, m_gstWrapper, m_glibWrapper); + if (!m_context.gstProfiler) + { + throw std::runtime_error("Cannot create GstProfiler"); + } + // Set callbacks m_glibWrapper->gSignalConnect(m_context.pipeline, "source-setup", G_CALLBACK(&GstGenericPlayer::setupSource), this); m_glibWrapper->gSignalConnect(m_context.pipeline, "element-setup", G_CALLBACK(&GstGenericPlayer::setupElement), this); @@ -248,11 +266,13 @@ void GstGenericPlayer::initMsePipeline() GST_WARNING("Failed to set pipeline to READY state"); } RIALTO_SERVER_LOG_MIL("New RialtoServer's pipeline created"); + auto recordId = m_context.gstProfiler->createRecord("Pipeline Created"); + if (recordId) + m_context.gstProfiler->logRecord(recordId.value()); } void GstGenericPlayer::resetWorkerThread() { - m_postponedFlushes.clear(); // Shutdown task thread m_workerThread->enqueueTask(m_taskFactory->createShutdown(*this)); m_workerThread->join(); @@ -299,6 +319,16 @@ void GstGenericPlayer::termPipeline() m_gstWrapper->gstObjectUnref(m_context.videoSink); m_context.videoSink = nullptr; } + if (m_context.playbackGroup.m_curAudioPlaysinkBin) + { + m_gstWrapper->gstObjectUnref(m_context.playbackGroup.m_curAudioPlaysinkBin); + m_context.playbackGroup.m_curAudioPlaysinkBin = nullptr; + } + + auto recordId = m_context.gstProfiler->createRecord("Pipeline Terminated"); + if (recordId) + m_context.gstProfiler->logRecord(recordId.value()); + m_context.gstProfiler->dumpToFile(); // Delete the pipeline m_gstWrapper->gstObjectUnref(m_context.pipeline); @@ -351,14 +381,6 @@ void GstGenericPlayer::attachSource(const std::unique_ptrenqueueTask(m_taskFactory->createRemoveSource(m_context, *this, mediaSourceType)); - } -} - void GstGenericPlayer::allSourcesAttached() { if (m_workerThread) @@ -406,13 +428,24 @@ bool GstGenericPlayer::getPosition(std::int64_t &position) position = getPosition(m_context.pipeline); if (position == -1) { - RIALTO_SERVER_LOG_WARN("Query position failed"); return false; } return true; } +bool GstGenericPlayer::getDuration(std::int64_t &duration) +{ + // We are on main thread here, but m_context.pipeline can be used, because it's modified only in GstGenericPlayer + // constructor and destructor. GstGenericPlayer is created/destructed on main thread, so we won't have a crash here. + if (!m_context.pipeline || !m_gstWrapper->gstElementQueryDuration(m_context.pipeline, GST_FORMAT_TIME, &duration)) + { + RIALTO_SERVER_LOG_WARN("Failed to query duration"); + return false; + } + return true; +} + GstElement *GstGenericPlayer::getSink(const MediaSourceType &mediaSourceType) const { const char *kSinkName{nullptr}; @@ -425,6 +458,9 @@ GstElement *GstGenericPlayer::getSink(const MediaSourceType &mediaSourceType) co case MediaSourceType::VIDEO: kSinkName = "video-sink"; break; + case MediaSourceType::SUBTITLE: + kSinkName = "text-sink"; + break; default: break; } @@ -443,7 +479,7 @@ GstElement *GstGenericPlayer::getSink(const MediaSourceType &mediaSourceType) co RIALTO_SERVER_LOG_DEBUG("Pipeline is valid: %p", m_context.pipeline); } m_glibWrapper->gObjectGet(m_context.pipeline, kSinkName, &sink, nullptr); - if (sink) + if (sink && firebolt::rialto::MediaSourceType::SUBTITLE != mediaSourceType) { GstElement *autoSink{sink}; if (firebolt::rialto::MediaSourceType::VIDEO == mediaSourceType) @@ -469,23 +505,6 @@ void GstGenericPlayer::setSourceFlushed(const MediaSourceType &mediaSourceType) m_flushWatcher->setFlushed(mediaSourceType); } -void GstGenericPlayer::postponeFlush(const MediaSourceType &mediaSourceType, bool resetTime) -{ - m_postponedFlushes.emplace_back(std::make_pair(mediaSourceType, resetTime)); -} - -void GstGenericPlayer::executePostponedFlushes() -{ - if (m_workerThread) - { - for (const auto &[mediaSourceType, resetTime] : m_postponedFlushes) - { - m_workerThread->enqueueTask(m_taskFactory->createFlush(m_context, *this, mediaSourceType, resetTime)); - } - } - m_postponedFlushes.clear(); -} - void GstGenericPlayer::notifyPlaybackInfo() { PlaybackInfo info; @@ -645,6 +664,466 @@ GstGenericPlayer::createAudioAttributes(const std::unique_ptrm_codecParam.c_str(), pAttrib->m_samplesPerSecond, pAttrib->m_numberOfChannels, + pAttrib->m_blockAlignment); + if (pAttrib->m_codecParam.compare(0, 4, std::string("mp4a")) == 0) + { + RIALTO_SERVER_LOG_DEBUG("Using AAC"); + capsString = m_glibWrapper->gStrdupPrintf("audio/mpeg, mpegversion=4, enable-svp=(string)%s", + svpenabled ? "true" : "false"); + *audioaac = true; + } + else + { + RIALTO_SERVER_LOG_DEBUG("Using EAC3"); + capsString = m_glibWrapper->gStrdupPrintf("audio/x-eac3, framed=(boolean)true, rate=(int)%u, channels=(int)%u, " + "alignment=(string)frame, enable-svp=(string)%s", + pAttrib->m_samplesPerSecond, pAttrib->m_numberOfChannels, + svpenabled ? "true" : "false"); + *audioaac = false; + } + *appsrcCaps = m_gstWrapper->gstCapsFromString(capsString); + m_glibWrapper->gFree(capsString); +} + +void GstGenericPlayer::haltAudioPlayback() +{ + // this function comes from rdk_gstreamer_utils + if (!m_context.playbackGroup.m_curAudioPlaysinkBin || !m_context.playbackGroup.m_curAudioDecodeBin) + { + RIALTO_SERVER_LOG_ERROR("haltAudioPlayback: audio playsink bin or decode bin is null"); + return; + } + GstState currentState{GST_STATE_VOID_PENDING}, pending{GST_STATE_VOID_PENDING}; + + // Transition Playsink to Ready + if (GST_STATE_CHANGE_FAILURE == + m_gstWrapper->gstElementSetState(m_context.playbackGroup.m_curAudioPlaysinkBin, GST_STATE_READY)) + { + RIALTO_SERVER_LOG_WARN("Failed to set AudioPlaysinkBin to READY"); + return; + } + m_gstWrapper->gstElementGetState(m_context.playbackGroup.m_curAudioPlaysinkBin, ¤tState, &pending, + GST_CLOCK_TIME_NONE); + if (currentState == GST_STATE_PAUSED) + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioPlaySinkBin State = %d", currentState); + // Transition Decodebin to Paused + if (GST_STATE_CHANGE_FAILURE == + m_gstWrapper->gstElementSetState(m_context.playbackGroup.m_curAudioDecodeBin, GST_STATE_PAUSED)) + { + RIALTO_SERVER_LOG_WARN("Failed to set AudioDecodeBin to PAUSED"); + return; + } + m_gstWrapper->gstElementGetState(m_context.playbackGroup.m_curAudioDecodeBin, ¤tState, &pending, + GST_CLOCK_TIME_NONE); + if (currentState == GST_STATE_PAUSED) + RIALTO_SERVER_LOG_DEBUG("OTF -> Current DecodeBin State = %d", currentState); +} + +void GstGenericPlayer::resumeAudioPlayback() +{ + // this function comes from rdk_gstreamer_utils + if (!m_context.playbackGroup.m_curAudioPlaysinkBin || !m_context.playbackGroup.m_curAudioDecodeBin) + { + RIALTO_SERVER_LOG_ERROR("resumeAudioPlayback: audio playsink bin or decode bin is null"); + return; + } + GstState currentState{GST_STATE_VOID_PENDING}, pending{GST_STATE_VOID_PENDING}; + m_gstWrapper->gstElementSyncStateWithParent(m_context.playbackGroup.m_curAudioPlaysinkBin); + m_gstWrapper->gstElementGetState(m_context.playbackGroup.m_curAudioPlaysinkBin, ¤tState, &pending, + GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> AudioPlaysinkbin State = %d Pending = %d", currentState, pending); + m_gstWrapper->gstElementSyncStateWithParent(m_context.playbackGroup.m_curAudioDecodeBin); + m_gstWrapper->gstElementGetState(m_context.playbackGroup.m_curAudioDecodeBin, ¤tState, &pending, + GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> Decodebin State = %d Pending = %d", currentState, pending); +} + +void GstGenericPlayer::firstTimeSwitchFromAC3toAAC(GstCaps *newAudioCaps) +{ + // this function comes from rdk_gstreamer_utils + if (!m_context.playbackGroup.m_curAudioTypefind || !m_context.playbackGroup.m_curAudioDecodeBin) + { + RIALTO_SERVER_LOG_ERROR("firstTimeSwitchFromAC3toAAC: audio typefind or decode bin is null"); + return; + } + GstState currentState{GST_STATE_VOID_PENDING}, pending{GST_STATE_VOID_PENDING}; + GstPad *pTypfdSrcPad = NULL; + GstPad *pTypfdSrcPeerPad = NULL; + GstPad *pNewAudioDecoderSrcPad = NULL; + GstElement *newAudioParse = NULL; + GstElement *newAudioDecoder = NULL; + GstElement *newQueue = NULL; + gboolean linkRet = false; + + /* Get the SinkPad of ASink - pTypfdSrcPeerPad */ + if ((pTypfdSrcPad = m_gstWrapper->gstElementGetStaticPad(m_context.playbackGroup.m_curAudioTypefind, "src")) != + NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current Typefind SrcPad = %p", pTypfdSrcPad); + if ((pTypfdSrcPeerPad = m_gstWrapper->gstPadGetPeer(pTypfdSrcPad)) != NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current Typefind Src Downstream Element Pad = %p", pTypfdSrcPeerPad); + // AudioDecoder Downstream Unlink + if (m_gstWrapper->gstPadUnlink(pTypfdSrcPad, pTypfdSrcPeerPad) == FALSE) + RIALTO_SERVER_LOG_DEBUG("OTF -> Typefind Downstream Unlink Failed"); + newAudioParse = m_gstWrapper->gstElementFactoryMake("aacparse", "aacparse"); + newAudioDecoder = m_gstWrapper->gstElementFactoryMake("avdec_aac", "avdec_aac"); + newQueue = m_gstWrapper->gstElementFactoryMake("queue", "aqueue"); + // Add new Decoder to Decodebin + if (m_gstWrapper->gstBinAdd(GST_BIN(m_context.playbackGroup.m_curAudioDecodeBin.load()), newAudioDecoder) == TRUE) + { + RIALTO_SERVER_LOG_DEBUG("OTF -> Added New AudioDecoder = %p", newAudioDecoder); + } + // Add new Parser to Decodebin + if (m_gstWrapper->gstBinAdd(GST_BIN(m_context.playbackGroup.m_curAudioDecodeBin.load()), newAudioParse) == TRUE) + { + RIALTO_SERVER_LOG_DEBUG("OTF -> Added New AudioParser = %p", newAudioParse); + } + // Add new Queue to Decodebin + if (m_gstWrapper->gstBinAdd(GST_BIN(m_context.playbackGroup.m_curAudioDecodeBin.load()), newQueue) == TRUE) + { + RIALTO_SERVER_LOG_DEBUG("OTF -> Added New queue = %p", newQueue); + } + if ((pNewAudioDecoderSrcPad = m_gstWrapper->gstElementGetStaticPad(newAudioDecoder, "src")) != NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioDecoder Src Pad = %p", pNewAudioDecoderSrcPad); + // Connect decoder to ASINK + if (m_gstWrapper->gstPadLink(pNewAudioDecoderSrcPad, pTypfdSrcPeerPad) != GST_PAD_LINK_OK) + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioDecoder Downstream Link Failed"); + linkRet = m_gstWrapper->gstElementLink(newAudioParse, newQueue) && + m_gstWrapper->gstElementLink(newQueue, newAudioDecoder); + if (!linkRet) + RIALTO_SERVER_LOG_DEBUG("OTF -> Downstream Link Failed for typefind, parser, decoder"); + /* Force Caps */ + RIALTO_SERVER_LOG_DEBUG("OTF -> Typefind Setting to READY"); + if (GST_STATE_CHANGE_FAILURE == + m_gstWrapper->gstElementSetState(m_context.playbackGroup.m_curAudioTypefind, GST_STATE_READY)) + { + RIALTO_SERVER_LOG_WARN("Failed to set Typefind to READY"); + m_gstWrapper->gstObjectUnref(pTypfdSrcPad); + m_gstWrapper->gstObjectUnref(pTypfdSrcPeerPad); + m_gstWrapper->gstObjectUnref(pNewAudioDecoderSrcPad); + return; + } + m_glibWrapper->gObjectSet(G_OBJECT(m_context.playbackGroup.m_curAudioTypefind), "force-caps", newAudioCaps, NULL); + m_gstWrapper->gstElementSyncStateWithParent(m_context.playbackGroup.m_curAudioTypefind); + m_gstWrapper->gstElementGetState(m_context.playbackGroup.m_curAudioTypefind, ¤tState, &pending, + GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> New Typefind State = %d Pending = %d", currentState, pending); + RIALTO_SERVER_LOG_DEBUG("OTF -> Typefind Syncing with Parent"); + m_context.playbackGroup.m_linkTypefindParser = true; + /* Update the state */ + m_gstWrapper->gstElementSyncStateWithParent(newAudioDecoder); + m_gstWrapper->gstElementGetState(newAudioDecoder, ¤tState, &pending, GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioDecoder State = %d Pending = %d", currentState, pending); + m_gstWrapper->gstElementSyncStateWithParent(newQueue); + m_gstWrapper->gstElementGetState(newQueue, ¤tState, &pending, GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> New queue State = %d Pending = %d", currentState, pending); + m_gstWrapper->gstElementSyncStateWithParent(newAudioParse); + m_gstWrapper->gstElementGetState(newAudioParse, ¤tState, &pending, GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioParser State = %d Pending = %d", currentState, pending); + m_gstWrapper->gstObjectUnref(pTypfdSrcPad); + m_gstWrapper->gstObjectUnref(pTypfdSrcPeerPad); + m_gstWrapper->gstObjectUnref(pNewAudioDecoderSrcPad); + return; +} + +bool GstGenericPlayer::switchAudioCodec(bool isAudioAAC, GstCaps *newAudioCaps) +{ // this function comes from rdk_gstreamer_utils + bool ret = false; + RIALTO_SERVER_LOG_DEBUG("Current Audio Codec AAC = %d Same as Incoming audio Codec AAC = %d", + m_context.playbackGroup.m_isAudioAAC, isAudioAAC); + if (m_context.playbackGroup.m_isAudioAAC == isAudioAAC) + { + return ret; + } + if ((m_context.playbackGroup.m_curAudioDecoder == NULL) && (!(m_context.playbackGroup.m_isAudioAAC)) && (isAudioAAC)) + { + firstTimeSwitchFromAC3toAAC(newAudioCaps); + m_context.playbackGroup.m_isAudioAAC = isAudioAAC; + return true; + } + if (!m_context.playbackGroup.m_curAudioDecoder || !m_context.playbackGroup.m_curAudioParse || + !m_context.playbackGroup.m_curAudioDecodeBin) + { + RIALTO_SERVER_LOG_ERROR("switchAudioCodec: audio decoder, parser or decode bin is null"); + return false; + } + GstElement *newAudioParse = NULL; + GstElement *newAudioDecoder = NULL; + GstPad *newAudioParseSrcPad = NULL; + GstPad *newAudioParseSinkPad = NULL; + GstPad *newAudioDecoderSrcPad = NULL; + GstPad *newAudioDecoderSinkPad = NULL; + GstPad *audioDecSrcPad = NULL; + GstPad *audioDecSinkPad = NULL; + GstPad *audioDecSrcPeerPad = NULL; + GstPad *audioDecSinkPeerPad = NULL; + GstPad *audioParseSrcPad = NULL; + GstPad *audioParseSinkPad = NULL; + GstPad *audioParseSrcPeerPad = NULL; + GstPad *audioParseSinkPeerPad = NULL; + GstState currentState{GST_STATE_VOID_PENDING}, pending{GST_STATE_VOID_PENDING}; + + // Get AudioDecoder Src Pads + if ((audioDecSrcPad = m_gstWrapper->gstElementGetStaticPad(m_context.playbackGroup.m_curAudioDecoder, "src")) != + NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioDecoder Src Pad = %p", audioDecSrcPad); + // Get AudioDecoder Sink Pads + if ((audioDecSinkPad = m_gstWrapper->gstElementGetStaticPad(m_context.playbackGroup.m_curAudioDecoder, "sink")) != + NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioDecoder Sink Pad = %p", audioDecSinkPad); + // Get AudioDecoder Src Peer i.e. Downstream Element Pad + if ((audioDecSrcPeerPad = m_gstWrapper->gstPadGetPeer(audioDecSrcPad)) != NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioDecoder Src Downstream Element Pad = %p", audioDecSrcPeerPad); + // Get AudioDecoder Sink Peer i.e. Upstream Element Pad + if ((audioDecSinkPeerPad = m_gstWrapper->gstPadGetPeer(audioDecSinkPad)) != NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioDecoder Sink Upstream Element Pad = %p", audioDecSinkPeerPad); + // Get AudioParser Src Pads + if ((audioParseSrcPad = m_gstWrapper->gstElementGetStaticPad(m_context.playbackGroup.m_curAudioParse, "src")) != + NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioParser Src Pad = %p", audioParseSrcPad); + // Get AudioParser Sink Pads + if ((audioParseSinkPad = m_gstWrapper->gstElementGetStaticPad(m_context.playbackGroup.m_curAudioParse, "sink")) != + NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioParser Sink Pad = %p", audioParseSinkPad); + // Get AudioParser Src Peer i.e. Downstream Element Pad + if ((audioParseSrcPeerPad = m_gstWrapper->gstPadGetPeer(audioParseSrcPad)) != NULL) // Unref the Peer Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioParser Src Downstream Element Pad = %p", audioParseSrcPeerPad); + // Get AudioParser Sink Peer i.e. Upstream Element Pad + if ((audioParseSinkPeerPad = m_gstWrapper->gstPadGetPeer(audioParseSinkPad)) != NULL) // Unref the Peer Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioParser Sink Upstream Element Pad = %p", audioParseSinkPeerPad); + // AudioDecoder Downstream Unlink + if (m_gstWrapper->gstPadUnlink(audioDecSrcPad, audioDecSrcPeerPad) == FALSE) + RIALTO_SERVER_LOG_DEBUG("OTF -> AudioDecoder Downstream Unlink Failed"); + // AudioDecoder Upstream Unlink + if (m_gstWrapper->gstPadUnlink(audioDecSinkPeerPad, audioDecSinkPad) == FALSE) + RIALTO_SERVER_LOG_DEBUG("OTF -> AudioDecoder Upstream Unlink Failed"); + // AudioParser Downstream Unlink + if (m_gstWrapper->gstPadUnlink(audioParseSrcPad, audioParseSrcPeerPad) == FALSE) + RIALTO_SERVER_LOG_DEBUG("OTF -> AudioParser Downstream Unlink Failed"); + // AudioParser Upstream Unlink + if (m_gstWrapper->gstPadUnlink(audioParseSinkPeerPad, audioParseSinkPad) == FALSE) + RIALTO_SERVER_LOG_DEBUG("OTF -> AudioParser Upstream Unlink Failed"); + // Current Audio Decoder NULL + if (GST_STATE_CHANGE_FAILURE == + m_gstWrapper->gstElementSetState(m_context.playbackGroup.m_curAudioDecoder, GST_STATE_NULL)) + { + RIALTO_SERVER_LOG_WARN("Failed to set AudioDecoder to NULL"); + } + m_gstWrapper->gstElementGetState(m_context.playbackGroup.m_curAudioDecoder, ¤tState, &pending, + GST_CLOCK_TIME_NONE); + if (currentState == GST_STATE_NULL) + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioDecoder State = %d", currentState); + // Current Audio Parser NULL + if (GST_STATE_CHANGE_FAILURE == + m_gstWrapper->gstElementSetState(m_context.playbackGroup.m_curAudioParse, GST_STATE_NULL)) + { + RIALTO_SERVER_LOG_WARN("Failed to set AudioParser to NULL"); + } + m_gstWrapper->gstElementGetState(m_context.playbackGroup.m_curAudioParse, ¤tState, &pending, + GST_CLOCK_TIME_NONE); + if (currentState == GST_STATE_NULL) + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioParser State = %d", currentState); + // Remove Audio Decoder From Decodebin + if (m_gstWrapper->gstBinRemove(GST_BIN(m_context.playbackGroup.m_curAudioDecodeBin.load()), + m_context.playbackGroup.m_curAudioDecoder) == TRUE) + { + RIALTO_SERVER_LOG_DEBUG("OTF -> Removed AudioDecoder = %p", m_context.playbackGroup.m_curAudioDecoder); + m_context.playbackGroup.m_curAudioDecoder = NULL; + } + // Remove Audio Parser From Decodebin + if (m_gstWrapper->gstBinRemove(GST_BIN(m_context.playbackGroup.m_curAudioDecodeBin.load()), + m_context.playbackGroup.m_curAudioParse) == TRUE) + { + RIALTO_SERVER_LOG_DEBUG("OTF -> Removed AudioParser = %p", m_context.playbackGroup.m_curAudioParse); + m_context.playbackGroup.m_curAudioParse = NULL; + } + // Create new Audio Decoder and Parser. The inverse of the current + if (m_context.playbackGroup.m_isAudioAAC) + { + newAudioParse = m_gstWrapper->gstElementFactoryMake("ac3parse", "ac3parse"); + newAudioDecoder = m_gstWrapper->gstElementFactoryMake("identity", "fake_aud_ac3dec"); + } + else + { + newAudioParse = m_gstWrapper->gstElementFactoryMake("aacparse", "aacparse"); + newAudioDecoder = m_gstWrapper->gstElementFactoryMake("avdec_aac", "avdec_aac"); + } + { + GstPadLinkReturn gstPadLinkRet = GST_PAD_LINK_OK; + GstElement *audioParseUpstreamEl = NULL; + // Add new Decoder to Decodebin + if (m_gstWrapper->gstBinAdd(GST_BIN(m_context.playbackGroup.m_curAudioDecodeBin.load()), newAudioDecoder) == TRUE) + { + RIALTO_SERVER_LOG_DEBUG("OTF -> Added New AudioDecoder = %p", newAudioDecoder); + } + // Add new Parser to Decodebin + if (m_gstWrapper->gstBinAdd(GST_BIN(m_context.playbackGroup.m_curAudioDecodeBin.load()), newAudioParse) == TRUE) + { + RIALTO_SERVER_LOG_DEBUG("OTF -> Added New AudioParser = %p", newAudioParse); + } + if ((newAudioDecoderSrcPad = m_gstWrapper->gstElementGetStaticPad(newAudioDecoder, "src")) != + NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioDecoder Src Pad = %p", newAudioDecoderSrcPad); + if ((newAudioDecoderSinkPad = m_gstWrapper->gstElementGetStaticPad(newAudioDecoder, "sink")) != + NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioDecoder Sink Pad = %p", newAudioDecoderSinkPad); + // Link New Decoder to Downstream followed by UpStream + if ((gstPadLinkRet = m_gstWrapper->gstPadLink(newAudioDecoderSrcPad, audioDecSrcPeerPad)) != GST_PAD_LINK_OK) + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioDecoder Downstream Link Failed"); + if ((gstPadLinkRet = m_gstWrapper->gstPadLink(audioDecSinkPeerPad, newAudioDecoderSinkPad)) != GST_PAD_LINK_OK) + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioDecoder Upstream Link Failed"); + if ((newAudioParseSrcPad = m_gstWrapper->gstElementGetStaticPad(newAudioParse, "src")) != NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioParser Src Pad = %p", newAudioParseSrcPad); + if ((newAudioParseSinkPad = m_gstWrapper->gstElementGetStaticPad(newAudioParse, "sink")) != NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioParser Sink Pad = %p", newAudioParseSinkPad); + // Link New Parser to Downstream followed by UpStream + if ((gstPadLinkRet = m_gstWrapper->gstPadLink(newAudioParseSrcPad, audioParseSrcPeerPad)) != GST_PAD_LINK_OK) + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioParser Downstream Link Failed %d", gstPadLinkRet); + if ((audioParseUpstreamEl = GST_ELEMENT_CAST(m_gstWrapper->gstPadGetParent(audioParseSinkPeerPad))) == + m_context.playbackGroup.m_curAudioTypefind) + { + RIALTO_SERVER_LOG_DEBUG("OTF -> Typefind Setting to READY"); + if (GST_STATE_CHANGE_FAILURE == m_gstWrapper->gstElementSetState(audioParseUpstreamEl, GST_STATE_READY)) + { + RIALTO_SERVER_LOG_WARN("Failed to set Typefind to READY in switchAudioCodec"); + } + m_glibWrapper->gObjectSet(G_OBJECT(audioParseUpstreamEl), "force-caps", newAudioCaps, NULL); + m_gstWrapper->gstElementSyncStateWithParent(audioParseUpstreamEl); + m_gstWrapper->gstElementGetState(audioParseUpstreamEl, ¤tState, &pending, GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> New Typefind State = %d Pending = %d", currentState, pending); + RIALTO_SERVER_LOG_DEBUG("OTF -> Typefind Syncing with Parent"); + m_context.playbackGroup.m_linkTypefindParser = true; + m_gstWrapper->gstObjectUnref(audioParseUpstreamEl); + } + m_gstWrapper->gstObjectUnref(newAudioDecoderSrcPad); + m_gstWrapper->gstObjectUnref(newAudioDecoderSinkPad); + m_gstWrapper->gstObjectUnref(newAudioParseSrcPad); + m_gstWrapper->gstObjectUnref(newAudioParseSinkPad); + } + m_gstWrapper->gstObjectUnref(audioParseSinkPeerPad); + m_gstWrapper->gstObjectUnref(audioParseSrcPeerPad); + m_gstWrapper->gstObjectUnref(audioParseSinkPad); + m_gstWrapper->gstObjectUnref(audioParseSrcPad); + m_gstWrapper->gstObjectUnref(audioDecSinkPeerPad); + m_gstWrapper->gstObjectUnref(audioDecSrcPeerPad); + m_gstWrapper->gstObjectUnref(audioDecSinkPad); + m_gstWrapper->gstObjectUnref(audioDecSrcPad); + m_gstWrapper->gstElementSyncStateWithParent(newAudioDecoder); + m_gstWrapper->gstElementGetState(newAudioDecoder, ¤tState, &pending, GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioDecoder State = %d Pending = %d", currentState, pending); + m_gstWrapper->gstElementSyncStateWithParent(newAudioParse); + m_gstWrapper->gstElementGetState(newAudioParse, ¤tState, &pending, GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioParser State = %d Pending = %d", currentState, pending); + m_context.playbackGroup.m_isAudioAAC = isAudioAAC; + return true; +} + +bool GstGenericPlayer::performAudioTrackCodecChannelSwitch(const void *pSampleAttr, + firebolt::rialto::wrappers::AudioAttributesPrivate *pAudioAttr, + uint32_t *pStatus, unsigned int *pui32Delay, + long long *pAudioChangeTargetPts, // NOLINT(runtime/int) + const long long *pcurrentDispPts, // NOLINT(runtime/int) + unsigned int *audioChangeStage, GstCaps **appsrcCaps, + bool *audioaac, bool svpenabled, GstElement *aSrc, bool *ret) +{ + // this function comes from rdk_gstreamer_utils + if (!pStatus || !pui32Delay || !pAudioChangeTargetPts || !pcurrentDispPts || !audioChangeStage || !appsrcCaps || + !audioaac || !aSrc || !ret) + { + RIALTO_SERVER_LOG_ERROR("performAudioTrackCodecChannelSwitch: invalid null parameter"); + return false; + } + + constexpr uint32_t kOk = 0; + constexpr uint32_t kWaitWhileIdling = 100; + constexpr int kAudioChangeGapThresholdMS = 40; + constexpr unsigned int kAudchgAlign = 3; + + struct timespec ts, now; + unsigned int reconfigDelayMs; + clock_gettime(CLOCK_MONOTONIC, &ts); + if (*pStatus != kOk || pSampleAttr == nullptr) + { + RIALTO_SERVER_LOG_DEBUG("No audio data ready yet"); + *pui32Delay = kWaitWhileIdling; + *ret = false; + return true; + } + RIALTO_SERVER_LOG_DEBUG("Received first audio packet after a flush, PTS"); + if (pAudioAttr) + { + const char *pCodecStr = pAudioAttr->m_codecParam.c_str(); + const char *pCodecAcc = strstr(pCodecStr, "mp4a"); + bool isAudioAAC = (pCodecAcc) ? true : false; + bool isCodecSwitch = false; + RIALTO_SERVER_LOG_DEBUG("Audio Attribute format %s channel %d samp %d, bitrate %d blockAlignment %d", pCodecStr, + pAudioAttr->m_numberOfChannels, pAudioAttr->m_samplesPerSecond, pAudioAttr->m_bitrate, + pAudioAttr->m_blockAlignment); + *pAudioChangeTargetPts = *pcurrentDispPts; + *audioChangeStage = kAudchgAlign; + if (*appsrcCaps) + { + m_gstWrapper->gstCapsUnref(*appsrcCaps); + *appsrcCaps = NULL; + } + if (isAudioAAC != *audioaac) + isCodecSwitch = true; + configAudioCap(pAudioAttr, audioaac, svpenabled, appsrcCaps); + { + gboolean sendRet = FALSE; + GstEvent *flushStart = NULL; + GstEvent *flushStop = NULL; + flushStart = m_gstWrapper->gstEventNewFlushStart(); + sendRet = m_gstWrapper->gstElementSendEvent(aSrc, flushStart); + if (!sendRet) + RIALTO_SERVER_LOG_DEBUG("failed to send flush-start event"); + flushStop = m_gstWrapper->gstEventNewFlushStop(TRUE); + sendRet = m_gstWrapper->gstElementSendEvent(aSrc, flushStop); + if (!sendRet) + RIALTO_SERVER_LOG_DEBUG("failed to send flush-stop event"); + } + if (!isCodecSwitch) + { + m_gstWrapper->gstAppSrcSetCaps(GST_APP_SRC(aSrc), *appsrcCaps); + } + else + { + RIALTO_SERVER_LOG_DEBUG("CODEC SWITCH mAudioAAC = %d", *audioaac); + haltAudioPlayback(); + if (switchAudioCodec(*audioaac, *appsrcCaps) == false) + { + RIALTO_SERVER_LOG_DEBUG("CODEC SWITCH FAILED switchAudioCodec mAudioAAC = %d", *audioaac); + } + m_gstWrapper->gstAppSrcSetCaps(GST_APP_SRC(aSrc), *appsrcCaps); + resumeAudioPlayback(); + } + clock_gettime(CLOCK_MONOTONIC, &now); + reconfigDelayMs = now.tv_nsec > ts.tv_nsec ? (now.tv_nsec - ts.tv_nsec) / 1000000 + : (1000 - (ts.tv_nsec - now.tv_nsec) / 1000000); + (*pAudioChangeTargetPts) += (reconfigDelayMs + kAudioChangeGapThresholdMS); + } + else + { + RIALTO_SERVER_LOG_DEBUG("first audio after change no attribute drop!"); + *pui32Delay = 0; + *ret = false; + return true; + } + *ret = true; + return true; +} + bool GstGenericPlayer::setImmediateOutput(const MediaSourceType &mediaSourceType, bool immediateOutputParam) { if (!m_workerThread) @@ -1001,6 +1480,9 @@ void GstGenericPlayer::pushSampleIfRequired(GstElement *source, const std::strin "], rate: %f, appliedRate %f, reset_time: %d\n", typeStr.c_str(), GST_TIME_ARGS(segment->start), GST_TIME_ARGS(segment->stop), segment->rate, segment->applied_rate, resetTime); + auto recordId = m_context.gstProfiler->createRecord("First Segment Received", typeStr); + if (recordId) + m_context.gstProfiler->logRecord(recordId.value()); GstCaps *currentCaps = m_gstWrapper->gstAppSrcGetCaps(GST_APP_SRC(source)); // We can't pass buffer in GstSample, because implementation of gst_app_src_push_sample @@ -1080,9 +1562,24 @@ bool GstGenericPlayer::reattachSource(const std::unique_ptrgetType()].appSrc)}; GstCaps *oldCaps = m_gstWrapper->gstAppSrcGetCaps(appSrc); + if ((!oldCaps) || (!m_gstWrapper->gstCapsIsEqual(caps, oldCaps))) { RIALTO_SERVER_LOG_DEBUG("Caps not equal. Perform audio track codec channel switch."); + + GstElement *sink = getSink(MediaSourceType::AUDIO); + if (!sink) + { + RIALTO_SERVER_LOG_ERROR("Failed to get audio sink"); + if (caps) + m_gstWrapper->gstCapsUnref(caps); + if (oldCaps) + m_gstWrapper->gstCapsUnref(oldCaps); + return false; + } + std::string sinkName = GST_ELEMENT_NAME(sink); + m_gstWrapper->gstObjectUnref(sink); + int sampleAttributes{ 0}; // rdk_gstreamer_utils::performAudioTrackCodecChannelSwitch checks if this param != NULL only. std::uint32_t status{0}; // must be 0 to make rdk_gstreamer_utils::performAudioTrackCodecChannelSwitch work @@ -1096,14 +1593,26 @@ bool GstGenericPlayer::reattachSource(const std::unique_ptrperformAudioTrackCodecChannelSwitch(&m_context.playbackGroup, &sampleAttributes, &(*audioAttributes), - &status, &ui32Delay, &audioChangeTargetPts, ¤tDispPts, - &audioChangeStage, - &caps, // may fail for amlogic - that implementation changes - // this parameter, it's probably used by Netflix later - &audioAac, svpEnabled, GST_ELEMENT(appSrc), &retVal); + + bool result = false; + if (m_glibWrapper->gStrHasPrefix(sinkName.c_str(), "amlhalasink")) + { + // due to problems audio codec change in prerolling, temporarily moved the code from rdk gstreamer utils to + // Rialto and applied fixes + result = performAudioTrackCodecChannelSwitch(&sampleAttributes, &(*audioAttributes), &status, &ui32Delay, + &audioChangeTargetPts, ¤tDispPts, &audioChangeStage, + &caps, &audioAac, svpEnabled, GST_ELEMENT(appSrc), &retVal); + } + else + { + result = m_rdkGstreamerUtilsWrapper->performAudioTrackCodecChannelSwitch(&m_context.playbackGroup, + &sampleAttributes, + &(*audioAttributes), &status, + &ui32Delay, &audioChangeTargetPts, + ¤tDispPts, &audioChangeStage, + &caps, &audioAac, svpEnabled, + GST_ELEMENT(appSrc), &retVal); + } if (!result || !retVal) { @@ -1149,7 +1658,7 @@ void GstGenericPlayer::scheduleAudioUnderflow() { if (m_workerThread) { - bool underflowEnabled = m_context.isPlaying && !m_context.audioSourceRemoved; + bool underflowEnabled = m_context.isPlaying; m_workerThread->enqueueTask( m_taskFactory->createUnderflow(m_context, *this, underflowEnabled, MediaSourceType::AUDIO)); } @@ -1186,8 +1695,9 @@ void GstGenericPlayer::cancelUnderflow(firebolt::rialto::MediaSourceType mediaSo } } -void GstGenericPlayer::play() +void GstGenericPlayer::play(bool &async) { + async = true; if (m_workerThread) { m_workerThread->enqueueTask(m_taskFactory->createPlay(*this)); @@ -1210,23 +1720,24 @@ void GstGenericPlayer::stop() } } -bool GstGenericPlayer::changePipelineState(GstState newState) +GstStateChangeReturn GstGenericPlayer::changePipelineState(GstState newState) { if (!m_context.pipeline) { RIALTO_SERVER_LOG_ERROR("Change state failed - pipeline is nullptr"); if (m_gstPlayerClient) m_gstPlayerClient->notifyPlaybackState(PlaybackState::FAILURE); - return false; + return GST_STATE_CHANGE_FAILURE; } - if (m_gstWrapper->gstElementSetState(m_context.pipeline, newState) == GST_STATE_CHANGE_FAILURE) + m_context.flushOnPrerollController->setTargetState(newState); + const GstStateChangeReturn result{m_gstWrapper->gstElementSetState(m_context.pipeline, newState)}; + if (result == GST_STATE_CHANGE_FAILURE) { RIALTO_SERVER_LOG_ERROR("Change state failed - Gstreamer returned an error"); if (m_gstPlayerClient) m_gstPlayerClient->notifyPlaybackState(PlaybackState::FAILURE); - return false; } - return true; + return result; } int64_t GstGenericPlayer::getPosition(GstElement *element) @@ -1693,7 +2204,6 @@ bool GstGenericPlayer::setErmContext() void GstGenericPlayer::startPositionReportingAndCheckAudioUnderflowTimer() { - static constexpr std::chrono::milliseconds kPlaybackInfoTimerMs{32}; if (m_positionReportingAndCheckAudioUnderflowTimer && m_positionReportingAndCheckAudioUnderflowTimer->isActive()) { return; @@ -1710,12 +2220,6 @@ void GstGenericPlayer::startPositionReportingAndCheckAudioUnderflowTimer() } }, firebolt::rialto::common::TimerType::PERIODIC); - - notifyPlaybackInfo(); - - m_playbackInfoTimer = - m_timerFactory - ->createTimer(kPlaybackInfoTimerMs, [this]() { notifyPlaybackInfo(); }, firebolt::rialto::common::TimerType::PERIODIC); } void GstGenericPlayer::stopPositionReportingAndCheckAudioUnderflowTimer() @@ -1725,7 +2229,25 @@ void GstGenericPlayer::stopPositionReportingAndCheckAudioUnderflowTimer() m_positionReportingAndCheckAudioUnderflowTimer->cancel(); m_positionReportingAndCheckAudioUnderflowTimer.reset(); } +} +void GstGenericPlayer::startNotifyPlaybackInfoTimer() +{ + static constexpr std::chrono::milliseconds kPlaybackInfoTimerMs{32}; + if (m_playbackInfoTimer && m_playbackInfoTimer->isActive()) + { + return; + } + + notifyPlaybackInfo(); + + m_playbackInfoTimer = + m_timerFactory + ->createTimer(kPlaybackInfoTimerMs, [this]() { notifyPlaybackInfo(); }, firebolt::rialto::common::TimerType::PERIODIC); +} + +void GstGenericPlayer::stopNotifyPlaybackInfoTimer() +{ if (m_playbackInfoTimer && m_playbackInfoTimer->isActive()) { m_playbackInfoTimer->cancel(); @@ -2041,7 +2563,7 @@ void GstGenericPlayer::flush(const MediaSourceType &mediaSourceType, bool resetT { async = isAsync(mediaSourceType); m_flushWatcher->setFlushing(mediaSourceType, async); - m_workerThread->enqueueTask(m_taskFactory->createFlush(m_context, *this, mediaSourceType, resetTime)); + m_workerThread->enqueueTask(m_taskFactory->createFlush(m_context, *this, mediaSourceType, resetTime, async)); } } diff --git a/media/server/gstplayer/source/GstProfiler.cpp b/media/server/gstplayer/source/GstProfiler.cpp new file mode 100644 index 000000000..3a87fe248 --- /dev/null +++ b/media/server/gstplayer/source/GstProfiler.cpp @@ -0,0 +1,410 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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. + */ + +#include "GstProfiler.h" +#include "RialtoServerLogging.h" +#include "Utils.h" + +#include +#include +#include +#include +#include + +#include +#include + +namespace firebolt::rialto::server +{ +std::weak_ptr GstProfilerFactory::m_factory; + +std::shared_ptr IGstProfilerFactory::getFactory() +{ + std::shared_ptr factory = GstProfilerFactory::m_factory.lock(); + + if (!factory) + { + try + { + factory = std::make_shared(); + } + catch (const std::exception &e) + { + RIALTO_SERVER_LOG_ERROR("Failed to create the gst profiler factory, reason: %s", e.what()); + } + + GstProfilerFactory::m_factory = factory; + } + + return factory; +} + +std::unique_ptr GstProfilerFactory::createGstProfiler(GstElement *pipeline, + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper) const +{ + return std::make_unique(pipeline, gstWrapper, glibWrapper); +} + +namespace +{ +inline constexpr std::array kKlassTokens{ + std::string_view{"Source"}, + std::string_view{"Decryptor"}, + std::string_view{"Decoder"}, +}; +inline constexpr std::string_view kModuleName{"GstProfiler"}; + +using Clock = std::chrono::system_clock; +using IProfiler = firebolt::rialto::common::IProfiler; + +GstPadProbeReturn probeCb(GstPad *pad, GstPadProbeInfo *info, gpointer userData) +{ + firebolt::rialto::server::IGstProfilerPrivate *self = + static_cast(userData); + return self->handleProbeCb(pad, info); +} + +std::optional diffMs(const std::optional &end, const std::optional &start) +{ + if (!end || !start) + return std::nullopt; + + return std::chrono::duration_cast(*end - *start).count(); +} + +std::optional maxTime(const std::optional &a, + const std::optional &b) +{ + if (a && b) + return std::max(*a, *b); + if (a) + return a; + if (b) + return b; + return std::nullopt; +} +} // namespace + +GstProfiler::GstProfiler(GstElement *pipeline, const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper) + : m_pipeline{pipeline}, m_gstWrapper{gstWrapper}, m_glibWrapper{glibWrapper} +{ + auto profilerFactory = firebolt::rialto::common::IProfilerFactory::createFactory(); + m_profiler = profilerFactory ? std::shared_ptr{profilerFactory->createProfiler(std::string{kModuleName})} + : nullptr; + m_enabled = (m_profiler != nullptr) && m_profiler->isEnabled(); + + if (m_enabled && m_pipeline) + m_gstWrapper->gstObjectRef(m_pipeline); +} + +GstProfiler::~GstProfiler() +{ + while (!m_probeCtxs.empty()) + { + auto &probeCtx = m_probeCtxs.back(); + if (probeCtx.id != 0) + { + m_gstWrapper->gstPadRemoveProbe(probeCtx.pad, probeCtx.id); + } + m_gstWrapper->gstObjectUnref(probeCtx.pad); + m_probeCtxs.pop_back(); + } + + if (m_enabled && m_pipeline) + m_gstWrapper->gstObjectUnref(m_pipeline); +} + +std::optional GstProfiler::createRecord(const std::string &stage) +{ + if (!m_enabled || !m_profiler) + return std::nullopt; + + auto id = m_profiler->record(stage); + if (!id) + return std::nullopt; + + return static_cast(*id); +} + +std::optional GstProfiler::createRecord(const std::string &stage, const std::string &info) +{ + if (!m_enabled || !m_profiler) + return std::nullopt; + + auto id = m_profiler->record(stage, info); + if (!id) + return std::nullopt; + + return static_cast(*id); +} + +void GstProfiler::scheduleGstElementRecord(GstElement *element) +{ + if (!m_enabled || !m_profiler) + return; + + if (!element) + return; + + auto stage = getFirstBufferExitStage(element); + if (!stage) + return; + + GstPad *pad = m_gstWrapper->gstElementGetStaticPad(element, "src"); + if (!pad) + return; + + std::string elementInfo; + if (isVideo(*m_gstWrapper, element)) + { + elementInfo = "Video"; + } + else if (isAudio(*m_gstWrapper, element)) + { + elementInfo = "Audio"; + } + else + { + gchar *rawName = m_gstWrapper->gstElementGetName(element); + elementInfo = deriveElementInfoFromName(rawName ? rawName : ""); + if (rawName) + m_glibWrapper->gFree(rawName); + } + + const auto probeId = m_gstWrapper->gstPadAddProbe(pad, GST_PAD_PROBE_TYPE_BUFFER, &probeCb, + static_cast(this), nullptr); + if (probeId == 0) + { + m_gstWrapper->gstObjectUnref(pad); + return; + } + + m_probeCtxs.emplace_back(ProbeCtx{m_profiler, stage.value(), std::move(elementInfo), pad, probeId}); +} + +std::vector GstProfiler::getRecords() const +{ + if (!m_profiler) + return {}; + + return m_profiler->getRecords(); +} + +void GstProfiler::logRecord(GstProfiler::RecordId id) +{ + if (!m_enabled || !m_profiler) + return; + + m_profiler->log(static_cast(id)); +} + +void GstProfiler::dumpToFile() const +{ + if (!m_enabled || !m_profiler) + return; + + if (!m_profiler->dumpToFile()) + { + RIALTO_SERVER_LOG_WARN("Failed to dump profiler records to file"); + } +} + +void GstProfiler::logPipelineSummary() const +{ + if (!m_enabled || !m_profiler) + return; + + const auto metrics = calculateMetrics(); + if (metrics) + { + RIALTO_SERVER_LOG_MIL("PROFILER | TUNETIME: %lld, %lld, %lld, %lld, %lld, %lld, %lld, %lld, %lld, " + "%lld, %lld, %lld, %lld", + metrics->preparation ? static_cast(*metrics->preparation) : -1, // NOLINT + metrics->videoDownload ? static_cast(*metrics->videoDownload) : -1, // NOLINT + metrics->audioDownload ? static_cast(*metrics->audioDownload) : -1, // NOLINT + metrics->videoSource ? static_cast(*metrics->videoSource) : -1, // NOLINT + metrics->audioSource ? static_cast(*metrics->audioSource) : -1, // NOLINT + metrics->videoDecryption ? static_cast(*metrics->videoDecryption) : -1, // NOLINT + metrics->audioDecryption ? static_cast(*metrics->audioDecryption) : -1, // NOLINT + metrics->videoDecode ? static_cast(*metrics->videoDecode) : -1, // NOLINT + metrics->audioDecode ? static_cast(*metrics->audioDecode) : -1, // NOLINT + metrics->preRoll ? static_cast(*metrics->preRoll) : -1, // NOLINT + metrics->play ? static_cast(*metrics->play) : -1, // NOLINT + metrics->total ? static_cast(*metrics->total) : -1, // NOLINT + metrics->totalWithoutApp ? static_cast(*metrics->totalWithoutApp) : -1); // NOLINT + } +} + +GstPadProbeReturn GstProfiler::handleProbeCb(GstPad *pad, GstPadProbeInfo *info) +{ + if (!(info->type & GST_PAD_PROBE_TYPE_BUFFER)) + return GST_PAD_PROBE_OK; + + if (!GST_PAD_PROBE_INFO_BUFFER(info)) + return GST_PAD_PROBE_OK; + + const auto probeCtx = + std::find_if(m_probeCtxs.begin(), m_probeCtxs.end(), [pad](const auto &ctx) { return ctx.pad == pad; }); + if (probeCtx == m_probeCtxs.end()) + return GST_PAD_PROBE_REMOVE; + + if (probeCtx->profiler) + { + const auto id = probeCtx->profiler->record(probeCtx->stage, probeCtx->info); + if (id) + { + probeCtx->profiler->log(id.value()); + } + } + + removeProbeCtx(pad); + return GST_PAD_PROBE_REMOVE; +} + +std::optional GstProfiler::getFirstBufferExitStage(GstElement *element) +{ + const gchar *klass = getElementClassMetadata(element); + if (!klass) + return std::nullopt; + + for (auto token : kKlassTokens) + { + if (m_glibWrapper->gStrrstr(klass, token.data()) != nullptr) + { + return std::string(token.data()) + " FB Exit"; + } + } + + return std::nullopt; +} + +const gchar *GstProfiler::getElementClassMetadata(GstElement *element) +{ + return m_gstWrapper->gstElementClassGetMetadata(GST_ELEMENT_CLASS(G_OBJECT_GET_CLASS(element)), + GST_ELEMENT_METADATA_KLASS); +} + +std::string GstProfiler::deriveElementInfoFromName(const std::string &name) const +{ + std::string lower = name; + std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) { return std::tolower(c); }); + + if (lower.find("vid") != std::string::npos || lower.find("video") != std::string::npos) + { + return "Video"; + } + + if (lower.find("aud") != std::string::npos || lower.find("audio") != std::string::npos) + { + return "Audio"; + } + + return name; +} + +void GstProfiler::removeProbeCtx(GstPad *pad) +{ + const auto probeCtx = + std::find_if(m_probeCtxs.begin(), m_probeCtxs.end(), [pad](const auto &ctx) { return ctx.pad == pad; }); + if (probeCtx == m_probeCtxs.end()) + return; + + m_gstWrapper->gstObjectUnref(probeCtx->pad); + m_probeCtxs.erase(probeCtx); +} + +std::optional GstProfiler::calculateMetrics() const +{ + const auto records = m_profiler->getRecords(); + + PipelineStageTimestamps timestamps; + + for (const auto &record : records) + { + const auto &stage = record.stage; + const auto &info = record.info; + + if (!timestamps.pipelineCreated && stage == "Pipeline Created") + timestamps.pipelineCreated = record.time; + else if (!timestamps.allSourcesAttached && stage == "All Sources Attached") + timestamps.allSourcesAttached = record.time; + else if (!timestamps.firstSegmentReceivedVideo && stage == "First Segment Received" && info == "Video") + timestamps.firstSegmentReceivedVideo = record.time; + else if (!timestamps.firstSegmentReceivedAudio && stage == "First Segment Received" && info == "Audio") + timestamps.firstSegmentReceivedAudio = record.time; + else if (!timestamps.sourceFbExitVideo && stage == "Source FB Exit" && info == "Video") + timestamps.sourceFbExitVideo = record.time; + else if (!timestamps.sourceFbExitAudio && stage == "Source FB Exit" && info == "Audio") + timestamps.sourceFbExitAudio = record.time; + else if (!timestamps.decryptorFbExitVideo && stage == "Decryptor FB Exit" && info == "Video") + timestamps.decryptorFbExitVideo = record.time; + else if (!timestamps.decryptorFbExitAudio && stage == "Decryptor FB Exit" && info == "Audio") + timestamps.decryptorFbExitAudio = record.time; + else if (!timestamps.decoderFbExitVideo && stage == "Decoder FB Exit" && info == "Video") + timestamps.decoderFbExitVideo = record.time; + else if (!timestamps.decoderFbExitAudio && stage == "Decoder FB Exit" && info == "Audio") + timestamps.decoderFbExitAudio = record.time; + else if (!timestamps.pipelinePaused && stage == "Pipeline State Changed" && info == "PAUSED") + timestamps.pipelinePaused = record.time; + else if (!timestamps.pipelinePlaying && stage == "Pipeline State Changed" && info == "PLAYING") + timestamps.pipelinePlaying = record.time; + } + + if (!timestamps.pipelineCreated || !timestamps.allSourcesAttached || !timestamps.firstSegmentReceivedVideo || + !timestamps.firstSegmentReceivedAudio || !timestamps.sourceFbExitVideo || !timestamps.sourceFbExitAudio || + !timestamps.decoderFbExitVideo || !timestamps.decoderFbExitAudio || !timestamps.pipelinePaused || + !timestamps.pipelinePlaying) + { + return std::nullopt; + } + + PipelineMetrics metrics; + + metrics.preparation = diffMs(timestamps.allSourcesAttached, timestamps.pipelineCreated); + metrics.videoDownload = diffMs(timestamps.firstSegmentReceivedVideo, timestamps.allSourcesAttached); + metrics.audioDownload = diffMs(timestamps.firstSegmentReceivedAudio, timestamps.allSourcesAttached); + metrics.videoSource = diffMs(timestamps.sourceFbExitVideo, timestamps.firstSegmentReceivedVideo); + metrics.audioSource = diffMs(timestamps.sourceFbExitAudio, timestamps.firstSegmentReceivedAudio); + + if (timestamps.decryptorFbExitVideo && timestamps.decryptorFbExitAudio) + { + metrics.videoDecryption = diffMs(timestamps.decryptorFbExitVideo, timestamps.sourceFbExitVideo); + metrics.audioDecryption = diffMs(timestamps.decryptorFbExitAudio, timestamps.sourceFbExitAudio); + metrics.videoDecode = diffMs(timestamps.decoderFbExitVideo, timestamps.decryptorFbExitVideo); + metrics.audioDecode = diffMs(timestamps.decoderFbExitAudio, timestamps.decryptorFbExitAudio); + } + else + { + metrics.videoDecode = diffMs(timestamps.decoderFbExitVideo, timestamps.sourceFbExitVideo); + metrics.audioDecode = diffMs(timestamps.decoderFbExitAudio, timestamps.sourceFbExitAudio); + } + + const auto firstMediaReady = maxTime(timestamps.firstSegmentReceivedVideo, timestamps.firstSegmentReceivedAudio); + + metrics.preRoll = diffMs(timestamps.pipelinePaused, firstMediaReady); + metrics.play = diffMs(timestamps.pipelinePlaying, timestamps.pipelinePaused); + metrics.total = diffMs(timestamps.pipelinePlaying, timestamps.pipelineCreated); + metrics.totalWithoutApp = diffMs(timestamps.pipelinePlaying, firstMediaReady); + + return metrics; +} + +} // namespace firebolt::rialto::server diff --git a/media/server/gstplayer/source/GstTextTrackSink.cpp b/media/server/gstplayer/source/GstTextTrackSink.cpp index b98c5b93f..16e137854 100644 --- a/media/server/gstplayer/source/GstTextTrackSink.cpp +++ b/media/server/gstplayer/source/GstTextTrackSink.cpp @@ -159,19 +159,12 @@ static void gst_rialto_text_track_sink_finalize(GObject *object) // NOLINT(build static gboolean gst_rialto_text_track_sink_start(GstBaseSink *sink) // NOLINT(build/function_format) { - const char *wayland_display = std::getenv("WAYLAND_DISPLAY"); - if (!wayland_display) - { - GST_ERROR_OBJECT(sink, "Failed to get WAYLAND_DISPLAY env variable"); - return false; - } - - std::string display{wayland_display}; + const std::string kDisplay{"westeros-asplayer-subtitles"}; GstRialtoTextTrackSink *self = GST_RIALTO_TEXT_TRACK_SINK(sink); try { self->priv->m_textTrackSession = - firebolt::rialto::server::ITextTrackSessionFactory::getFactory().createTextTrackSession(display); + firebolt::rialto::server::ITextTrackSessionFactory::getFactory().createTextTrackSession(kDisplay); } catch (const std::exception &e) { diff --git a/media/server/gstplayer/source/GstWebAudioPlayer.cpp b/media/server/gstplayer/source/GstWebAudioPlayer.cpp index ef6aa666a..551730e15 100644 --- a/media/server/gstplayer/source/GstWebAudioPlayer.cpp +++ b/media/server/gstplayer/source/GstWebAudioPlayer.cpp @@ -125,8 +125,8 @@ GstWebAudioPlayer::GstWebAudioPlayer(IGstWebAudioPlayerClient *client, const uin } if ((!gstDispatcherThreadFactory) || - (!(m_gstDispatcherThread = - gstDispatcherThreadFactory->createGstDispatcherThread(*this, m_context.pipeline, m_gstWrapper)))) + (!(m_gstDispatcherThread = gstDispatcherThreadFactory->createGstDispatcherThread(*this, m_context.pipeline, + nullptr, m_gstWrapper)))) { termWebAudioPipeline(); resetWorkerThread(); diff --git a/media/server/gstplayer/source/Utils.cpp b/media/server/gstplayer/source/Utils.cpp index 243aa060d..520778de4 100644 --- a/media/server/gstplayer/source/Utils.cpp +++ b/media/server/gstplayer/source/Utils.cpp @@ -64,6 +64,11 @@ bool isVideoParser(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, Gs return isType(gstWrapper, element, GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO); } +bool isAudioParser(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element) +{ + return isType(gstWrapper, element, GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO); +} + bool isVideoSink(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element) { return isType(gstWrapper, element, GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO); diff --git a/media/server/gstplayer/source/tasks/generic/AttachSamples.cpp b/media/server/gstplayer/source/tasks/generic/AttachSamples.cpp index ef4605d4e..a9049f44b 100644 --- a/media/server/gstplayer/source/tasks/generic/AttachSamples.cpp +++ b/media/server/gstplayer/source/tasks/generic/AttachSamples.cpp @@ -22,6 +22,7 @@ #include "IGstGenericPlayerPrivate.h" #include "RialtoServerLogging.h" #include "TypeConverters.h" +#include namespace firebolt::rialto::server::tasks::generic { @@ -42,7 +43,7 @@ AttachSamples::AttachSamples(GenericPlayerContext &context, dynamic_cast(*mediaSegment); VideoData videoData = {gstBuffer, videoSegment.getWidth(), videoSegment.getHeight(), videoSegment.getFrameRate(), videoSegment.getCodecData()}; - m_videoData.push_back(videoData); + m_videoData.push_back(std::move(videoData)); } catch (const std::exception &e) { @@ -62,7 +63,7 @@ AttachSamples::AttachSamples(GenericPlayerContext &context, audioSegment.getCodecData(), audioSegment.getClippingStart(), audioSegment.getClippingEnd()}; - m_audioData.push_back(audioData); + m_audioData.push_back(std::move(audioData)); } catch (const std::exception &e) { diff --git a/media/server/gstplayer/source/tasks/generic/AttachSource.cpp b/media/server/gstplayer/source/tasks/generic/AttachSource.cpp index f65daa212..8ca2b2405 100644 --- a/media/server/gstplayer/source/tasks/generic/AttachSource.cpp +++ b/media/server/gstplayer/source/tasks/generic/AttachSource.cpp @@ -59,7 +59,7 @@ void AttachSource::execute() const { addSource(); } - else if (m_attachedSource->getType() == MediaSourceType::AUDIO && m_context.audioSourceRemoved) + else if (m_attachedSource->getType() == MediaSourceType::AUDIO) { reattachAudioSource(); } @@ -77,22 +77,26 @@ void AttachSource::addSource() const RIALTO_SERVER_LOG_ERROR("Failed to create caps from media source"); return; } + std::string profilerInfo; gchar *capsStr = m_gstWrapper->gstCapsToString(caps); GstElement *appSrc = nullptr; if (m_attachedSource->getType() == MediaSourceType::AUDIO) { RIALTO_SERVER_LOG_MIL("Adding Audio appsrc with caps %s", capsStr); appSrc = m_gstWrapper->gstElementFactoryMake("appsrc", "audsrc"); + profilerInfo = "audsrc"; } else if (m_attachedSource->getType() == MediaSourceType::VIDEO) { RIALTO_SERVER_LOG_MIL("Adding Video appsrc with caps %s", capsStr); appSrc = m_gstWrapper->gstElementFactoryMake("appsrc", "vidsrc"); + profilerInfo = "vidsrc"; } else if (m_attachedSource->getType() == MediaSourceType::SUBTITLE) { RIALTO_SERVER_LOG_MIL("Adding Subtitle appsrc with caps %s", capsStr); appSrc = m_gstWrapper->gstElementFactoryMake("appsrc", "subsrc"); + profilerInfo = "subsrc"; if (m_glibWrapper->gObjectClassFindProperty(G_OBJECT_GET_CLASS(m_context.pipeline), "text-sink")) { @@ -102,6 +106,13 @@ void AttachSource::addSource() const m_glibWrapper->gObjectSet(m_context.pipeline, "text-sink", elem, nullptr); } } + if (appSrc) + { + auto recordId = m_context.gstProfiler->createRecord("Created AppSrc Element", profilerInfo); + if (recordId) + m_context.gstProfiler->logRecord(recordId.value()); + } + m_glibWrapper->gFree(capsStr); m_gstWrapper->gstAppSrcSetCaps(GST_APP_SRC(appSrc), caps); @@ -118,14 +129,6 @@ void AttachSource::reattachAudioSource() const RIALTO_SERVER_LOG_ERROR("Reattaching source failed!"); return; } - - // Restart audio sink - m_player.setPlaybinFlags(true); - - m_context.streamInfo[m_attachedSource->getType()].isDataNeeded = true; - m_context.audioSourceRemoved = false; - m_player.notifyNeedMediaData(MediaSourceType::AUDIO); - RIALTO_SERVER_LOG_MIL("Audio source reattached"); } } // namespace firebolt::rialto::server::tasks::generic diff --git a/media/server/gstplayer/source/tasks/generic/CheckAudioUnderflow.cpp b/media/server/gstplayer/source/tasks/generic/CheckAudioUnderflow.cpp index c027c69a2..55e641304 100644 --- a/media/server/gstplayer/source/tasks/generic/CheckAudioUnderflow.cpp +++ b/media/server/gstplayer/source/tasks/generic/CheckAudioUnderflow.cpp @@ -31,7 +31,7 @@ namespace firebolt::rialto::server::tasks::generic { CheckAudioUnderflow::CheckAudioUnderflow(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, IGstGenericPlayerClient *client, - std::shared_ptr gstWrapper) + const std::shared_ptr &gstWrapper) : m_context{context}, m_player(player), m_gstPlayerClient{client}, m_gstWrapper{gstWrapper} { } @@ -55,7 +55,7 @@ void CheckAudioUnderflow::execute() const { RIALTO_SERVER_LOG_WARN("Audio stream underflow! Position %" PRIu64 ", lastAudioSampleTimestamps: %" PRIu64, position, m_context.lastAudioSampleTimestamps); - bool underflowEnabled = m_context.isPlaying && !m_context.audioSourceRemoved; + bool underflowEnabled = m_context.isPlaying; Underflow task(m_context, m_player, m_gstPlayerClient, underflowEnabled, MediaSourceType::AUDIO); task.execute(); } diff --git a/media/server/gstplayer/source/tasks/generic/DeepElementAdded.cpp b/media/server/gstplayer/source/tasks/generic/DeepElementAdded.cpp index 83e6bef12..59d9cf21b 100644 --- a/media/server/gstplayer/source/tasks/generic/DeepElementAdded.cpp +++ b/media/server/gstplayer/source/tasks/generic/DeepElementAdded.cpp @@ -19,6 +19,7 @@ #include "tasks/generic/DeepElementAdded.h" #include "RialtoServerLogging.h" +#include "Utils.h" namespace { @@ -53,6 +54,8 @@ DeepElementAdded::DeepElementAdded(GenericPlayerContext &context, IGstGenericPla m_glibWrapper->gSignalConnect(G_OBJECT(m_element), "have-type", G_CALLBACK(onHaveType), &m_player); m_callbackRegistered = true; } + + m_context.gstProfiler->scheduleGstElementRecord(m_element); } } } @@ -69,42 +72,48 @@ void DeepElementAdded::execute() const m_context.playbackGroup.m_gstPipeline = GST_ELEMENT(m_pipeline); RIALTO_SERVER_LOG_DEBUG("Element = %p Bin = %p Pipeline = %p", m_element, m_bin, m_pipeline); - if (m_callbackRegistered) - { - m_context.playbackGroup.m_curAudioTypefind = m_element; - } if (m_elementName) { if (m_gstWrapper->gstObjectCast(m_bin) == m_gstWrapper->gstObjectCast(m_context.playbackGroup.m_curAudioDecodeBin)) { - if (m_glibWrapper->gStrrstr(m_elementName, "parse")) + if (isAudioParser(*m_gstWrapper, m_element)) { RIALTO_SERVER_LOG_DEBUG("curAudioParse = %s", m_elementName); m_context.playbackGroup.m_curAudioParse = m_element; } - else if (m_glibWrapper->gStrrstr(m_elementName, "dec")) + else if (isAudioDecoder(*m_gstWrapper, m_element)) { RIALTO_SERVER_LOG_DEBUG("curAudioDecoder = %s", m_elementName); m_context.playbackGroup.m_curAudioDecoder = m_element; } + else if (m_callbackRegistered && m_glibWrapper->gStrrstr(m_elementName, "typefind")) + { + RIALTO_SERVER_LOG_DEBUG("curAudioTypefind = %s", m_elementName); + m_context.playbackGroup.m_curAudioTypefind = m_element; + } } - else + else if (isAudioSink(*m_gstWrapper, m_element)) { - if (m_glibWrapper->gStrrstr(m_elementName, "audiosink")) + GstElement *audioSinkParent = reinterpret_cast(m_gstWrapper->gstElementGetParent(m_element)); + if (audioSinkParent) { - GstElement *audioSinkParent = - reinterpret_cast(m_gstWrapper->gstElementGetParent(m_element)); - if (audioSinkParent) + gchar *audioSinkParentName = m_gstWrapper->gstElementGetName(audioSinkParent); + RIALTO_SERVER_LOG_DEBUG("audioSinkParentName = %s", audioSinkParentName); + if (audioSinkParentName && m_glibWrapper->gStrrstr(audioSinkParentName, "bin")) { - gchar *audioSinkParentName = m_gstWrapper->gstElementGetName(audioSinkParent); - RIALTO_SERVER_LOG_DEBUG("audioSinkParentName = %s", audioSinkParentName); - if (audioSinkParentName && m_glibWrapper->gStrrstr(audioSinkParentName, "bin")) + RIALTO_SERVER_LOG_DEBUG("curAudioPlaysinkBin = %s", audioSinkParentName); + if (m_context.playbackGroup.m_curAudioPlaysinkBin) { - RIALTO_SERVER_LOG_DEBUG("curAudioPlaysinkBin = %s", audioSinkParentName); - m_context.playbackGroup.m_curAudioPlaysinkBin = audioSinkParent; + // Unref previous audio playsink bin, if exists + m_gstWrapper->gstObjectUnref(m_context.playbackGroup.m_curAudioPlaysinkBin); } - m_glibWrapper->gFree(audioSinkParentName); + m_context.playbackGroup.m_curAudioPlaysinkBin = audioSinkParent; + } + else + { + m_gstWrapper->gstObjectUnref(audioSinkParent); } + m_glibWrapper->gFree(audioSinkParentName); } } } diff --git a/media/server/gstplayer/source/tasks/generic/Eos.cpp b/media/server/gstplayer/source/tasks/generic/Eos.cpp index 371487570..af2a8193e 100644 --- a/media/server/gstplayer/source/tasks/generic/Eos.cpp +++ b/media/server/gstplayer/source/tasks/generic/Eos.cpp @@ -26,7 +26,7 @@ namespace firebolt::rialto::server::tasks::generic { Eos::Eos(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - std::shared_ptr gstWrapper, + const std::shared_ptr &gstWrapper, const firebolt::rialto::MediaSourceType &type) : m_context{context}, m_player{player}, m_gstWrapper{gstWrapper}, m_type{type} { diff --git a/media/server/gstplayer/source/tasks/generic/FinishSetupSource.cpp b/media/server/gstplayer/source/tasks/generic/FinishSetupSource.cpp index f2a5cf323..a4d49fdf4 100644 --- a/media/server/gstplayer/source/tasks/generic/FinishSetupSource.cpp +++ b/media/server/gstplayer/source/tasks/generic/FinishSetupSource.cpp @@ -124,5 +124,8 @@ void FinishSetupSource::execute() const m_context.setupSourceFinished = true; RIALTO_SERVER_LOG_MIL("All sources attached."); + auto recordId = m_context.gstProfiler->createRecord("All Sources Attached"); + if (recordId) + m_context.gstProfiler->logRecord(recordId.value()); } } // namespace firebolt::rialto::server::tasks::generic diff --git a/media/server/gstplayer/source/tasks/generic/Flush.cpp b/media/server/gstplayer/source/tasks/generic/Flush.cpp index 9c7c5196f..f4551487a 100644 --- a/media/server/gstplayer/source/tasks/generic/Flush.cpp +++ b/media/server/gstplayer/source/tasks/generic/Flush.cpp @@ -25,10 +25,10 @@ namespace firebolt::rialto::server::tasks::generic { Flush::Flush(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, IGstGenericPlayerClient *client, - std::shared_ptr gstWrapper, const MediaSourceType &type, - bool resetTime) + const std::shared_ptr &gstWrapper, const MediaSourceType &type, + bool resetTime, bool isAsync) : m_context{context}, m_player{player}, m_gstPlayerClient{client}, m_gstWrapper{gstWrapper}, m_type{type}, - m_resetTime{resetTime} + m_resetTime{resetTime}, m_isAsync{isAsync} { RIALTO_SERVER_LOG_DEBUG("Constructing Flush"); } @@ -40,13 +40,6 @@ Flush::~Flush() void Flush::execute() const { - if (m_context.flushOnPrerollController.shouldPostponeFlush(m_type)) - { - RIALTO_SERVER_LOG_WARN("Postponing Flush for %s source", common::convertMediaSourceType(m_type)); - m_player.postponeFlush(m_type, m_resetTime); - return; - } - RIALTO_SERVER_LOG_DEBUG("Executing Flush for %s source", common::convertMediaSourceType(m_type)); // Get source first @@ -83,9 +76,9 @@ void Flush::execute() const if (GST_STATE(m_context.pipeline) >= GST_STATE_PAUSED) { - m_player.stopPositionReportingAndCheckAudioUnderflowTimer(); - m_context.flushOnPrerollController.setFlushing(m_type, GST_STATE(m_context.pipeline)); + m_context.flushOnPrerollController->waitIfRequired(m_type); + RIALTO_SERVER_LOG_MIL("Sending flush event for %s source.", common::convertMediaSourceType(m_type)); // Flush source GstEvent *flushStart = m_gstWrapper->gstEventNewFlushStart(); if (!m_gstWrapper->gstElementSendEvent(source, flushStart)) @@ -98,6 +91,11 @@ void Flush::execute() const { RIALTO_SERVER_LOG_WARN("failed to send flush-stop event for %s", common::convertMediaSourceType(m_type)); } + + if (m_isAsync) + { + m_context.flushOnPrerollController->setFlushing(m_type); + } } else { diff --git a/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp b/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp index 97b2d3452..318f04379 100644 --- a/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp +++ b/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp @@ -33,7 +33,6 @@ #include "tasks/generic/Play.h" #include "tasks/generic/ProcessAudioGap.h" #include "tasks/generic/ReadShmDataAndAttachSamples.h" -#include "tasks/generic/RemoveSource.h" #include "tasks/generic/RenderFrame.h" #include "tasks/generic/ReportPosition.h" #include "tasks/generic/SetBufferingLimit.h" @@ -147,13 +146,6 @@ std::unique_ptr GenericPlayerTaskFactory::createReadShmDataAndAttac return std::make_unique(context, m_gstWrapper, player, dataReader); } -std::unique_ptr -GenericPlayerTaskFactory::createRemoveSource(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - const firebolt::rialto::MediaSourceType &type) const -{ - return std::make_unique(context, player, m_client, m_gstWrapper, type); -} - std::unique_ptr GenericPlayerTaskFactory::createReportPosition(GenericPlayerContext &context, IGstGenericPlayerPrivate &player) const { @@ -295,9 +287,9 @@ std::unique_ptr GenericPlayerTaskFactory::createPing(std::unique_pt std::unique_ptr GenericPlayerTaskFactory::createFlush(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, const firebolt::rialto::MediaSourceType &type, - bool resetTime) const + bool resetTime, bool isAsync) const { - return std::make_unique(context, player, m_client, m_gstWrapper, type, resetTime); + return std::make_unique(context, player, m_client, m_gstWrapper, type, resetTime, isAsync); } std::unique_ptr diff --git a/media/server/gstplayer/source/tasks/generic/HandleBusMessage.cpp b/media/server/gstplayer/source/tasks/generic/HandleBusMessage.cpp index a6bbdcb8f..c8af5f1d2 100644 --- a/media/server/gstplayer/source/tasks/generic/HandleBusMessage.cpp +++ b/media/server/gstplayer/source/tasks/generic/HandleBusMessage.cpp @@ -54,13 +54,18 @@ void HandleBusMessage::execute() const { GstState oldState, newState, pending; m_gstWrapper->gstMessageParseStateChanged(m_message, &oldState, &newState, &pending); - RIALTO_SERVER_LOG_MIL("State changed (old: %s, new: %s, pending: %s)", - m_gstWrapper->gstElementStateGetName(oldState), - m_gstWrapper->gstElementStateGetName(newState), - m_gstWrapper->gstElementStateGetName(pending)); + const char *oldStateName = m_gstWrapper->gstElementStateGetName(oldState); + const char *newStateName = m_gstWrapper->gstElementStateGetName(newState); + const char *pendingStateName = m_gstWrapper->gstElementStateGetName(pending); + RIALTO_SERVER_LOG_MIL("State changed (old: %s, new: %s, pending: %s)", oldStateName, newStateName, + pendingStateName); + auto recordId = m_context.gstProfiler->createRecord("Pipeline State Changed", newStateName); + if (recordId) + m_context.gstProfiler->logRecord(recordId.value()); + if (newState == GST_STATE_PLAYING) + m_context.gstProfiler->logPipelineSummary(); - std::string filename = std::string(m_gstWrapper->gstElementStateGetName(oldState)) + "-" + - std::string(m_gstWrapper->gstElementStateGetName(newState)); + std::string filename = std::string(oldStateName) + "-" + std::string(newStateName); m_gstWrapper->gstDebugBinToDotFileWithTs(GST_BIN(m_context.pipeline), GST_DEBUG_GRAPH_SHOW_ALL, filename.c_str()); if (!m_gstPlayerClient) @@ -71,15 +76,15 @@ void HandleBusMessage::execute() const { case GST_STATE_NULL: { - m_context.flushOnPrerollController.reset(); m_gstPlayerClient->notifyPlaybackState(PlaybackState::STOPPED); break; } case GST_STATE_PAUSED: { + m_player.startNotifyPlaybackInfoTimer(); + m_player.stopPositionReportingAndCheckAudioUnderflowTimer(); if (pending != GST_STATE_PAUSED) { - m_context.flushOnPrerollController.stateReached(newState); // If async flush was requested before HandleBusMessage task creation (but it was not executed yet) // or if async flush was created after HandleBusMessage task creation (but before its execution) // we can't report playback state, because async flush causes state loss - reported state is probably invalid. @@ -93,8 +98,8 @@ void HandleBusMessage::execute() const // Subsequent newState==GST_STATE_PAUSED, pending!=GST_STATE_PAUSED transition will // indicate that the pipeline is prerolled and it reached GST_STATE_PAUSED state after seek. m_gstPlayerClient->notifyPlaybackState(PlaybackState::PAUSED); - m_player.notifyPlaybackInfo(); } + if (m_player.hasSourceType(MediaSourceType::SUBTITLE)) { m_player.stopSubtitleClockResyncTimer(); @@ -103,8 +108,6 @@ void HandleBusMessage::execute() const } case GST_STATE_PLAYING: { - m_context.flushOnPrerollController.stateReached(newState); - m_player.executePostponedFlushes(); // If async flush was requested before HandleBusMessage task creation (but it was not executed yet) // or if async flush was created after HandleBusMessage task creation (but before its execution) // we can't report playback state, because async flush causes state loss - reported state is probably invalid. @@ -128,8 +131,12 @@ void HandleBusMessage::execute() const break; } case GST_STATE_VOID_PENDING: + { + break; + } case GST_STATE_READY: { + m_player.stopNotifyPlaybackInfoTimer(); break; } } diff --git a/media/server/gstplayer/source/tasks/generic/NeedData.cpp b/media/server/gstplayer/source/tasks/generic/NeedData.cpp index ca6c1865e..acf7417c5 100644 --- a/media/server/gstplayer/source/tasks/generic/NeedData.cpp +++ b/media/server/gstplayer/source/tasks/generic/NeedData.cpp @@ -59,11 +59,6 @@ void NeedData::execute() const if (m_gstPlayerClient && !elem.second.isNeedDataPending) { - if (sourceType == MediaSourceType::AUDIO && m_context.audioSourceRemoved) - { - RIALTO_SERVER_LOG_DEBUG("Audio source is removed, no need to request data"); - break; - } elem.second.isNeedDataPending = m_gstPlayerClient->notifyNeedMediaData(sourceType); } break; diff --git a/media/server/gstplayer/source/tasks/generic/ProcessAudioGap.cpp b/media/server/gstplayer/source/tasks/generic/ProcessAudioGap.cpp index 13f5b870b..049f8b89a 100644 --- a/media/server/gstplayer/source/tasks/generic/ProcessAudioGap.cpp +++ b/media/server/gstplayer/source/tasks/generic/ProcessAudioGap.cpp @@ -25,7 +25,7 @@ namespace firebolt::rialto::server::tasks::generic ProcessAudioGap::ProcessAudioGap( GenericPlayerContext &context, const std::shared_ptr &gstWrapper, const std::shared_ptr &glibWrapper, - const std::shared_ptr rdkGstreamerUtilsWrapper, + const std::shared_ptr &rdkGstreamerUtilsWrapper, std::int64_t position, std::uint32_t duration, std::int64_t discontinuityGap, bool audioAac) : m_context{context}, m_gstWrapper{gstWrapper}, m_glibWrapper{glibWrapper}, m_rdkGstreamerUtilsWrapper{rdkGstreamerUtilsWrapper}, m_position{position}, m_duration{duration}, diff --git a/media/server/gstplayer/source/tasks/generic/RemoveSource.cpp b/media/server/gstplayer/source/tasks/generic/RemoveSource.cpp deleted file mode 100644 index 29127c08c..000000000 --- a/media/server/gstplayer/source/tasks/generic/RemoveSource.cpp +++ /dev/null @@ -1,81 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Sky UK - * - * 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. - */ - -#include "tasks/generic/RemoveSource.h" -#include "RialtoServerLogging.h" -#include "TypeConverters.h" - -namespace firebolt::rialto::server::tasks::generic -{ -RemoveSource::RemoveSource(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - IGstGenericPlayerClient *client, - std::shared_ptr gstWrapper, - const MediaSourceType &type) - : m_context{context}, m_player{player}, m_gstPlayerClient{client}, m_gstWrapper{gstWrapper}, m_type{type} -{ - RIALTO_SERVER_LOG_DEBUG("Constructing RemoveSource"); -} - -RemoveSource::~RemoveSource() -{ - RIALTO_SERVER_LOG_DEBUG("RemoveSource finished"); -} - -void RemoveSource::execute() const -{ - RIALTO_SERVER_LOG_DEBUG("Executing RemoveSource for %s source", common::convertMediaSourceType(m_type)); - if (MediaSourceType::AUDIO != m_type) - { - RIALTO_SERVER_LOG_DEBUG("RemoveSource not supported for type != AUDIO"); - return; - } - m_context.audioSourceRemoved = true; - m_gstPlayerClient->invalidateActiveRequests(m_type); - GstElement *source{nullptr}; - auto sourceElem = m_context.streamInfo.find(m_type); - if (sourceElem != m_context.streamInfo.end()) - { - source = sourceElem->second.appSrc; - } - if (!source) - { - RIALTO_SERVER_LOG_WARN("failed to flush - source is NULL"); - return; - } - sourceElem->second.buffers.clear(); - sourceElem->second.isDataNeeded = false; - sourceElem->second.isNeedDataPending = false; - m_player.stopPositionReportingAndCheckAudioUnderflowTimer(); - GstEvent *flushStart = m_gstWrapper->gstEventNewFlushStart(); - if (!m_gstWrapper->gstElementSendEvent(source, flushStart)) - { - RIALTO_SERVER_LOG_WARN("failed to send flush-start event"); - } - GstEvent *flushStop = m_gstWrapper->gstEventNewFlushStop(FALSE); - if (!m_gstWrapper->gstElementSendEvent(source, flushStop)) - { - RIALTO_SERVER_LOG_WARN("failed to send flush-stop event"); - } - - // Turn audio off, removing audio sink from playsink - m_player.setPlaybinFlags(false); - - RIALTO_SERVER_LOG_MIL("%s source removed", common::convertMediaSourceType(m_type)); -} -} // namespace firebolt::rialto::server::tasks::generic diff --git a/media/server/gstplayer/source/tasks/generic/SetMute.cpp b/media/server/gstplayer/source/tasks/generic/SetMute.cpp index da66764a8..5bd4743e1 100644 --- a/media/server/gstplayer/source/tasks/generic/SetMute.cpp +++ b/media/server/gstplayer/source/tasks/generic/SetMute.cpp @@ -25,8 +25,8 @@ namespace firebolt::rialto::server::tasks::generic { SetMute::SetMute(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, const MediaSourceType &mediaSourceType, bool mute) : m_context{context}, m_player{player}, m_gstWrapper{gstWrapper}, m_glibWrapper{glibWrapper}, m_mediaSourceType{mediaSourceType}, m_mute{mute} diff --git a/media/server/gstplayer/source/tasks/generic/SetPlaybackRate.cpp b/media/server/gstplayer/source/tasks/generic/SetPlaybackRate.cpp index f5f8f609c..dc7bb9ffb 100644 --- a/media/server/gstplayer/source/tasks/generic/SetPlaybackRate.cpp +++ b/media/server/gstplayer/source/tasks/generic/SetPlaybackRate.cpp @@ -31,8 +31,9 @@ const char kCustomInstantRateChangeEventName[] = "custom-instant-rate-change"; namespace firebolt::rialto::server::tasks::generic { SetPlaybackRate::SetPlaybackRate(GenericPlayerContext &context, - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, double rate) + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, + double rate) : m_context{context}, m_gstWrapper{gstWrapper}, m_glibWrapper{glibWrapper}, m_rate{rate} { RIALTO_SERVER_LOG_DEBUG("Constructing SetPlaybackRate"); diff --git a/media/server/gstplayer/source/tasks/generic/SetPosition.cpp b/media/server/gstplayer/source/tasks/generic/SetPosition.cpp index df9d1b72b..db485ddad 100644 --- a/media/server/gstplayer/source/tasks/generic/SetPosition.cpp +++ b/media/server/gstplayer/source/tasks/generic/SetPosition.cpp @@ -28,7 +28,8 @@ namespace firebolt::rialto::server::tasks::generic { SetPosition::SetPosition(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, IGstGenericPlayerClient *client, - std::shared_ptr gstWrapper, std::int64_t position) + const std::shared_ptr &gstWrapper, + std::int64_t position) : m_context{context}, m_player{player}, m_gstPlayerClient{client}, m_gstWrapper{gstWrapper}, m_position{position} { RIALTO_SERVER_LOG_DEBUG("Constructing SetPosition"); diff --git a/media/server/gstplayer/source/tasks/generic/SetTextTrackIdentifier.cpp b/media/server/gstplayer/source/tasks/generic/SetTextTrackIdentifier.cpp index b29ab0572..7b72b08d4 100644 --- a/media/server/gstplayer/source/tasks/generic/SetTextTrackIdentifier.cpp +++ b/media/server/gstplayer/source/tasks/generic/SetTextTrackIdentifier.cpp @@ -24,7 +24,7 @@ namespace firebolt::rialto::server::tasks::generic { SetTextTrackIdentifier::SetTextTrackIdentifier(GenericPlayerContext &context, - std::shared_ptr glibWrapper, + const std::shared_ptr &glibWrapper, const std::string &textTrackIdentifier) : m_context{context}, m_glibWrapper{glibWrapper}, m_textTrackIdentifier{textTrackIdentifier} { diff --git a/media/server/gstplayer/source/tasks/generic/SetVolume.cpp b/media/server/gstplayer/source/tasks/generic/SetVolume.cpp index 0ceeae94f..e904392b2 100644 --- a/media/server/gstplayer/source/tasks/generic/SetVolume.cpp +++ b/media/server/gstplayer/source/tasks/generic/SetVolume.cpp @@ -27,9 +27,9 @@ namespace firebolt::rialto::server::tasks::generic { SetVolume::SetVolume(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, - std::shared_ptr rdkGstreamerUtilsWrapper, + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, + const std::shared_ptr &rdkGstreamerUtilsWrapper, double targetVolume, uint32_t volumeDuration, firebolt::rialto::EaseType easeType) : m_context{context}, m_player{player}, m_gstWrapper{gstWrapper}, m_glibWrapper{glibWrapper}, m_rdkGstreamerUtilsWrapper{rdkGstreamerUtilsWrapper}, m_targetVolume{targetVolume}, diff --git a/media/server/gstplayer/source/tasks/generic/SetupElement.cpp b/media/server/gstplayer/source/tasks/generic/SetupElement.cpp index 99588430a..d3fd0f12a 100644 --- a/media/server/gstplayer/source/tasks/generic/SetupElement.cpp +++ b/media/server/gstplayer/source/tasks/generic/SetupElement.cpp @@ -129,8 +129,8 @@ void autoAudioSinkChildRemovedCallback(GstChildProxy *obj, GObject *object, gcha namespace firebolt::rialto::server::tasks::generic { SetupElement::SetupElement(GenericPlayerContext &context, - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, IGstGenericPlayerPrivate &player, GstElement *element) : m_context{context}, m_gstWrapper{gstWrapper}, m_glibWrapper{glibWrapper}, m_player{player}, m_element{element} { @@ -309,6 +309,12 @@ void SetupElement::execute() const { m_player.setBufferingLimit(); } + if (m_context.isLive && + m_glibWrapper->gObjectClassFindProperty(G_OBJECT_GET_CLASS(m_element), "enable-rate-correction")) + { + RIALTO_SERVER_LOG_INFO("Enabling rate correction for broadcom decoder."); + m_glibWrapper->gObjectSet(m_element, "enable-rate-correction", TRUE, nullptr); + } } else if (isAudioSink(*m_gstWrapper, m_element)) { diff --git a/media/server/gstplayer/source/tasks/generic/Stop.cpp b/media/server/gstplayer/source/tasks/generic/Stop.cpp index 3f82478b5..bc566d6a7 100644 --- a/media/server/gstplayer/source/tasks/generic/Stop.cpp +++ b/media/server/gstplayer/source/tasks/generic/Stop.cpp @@ -38,6 +38,7 @@ void Stop::execute() const { RIALTO_SERVER_LOG_DEBUG("Executing Stop"); m_player.stopPositionReportingAndCheckAudioUnderflowTimer(); + m_player.stopNotifyPlaybackInfoTimer(); m_player.changePipelineState(GST_STATE_NULL); for (auto &streamInfo : m_context.streamInfo) { diff --git a/media/server/gstplayer/source/tasks/generic/UpdatePlaybackGroup.cpp b/media/server/gstplayer/source/tasks/generic/UpdatePlaybackGroup.cpp index 36f3f79b7..489ccf047 100644 --- a/media/server/gstplayer/source/tasks/generic/UpdatePlaybackGroup.cpp +++ b/media/server/gstplayer/source/tasks/generic/UpdatePlaybackGroup.cpp @@ -23,8 +23,8 @@ namespace firebolt::rialto::server::tasks::generic { UpdatePlaybackGroup::UpdatePlaybackGroup(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, GstElement *typefind, const GstCaps *caps) : m_context{context}, m_player{player}, m_gstWrapper{gstWrapper}, m_glibWrapper{glibWrapper}, m_typefind{typefind}, m_caps{caps} @@ -68,6 +68,20 @@ void UpdatePlaybackGroup::execute() const { m_player.setUseBuffering(); } + if (m_context.playbackGroup.m_linkTypefindParser && m_context.playbackGroup.m_curAudioTypefind && + m_context.playbackGroup.m_curAudioParse) + { + if (m_gstWrapper->gstElementLink(m_context.playbackGroup.m_curAudioTypefind, + m_context.playbackGroup.m_curAudioParse)) + { + RIALTO_SERVER_LOG_DEBUG("Linked typefind to parser"); + m_context.playbackGroup.m_linkTypefindParser = false; + } + else + { + RIALTO_SERVER_LOG_DEBUG("Failed to link typefind to parser"); + } + } } m_glibWrapper->gFree(elementName); m_gstWrapper->gstObjectUnref(typeFindParent); diff --git a/media/server/gstplayer/source/tasks/webAudio/Eos.cpp b/media/server/gstplayer/source/tasks/webAudio/Eos.cpp index be11e5405..33e2d91e4 100644 --- a/media/server/gstplayer/source/tasks/webAudio/Eos.cpp +++ b/media/server/gstplayer/source/tasks/webAudio/Eos.cpp @@ -24,7 +24,7 @@ namespace firebolt::rialto::server::tasks::webaudio { -Eos::Eos(WebAudioPlayerContext &context, std::shared_ptr gstWrapper) +Eos::Eos(WebAudioPlayerContext &context, const std::shared_ptr &gstWrapper) : m_context{context}, m_gstWrapper{gstWrapper} { RIALTO_SERVER_LOG_DEBUG("Constructing Eos"); diff --git a/media/server/gstplayer/source/tasks/webAudio/HandleBusMessage.cpp b/media/server/gstplayer/source/tasks/webAudio/HandleBusMessage.cpp index e8ffad051..12de30447 100644 --- a/media/server/gstplayer/source/tasks/webAudio/HandleBusMessage.cpp +++ b/media/server/gstplayer/source/tasks/webAudio/HandleBusMessage.cpp @@ -27,8 +27,8 @@ namespace firebolt::rialto::server::tasks::webaudio { HandleBusMessage::HandleBusMessage(WebAudioPlayerContext &context, IGstWebAudioPlayerPrivate &player, IGstWebAudioPlayerClient *client, - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, GstMessage *message) : m_context{context}, m_player{player}, m_gstPlayerClient{client}, m_gstWrapper{gstWrapper}, m_glibWrapper{glibWrapper}, m_message{message} diff --git a/media/server/gstplayer/source/tasks/webAudio/SetCaps.cpp b/media/server/gstplayer/source/tasks/webAudio/SetCaps.cpp index 5d42d4c31..533158f02 100644 --- a/media/server/gstplayer/source/tasks/webAudio/SetCaps.cpp +++ b/media/server/gstplayer/source/tasks/webAudio/SetCaps.cpp @@ -33,8 +33,8 @@ namespace class WebAudioCapsBuilder { public: - WebAudioCapsBuilder(std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper) + WebAudioCapsBuilder(const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper) : m_gstWrapper(gstWrapper), m_glibWrapper(glibWrapper) { } @@ -49,8 +49,8 @@ class WebAudioCapsBuilder class WebAudioPcmCapsBuilder : public WebAudioCapsBuilder { public: - WebAudioPcmCapsBuilder(std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, + WebAudioPcmCapsBuilder(const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, const WebAudioPcmConfig &pcmConfig) : WebAudioCapsBuilder(gstWrapper, glibWrapper), m_pcmConfig(pcmConfig) { @@ -105,8 +105,9 @@ class WebAudioPcmCapsBuilder : public WebAudioCapsBuilder }; }; // namespace -SetCaps::SetCaps(WebAudioPlayerContext &context, std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, +SetCaps::SetCaps(WebAudioPlayerContext &context, + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, const std::string &audioMimeType, std::weak_ptr webAudioConfig) : m_context{context}, m_gstWrapper{gstWrapper}, m_glibWrapper{glibWrapper}, m_audioMimeType{audioMimeType} { diff --git a/media/server/gstplayer/source/tasks/webAudio/SetVolume.cpp b/media/server/gstplayer/source/tasks/webAudio/SetVolume.cpp index 88c94bfe6..84b2605a6 100644 --- a/media/server/gstplayer/source/tasks/webAudio/SetVolume.cpp +++ b/media/server/gstplayer/source/tasks/webAudio/SetVolume.cpp @@ -25,7 +25,7 @@ namespace firebolt::rialto::server::tasks::webaudio { SetVolume::SetVolume(WebAudioPlayerContext &context, - std::shared_ptr gstWrapper, double volume) + const std::shared_ptr &gstWrapper, double volume) : m_context{context}, m_gstWrapper{gstWrapper}, m_volume{volume} { RIALTO_SERVER_LOG_DEBUG("Constructing SetVolume"); diff --git a/media/server/gstplayer/source/tasks/webAudio/WriteBuffer.cpp b/media/server/gstplayer/source/tasks/webAudio/WriteBuffer.cpp index 8d7aebd32..bda9b5685 100644 --- a/media/server/gstplayer/source/tasks/webAudio/WriteBuffer.cpp +++ b/media/server/gstplayer/source/tasks/webAudio/WriteBuffer.cpp @@ -27,7 +27,7 @@ namespace firebolt::rialto::server::tasks::webaudio { WriteBuffer::WriteBuffer(WebAudioPlayerContext &context, - std::shared_ptr gstWrapper, uint8_t *mainPtr, + const std::shared_ptr &gstWrapper, uint8_t *mainPtr, uint32_t mainLength, uint8_t *wrapPtr, uint32_t wrapLength) : m_context{context}, m_gstWrapper{gstWrapper}, m_mainPtr{mainPtr}, m_mainLength{mainLength}, m_wrapPtr{wrapPtr}, m_wrapLength{wrapLength} diff --git a/media/server/ipc/include/MediaPipelineModuleService.h b/media/server/ipc/include/MediaPipelineModuleService.h index f0321827b..92e54e463 100644 --- a/media/server/ipc/include/MediaPipelineModuleService.h +++ b/media/server/ipc/include/MediaPipelineModuleService.h @@ -84,6 +84,8 @@ class MediaPipelineModuleService : public IMediaPipelineModuleService ::google::protobuf::Closure *done) override; void getPosition(::google::protobuf::RpcController *controller, const ::firebolt::rialto::GetPositionRequest *request, ::firebolt::rialto::GetPositionResponse *response, ::google::protobuf::Closure *done) override; + void getDuration(::google::protobuf::RpcController *controller, const ::firebolt::rialto::GetDurationRequest *request, + ::firebolt::rialto::GetDurationResponse *response, ::google::protobuf::Closure *done) override; void setImmediateOutput(::google::protobuf::RpcController *controller, const ::firebolt::rialto::SetImmediateOutputRequest *request, ::firebolt::rialto::SetImmediateOutputResponse *response, diff --git a/media/server/ipc/source/MediaKeysModuleService.cpp b/media/server/ipc/source/MediaKeysModuleService.cpp index 4cb5f6293..6368f93ec 100644 --- a/media/server/ipc/source/MediaKeysModuleService.cpp +++ b/media/server/ipc/source/MediaKeysModuleService.cpp @@ -66,6 +66,10 @@ convertMediaKeyErrorStatus(const firebolt::rialto::MediaKeyErrorStatus &errorSta { return firebolt::rialto::ProtoMediaKeyErrorStatus::FAIL; } + case firebolt::rialto::MediaKeyErrorStatus::OUTPUT_RESTRICTED: + { + // TODO + } } return firebolt::rialto::ProtoMediaKeyErrorStatus::FAIL; } @@ -100,6 +104,22 @@ firebolt::rialto::InitDataType covertInitDataType(firebolt::rialto::GenerateRequ return firebolt::rialto::InitDataType::UNKNOWN; } } + +firebolt::rialto::LimitedDurationLicense +covertLimitedDurationLicense(firebolt::rialto::GenerateRequestRequest_LimitedDurationLicense protoLimitedDurationLicense) +{ + switch (protoLimitedDurationLicense) + { + case firebolt::rialto::GenerateRequestRequest_LimitedDurationLicense::GenerateRequestRequest_LimitedDurationLicense_NOT_SPECIFIED: + return firebolt::rialto::LimitedDurationLicense::NOT_SPECIFIED; + case firebolt::rialto::GenerateRequestRequest_LimitedDurationLicense::GenerateRequestRequest_LimitedDurationLicense_ENABLED: + return firebolt::rialto::LimitedDurationLicense::ENABLED; + case firebolt::rialto::GenerateRequestRequest_LimitedDurationLicense::GenerateRequestRequest_LimitedDurationLicense_DISABLED: + return firebolt::rialto::LimitedDurationLicense::DISABLED; + default: + return firebolt::rialto::LimitedDurationLicense::NOT_SPECIFIED; + } +} } // namespace namespace firebolt::rialto::server::ipc @@ -262,7 +282,7 @@ void MediaKeysModuleService::createKeySession(::google::protobuf::RpcController m_cdmService.createKeySession(request->media_keys_handle(), convertKeySessionType(request->session_type()), std::make_shared(request->media_keys_handle(), ipcController->getClient()), - request->is_ldl(), keySessionId); + keySessionId); if (MediaKeyErrorStatus::OK == status) { response->set_key_session_id(keySessionId); @@ -278,10 +298,11 @@ void MediaKeysModuleService::generateRequest(::google::protobuf::RpcController * { RIALTO_SERVER_LOG_DEBUG("entry:"); - MediaKeyErrorStatus status = m_cdmService.generateRequest(request->media_keys_handle(), request->key_session_id(), - covertInitDataType(request->init_data_type()), - std::vector{request->init_data().begin(), - request->init_data().end()}); + MediaKeyErrorStatus status = + m_cdmService.generateRequest(request->media_keys_handle(), request->key_session_id(), + covertInitDataType(request->init_data_type()), + std::vector{request->init_data().begin(), request->init_data().end()}, + covertLimitedDurationLicense(request->ldl_state())); response->set_error_status(convertMediaKeyErrorStatus(status)); done->Run(); } diff --git a/media/server/ipc/source/MediaPipelineModuleService.cpp b/media/server/ipc/source/MediaPipelineModuleService.cpp index 77c1387aa..b25712cf5 100644 --- a/media/server/ipc/source/MediaPipelineModuleService.cpp +++ b/media/server/ipc/source/MediaPipelineModuleService.cpp @@ -26,6 +26,7 @@ #include #include #include +#include namespace { @@ -391,7 +392,7 @@ void MediaPipelineModuleService::load(::google::protobuf::RpcController *control { RIALTO_SERVER_LOG_DEBUG("entry:"); if (!m_mediaPipelineService.load(request->session_id(), convertMediaType(request->type()), request->mime_type(), - request->url())) + request->url(), request->is_live())) { RIALTO_SERVER_LOG_ERROR("Load failed"); controller->SetFailed("Operation failed"); @@ -471,8 +472,8 @@ void MediaPipelineModuleService::attachSource(::google::protobuf::RpcController { framed = kConfig.framed(); } - AudioConfig audioConfig{numberofchannels, sampleRate, codecSpecificConfig, format, - layout, channelMask, streamHeaders, framed}; + AudioConfig audioConfig{numberofchannels, sampleRate, std::move(codecSpecificConfig), format, + layout, channelMask, std::move(streamHeaders), framed}; mediaSource = std::make_unique(request->mime_type(), hasDrm, audioConfig, @@ -566,11 +567,13 @@ void MediaPipelineModuleService::play(::google::protobuf::RpcController *control ::firebolt::rialto::PlayResponse *response, ::google::protobuf::Closure *done) { RIALTO_SERVER_LOG_DEBUG("entry:"); - if (!m_mediaPipelineService.play(request->session_id())) + bool async{false}; + if (!m_mediaPipelineService.play(request->session_id(), async)) { RIALTO_SERVER_LOG_ERROR("Play failed"); controller->SetFailed("Operation failed"); } + response->set_async(async); done->Run(); } @@ -662,6 +665,25 @@ void MediaPipelineModuleService::getPosition(::google::protobuf::RpcController * done->Run(); } +void MediaPipelineModuleService::getDuration(::google::protobuf::RpcController *controller, + const ::firebolt::rialto::GetDurationRequest *request, + ::firebolt::rialto::GetDurationResponse *response, + ::google::protobuf::Closure *done) +{ + RIALTO_SERVER_LOG_DEBUG("entry:"); + int64_t duration{}; + if (!m_mediaPipelineService.getDuration(request->session_id(), duration)) + { + RIALTO_SERVER_LOG_ERROR("Get duration failed"); + controller->SetFailed("Operation failed"); + } + else + { + response->set_duration(duration); + } + done->Run(); +} + void MediaPipelineModuleService::setImmediateOutput(::google::protobuf::RpcController *controller, const ::firebolt::rialto::SetImmediateOutputRequest *request, ::firebolt::rialto::SetImmediateOutputResponse *response, diff --git a/media/server/main/include/ActiveRequests.h b/media/server/main/include/ActiveRequests.h index b90ef73a7..8e4af1644 100644 --- a/media/server/main/include/ActiveRequests.h +++ b/media/server/main/include/ActiveRequests.h @@ -24,6 +24,7 @@ #include #include #include +#include namespace firebolt::rialto::server { @@ -54,6 +55,7 @@ class ActiveRequests : public IActiveRequests std::uint32_t m_bytesWritten; std::uint32_t m_maxMediaBytes; IMediaPipeline::MediaSegmentVector m_segments; + std::vector> m_segmentBuffers; }; ActiveRequests(); diff --git a/media/server/main/include/IMediaKeySession.h b/media/server/main/include/IMediaKeySession.h index bddb27155..5bb552cc1 100644 --- a/media/server/main/include/IMediaKeySession.h +++ b/media/server/main/include/IMediaKeySession.h @@ -55,14 +55,13 @@ class IMediaKeySessionFactory * @param[in] ocdmSystem : The ocdm system object to create the session on. * @param[in] sessionType : The session type. * @param[in] client : Client object for callbacks. - * @param[in] isLDL : Is this an LDL. * * @retval the new media keys instance or null on error. */ virtual std::unique_ptr createMediaKeySession(const std::string &keySystem, int32_t keySessionId, const firebolt::rialto::wrappers::IOcdmSystem &ocdmSystem, KeySessionType sessionType, - std::weak_ptr client, bool isLDL) const = 0; + std::weak_ptr client) const = 0; }; /** @@ -84,11 +83,12 @@ class IMediaKeySession * * @param[in] initDataType : The init data type. * @param[in] initData : The init data. + * @param[in] ldlState : The Limited Duration License state. Most of key systems do not need this parameter. * * @retval an error status. */ - virtual MediaKeyErrorStatus generateRequest(InitDataType initDataType, const std::vector &initData) = 0; - + virtual MediaKeyErrorStatus generateRequest(InitDataType initDataType, const std::vector &initData, + const LimitedDurationLicense &ldlState) = 0; /** * @brief Loads the existing key session. * @@ -179,13 +179,6 @@ class IMediaKeySession * @retval an error status. */ virtual MediaKeyErrorStatus selectKeyId(const std::vector &keyId) = 0; - - /** - * @brief Checks, if key system of media key session is Netflix playready. - * - * @retval true if key system is Netflix playready - */ - virtual bool isNetflixPlayreadyKeySystem() const = 0; }; } // namespace firebolt::rialto::server diff --git a/media/server/main/include/MainThread.h b/media/server/main/include/MainThread.h index b7fef969a..afb55ec62 100644 --- a/media/server/main/include/MainThread.h +++ b/media/server/main/include/MainThread.h @@ -68,9 +68,9 @@ class MainThread : public IMainThread int32_t registerClient() override; void unregisterClient(uint32_t clientId) override; - void enqueueTask(uint32_t clientId, Task task) override; - void enqueueTaskAndWait(uint32_t clientId, Task task) override; - void enqueuePriorityTaskAndWait(uint32_t clientId, Task task) override; + void enqueueTask(uint32_t clientId, const Task &task) override; + void enqueueTaskAndWait(uint32_t clientId, const Task &task) override; + void enqueuePriorityTaskAndWait(uint32_t clientId, const Task &task) override; private: /** @@ -78,6 +78,7 @@ class MainThread : public IMainThread */ struct TaskInfo { + bool done{false}; /**< A flag indicating whether the task has completed. */ uint32_t clientId; /**< The id of the client creating the task. */ Task task; /**< The task to execute. */ std::unique_ptr mutex; /**< Mutex for the task condition variable. */ diff --git a/media/server/main/include/MediaKeySession.h b/media/server/main/include/MediaKeySession.h index 0a05fee9c..e4eeca6a8 100644 --- a/media/server/main/include/MediaKeySession.h +++ b/media/server/main/include/MediaKeySession.h @@ -43,8 +43,7 @@ class MediaKeySessionFactory : public IMediaKeySessionFactory std::unique_ptr createMediaKeySession(const std::string &keySystem, int32_t keySessionId, const firebolt::rialto::wrappers::IOcdmSystem &ocdmSystem, KeySessionType sessionType, - std::weak_ptr client, - bool isLDL) const override; + std::weak_ptr client) const override; }; /** @@ -61,20 +60,19 @@ class MediaKeySession : public IMediaKeySession, public firebolt::rialto::wrappe * @param[in] ocdmSystem : The ocdm system object to create the session on. * @param[in] sessionType : The session type. * @param[in] client : Client object for callbacks. - * @param[in] isLDL : Is this an LDL. * @param[in] mainThreadFactory : The main thread factory. */ MediaKeySession(const std::string &keySystem, int32_t keySessionId, const firebolt::rialto::wrappers::IOcdmSystem &ocdmSystem, KeySessionType sessionType, - std::weak_ptr client, bool isLDL, - const std::shared_ptr &mainThreadFactory); + std::weak_ptr client, const std::shared_ptr &mainThreadFactory); /** * @brief Virtual destructor. */ virtual ~MediaKeySession(); - MediaKeyErrorStatus generateRequest(InitDataType initDataType, const std::vector &initData) override; + MediaKeyErrorStatus generateRequest(InitDataType initDataType, const std::vector &initData, + const LimitedDurationLicense &ldlState) override; MediaKeyErrorStatus loadSession() override; @@ -96,8 +94,6 @@ class MediaKeySession : public IMediaKeySession, public firebolt::rialto::wrappe MediaKeyErrorStatus selectKeyId(const std::vector &keyId) override; - bool isNetflixPlayreadyKeySystem() const override; - void onProcessChallenge(const char url[], const uint8_t challenge[], const uint16_t challengeLength) override; void onKeyUpdated(const uint8_t keyId[], const uint8_t keyIdLength) override; @@ -137,11 +133,6 @@ class MediaKeySession : public IMediaKeySession, public firebolt::rialto::wrappe */ std::shared_ptr m_mainThread; - /** - * @brief Is the session LDL. - */ - const bool m_kIsLDL; - /** * @brief Is the ocdm session constructed. */ @@ -187,12 +178,24 @@ class MediaKeySession : public IMediaKeySession, public firebolt::rialto::wrappe */ std::mutex m_ocdmErrorMutex; + /** + * @brief Drm header to be set once the session is constructed + */ + std::vector m_queuedDrmHeader; + + /** + * @brief Flag used to check if extended interface is used + */ + bool m_extendedInterfaceInUse{false}; + /** * @brief Posts a getChallenge task onto the main thread. * + * @param[in] ldlState : The Limited Duration License state. + * * The challenge data is retrieved from ocdm and notified on a onLicenseRequest. */ - void getChallenge(); + void getChallenge(const LimitedDurationLicense &ldlState); /** * @brief Initalises the ocdm error data which checks for onError callbacks. diff --git a/media/server/main/include/MediaKeysCapabilities.h b/media/server/main/include/MediaKeysCapabilities.h index 524d1d664..92cff1e41 100644 --- a/media/server/main/include/MediaKeysCapabilities.h +++ b/media/server/main/include/MediaKeysCapabilities.h @@ -57,8 +57,8 @@ class MediaKeysCapabilities : public IMediaKeysCapabilities * @param[in] ocdmFactory : The ocdm factory. * @param[in] ocdmSystemFactory : The ocdmSystem factory. */ - MediaKeysCapabilities(std::shared_ptr ocdmFactory, - std::shared_ptr ocdmSystemFactory); + MediaKeysCapabilities(const std::shared_ptr &ocdmFactory, + const std::shared_ptr &ocdmSystemFactory); /** * @brief Virtual destructor. diff --git a/media/server/main/include/MediaKeysServerInternal.h b/media/server/main/include/MediaKeysServerInternal.h index 6e934e004..12924decd 100644 --- a/media/server/main/include/MediaKeysServerInternal.h +++ b/media/server/main/include/MediaKeysServerInternal.h @@ -64,8 +64,8 @@ class MediaKeysServerInternal : public IMediaKeysServerInternal * */ MediaKeysServerInternal(const std::string &keySystem, const std::shared_ptr &mainThreadFactory, - std::shared_ptr ocdmSystemFactory, - std::shared_ptr mediaKeySessionFactory); + const std::shared_ptr &ocdmSystemFactory, + const std::shared_ptr &mediaKeySessionFactory); /** * @brief Virtual destructor. @@ -76,11 +76,12 @@ class MediaKeysServerInternal : public IMediaKeysServerInternal bool containsKey(int32_t keySessionId, const std::vector &keyId) override; - MediaKeyErrorStatus createKeySession(KeySessionType sessionType, std::weak_ptr client, bool isLDL, + MediaKeyErrorStatus createKeySession(KeySessionType sessionType, std::weak_ptr client, int32_t &keySessionId) override; MediaKeyErrorStatus generateRequest(int32_t keySessionId, InitDataType initDataType, - const std::vector &initData) override; + const std::vector &initData, + const LimitedDurationLicense &ldlState) override; MediaKeyErrorStatus loadSession(int32_t keySessionId) override; @@ -114,8 +115,6 @@ class MediaKeysServerInternal : public IMediaKeysServerInternal MediaKeyErrorStatus getMetricSystemData(std::vector &buffer) override; - bool isNetflixPlayreadyKeySystem() const override; - void ping(std::unique_ptr &&heartbeatHandler) override; private: @@ -154,13 +153,12 @@ class MediaKeysServerInternal : public IMediaKeysServerInternal * * @param[in] sessionType : The session type. * @param[in] client : Client object for callbacks - * @param[in] isLDL : Is this an LDL * @param[out] keySessionId: The key session id * * @retval an error status. */ MediaKeyErrorStatus createKeySessionInternal(KeySessionType sessionType, std::weak_ptr client, - bool isLDL, int32_t &keySessionId); + int32_t &keySessionId); /** * @brief Generate internally, only to be called on the main thread. @@ -168,11 +166,13 @@ class MediaKeysServerInternal : public IMediaKeysServerInternal * @param[in] keySessionId : The key session id for the session. * @param[in] initDataType : The init data type. * @param[in] initData : The init data. + * @param[in] ldlState : The Limited Duration License state. Most of key systems do not need this parameter. * * @retval an error status. */ MediaKeyErrorStatus generateRequestInternal(int32_t keySessionId, InitDataType initDataType, - const std::vector &initData); + const std::vector &initData, + const LimitedDurationLicense &ldlState); /** * @brief Load internally, only to be called on the main thread. diff --git a/media/server/main/include/MediaPipelineCapabilities.h b/media/server/main/include/MediaPipelineCapabilities.h index be5a34451..da5ec541b 100644 --- a/media/server/main/include/MediaPipelineCapabilities.h +++ b/media/server/main/include/MediaPipelineCapabilities.h @@ -55,7 +55,7 @@ class MediaPipelineCapabilities : public IMediaPipelineCapabilities * * @param[in] gstCapabilitiesFactory : The gstreamer capabilities factory. */ - explicit MediaPipelineCapabilities(std::shared_ptr gstCapabilitiesFactory); + explicit MediaPipelineCapabilities(const std::shared_ptr &gstCapabilitiesFactory); /** * @brief Virtual destructor. diff --git a/media/server/main/include/MediaPipelineServerInternal.h b/media/server/main/include/MediaPipelineServerInternal.h index e546ee795..b165419ee 100644 --- a/media/server/main/include/MediaPipelineServerInternal.h +++ b/media/server/main/include/MediaPipelineServerInternal.h @@ -78,11 +78,12 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public * @param[in] activeRequests : The active requests * @param[in] decryptionService : The decryption service */ - MediaPipelineServerInternal(std::shared_ptr client, const VideoRequirements &videoRequirements, + MediaPipelineServerInternal(const std::shared_ptr &client, + const VideoRequirements &videoRequirements, const std::shared_ptr &gstPlayerFactory, int sessionId, const std::shared_ptr &shmBuffer, const std::shared_ptr &mainThreadFactory, - std::shared_ptr timerFactory, + const std::shared_ptr &timerFactory, std::unique_ptr &&dataReaderFactory, std::unique_ptr &&activeRequests, IDecryptionService &decryptionService); @@ -91,7 +92,7 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public */ virtual ~MediaPipelineServerInternal(); - bool load(MediaType type, const std::string &mimeType, const std::string &url) override; + bool load(MediaType type, const std::string &mimeType, const std::string &url, bool isLive) override; bool attachSource(const std::unique_ptr &source) override; @@ -99,7 +100,7 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public bool allSourcesAttached() override; - bool play() override; + bool play(bool &async) override; bool pause() override; @@ -170,6 +171,8 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public bool switchSource(const std::unique_ptr &source) override; + bool getDuration(int64_t &duration) override; + AddSegmentStatus addSegment(uint32_t needDataRequestId, const std::unique_ptr &mediaSegment) override; std::weak_ptr getClient() override; @@ -313,10 +316,11 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public * @param[in] type : The media type. * @param[in] mimeType : The MIME type. * @param[in] url : The URL. + * @param[in] isLive : Indicates if the media is live. * * @retval true on success. */ - bool loadInternal(MediaType type, const std::string &mimeType, const std::string &url); + bool loadInternal(MediaType type, const std::string &mimeType, const std::string &url, bool isLive); /** * @brief Attach source internally, only to be called on the main thread. @@ -346,9 +350,11 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public /** * @brief Play internally, only to be called on the main thread. * + * @param[out] async : True if play method call is asynchronous + * * @retval true on success. */ - bool playInternal(); + bool playInternal(bool &async); /** * @brief Pause internally, only to be called on the main thread. diff --git a/media/server/main/include/ShmUtils.h b/media/server/main/include/ShmUtils.h index 6ebf425a6..ede9aa5a4 100644 --- a/media/server/main/include/ShmUtils.h +++ b/media/server/main/include/ShmUtils.h @@ -25,6 +25,7 @@ namespace firebolt::rialto::server { +constexpr std::uint32_t kPrerollNumFrames{3}; constexpr std::uint32_t kMaxFrames{24}; constexpr std::uint32_t getMaxMetadataBytes() { diff --git a/media/server/main/interface/IDecryptionService.h b/media/server/main/interface/IDecryptionService.h index 322b7aad4..3d52056f5 100644 --- a/media/server/main/interface/IDecryptionService.h +++ b/media/server/main/interface/IDecryptionService.h @@ -32,7 +32,7 @@ class IDecryptionService public: virtual ~IDecryptionService() = default; virtual MediaKeyErrorStatus decrypt(int32_t keySessionId, GstBuffer *encrypted, GstCaps *caps) = 0; - virtual bool isNetflixPlayreadyKeySystem(int32_t keySessionId) = 0; + virtual bool isExtendedInterfaceUsed(int32_t keySessionId) = 0; virtual MediaKeyErrorStatus selectKeyId(int32_t keySessionId, const std::vector &keyId) = 0; virtual void incrementSessionIdUsageCounter(int32_t keySessionId) = 0; virtual void decrementSessionIdUsageCounter(int32_t keySessionId) = 0; diff --git a/media/server/main/interface/IMainThread.h b/media/server/main/interface/IMainThread.h index 6a4ac3490..7e9a12f2a 100644 --- a/media/server/main/interface/IMainThread.h +++ b/media/server/main/interface/IMainThread.h @@ -94,7 +94,7 @@ class IMainThread * @param[in] clientId : The id of the registered client. * @param[in] task : Task to queue. */ - virtual void enqueueTask(uint32_t clientId, Task task) = 0; + virtual void enqueueTask(uint32_t clientId, const Task &task) = 0; /** * @brief Enqueue a task on the main thread and wait for it to finish before returning. @@ -102,7 +102,7 @@ class IMainThread * @param[in] clientId : The id of the registered client. * @param[in] task : Task to queue. */ - virtual void enqueueTaskAndWait(uint32_t clientId, Task task) = 0; + virtual void enqueueTaskAndWait(uint32_t clientId, const Task &task) = 0; /** * @brief Enqueue a priority task on the main thread and wait for it to finish before returning. @@ -110,7 +110,7 @@ class IMainThread * @param[in] clientId : The id of the registered client. * @param[in] task : Task to queue. */ - virtual void enqueuePriorityTaskAndWait(uint32_t clientId, Task task) = 0; + virtual void enqueuePriorityTaskAndWait(uint32_t clientId, const Task &task) = 0; }; } // namespace firebolt::rialto::server diff --git a/media/server/main/interface/IMediaKeysServerInternal.h b/media/server/main/interface/IMediaKeysServerInternal.h index 45069b19a..5de4ffc60 100644 --- a/media/server/main/interface/IMediaKeysServerInternal.h +++ b/media/server/main/interface/IMediaKeysServerInternal.h @@ -89,13 +89,6 @@ class IMediaKeysServerInternal : public IMediaKeys */ virtual MediaKeyErrorStatus decrypt(int32_t keySessionId, GstBuffer *encrypted, GstCaps *caps) = 0; - /** - * @brief Checks, if key system of media key session is Netflix Playready. - * - * @retval true if key system is Playready - */ - virtual bool isNetflixPlayreadyKeySystem() const = 0; - /** * @brief Checks, if MediaKeys main thread is not deadlocked * diff --git a/media/server/main/source/ActiveRequests.cpp b/media/server/main/source/ActiveRequests.cpp index 3431fc672..343adcd1c 100644 --- a/media/server/main/source/ActiveRequests.cpp +++ b/media/server/main/source/ActiveRequests.cpp @@ -18,18 +18,14 @@ */ #include "ActiveRequests.h" +#include "RialtoServerLogging.h" #include +#include #include namespace firebolt::rialto::server { -ActiveRequests::ActiveRequestsData::~ActiveRequestsData() -{ - for (std::unique_ptr &segment : m_segments) - { - delete[] segment->getData(); - } -} +ActiveRequests::ActiveRequestsData::~ActiveRequestsData() {} AddSegmentStatus ActiveRequests::ActiveRequestsData::addSegment(const std::unique_ptr &segment) { @@ -38,10 +34,10 @@ AddSegmentStatus ActiveRequests::ActiveRequestsData::addSegment(const std::uniqu std::unique_ptr copiedSegment = segment->copy(); - uint8_t *data = new uint8_t[segment->getDataLength()]; - std::memcpy(data, segment->getData(), segment->getDataLength()); + m_segmentBuffers.emplace_back(segment->getDataLength()); + std::memcpy(m_segmentBuffers.back().data(), segment->getData(), segment->getDataLength()); - copiedSegment->setData(segment->getDataLength(), data); + copiedSegment->setData(m_segmentBuffers.back().size(), m_segmentBuffers.back().data()); m_segments.push_back(std::move(copiedSegment)); m_bytesWritten += segment->getDataLength(); @@ -53,7 +49,23 @@ ActiveRequests::ActiveRequests() : m_currentId{0} {} std::uint32_t ActiveRequests::insert(const MediaSourceType &mediaSourceType, std::uint32_t maxMediaBytes) { std::unique_lock lock{m_mutex}; - m_requestMap.insert(std::make_pair(m_currentId, ActiveRequestsData(mediaSourceType, maxMediaBytes))); + + if (m_currentId == std::numeric_limits::max()) + { + m_currentId = 1; + } + + auto [it, inserted] = m_requestMap.emplace(m_currentId, ActiveRequestsData(mediaSourceType, maxMediaBytes)); + if (!inserted) + { + do + { + ++m_currentId; + } while (m_requestMap.find(m_currentId) != m_requestMap.end()); + + m_requestMap.emplace(m_currentId, ActiveRequestsData(mediaSourceType, maxMediaBytes)); + } + return m_currentId++; } diff --git a/media/server/main/source/MainThread.cpp b/media/server/main/source/MainThread.cpp index 30c2661d0..38aa64746 100644 --- a/media/server/main/source/MainThread.cpp +++ b/media/server/main/source/MainThread.cpp @@ -99,6 +99,7 @@ void MainThread::mainThreadLoop() if (nullptr != kTaskInfo->cv) { std::unique_lock lockTask(*(kTaskInfo->mutex)); + kTaskInfo->done = true; kTaskInfo->cv->notify_one(); } } @@ -111,7 +112,7 @@ const std::shared_ptr MainThread::waitForTask() { m_taskQueueCv.wait(lock, [this] { return !m_taskQueue.empty(); }); } - const std::shared_ptr kTaskInfo = m_taskQueue.front(); + const auto kTaskInfo = m_taskQueue.front(); m_taskQueue.pop_front(); return kTaskInfo; } @@ -136,19 +137,19 @@ void MainThread::unregisterClient(uint32_t clientId) m_registeredClients.erase(clientId); } -void MainThread::enqueueTask(uint32_t clientId, Task task) +void MainThread::enqueueTask(uint32_t clientId, const Task &task) { std::shared_ptr newTask = std::make_shared(); newTask->clientId = clientId; newTask->task = task; { std::unique_lock lock(m_taskQueueMutex); - m_taskQueue.push_back(newTask); + m_taskQueue.push_back(std::move(newTask)); } m_taskQueueCv.notify_one(); } -void MainThread::enqueueTaskAndWait(uint32_t clientId, Task task) +void MainThread::enqueueTaskAndWait(uint32_t clientId, const Task &task) { std::shared_ptr newTask = std::make_shared(); newTask->clientId = clientId; @@ -164,11 +165,11 @@ void MainThread::enqueueTaskAndWait(uint32_t clientId, Task task) } m_taskQueueCv.notify_one(); - newTask->cv->wait(lockTask); + newTask->cv->wait(lockTask, [&] { return newTask->done; }); } } -void MainThread::enqueuePriorityTaskAndWait(uint32_t clientId, Task task) +void MainThread::enqueuePriorityTaskAndWait(uint32_t clientId, const Task &task) { std::shared_ptr newTask = std::make_shared(); newTask->clientId = clientId; @@ -184,7 +185,7 @@ void MainThread::enqueuePriorityTaskAndWait(uint32_t clientId, Task task) } m_taskQueueCv.notify_one(); - newTask->cv->wait(lockTask); + newTask->cv->wait(lockTask, [&] { return newTask->done; }); } } } // namespace firebolt::rialto::server diff --git a/media/server/main/source/MediaKeySession.cpp b/media/server/main/source/MediaKeySession.cpp index 414856cd8..e6cc38355 100644 --- a/media/server/main/source/MediaKeySession.cpp +++ b/media/server/main/source/MediaKeySession.cpp @@ -18,6 +18,7 @@ */ #include +#include #include "MediaKeySession.h" #include "MediaKeysCommon.h" @@ -41,15 +42,16 @@ std::shared_ptr IMediaKeySessionFactory::createFactory( return factory; } -std::unique_ptr MediaKeySessionFactory::createMediaKeySession( - const std::string &keySystem, int32_t keySessionId, const firebolt::rialto::wrappers::IOcdmSystem &ocdmSystem, - KeySessionType sessionType, std::weak_ptr client, bool isLDL) const +std::unique_ptr +MediaKeySessionFactory::createMediaKeySession(const std::string &keySystem, int32_t keySessionId, + const firebolt::rialto::wrappers::IOcdmSystem &ocdmSystem, + KeySessionType sessionType, std::weak_ptr client) const { std::unique_ptr mediaKeys; try { mediaKeys = std::make_unique(keySystem, keySessionId, ocdmSystem, sessionType, client, - isLDL, server::IMainThreadFactory::createFactory()); + server::IMainThreadFactory::createFactory()); } catch (const std::exception &e) { @@ -61,11 +63,11 @@ std::unique_ptr MediaKeySessionFactory::createMediaKeySession( MediaKeySession::MediaKeySession(const std::string &keySystem, int32_t keySessionId, const firebolt::rialto::wrappers::IOcdmSystem &ocdmSystem, KeySessionType sessionType, - std::weak_ptr client, bool isLDL, + std::weak_ptr client, const std::shared_ptr &mainThreadFactory) : m_kKeySystem(keySystem), m_kKeySessionId(keySessionId), m_kSessionType(sessionType), m_mediaKeysClient(client), - m_kIsLDL(isLDL), m_isSessionConstructed(false), m_isSessionClosed(false), m_licenseRequested(false), - m_ongoingOcdmOperation(false), m_ocdmError(false) + m_isSessionConstructed(false), m_isSessionClosed(false), m_licenseRequested(false), m_ongoingOcdmOperation(false), + m_ocdmError(false) { RIALTO_SERVER_LOG_DEBUG("entry:"); @@ -106,19 +108,28 @@ MediaKeySession::~MediaKeySession() m_mainThread->unregisterClient(m_mainThreadClientId); } -MediaKeyErrorStatus MediaKeySession::generateRequest(InitDataType initDataType, const std::vector &initData) +MediaKeyErrorStatus MediaKeySession::generateRequest(InitDataType initDataType, const std::vector &initData, + const LimitedDurationLicense &ldlState) { RIALTO_SERVER_LOG_DEBUG("entry:"); - // Set the request flag for the onLicenseRequest callback - m_licenseRequested = true; + if (LimitedDurationLicense::NOT_SPECIFIED != ldlState) + { + m_extendedInterfaceInUse = true; + } + else + { + // Set the request flag for the onLicenseRequest callback + m_licenseRequested = true; + } + + auto status = MediaKeyErrorStatus::OK; // Only construct session if it hasnt previously been constructed if (!m_isSessionConstructed) { initOcdmErrorChecking(); - MediaKeyErrorStatus status = - m_ocdmSession->constructSession(m_kSessionType, initDataType, &initData[0], initData.size()); + status = m_ocdmSession->constructSession(m_kSessionType, initDataType, &initData[0], initData.size()); if (MediaKeyErrorStatus::OK != status) { RIALTO_SERVER_LOG_ERROR("Failed to construct the key session"); @@ -127,11 +138,11 @@ MediaKeyErrorStatus MediaKeySession::generateRequest(InitDataType initDataType, else { m_isSessionConstructed = true; - if (isNetflixPlayreadyKeySystem()) + if (!m_queuedDrmHeader.empty()) { - // Ocdm-playready does not notify onProcessChallenge when complete. - // Fetch the challenge manually. - getChallenge(); + RIALTO_SERVER_LOG_DEBUG("Setting queued drm header after session construction"); + setDrmHeader(m_queuedDrmHeader); + m_queuedDrmHeader.clear(); } } @@ -139,27 +150,33 @@ MediaKeyErrorStatus MediaKeySession::generateRequest(InitDataType initDataType, { status = MediaKeyErrorStatus::FAIL; } + } - return status; + if (m_isSessionConstructed && m_extendedInterfaceInUse) + { + // Ocdm-playready does not notify onProcessChallenge when complete. + // Fetch the challenge manually. + getChallenge(ldlState); } - return MediaKeyErrorStatus::OK; + return status; } -void MediaKeySession::getChallenge() +void MediaKeySession::getChallenge(const LimitedDurationLicense &ldlState) { RIALTO_SERVER_LOG_DEBUG("entry:"); auto task = [&]() { + const bool kIsLdl{LimitedDurationLicense::ENABLED == ldlState}; uint32_t challengeSize = 0; - MediaKeyErrorStatus status = m_ocdmSession->getChallengeData(m_kIsLDL, nullptr, &challengeSize); + MediaKeyErrorStatus status = m_ocdmSession->getChallengeData(kIsLdl, nullptr, &challengeSize); if (challengeSize == 0) { RIALTO_SERVER_LOG_ERROR("Failed to get the challenge data size, no onLicenseRequest will be generated"); return; } std::vector challenge(challengeSize, 0x00); - status = m_ocdmSession->getChallengeData(m_kIsLDL, &challenge[0], &challengeSize); + status = m_ocdmSession->getChallengeData(kIsLdl, &challenge[0], &challengeSize); if (MediaKeyErrorStatus::OK != status) { RIALTO_SERVER_LOG_ERROR("Failed to get the challenge data, no onLicenseRequest will be generated"); @@ -167,6 +184,7 @@ void MediaKeySession::getChallenge() } std::string url; + m_licenseRequested = true; onProcessChallenge(url.c_str(), &challenge[0], challengeSize); }; m_mainThread->enqueueTask(m_mainThreadClientId, task); @@ -195,7 +213,7 @@ MediaKeyErrorStatus MediaKeySession::updateSession(const std::vector &r initOcdmErrorChecking(); MediaKeyErrorStatus status; - if (isNetflixPlayreadyKeySystem()) + if (m_extendedInterfaceInUse) { status = m_ocdmSession->storeLicenseData(&responseData[0], responseData.size()); if (MediaKeyErrorStatus::OK != status) @@ -243,7 +261,7 @@ MediaKeyErrorStatus MediaKeySession::closeKeySession() initOcdmErrorChecking(); MediaKeyErrorStatus status; - if (isNetflixPlayreadyKeySystem()) + if (m_extendedInterfaceInUse) { if (MediaKeyErrorStatus::OK != m_ocdmSession->cancelChallengeData()) { @@ -322,6 +340,14 @@ MediaKeyErrorStatus MediaKeySession::setDrmHeader(const std::vector &re { initOcdmErrorChecking(); + if (!m_isSessionConstructed) + { + RIALTO_SERVER_LOG_INFO("Session not yet constructed, queueing drm header to be set after construction"); + m_extendedInterfaceInUse = true; + m_queuedDrmHeader = requestData; + return MediaKeyErrorStatus::OK; + } + MediaKeyErrorStatus status = m_ocdmSession->setDrmHeader(requestData.data(), requestData.size()); if (MediaKeyErrorStatus::OK != status) { @@ -363,6 +389,7 @@ MediaKeyErrorStatus MediaKeySession::selectKeyId(const std::vector &key initOcdmErrorChecking(); + m_extendedInterfaceInUse = true; MediaKeyErrorStatus status = m_ocdmSession->selectKeyId(keyId.size(), keyId.data()); if (MediaKeyErrorStatus::OK == status) { @@ -378,16 +405,11 @@ MediaKeyErrorStatus MediaKeySession::selectKeyId(const std::vector &key return status; } -bool MediaKeySession::isNetflixPlayreadyKeySystem() const -{ - return m_kKeySystem.find("netflix") != std::string::npos; -} - void MediaKeySession::onProcessChallenge(const char url[], const uint8_t challenge[], const uint16_t challengeLength) { std::string urlStr = url; std::vector challengeVec = std::vector{challenge, challenge + challengeLength}; - auto task = [&, urlStr, challengeVec]() + auto task = [&, urlStr = std::move(urlStr), challengeVec = std::move(challengeVec)]() { std::shared_ptr client = m_mediaKeysClient.lock(); if (client) @@ -409,7 +431,7 @@ void MediaKeySession::onProcessChallenge(const char url[], const uint8_t challen void MediaKeySession::onKeyUpdated(const uint8_t keyId[], const uint8_t keyIdLength) { std::vector keyIdVec = std::vector{keyId, keyId + keyIdLength}; - auto task = [&, keyIdVec]() + auto task = [&, keyIdVec = std::move(keyIdVec)]() { std::shared_ptr client = m_mediaKeysClient.lock(); if (client) diff --git a/media/server/main/source/MediaKeysCapabilities.cpp b/media/server/main/source/MediaKeysCapabilities.cpp index f0ad87a81..56123ce91 100644 --- a/media/server/main/source/MediaKeysCapabilities.cpp +++ b/media/server/main/source/MediaKeysCapabilities.cpp @@ -47,6 +47,8 @@ const char *toString(const firebolt::rialto::MediaKeyErrorStatus &status) return "NOT_SUPPORTED"; case firebolt::rialto::MediaKeyErrorStatus::INVALID_STATE: return "INVALID_STATE"; + case firebolt::rialto::MediaKeyErrorStatus::OUTPUT_RESTRICTED: + return "OUTPUT_RESTRICTED"; } return "Unknown"; } @@ -91,8 +93,9 @@ std::shared_ptr MediaKeysCapabilitiesFactory::getMediaKe namespace firebolt::rialto::server { -MediaKeysCapabilities::MediaKeysCapabilities(std::shared_ptr ocdmFactory, - std::shared_ptr ocdmSystemFactory) +MediaKeysCapabilities::MediaKeysCapabilities( + const std::shared_ptr &ocdmFactory, + const std::shared_ptr &ocdmSystemFactory) : m_ocdmSystemFactory{ocdmSystemFactory} { RIALTO_SERVER_LOG_DEBUG("entry:"); diff --git a/media/server/main/source/MediaKeysServerInternal.cpp b/media/server/main/source/MediaKeysServerInternal.cpp index 7fd1b58bf..8f6ca5a2f 100644 --- a/media/server/main/source/MediaKeysServerInternal.cpp +++ b/media/server/main/source/MediaKeysServerInternal.cpp @@ -18,10 +18,40 @@ */ #include +#include +#include #include "MediaKeysServerInternal.h" #include "RialtoServerLogging.h" +/*namespace +{ +const char *toString(const firebolt::rialto::MediaKeyErrorStatus &status) +{ + switch (status) + { + case firebolt::rialto::MediaKeyErrorStatus::OK: + return "OK"; + case firebolt::rialto::MediaKeyErrorStatus::FAIL: + return "FAIL"; + case firebolt::rialto::MediaKeyErrorStatus::BAD_SESSION_ID: + return "BAD_SESSION_ID"; + case firebolt::rialto::MediaKeyErrorStatus::INTERFACE_NOT_IMPLEMENTED: + return "INTERFACE_NOT_IMPLEMENTED"; + case firebolt::rialto::MediaKeyErrorStatus::BUFFER_TOO_SMALL: + return "BUFFER_TOO_SMALL"; + case firebolt::rialto::MediaKeyErrorStatus::NOT_SUPPORTED: + return "NOT_SUPPORTED"; + case firebolt::rialto::MediaKeyErrorStatus::INVALID_STATE: + return "INVALID_STATE"; + case firebolt::rialto::MediaKeyErrorStatus::OUTPUT_RESTRICTED: + return "OUTPUT_RESTRICTED"; + } + return "Unknown"; +} +} */// namespace + + namespace firebolt::rialto { const char *mediaKeyErrorStatusToString(const MediaKeyErrorStatus &status) @@ -51,6 +81,9 @@ std::shared_ptr IMediaKeysFactory::createFactory() namespace firebolt::rialto::server { +constexpr std::chrono::milliseconds kOutputRestrictedRetryInterval{250}; +constexpr std::chrono::seconds kOutputRestrictedRetryTimeout{6}; + int32_t generateSessionId() { static int32_t keySessionId{0}; @@ -103,8 +136,8 @@ namespace firebolt::rialto::server { MediaKeysServerInternal::MediaKeysServerInternal( const std::string &keySystem, const std::shared_ptr &mainThreadFactory, - std::shared_ptr ocdmSystemFactory, - std::shared_ptr mediaKeySessionFactory) + const std::shared_ptr &ocdmSystemFactory, + const std::shared_ptr &mediaKeySessionFactory) : m_mediaKeySessionFactory(mediaKeySessionFactory), m_kKeySystem(keySystem) { RIALTO_SERVER_LOG_DEBUG("entry:"); @@ -210,13 +243,13 @@ bool MediaKeysServerInternal::containsKeyInternal(int32_t keySessionId, const st } MediaKeyErrorStatus MediaKeysServerInternal::createKeySession(KeySessionType sessionType, - std::weak_ptr client, bool isLDL, + std::weak_ptr client, int32_t &keySessionId) { RIALTO_SERVER_LOG_DEBUG("entry:"); MediaKeyErrorStatus status; - auto task = [&]() { status = createKeySessionInternal(sessionType, client, isLDL, keySessionId); }; + auto task = [&]() { status = createKeySessionInternal(sessionType, client, keySessionId); }; m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); return status; @@ -224,12 +257,12 @@ MediaKeyErrorStatus MediaKeysServerInternal::createKeySession(KeySessionType ses MediaKeyErrorStatus MediaKeysServerInternal::createKeySessionInternal(KeySessionType sessionType, std::weak_ptr client, - bool isLDL, int32_t &keySessionId) + int32_t &keySessionId) { int32_t keySessionIdTemp = generateSessionId(); std::unique_ptr mediaKeySession = m_mediaKeySessionFactory->createMediaKeySession(m_kKeySystem, keySessionIdTemp, *m_ocdmSystem, sessionType, - client, isLDL); + client); if (!mediaKeySession) { RIALTO_SERVER_LOG_ERROR("Failed to create a new media key session"); @@ -242,19 +275,21 @@ MediaKeyErrorStatus MediaKeysServerInternal::createKeySessionInternal(KeySession } MediaKeyErrorStatus MediaKeysServerInternal::generateRequest(int32_t keySessionId, InitDataType initDataType, - const std::vector &initData) + const std::vector &initData, + const LimitedDurationLicense &ldlState) { RIALTO_SERVER_LOG_DEBUG("entry:"); MediaKeyErrorStatus status; - auto task = [&]() { status = generateRequestInternal(keySessionId, initDataType, initData); }; + auto task = [&]() { status = generateRequestInternal(keySessionId, initDataType, initData, ldlState); }; m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); return status; } MediaKeyErrorStatus MediaKeysServerInternal::generateRequestInternal(int32_t keySessionId, InitDataType initDataType, - const std::vector &initData) + const std::vector &initData, + const LimitedDurationLicense &ldlState) { auto sessionIter = m_mediaKeySessions.find(keySessionId); if (sessionIter == m_mediaKeySessions.end()) @@ -263,7 +298,7 @@ MediaKeyErrorStatus MediaKeysServerInternal::generateRequestInternal(int32_t key return MediaKeyErrorStatus::BAD_SESSION_ID; } - MediaKeyErrorStatus status = sessionIter->second->generateRequest(initDataType, initData); + MediaKeyErrorStatus status = sessionIter->second->generateRequest(initDataType, initData, ldlState); if (MediaKeyErrorStatus::OK != status) { RIALTO_SERVER_LOG_ERROR("Failed to generate request for the key session %d", keySessionId); @@ -569,12 +604,51 @@ MediaKeyErrorStatus MediaKeysServerInternal::getCdmKeySessionIdInternal(int32_t MediaKeyErrorStatus MediaKeysServerInternal::decrypt(int32_t keySessionId, GstBuffer *encrypted, GstCaps *caps) { - RIALTO_SERVER_LOG_DEBUG("entry:"); + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE: entry:decrypt"); - MediaKeyErrorStatus status; - auto task = [&]() { status = decryptInternal(keySessionId, encrypted, caps); }; + MediaKeyErrorStatus status{MediaKeyErrorStatus::FAIL}; + const auto deadline = std::chrono::steady_clock::now() + kOutputRestrictedRetryTimeout; + do + { + auto task = [&]() { status = decryptInternal(keySessionId, encrypted, caps); }; + m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE : Key session id :%d", keySessionId); + switch (status) + { + case firebolt::rialto::MediaKeyErrorStatus::OK: + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE : Key session status : OK"); + break; + case firebolt::rialto::MediaKeyErrorStatus::FAIL: + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE : Key session status : FAIL"); + break; + case firebolt::rialto::MediaKeyErrorStatus::BAD_SESSION_ID: + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE : Key session status : BAD_SESSION_ID"); + break; + case firebolt::rialto::MediaKeyErrorStatus::INTERFACE_NOT_IMPLEMENTED: + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE : Key session status : INTERFACE_NOT_IMPLEMENTED"); + break; + case firebolt::rialto::MediaKeyErrorStatus::BUFFER_TOO_SMALL: + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE : Key session status : BUFFER_TOO_SMALL"); + break; + case firebolt::rialto::MediaKeyErrorStatus::NOT_SUPPORTED: + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE : Key session status : NOT_SUPPORTED"); + break; + case firebolt::rialto::MediaKeyErrorStatus::INVALID_STATE: + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE : Key session status : INVALID_STATE"); + break; + case firebolt::rialto::MediaKeyErrorStatus::OUTPUT_RESTRICTED: + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE : Key session status : OUTPUT_RESTRICTED"); + break; + } + + if (status != MediaKeyErrorStatus::OUTPUT_RESTRICTED) + { + break; + } + RIALTO_SERVER_LOG_WARN("Decrypt returned OUTPUT_RESTRICTED, retrying after delay"); + std::this_thread::sleep_for(kOutputRestrictedRetryInterval); + } while (std::chrono::steady_clock::now() < deadline); - m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); return status; } @@ -598,12 +672,6 @@ MediaKeyErrorStatus MediaKeysServerInternal::decryptInternal(int32_t keySessionI return status; } -bool MediaKeysServerInternal::isNetflixPlayreadyKeySystem() const -{ - RIALTO_SERVER_LOG_DEBUG("entry:"); - return m_kKeySystem.find("netflix") != std::string::npos; -} - void MediaKeysServerInternal::ping(std::unique_ptr &&heartbeatHandler) { RIALTO_SERVER_LOG_DEBUG("entry:"); diff --git a/media/server/main/source/MediaPipelineCapabilities.cpp b/media/server/main/source/MediaPipelineCapabilities.cpp index 67cd7c19a..ffd5ec585 100644 --- a/media/server/main/source/MediaPipelineCapabilities.cpp +++ b/media/server/main/source/MediaPipelineCapabilities.cpp @@ -61,7 +61,7 @@ std::unique_ptr MediaPipelineCapabilitiesFactory::cr namespace firebolt::rialto::server { -MediaPipelineCapabilities::MediaPipelineCapabilities(std::shared_ptr gstCapabilitiesFactory) +MediaPipelineCapabilities::MediaPipelineCapabilities(const std::shared_ptr &gstCapabilitiesFactory) : m_kGstCapabilitiesFactory{gstCapabilitiesFactory} { RIALTO_SERVER_LOG_DEBUG("entry:"); diff --git a/media/server/main/source/MediaPipelineServerInternal.cpp b/media/server/main/source/MediaPipelineServerInternal.cpp index f76396bec..6476c9504 100644 --- a/media/server/main/source/MediaPipelineServerInternal.cpp +++ b/media/server/main/source/MediaPipelineServerInternal.cpp @@ -129,10 +129,10 @@ std::unique_ptr MediaPipelineServerInterna } MediaPipelineServerInternal::MediaPipelineServerInternal( - std::shared_ptr client, const VideoRequirements &videoRequirements, + const std::shared_ptr &client, const VideoRequirements &videoRequirements, const std::shared_ptr &gstPlayerFactory, int sessionId, const std::shared_ptr &shmBuffer, const std::shared_ptr &mainThreadFactory, - std::shared_ptr timerFactory, std::unique_ptr &&dataReaderFactory, + const std::shared_ptr &timerFactory, std::unique_ptr &&dataReaderFactory, std::unique_ptr &&activeRequests, IDecryptionService &decryptionService) : m_mediaPipelineClient(client), m_kGstPlayerFactory(gstPlayerFactory), m_kVideoRequirements(videoRequirements), m_sessionId{sessionId}, m_shmBuffer{shmBuffer}, m_dataReaderFactory{std::move(dataReaderFactory)}, @@ -192,18 +192,19 @@ MediaPipelineServerInternal::~MediaPipelineServerInternal() m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); } -bool MediaPipelineServerInternal::load(MediaType type, const std::string &mimeType, const std::string &url) +bool MediaPipelineServerInternal::load(MediaType type, const std::string &mimeType, const std::string &url, bool isLive) { RIALTO_SERVER_LOG_DEBUG("entry:"); bool result; - auto task = [&]() { result = loadInternal(type, mimeType, url); }; + auto task = [&]() { result = loadInternal(type, mimeType, url, isLive); }; m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); return result; } -bool MediaPipelineServerInternal::loadInternal(MediaType type, const std::string &mimeType, const std::string &url) +bool MediaPipelineServerInternal::loadInternal(MediaType type, const std::string &mimeType, const std::string &url, + bool isLive) { std::unique_lock lock{m_getPropertyMutex}; /* If gstreamer player already created, destroy the old one first */ @@ -214,7 +215,7 @@ bool MediaPipelineServerInternal::loadInternal(MediaType type, const std::string m_gstPlayer = m_kGstPlayerFactory - ->createGstGenericPlayer(this, m_decryptionService, type, m_kVideoRequirements, + ->createGstGenericPlayer(this, m_decryptionService, type, m_kVideoRequirements, isLive, firebolt::rialto::wrappers::IRdkGstreamerUtilsWrapperFactory::getFactory()); if (!m_gstPlayer) { @@ -299,8 +300,12 @@ bool MediaPipelineServerInternal::removeSourceInternal(int32_t id) return false; } - m_gstPlayer->removeSource(sourceIter->first); - m_needMediaDataTimers.erase(sourceIter->first); + MediaSourceType type = sourceIter->first; + + m_needMediaDataTimers.erase(type); + m_noAvailableSamplesCounter.erase(type); + m_isMediaTypeEosMap.erase(type); + m_attachedSources.erase(sourceIter); return true; } @@ -335,18 +340,18 @@ bool MediaPipelineServerInternal::allSourcesAttachedInternal() return true; } -bool MediaPipelineServerInternal::play() +bool MediaPipelineServerInternal::play(bool &async) { RIALTO_SERVER_LOG_DEBUG("entry:"); bool result; - auto task = [&]() { result = playInternal(); }; + auto task = [&]() { result = playInternal(async); }; m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); return result; } -bool MediaPipelineServerInternal::playInternal() +bool MediaPipelineServerInternal::playInternal(bool &async) { if (!m_gstPlayer) { @@ -354,7 +359,7 @@ bool MediaPipelineServerInternal::playInternal() return false; } - m_gstPlayer->play(); + m_gstPlayer->play(async); return true; } @@ -477,6 +482,20 @@ bool MediaPipelineServerInternal::getPosition(int64_t &position) return m_gstPlayer->getPosition(position); } +bool MediaPipelineServerInternal::getDuration(int64_t &duration) +{ + RIALTO_SERVER_LOG_DEBUG("entry:"); + + std::shared_lock lock{m_getPropertyMutex}; + + if (!m_gstPlayer) + { + RIALTO_SERVER_LOG_ERROR("Failed to get duration - Gstreamer player has not been loaded"); + return false; + } + return m_gstPlayer->getDuration(duration); +} + bool MediaPipelineServerInternal::getStats(int32_t sourceId, uint64_t &renderedFrames, uint64_t &droppedFrames) { RIALTO_SERVER_LOG_DEBUG("entry:"); @@ -1152,6 +1171,8 @@ bool MediaPipelineServerInternal::flushInternal(int32_t sourceId, bool resetTime m_gstPlayer->flush(sourceIter->first, resetTime, async); + m_needMediaDataTimers.erase(sourceIter->first); + // Reset Eos on flush auto it = m_isMediaTypeEosMap.find(sourceIter->first); if (it != m_isMediaTypeEosMap.end() && it->second) diff --git a/media/server/main/source/NeedMediaData.cpp b/media/server/main/source/NeedMediaData.cpp index 3d24cdc4e..9437b9267 100644 --- a/media/server/main/source/NeedMediaData.cpp +++ b/media/server/main/source/NeedMediaData.cpp @@ -33,6 +33,12 @@ NeedMediaData::NeedMediaData(std::weak_ptr client, IActive : m_client{client}, m_activeRequests{activeRequests}, m_mediaSourceType{mediaSourceType}, m_frameCount{kMaxFrames}, m_sourceId{sourceId} { + if (PlaybackState::PLAYING != currentPlaybackState) + { + RIALTO_SERVER_LOG_DEBUG("Pipeline in prerolling state. Sending smaller frame count for %s", + common::convertMediaSourceType(m_mediaSourceType)); + m_frameCount = kPrerollNumFrames; + } if (MediaSourceType::AUDIO != mediaSourceType && MediaSourceType::VIDEO != mediaSourceType && MediaSourceType::SUBTITLE != mediaSourceType) { diff --git a/media/server/service/include/ICdmService.h b/media/server/service/include/ICdmService.h index fdcbc95e5..d33d7b17b 100644 --- a/media/server/service/include/ICdmService.h +++ b/media/server/service/include/ICdmService.h @@ -48,10 +48,11 @@ class ICdmService virtual bool createMediaKeys(int mediaKeysHandle, std::string keySystem) = 0; virtual bool destroyMediaKeys(int mediaKeysHandle) = 0; virtual MediaKeyErrorStatus createKeySession(int mediaKeysHandle, KeySessionType sessionType, - const std::shared_ptr &client, bool isLDL, + const std::shared_ptr &client, int32_t &keySessionId) = 0; virtual MediaKeyErrorStatus generateRequest(int mediaKeysHandle, int32_t keySessionId, InitDataType initDataType, - const std::vector &initData) = 0; + const std::vector &initData, + const LimitedDurationLicense &ldlState) = 0; virtual MediaKeyErrorStatus loadSession(int mediaKeysHandle, int32_t keySessionId) = 0; virtual MediaKeyErrorStatus updateSession(int mediaKeysHandle, int32_t keySessionId, const std::vector &responseData) = 0; diff --git a/media/server/service/include/IMediaPipelineService.h b/media/server/service/include/IMediaPipelineService.h index 7ba9e8a1c..77a27d34a 100644 --- a/media/server/service/include/IMediaPipelineService.h +++ b/media/server/service/include/IMediaPipelineService.h @@ -44,11 +44,11 @@ class IMediaPipelineService virtual bool createSession(int sessionId, const std::shared_ptr &mediaPipelineClient, std::uint32_t maxWidth, std::uint32_t maxHeight) = 0; virtual bool destroySession(int sessionId) = 0; - virtual bool load(int sessionId, MediaType type, const std::string &mimeType, const std::string &url) = 0; + virtual bool load(int sessionId, MediaType type, const std::string &mimeType, const std::string &url, bool isLive) = 0; virtual bool attachSource(int sessionId, const std::unique_ptr &source) = 0; virtual bool removeSource(int sessionId, std::int32_t sourceId) = 0; virtual bool allSourcesAttached(int sessionId) = 0; - virtual bool play(int sessionId) = 0; + virtual bool play(int sessionId, bool &async) = 0; virtual bool pause(int sessionId) = 0; virtual bool stop(int sessionId) = 0; virtual bool setPlaybackRate(int sessionId, double rate) = 0; @@ -91,6 +91,7 @@ class IMediaPipelineService virtual void ping(const std::shared_ptr &heartbeatProcedure) = 0; virtual bool switchSource(int sessionId, const std::unique_ptr &source) = 0; virtual bool isVideoMaster(bool &isVideoMaster) = 0; + virtual bool getDuration(int sessionId, std::int64_t &duration) = 0; }; } // namespace firebolt::rialto::server::service diff --git a/media/server/service/source/CdmService.cpp b/media/server/service/source/CdmService.cpp index 8ea716f61..01dbad6d4 100644 --- a/media/server/service/source/CdmService.cpp +++ b/media/server/service/source/CdmService.cpp @@ -31,8 +31,8 @@ namespace firebolt::rialto::server::service { CdmService::CdmService(std::shared_ptr &&mediaKeysFactory, std::shared_ptr &&mediaKeysCapabilitiesFactory) - : m_mediaKeysFactory{mediaKeysFactory}, m_mediaKeysCapabilitiesFactory{mediaKeysCapabilitiesFactory}, - m_isActive{false} + : m_mediaKeysFactory{std::move(mediaKeysFactory)}, + m_mediaKeysCapabilitiesFactory{std::move(mediaKeysCapabilitiesFactory)}, m_isActive{false} { RIALTO_SERVER_LOG_DEBUG("CdmService is constructed"); } @@ -121,8 +121,7 @@ bool CdmService::destroyMediaKeys(int mediaKeysHandle) } MediaKeyErrorStatus CdmService::createKeySession(int mediaKeysHandle, KeySessionType sessionType, - const std::shared_ptr &client, bool isLDL, - int32_t &keySessionId) + const std::shared_ptr &client, int32_t &keySessionId) { RIALTO_SERVER_LOG_DEBUG("CdmService requested to create key session: %d", mediaKeysHandle); @@ -134,7 +133,7 @@ MediaKeyErrorStatus CdmService::createKeySession(int mediaKeysHandle, KeySession return MediaKeyErrorStatus::FAIL; } - MediaKeyErrorStatus status = mediaKeysIter->second->createKeySession(sessionType, client, isLDL, keySessionId); + MediaKeyErrorStatus status = mediaKeysIter->second->createKeySession(sessionType, client, keySessionId); if (MediaKeyErrorStatus::OK == status) { if (m_mediaKeysClients.find(keySessionId) != m_mediaKeysClients.end()) @@ -143,9 +142,7 @@ MediaKeyErrorStatus CdmService::createKeySession(int mediaKeysHandle, KeySession static_cast(removeKeySessionInternal(mediaKeysHandle, keySessionId)); return MediaKeyErrorStatus::FAIL; } - m_sessionInfo.emplace( - std::make_pair(keySessionId, - MediaKeySessionInfo{mediaKeysHandle, mediaKeysIter->second->isNetflixPlayreadyKeySystem()})); + m_sessionInfo.emplace(std::make_pair(keySessionId, MediaKeySessionInfo{mediaKeysHandle})); m_mediaKeysClients.emplace(std::make_pair(keySessionId, client)); } @@ -153,7 +150,8 @@ MediaKeyErrorStatus CdmService::createKeySession(int mediaKeysHandle, KeySession } MediaKeyErrorStatus CdmService::generateRequest(int mediaKeysHandle, int32_t keySessionId, InitDataType initDataType, - const std::vector &initData) + const std::vector &initData, + const LimitedDurationLicense &ldlState) { RIALTO_SERVER_LOG_DEBUG("CdmService requested to generate request: %d", mediaKeysHandle); @@ -164,7 +162,11 @@ MediaKeyErrorStatus CdmService::generateRequest(int mediaKeysHandle, int32_t key RIALTO_SERVER_LOG_ERROR("Media keys handle: %d does not exists", mediaKeysHandle); return MediaKeyErrorStatus::FAIL; } - return mediaKeysIter->second->generateRequest(keySessionId, initDataType, initData); + if (LimitedDurationLicense::NOT_SPECIFIED != ldlState && m_sessionInfo.find(keySessionId) != m_sessionInfo.end()) + { + m_sessionInfo[keySessionId].isExtendedInterfaceUsed = true; + } + return mediaKeysIter->second->generateRequest(keySessionId, initDataType, initData, ldlState); } MediaKeyErrorStatus CdmService::loadSession(int mediaKeysHandle, int32_t keySessionId) @@ -289,7 +291,10 @@ MediaKeyErrorStatus CdmService::setDrmHeader(int mediaKeysHandle, int32_t keySes RIALTO_SERVER_LOG_ERROR("Media keys handle: %d does not exists", mediaKeysHandle); return MediaKeyErrorStatus::FAIL; } - + if (m_sessionInfo.find(keySessionId) != m_sessionInfo.end()) + { + m_sessionInfo[keySessionId].isExtendedInterfaceUsed = true; + } return mediaKeysIter->second->setDrmHeader(keySessionId, requestData); } @@ -504,9 +509,9 @@ MediaKeyErrorStatus CdmService::decrypt(int32_t keySessionId, GstBuffer *encrypt return m_mediaKeys[mediaKeysHandleIter->second.mediaKeysHandle]->decrypt(keySessionId, encrypted, caps); } -bool CdmService::isNetflixPlayreadyKeySystem(int32_t keySessionId) +bool CdmService::isExtendedInterfaceUsed(int32_t keySessionId) { - RIALTO_SERVER_LOG_DEBUG("CdmService requested to check if key system is Netflix Playready, key session id: %d", + RIALTO_SERVER_LOG_DEBUG("CdmService requested to check if extended interface is used, key session id: %d", keySessionId); std::lock_guard lock{m_mediaKeysMutex}; @@ -516,7 +521,7 @@ bool CdmService::isNetflixPlayreadyKeySystem(int32_t keySessionId) RIALTO_SERVER_LOG_ERROR("Media keys handle for mksId: %d does not exists", keySessionId); return false; } - return mediaKeysHandleIter->second.isNetflixPlayready; + return mediaKeysHandleIter->second.isExtendedInterfaceUsed; } MediaKeyErrorStatus CdmService::selectKeyId(int32_t keySessionId, const std::vector &keyId) @@ -530,6 +535,10 @@ MediaKeyErrorStatus CdmService::selectKeyId(int32_t keySessionId, const std::vec RIALTO_SERVER_LOG_ERROR("Media keys handle for mksId: %d does not exists", keySessionId); return MediaKeyErrorStatus::FAIL; } + if (m_sessionInfo.find(keySessionId) != m_sessionInfo.end()) + { + m_sessionInfo[keySessionId].isExtendedInterfaceUsed = true; + } return m_mediaKeys[mediaKeysHandleIter->second.mediaKeysHandle]->selectKeyId(keySessionId, keyId); } diff --git a/media/server/service/source/CdmService.h b/media/server/service/source/CdmService.h index 468f6a706..b97639685 100644 --- a/media/server/service/source/CdmService.h +++ b/media/server/service/source/CdmService.h @@ -37,7 +37,7 @@ class CdmService : public ICdmService, public IDecryptionService struct MediaKeySessionInfo { int mediaKeysHandle; - bool isNetflixPlayready{false}; + bool isExtendedInterfaceUsed{false}; uint32_t refCounter{0}; bool shouldBeClosed{false}; bool shouldBeReleased{false}; @@ -54,10 +54,10 @@ class CdmService : public ICdmService, public IDecryptionService bool createMediaKeys(int mediaKeysHandle, std::string keySystem) override; bool destroyMediaKeys(int mediaKeysHandle) override; MediaKeyErrorStatus createKeySession(int mediaKeysHandle, KeySessionType sessionType, - const std::shared_ptr &client, bool isLDL, - int32_t &keySessionId) override; + const std::shared_ptr &client, int32_t &keySessionId) override; MediaKeyErrorStatus generateRequest(int mediaKeysHandle, int32_t keySessionId, InitDataType initDataType, - const std::vector &initData) override; + const std::vector &initData, + const LimitedDurationLicense &ldlState) override; MediaKeyErrorStatus loadSession(int mediaKeysHandle, int32_t keySessionId) override; MediaKeyErrorStatus updateSession(int mediaKeysHandle, int32_t keySessionId, const std::vector &responseData) override; @@ -83,7 +83,7 @@ class CdmService : public ICdmService, public IDecryptionService bool getSupportedKeySystemVersion(const std::string &keySystem, std::string &version) override; bool isServerCertificateSupported(const std::string &keySystem) override; MediaKeyErrorStatus decrypt(int32_t keySessionId, GstBuffer *encrypted, GstCaps *caps) override; - bool isNetflixPlayreadyKeySystem(int32_t keySessionId) override; + bool isExtendedInterfaceUsed(int32_t keySessionId) override; MediaKeyErrorStatus selectKeyId(int32_t keySessionId, const std::vector &keyId) override; void incrementSessionIdUsageCounter(int32_t keySessionId) override; void decrementSessionIdUsageCounter(int32_t keySessionId) override; diff --git a/media/server/service/source/MediaPipelineService.cpp b/media/server/service/source/MediaPipelineService.cpp index 5e7c40063..f3d8136ab 100644 --- a/media/server/service/source/MediaPipelineService.cpp +++ b/media/server/service/source/MediaPipelineService.cpp @@ -32,7 +32,7 @@ MediaPipelineService::MediaPipelineService( IPlaybackService &playbackService, std::shared_ptr &&mediaPipelineFactory, std::shared_ptr &&mediaPipelineCapabilitiesFactory, IDecryptionService &decryptionService) - : m_playbackService{playbackService}, m_mediaPipelineFactory{mediaPipelineFactory}, + : m_playbackService{playbackService}, m_mediaPipelineFactory{std::move(mediaPipelineFactory)}, m_mediaPipelineCapabilities{mediaPipelineCapabilitiesFactory->createMediaPipelineCapabilities()}, m_decryptionService{decryptionService} { @@ -113,7 +113,8 @@ bool MediaPipelineService::destroySession(int sessionId) return true; } -bool MediaPipelineService::load(int sessionId, MediaType type, const std::string &mimeType, const std::string &url) +bool MediaPipelineService::load(int sessionId, MediaType type, const std::string &mimeType, const std::string &url, + bool isLive) { RIALTO_SERVER_LOG_INFO("MediaPipelineService requested to load session with id: %d", sessionId); @@ -124,7 +125,7 @@ bool MediaPipelineService::load(int sessionId, MediaType type, const std::string RIALTO_SERVER_LOG_ERROR("Session with id: %d does not exists", sessionId); return false; } - return mediaPipelineIter->second->load(type, mimeType, url); + return mediaPipelineIter->second->load(type, mimeType, url, isLive); } bool MediaPipelineService::attachSource(int sessionId, const std::unique_ptr &source) @@ -169,7 +170,7 @@ bool MediaPipelineService::allSourcesAttached(int sessionId) return mediaPipelineIter->second->allSourcesAttached(); } -bool MediaPipelineService::play(int sessionId) +bool MediaPipelineService::play(int sessionId, bool &async) { RIALTO_SERVER_LOG_INFO("MediaPipelineService requested to play, session id: %d", sessionId); @@ -180,7 +181,7 @@ bool MediaPipelineService::play(int sessionId) RIALTO_SERVER_LOG_ERROR("Session with id: %d does not exists", sessionId); return false; } - return mediaPipelineIter->second->play(); + return mediaPipelineIter->second->play(async); } bool MediaPipelineService::pause(int sessionId) @@ -253,6 +254,20 @@ bool MediaPipelineService::getPosition(int sessionId, std::int64_t &position) return mediaPipelineIter->second->getPosition(position); } +bool MediaPipelineService::getDuration(int sessionId, std::int64_t &duration) +{ + RIALTO_SERVER_LOG_INFO("MediaPipelineService requested to get duration, session id: %d", sessionId); + + std::lock_guard lock{m_mediaPipelineMutex}; + auto mediaPipelineIter = m_mediaPipelines.find(sessionId); + if (mediaPipelineIter == m_mediaPipelines.end()) + { + RIALTO_SERVER_LOG_ERROR("Session with id: %d does not exist", sessionId); + return false; + } + return mediaPipelineIter->second->getDuration(duration); +} + bool MediaPipelineService::setImmediateOutput(int sessionId, int32_t sourceId, bool immediateOutput) { RIALTO_SERVER_LOG_INFO("MediaPipelineService requested to setImmediateOutput, session id: %d", sessionId); diff --git a/media/server/service/source/MediaPipelineService.h b/media/server/service/source/MediaPipelineService.h index d68edadff..06eaf320f 100644 --- a/media/server/service/source/MediaPipelineService.h +++ b/media/server/service/source/MediaPipelineService.h @@ -55,16 +55,17 @@ class MediaPipelineService : public IMediaPipelineService bool createSession(int sessionId, const std::shared_ptr &mediaPipelineClient, std::uint32_t maxWidth, std::uint32_t maxHeight) override; bool destroySession(int sessionId) override; - bool load(int sessionId, MediaType type, const std::string &mimeType, const std::string &url) override; + bool load(int sessionId, MediaType type, const std::string &mimeType, const std::string &url, bool isLive) override; bool attachSource(int sessionId, const std::unique_ptr &source) override; bool removeSource(int sessionId, std::int32_t sourceId) override; bool allSourcesAttached(int sessionId) override; - bool play(int sessionId) override; + bool play(int sessionId, bool &async) override; bool pause(int sessionId) override; bool stop(int sessionId) override; bool setPlaybackRate(int sessionId, double rate) override; bool setPosition(int sessionId, std::int64_t position) override; bool getPosition(int sessionId, std::int64_t &position) override; + bool getDuration(int sessionId, std::int64_t &duration) override; bool setImmediateOutput(int sessionId, int32_t sourceId, bool immediateOutput) override; bool getImmediateOutput(int sessionId, int32_t sourceId, bool &immediateOutput) override; bool getStats(int sessionId, int32_t sourceId, uint64_t &renderedFrames, uint64_t &droppedFrames) override; diff --git a/media/server/service/source/WebAudioPlayerService.cpp b/media/server/service/source/WebAudioPlayerService.cpp index e2182c201..b3831e63e 100644 --- a/media/server/service/source/WebAudioPlayerService.cpp +++ b/media/server/service/source/WebAudioPlayerService.cpp @@ -34,7 +34,7 @@ namespace firebolt::rialto::server::service { WebAudioPlayerService::WebAudioPlayerService(IPlaybackService &playbackService, std::shared_ptr &&webAudioPlayerFactory) - : m_playbackService{playbackService}, m_webAudioPlayerFactory{webAudioPlayerFactory} + : m_playbackService{playbackService}, m_webAudioPlayerFactory{std::move(webAudioPlayerFactory)} { RIALTO_SERVER_LOG_DEBUG("WebAudioPlayerService is constructed"); } diff --git a/proto/mediakeysmodule.proto b/proto/mediakeysmodule.proto index 0fa4905ad..204b85f48 100644 --- a/proto/mediakeysmodule.proto +++ b/proto/mediakeysmodule.proto @@ -203,6 +203,8 @@ message CreateKeySessionResponse { * @param[in] key_session_id The key session id for the session. * @param[in] init_data_type The init data type. * @param[in] init_data The init data. + * @param[in] ldlState The Limited Duration License state. Most of key systems do not need this parameter, + * so the default value is NOT_SPECIFIED. * * @retval an error status. */ @@ -215,11 +217,17 @@ message GenerateRequestRequest { DRMHEADER = 4; ///< The init data is in DrmHeader format. }; + enum LimitedDurationLicense { + NOT_SPECIFIED = 0; ///< The license duration is not specified + ENABLED = 1; ///< The license has a limited duration + DISABLED = 2; ///< The license does not have a limited duration + }; + optional int32 media_keys_handle = 1 [default = -1]; optional int32 key_session_id = 2 [default = -1]; optional InitDataType init_data_type = 3; repeated uint32 init_data = 4; - + optional LimitedDurationLicense ldl_state = 5 [default = NOT_SPECIFIED]; } message GenerateRequestResponse { optional ProtoMediaKeyErrorStatus error_status = 1 [default = FAIL]; diff --git a/proto/mediapipelinemodule.proto b/proto/mediapipelinemodule.proto index f56a19970..ed8bbab63 100644 --- a/proto/mediapipelinemodule.proto +++ b/proto/mediapipelinemodule.proto @@ -67,13 +67,14 @@ message DestroySessionResponse { } /** - * @fn void load(MediaType type, string mime_type, string url) + * @fn void load(MediaType type, string mime_type, string url, bool isLive) * @brief Loads the media pipeline. * * @param[in] session_id The id of the A/V session. * @param[in] type The type of media. * @param[in] mime_type The mime type. * @param[in] url The url. + * @param[in] isLive Indicates if the media is live. * */ message LoadRequest { @@ -86,6 +87,7 @@ message LoadRequest { optional MediaType type = 2; optional string mime_type = 3; optional string url = 4; + optional bool is_live = 5; } message LoadResponse { } @@ -261,7 +263,8 @@ message SetVideoWindowResponse { * @fn void play(int session_id) * @brief Starts playback on a session. * - * @param[in] session_id The id of the A/V session. + * @param[in] session_id The id of the A/V session. + * @param[out] async True if play method call is asynchronous * * This method is asynchronous. Once the backend is successfully playing it will notify the media player client of * playback state. @@ -271,6 +274,7 @@ message PlayRequest { optional int32 session_id = 1 [default = -1]; } message PlayResponse { + optional bool async = 1 [default = true]; } /** @@ -343,6 +347,22 @@ message GetPositionResponse { optional int64 position = 1 [default = -1]; } +/** + * @fn void getDuration(int session_id) + * @brief Get the playback Duration in nanoseconds. + * + * @param[in] session_id The id of the A/V session. + * + * This method is considered to be synchronous, it returns current playback duration + * + */ +message GetDurationRequest { + optional int32 session_id = 1 [default = -1]; +} +message GetDurationResponse { + optional int64 duration = 1 [default = -1]; +} + /** * @fn void setPlaybackRate(int session_id, double rate) * @brief Set the playback position in nanoseconds. @@ -1251,4 +1271,12 @@ service MediaPipelineModule { */ rpc getUseBuffering(GetUseBufferingRequest) returns (GetUseBufferingResponse) { } + + /** + * @brief Gets the current duration of the playback + * @see GetDurationRequest + */ + rpc getDuration(GetDurationRequest) returns (GetDurationResponse) { + } + } diff --git a/scripts/gtest/build_and_run_tests.py b/scripts/gtest/build_and_run_tests.py index 231f538a9..3ad119f44 100644 --- a/scripts/gtest/build_and_run_tests.py +++ b/scripts/gtest/build_and_run_tests.py @@ -73,6 +73,8 @@ def buildAndRunGTests(args, f, buildDefines, suitesToRun): os.environ["RIALTO_CONSOLE_LOG"] = "1" # Set env variable to enable debug prints os.environ["RIALTO_DEBUG"] = "5" + # Enable profiler for all test suites. + os.environ["PROFILER_ENABLED"] = "true" # Clean if required if args['clean'] == True: @@ -147,7 +149,8 @@ def runTests (suites, doListTests, gtestFilter, outputDir, resultsFile, xmlFile, # Run the command if resultsFile != None: - status = runcmd(executeCmd, cwd=os.getcwd() + '/' + outputDir, stdout=resultsFile, stderr=subprocess.STDOUT) + status = runcmd(executeCmd, cwd=os.getcwd() + '/' + outputDir, stdout=resultsFile, + stderr=subprocess.STDOUT) else: status = runcmd(executeCmd, cwd=os.getcwd() + '/' + outputDir, stderr=subprocess.STDOUT) diff --git a/serverManager/common/source/HealthcheckService.cpp b/serverManager/common/source/HealthcheckService.cpp index 6a478a64d..7abc8e205 100644 --- a/serverManager/common/source/HealthcheckService.cpp +++ b/serverManager/common/source/HealthcheckService.cpp @@ -65,7 +65,7 @@ void HealthcheckService::onPingSent(int serverId, int pingId) return; } m_remainingPings.insert(serverId); - m_failedPings.try_emplace(serverId, 0); + m_failedPings.try_emplace(serverId, std::set{}); } void HealthcheckService::onPingFailed(int serverId, int pingId) @@ -86,7 +86,7 @@ void HealthcheckService::onPingFailed(int serverId, int pingId) { m_sessionServerAppManager.onSessionServerStateChanged(serverId, firebolt::rialto::common::SessionServerState::ERROR); - m_failedPings.emplace(serverId, 1); + m_failedPings.emplace(serverId, std::set{pingId}); } } @@ -95,15 +95,26 @@ void HealthcheckService::onAckReceived(int serverId, int pingId, bool success) std::unique_lock lock{m_mutex}; if (pingId != m_currentPingId) { - RIALTO_SERVER_MANAGER_LOG_WARN("Unexpected ack received from server id: %d. Current ping id: %d, received ping " - "id: %d", - serverId, m_currentPingId, pingId); + if (success && m_failedPings[serverId].find(pingId) != m_failedPings[serverId].end()) + { + RIALTO_SERVER_MANAGER_LOG_WARN("Late ack received for server id: %d, Current ping id: %d, received ping " + "id: %d. Removing from failed pings list", + serverId, m_currentPingId, pingId); + m_failedPings[serverId].erase(pingId); + } + else + { + RIALTO_SERVER_MANAGER_LOG_ERROR("Unexpected ack received from server id: %d. Current ping id: %d, received " + "ping " + "id: %d", + serverId, m_currentPingId, pingId); + } return; } m_remainingPings.erase(serverId); if (success) { - m_failedPings[serverId] = 0; + m_failedPings[serverId].clear(); } else { @@ -136,12 +147,13 @@ void HealthcheckService::sendPing() void HealthcheckService::handleError(int serverId) { m_sessionServerAppManager.onSessionServerStateChanged(serverId, firebolt::rialto::common::SessionServerState::ERROR); - unsigned &failedPingsNum{m_failedPings[serverId]}; - if (++failedPingsNum >= m_kNumOfFailedPingsBeforeRecovery) + auto &failedPings{m_failedPings[serverId]}; + failedPings.insert(m_currentPingId); + if (failedPings.size() >= m_kNumOfFailedPingsBeforeRecovery) { RIALTO_SERVER_MANAGER_LOG_WARN( "Max num of failed pings reached for server with id: %d. Starting recovery action", serverId); - failedPingsNum = 0; + failedPings.clear(); m_sessionServerAppManager.restartServer(serverId); } } diff --git a/serverManager/common/source/HealthcheckService.h b/serverManager/common/source/HealthcheckService.h index d0517bdb4..b3649ac0b 100644 --- a/serverManager/common/source/HealthcheckService.h +++ b/serverManager/common/source/HealthcheckService.h @@ -53,7 +53,7 @@ class HealthcheckService : public IHealthcheckService std::mutex m_mutex; int m_currentPingId; std::set m_remainingPings; - std::map m_failedPings; + std::map> m_failedPings; }; } // namespace rialto::servermanager::common diff --git a/serverManager/common/source/ISessionServerAppFactory.h b/serverManager/common/source/ISessionServerAppFactory.h index 18c5323ce..7b2d9d1ab 100644 --- a/serverManager/common/source/ISessionServerAppFactory.h +++ b/serverManager/common/source/ISessionServerAppFactory.h @@ -36,11 +36,11 @@ class ISessionServerAppFactory ISessionServerAppFactory() = default; virtual ~ISessionServerAppFactory() = default; - virtual std::unique_ptr + virtual std::shared_ptr create(const std::string &appName, const firebolt::rialto::common::SessionServerState &initialState, const firebolt::rialto::common::AppConfig &appConfig, SessionServerAppManager &sessionServerAppManager, std::unique_ptr &&namedSocket) const = 0; - virtual std::unique_ptr + virtual std::shared_ptr create(SessionServerAppManager &sessionServerAppManager, std::unique_ptr &&namedSocket) const = 0; }; diff --git a/serverManager/common/source/SessionServerApp.cpp b/serverManager/common/source/SessionServerApp.cpp index 75b6388d7..ac5cac45a 100644 --- a/serverManager/common/source/SessionServerApp.cpp +++ b/serverManager/common/source/SessionServerApp.cpp @@ -298,7 +298,6 @@ void SessionServerApp::kill() if (m_pid > 0) { m_linuxWrapper->kill(m_pid, SIGKILL); - m_pid = -1; } } diff --git a/serverManager/common/source/SessionServerAppFactory.cpp b/serverManager/common/source/SessionServerAppFactory.cpp index 24e940c52..63e01c305 100644 --- a/serverManager/common/source/SessionServerAppFactory.cpp +++ b/serverManager/common/source/SessionServerAppFactory.cpp @@ -38,12 +38,12 @@ SessionServerAppFactory::SessionServerAppFactory(const std::list &e { } -std::unique_ptr SessionServerAppFactory::create( +std::shared_ptr SessionServerAppFactory::create( const std::string &appName, const firebolt::rialto::common::SessionServerState &initialState, const firebolt::rialto::common::AppConfig &appConfig, SessionServerAppManager &sessionServerAppManager, std::unique_ptr &&namedSocket) const { - return std::make_unique(appName, initialState, appConfig, + return std::make_shared(appName, initialState, appConfig, m_linuxWrapperFactory->createLinuxWrapper(), firebolt::rialto::common::ITimerFactory::getFactory(), sessionServerAppManager, m_kEnvironmentVariables, m_kSessionServerPath, @@ -51,11 +51,11 @@ std::unique_ptr SessionServerAppFactory::create( m_kSocketGroup, std::move(namedSocket)); } -std::unique_ptr +std::shared_ptr SessionServerAppFactory::create(SessionServerAppManager &sessionServerAppManager, std::unique_ptr &&namedSocket) const { - return std::make_unique(m_linuxWrapperFactory->createLinuxWrapper(), + return std::make_shared(m_linuxWrapperFactory->createLinuxWrapper(), firebolt::rialto::common::ITimerFactory::getFactory(), sessionServerAppManager, m_kEnvironmentVariables, m_kSessionServerPath, m_kSessionServerStartupTimeout, m_kSocketPermissions, m_kSocketOwner, diff --git a/serverManager/common/source/SessionServerAppFactory.h b/serverManager/common/source/SessionServerAppFactory.h index 5802b7841..c8a49b82e 100644 --- a/serverManager/common/source/SessionServerAppFactory.h +++ b/serverManager/common/source/SessionServerAppFactory.h @@ -39,11 +39,11 @@ class SessionServerAppFactory : public ISessionServerAppFactory const std::string &socketGroup); ~SessionServerAppFactory() override = default; - std::unique_ptr + std::shared_ptr create(const std::string &appName, const firebolt::rialto::common::SessionServerState &initialState, const firebolt::rialto::common::AppConfig &appConfig, SessionServerAppManager &sessionServerAppManager, std::unique_ptr &&namedSocket) const override; - std::unique_ptr + std::shared_ptr create(SessionServerAppManager &sessionServerAppManager, std::unique_ptr &&namedSocket) const override; diff --git a/serverManager/common/source/SessionServerAppManager.cpp b/serverManager/common/source/SessionServerAppManager.cpp index 7ce152a51..08b465973 100644 --- a/serverManager/common/source/SessionServerAppManager.cpp +++ b/serverManager/common/source/SessionServerAppManager.cpp @@ -24,11 +24,6 @@ #include #include -namespace -{ -const std::unique_ptr kInvalidSessionServer; -} // namespace - namespace rialto::servermanager::common { SessionServerAppManager::SessionServerAppManager( @@ -82,7 +77,7 @@ bool SessionServerAppManager::handleInitiateApplication(const std::string &appNa toString(state)); if (state != firebolt::rialto::common::SessionServerState::NOT_RUNNING && !getServerByAppName(appName)) { - const auto &preloadedServer{getPreloadedServer()}; + auto preloadedServer{getPreloadedServer()}; if (preloadedServer) { return configurePreloadedSessionServer(preloadedServer, appName, state, appConfig); @@ -153,10 +148,10 @@ std::string SessionServerAppManager::getAppConnectionInfo(const std::string &app m_eventThread->add( [&]() { - const auto &kSessionServer{getServerByAppName(appName)}; - if (kSessionServer) + auto sessionServer{getServerByAppName(appName)}; + if (sessionServer) { - return p.set_value(kSessionServer->getSessionManagementSocketName()); + return p.set_value(sessionServer->getSessionManagementSocketName()); } RIALTO_SERVER_MANAGER_LOG_ERROR("App: %s could not be found", appName.c_str()); return p.set_value(""); @@ -196,18 +191,22 @@ void SessionServerAppManager::handleRestartServer(int serverId) RIALTO_SERVER_MANAGER_LOG_DEBUG("Not restarting serverId: %d as server manager is shutting down", serverId); return; } - const auto &kSessionServer{getServerById(serverId)}; - if (!kSessionServer) + auto sessionServer{getServerById(serverId)}; + if (!sessionServer) { RIALTO_SERVER_MANAGER_LOG_WARN("Unable to restart server, serverId: %d", serverId); return; } + if (m_healthcheckService) + { + m_healthcheckService->onServerRemoved(sessionServer->getServerId()); + } // First, get all needed information from current app - const std::string kAppName{kSessionServer->getAppName()}; - const firebolt::rialto::common::SessionServerState kState{kSessionServer->getExpectedState()}; - const firebolt::rialto::common::AppConfig kAppConfig{kSessionServer->getSessionManagementSocketName(), - kSessionServer->getClientDisplayName()}; - std::unique_ptr namedSocket{std::move(kSessionServer->releaseNamedSocket())}; + const std::string kAppName{sessionServer->getAppName()}; + const firebolt::rialto::common::SessionServerState kState{sessionServer->getExpectedState()}; + const firebolt::rialto::common::AppConfig kAppConfig{sessionServer->getSessionManagementSocketName(), + sessionServer->getClientDisplayName()}; + std::unique_ptr namedSocket{std::move(sessionServer->releaseNamedSocket())}; if (firebolt::rialto::common::SessionServerState::INACTIVE != kState && firebolt::rialto::common::SessionServerState::ACTIVE != kState) { @@ -216,8 +215,9 @@ void SessionServerAppManager::handleRestartServer(int serverId) } RIALTO_SERVER_MANAGER_LOG_DEBUG("Restarting server with id: %d", serverId); // Then kill the app - kSessionServer->kill(); + sessionServer->kill(); handleSessionServerStateChange(serverId, firebolt::rialto::common::SessionServerState::NOT_RUNNING); + sessionServer.reset(); // Finally, spawn the new app with old settings and set named socket if present auto app = m_sessionServerAppFactory->create(kAppName, kState, kAppConfig, *this, std::move(namedSocket)); @@ -235,7 +235,7 @@ void SessionServerAppManager::handleRestartServer(int serverId) } } -bool SessionServerAppManager::connectSessionServer(const std::unique_ptr &kSessionServer) +bool SessionServerAppManager::connectSessionServer(const std::shared_ptr &kSessionServer) { if (!kSessionServer) { @@ -260,7 +260,7 @@ bool SessionServerAppManager::connectSessionServer(const std::unique_ptr &kSessionServer) +bool SessionServerAppManager::configureSessionServer(const std::shared_ptr &kSessionServer) { if (!kSessionServer) { @@ -274,7 +274,7 @@ bool SessionServerAppManager::configureSessionServer(const std::unique_ptr &kSessionServer, +bool SessionServerAppManager::configurePreloadedSessionServer(const std::shared_ptr &kSessionServer, const std::string &appName, const firebolt::rialto::common::SessionServerState &state, const firebolt::rialto::common::AppConfig &appConfig) @@ -302,18 +302,22 @@ bool SessionServerAppManager::changeSessionServerState(const std::string &appNam { RIALTO_SERVER_MANAGER_LOG_INFO("RialtoServerManager requests to change state of %s to %s", appName.c_str(), toString(newState)); - const auto &kSessionServer{getServerByAppName(appName)}; - if (!kSessionServer) + auto sessionServer{getServerByAppName(appName)}; + if (!sessionServer) { RIALTO_SERVER_MANAGER_LOG_ERROR("Change state of %s to %s failed - session server not found.", appName.c_str(), toString(newState)); return false; } - kSessionServer->setExpectedState(newState); - if (!m_ipcController->performSetState(kSessionServer->getServerId(), newState)) + sessionServer->setExpectedState(newState); + if (m_healthcheckService && firebolt::rialto::common::SessionServerState::NOT_RUNNING == newState) + { + m_healthcheckService->onServerRemoved(sessionServer->getServerId()); + } + if (!m_ipcController->performSetState(sessionServer->getServerId(), newState)) { RIALTO_SERVER_MANAGER_LOG_ERROR("Change state of %s to %s failed.", appName.c_str(), toString(newState)); - handleStateChangeFailure(kSessionServer, newState); + handleStateChangeFailure(sessionServer, newState); return false; } RIALTO_SERVER_MANAGER_LOG_INFO("Change state of %s to %s succeeded.", appName.c_str(), toString(newState)); @@ -324,46 +328,49 @@ void SessionServerAppManager::handleSessionServerStateChange(int serverId, firebolt::rialto::common::SessionServerState newState) { RIALTO_SERVER_MANAGER_LOG_INFO("SessionServer with id: %d changed state to %s", serverId, toString(newState)); - const auto &kSessionServer{getServerById(serverId)}; - if (!kSessionServer) + auto sessionServer{getServerById(serverId)}; + if (!sessionServer) { RIALTO_SERVER_MANAGER_LOG_WARN("SessionServer with id: %d not found", serverId); return; } - std::string appName{kSessionServer->getAppName()}; + std::string appName{sessionServer->getAppName()}; if (!appName.empty() && m_stateObserver) // empty app name is when SessionServer is preloaded { m_stateObserver->stateChanged(appName, newState); } if (firebolt::rialto::common::SessionServerState::UNINITIALIZED == newState) { - kSessionServer->cancelStartupTimer(); - if (!kSessionServer->isPreloaded() && !configureSessionServer(kSessionServer)) + sessionServer->cancelStartupTimer(); + if (!sessionServer->isPreloaded() && !configureSessionServer(sessionServer)) { handleSessionServerStateChange(serverId, firebolt::rialto::common::SessionServerState::ERROR); - kSessionServer->kill(); + sessionServer->kill(); handleSessionServerStateChange(serverId, firebolt::rialto::common::SessionServerState::NOT_RUNNING); } } - else if (newState == firebolt::rialto::common::SessionServerState::ERROR && kSessionServer->isPreloaded()) + else if (newState == firebolt::rialto::common::SessionServerState::ERROR && sessionServer->isPreloaded()) { m_ipcController->removeClient(serverId); - kSessionServer->kill(); + sessionServer->kill(); if (m_healthcheckService) { - m_healthcheckService->onServerRemoved(kSessionServer->getServerId()); + m_healthcheckService->onServerRemoved(sessionServer->getServerId()); + } + m_sessionServerApps.erase(sessionServer); + if (!m_isShuttingDown) + { + connectSessionServer(preloadSessionServer()); } - m_sessionServerApps.erase(kSessionServer); - connectSessionServer(preloadSessionServer()); } else if (newState == firebolt::rialto::common::SessionServerState::NOT_RUNNING) { m_ipcController->removeClient(serverId); if (m_healthcheckService) { - m_healthcheckService->onServerRemoved(kSessionServer->getServerId()); + m_healthcheckService->onServerRemoved(sessionServer->getServerId()); } - m_sessionServerApps.erase(kSessionServer); + m_sessionServerApps.erase(sessionServer); } } @@ -394,7 +401,7 @@ void SessionServerAppManager::shutdownAllSessionServers() m_sessionServerApps.clear(); } -const std::unique_ptr & +std::shared_ptr SessionServerAppManager::launchSessionServer(const std::string &appName, const firebolt::rialto::common::SessionServerState &kInitialState, const firebolt::rialto::common::AppConfig &appConfig) @@ -404,31 +411,29 @@ SessionServerAppManager::launchSessionServer(const std::string &appName, m_namedSocketFactory.createNamedSocket()); if (app->launch()) { - auto result = m_sessionServerApps.emplace(std::move(app)); - if (result.second) + if (m_sessionServerApps.emplace(app).second) { - return *result.first; + return app; } } - return kInvalidSessionServer; + return nullptr; } -const std::unique_ptr &SessionServerAppManager::preloadSessionServer() +std::shared_ptr SessionServerAppManager::preloadSessionServer() { RIALTO_SERVER_MANAGER_LOG_INFO("Preloading new Rialto Session Server"); auto app = m_sessionServerAppFactory->create(*this, m_namedSocketFactory.createNamedSocket()); if (app->launch()) { - auto result = m_sessionServerApps.emplace(std::move(app)); - if (result.second) + if (m_sessionServerApps.emplace(app).second) { - return *result.first; + return app; } } - return kInvalidSessionServer; + return nullptr; } -const std::unique_ptr &SessionServerAppManager::getPreloadedServer() const +std::shared_ptr SessionServerAppManager::getPreloadedServer() const { auto iter = std::find_if(m_sessionServerApps.begin(), m_sessionServerApps.end(), [](const auto &srv) { return srv->isPreloaded() && srv->isConnected(); }); @@ -436,10 +441,10 @@ const std::unique_ptr &SessionServerAppManager::getPreloadedS { return *iter; } - return kInvalidSessionServer; + return nullptr; } -const std::unique_ptr &SessionServerAppManager::getServerByAppName(const std::string &appName) const +std::shared_ptr SessionServerAppManager::getServerByAppName(const std::string &appName) const { auto iter{std::find_if(m_sessionServerApps.begin(), m_sessionServerApps.end(), [&](const auto &srv) { return srv->getAppName() == appName; })}; @@ -447,10 +452,10 @@ const std::unique_ptr &SessionServerAppManager::getServerByAp { return *iter; } - return kInvalidSessionServer; + return nullptr; } -const std::unique_ptr &SessionServerAppManager::getServerById(int serverId) const +std::shared_ptr SessionServerAppManager::getServerById(int serverId) const { auto iter{std::find_if(m_sessionServerApps.begin(), m_sessionServerApps.end(), [&](const auto &srv) { return srv->getServerId() == serverId; })}; @@ -458,10 +463,10 @@ const std::unique_ptr &SessionServerAppManager::getServerById { return *iter; } - return kInvalidSessionServer; + return nullptr; } -void SessionServerAppManager::handleStateChangeFailure(const std::unique_ptr &kSessionServer, +void SessionServerAppManager::handleStateChangeFailure(const std::shared_ptr &kSessionServer, const firebolt::rialto::common::SessionServerState &state) { if (state == firebolt::rialto::common::SessionServerState::NOT_RUNNING) @@ -477,7 +482,7 @@ void SessionServerAppManager::handleStateChangeFailure(const std::unique_ptr &kSessionServer) +bool SessionServerAppManager::configureSessionServerWithSocketName(const std::shared_ptr &kSessionServer) { RIALTO_SERVER_MANAGER_LOG_DEBUG("Configuring Session Server using socket name"); const auto kInitialState{kSessionServer->getInitialState()}; @@ -502,7 +507,7 @@ bool SessionServerAppManager::configureSessionServerWithSocketName(const std::un return true; } -bool SessionServerAppManager::configureSessionServerWithSocketFd(const std::unique_ptr &kSessionServer) +bool SessionServerAppManager::configureSessionServerWithSocketFd(const std::shared_ptr &kSessionServer) { RIALTO_SERVER_MANAGER_LOG_DEBUG("Configuring Session Server using socket fd"); const auto kInitialState{kSessionServer->getInitialState()}; @@ -530,19 +535,23 @@ void SessionServerAppManager::onServerStartupTimeout(int serverId) void SessionServerAppManager::handleServerStartupTimeout(int serverId) { - const auto &kSessionServer{getServerById(serverId)}; - if (!kSessionServer) + auto sessionServer{getServerById(serverId)}; + if (!sessionServer) { RIALTO_SERVER_MANAGER_LOG_WARN("Unable to handle startup timeout for serverId: %d", serverId); return; } - const bool isPreloaded{kSessionServer->isPreloaded()}; + const bool isPreloaded{sessionServer->isPreloaded()}; RIALTO_SERVER_MANAGER_LOG_WARN("Killing: %d", serverId); handleSessionServerStateChange(serverId, firebolt::rialto::common::SessionServerState::ERROR); if (!isPreloaded) { - kSessionServer->kill(); + if (m_healthcheckService) + { + m_healthcheckService->onServerRemoved(sessionServer->getServerId()); + } + sessionServer->kill(); handleSessionServerStateChange(serverId, firebolt::rialto::common::SessionServerState::NOT_RUNNING); } } diff --git a/serverManager/common/source/SessionServerAppManager.h b/serverManager/common/source/SessionServerAppManager.h index b1d684348..e16f26f70 100644 --- a/serverManager/common/source/SessionServerAppManager.h +++ b/serverManager/common/source/SessionServerAppManager.h @@ -64,9 +64,9 @@ class SessionServerAppManager : public ISessionServerAppManager void onServerStartupTimeout(int serverId) override; private: - bool connectSessionServer(const std::unique_ptr &sessionServer); - bool configureSessionServer(const std::unique_ptr &sessionServer); - bool configurePreloadedSessionServer(const std::unique_ptr &sessionServer, + bool connectSessionServer(const std::shared_ptr &sessionServer); + bool configureSessionServer(const std::shared_ptr &sessionServer); + bool configurePreloadedSessionServer(const std::shared_ptr &sessionServer, const std::string &appName, const firebolt::rialto::common::SessionServerState &state, const firebolt::rialto::common::AppConfig &appConfig); @@ -75,26 +75,26 @@ class SessionServerAppManager : public ISessionServerAppManager void handleSessionServerStateChange(int serverId, firebolt::rialto::common::SessionServerState newState); void handleAck(int serverId, int pingId, bool success); void shutdownAllSessionServers(); - const std::unique_ptr & + std::shared_ptr launchSessionServer(const std::string &appName, const firebolt::rialto::common::SessionServerState &initialState, const firebolt::rialto::common::AppConfig &appConfig); - void handleStateChangeFailure(const std::unique_ptr &sessionServer, + void handleStateChangeFailure(const std::shared_ptr &sessionServer, const firebolt::rialto::common::SessionServerState &state); - const std::unique_ptr &preloadSessionServer(); - const std::unique_ptr &getPreloadedServer() const; - const std::unique_ptr &getServerByAppName(const std::string &appName) const; - const std::unique_ptr &getServerById(int serverId) const; + std::shared_ptr preloadSessionServer(); + std::shared_ptr getPreloadedServer() const; + std::shared_ptr getServerByAppName(const std::string &appName) const; + std::shared_ptr getServerById(int serverId) const; bool handleInitiateApplication(const std::string &appName, const firebolt::rialto::common::SessionServerState &state, const firebolt::rialto::common::AppConfig &appConfig); void handleRestartServer(int serverId); - bool configureSessionServerWithSocketName(const std::unique_ptr &kSessionServer); - bool configureSessionServerWithSocketFd(const std::unique_ptr &kSessionServer); + bool configureSessionServerWithSocketName(const std::shared_ptr &kSessionServer); + bool configureSessionServerWithSocketFd(const std::shared_ptr &kSessionServer); void handleServerStartupTimeout(int serverId); private: std::unique_ptr &m_ipcController; std::unique_ptr m_eventThread; - std::set> m_sessionServerApps; + std::set> m_sessionServerApps; std::unique_ptr m_sessionServerAppFactory; std::shared_ptr m_stateObserver; std::unique_ptr m_healthcheckService; diff --git a/serverManager/ipc/source/Client.cpp b/serverManager/ipc/source/Client.cpp index 2690093fc..c11c0693c 100644 --- a/serverManager/ipc/source/Client.cpp +++ b/serverManager/ipc/source/Client.cpp @@ -138,6 +138,15 @@ Client::Client(std::unique_ptr &sessionServerA Client::~Client() { RIALTO_SERVER_MANAGER_LOG_INFO("Client for serverId: %d is destructed", m_serverId); + m_isShuttingDown = true; + if (m_ipcLoop && m_ipcLoop->channel()) + { + for (const auto &tag : m_eventTags) + { + m_ipcLoop->channel()->unsubscribe(tag); + } + m_eventTags.clear(); + } m_serviceStub.reset(); m_ipcLoop.reset(); } @@ -151,9 +160,18 @@ bool Client::connect() return false; } m_serviceStub = std::make_unique<::rialto::ServerManagerModule_Stub>(m_ipcLoop->channel()); - m_ipcLoop->channel()->subscribe( - std::bind(&Client::onStateChangedEvent, this, std::placeholders::_1)); - m_ipcLoop->channel()->subscribe(std::bind(&Client::onAckEvent, this, std::placeholders::_1)); + int eventTag{m_ipcLoop->channel()->subscribe( + std::bind(&Client::onStateChangedEvent, this, std::placeholders::_1))}; + if (eventTag >= 0) + { + m_eventTags.push_back(eventTag); + } + eventTag = + m_ipcLoop->channel()->subscribe(std::bind(&Client::onAckEvent, this, std::placeholders::_1)); + if (eventTag >= 0) + { + m_eventTags.push_back(eventTag); + } return true; } @@ -308,6 +326,12 @@ bool Client::setLogLevels(const service::LoggingLevels &logLevels) const void Client::onDisconnected() const { + if (!m_sessionServerAppManager || m_isShuttingDown) + { + RIALTO_SERVER_MANAGER_LOG_DEBUG("Connection to serverId: %d broken, but server manager is shutting down", + m_serverId); + return; + } RIALTO_SERVER_MANAGER_LOG_WARN("Connection to serverId: %d broken, server probably crashed. Starting recovery", m_serverId); m_sessionServerAppManager->restartServer(m_serverId); @@ -316,7 +340,7 @@ void Client::onDisconnected() const void Client::onStateChangedEvent(const std::shared_ptr &event) const { RIALTO_SERVER_MANAGER_LOG_DEBUG("StateChangedEvent received for serverId: %d", m_serverId); - if (!m_sessionServerAppManager || !event) + if (!m_sessionServerAppManager || !event || m_isShuttingDown) { RIALTO_SERVER_MANAGER_LOG_WARN("Problem during StateChangedEvent processing"); return; @@ -327,7 +351,7 @@ void Client::onStateChangedEvent(const std::shared_ptr &event) const { RIALTO_SERVER_MANAGER_LOG_DEBUG("AckEvent received for serverId: %d", m_serverId); - if (!m_sessionServerAppManager || !event) + if (!m_sessionServerAppManager || !event || m_isShuttingDown) { RIALTO_SERVER_MANAGER_LOG_WARN("Problem during AckEvent processing"); return; diff --git a/serverManager/ipc/source/Client.h b/serverManager/ipc/source/Client.h index f97c87f02..0eb84aa6f 100644 --- a/serverManager/ipc/source/Client.h +++ b/serverManager/ipc/source/Client.h @@ -26,6 +26,7 @@ #include "LoggingLevels.h" #include #include +#include namespace rialto { @@ -69,9 +70,11 @@ class Client int m_serverId; std::unique_ptr &m_sessionServerAppManager; int m_socket; + bool m_isShuttingDown{false}; std::shared_ptr<::firebolt::rialto::ipc::IChannel> m_channel; std::shared_ptr m_ipcLoop; std::unique_ptr<::rialto::ServerManagerModule_Stub> m_serviceStub; + std::vector m_eventTags; }; } // namespace rialto::servermanager::ipc diff --git a/serverManager/serverManagerSim/HttpRequest.cpp b/serverManager/serverManagerSim/HttpRequest.cpp index ba2bfb50c..154cff654 100644 --- a/serverManager/serverManagerSim/HttpRequest.cpp +++ b/serverManager/serverManagerSim/HttpRequest.cpp @@ -19,6 +19,7 @@ #include "HttpRequest.h" #include +#include namespace { @@ -28,16 +29,16 @@ std::vector splitUri(std::string uri) size_t pos = 0; while ((pos = uri.find("/")) != std::string::npos) { - const std::string token = uri.substr(0, pos); + std::string token = uri.substr(0, pos); if (!token.empty()) { - result.push_back(token); + result.push_back(std::move(token)); } uri.erase(0, pos + 1); } if (!uri.empty()) { - result.push_back(uri); + result.push_back(std::move(uri)); } return result; } diff --git a/serverManager/service/source/ServerManagerService.cpp b/serverManager/service/source/ServerManagerService.cpp index 09fa6126e..9edd1576f 100644 --- a/serverManager/service/source/ServerManagerService.cpp +++ b/serverManager/service/source/ServerManagerService.cpp @@ -59,7 +59,7 @@ ServerManagerService::ServerManagerService(std::unique_ptr &&co ServerManagerService::~ServerManagerService() { - RIALTO_SERVER_MANAGER_LOG_INFO("RialtoServerManager is closing..."); + RIALTO_SERVER_MANAGER_LOG_MIL("RialtoServerManager is closing..."); if (m_logHandler) { firebolt::rialto::logging::setLogHandler(RIALTO_COMPONENT_SERVER_MANAGER, 0, false); diff --git a/tests/common/externalLibraryMocks/GstWrapperMock.h b/tests/common/externalLibraryMocks/GstWrapperMock.h index 401b8e2f7..185084ff5 100644 --- a/tests/common/externalLibraryMocks/GstWrapperMock.h +++ b/tests/common/externalLibraryMocks/GstWrapperMock.h @@ -87,6 +87,7 @@ class GstWrapperMock : public IGstWrapper MOCK_METHOD(void, gstBusSetSyncHandler, (GstBus *, GstBusSyncHandler, gpointer, GDestroyNotify), (override)); MOCK_METHOD(GstFlowReturn, gstAppSrcEndOfStream, (GstAppSrc *), (override)); MOCK_METHOD(gboolean, gstElementQueryPosition, (GstElement *, GstFormat, gint64 *), (override)); + MOCK_METHOD(gboolean, gstElementQueryDuration, (GstElement *, GstFormat, gint64 *), (override)); MOCK_METHOD(GstFlowReturn, gstAppSrcPushBuffer, (GstAppSrc *, GstBuffer *), (override)); MOCK_METHOD(GstBuffer *, gstBufferNew, (), (override)); MOCK_METHOD(GstBuffer *, gstBufferNewAllocate, (GstAllocator *, gsize, GstAllocationParams *), (override)); @@ -220,6 +221,18 @@ class GstWrapperMock : public IGstWrapper MOCK_METHOD(gboolean, gstIsBaseParse, (GstElement * element), (const, override)); MOCK_METHOD(void, gstBaseParseSetPtsInterpolation, (GstBaseParse * parse, gboolean ptsInterpolate), (const, override)); + MOCK_METHOD(GstStateChangeReturn, gstElementGetState, + (GstElement * element, GstState *state, GstState *pending, GstClockTime timeout), (override)); + MOCK_METHOD(GstPad *, gstPadGetPeer, (GstPad * pad), (override)); + MOCK_METHOD(gboolean, gstPadUnlink, (GstPad * srcpad, GstPad *sinkpad), (override)); + MOCK_METHOD(GstPadLinkReturn, gstPadLink, (GstPad * srcpad, GstPad *sinkpad), (override)); + MOCK_METHOD(gboolean, gstBinRemove, (GstBin * bin, GstElement *element), (override)); + MOCK_METHOD(GstObject *, gstPadGetParent, (GstPad * pad), (override)); + MOCK_METHOD(gulong, gstPadAddProbe, + (GstPad * pad, GstPadProbeType mask, GstPadProbeCallback callback, gpointer userData, + GDestroyNotify destroyData), + (override)); + MOCK_METHOD(void, gstPadRemoveProbe, (GstPad * pad, gulong id), (override)); GstCaps *gstCapsNewSimple(const char *media_type, const char *fieldname, ...) const override { diff --git a/tests/common/matchers/MediaKeysProtoRequestMatchers.h b/tests/common/matchers/MediaKeysProtoRequestMatchers.h index 4784bc0a3..ec147a8ed 100644 --- a/tests/common/matchers/MediaKeysProtoRequestMatchers.h +++ b/tests/common/matchers/MediaKeysProtoRequestMatchers.h @@ -42,12 +42,11 @@ MATCHER_P(destroyMediaKeysRequestMatcher, mediaKeysHandle, "") return (kRequest->media_keys_handle() == mediaKeysHandle); } -MATCHER_P3(createKeySessionRequestMatcher, mediaKeysHandle, sessionType, isLdl, "") +MATCHER_P2(createKeySessionRequestMatcher, mediaKeysHandle, sessionType, "") { const ::firebolt::rialto::CreateKeySessionRequest *kRequest = dynamic_cast(arg); - return ((kRequest->media_keys_handle() == mediaKeysHandle) && (kRequest->session_type() == sessionType) && - (kRequest->is_ldl() == isLdl)); + return ((kRequest->media_keys_handle() == mediaKeysHandle) && (kRequest->session_type() == sessionType)); } MATCHER_P4(generateRequestRequestMatcher, mediaKeysHandle, keySessionId, initDataType, initData, "") diff --git a/tests/common/matchers/MediaPipelineProtoRequestMatchers.h b/tests/common/matchers/MediaPipelineProtoRequestMatchers.h index 04c48ad78..ac34ace4b 100644 --- a/tests/common/matchers/MediaPipelineProtoRequestMatchers.h +++ b/tests/common/matchers/MediaPipelineProtoRequestMatchers.h @@ -37,11 +37,11 @@ MATCHER_P2(createSessionRequestMatcher, maxWidth, maxHeight, "") return ((kRequest->max_width() == maxWidth) && (kRequest->max_height() == maxHeight)); } -MATCHER_P4(loadRequestMatcher, sessionId, type, mimeType, url, "") +MATCHER_P5(loadRequestMatcher, sessionId, type, mimeType, url, isLive, "") { const ::firebolt::rialto::LoadRequest *kRequest = dynamic_cast(arg); return ((kRequest->session_id() == sessionId) && (kRequest->type() == type) && - (kRequest->mime_type() == mimeType) && (kRequest->url() == url)); + (kRequest->mime_type() == mimeType) && (kRequest->url() == url) && (kRequest->is_live() == isLive)); } MATCHER_P(playRequestMatcher, sessionId, "") @@ -352,6 +352,13 @@ MATCHER_P(getPositionRequestMatcher, sessionId, "") return ((kRequest->session_id() == sessionId)); } +MATCHER_P(getDurationRequestMatcher, sessionId, "") +{ + const ::firebolt::rialto::GetDurationRequest *kRequest = + dynamic_cast(arg); + return ((kRequest->session_id() == sessionId)); +} + MATCHER_P2(setImmediateOutputRequestMatcher, sessionId, sourceId, "") { const ::firebolt::rialto::SetImmediateOutputRequest *kRequest = diff --git a/tests/common/protoUtils/MediaKeysProtoUtils.h b/tests/common/protoUtils/MediaKeysProtoUtils.h index 56ca3e58d..5d8a94132 100644 --- a/tests/common/protoUtils/MediaKeysProtoUtils.h +++ b/tests/common/protoUtils/MediaKeysProtoUtils.h @@ -163,6 +163,21 @@ convertInitDataType(const firebolt::rialto::InitDataType &initDataType) } } +inline firebolt::rialto::GenerateRequestRequest_LimitedDurationLicense +convertLimitedDurationLicense(const firebolt::rialto::LimitedDurationLicense &ldlState) +{ + switch (ldlState) + { + case firebolt::rialto::LimitedDurationLicense::ENABLED: + return firebolt::rialto::GenerateRequestRequest_LimitedDurationLicense_ENABLED; + case firebolt::rialto::LimitedDurationLicense::DISABLED: + return firebolt::rialto::GenerateRequestRequest_LimitedDurationLicense_DISABLED; + case firebolt::rialto::LimitedDurationLicense::NOT_SPECIFIED: + default: + return firebolt::rialto::GenerateRequestRequest_LimitedDurationLicense_NOT_SPECIFIED; + } +} + inline firebolt::rialto::KeyStatus convertKeyStatus(const firebolt::rialto::KeyStatusesChangedEvent_KeyStatus &keyStatus) { switch (keyStatus) diff --git a/tests/componenttests/client/mocks/MediaPipelineModuleMock.h b/tests/componenttests/client/mocks/MediaPipelineModuleMock.h index a6fc772c0..4960afae5 100644 --- a/tests/componenttests/client/mocks/MediaPipelineModuleMock.h +++ b/tests/componenttests/client/mocks/MediaPipelineModuleMock.h @@ -64,6 +64,9 @@ class MediaPipelineModuleMock : public ::firebolt::rialto::MediaPipelineModule MOCK_METHOD(void, getPosition, (::google::protobuf::RpcController * controller, const ::firebolt::rialto::GetPositionRequest *request, ::firebolt::rialto::GetPositionResponse *response, ::google::protobuf::Closure *done)); + MOCK_METHOD(void, getDuration, + (::google::protobuf::RpcController * controller, const ::firebolt::rialto::GetDurationRequest *request, + ::firebolt::rialto::GetDurationResponse *response, ::google::protobuf::Closure *done)); MOCK_METHOD(void, setImmediateOutput, (::google::protobuf::RpcController * controller, const ::firebolt::rialto::SetImmediateOutputRequest *request, @@ -218,6 +221,13 @@ class MediaPipelineModuleMock : public ::firebolt::rialto::MediaPipelineModule return response; } + ::firebolt::rialto::GetDurationResponse getDurationResponse(const int64_t duration) + { + firebolt::rialto::GetDurationResponse response; + response.set_duration(duration); + return response; + } + ::firebolt::rialto::SetImmediateOutputResponse setImmediateOutputResponse() { firebolt::rialto::SetImmediateOutputResponse response; diff --git a/tests/componenttests/client/tests/base/MediaKeysTestMethods.cpp b/tests/componenttests/client/tests/base/MediaKeysTestMethods.cpp index 59341e47a..494efd4f1 100644 --- a/tests/componenttests/client/tests/base/MediaKeysTestMethods.cpp +++ b/tests/componenttests/client/tests/base/MediaKeysTestMethods.cpp @@ -32,7 +32,6 @@ const std::string kKeySystemWidevine{"com.widevine.alpha"}; const std::string kKeySystemPlayready{"com.netflix.playready"}; constexpr int32_t kMediaKeysHandle{1}; constexpr firebolt::rialto::KeySessionType kSessionTypeTemp{firebolt::rialto::KeySessionType::TEMPORARY}; -constexpr bool kIsNotLdl{false}; constexpr firebolt::rialto::MediaKeyErrorStatus kStatusOk{firebolt::rialto::MediaKeyErrorStatus::OK}; constexpr firebolt::rialto::MediaKeyErrorStatus kStatusFailed{firebolt::rialto::MediaKeyErrorStatus::FAIL}; constexpr firebolt::rialto::MediaKeyErrorStatus kStatusInterfaceNotImplemented{ @@ -108,8 +107,7 @@ void MediaKeysTestMethods::shouldCreateKeySession() { EXPECT_CALL(*m_mediaKeysModuleMock, createKeySession(_, - createKeySessionRequestMatcher(kMediaKeysHandle, - convertKeySessionType(kSessionTypeTemp), kIsNotLdl), + createKeySessionRequestMatcher(kMediaKeysHandle, convertKeySessionType(kSessionTypeTemp)), _, _)) .WillOnce(DoAll(SetArgPointee<2>(m_mediaKeysModuleMock->createKeySessionResponse(kStatusOk, kKeySessionId)), WithArgs<0, 3>(Invoke(&(*m_mediaKeysModuleMock), &MediaKeysModuleMock::defaultReturn)))); @@ -119,8 +117,7 @@ void MediaKeysTestMethods::shouldCreateKeySessionFailure() { EXPECT_CALL(*m_mediaKeysModuleMock, createKeySession(_, - createKeySessionRequestMatcher(kMediaKeysHandle, - convertKeySessionType(kSessionTypeTemp), kIsNotLdl), + createKeySessionRequestMatcher(kMediaKeysHandle, convertKeySessionType(kSessionTypeTemp)), _, _)) .WillOnce(DoAll(SetArgPointee<2>(m_mediaKeysModuleMock->createKeySessionResponse(kStatusFailed, kKeySessionId)), WithArgs<0, 3>(Invoke(&(*m_mediaKeysModuleMock), &MediaKeysModuleMock::defaultReturn)))); @@ -129,15 +126,14 @@ void MediaKeysTestMethods::shouldCreateKeySessionFailure() void MediaKeysTestMethods::createKeySession() { int32_t keySessionId; - EXPECT_EQ(m_mediaKeys->createKeySession(kSessionTypeTemp, m_mediaKeysClientMock, kIsNotLdl, keySessionId), kStatusOk); + EXPECT_EQ(m_mediaKeys->createKeySession(kSessionTypeTemp, m_mediaKeysClientMock, keySessionId), kStatusOk); EXPECT_EQ(keySessionId, kKeySessionId); } void MediaKeysTestMethods::createKeySessionFailure() { int32_t keySessionId; - EXPECT_EQ(m_mediaKeys->createKeySession(kSessionTypeTemp, m_mediaKeysClientMock, kIsNotLdl, keySessionId), - kStatusFailed); + EXPECT_EQ(m_mediaKeys->createKeySession(kSessionTypeTemp, m_mediaKeysClientMock, keySessionId), kStatusFailed); } void MediaKeysTestMethods::shouldGenerateRequest() diff --git a/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp index 51ef2b5d4..00693bbdf 100644 --- a/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp +++ b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp @@ -91,6 +91,7 @@ constexpr int64_t kDiscontinuityGap{1}; constexpr bool kIsAudioAac{false}; const std::vector kSupportedProperties{"immediate-output", "testProp2"}; constexpr uint64_t kStopPosition{452345}; +constexpr bool kIsLive{false}; } // namespace namespace firebolt::rialto::client::ct @@ -892,7 +893,8 @@ void MediaPipelineTestMethods::shouldNotifyPlaybackStateFailure() void MediaPipelineTestMethods::playFailure() { - EXPECT_EQ(m_mediaPipeline->play(), false); + bool async{false}; + EXPECT_EQ(m_mediaPipeline->play(async), false); } void MediaPipelineTestMethods::pauseFailure() @@ -1423,6 +1425,20 @@ void MediaPipelineTestMethods::getPosition(const int64_t expectedPosition) EXPECT_EQ(returnPosition, expectedPosition); } +void MediaPipelineTestMethods::shouldGetDuration(const int64_t duration) +{ + EXPECT_CALL(*m_mediaPipelineModuleMock, getDuration(_, getDurationRequestMatcher(kSessionId), _, _)) + .WillOnce(DoAll(SetArgPointee<2>(m_mediaPipelineModuleMock->getDurationResponse(duration)), + WithArgs<0, 3>(Invoke(&(*m_mediaPipelineModuleMock), &MediaPipelineModuleMock::defaultReturn)))); +} + +void MediaPipelineTestMethods::getDuration(const int64_t expectedDuration) +{ + int64_t returnDuration; + EXPECT_EQ(m_mediaPipeline->getDuration(returnDuration), true); + EXPECT_EQ(returnDuration, expectedDuration); +} + void MediaPipelineTestMethods::shouldSetImmediateOutput(bool immediateOutput) { EXPECT_CALL(*m_mediaPipelineModuleMock, @@ -1830,7 +1846,7 @@ void MediaPipelineTestMethods::shouldLoadInternal(const int32_t sessionId, const const std::string &mimeType, const std::string &url) { EXPECT_CALL(*m_mediaPipelineModuleMock, - load(_, loadRequestMatcher(sessionId, convertMediaType(mediaType), mimeType, url), _, _)) + load(_, loadRequestMatcher(sessionId, convertMediaType(mediaType), mimeType, url, kIsLive), _, _)) .WillOnce(WithArgs<0, 3>(Invoke(&(*m_mediaPipelineModuleMock), &MediaPipelineModuleMock::defaultReturn))); } @@ -1949,7 +1965,7 @@ void MediaPipelineTestMethods::loadInternal(const std::unique_ptrload(mediaType, mimeType, url), status); + EXPECT_EQ(mediaPipeline->load(mediaType, mimeType, url, kIsLive), status); } void MediaPipelineTestMethods::removeSourceInternal(const std::unique_ptr &mediaPipeline, @@ -2033,7 +2049,8 @@ void MediaPipelineTestMethods::haveDataInternal(const std::unique_ptr &mediaPipeline, const bool status) { - EXPECT_EQ(mediaPipeline->play(), status); + bool async{false}; + EXPECT_EQ(mediaPipeline->play(async), status); } void MediaPipelineTestMethods::sendNotifyPlaybackStateInternal(const int32_t sessionId, const PlaybackState &state) diff --git a/tests/componenttests/client/tests/base/MediaPipelineTestMethods.h b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.h index c6b9d7f74..ebe103cc3 100644 --- a/tests/componenttests/client/tests/base/MediaPipelineTestMethods.h +++ b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.h @@ -128,6 +128,7 @@ class MediaPipelineTestMethods void shouldRenderFrame(); void shouldRenderFrameFailure(); void shouldGetPosition(const int64_t position); + void shouldGetDuration(const int64_t duration); void shouldSetImmediateOutput(bool immediateOutput); void shouldGetImmediateOutput(bool immediateOutput); void shouldGetStats(uint64_t renderedFrames, uint64_t droppedFrames); @@ -246,6 +247,7 @@ class MediaPipelineTestMethods void renderFrame(); void renderFrameFailure(); void getPosition(const int64_t expectedPosition); + void getDuration(const int64_t expectedDuration); void setImmediateOutput(bool immediateOutput); void getImmediateOutput(bool immediateOutput); void getStats(uint64_t expectedFrames, uint64_t expectedDropped); diff --git a/tests/componenttests/client/tests/mse/PipelinePropertyTest.cpp b/tests/componenttests/client/tests/mse/PipelinePropertyTest.cpp index a64aeadd7..374253730 100644 --- a/tests/componenttests/client/tests/mse/PipelinePropertyTest.cpp +++ b/tests/componenttests/client/tests/mse/PipelinePropertyTest.cpp @@ -108,6 +108,18 @@ class PipelinePropertyTest : public ClientComponentTest * GetUseBuffering * Expect that GetUseBuffering propagated to the server and gets the property * + * Step 13: Set Report Decode Errors + * Set report decode errors + * Expect that Set report decode errors propagated to the server and sets the property + * + * Step 14: Get Queued Frames + * GetQueuedFrames + * Expect that GetQueuedFrames propagated to the server and gets the number of queued frames + * + * Step 15: Get Duration + * GetDuration + * Expect that GetDuration propagated to the server and gets the duration + * * Test Teardown: * Terminate the media session. * Memory region created for the shared buffer is closed. @@ -174,5 +186,10 @@ TEST_F(PipelinePropertyTest, setAndGetPipelineProperties) // Step 12: Get UseBuffering MediaPipelineTestMethods::shouldGetUseBuffering(useBuffering); MediaPipelineTestMethods::getUseBuffering(useBuffering); + + // Step 13: Get Duration + constexpr int64_t duration{123456789}; + MediaPipelineTestMethods::shouldGetDuration(duration); + MediaPipelineTestMethods::getDuration(duration); } } // namespace firebolt::rialto::client::ct diff --git a/tests/componenttests/server/common/ActionTraits.h b/tests/componenttests/server/common/ActionTraits.h index d601e8d9f..e0326bb82 100644 --- a/tests/componenttests/server/common/ActionTraits.h +++ b/tests/componenttests/server/common/ActionTraits.h @@ -344,6 +344,14 @@ struct ProcessAudioGap static constexpr auto m_kFunction{&Stub::processAudioGap}; }; +struct GetDuration +{ + using RequestType = ::firebolt::rialto::GetDurationRequest; + using ResponseType = ::firebolt::rialto::GetDurationResponse; + using Stub = ::firebolt::rialto::MediaPipelineModule_Stub; + static constexpr auto m_kFunction{&Stub::getDuration}; +}; + // mediakeys module struct CreateMediaKeys { diff --git a/tests/componenttests/server/common/Constants.h b/tests/componenttests/server/common/Constants.h index 0072a59a9..422e9c33c 100644 --- a/tests/componenttests/server/common/Constants.h +++ b/tests/componenttests/server/common/Constants.h @@ -54,6 +54,8 @@ constexpr double kRate{1.0}; constexpr uint64_t kRenderedFrames{54321}; constexpr uint64_t kDroppedFrames{76}; constexpr uint64_t kStopPosition{234234}; +constexpr int kPrerollNumFrames{3}; +constexpr int kFrameCountInPlayingState{24}; } // namespace firebolt::rialto::server::ct #endif // FIREBOLT_RIALTO_SERVER_CT_CONSTANTS_H_ diff --git a/tests/componenttests/server/common/ExpectMessage.h b/tests/componenttests/server/common/ExpectMessage.h index 839319e37..bd140f92a 100644 --- a/tests/componenttests/server/common/ExpectMessage.h +++ b/tests/componenttests/server/common/ExpectMessage.h @@ -70,7 +70,7 @@ template class ExpectMessage EventRanger &m_eventRanger; std::shared_ptr m_message{nullptr}; std::function m_filter{[](const MessageType &) { return true; }}; - std::chrono::milliseconds m_timeout{400}; + std::chrono::milliseconds m_timeout{600}; }; } // namespace firebolt::rialto::server::ct diff --git a/tests/componenttests/server/common/MessageBuilders.cpp b/tests/componenttests/server/common/MessageBuilders.cpp index 848b925f8..bdfb44a01 100644 --- a/tests/componenttests/server/common/MessageBuilders.cpp +++ b/tests/componenttests/server/common/MessageBuilders.cpp @@ -407,6 +407,13 @@ ::firebolt::rialto::ProcessAudioGapRequest createProcessAudioGapRequest(int sess return request; } +::firebolt::rialto::GetDurationRequest createGetDurationRequest(int sessionId) +{ + ::firebolt::rialto::GetDurationRequest request; + request.set_session_id(sessionId); + return request; +} + ::firebolt::rialto::CreateMediaKeysRequest createCreateMediaKeysRequestWidevine() { ::firebolt::rialto::CreateMediaKeysRequest request; @@ -430,7 +437,8 @@ ::firebolt::rialto::CreateKeySessionRequest createCreateKeySessionRequest(int me } ::firebolt::rialto::GenerateRequestRequest createGenerateRequestRequest(int mediaKeysHandle, int keySessionId, - const std::vector &initData) + const std::vector &initData, + bool extendedInterface) { ::firebolt::rialto::GenerateRequestRequest request; request.set_media_keys_handle(mediaKeysHandle); @@ -440,6 +448,10 @@ ::firebolt::rialto::GenerateRequestRequest createGenerateRequestRequest(int medi { request.add_init_data(i); } + if (extendedInterface) + { + request.set_ldl_state(::firebolt::rialto::GenerateRequestRequest_LimitedDurationLicense_DISABLED); + } return request; } diff --git a/tests/componenttests/server/common/MessageBuilders.h b/tests/componenttests/server/common/MessageBuilders.h index dae4679ae..0c255bb5f 100644 --- a/tests/componenttests/server/common/MessageBuilders.h +++ b/tests/componenttests/server/common/MessageBuilders.h @@ -83,13 +83,15 @@ ::firebolt::rialto::SetSourcePositionRequest createSetSourcePositionRequest(int ::firebolt::rialto::ProcessAudioGapRequest createProcessAudioGapRequest(int sessionId, std::int64_t position, unsigned duration, std::int64_t discontinuityGap, bool audioAac); +::firebolt::rialto::GetDurationRequest createGetDurationRequest(int sessionId); // media keys module ::firebolt::rialto::CreateMediaKeysRequest createCreateMediaKeysRequestWidevine(); ::firebolt::rialto::CreateMediaKeysRequest createCreateMediaKeysRequestNetflix(); ::firebolt::rialto::CreateKeySessionRequest createCreateKeySessionRequest(int mediaKeysHandle); ::firebolt::rialto::GenerateRequestRequest createGenerateRequestRequest(int mediaKeysHandle, int keySessionId, - const std::vector &initData); + const std::vector &initData, + bool extendedInterface = false); ::firebolt::rialto::UpdateSessionRequest createUpdateSessionRequest(int mediaKeysHandle, int keySessionId, const std::vector &response); ::firebolt::rialto::ContainsKeyRequest createContainsKeyRequest(int mediaKeysHandle, int keySessionId, diff --git a/tests/componenttests/server/fixtures/MediaPipelineTest.cpp b/tests/componenttests/server/fixtures/MediaPipelineTest.cpp index 03eadf4bc..94f69405e 100644 --- a/tests/componenttests/server/fixtures/MediaPipelineTest.cpp +++ b/tests/componenttests/server/fixtures/MediaPipelineTest.cpp @@ -35,6 +35,7 @@ using testing::_; using testing::AtLeast; +using testing::AtMost; using testing::DoAll; using testing::Invoke; using testing::Return; @@ -52,7 +53,6 @@ constexpr GType kGstPlayFlagsType{static_cast(123)}; constexpr unsigned kNeededDataLength{1}; constexpr std::chrono::milliseconds kWorkerTimeout{200}; GstAudioClippingMeta kClippingMeta{}; -constexpr int kNeedDataFrameCount{24}; } // namespace namespace firebolt::rialto::server::ct @@ -67,6 +67,7 @@ MediaPipelineTest::MediaPipelineTest() MediaPipelineTest::~MediaPipelineTest() { positionUpdatesShouldNotBeReceivedFromNow(); + playbackInfoUpdatesShouldNotBeReceivedFromNow(); } void MediaPipelineTest::gstPlayerWillBeCreated() @@ -91,6 +92,7 @@ void MediaPipelineTest::gstPlayerWillBeCreated() EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_playsink)); EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(&m_pipeline, GST_STATE_READY)) .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectRef(&m_pipeline)).Times(AtMost(1)); // In case of longer testruns, GstPlayer may request to query position and volume EXPECT_CALL(*m_gstWrapperMock, gstStateLock(_)).Times(AtLeast(0)); @@ -108,7 +110,14 @@ void MediaPipelineTest::gstPlayerWillBeCreated() return true; })); - EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("audio-sink"), _)).Times(AtLeast(0)); + EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("audio-sink"), _)) + .WillRepeatedly(Invoke( + [&](gpointer object, const gchar *first_property_name, void *element) + { + GstElement **elementPtr = reinterpret_cast(element); + *elementPtr = m_audioSink; + })); + EXPECT_CALL(*m_gstWrapperMock, gstStreamVolumeGetVolume(_, GST_STREAM_VOLUME_FORMAT_LINEAR)) .Times(AtLeast(0)) .WillRepeatedly(Return(kVolume)); @@ -121,7 +130,7 @@ void MediaPipelineTest::gstPlayerWillBeDestructed() EXPECT_CALL(*m_gstWrapperMock, gstPipelineGetBus(GST_PIPELINE(&m_pipeline))).WillOnce(Return(&m_bus)); EXPECT_CALL(*m_gstWrapperMock, gstBusSetSyncHandler(&m_bus, nullptr, nullptr, nullptr)); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_bus)); - EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_pipeline)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_pipeline)).Times(testing::Between(1, 2)); } void MediaPipelineTest::audioSourceWillBeAttached() @@ -397,25 +406,6 @@ void MediaPipelineTest::willEos(GstAppSrc *appSrc) EXPECT_CALL(*m_gstWrapperMock, gstAppSrcEndOfStream(appSrc)).WillOnce(Return(GST_FLOW_OK)); } -void MediaPipelineTest::willRemoveAudioSource() -{ - EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStart()).WillOnce(Return(&m_flushStartEvent)); - EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&m_audioAppSrc), &m_flushStartEvent)) - .WillOnce(Return(TRUE)); - EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStop(0)).WillOnce(Return(&m_flushStopEvent)); - EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&m_audioAppSrc), &m_flushStopEvent)) - .WillOnce(Return(TRUE)); - - EXPECT_CALL(*m_glibWrapperMock, gTypeFromName(StrEq("GstPlayFlags"))).Times(3).WillRepeatedly(Return(kGstPlayFlagsType)); - EXPECT_CALL(*m_glibWrapperMock, gTypeClassRef(kGstPlayFlagsType)).Times(3).WillRepeatedly(Return(&m_flagsClass)); - EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("video"))).WillOnce(Return(&m_videoFlag)); - EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("native-video"))) - .WillOnce(Return(&m_nativeVideoFlag)); - EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("text"))).WillOnce(Return(&m_subtitleFlag)); - EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(&m_pipeline, StrEq("flags"))) - .WillOnce(Invoke(this, &MediaPipelineTest::workerFinished)); -} - void MediaPipelineTest::willStop() { EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(&m_pipeline, GST_STATE_NULL)) @@ -428,19 +418,6 @@ void MediaPipelineTest::willStop() EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_bus)); } -void MediaPipelineTest::willSetAudioAndVideoFlags() -{ - EXPECT_CALL(*m_glibWrapperMock, gTypeFromName(StrEq("GstPlayFlags"))).Times(4).WillRepeatedly(Return(kGstPlayFlagsType)); - EXPECT_CALL(*m_glibWrapperMock, gTypeClassRef(kGstPlayFlagsType)).Times(4).WillRepeatedly(Return(&m_flagsClass)); - EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryFind(StrEq("brcmaudiosink"))).WillOnce(Return(nullptr)); - EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("audio"))).WillOnce(Return(&m_audioFlag)); - EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("video"))).WillOnce(Return(&m_videoFlag)); - EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("native-video"))) - .WillOnce(Return(&m_nativeVideoFlag)); - EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("text"))).WillOnce(Return(&m_subtitleFlag)); - EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(&m_pipeline, StrEq("flags"))); -} - void MediaPipelineTest::createSession() { // Use matchResponse to store session id @@ -501,28 +478,28 @@ void MediaPipelineTest::setupSource() void MediaPipelineTest::indicateAllSourcesAttached(const std::vector &appsrcs) { ExpectMessage expectedPlaybackStateChange(m_clientStub); - std::map>> expectedNeedDataMap; + std::vector>>> expectedNeedData; for (const GstAppSrc *appSrc : appsrcs) { const int kSourceId = ((appSrc == &m_audioAppSrc) ? m_audioSourceId : m_videoSourceId); auto expectation{std::make_unique>(m_clientStub)}; expectation->setFilter([kSourceId](const firebolt::rialto::NeedMediaDataEvent &msg) { return msg.source_id() == kSourceId; }); - expectedNeedDataMap.emplace(kSourceId, std::move(expectation)); + expectedNeedData.emplace_back(kSourceId, std::move(expectation)); } auto allSourcesAttachedReq{createAllSourcesAttachedRequest(m_sessionId)}; ConfigureAction(m_clientStub).send(allSourcesAttachedReq).expectSuccess(); - for (const auto &[sourceId, expectedNeedData] : expectedNeedDataMap) + for (const auto &[sourceId, expectedNeedDataEntry] : expectedNeedData) { auto &needDataPtr = ((sourceId == m_audioSourceId) ? m_lastAudioNeedData : m_lastVideoNeedData); - auto receivedNeedData{expectedNeedData->getMessage()}; + auto receivedNeedData{expectedNeedDataEntry->getMessage()}; ASSERT_TRUE(receivedNeedData); EXPECT_EQ(receivedNeedData->session_id(), m_sessionId); EXPECT_EQ(receivedNeedData->source_id(), sourceId); - EXPECT_EQ(receivedNeedData->frame_count(), kNeedDataFrameCount); + EXPECT_EQ(receivedNeedData->frame_count(), kPrerollNumFrames); needDataPtr = receivedNeedData; } @@ -534,6 +511,8 @@ void MediaPipelineTest::indicateAllSourcesAttached(const std::vector(m_clientStub).send(pauseReq).expectSuccess(); positionUpdatesShouldNotBeReceivedFromNow(); @@ -555,7 +534,7 @@ void MediaPipelineTest::notifyPaused() ASSERT_TRUE(receivedPlaybackInfo); } -void MediaPipelineTest::pushAudioData(unsigned dataCountToPush) +void MediaPipelineTest::pushAudioData(unsigned dataCountToPush, int needDataFrameCount) { // First, generate new data std::vector> segments(dataCountToPush); @@ -588,11 +567,11 @@ void MediaPipelineTest::pushAudioData(unsigned dataCountToPush) ASSERT_TRUE(receivedNeedData); EXPECT_EQ(receivedNeedData->session_id(), m_sessionId); EXPECT_EQ(receivedNeedData->source_id(), m_audioSourceId); - EXPECT_EQ(receivedNeedData->frame_count(), kNeedDataFrameCount); + EXPECT_EQ(receivedNeedData->frame_count(), needDataFrameCount); m_lastAudioNeedData = receivedNeedData; } -void MediaPipelineTest::pushVideoData(unsigned dataCountToPush) +void MediaPipelineTest::pushVideoData(unsigned dataCountToPush, int needDataFrameCount) { // First, generate new data std::vector> segments(dataCountToPush); @@ -625,11 +604,11 @@ void MediaPipelineTest::pushVideoData(unsigned dataCountToPush) ASSERT_TRUE(receivedNeedData); EXPECT_EQ(receivedNeedData->session_id(), m_sessionId); EXPECT_EQ(receivedNeedData->source_id(), m_videoSourceId); - EXPECT_EQ(receivedNeedData->frame_count(), kNeedDataFrameCount); + EXPECT_EQ(receivedNeedData->frame_count(), needDataFrameCount); m_lastVideoNeedData = receivedNeedData; } -void MediaPipelineTest::pushAudioSample() +void MediaPipelineTest::pushAudioSample(int needDataFrameCount) { // First, generate new data std::unique_ptr segment{SegmentBuilder().basicAudioSegment(m_audioSourceId)()}; @@ -657,11 +636,11 @@ void MediaPipelineTest::pushAudioSample() ASSERT_TRUE(receivedNeedData); EXPECT_EQ(receivedNeedData->session_id(), m_sessionId); EXPECT_EQ(receivedNeedData->source_id(), m_audioSourceId); - EXPECT_EQ(receivedNeedData->frame_count(), kNeedDataFrameCount); + EXPECT_EQ(receivedNeedData->frame_count(), needDataFrameCount); m_lastAudioNeedData = receivedNeedData; } -void MediaPipelineTest::pushVideoSample() +void MediaPipelineTest::pushVideoSample(int needDataFrameCount) { // First, generate new data std::unique_ptr segment{SegmentBuilder().basicVideoSegment(m_videoSourceId)()}; @@ -689,7 +668,7 @@ void MediaPipelineTest::pushVideoSample() ASSERT_TRUE(receivedNeedData); EXPECT_EQ(receivedNeedData->session_id(), m_sessionId); EXPECT_EQ(receivedNeedData->source_id(), m_videoSourceId); - EXPECT_EQ(receivedNeedData->frame_count(), kNeedDataFrameCount); + EXPECT_EQ(receivedNeedData->frame_count(), needDataFrameCount); m_lastVideoNeedData = receivedNeedData; } @@ -809,10 +788,6 @@ void MediaPipelineTest::removeSource(int sourceId) { auto removeSourceReq{createRemoveSourceRequest(m_sessionId, sourceId)}; ConfigureAction(m_clientStub).send(removeSourceReq).expectSuccess(); - - // Sources other than audio do not do anything for RemoveSource - if (m_audioSourceId == sourceId) - waitWorker(); } void MediaPipelineTest::stop() @@ -830,6 +805,7 @@ void MediaPipelineTest::stop() EXPECT_EQ(receivedPlaybackStateChange->state(), ::firebolt::rialto::PlaybackStateChangeEvent_PlaybackState_STOPPED); positionUpdatesShouldNotBeReceivedFromNow(); + playbackInfoUpdatesShouldNotBeReceivedFromNow(); } void MediaPipelineTest::destroySession() @@ -853,11 +829,6 @@ void MediaPipelineTest::mayReceivePositionUpdates() { m_positionChangeEventSuppressionId = m_clientStub.addSuppression(); } - - if (-1 == m_playbackInfoEventSuppressionId) - { - m_playbackInfoEventSuppressionId = m_clientStub.addSuppression(); - } } void MediaPipelineTest::positionUpdatesShouldNotBeReceivedFromNow() @@ -867,7 +838,18 @@ void MediaPipelineTest::positionUpdatesShouldNotBeReceivedFromNow() m_clientStub.removeSuppression(m_positionChangeEventSuppressionId); m_positionChangeEventSuppressionId = -1; } +} + +void MediaPipelineTest::mayReceivePlaybackInfoUpdates() +{ + if (-1 == m_playbackInfoEventSuppressionId) + { + m_playbackInfoEventSuppressionId = m_clientStub.addSuppression(); + } +} +void MediaPipelineTest::playbackInfoUpdatesShouldNotBeReceivedFromNow() +{ if (-1 != m_playbackInfoEventSuppressionId) { m_clientStub.removeSuppression(m_playbackInfoEventSuppressionId); diff --git a/tests/componenttests/server/fixtures/MediaPipelineTest.h b/tests/componenttests/server/fixtures/MediaPipelineTest.h index efe006423..b44e5d298 100644 --- a/tests/componenttests/server/fixtures/MediaPipelineTest.h +++ b/tests/componenttests/server/fixtures/MediaPipelineTest.h @@ -20,6 +20,7 @@ #ifndef FIREBOLT_RIALTO_SERVER_CT_MEDIA_PIPELINE_TEST_H_ #define FIREBOLT_RIALTO_SERVER_CT_MEDIA_PIPELINE_TEST_H_ +#include "Constants.h" #include "GstSrc.h" #include "GstreamerStub.h" #include "IMediaPipeline.h" @@ -57,9 +58,7 @@ class MediaPipelineTest : public RialtoServerComponentTest void willNotifyPaused(); void willPlay(); void willEos(GstAppSrc *appSrc); - void willRemoveAudioSource(); void willStop(); - void willSetAudioAndVideoFlags(); void willSetStateInvalidForQueryPosition(); void createSession(); @@ -70,10 +69,10 @@ class MediaPipelineTest : public RialtoServerComponentTest void indicateAllSourcesAttached(const std::vector &appsrcs); void pause(); void notifyPaused(); - void pushAudioData(unsigned dataCountToPush); - void pushVideoData(unsigned dataCountToPush); - void pushAudioSample(); - void pushVideoSample(); + void pushAudioData(unsigned dataCountToPush, int needDataFrameCount = kPrerollNumFrames); + void pushVideoData(unsigned dataCountToPush, int needDataFrameCount = kPrerollNumFrames); + void pushAudioSample(int needDataFrameCount = kPrerollNumFrames); + void pushVideoSample(int needDataFrameCount = kPrerollNumFrames); void play(); void eosAudio(unsigned dataCountToPush); void eosVideo(unsigned dataCountToPush); @@ -88,6 +87,8 @@ class MediaPipelineTest : public RialtoServerComponentTest void initShm(); void mayReceivePositionUpdates(); void positionUpdatesShouldNotBeReceivedFromNow(); + void mayReceivePlaybackInfoUpdates(); + void playbackInfoUpdatesShouldNotBeReceivedFromNow(); protected: int m_sessionId{-1}; @@ -120,6 +121,7 @@ class MediaPipelineTest : public RialtoServerComponentTest GstSample *m_sample{nullptr}; std::shared_ptr<::firebolt::rialto::NeedMediaDataEvent> m_lastAudioNeedData{nullptr}; std::shared_ptr<::firebolt::rialto::NeedMediaDataEvent> m_lastVideoNeedData{nullptr}; + GstElement *m_audioSink{nullptr}; // Position Update events may be received in PLAYING state. We have to suppress them // to avoid occassional test failures diff --git a/tests/componenttests/server/tests/CMakeLists.txt b/tests/componenttests/server/tests/CMakeLists.txt index 9fe1d15bb..72a5a04e7 100644 --- a/tests/componenttests/server/tests/CMakeLists.txt +++ b/tests/componenttests/server/tests/CMakeLists.txt @@ -56,13 +56,13 @@ add_gtests ( mediaPipeline/PositionUpdatesTest.cpp mediaPipeline/ProcessAudioGapTest.cpp mediaPipeline/QosUpdatesTest.cpp - mediaPipeline/RemoveAudioPlaybackTest.cpp mediaPipeline/RenderFrameTest.cpp mediaPipeline/SetPlaybackRateTest.cpp mediaPipeline/SetPositionTest.cpp mediaPipeline/SetSourcePositionTest.cpp mediaPipeline/SetVideoWindowTest.cpp mediaPipeline/SourceTest.cpp + mediaPipeline/SwitchAudioPlaybackTest.cpp mediaPipeline/UnderflowTest.cpp mediaPipeline/VolumeTest.cpp mediaPipeline/WriteSegmentsTest.cpp diff --git a/tests/componenttests/server/tests/mediaKeys/LicenseRenewalTest.cpp b/tests/componenttests/server/tests/mediaKeys/LicenseRenewalTest.cpp index 68a42b773..49a3249e9 100644 --- a/tests/componenttests/server/tests/mediaKeys/LicenseRenewalTest.cpp +++ b/tests/componenttests/server/tests/mediaKeys/LicenseRenewalTest.cpp @@ -130,12 +130,7 @@ void LicenseRenewalTest::updateOneKey() * Server notifies the client that of license renewal. * Expect that the license renewal notification is processed by the client. * - * Step 2: Update session - * updateSession with the updated license. - * Expect that updateSession is processed by the server. - * Api call returns with success. - * - * Step 3: Notify key statuses changed + * Step 2: Notify key statuses changed * Server notifies the client of key statuses changed. * Expect that the key statuses changed notification is processed by the client. * @@ -149,20 +144,77 @@ void LicenseRenewalTest::updateOneKey() */ TEST_F(LicenseRenewalTest, licenseRenewal) { - createMediaKeysNetflix(); + createMediaKeysWidevine(); ocdmSessionWillBeCreated(); createKeySession(); // Step 1: Notify license renewal licenseRenew(); - // Step 2: Update session + // Step 2: Notify key statuses changed + updateOneKey(); + updateAllKeys(); +} + +/* + * Component Test: License renewal sequence for netflix playready. + * Test Objective: + * Test the notification of license renewal and updating of the new license. + * + * Sequence Diagrams: + * License Renewal - Cobalt/OCDM, Update MKS - Cobalt/OCDM, "Destroy" MKS - Cobalt/OCDM + * - https://wiki.rdkcentral.com/display/ASP/Rialto+Media+Key+Session+Management+Design + * + * Test Setup: + * Language: C++ + * Testing Framework: Google Test + * Components: MediaKeys + * + * Test Initialize: + * RialtoServerComponentTest::RialtoServerComponentTest() will set up wrappers and + * starts rialtoServer running in its own thread + * send a CreateMediaKeys message to rialtoServer + * expect a "createSession" call (to OCDM mock) + * send a CreateKeySession message to rialtoServer + * generate request message for playready and send it to the client + * expect the client to process the generate request message + * + * + * Test Steps: + * Step 1: Update session + * updateSession with the updated license. + * Expect that updateSession is processed by the server. + * Api call returns with success. + * + * Step 2: Close session + * closeSession. + * Expect that closeSession is processed by the server. + * Api call returns with success. + * + * Test Tear-down: + * Server is terminated. + * + * Expected Results: + * Client can be notified of license renewal and update the key session successfully. + * + * Code: + */ +TEST_F(LicenseRenewalTest, licenseRenewalNetflix) +{ + createMediaKeysNetflix(); + ocdmSessionWillBeCreated(); + createKeySession(); + willGenerateRequestPlayready(); + generateRequestPlayready(); + + // Step 1: Update session willUpdateSessionNetflix(); updateSessionNetflix(); - // Step 3: Notify key statuses changed - updateOneKey(); - updateAllKeys(); + // Step 2: Close session + willCloseKeySessionPlayready(); + closeKeySessionPlayready(); + willRelease(); } } // namespace firebolt::rialto::server::ct diff --git a/tests/componenttests/server/tests/mediaKeys/MediaKeysTest.cpp b/tests/componenttests/server/tests/mediaKeys/MediaKeysTest.cpp index c954b8af5..def0de62d 100644 --- a/tests/componenttests/server/tests/mediaKeys/MediaKeysTest.cpp +++ b/tests/componenttests/server/tests/mediaKeys/MediaKeysTest.cpp @@ -42,8 +42,6 @@ class MediaKeysTest : public MediaKeysTestMethods void generateRequestFail(); void shouldFailToCreateKeySessionWhenMksIdIsWrong(); - - const std::vector m_kInitData{1, 2, 7}; }; void MediaKeysTest::willGenerateRequestFail() diff --git a/tests/componenttests/server/tests/mediaKeys/MediaKeysTestMethods.cpp b/tests/componenttests/server/tests/mediaKeys/MediaKeysTestMethods.cpp index 9bfd4e182..b9e91dc4e 100644 --- a/tests/componenttests/server/tests/mediaKeys/MediaKeysTestMethods.cpp +++ b/tests/componenttests/server/tests/mediaKeys/MediaKeysTestMethods.cpp @@ -85,6 +85,69 @@ void MediaKeysTestMethods::ocdmSessionWillBeCreated() })); } +void MediaKeysTestMethods::willGenerateRequestPlayready() +{ + EXPECT_CALL(m_ocdmSessionMock, constructSession(KeySessionType::TEMPORARY, InitDataType::CENC, _, m_kInitData.size())) + .WillOnce(testing::Invoke( + [&](KeySessionType sessionType, InitDataType initDataType, const uint8_t initData[], + uint32_t initDataSize) -> MediaKeyErrorStatus + { + for (uint32_t i = 0; i < initDataSize; ++i) + { + EXPECT_EQ(initData[i], m_kInitData[i]); + } + + return MediaKeyErrorStatus::OK; + })); + + EXPECT_CALL(m_ocdmSessionMock, getChallengeData(false, _, _)) + .WillOnce(testing::Invoke( + [&](bool isLDL, const uint8_t *challenge, uint32_t *challengeSize) -> MediaKeyErrorStatus + { + // This first call asks for the size of the data + EXPECT_EQ(challenge, nullptr); + *challengeSize = m_kLicenseRequestMessage.size(); + return MediaKeyErrorStatus::OK; + })) + .WillOnce(testing::Invoke( + [&](bool isLDL, uint8_t *challenge, const uint32_t *challengeSize) -> MediaKeyErrorStatus + { + // This second call asks for the data + EXPECT_EQ(*challengeSize, m_kLicenseRequestMessage.size()); + for (size_t i = 0; i < m_kLicenseRequestMessage.size(); ++i) + { + challenge[i] = m_kLicenseRequestMessage[i]; + } + return MediaKeyErrorStatus::OK; + })); +} + +void MediaKeysTestMethods::generateRequestPlayready() +{ + constexpr bool kUseExtendedInterface{true}; + auto request{createGenerateRequestRequest(m_mediaKeysHandle, m_mediaKeySessionId, m_kInitData, kUseExtendedInterface)}; + + ExpectMessage<::firebolt::rialto::LicenseRequestEvent> expectedMessage(m_clientStub); + + ConfigureAction(m_clientStub) + .send(request) + .expectSuccess() + .matchResponse([&](const firebolt::rialto::GenerateRequestResponse &resp) + { EXPECT_EQ(resp.error_status(), ProtoMediaKeyErrorStatus::OK); }); + + auto message = expectedMessage.getMessage(); + ASSERT_TRUE(message); + ASSERT_EQ(message->media_keys_handle(), m_mediaKeysHandle); + ASSERT_EQ(message->key_session_id(), m_mediaKeySessionId); + EXPECT_EQ(message->url(), ""); + const unsigned int kMax = message->license_request_message_size(); + ASSERT_EQ(kMax, m_kLicenseRequestMessage.size()); + for (unsigned int i = 0; i < kMax; ++i) + { + ASSERT_EQ(message->license_request_message(i), m_kLicenseRequestMessage[i]); + } +} + void MediaKeysTestMethods::willUpdateSessionNetflix() { EXPECT_CALL(m_ocdmSessionMock, storeLicenseData(_, m_kUpdateSessionNetflixResponse.size())) @@ -109,6 +172,23 @@ void MediaKeysTestMethods::updateSessionNetflix() { EXPECT_EQ(resp.error_status(), ProtoMediaKeyErrorStatus::OK); }); } +void MediaKeysTestMethods::willCloseKeySessionPlayready() +{ + EXPECT_CALL(m_ocdmSessionMock, cancelChallengeData()).WillOnce(Return(MediaKeyErrorStatus::OK)); + EXPECT_CALL(m_ocdmSessionMock, cleanDecryptContext()).WillOnce(Return(MediaKeyErrorStatus::OK)); +} + +void MediaKeysTestMethods::closeKeySessionPlayready() +{ + auto request{createCloseKeySessionRequest(m_mediaKeysHandle, m_mediaKeySessionId)}; + + ConfigureAction(m_clientStub) + .send(request) + .expectSuccess() + .matchResponse([&](const firebolt::rialto::CloseKeySessionResponse &resp) + { EXPECT_EQ(resp.error_status(), ProtoMediaKeyErrorStatus::OK); }); +} + void MediaKeysTestMethods::willTeardown() { // For teardown... diff --git a/tests/componenttests/server/tests/mediaKeys/MediaKeysTestMethods.h b/tests/componenttests/server/tests/mediaKeys/MediaKeysTestMethods.h index 0cfdea24c..65f16b0fb 100644 --- a/tests/componenttests/server/tests/mediaKeys/MediaKeysTestMethods.h +++ b/tests/componenttests/server/tests/mediaKeys/MediaKeysTestMethods.h @@ -41,9 +41,15 @@ class MediaKeysTestMethods : public RialtoServerComponentTest void createKeySession(); void ocdmSessionWillBeCreated(); + void willGenerateRequestPlayready(); + void generateRequestPlayready(); + void willUpdateSessionNetflix(); void updateSessionNetflix(); + void willCloseKeySessionPlayready(); + void closeKeySessionPlayready(); + void willTeardown(); void willRelease(); @@ -59,6 +65,8 @@ class MediaKeysTestMethods : public RialtoServerComponentTest firebolt::rialto::wrappers::IOcdmSessionClient *m_ocdmSessionClient{0}; const std::vector m_kUpdateSessionNetflixResponse{5, 6}; + const std::vector m_kInitData{1, 2, 7}; + const std::vector m_kLicenseRequestMessage{'d', 'z', 'f'}; }; } // namespace firebolt::rialto::server::ct diff --git a/tests/componenttests/server/tests/mediaKeys/SessionReadyForDecryptionTest.cpp b/tests/componenttests/server/tests/mediaKeys/SessionReadyForDecryptionTest.cpp index bd2e0f97f..b98e4802d 100644 --- a/tests/componenttests/server/tests/mediaKeys/SessionReadyForDecryptionTest.cpp +++ b/tests/componenttests/server/tests/mediaKeys/SessionReadyForDecryptionTest.cpp @@ -58,8 +58,6 @@ class SessionReadyForDecryptionTest : public virtual MediaKeysTestMethods void destroyMediaKeysRequest(); const std::vector kResponse{4, 1, 3}; - const std::vector m_kInitData{1, 2, 7}; - const std::vector m_kLicenseRequestMessage{'d', 'z', 'f'}; }; void SessionReadyForDecryptionTest::willGenerateRequestWidevine() @@ -149,7 +147,8 @@ void SessionReadyForDecryptionTest::willGenerateRequestNetflix() void SessionReadyForDecryptionTest::generateRequestNetflix() { - auto request{createGenerateRequestRequest(m_mediaKeysHandle, m_mediaKeySessionId, m_kInitData)}; + constexpr bool kUseExtendedInterface{true}; + auto request{createGenerateRequestRequest(m_mediaKeysHandle, m_mediaKeySessionId, m_kInitData, kUseExtendedInterface)}; ExpectMessage<::firebolt::rialto::LicenseRequestEvent> expectedMessage(m_clientStub); diff --git a/tests/componenttests/server/tests/mediaKeys/SetDrmHeaderTest.cpp b/tests/componenttests/server/tests/mediaKeys/SetDrmHeaderTest.cpp index a8aaa4de1..e9e6ac27d 100644 --- a/tests/componenttests/server/tests/mediaKeys/SetDrmHeaderTest.cpp +++ b/tests/componenttests/server/tests/mediaKeys/SetDrmHeaderTest.cpp @@ -87,16 +87,32 @@ void SetDrmHeaderTest::setDrmHeader(const std::vector &kKeyId) * * * Test Steps: - * Step 1: Set the drm header + * Step 1: generateRequest + * client sends generateRequest message to rialtoServer + * rialtoServer passes request to OCDM library + * ocdm lib returns success + * rialtoServer returns a success message to the client + * + * rialtoServer calls OCDM library get_challenge_data() + * rialtoServer should forward this request, via an onLicenceRequest + * message, to the client. The content of this message should match + * the details from the ocdm library + + * Step 2: Set the drm header * setDrmHeader first header. * Expect that setDrmHeader is processed by the server. * Api call returns with success. * - * Step 2: Set the drm header for a second time with different header + * Step 3: Set the drm header for a second time with different header * setDrmHeader second header. * Expect that setDrmHeader is processed by the server. * Api call returns with success. * + * Step 4: Close session + * closeSession. + * Expect that closeSession is processed by the server. + * Api call returns with success. + * * Test Tear-down: * Server is terminated. * @@ -111,13 +127,22 @@ TEST_F(SetDrmHeaderTest, multiple) ocdmSessionWillBeCreated(); createKeySession(); - // Step 1: Set the drm header + // Step 1: generateRequest + willGenerateRequestPlayready(); + generateRequestPlayready(); + + // Step 2: Set the drm header willSetDrmHeader(kKeyId1); setDrmHeader(kKeyId1); - // Step 2: Set the drm header for a second time with different header + // Step 3: Set the drm header for a second time with different header willSetDrmHeader(kKeyId2); setDrmHeader(kKeyId2); + + // Step 4: Close session + willCloseKeySessionPlayready(); + closeKeySessionPlayready(); + willRelease(); } } // namespace firebolt::rialto::server::ct diff --git a/tests/componenttests/server/tests/mediaPipeline/AudioOnlyPlaybackTest.cpp b/tests/componenttests/server/tests/mediaPipeline/AudioOnlyPlaybackTest.cpp index a53f69159..abbe7431e 100644 --- a/tests/componenttests/server/tests/mediaPipeline/AudioOnlyPlaybackTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/AudioOnlyPlaybackTest.cpp @@ -23,7 +23,7 @@ namespace { constexpr unsigned kFramesToPush{1}; -constexpr int kFrameCountInPausedState{3}; +constexpr int kPrerollNumFrames{3}; constexpr int kFrameCountInPlayingState{24}; } // namespace @@ -175,7 +175,7 @@ TEST_F(MediaPipelineTest, AudioOnlyPlayback) play(); // Step 9: Write 1 audio frame - pushAudioData(kFramesToPush); + pushAudioData(kFramesToPush, kFrameCountInPlayingState); // Step 10: End of audio stream willEos(&m_audioAppSrc); @@ -185,7 +185,6 @@ TEST_F(MediaPipelineTest, AudioOnlyPlayback) gstNotifyEos(); // Step 12: Remove source - willRemoveAudioSource(); removeSource(m_audioSourceId); // Step 13: Stop diff --git a/tests/componenttests/server/tests/mediaPipeline/AudioSourceSwitchTest.cpp b/tests/componenttests/server/tests/mediaPipeline/AudioSourceSwitchTest.cpp index b9710bbf1..31f3411d2 100644 --- a/tests/componenttests/server/tests/mediaPipeline/AudioSourceSwitchTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/AudioSourceSwitchTest.cpp @@ -26,6 +26,8 @@ #include "MessageBuilders.h" using testing::_; +using testing::AtLeast; +using testing::Invoke; using testing::Return; using testing::StrEq; @@ -39,8 +41,15 @@ namespace firebolt::rialto::server::ct class AudioSourceSwitchTest : public MediaPipelineTest { public: - AudioSourceSwitchTest() = default; - ~AudioSourceSwitchTest() = default; + AudioSourceSwitchTest() + { + GstElementFactory *elementFactory = gst_element_factory_find("fakesrc"); + m_audioSink = gst_element_factory_create(elementFactory, nullptr); + EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(m_audioSink))).WillRepeatedly(Return("audio_sink")); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_audioSink)).Times(AtLeast(0)); + gst_object_unref(elementFactory); + } + ~AudioSourceSwitchTest() override { gst_object_unref(m_audioSink); } void willSwitchAudioSource() { @@ -61,6 +70,7 @@ class AudioSourceSwitchTest : public MediaPipelineTest EXPECT_CALL(*m_gstWrapperMock, gstCapsIsEqual(&m_audioCaps, &m_oldCaps)).WillOnce(Return(FALSE)); EXPECT_CALL(*m_gstWrapperMock, gstCapsToString(&m_oldCaps)).WillOnce(Return(&m_oldCapsStr)); EXPECT_CALL(*m_glibWrapperMock, gFree(&m_oldCapsStr)); + EXPECT_CALL(*m_glibWrapperMock, gStrHasPrefix(_, StrEq("amlhalasink"))).WillOnce(Return(FALSE)); EXPECT_CALL(*m_rdkGstreamerUtilsWrapperMock, performAudioTrackCodecChannelSwitch(_, _, _, _, _, _, _, _, _, _, kSvpEnabled, GST_ELEMENT(&m_audioAppSrc), _)) @@ -179,7 +189,6 @@ TEST_F(AudioSourceSwitchTest, SwitchAudioSource) switchAudioSource(); // Step 6: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/DualVideoPlaybackTest.cpp b/tests/componenttests/server/tests/mediaPipeline/DualVideoPlaybackTest.cpp index 7daa9fad3..b92fdc4d0 100644 --- a/tests/componenttests/server/tests/mediaPipeline/DualVideoPlaybackTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/DualVideoPlaybackTest.cpp @@ -42,6 +42,7 @@ const std::string kDummyStateName{"dummy"}; using testing::_; using testing::AtLeast; +using testing::AtMost; using testing::DoAll; using testing::Invoke; using testing::Return; @@ -98,6 +99,7 @@ class DualVideoPlaybackTest : public MediaPipelineTest EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_secondaryPlaysink)); EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(&m_secondaryPipeline, GST_STATE_READY)) .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectRef(&m_secondaryPipeline)).Times(AtMost(1)); // In case of longer testruns, GstPlayer may request to query position EXPECT_CALL(*m_gstWrapperMock, gstElementQueryPosition(&m_secondaryPipeline, GST_FORMAT_TIME, _)) @@ -279,7 +281,7 @@ class DualVideoPlaybackTest : public MediaPipelineTest .WillOnce(Return(&m_secondaryBus)); EXPECT_CALL(*m_gstWrapperMock, gstBusSetSyncHandler(&m_secondaryBus, nullptr, nullptr, nullptr)); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_secondaryBus)); - EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_secondaryPipeline)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_secondaryPipeline)).Times(testing::Between(1, 2)); } void createSecondaryFullSession() @@ -342,7 +344,7 @@ class DualVideoPlaybackTest : public MediaPipelineTest ASSERT_TRUE(receivedNeedData); EXPECT_EQ(receivedNeedData->session_id(), m_secondarySessionId); EXPECT_EQ(receivedNeedData->source_id(), m_secondaryVideoSourceId); - EXPECT_EQ(receivedNeedData->frame_count(), kFrameCountInPlayingState); + EXPECT_EQ(receivedNeedData->frame_count(), kPrerollNumFrames); m_lastSecondaryNeedData = receivedNeedData; auto receivedPlaybackStateChange{expectedPlaybackStateChange.getMessage()}; @@ -687,8 +689,8 @@ TEST_F(DualVideoPlaybackTest, playbackFullDualVideo) { ExpectMessage expectedNetworkStateChange{m_clientStub}; - pushAudioData(kFramesToPush); - pushVideoData(kFramesToPush); + pushAudioData(kFramesToPush, kFrameCountInPlayingState); + pushVideoData(kFramesToPush, kFrameCountInPlayingState); auto receivedNetworkStateChange{expectedNetworkStateChange.getMessage()}; ASSERT_TRUE(receivedNetworkStateChange); @@ -731,7 +733,6 @@ TEST_F(DualVideoPlaybackTest, playbackFullDualVideo) destroySecondarySession(); // Step 16: Terminate the primary media session - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); willStop(); @@ -940,8 +941,8 @@ TEST_F(DualVideoPlaybackTest, playbackNoResouceManagerSecondaryVideo) { ExpectMessage expectedNetworkStateChange{m_clientStub}; - pushAudioData(kFramesToPush); - pushVideoData(kFramesToPush); + pushAudioData(kFramesToPush, kFrameCountInPlayingState); + pushVideoData(kFramesToPush, kFrameCountInPlayingState); auto receivedNetworkStateChange{expectedNetworkStateChange.getMessage()}; ASSERT_TRUE(receivedNetworkStateChange); @@ -984,7 +985,6 @@ TEST_F(DualVideoPlaybackTest, playbackNoResouceManagerSecondaryVideo) destroySecondarySession(); // Step 16: Terminate the primary media session - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); willStop(); diff --git a/tests/componenttests/server/tests/mediaPipeline/EncryptedPlaybackTest.cpp b/tests/componenttests/server/tests/mediaPipeline/EncryptedPlaybackTest.cpp index 89d8f9fae..e6334c454 100644 --- a/tests/componenttests/server/tests/mediaPipeline/EncryptedPlaybackTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/EncryptedPlaybackTest.cpp @@ -29,7 +29,6 @@ namespace { constexpr unsigned kFramesToPush{1}; -constexpr int kFrameCount{24}; } // namespace using testing::_; @@ -300,8 +299,8 @@ TEST_F(EncryptedPlaybackTest, EncryptedPlayback) { ExpectMessage expectedNetworkStateChange{m_clientStub}; - pushEncryptedAudioData(kFrameCount); - pushEncryptedVideoData(kFrameCount); + pushEncryptedAudioData(kPrerollNumFrames); + pushEncryptedVideoData(kPrerollNumFrames); auto receivedNetworkStateChange{expectedNetworkStateChange.getMessage()}; ASSERT_TRUE(receivedNetworkStateChange); @@ -319,8 +318,8 @@ TEST_F(EncryptedPlaybackTest, EncryptedPlayback) // Step 10: Write 1 encrypted audio frame // Step 11: Write 1 encrypted video frame - pushEncryptedAudioData(kFrameCount); - pushEncryptedVideoData(kFrameCount); + pushEncryptedAudioData(kFrameCountInPlayingState); + pushEncryptedVideoData(kFrameCountInPlayingState); // Step 12: End of audio stream // Step 13: End of video stream @@ -331,7 +330,6 @@ TEST_F(EncryptedPlaybackTest, EncryptedPlayback) // Step 14: Notify end of stream gstNotifyEos(); - willRemoveAudioSource(); // Step 15: Remove sources removeSource(m_audioSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/FlushTest.cpp b/tests/componenttests/server/tests/mediaPipeline/FlushTest.cpp index 39ef64847..eefc8c03c 100644 --- a/tests/componenttests/server/tests/mediaPipeline/FlushTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/FlushTest.cpp @@ -34,6 +34,7 @@ constexpr bool kAsync{true}; } // namespace using testing::_; +using testing::AtLeast; using testing::Invoke; using testing::Return; using testing::StrEq; @@ -45,13 +46,14 @@ class FlushTest : public MediaPipelineTest GstEvent m_flushStartEvent{}; GstEvent m_flushStopEvent{}; GstSegment m_segment{}; - GstElement *m_audioSink{nullptr}; public: FlushTest() { GstElementFactory *elementFactory = gst_element_factory_find("fakesrc"); m_audioSink = gst_element_factory_create(elementFactory, nullptr); + EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(m_audioSink))).WillRepeatedly(Return("audio_sink")); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_audioSink)).Times(AtLeast(0)); gst_object_unref(elementFactory); } @@ -59,14 +61,6 @@ class FlushTest : public MediaPipelineTest void willFlush() { - EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("audio-sink"), _)) - .WillOnce(Invoke( - [&](gpointer object, const gchar *first_property_name, void *element) - { - GstElement **elementPtr = reinterpret_cast(element); - *elementPtr = m_audioSink; - })); - EXPECT_CALL(*m_glibWrapperMock, gTypeName(_)).WillRepeatedly(Return("GstStreamVolume")); EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(m_audioSink, StrEq("async"), _)) .WillOnce(Invoke( @@ -75,7 +69,6 @@ class FlushTest : public MediaPipelineTest gboolean *asyncPtr = reinterpret_cast(element); *asyncPtr = static_cast(kAsync); })); - EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_audioSink)); EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStart()).WillOnce(Return(&m_flushStartEvent)); EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&m_audioAppSrc), &m_flushStartEvent)) .WillOnce(Return(true)); @@ -303,7 +296,6 @@ TEST_F(FlushTest, flushAudioSourceSuccess) gstNotifyEos(); // Step 13: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/HaveDataFailureTest.cpp b/tests/componenttests/server/tests/mediaPipeline/HaveDataFailureTest.cpp index 3b2cbdf7b..e96458c3f 100644 --- a/tests/componenttests/server/tests/mediaPipeline/HaveDataFailureTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/HaveDataFailureTest.cpp @@ -26,7 +26,7 @@ namespace { constexpr unsigned kFramesToPush{1}; -constexpr int kFrameCountInPausedState{24}; +constexpr int kTestFrameCount{3}; } // namespace namespace firebolt::rialto::server::ct @@ -50,7 +50,7 @@ class HaveDataFailureTest : public MediaPipelineTest ASSERT_TRUE(receivedNeedData); EXPECT_EQ(receivedNeedData->session_id(), m_sessionId); EXPECT_EQ(receivedNeedData->source_id(), needData->source_id()); - EXPECT_EQ(receivedNeedData->frame_count(), kFrameCountInPausedState); + EXPECT_EQ(receivedNeedData->frame_count(), kTestFrameCount); needData = receivedNeedData; } }; @@ -168,7 +168,6 @@ TEST_F(HaveDataFailureTest, HaveDataError) failHaveData(m_lastVideoNeedData); // Step 14: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/MuteTest.cpp b/tests/componenttests/server/tests/mediaPipeline/MuteTest.cpp index 4f6bebdf6..4f155cd90 100644 --- a/tests/componenttests/server/tests/mediaPipeline/MuteTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/MuteTest.cpp @@ -159,7 +159,6 @@ TEST_F(MuteTest, Mute) getMute(); // Step 7: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/NonFatalPlayerErrorUpdatesTest.cpp b/tests/componenttests/server/tests/mediaPipeline/NonFatalPlayerErrorUpdatesTest.cpp index 233c27409..f448344f4 100644 --- a/tests/componenttests/server/tests/mediaPipeline/NonFatalPlayerErrorUpdatesTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/NonFatalPlayerErrorUpdatesTest.cpp @@ -286,7 +286,6 @@ TEST_F(NonFatalPlayerErrorUpdatesTest, warningMessage) // Step 15: Notify end of stream gstNotifyEos(); - willRemoveAudioSource(); // Step 16: Remove sources removeSource(m_audioSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/PipelinePropertyTest.cpp b/tests/componenttests/server/tests/mediaPipeline/PipelinePropertyTest.cpp index dc42d23ae..e75140fb1 100644 --- a/tests/componenttests/server/tests/mediaPipeline/PipelinePropertyTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/PipelinePropertyTest.cpp @@ -39,6 +39,7 @@ constexpr bool kSyncOff{true}; constexpr int32_t kStreamSyncMode{1}; constexpr int32_t kBufferingLimit{4321}; constexpr bool kUseBuffering{true}; +constexpr int64_t kDuration{434523241}; } // namespace namespace firebolt::rialto::server::ct @@ -215,6 +216,24 @@ class PipelinePropertyTest : public MediaPipelineTest EXPECT_CALL(*m_gstWrapperMock, gstIteratorFree(&m_it)).WillOnce(Invoke(this, &MediaPipelineTest::workerFinished)); } + void willGetDuration() + { + EXPECT_CALL(*m_gstWrapperMock, gstElementQueryDuration(&m_pipeline, GST_FORMAT_TIME, _)) + .WillOnce(Invoke( + [&](GstElement *element, GstFormat format, gint64 *duration) + { + *duration = kDuration; + workerFinished(); + return TRUE; + })); + } + + void willFailToGetDuration() + { + EXPECT_CALL(*m_gstWrapperMock, gstElementQueryDuration(&m_pipeline, GST_FORMAT_TIME, _)) + .WillOnce(DoAll(Invoke(this, &MediaPipelineTest::workerFinished), Return(FALSE))); + } + void setImmediateOutput() { auto req{createSetImmediateOutputRequest(m_sessionId, m_videoSourceId, kImmediateOutput)}; @@ -395,6 +414,23 @@ class PipelinePropertyTest : public MediaPipelineTest ConfigureAction(m_clientStub).send(req).expectFailure(); } + void getDurationSuccess() + { + auto req{createGetDurationRequest(m_sessionId)}; + ConfigureAction(m_clientStub) + .send(req) + .expectSuccess() + .matchResponse([&](const auto &resp) { EXPECT_EQ(resp.duration(), kDuration); }); + waitWorker(); + } + + void getDurationFailure() + { + auto req{createGetDurationRequest(m_sessionId)}; + ConfigureAction(m_clientStub).send(req).expectFailure(); + waitWorker(); + } + private: GstElement *m_element{nullptr}; GstStructure m_testStructure; @@ -488,18 +524,21 @@ class PipelinePropertyTest : public MediaPipelineTest * Step 15: Get Use Buffering * Will get the UseBuffering property of the decodebin on the Rialto Server * - * Step 16: Remove sources + * Step 16: Get Duration + * Will get the duration of the playback on the Rialto Server + * + * Step 17: Remove sources * Remove the audio source. * Expect that audio source is removed. * Remove the video source. * Expect that video source is removed. * - * Step 17: Stop + * Step 18: Stop * Stop the playback. * Expect that stop propagated to the gstreamer pipeline. * Expect that server notifies the client that the Playback state has changed to STOPPED. * - * Step 18: Destroy media session + * Step 19: Destroy media session * Send DestroySessionRequest. * Expect that the session is destroyed on the server. * @@ -579,8 +618,11 @@ TEST_F(PipelinePropertyTest, pipelinePropertyGetAndSetSuccess) // Step 15: Get Use Buffering getUseBuffering(); - // Step 16: Remove sources - willRemoveAudioSource(); + // Step 16: Get Duration + willGetDuration(); + getDurationSuccess(); + + // Step 17: Remove sources removeSource(m_audioSourceId); removeSource(m_videoSourceId); @@ -694,18 +736,22 @@ TEST_F(PipelinePropertyTest, pipelinePropertyGetAndSetSuccess) * Rialto client sends UseBufferingRequest and waits for response * UseBufferingResponse is false because the sessionId is wrong * - * Step 16: Remove sources + * Step 16: Fail to Get Duration + * Rialto client sends GetDurationRequest and waits for response + * GetDurationResponse is false because the server couldn't process it + * + * Step 17: Remove sources * Remove the audio source. * Expect that audio source is removed. * Remove the video source. * Expect that video source is removed. * - * Step 17: Stop + * Step 18: Stop * Stop the playback. * Expect that stop propagated to the gstreamer pipeline. * Expect that server notifies the client that the Playback state has changed to STOPPED. * - * Step 18: Destroy media session + * Step 19: Destroy media session * Send DestroySessionRequest. * Expect that the session is destroyed on the server. * @@ -785,16 +831,19 @@ TEST_F(PipelinePropertyTest, pipelinePropertyGetAndSetFailures) // Step 15: Fail to Set Use Buffering setUseBufferingFailure(); - // Step 16: Remove sources - willRemoveAudioSource(); + // Step 16: Fail to Get Duration + willFailToGetDuration(); + getDurationFailure(); + + // Step 17: Remove sources removeSource(m_audioSourceId); removeSource(m_videoSourceId); - // Step 17: Stop + // Step 18: Stop willStop(); stop(); - // Step 18: Destroy media session + // Step 19: Destroy media session gstPlayerWillBeDestructed(); destroySession(); } diff --git a/tests/componenttests/server/tests/mediaPipeline/PlayPauseStopFailuresTest.cpp b/tests/componenttests/server/tests/mediaPipeline/PlayPauseStopFailuresTest.cpp index d487d4f1c..8c40ab32c 100644 --- a/tests/componenttests/server/tests/mediaPipeline/PlayPauseStopFailuresTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/PlayPauseStopFailuresTest.cpp @@ -28,7 +28,7 @@ using testing::Return; namespace { constexpr unsigned kFramesToPush{1}; -constexpr int kFrameCountInPausedState{3}; +constexpr int kPrerollNumFrames{3}; } // namespace namespace firebolt::rialto::server::ct diff --git a/tests/componenttests/server/tests/mediaPipeline/PlaybackTest.cpp b/tests/componenttests/server/tests/mediaPipeline/PlaybackTest.cpp index c82a0ac8a..1f713c22b 100644 --- a/tests/componenttests/server/tests/mediaPipeline/PlaybackTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/PlaybackTest.cpp @@ -180,8 +180,8 @@ TEST_F(MediaPipelineTest, playback) { ExpectMessage expectedNetworkStateChange{m_clientStub}; - pushAudioData(kFramesToPush); - pushVideoData(kFramesToPush); + pushAudioData(kFramesToPush, kPrerollNumFrames); + pushVideoData(kFramesToPush, kPrerollNumFrames); auto receivedNetworkStateChange{expectedNetworkStateChange.getMessage()}; ASSERT_TRUE(receivedNetworkStateChange); @@ -199,8 +199,8 @@ TEST_F(MediaPipelineTest, playback) // Step 10: Write 1 audio frame // Step 11: Write 1 video frame - pushAudioData(kFramesToPush); - pushVideoData(kFramesToPush); + pushAudioData(kFramesToPush, kFrameCountInPlayingState); + pushVideoData(kFramesToPush, kFrameCountInPlayingState); // Step 12: End of audio stream // Step 13: End of video stream @@ -211,7 +211,6 @@ TEST_F(MediaPipelineTest, playback) // Step 14: Notify end of stream gstNotifyEos(); - willRemoveAudioSource(); // Step 15: Remove sources removeSource(m_audioSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/PositionUpdatesTest.cpp b/tests/componenttests/server/tests/mediaPipeline/PositionUpdatesTest.cpp index e683a603b..f4509e43f 100644 --- a/tests/componenttests/server/tests/mediaPipeline/PositionUpdatesTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/PositionUpdatesTest.cpp @@ -227,8 +227,8 @@ TEST_F(PositionUpdatesTest, PositionUpdate) // Step 9: Write 1 audio frame // Step 10: Write 1 video frame - pushAudioData(kFramesToPush); - pushVideoData(kFramesToPush); + pushAudioData(kFramesToPush, kFrameCountInPlayingState); + pushVideoData(kFramesToPush, kFrameCountInPlayingState); // Step 11: Expect position update waitForPositionUpdate(); @@ -244,7 +244,6 @@ TEST_F(PositionUpdatesTest, PositionUpdate) gstNotifyEos(); // Step 15: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); @@ -424,8 +423,8 @@ TEST_F(PositionUpdatesTest, GetPositionSuccess) // Step 9: Write 1 audio frame // Step 10: Write 1 video frame - pushAudioData(kFramesToPush); - pushVideoData(kFramesToPush); + pushAudioData(kFramesToPush, kFrameCountInPlayingState); + pushVideoData(kFramesToPush, kFrameCountInPlayingState); // Step 11: Get Position getPosition(); @@ -442,7 +441,6 @@ TEST_F(PositionUpdatesTest, GetPositionSuccess) // Step 18: Remove sources // Step 19: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); @@ -548,8 +546,7 @@ TEST_F(PositionUpdatesTest, getPositionFailure) willSetStateInvalidForQueryPosition(); getPositionFailure(); - // Step 7: Remove sources - willRemoveAudioSource(); + // Step 7: Remove sources, kFrameCountInPlayingState removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/QosUpdatesTest.cpp b/tests/componenttests/server/tests/mediaPipeline/QosUpdatesTest.cpp index eefd6a982..f3503df5a 100644 --- a/tests/componenttests/server/tests/mediaPipeline/QosUpdatesTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/QosUpdatesTest.cpp @@ -342,7 +342,6 @@ TEST_F(QosUpdatesTest, QosUpdates) // Step 14: Notify end of stream gstNotifyEos(); - willRemoveAudioSource(); // Step 15: Remove sources removeSource(m_audioSourceId); @@ -526,7 +525,6 @@ TEST_F(QosUpdatesTest, StatsFailure) // Step 12: Notify end of stream gstNotifyEos(); - willRemoveAudioSource(); // Step 13: Remove sources removeSource(m_audioSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/RenderFrameTest.cpp b/tests/componenttests/server/tests/mediaPipeline/RenderFrameTest.cpp index 4c73eeafe..9a8987e05 100644 --- a/tests/componenttests/server/tests/mediaPipeline/RenderFrameTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/RenderFrameTest.cpp @@ -189,7 +189,6 @@ TEST_F(RenderFrameTest, RenderFrameSuccess) renderFrame(); // Step 6: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); @@ -303,7 +302,6 @@ TEST_F(RenderFrameTest, renderFrameFailure) renderFrameFailure(); // Step 6: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/SetPositionTest.cpp b/tests/componenttests/server/tests/mediaPipeline/SetPositionTest.cpp index a93b0d624..26eae1041 100644 --- a/tests/componenttests/server/tests/mediaPipeline/SetPositionTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/SetPositionTest.cpp @@ -29,7 +29,7 @@ namespace constexpr unsigned kFramesToPush{1}; constexpr int kPositionInPaused{10}; constexpr int kPositionInPlaying{0}; -constexpr double kPlaybackRate{1.0}; +constexpr double kSeekPlaybackRate{1.0}; } // namespace using testing::Return; @@ -44,7 +44,7 @@ class SetPositionTest : public MediaPipelineTest void willSetPosition(std::int64_t position) { - EXPECT_CALL(*m_gstWrapperMock, gstElementSeek(&m_pipeline, kPlaybackRate, GST_FORMAT_TIME, + EXPECT_CALL(*m_gstWrapperMock, gstElementSeek(&m_pipeline, kSeekPlaybackRate, GST_FORMAT_TIME, static_cast(GST_SEEK_FLAG_FLUSH), GST_SEEK_TYPE_SET, position, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) .WillOnce(Return(TRUE)); @@ -99,7 +99,7 @@ class SetPositionTest : public MediaPipelineTest void willFailToSetPosition() { - EXPECT_CALL(*m_gstWrapperMock, gstElementSeek(&m_pipeline, kPlaybackRate, GST_FORMAT_TIME, + EXPECT_CALL(*m_gstWrapperMock, gstElementSeek(&m_pipeline, kSeekPlaybackRate, GST_FORMAT_TIME, static_cast(GST_SEEK_FLAG_FLUSH), GST_SEEK_TYPE_SET, kPositionInPaused, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) .WillOnce(Return(FALSE)); @@ -311,7 +311,6 @@ TEST_F(SetPositionTest, SetPosition) gstNotifyEos(); // Step 14: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); @@ -463,7 +462,6 @@ TEST_F(SetPositionTest, SetPositionFailure) SetPositionFailure(); // Step 9: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/SetSourcePositionTest.cpp b/tests/componenttests/server/tests/mediaPipeline/SetSourcePositionTest.cpp index ca5f2c3b0..96f36b0a8 100644 --- a/tests/componenttests/server/tests/mediaPipeline/SetSourcePositionTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/SetSourcePositionTest.cpp @@ -227,7 +227,6 @@ TEST_F(SetSourcePositionTest, setSourcePositionSuccess) gstNotifyEos(); // Step 13: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/SetVideoWindowTest.cpp b/tests/componenttests/server/tests/mediaPipeline/SetVideoWindowTest.cpp index 326842175..66e66bf1e 100644 --- a/tests/componenttests/server/tests/mediaPipeline/SetVideoWindowTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/SetVideoWindowTest.cpp @@ -173,7 +173,6 @@ TEST_F(SetVideoWindowTest, SetVideoWindow) setVideoWindow(); // Step 6: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/SourceTest.cpp b/tests/componenttests/server/tests/mediaPipeline/SourceTest.cpp index dfa103706..a956bed92 100644 --- a/tests/componenttests/server/tests/mediaPipeline/SourceTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/SourceTest.cpp @@ -99,7 +99,6 @@ TEST_F(MediaPipelineTest, shouldAttachAudioSourceOnly) indicateAllSourcesAttached({&m_audioAppSrc}); // Step 4: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); // Step 5: Stop @@ -196,7 +195,6 @@ TEST_F(MediaPipelineTest, shouldAttachBothSources) indicateAllSourcesAttached({&m_audioAppSrc, &m_videoAppSrc}); // Step 4: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/RemoveAudioPlaybackTest.cpp b/tests/componenttests/server/tests/mediaPipeline/SwitchAudioPlaybackTest.cpp similarity index 57% rename from tests/componenttests/server/tests/mediaPipeline/RemoveAudioPlaybackTest.cpp rename to tests/componenttests/server/tests/mediaPipeline/SwitchAudioPlaybackTest.cpp index f5d613c95..1959300c1 100644 --- a/tests/componenttests/server/tests/mediaPipeline/RemoveAudioPlaybackTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/SwitchAudioPlaybackTest.cpp @@ -17,71 +17,110 @@ * limitations under the License. */ +#include "ActionTraits.h" +#include "ConfigureAction.h" #include "Constants.h" #include "ExpectMessage.h" #include "Matchers.h" #include "MediaPipelineTest.h" +#include "MessageBuilders.h" using testing::_; +using testing::Invoke; using testing::Return; using testing::StrEq; namespace { constexpr int kFramesToPush{3}; +constexpr bool kResetTime{false}; +constexpr bool kAsync{true}; } // namespace namespace firebolt::rialto::server::ct { -class RemoveAudioPlaybackTest : public MediaPipelineTest +class SwitchAudioPlaybackTest : public MediaPipelineTest { public: - RemoveAudioPlaybackTest() = default; - ~RemoveAudioPlaybackTest() = default; + SwitchAudioPlaybackTest() = default; + ~SwitchAudioPlaybackTest() = default; - void willReattachAudioSource() + void willFlushAudioSource() { - EXPECT_CALL(*m_gstWrapperMock, gstCapsNewEmptySimple(StrEq("audio/mpeg"))).WillOnce(Return(&m_audioCaps)); + EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStart()).WillOnce(Return(&m_flushStartEvent)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&m_audioAppSrc), &m_flushStartEvent)) + .WillOnce(Return(true)); + EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStop(kResetTime)).WillOnce(Return(&m_flushStopEvent)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&m_audioAppSrc), &m_flushStopEvent)) + .WillOnce(Return(true)); + } + + void flushAudioSource() + { + // After successful Flush procedure, SourceFlushedEvent is sent. + ExpectMessage expectedSourceFlushed{m_clientStub}; + expectedSourceFlushed.setFilter([&](const SourceFlushedEvent &event) + { return event.source_id() == m_audioSourceId; }); + + // After successful Flush, NeedData for source is sent. + ExpectMessage expectedAudioNeedData{m_clientStub}; + expectedAudioNeedData.setFilter([&](const NeedMediaDataEvent &event) + { return event.source_id() == m_audioSourceId; }); + + // Send FlushRequest and expect success + auto request{createFlushRequest(m_sessionId, m_audioSourceId, kResetTime)}; + ConfigureAction(m_clientStub) + .send(request) + .expectSuccess() + .matchResponse([&](const FlushResponse &response) { EXPECT_EQ(kAsync, response.async()); }); + + // Check received SourceFlushedEvent events + auto receivedSourceFlushed{expectedSourceFlushed.getMessage()}; + ASSERT_TRUE(receivedSourceFlushed); + EXPECT_EQ(receivedSourceFlushed->session_id(), m_sessionId); + EXPECT_EQ(receivedSourceFlushed->source_id(), m_audioSourceId); + + // Check received NeedDataReqs + auto receivedAudioNeedData{expectedAudioNeedData.getMessage()}; + ASSERT_TRUE(receivedAudioNeedData); + EXPECT_EQ(receivedAudioNeedData->session_id(), m_sessionId); + EXPECT_EQ(receivedAudioNeedData->source_id(), m_audioSourceId); + m_lastAudioNeedData = receivedAudioNeedData; + } + + void willSwitchAudioSource() + { + EXPECT_CALL(*m_gstWrapperMock, gstCapsNewEmptySimple(StrEq("audio/mpeg"))).WillOnce(Return(&m_newCaps)); EXPECT_CALL(*m_gstWrapperMock, - gstCapsSetSimpleStringStub(&m_audioCaps, StrEq("alignment"), G_TYPE_STRING, StrEq("nal"))); + gstCapsSetSimpleStringStub(&m_newCaps, StrEq("alignment"), G_TYPE_STRING, StrEq("nal"))); EXPECT_CALL(*m_gstWrapperMock, - gstCapsSetSimpleStringStub(&m_audioCaps, StrEq("stream-format"), G_TYPE_STRING, StrEq("raw"))); - EXPECT_CALL(*m_gstWrapperMock, gstCapsSetSimpleIntStub(&m_audioCaps, StrEq("mpegversion"), G_TYPE_INT, 4)); + gstCapsSetSimpleStringStub(&m_newCaps, StrEq("stream-format"), G_TYPE_STRING, StrEq("raw"))); + EXPECT_CALL(*m_gstWrapperMock, gstCapsSetSimpleIntStub(&m_newCaps, StrEq("mpegversion"), G_TYPE_INT, 4)); EXPECT_CALL(*m_gstWrapperMock, - gstCapsSetSimpleIntStub(&m_audioCaps, StrEq("channels"), G_TYPE_INT, kNumOfChannels)); - EXPECT_CALL(*m_gstWrapperMock, gstCapsSetSimpleIntStub(&m_audioCaps, StrEq("rate"), G_TYPE_INT, kSampleRate)); - EXPECT_CALL(*m_gstWrapperMock, gstAppSrcGetCaps(&m_audioAppSrc)).WillOnce(Return(&m_oldCaps)); - EXPECT_CALL(*m_gstWrapperMock, gstCapsIsEqual(&m_audioCaps, &m_oldCaps)).WillOnce(Return(TRUE)); - EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&m_oldCaps)); + gstCapsSetSimpleIntStub(&m_newCaps, StrEq("channels"), G_TYPE_INT, kNumOfChannels)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsSetSimpleIntStub(&m_newCaps, StrEq("rate"), G_TYPE_INT, kSampleRate)); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcGetCaps(&m_audioAppSrc)).WillOnce(Return(&m_audioCaps)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsIsEqual(&m_newCaps, &m_audioCaps)).WillOnce(Return(true)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&m_newCaps)); EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&m_audioCaps)).WillOnce(Invoke(this, &MediaPipelineTest::workerFinished)); - - willSetAudioAndVideoFlags(); } - void reattachAudioSource() + void switchAudioSource() { - ExpectMessage expectedNeedData{m_clientStub}; - - attachAudioSource(); - - auto receivedNeedData{expectedNeedData.getMessage()}; - ASSERT_TRUE(receivedNeedData); - EXPECT_EQ(receivedNeedData->session_id(), m_sessionId); - EXPECT_EQ(receivedNeedData->source_id(), m_audioSourceId); - EXPECT_EQ(receivedNeedData->frame_count(), 24); - m_lastAudioNeedData = receivedNeedData; + auto attachAudioSourceReq{createAttachAudioSourceRequest(m_sessionId)}; + attachAudioSourceReq.set_switch_source(true); + ConfigureAction(m_clientStub).send(attachAudioSourceReq).expectSuccess(); + waitWorker(); } private: - GstCaps m_oldCaps{}; - gchar m_oldCapsStr{}; + GstCaps m_newCaps{}; }; /* - * Component Test: Playback content when audio source has been removed and reattached. + * Component Test: Playback content when audio source has been switched. * Test Objective: - * Test that video only playback can continue if the audio source is removed, and that audio can be restarted - * when it is reattached. + * Test that audio source can be switched mid playback and that video playback is unaffected. * * Sequence Diagrams: * Rialto Dynamic Audio Stream Switching @@ -138,49 +177,35 @@ class RemoveAudioPlaybackTest : public MediaPipelineTest * Expect that gstreamer pipeline is paused. * Expect that server notifies the client that the Network state has changed to PAUSED. * - * Step 8: Remove Audio Source - * Remove the audio source. - * Expect that audio source is removed. + * Step 8: Flush Audio Source + * Flush the audio source. + * Expect that audio source is flushed. * - * Step 9: Write video frames - * Write video frames. + * Step 9: Switch Audio Source + * Switch the audio source. + * Expect that audio source is switched. * - * Step 10: Play - * Play the content. - * Expect that gstreamer pipeline is playing. - * Expect that server notifies the client that the Network state has changed to PLAYING. - * - * Step 11: Pause - * Pause the content. - * Expect that gstreamer pipeline is paused. - * Expect that server notifies the client that the Network state has changed to PAUSED. - * - * Step 12: Reattach audio source - * Attach the audio source again. - * Expect that reattach procedure is triggered. - * Expect that audio source is attached. - * - * Step 13: Write video and audio frames + * Step 10: Write video and audio frames * Write video frames. * Write audio frames. * - * Step 14: Play + * Step 11: Play * Play the content. * Expect that gstreamer pipeline is playing. * Expect that server notifies the client that the Network state has changed to PLAYING. * - * Step 15: Remove sources + * Step 12: Remove sources * Remove the audio source. * Expect that audio source is removed. * Remove the video source. * Expect that video source is removed. * - * Step 16: Stop + * Step 13: Stop * Stop the playback. * Expect that stop propagated to the gstreamer pipeline. * Expect that server notifies the client that the Playback state has changed to STOPPED. * - * Step 17: Destroy media session + * Step 14: Destroy media session * Send DestroySessionRequest. * Expect that the session is destroyed on the server. * @@ -194,7 +219,7 @@ class RemoveAudioPlaybackTest : public MediaPipelineTest * * Code: */ -TEST_F(RemoveAudioPlaybackTest, RemoveAudio) +TEST_F(SwitchAudioPlaybackTest, SwitchAudio) { // Step 1: Create a new media session createSession(); @@ -243,46 +268,33 @@ TEST_F(RemoveAudioPlaybackTest, RemoveAudio) pause(); willNotifyPaused(); notifyPaused(); + GST_STATE(&m_pipeline) = GST_STATE_PAUSED; - // Step 8: Remove Audio Source - willRemoveAudioSource(); - removeSource(m_audioSourceId); - - // Step 9: Write video frames - pushVideoData(kFramesToPush); - - // Step 10: Play - willPlay(); - play(); - - // Step 11: Pause - willPause(); - pause(); - willNotifyPaused(); - notifyPaused(); + // Step 8: Flush Audio Source + willFlushAudioSource(); + flushAudioSource(); - // Step 12: Reattach audio source - willReattachAudioSource(); - reattachAudioSource(); + // Step 9: Switch Audio Source + willSwitchAudioSource(); + switchAudioSource(); - // Step 13: Write video and audio frames + // Step 10: Write video and audio frames pushAudioData(kFramesToPush); pushVideoData(kFramesToPush); - // Step 14: Play + // Step 11: Play willPlay(); play(); - // Step 15: Remove sources - willRemoveAudioSource(); + // Step 12: Remove sources removeSource(m_audioSourceId); removeSource(m_videoSourceId); - // Step 16: Stop + // Step 13: Stop willStop(); stop(); - // Step 17: Destroy media session + // Step 14: Destroy media session gstPlayerWillBeDestructed(); destroySession(); } diff --git a/tests/componenttests/server/tests/mediaPipeline/UnderflowTest.cpp b/tests/componenttests/server/tests/mediaPipeline/UnderflowTest.cpp index 00eb3d6b6..ed58398c2 100644 --- a/tests/componenttests/server/tests/mediaPipeline/UnderflowTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/UnderflowTest.cpp @@ -400,7 +400,6 @@ TEST_F(UnderflowTest, underflow) // Step 15: Notify end of stream gstNotifyEos(); - willRemoveAudioSource(); // Step 16: Remove sources removeSource(m_audioSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/VolumeTest.cpp b/tests/componenttests/server/tests/mediaPipeline/VolumeTest.cpp index 80df4d42b..d54ee116a 100644 --- a/tests/componenttests/server/tests/mediaPipeline/VolumeTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/VolumeTest.cpp @@ -253,7 +253,6 @@ TEST_F(VolumeTest, Volume) getVolume(); // Step 9: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/WriteSegmentsTest.cpp b/tests/componenttests/server/tests/mediaPipeline/WriteSegmentsTest.cpp index 15b0015a1..2c8658313 100644 --- a/tests/componenttests/server/tests/mediaPipeline/WriteSegmentsTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/WriteSegmentsTest.cpp @@ -194,8 +194,8 @@ TEST_F(MediaPipelineTest, WriteSegments) // Step 9: Write 3 audio frames // Step 10: Write 3 video frames - pushAudioData(kFramesToPushBeforePreroll); - pushVideoData(kFramesToPushBeforePreroll); + pushAudioData(kFramesToPushBeforePreroll, kFrameCountInPlayingState); + pushVideoData(kFramesToPushBeforePreroll, kFrameCountInPlayingState); // Step 11: Send 4 frames and end of audio stream // Step 12: Send 4 frames and end of video stream @@ -206,7 +206,6 @@ TEST_F(MediaPipelineTest, WriteSegments) // Step 13: Notify end of stream gstNotifyEos(); - willRemoveAudioSource(); // Step 14: Remove sources removeSource(m_audioSourceId); diff --git a/tests/unittests/common/mocks/ProfilerFactoryMock.h b/tests/unittests/common/mocks/ProfilerFactoryMock.h new file mode 100644 index 000000000..9f46b47d9 --- /dev/null +++ b/tests/unittests/common/mocks/ProfilerFactoryMock.h @@ -0,0 +1,40 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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. + */ + +#ifndef FIREBOLT_RIALTO_COMMON_PROFILER_FACTORY_MOCK_H_ +#define FIREBOLT_RIALTO_COMMON_PROFILER_FACTORY_MOCK_H_ + +#include "IProfiler.h" +#include +#include +#include + +namespace firebolt::rialto::common +{ +class ProfilerFactoryMock : public IProfilerFactory +{ +public: + ProfilerFactoryMock() = default; + virtual ~ProfilerFactoryMock() = default; + + MOCK_METHOD(std::unique_ptr, createProfiler, (std::string moduleName), (const, override)); +}; +} // namespace firebolt::rialto::common + +#endif // FIREBOLT_RIALTO_COMMON_PROFILER_FACTORY_MOCK_H_ diff --git a/tests/unittests/common/mocks/ProfilerMock.h b/tests/unittests/common/mocks/ProfilerMock.h new file mode 100644 index 000000000..a8872a150 --- /dev/null +++ b/tests/unittests/common/mocks/ProfilerMock.h @@ -0,0 +1,51 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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. + */ + +#ifndef FIREBOLT_RIALTO_COMMON_PROFILER_MOCK_H_ +#define FIREBOLT_RIALTO_COMMON_PROFILER_MOCK_H_ + +#include "IProfiler.h" + +#include +#include +#include +#include + +namespace firebolt::rialto::common +{ +class ProfilerMock : public IProfiler +{ +public: + ProfilerMock() = default; + ~ProfilerMock() override = default; + + MOCK_METHOD(bool, isEnabled, (), (const, noexcept, override)); + + MOCK_METHOD(std::optional, record, (const std::string &stage), (override)); + MOCK_METHOD(std::optional, record, (const std::string &stage, const std::string &info), (override)); + + MOCK_METHOD(void, log, (RecordId id), (override)); + + MOCK_METHOD(bool, dumpToFile, (), (const, override)); + + MOCK_METHOD(std::vector, getRecords, (), (const, override)); +}; +} // namespace firebolt::rialto::common + +#endif // FIREBOLT_RIALTO_COMMON_PROFILER_MOCK_H_ diff --git a/tests/unittests/common/unittests/CMakeLists.txt b/tests/unittests/common/unittests/CMakeLists.txt index b9d1ab2a8..b7977dfe4 100644 --- a/tests/unittests/common/unittests/CMakeLists.txt +++ b/tests/unittests/common/unittests/CMakeLists.txt @@ -28,6 +28,7 @@ add_gtests ( # gtest code TimerTests.cpp EventThreadTests.cpp + ProfilerTests.cpp ) target_include_directories( diff --git a/tests/unittests/common/unittests/ProfilerTests.cpp b/tests/unittests/common/unittests/ProfilerTests.cpp new file mode 100644 index 000000000..10ee1cbd5 --- /dev/null +++ b/tests/unittests/common/unittests/ProfilerTests.cpp @@ -0,0 +1,261 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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. + */ + +#include "IProfiler.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace firebolt::rialto::common; + +namespace +{ +std::optional findRecord(const std::vector &records, const std::string &stage, + const std::optional &info = std::nullopt) +{ + const auto it = std::find_if(records.begin(), records.end(), [&](const auto &record) + { return record.stage == stage && (!info.has_value() || record.info == info.value()); }); + + if (it != records.end()) + { + return *it; + } + return std::nullopt; +} +} // namespace + +class ProfilerTests : public ::testing::Test +{ +protected: + void SetUp() override + { + saveEnv("PROFILER_ENABLED", m_originalProfilerEnabled); + saveEnv("PROFILER_DUMP_FILE_NAME", m_originalProfilerDumpFileName); + setenv("PROFILER_ENABLED", "true", 1); + + factory = IProfilerFactory::createFactory(); + ASSERT_TRUE(factory); + + profiler = factory->createProfiler("UnitTestModule"); + ASSERT_TRUE(profiler); + ASSERT_TRUE(profiler->isEnabled()); + } + + void TearDown() override + { + restoreEnv("PROFILER_ENABLED", m_originalProfilerEnabled); + restoreEnv("PROFILER_DUMP_FILE_NAME", m_originalProfilerDumpFileName); + } + + static void saveEnv(const char *name, std::optional &value) + { + const char *envValue = std::getenv(name); + if (envValue) + value = envValue; + else + value = std::nullopt; + } + + static void restoreEnv(const char *name, const std::optional &value) + { + if (value) + setenv(name, value->c_str(), 1); + else + unsetenv(name); + } + + std::shared_ptr factory; + std::unique_ptr profiler; + std::optional m_originalProfilerEnabled; + std::optional m_originalProfilerDumpFileName; +}; + +TEST_F(ProfilerTests, RecordAndFindByStage) +{ + const auto id = profiler->record("Stage1"); + ASSERT_TRUE(id.has_value()); + + const auto found = findRecord(profiler->getRecords(), "Stage1"); + ASSERT_TRUE(found.has_value()); + + EXPECT_EQ(found->id, id.value()); +} + +TEST_F(ProfilerTests, RecordAndFindByStageAndInfo) +{ + const auto id = profiler->record("Stage1", "InfoA"); + ASSERT_TRUE(id.has_value()); + + const auto found = findRecord(profiler->getRecords(), "Stage1", "InfoA"); + ASSERT_TRUE(found.has_value()); + EXPECT_EQ(found->id, id.value()); + + EXPECT_FALSE(findRecord(profiler->getRecords(), "Stage1", "InfoB").has_value()); +} + +TEST_F(ProfilerTests, GetRecordsReturnsRecordedEntries) +{ + const auto id1 = profiler->record("Stage1"); + ASSERT_TRUE(id1.has_value()); + + const auto id2 = profiler->record("Stage2", "Info2"); + ASSERT_TRUE(id2.has_value()); + + const auto records = profiler->getRecords(); + ASSERT_GE(records.size(), 2U); + + const auto &record1 = records[records.size() - 2]; + EXPECT_EQ(record1.moduleName, "UnitTestModule"); + EXPECT_EQ(record1.id, id1.value()); + EXPECT_EQ(record1.stage, "Stage1"); + EXPECT_TRUE(record1.info.empty()); + + const auto &record2 = records[records.size() - 1]; + EXPECT_EQ(record2.moduleName, "UnitTestModule"); + EXPECT_EQ(record2.id, id2.value()); + EXPECT_EQ(record2.stage, "Stage2"); + EXPECT_EQ(record2.info, "Info2"); +} + +TEST_F(ProfilerTests, DumpCreatesFile) +{ + (void)profiler->record("StageDump", "InfoDump"); + const auto suffix = std::to_string(std::random_device{}()); + const std::string path = std::string{"/tmp/rialto_profiler_ut_dump_"} + suffix + ".txt"; + setenv("PROFILER_DUMP_FILE_NAME", path.c_str(), 1); + + auto dumpProfiler = factory->createProfiler("UnitTestModule"); + ASSERT_TRUE(dumpProfiler); + ASSERT_TRUE(dumpProfiler->record("StageDump", "InfoDump").has_value()); + ASSERT_TRUE(dumpProfiler->dumpToFile()); + + std::ifstream in(path); + ASSERT_TRUE(in.good()); + + const std::string content((std::istreambuf_iterator(in)), std::istreambuf_iterator()); + EXPECT_FALSE(content.empty()); + EXPECT_NE(content.find("StageDump"), std::string::npos); + + std::remove(path.c_str()); +} + +TEST_F(ProfilerTests, DumpAppendsToExistingFile) +{ + const auto suffix = std::to_string(std::random_device{}()); + const std::string path = std::string{"/tmp/rialto_profiler_ut_append_"} + suffix + ".txt"; + setenv("PROFILER_DUMP_FILE_NAME", path.c_str(), 1); + + auto dumpProfiler = factory->createProfiler("UnitTestModule"); + ASSERT_TRUE(dumpProfiler); + ASSERT_TRUE(dumpProfiler->record("Stage1").has_value()); + ASSERT_TRUE(dumpProfiler->dumpToFile()); + + ASSERT_TRUE(dumpProfiler->record("Stage2").has_value()); + ASSERT_TRUE(dumpProfiler->dumpToFile()); + + std::ifstream in(path); + ASSERT_TRUE(in.good()); + + const std::string content((std::istreambuf_iterator(in)), std::istreambuf_iterator()); + EXPECT_NE(content.find("Stage1"), std::string::npos); + EXPECT_NE(content.find("Stage2"), std::string::npos); + EXPECT_LT(content.find("Stage1"), content.rfind("Stage2")); + + std::remove(path.c_str()); +} + +TEST_F(ProfilerTests, DumpToFileUsesCachedEnvValue) +{ + const auto suffix = std::to_string(std::random_device{}()); + const std::string path = std::string{"/tmp/rialto_profiler_ut_configured_"} + suffix + ".txt"; + setenv("PROFILER_DUMP_FILE_NAME", path.c_str(), 1); + + auto configuredProfiler = factory->createProfiler("UnitTestModule"); + ASSERT_TRUE(configuredProfiler); + ASSERT_TRUE(configuredProfiler->record("StageConfigured").has_value()); + ASSERT_TRUE(configuredProfiler->dumpToFile()); + + unsetenv("PROFILER_DUMP_FILE_NAME"); + ASSERT_TRUE(configuredProfiler->record("StageConfiguredCached").has_value()); + ASSERT_TRUE(configuredProfiler->dumpToFile()); + + std::ifstream in(path); + ASSERT_TRUE(in.good()); + + const std::string content((std::istreambuf_iterator(in)), std::istreambuf_iterator()); + EXPECT_NE(content.find("StageConfigured"), std::string::npos); + EXPECT_NE(content.find("StageConfiguredCached"), std::string::npos); + + std::remove(path.c_str()); +} + +TEST_F(ProfilerTests, StartsEnabledWhenEnvTrue) +{ + setenv("PROFILER_ENABLED", "true", 1); + + auto envProfiler = factory->createProfiler("UnitTestModule"); + + ASSERT_TRUE(envProfiler); + EXPECT_TRUE(envProfiler->isEnabled()); +} + +TEST_F(ProfilerTests, StartsDisabledWhenEnvValueInvalid) +{ + setenv("PROFILER_ENABLED", "definitely-not-a-bool", 1); + + auto envProfiler = factory->createProfiler("UnitTestModule"); + + ASSERT_TRUE(envProfiler); + EXPECT_FALSE(envProfiler->isEnabled()); +} + +TEST_F(ProfilerTests, StartsDisabledWhenEnvFalse) +{ + setenv("PROFILER_ENABLED", "false", 1); + + auto envProfiler = factory->createProfiler("UnitTestModule"); + + ASSERT_TRUE(envProfiler); + EXPECT_FALSE(envProfiler->isEnabled()); +} + +TEST_F(ProfilerTests, GetRecordsDoesNotContainMissingStage) +{ + ASSERT_TRUE(profiler->record("Stage1").has_value()); + + EXPECT_FALSE(findRecord(profiler->getRecords(), "MissingStage").has_value()); +} + +TEST_F(ProfilerTests, DumpToFileReturnsFalseForInvalidPath) +{ + setenv("PROFILER_DUMP_FILE_NAME", "/proc/rialto_profiler_ut_dump.txt", 1); + auto dumpProfiler = factory->createProfiler("UnitTestModule"); + ASSERT_TRUE(dumpProfiler); + EXPECT_FALSE(dumpProfiler->dumpToFile()); +} + +TEST_F(ProfilerTests, DumpToFileReturnsFalseWhenEnvMissing) +{ + EXPECT_FALSE(profiler->dumpToFile()); +} diff --git a/tests/unittests/common/unittests/TimerTests.cpp b/tests/unittests/common/unittests/TimerTests.cpp index f6f8aa1bb..0825e16ee 100644 --- a/tests/unittests/common/unittests/TimerTests.cpp +++ b/tests/unittests/common/unittests/TimerTests.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include "ITimer.h" @@ -64,6 +65,33 @@ TEST(TimerTests, ShouldCancelTimer) EXPECT_FALSE(callFlag); } +TEST(TimerTests, ShouldCancelFromSameThread) +{ + std::mutex mtx; + std::condition_variable cv; + bool cancelled = false; + + std::shared_ptr timer; + timer = ITimerFactory::getFactory()->createTimer( + std::chrono::milliseconds{50}, + [&]() + { + timer->cancel(); + { + std::lock_guard lock{mtx}; + cancelled = true; + } + cv.notify_one(); + }, + TimerType::ONE_SHOT); + + std::unique_lock lock{mtx}; + cv.wait_for(lock, kEnoughTimeForTestToComplete, [&] { return cancelled; }); + + EXPECT_TRUE(cancelled); + EXPECT_FALSE(timer->isActive()); +} + TEST(TimerTests, ShouldTimeoutPeriodicTimer) { std::mutex mtx; @@ -88,3 +116,30 @@ TEST(TimerTests, ShouldTimeoutPeriodicTimer) EXPECT_GE(callCounter, 3); } } + +TEST(TimerTests, ShouldCancelPeriodicTimerInCallback) +{ + std::mutex mtx; + { // This scope is required to suppress a false warning from cppcheck (about the mutex above) + std::condition_variable cv; + std::unique_lock lock{mtx}; + unsigned callCounter{0}; + std::unique_ptr timer{ITimerFactory::getFactory()->createTimer( + std::chrono::milliseconds{30}, + [&]() + { + std::unique_lock lock{mtx}; + ++callCounter; + if (callCounter >= 3) + { + timer->cancel(); + cv.notify_one(); + } + }, + TimerType::PERIODIC)}; + EXPECT_TRUE(timer->isActive()); + cv.wait_for(lock, kEnoughTimeForTestToComplete); + std::this_thread::sleep_for(std::chrono::milliseconds{30}); + EXPECT_EQ(callCounter, 3); + } +} diff --git a/tests/unittests/ipc/mocks/IpcServerMock.h b/tests/unittests/ipc/mocks/IpcServerMock.h index d0509ea1b..140ac9339 100644 --- a/tests/unittests/ipc/mocks/IpcServerMock.h +++ b/tests/unittests/ipc/mocks/IpcServerMock.h @@ -34,12 +34,13 @@ class ServerMock : public IServer virtual ~ServerMock() = default; MOCK_METHOD(bool, addSocket, - (const std::string &socketPath, std::function &)> clientConnectedCb, - std::function &)> clientDisconnectedCb), + (const std::string &socketPath, + const std::function &)> &clientConnectedCb, + const std::function &)> &clientDisconnectedCb), (override)); MOCK_METHOD(bool, addSocket, - (int fd, std::function &)> clientConnectedCb, - std::function &)> clientDisconnectedCb), + (int fd, const std::function &)> &clientConnectedCb, + const std::function &)> &clientDisconnectedCb), (override)); MOCK_METHOD(std::shared_ptr, addClient, (int socketFd, std::function &)> clientDisconnectedCb), (override)); diff --git a/tests/unittests/media/client/ipc/CMakeLists.txt b/tests/unittests/media/client/ipc/CMakeLists.txt index c4018151c..a86efde21 100644 --- a/tests/unittests/media/client/ipc/CMakeLists.txt +++ b/tests/unittests/media/client/ipc/CMakeLists.txt @@ -35,6 +35,7 @@ add_gtests ( mediaPipelineIpc/SetPositionTest.cpp mediaPipelineIpc/SetPlaybackRateTest.cpp mediaPipelineIpc/GetPositionTest.cpp + mediaPipelineIpc/GetDurationTest.cpp mediaPipelineIpc/SetImmediateOutputTest.cpp mediaPipelineIpc/GetImmediateOutputTest.cpp mediaPipelineIpc/GetStatsTest.cpp diff --git a/tests/unittests/media/client/ipc/mediaKeysIpc/CreateKeySessionTest.cpp b/tests/unittests/media/client/ipc/mediaKeysIpc/CreateKeySessionTest.cpp index 527f55d4e..c19048ad2 100644 --- a/tests/unittests/media/client/ipc/mediaKeysIpc/CreateKeySessionTest.cpp +++ b/tests/unittests/media/client/ipc/mediaKeysIpc/CreateKeySessionTest.cpp @@ -25,7 +25,6 @@ class RialtoClientMediaKeysIpcCreateKeySessionTest : public MediaKeysIpcTestBase { protected: KeySessionType m_keySessionType = KeySessionType::PERSISTENT_LICENCE; - bool m_isLdl = false; RialtoClientMediaKeysIpcCreateKeySessionTest() { createMediaKeysIpc(); } @@ -51,13 +50,12 @@ TEST_F(RialtoClientMediaKeysIpcCreateKeySessionTest, Success) EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("createKeySession"), m_controllerMock.get(), - createKeySessionRequestMatcher(m_mediaKeysHandle, convertKeySessionType(m_keySessionType), - m_isLdl), + createKeySessionRequestMatcher(m_mediaKeysHandle, convertKeySessionType(m_keySessionType)), _, m_blockingClosureMock.get())) .WillOnce(WithArgs<3>( Invoke(this, &RialtoClientMediaKeysIpcCreateKeySessionTest::setCreateKeySessionResponseSuccess))); - EXPECT_EQ(m_mediaKeysIpc->createKeySession(m_keySessionType, m_mediaKeysClientMock, m_isLdl, returnKeySessionid), + EXPECT_EQ(m_mediaKeysIpc->createKeySession(m_keySessionType, m_mediaKeysClientMock, returnKeySessionid), MediaKeyErrorStatus::OK); EXPECT_EQ(returnKeySessionid, m_kKeySessionId); @@ -76,7 +74,7 @@ TEST_F(RialtoClientMediaKeysIpcCreateKeySessionTest, ChannelDisconnected) expectIpcApiCallDisconnected(); expectUnsubscribeEvents(); - EXPECT_EQ(m_mediaKeysIpc->createKeySession(m_keySessionType, m_mediaKeysClientMock, m_isLdl, returnKeySessionid), + EXPECT_EQ(m_mediaKeysIpc->createKeySession(m_keySessionType, m_mediaKeysClientMock, returnKeySessionid), MediaKeyErrorStatus::FAIL); // Reattach channel on destroySession @@ -98,7 +96,7 @@ TEST_F(RialtoClientMediaKeysIpcCreateKeySessionTest, ReconnectChannel) .WillOnce(WithArgs<3>( Invoke(this, &RialtoClientMediaKeysIpcCreateKeySessionTest::setCreateKeySessionResponseSuccess))); - EXPECT_EQ(m_mediaKeysIpc->createKeySession(m_keySessionType, m_mediaKeysClientMock, m_isLdl, returnKeySessionid), + EXPECT_EQ(m_mediaKeysIpc->createKeySession(m_keySessionType, m_mediaKeysClientMock, returnKeySessionid), MediaKeyErrorStatus::OK); } @@ -112,7 +110,7 @@ TEST_F(RialtoClientMediaKeysIpcCreateKeySessionTest, Failure) EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("createKeySession"), _, _, _, _)); - EXPECT_EQ(m_mediaKeysIpc->createKeySession(m_keySessionType, m_mediaKeysClientMock, m_isLdl, returnKeySessionid), + EXPECT_EQ(m_mediaKeysIpc->createKeySession(m_keySessionType, m_mediaKeysClientMock, returnKeySessionid), MediaKeyErrorStatus::FAIL); } @@ -128,6 +126,6 @@ TEST_F(RialtoClientMediaKeysIpcCreateKeySessionTest, ErrorReturn) .WillOnce( WithArgs<3>(Invoke(this, &RialtoClientMediaKeysIpcCreateKeySessionTest::setCreateKeySessionResponseFailed))); - EXPECT_EQ(m_mediaKeysIpc->createKeySession(m_keySessionType, m_mediaKeysClientMock, m_isLdl, returnKeySessionid), + EXPECT_EQ(m_mediaKeysIpc->createKeySession(m_keySessionType, m_mediaKeysClientMock, returnKeySessionid), m_errorStatus); } diff --git a/tests/unittests/media/client/ipc/mediaKeysIpc/base/MediaKeysIpcTestBase.cpp b/tests/unittests/media/client/ipc/mediaKeysIpc/base/MediaKeysIpcTestBase.cpp index e4de26e0d..c0ad54e56 100644 --- a/tests/unittests/media/client/ipc/mediaKeysIpc/base/MediaKeysIpcTestBase.cpp +++ b/tests/unittests/media/client/ipc/mediaKeysIpc/base/MediaKeysIpcTestBase.cpp @@ -103,7 +103,7 @@ void MediaKeysIpcTestBase::createKeySession() EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("createKeySession"), _, _, _, _)) .WillOnce(WithArgs<3>(Invoke(this, &MediaKeysIpcTestBase::setCreateKeySessionResponseSuccess))); - EXPECT_EQ(m_mediaKeysIpc->createKeySession(KeySessionType::PERSISTENT_LICENCE, m_mediaKeysClientMock, false, + EXPECT_EQ(m_mediaKeysIpc->createKeySession(KeySessionType::PERSISTENT_LICENCE, m_mediaKeysClientMock, returnKeySessionid), MediaKeyErrorStatus::OK); } diff --git a/tests/unittests/media/client/ipc/mediaPipelineIpc/GetDurationTest.cpp b/tests/unittests/media/client/ipc/mediaPipelineIpc/GetDurationTest.cpp new file mode 100644 index 000000000..12d83b993 --- /dev/null +++ b/tests/unittests/media/client/ipc/mediaPipelineIpc/GetDurationTest.cpp @@ -0,0 +1,97 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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. + */ + +#include "MediaPipelineIpcTestBase.h" +#include "MediaPipelineProtoRequestMatchers.h" + +class RialtoClientMediaPipelineIpcGetDurationTest : public MediaPipelineIpcTestBase +{ +protected: + virtual void SetUp() + { + MediaPipelineIpcTestBase::SetUp(); + + createMediaPipelineIpc(); + } + + virtual void TearDown() + { + destroyMediaPipelineIpc(); + + MediaPipelineIpcTestBase::TearDown(); + } +}; + +/** + * Test that getDuration can be called successfully. + */ +TEST_F(RialtoClientMediaPipelineIpcGetDurationTest, Success) +{ + expectIpcApiCallSuccess(); + + EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("getDuration"), m_controllerMock.get(), + getDurationRequestMatcher(m_sessionId), _, m_blockingClosureMock.get())); + + int64_t duration; + EXPECT_TRUE(m_mediaPipelineIpc->getDuration(duration)); +} + +/** + * Test that getDuration fails if the ipc channel disconnected. + */ +TEST_F(RialtoClientMediaPipelineIpcGetDurationTest, ChannelDisconnected) +{ + expectIpcApiCallDisconnected(); + expectUnsubscribeEvents(); + + int64_t duration; + EXPECT_FALSE(m_mediaPipelineIpc->getDuration(duration)); + + // Reattach channel on destroySession + EXPECT_CALL(*m_ipcClientMock, getChannel()).WillOnce(Return(m_channelMock)).RetiresOnSaturation(); + expectSubscribeEvents(); +} + +/** + * Test that getDuration fails if the ipc channel disconnected and succeeds if the channel is reconnected. + */ +TEST_F(RialtoClientMediaPipelineIpcGetDurationTest, ReconnectChannel) +{ + expectIpcApiCallReconnected(); + expectUnsubscribeEvents(); + expectSubscribeEvents(); + + EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("getDuration"), _, _, _, _)); + + int64_t duration; + EXPECT_TRUE(m_mediaPipelineIpc->getDuration(duration)); +} + +/** + * Test that getDuration fails when ipc fails. + */ +TEST_F(RialtoClientMediaPipelineIpcGetDurationTest, GetDurationFailure) +{ + expectIpcApiCallFailure(); + + EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("getDuration"), _, _, _, _)); + + int64_t duration; + EXPECT_FALSE(m_mediaPipelineIpc->getDuration(duration)); +} diff --git a/tests/unittests/media/client/ipc/mediaPipelineIpc/LoadTest.cpp b/tests/unittests/media/client/ipc/mediaPipelineIpc/LoadTest.cpp index cd2a96e34..e8cbccbcd 100644 --- a/tests/unittests/media/client/ipc/mediaPipelineIpc/LoadTest.cpp +++ b/tests/unittests/media/client/ipc/mediaPipelineIpc/LoadTest.cpp @@ -27,6 +27,7 @@ class RialtoClientMediaPipelineIpcLoadTest : public MediaPipelineIpcTestBase firebolt::rialto::LoadRequest_MediaType m_protoType = firebolt::rialto::LoadRequest_MediaType_MSE; const std::string m_mimeType = "mime"; const std::string m_url = "mse://1"; + const bool m_isLive = true; virtual void SetUp() { @@ -51,10 +52,10 @@ TEST_F(RialtoClientMediaPipelineIpcLoadTest, Success) expectIpcApiCallSuccess(); EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("load"), m_controllerMock.get(), - loadRequestMatcher(m_sessionId, m_protoType, m_mimeType, m_url), _, + loadRequestMatcher(m_sessionId, m_protoType, m_mimeType, m_url, m_isLive), _, m_blockingClosureMock.get())); - EXPECT_EQ(m_mediaPipelineIpc->load(m_type, m_mimeType, m_url), true); + EXPECT_EQ(m_mediaPipelineIpc->load(m_type, m_mimeType, m_url, m_isLive), true); } /** @@ -65,7 +66,7 @@ TEST_F(RialtoClientMediaPipelineIpcLoadTest, ChannelDisconnected) expectIpcApiCallDisconnected(); expectUnsubscribeEvents(); - EXPECT_EQ(m_mediaPipelineIpc->load(m_type, m_mimeType, m_url), false); + EXPECT_EQ(m_mediaPipelineIpc->load(m_type, m_mimeType, m_url, m_isLive), false); // Reattach channel on destroySession EXPECT_CALL(*m_ipcClientMock, getChannel()).WillOnce(Return(m_channelMock)).RetiresOnSaturation(); @@ -83,7 +84,7 @@ TEST_F(RialtoClientMediaPipelineIpcLoadTest, ReconnectChannel) EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("load"), _, _, _, _)); - EXPECT_EQ(m_mediaPipelineIpc->load(m_type, m_mimeType, m_url), true); + EXPECT_EQ(m_mediaPipelineIpc->load(m_type, m_mimeType, m_url, m_isLive), true); } /** @@ -95,5 +96,5 @@ TEST_F(RialtoClientMediaPipelineIpcLoadTest, LoadFailure) EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("load"), _, _, _, _)); - EXPECT_EQ(m_mediaPipelineIpc->load(m_type, m_mimeType, m_url), false); + EXPECT_EQ(m_mediaPipelineIpc->load(m_type, m_mimeType, m_url, m_isLive), false); } diff --git a/tests/unittests/media/client/ipc/mediaPipelineIpc/PlayPauseTest.cpp b/tests/unittests/media/client/ipc/mediaPipelineIpc/PlayPauseTest.cpp index 2db4edf0b..dec195144 100644 --- a/tests/unittests/media/client/ipc/mediaPipelineIpc/PlayPauseTest.cpp +++ b/tests/unittests/media/client/ipc/mediaPipelineIpc/PlayPauseTest.cpp @@ -43,12 +43,13 @@ class RialtoClientMediaPipelineIpcPlayPauseTest : public MediaPipelineIpcTestBas */ TEST_F(RialtoClientMediaPipelineIpcPlayPauseTest, PlaySuccess) { + bool async{false}; expectIpcApiCallSuccess(); EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("play"), m_controllerMock.get(), playRequestMatcher(m_sessionId), _, m_blockingClosureMock.get())); - EXPECT_EQ(m_mediaPipelineIpc->play(), true); + EXPECT_EQ(m_mediaPipelineIpc->play(async), true); } /** @@ -56,10 +57,11 @@ TEST_F(RialtoClientMediaPipelineIpcPlayPauseTest, PlaySuccess) */ TEST_F(RialtoClientMediaPipelineIpcPlayPauseTest, PlayChannelDisconnected) { + bool async{false}; expectIpcApiCallDisconnected(); expectUnsubscribeEvents(); - EXPECT_EQ(m_mediaPipelineIpc->play(), false); + EXPECT_EQ(m_mediaPipelineIpc->play(async), false); // Reattach channel on destroySession EXPECT_CALL(*m_ipcClientMock, getChannel()).WillOnce(Return(m_channelMock)).RetiresOnSaturation(); @@ -71,13 +73,14 @@ TEST_F(RialtoClientMediaPipelineIpcPlayPauseTest, PlayChannelDisconnected) */ TEST_F(RialtoClientMediaPipelineIpcPlayPauseTest, PlayReconnectChannel) { + bool async{false}; expectIpcApiCallReconnected(); expectUnsubscribeEvents(); expectSubscribeEvents(); EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("play"), _, _, _, _)); - EXPECT_EQ(m_mediaPipelineIpc->play(), true); + EXPECT_EQ(m_mediaPipelineIpc->play(async), true); } /** @@ -85,11 +88,12 @@ TEST_F(RialtoClientMediaPipelineIpcPlayPauseTest, PlayReconnectChannel) */ TEST_F(RialtoClientMediaPipelineIpcPlayPauseTest, PlayFailure) { + bool async{false}; expectIpcApiCallFailure(); EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("play"), _, _, _, _)); - EXPECT_EQ(m_mediaPipelineIpc->play(), false); + EXPECT_EQ(m_mediaPipelineIpc->play(async), false); } /** diff --git a/tests/unittests/media/client/main/CMakeLists.txt b/tests/unittests/media/client/main/CMakeLists.txt index 17a5b0d95..7518c2e2a 100644 --- a/tests/unittests/media/client/main/CMakeLists.txt +++ b/tests/unittests/media/client/main/CMakeLists.txt @@ -32,6 +32,7 @@ add_gtests ( mediaPipeline/SetPositionTest.cpp mediaPipeline/SetPlaybackRateTest.cpp mediaPipeline/GetPositionTest.cpp + mediaPipeline/GetDurationTest.cpp mediaPipeline/SetImmediateOutputTest.cpp mediaPipeline/GetImmediateOutputTest.cpp mediaPipeline/GetStatsTest.cpp diff --git a/tests/unittests/media/client/main/mediaKeys/KeySessionTest.cpp b/tests/unittests/media/client/main/mediaKeys/KeySessionTest.cpp index b4040ac2c..91f501c43 100644 --- a/tests/unittests/media/client/main/mediaKeys/KeySessionTest.cpp +++ b/tests/unittests/media/client/main/mediaKeys/KeySessionTest.cpp @@ -65,14 +65,12 @@ TEST_F(RialtoClientMediaKeysKeySessionTest, CreateKeySession) KeySessionType sessionType = KeySessionType::PERSISTENT_LICENCE; std::shared_ptr> mediaKeysClientMock = std::make_shared>(); - bool isLDL = false; int32_t returnKeySessionId; - EXPECT_CALL(*m_mediaKeysIpcMock, createKeySession(sessionType, _, isLDL, _)) - .WillOnce(DoAll(SetArgReferee<3>(m_kKeySessionId), Return(m_mediaKeyErrorStatus))); + EXPECT_CALL(*m_mediaKeysIpcMock, createKeySession(sessionType, _, _)) + .WillOnce(DoAll(SetArgReferee<2>(m_kKeySessionId), Return(m_mediaKeyErrorStatus))); - EXPECT_EQ(m_mediaKeys->createKeySession(sessionType, mediaKeysClientMock, isLDL, returnKeySessionId), - m_mediaKeyErrorStatus); + EXPECT_EQ(m_mediaKeys->createKeySession(sessionType, mediaKeysClientMock, returnKeySessionId), m_mediaKeyErrorStatus); EXPECT_EQ(returnKeySessionId, m_kKeySessionId); } @@ -83,11 +81,12 @@ TEST_F(RialtoClientMediaKeysKeySessionTest, GenerateRequest) { InitDataType initDataType = InitDataType::KEY_IDS; std::vector initData{7, 8, 9}; + LimitedDurationLicense ldlState{LimitedDurationLicense::NOT_SPECIFIED}; - EXPECT_CALL(*m_mediaKeysIpcMock, generateRequest(m_kKeySessionId, initDataType, initData)) + EXPECT_CALL(*m_mediaKeysIpcMock, generateRequest(m_kKeySessionId, initDataType, initData, ldlState)) .WillOnce(Return(m_mediaKeyErrorStatus)); - EXPECT_EQ(m_mediaKeys->generateRequest(m_kKeySessionId, initDataType, initData), m_mediaKeyErrorStatus); + EXPECT_EQ(m_mediaKeys->generateRequest(m_kKeySessionId, initDataType, initData, ldlState), m_mediaKeyErrorStatus); } /** diff --git a/tests/unittests/media/client/main/mediaKeys/NetflixKeySessionTest.cpp b/tests/unittests/media/client/main/mediaKeys/NetflixKeySessionTest.cpp index ee2001606..16548c9cf 100644 --- a/tests/unittests/media/client/main/mediaKeys/NetflixKeySessionTest.cpp +++ b/tests/unittests/media/client/main/mediaKeys/NetflixKeySessionTest.cpp @@ -69,14 +69,12 @@ TEST_F(RialtoClientMediaKeysNetflixKeySessionTest, CreateKeySession) KeySessionType sessionType = KeySessionType::PERSISTENT_LICENCE; std::shared_ptr> mediaKeysClientMock = std::make_shared>(); - bool isLDL = false; int32_t returnKeySessionId; - EXPECT_CALL(*m_mediaKeysIpcMock, createKeySession(sessionType, _, isLDL, _)) - .WillOnce(DoAll(SetArgReferee<3>(m_keySessionId), Return(m_mediaKeyErrorStatus))); + EXPECT_CALL(*m_mediaKeysIpcMock, createKeySession(sessionType, _, _)) + .WillOnce(DoAll(SetArgReferee<2>(m_keySessionId), Return(m_mediaKeyErrorStatus))); - EXPECT_EQ(m_mediaKeys->createKeySession(sessionType, mediaKeysClientMock, isLDL, returnKeySessionId), - m_mediaKeyErrorStatus); + EXPECT_EQ(m_mediaKeys->createKeySession(sessionType, mediaKeysClientMock, returnKeySessionId), m_mediaKeyErrorStatus); EXPECT_EQ(returnKeySessionId, m_keySessionId); // Update key should be possible, as keySession should be present in KeyIdMap EXPECT_TRUE(KeyIdMap::instance().updateKey(m_keySessionId, m_keyId)); diff --git a/tests/unittests/media/client/main/mediaPipeline/GetDurationTest.cpp b/tests/unittests/media/client/main/mediaPipeline/GetDurationTest.cpp new file mode 100644 index 000000000..64122f833 --- /dev/null +++ b/tests/unittests/media/client/main/mediaPipeline/GetDurationTest.cpp @@ -0,0 +1,66 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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. + */ + +#include "MediaPipelineTestBase.h" + +class RialtoClientMediaPipelineGetDurationTest : public MediaPipelineTestBase +{ +protected: + virtual void SetUp() + { + MediaPipelineTestBase::SetUp(); + + createMediaPipeline(); + } + + virtual void TearDown() + { + destroyMediaPipeline(); + + MediaPipelineTestBase::TearDown(); + } +}; + +/** + * Test that GetDuration returns success if the IPC API succeeds. + */ +TEST_F(RialtoClientMediaPipelineGetDurationTest, GetDurationSuccess) +{ + constexpr int64_t kExpectedDuration{123}; + int64_t resultDuration{}; + EXPECT_CALL(*m_mediaPipelineIpcMock, getDuration(resultDuration)) + .WillOnce(Invoke( + [&](int64_t &duration) + { + duration = kExpectedDuration; + return true; + })); + EXPECT_TRUE(m_mediaPipeline->getDuration(resultDuration)); + EXPECT_EQ(resultDuration, kExpectedDuration); +} + +/** + * Test that GetDuration returns failure if the IPC API fails. + */ +TEST_F(RialtoClientMediaPipelineGetDurationTest, GetDurationFailure) +{ + int64_t resultDuration{}; + EXPECT_CALL(*m_mediaPipelineIpcMock, getDuration(resultDuration)).WillOnce(Return(false)); + EXPECT_FALSE(m_mediaPipeline->getDuration(resultDuration)); +} diff --git a/tests/unittests/media/client/main/mediaPipeline/LoadTest.cpp b/tests/unittests/media/client/main/mediaPipeline/LoadTest.cpp index 977168d82..7d2486d65 100644 --- a/tests/unittests/media/client/main/mediaPipeline/LoadTest.cpp +++ b/tests/unittests/media/client/main/mediaPipeline/LoadTest.cpp @@ -25,6 +25,7 @@ class RialtoClientMediaPipelineLoadTest : public MediaPipelineTestBase MediaType m_type = MediaType::MSE; const std::string m_kMimeType = "mime"; const std::string m_kUrl = "mse://1"; + const bool m_kIsLive = false; virtual void SetUp() { @@ -46,9 +47,9 @@ class RialtoClientMediaPipelineLoadTest : public MediaPipelineTestBase */ TEST_F(RialtoClientMediaPipelineLoadTest, Success) { - EXPECT_CALL(*m_mediaPipelineIpcMock, load(m_type, m_kMimeType, m_kUrl)).WillOnce(Return(true)); + EXPECT_CALL(*m_mediaPipelineIpcMock, load(m_type, m_kMimeType, m_kUrl, m_kIsLive)).WillOnce(Return(true)); - EXPECT_EQ(m_mediaPipeline->load(m_type, m_kMimeType, m_kUrl), true); + EXPECT_EQ(m_mediaPipeline->load(m_type, m_kMimeType, m_kUrl, m_kIsLive), true); } /** @@ -56,7 +57,7 @@ TEST_F(RialtoClientMediaPipelineLoadTest, Success) */ TEST_F(RialtoClientMediaPipelineLoadTest, Failure) { - EXPECT_CALL(*m_mediaPipelineIpcMock, load(m_type, m_kMimeType, m_kUrl)).WillOnce(Return(false)); + EXPECT_CALL(*m_mediaPipelineIpcMock, load(m_type, m_kMimeType, m_kUrl, m_kIsLive)).WillOnce(Return(false)); - EXPECT_EQ(m_mediaPipeline->load(m_type, m_kMimeType, m_kUrl), false); + EXPECT_EQ(m_mediaPipeline->load(m_type, m_kMimeType, m_kUrl, m_kIsLive), false); } diff --git a/tests/unittests/media/client/main/mediaPipeline/MediaPipelineProxyTest.cpp b/tests/unittests/media/client/main/mediaPipeline/MediaPipelineProxyTest.cpp index be98cad21..ec5e31896 100644 --- a/tests/unittests/media/client/main/mediaPipeline/MediaPipelineProxyTest.cpp +++ b/tests/unittests/media/client/main/mediaPipeline/MediaPipelineProxyTest.cpp @@ -77,11 +77,12 @@ TEST_F(RialtoClientMediaPipelineProxyTest, TestPassthrough) constexpr bool kEnabled{true}; constexpr uint32_t kBufferingLimit{5326}; constexpr uint64_t kStopPosition{4234}; + constexpr bool kIsLive{false}; ///////////////////////////////////////////// - EXPECT_CALL(*mediaPipelineMock, load(MediaType::MSE, StrEq(kMimeType), StrEq(kUrl))).WillOnce(Return(true)); - EXPECT_TRUE(proxy->load(MediaType::MSE, kMimeType, kUrl)); + EXPECT_CALL(*mediaPipelineMock, load(MediaType::MSE, StrEq(kMimeType), StrEq(kUrl), kIsLive)).WillOnce(Return(true)); + EXPECT_TRUE(proxy->load(MediaType::MSE, kMimeType, kUrl, kIsLive)); ///////////////////////////////////////////// @@ -100,8 +101,9 @@ TEST_F(RialtoClientMediaPipelineProxyTest, TestPassthrough) ///////////////////////////////////////////// - EXPECT_CALL(*mediaPipelineMock, play()).WillOnce(Return(true)); - EXPECT_TRUE(proxy->play()); + bool async{false}; + EXPECT_CALL(*mediaPipelineMock, play(_)).WillOnce(Return(true)); + EXPECT_TRUE(proxy->play(async)); ///////////////////////////////////////////// @@ -134,6 +136,15 @@ TEST_F(RialtoClientMediaPipelineProxyTest, TestPassthrough) ///////////////////////////////////////////// + EXPECT_CALL(*mediaPipelineMock, getDuration(_)).WillOnce(DoAll(SetArgReferee<0>(kDuration), Return(true))); + { + int64_t duration; + EXPECT_TRUE(proxy->getDuration(duration)); + EXPECT_EQ(duration, kDuration); + } + + ///////////////////////////////////////////// + EXPECT_CALL(*mediaPipelineMock, getStats(_, _, _)) .WillOnce(DoAll(SetArgReferee<1>(kRenderedFrames), SetArgReferee<2>(kDroppedFrames), Return(true))); { diff --git a/tests/unittests/media/client/main/mediaPipeline/PlayPauseTest.cpp b/tests/unittests/media/client/main/mediaPipeline/PlayPauseTest.cpp index a58b80891..8c78c550f 100644 --- a/tests/unittests/media/client/main/mediaPipeline/PlayPauseTest.cpp +++ b/tests/unittests/media/client/main/mediaPipeline/PlayPauseTest.cpp @@ -42,9 +42,10 @@ class RialtoClientMediaPipelinePlayPauseTest : public MediaPipelineTestBase */ TEST_F(RialtoClientMediaPipelinePlayPauseTest, PlaySuccess) { - EXPECT_CALL(*m_mediaPipelineIpcMock, play()).WillOnce(Return(true)); + bool async{false}; + EXPECT_CALL(*m_mediaPipelineIpcMock, play(_)).WillOnce(Return(true)); - EXPECT_EQ(m_mediaPipeline->play(), true); + EXPECT_EQ(m_mediaPipeline->play(async), true); } /** @@ -52,9 +53,10 @@ TEST_F(RialtoClientMediaPipelinePlayPauseTest, PlaySuccess) */ TEST_F(RialtoClientMediaPipelinePlayPauseTest, PlayFailure) { - EXPECT_CALL(*m_mediaPipelineIpcMock, play()).WillOnce(Return(false)); + bool async{false}; + EXPECT_CALL(*m_mediaPipelineIpcMock, play(_)).WillOnce(Return(false)); - EXPECT_EQ(m_mediaPipeline->play(), false); + EXPECT_EQ(m_mediaPipeline->play(async), false); } /** diff --git a/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcMock.h b/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcMock.h index ed99bfb8e..b955fce2a 100644 --- a/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcMock.h +++ b/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcMock.h @@ -37,9 +37,10 @@ class MediaPipelineIpcMock : public IMediaPipelineIpc (override)); MOCK_METHOD(bool, removeSource, (int32_t sourceId), (override)); MOCK_METHOD(bool, allSourcesAttached, (), (override)); - MOCK_METHOD(bool, load, (MediaType type, const std::string &mimeType, const std::string &url), (override)); + MOCK_METHOD(bool, load, (MediaType type, const std::string &mimeType, const std::string &url, bool isLive), + (override)); MOCK_METHOD(bool, setVideoWindow, (uint32_t x, uint32_t y, uint32_t width, uint32_t height), (override)); - MOCK_METHOD(bool, play, (), (override)); + MOCK_METHOD(bool, play, (bool &async), (override)); MOCK_METHOD(bool, pause, (), (override)); MOCK_METHOD(bool, stop, (), (override)); MOCK_METHOD(bool, haveData, (MediaSourceStatus status, uint32_t numFrames, uint32_t requestId), (override)); @@ -74,6 +75,7 @@ class MediaPipelineIpcMock : public IMediaPipelineIpc MOCK_METHOD(bool, setUseBuffering, (bool useBuffering), (override)); MOCK_METHOD(bool, getUseBuffering, (bool &useBuffering), (override)); MOCK_METHOD(bool, switchSource, (const std::unique_ptr &source), (override)); + MOCK_METHOD(bool, getDuration, (int64_t & duration), (override)); }; } // namespace firebolt::rialto::client diff --git a/tests/unittests/media/client/mocks/main/MediaPipelineAndControlClientMock.h b/tests/unittests/media/client/mocks/main/MediaPipelineAndControlClientMock.h index 0d1427fd4..54349ba06 100644 --- a/tests/unittests/media/client/mocks/main/MediaPipelineAndControlClientMock.h +++ b/tests/unittests/media/client/mocks/main/MediaPipelineAndControlClientMock.h @@ -31,7 +31,8 @@ namespace firebolt::rialto::client class MediaPipelineAndControlClientMock : public IMediaPipelineAndIControlClient { public: - MOCK_METHOD(bool, load, (MediaType type, const std::string &mimeType, const std::string &url), (override)); + MOCK_METHOD(bool, load, (MediaType type, const std::string &mimeType, const std::string &url, bool isLive), + (override)); MOCK_METHOD(bool, attachSource, (const std::unique_ptr &source), (override)); @@ -39,7 +40,7 @@ class MediaPipelineAndControlClientMock : public IMediaPipelineAndIControlClient MOCK_METHOD(bool, allSourcesAttached, (), (override)); - MOCK_METHOD(bool, play, (), (override)); + MOCK_METHOD(bool, play, (bool &async), (override)); MOCK_METHOD(bool, pause, (), (override)); MOCK_METHOD(bool, stop, (), (override)); @@ -89,6 +90,7 @@ class MediaPipelineAndControlClientMock : public IMediaPipelineAndIControlClient MOCK_METHOD(bool, setUseBuffering, (bool useBuffering), (override)); MOCK_METHOD(bool, getUseBuffering, (bool &useBuffering), (override)); MOCK_METHOD(bool, switchSource, (const std::unique_ptr &source), (override)); + MOCK_METHOD(bool, getDuration, (int64_t & duration), (override)); }; } // namespace firebolt::rialto::client diff --git a/tests/unittests/media/interface/mocks/MediaKeysMock.h b/tests/unittests/media/interface/mocks/MediaKeysMock.h index 450fd0197..14d3a6171 100644 --- a/tests/unittests/media/interface/mocks/MediaKeysMock.h +++ b/tests/unittests/media/interface/mocks/MediaKeysMock.h @@ -37,10 +37,11 @@ class MediaKeysMock : public IMediaKeys MOCK_METHOD(MediaKeyErrorStatus, selectKeyId, (int32_t keySessionId, const std::vector &keyId), (override)); MOCK_METHOD(bool, containsKey, (int32_t keySessionId, const std::vector &keyId), (override)); MOCK_METHOD(MediaKeyErrorStatus, createKeySession, - (KeySessionType sessionType, std::weak_ptr client, bool isLDL, int32_t &keySessionId), - (override)); + (KeySessionType sessionType, std::weak_ptr client, int32_t &keySessionId), (override)); MOCK_METHOD(MediaKeyErrorStatus, generateRequest, - (int32_t keySessionId, InitDataType initDataType, const std::vector &initData), (override)); + (int32_t keySessionId, InitDataType initDataType, const std::vector &initData, + const LimitedDurationLicense &ldlState), + (override)); MOCK_METHOD(MediaKeyErrorStatus, loadSession, (int32_t keySessionId), (override)); MOCK_METHOD(MediaKeyErrorStatus, updateSession, (int32_t keySessionId, const std::vector &responseData), (override)); diff --git a/tests/unittests/media/server/gstplayer/CMakeLists.txt b/tests/unittests/media/server/gstplayer/CMakeLists.txt index 7f6edfd76..c2a1c1e02 100644 --- a/tests/unittests/media/server/gstplayer/CMakeLists.txt +++ b/tests/unittests/media/server/gstplayer/CMakeLists.txt @@ -44,7 +44,6 @@ add_gtests(RialtoServerGstPlayerUnitTests genericPlayer/tasksTests/PlayTest.cpp genericPlayer/tasksTests/ProcessAudioGapTest.cpp genericPlayer/tasksTests/ReadShmDataAndAttachSamplesTest.cpp - genericPlayer/tasksTests/RemoveSourceTest.cpp genericPlayer/tasksTests/RenderFrameTest.cpp genericPlayer/tasksTests/ReportPositionTest.cpp genericPlayer/tasksTests/SetBufferingLimitTest.cpp @@ -121,6 +120,9 @@ add_gtests(RialtoServerGstPlayerUnitTests #FlushWatcher unittests flushWatcher/FlushWatcherTests.cpp + #GstProfiler unittests + profiler/GstProfilerTests.cpp + ) target_include_directories(RialtoServerGstPlayerUnitTests diff --git a/tests/unittests/media/server/gstplayer/decryptor/DecryptTest.cpp b/tests/unittests/media/server/gstplayer/decryptor/DecryptTest.cpp index 42d82fc60..ec941d3f4 100644 --- a/tests/unittests/media/server/gstplayer/decryptor/DecryptTest.cpp +++ b/tests/unittests/media/server/gstplayer/decryptor/DecryptTest.cpp @@ -144,12 +144,12 @@ class RialtoServerDecryptorPrivateDecryptTest : public ::testing::Test void expectWidevineKeySystem() { - EXPECT_CALL(*m_decryptionServiceMock, isNetflixPlayreadyKeySystem(m_keySessionId)).WillOnce(Return(false)); + EXPECT_CALL(*m_decryptionServiceMock, isExtendedInterfaceUsed(m_keySessionId)).WillOnce(Return(false)); } void expectPlayreadyKeySystem() { - EXPECT_CALL(*m_decryptionServiceMock, isNetflixPlayreadyKeySystem(m_keySessionId)).WillOnce(Return(true)); + EXPECT_CALL(*m_decryptionServiceMock, isExtendedInterfaceUsed(m_keySessionId)).WillOnce(Return(true)); } void expectKeyMappingFailure() diff --git a/tests/unittests/media/server/gstplayer/dispatcherThread/GstDispatcherThreadTest.cpp b/tests/unittests/media/server/gstplayer/dispatcherThread/GstDispatcherThreadTest.cpp index 54aeffd82..2805d68ab 100644 --- a/tests/unittests/media/server/gstplayer/dispatcherThread/GstDispatcherThreadTest.cpp +++ b/tests/unittests/media/server/gstplayer/dispatcherThread/GstDispatcherThreadTest.cpp @@ -18,6 +18,7 @@ */ #include "GstDispatcherThread.h" +#include "FlushOnPrerollControllerMock.h" #include "GenericPlayerTaskFactoryMock.h" #include "GstDispatcherThreadClientMock.h" #include "GstWrapperMock.h" @@ -56,6 +57,8 @@ class GstDispatcherThreadTest : public ::testing::Test dynamic_cast &>(*workerThreadFactory)}; std::unique_ptr workerThread{std::make_unique>()}; StrictMock &m_workerThreadMock{dynamic_cast &>(*workerThread)}; + std::shared_ptr> m_flushOnPrerollControllerMock{ + std::make_shared>()}; std::mutex m_dispatcherThreadMutex; std::condition_variable m_dispatcherThreadCond; @@ -88,7 +91,8 @@ TEST_F(GstDispatcherThreadTest, PollTimeout) m_dispatcherThreadCond.notify_all(); })); - auto sut = std::make_unique(m_client, &m_pipeline, m_gstWrapperMock); + auto sut = + std::make_unique(m_client, &m_pipeline, m_flushOnPrerollControllerMock, m_gstWrapperMock); // wait for dispatcher thread std::unique_lock dispatcherLock(m_dispatcherThreadMutex); @@ -98,7 +102,7 @@ TEST_F(GstDispatcherThreadTest, PollTimeout) } /** - * Test that a GST_MESSAGE_STATE_CHANGED message is handled correctly. + * Test that a GST_MESSAGE_STATE_CHANGED message (to GST_STATE_PAUSED) is handled correctly. */ TEST_F(GstDispatcherThreadTest, StateChangedToPaused) { @@ -127,7 +131,57 @@ TEST_F(GstDispatcherThreadTest, StateChangedToPaused) EXPECT_CALL(*m_gstWrapperMock, gstBusTimedPopFiltered(&m_bus, 100 * GST_MSECOND, _)).WillOnce(Return(&messageError)); EXPECT_CALL(m_client, handleBusMessage(_)); } + EXPECT_CALL(*m_flushOnPrerollControllerMock, stateReached(GST_STATE_PAUSED)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_bus)) + .WillOnce(Invoke( + [this](gpointer bus) + { + std::unique_lock lock(m_dispatcherThreadMutex); + m_dispatcherThreadDone = true; + m_dispatcherThreadCond.notify_all(); + })); + + auto sut = + std::make_unique(m_client, &m_pipeline, m_flushOnPrerollControllerMock, m_gstWrapperMock); + + // wait for dispatcher thread + std::unique_lock dispatcherLock(m_dispatcherThreadMutex); + bool status = m_dispatcherThreadCond.wait_for(dispatcherLock, std::chrono::milliseconds(200), + [this]() { return m_dispatcherThreadDone; }); + EXPECT_TRUE(status); +} + +/** + * Test that a GST_MESSAGE_STATE_CHANGED message (to GST_STATE_PLAYING) is handled correctly. + */ +TEST_F(GstDispatcherThreadTest, StateChangedToPlaying) +{ + GST_MESSAGE_SRC(&m_message) = GST_OBJECT(&m_pipeline); + GST_MESSAGE_TYPE(&m_message) = GST_MESSAGE_STATE_CHANGED; + + GstState oldState = GST_STATE_READY; + GstState newState = GST_STATE_PLAYING; + GstState pending = GST_STATE_VOID_PENDING; + + GstMessage messageError = {}; + GST_MESSAGE_SRC(&messageError) = GST_OBJECT(&m_pipeline); + GST_MESSAGE_TYPE(&messageError) = GST_MESSAGE_ERROR; + + EXPECT_CALL(*m_gstWrapperMock, gstPipelineGetBus(GST_PIPELINE(&m_pipeline))).WillOnce(Return(&m_bus)); + + EXPECT_CALL(*m_gstWrapperMock, gstMessageParseStateChanged(&m_message, _, _, _)) + .WillOnce(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); + { + InSequence seq; + EXPECT_CALL(*m_gstWrapperMock, gstBusTimedPopFiltered(&m_bus, 100 * GST_MSECOND, _)).WillOnce(Return(&m_message)); + EXPECT_CALL(m_client, handleBusMessage(_)); + + // Signal error to stop the thread + EXPECT_CALL(*m_gstWrapperMock, gstBusTimedPopFiltered(&m_bus, 100 * GST_MSECOND, _)).WillOnce(Return(&messageError)); + EXPECT_CALL(m_client, handleBusMessage(_)); + } + EXPECT_CALL(*m_flushOnPrerollControllerMock, stateReached(GST_STATE_PLAYING)); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_bus)) .WillOnce(Invoke( [this](gpointer bus) @@ -137,7 +191,8 @@ TEST_F(GstDispatcherThreadTest, StateChangedToPaused) m_dispatcherThreadCond.notify_all(); })); - auto sut = std::make_unique(m_client, &m_pipeline, m_gstWrapperMock); + auto sut = + std::make_unique(m_client, &m_pipeline, m_flushOnPrerollControllerMock, m_gstWrapperMock); // wait for dispatcher thread std::unique_lock dispatcherLock(m_dispatcherThreadMutex); @@ -163,6 +218,57 @@ TEST_F(GstDispatcherThreadTest, StateChangedToStop) EXPECT_CALL(*m_gstWrapperMock, gstPipelineGetBus(GST_PIPELINE(&m_pipeline))).WillOnce(Return(&m_bus)); EXPECT_CALL(*m_gstWrapperMock, gstBusTimedPopFiltered(&m_bus, 100 * GST_MSECOND, _)).WillOnce(Return(&m_message)); EXPECT_CALL(m_client, handleBusMessage(_)); + EXPECT_CALL(*m_flushOnPrerollControllerMock, reset()); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_bus)) + .WillOnce(Invoke( + [this](gpointer bus) + { + std::unique_lock lock(m_dispatcherThreadMutex); + m_dispatcherThreadDone = true; + m_dispatcherThreadCond.notify_all(); + })); + + auto sut = + std::make_unique(m_client, &m_pipeline, m_flushOnPrerollControllerMock, m_gstWrapperMock); + + // wait for dispatcher thread + std::unique_lock dispatcherLock(m_dispatcherThreadMutex); + bool status = m_dispatcherThreadCond.wait_for(dispatcherLock, std::chrono::milliseconds(200), + [this]() { return m_dispatcherThreadDone; }); + EXPECT_TRUE(status); +} + +/** + * Test that a GST_MESSAGE_STATE_CHANGED message (to GST_STATE_PAUSED, pending PAUSED) is handled correctly. + */ +TEST_F(GstDispatcherThreadTest, StateChangedToPrerolling) +{ + GST_MESSAGE_SRC(&m_message) = GST_OBJECT(&m_pipeline); + GST_MESSAGE_TYPE(&m_message) = GST_MESSAGE_STATE_CHANGED; + + GstState oldState = GST_STATE_READY; + GstState newState = GST_STATE_PAUSED; + GstState pending = GST_STATE_PAUSED; + + GstMessage messageError = {}; + GST_MESSAGE_SRC(&messageError) = GST_OBJECT(&m_pipeline); + GST_MESSAGE_TYPE(&messageError) = GST_MESSAGE_ERROR; + + EXPECT_CALL(*m_gstWrapperMock, gstPipelineGetBus(GST_PIPELINE(&m_pipeline))).WillOnce(Return(&m_bus)); + + EXPECT_CALL(*m_gstWrapperMock, gstMessageParseStateChanged(&m_message, _, _, _)) + .WillOnce(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); + + { + InSequence seq; + EXPECT_CALL(*m_gstWrapperMock, gstBusTimedPopFiltered(&m_bus, 100 * GST_MSECOND, _)).WillOnce(Return(&m_message)); + EXPECT_CALL(m_client, handleBusMessage(_)); + + // Signal error to stop the thread + EXPECT_CALL(*m_gstWrapperMock, gstBusTimedPopFiltered(&m_bus, 100 * GST_MSECOND, _)).WillOnce(Return(&messageError)); + EXPECT_CALL(m_client, handleBusMessage(_)); + } + EXPECT_CALL(*m_flushOnPrerollControllerMock, setPrerolling()); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_bus)) .WillOnce(Invoke( [this](gpointer bus) @@ -172,7 +278,8 @@ TEST_F(GstDispatcherThreadTest, StateChangedToStop) m_dispatcherThreadCond.notify_all(); })); - auto sut = std::make_unique(m_client, &m_pipeline, m_gstWrapperMock); + auto sut = + std::make_unique(m_client, &m_pipeline, m_flushOnPrerollControllerMock, m_gstWrapperMock); // wait for dispatcher thread std::unique_lock dispatcherLock(m_dispatcherThreadMutex); @@ -200,7 +307,8 @@ TEST_F(GstDispatcherThreadTest, Error) m_dispatcherThreadCond.notify_all(); })); - auto sut = std::make_unique(m_client, &m_pipeline, m_gstWrapperMock); + auto sut = + std::make_unique(m_client, &m_pipeline, m_flushOnPrerollControllerMock, m_gstWrapperMock); // wait for dispatcher thread std::unique_lock dispatcherLock(m_dispatcherThreadMutex); @@ -243,7 +351,8 @@ TEST_F(GstDispatcherThreadTest, StateChangedToPausedNonPipeline) m_dispatcherThreadCond.notify_all(); })); - auto sut = std::make_unique(m_client, &m_pipeline, m_gstWrapperMock); + auto sut = + std::make_unique(m_client, &m_pipeline, m_flushOnPrerollControllerMock, m_gstWrapperMock); // wait for dispatcher thread std::unique_lock dispatcherLock(m_dispatcherThreadMutex); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/CreateTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/CreateTest.cpp index 901d222ad..a48656c62 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/CreateTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/CreateTest.cpp @@ -49,6 +49,13 @@ class RialtoServerCreateGstGenericPlayerTest : public GstGenericPlayerTestCommon GstStructure m_contextStructure{}; GstElement m_westerosSink{}; GParamSpec m_rectangleSpec{}; + const bool m_kIsLive{false}; + + void expectCreateProfiler() + { + EXPECT_CALL(*m_gstProfilerFactoryMock, createGstProfiler(&m_pipeline, _, _)) + .WillOnce(Return(ByMove(std::move(m_gstProfiler)))); + } void expectCreatePipeline() { @@ -59,6 +66,7 @@ class RialtoServerCreateGstGenericPlayerTest : public GstGenericPlayerTestCommon expectSetSignalCallbacks(); expectSetUri(); expectCheckPlaySink(); + expectCreateProfiler(); EXPECT_CALL(*m_gstSrcMock, initSrc()); EXPECT_CALL(m_workerThreadFactoryMock, createWorkerThread()).WillOnce(Return(ByMove(std::move(workerThread)))); @@ -71,13 +79,14 @@ class RialtoServerCreateGstGenericPlayerTest : public GstGenericPlayerTestCommon gstPlayerWillBeCreated(); EXPECT_NO_THROW( - m_gstPlayer = - std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, m_type, m_videoReq, - m_gstWrapperMock, m_glibWrapperMock, m_rdkGstreamerUtilsWrapperMock, - m_gstInitialiserMock, std::move(m_flushWatcher), m_gstSrcFactoryMock, - m_timerFactoryMock, std::move(m_taskFactory), - std::move(workerThreadFactory), std::move(gstDispatcherThreadFactory), - m_gstProtectionMetadataFactoryMock)); + m_gstPlayer = std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, m_type, + m_videoReq, m_kIsLive, m_gstWrapperMock, m_glibWrapperMock, + m_rdkGstreamerUtilsWrapperMock, m_gstInitialiserMock, + std::move(m_flushWatcher), m_gstSrcFactoryMock, + m_gstProfilerFactoryMock, m_timerFactoryMock, + std::move(m_taskFactory), std::move(workerThreadFactory), + std::move(gstDispatcherThreadFactory), + m_gstProtectionMetadataFactoryMock)); EXPECT_NE(m_gstPlayer, nullptr); } @@ -145,18 +154,19 @@ TEST_F(RialtoServerCreateGstGenericPlayerTest, FactoryCreatesObject) expectCheckPlaySink(); EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(&m_pipeline, GST_STATE_READY)) .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectRef(&m_pipeline)).WillOnce(Return(&m_pipeline)); std::shared_ptr factory = firebolt::rialto::server::IGstGenericPlayerFactory::getFactory(); ASSERT_NE(factory, nullptr); auto player{factory->createGstGenericPlayer(&m_gstPlayerClient, m_decryptionServiceMock, m_type, m_videoReq, - m_rdkGstreamerUtilsWrapperFactoryMock)}; + m_kIsLive, m_rdkGstreamerUtilsWrapperFactoryMock)}; EXPECT_NE(player, nullptr); // Destroy expectations EXPECT_CALL(*m_gstWrapperMock, gstBusSetSyncHandler(nullptr, nullptr, nullptr, nullptr)); EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(_, GST_STATE_NULL)).WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); - EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(_)).Times(2); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(_)).Times(3); player.reset(); // Cleanup @@ -336,9 +346,10 @@ TEST_F(RialtoServerCreateGstGenericPlayerTest, CreateWesterossinkFailsCreateCont EXPECT_CALL(*m_gstWrapperMock, gstContextNew(StrEq("erm"), false)).WillOnce(Return(nullptr)); EXPECT_THROW(m_gstPlayer = std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, m_type, - m_videoReq, m_gstWrapperMock, m_glibWrapperMock, - m_rdkGstreamerUtilsWrapperMock, m_gstInitialiserMock, - std::move(m_flushWatcher), m_gstSrcFactoryMock, + m_videoReq, m_kIsLive, m_gstWrapperMock, + m_glibWrapperMock, m_rdkGstreamerUtilsWrapperMock, + m_gstInitialiserMock, std::move(m_flushWatcher), + m_gstSrcFactoryMock, m_gstProfilerFactoryMock, m_timerFactoryMock, std::move(m_taskFactory), std::move(workerThreadFactory), std::move(gstDispatcherThreadFactory), @@ -354,11 +365,11 @@ TEST_F(RialtoServerCreateGstGenericPlayerTest, GstSrcFactoryNull) { EXPECT_CALL(m_gstInitialiserMock, waitForInitialisation()); EXPECT_THROW(m_gstPlayer = std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, m_type, - m_videoReq, m_gstWrapperMock, m_glibWrapperMock, - m_rdkGstreamerUtilsWrapperMock, m_gstInitialiserMock, - std::move(m_flushWatcher), nullptr, - m_timerFactoryMock, std::move(m_taskFactory), - std::move(workerThreadFactory), + m_videoReq, m_kIsLive, m_gstWrapperMock, + m_glibWrapperMock, m_rdkGstreamerUtilsWrapperMock, + m_gstInitialiserMock, std::move(m_flushWatcher), + nullptr, m_gstProfilerFactoryMock, m_timerFactoryMock, + std::move(m_taskFactory), std::move(workerThreadFactory), std::move(gstDispatcherThreadFactory), m_gstProtectionMetadataFactoryMock), std::runtime_error); @@ -374,9 +385,10 @@ TEST_F(RialtoServerCreateGstGenericPlayerTest, TimerFactoryFails) initFactories(); EXPECT_THROW(m_gstPlayer = std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, m_type, - m_videoReq, m_gstWrapperMock, m_glibWrapperMock, - m_rdkGstreamerUtilsWrapperMock, m_gstInitialiserMock, - std::move(m_flushWatcher), m_gstSrcFactoryMock, + m_videoReq, m_kIsLive, m_gstWrapperMock, + m_glibWrapperMock, m_rdkGstreamerUtilsWrapperMock, + m_gstInitialiserMock, std::move(m_flushWatcher), + m_gstSrcFactoryMock, m_gstProfilerFactoryMock, nullptr, std::move(m_taskFactory), std::move(workerThreadFactory), std::move(gstDispatcherThreadFactory), @@ -394,9 +406,10 @@ TEST_F(RialtoServerCreateGstGenericPlayerTest, GstSrcFactoryFails) EXPECT_CALL(*m_gstSrcFactoryMock, getGstSrc()).WillOnce(Return(nullptr)); EXPECT_THROW(m_gstPlayer = std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, m_type, - m_videoReq, m_gstWrapperMock, m_glibWrapperMock, - m_rdkGstreamerUtilsWrapperMock, m_gstInitialiserMock, - std::move(m_flushWatcher), m_gstSrcFactoryMock, + m_videoReq, m_kIsLive, m_gstWrapperMock, + m_glibWrapperMock, m_rdkGstreamerUtilsWrapperMock, + m_gstInitialiserMock, std::move(m_flushWatcher), + m_gstSrcFactoryMock, m_gstProfilerFactoryMock, m_timerFactoryMock, std::move(m_taskFactory), std::move(workerThreadFactory), std::move(gstDispatcherThreadFactory), @@ -419,14 +432,15 @@ TEST_F(RialtoServerCreateGstGenericPlayerTest, UnknownMediaType) EXPECT_CALL(*m_gstProtectionMetadataFactoryMock, createProtectionMetadataWrapper(_)) .WillOnce(Return(ByMove(std::move(m_gstProtectionMetadataWrapper)))); - EXPECT_THROW(m_gstPlayer = std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, - MediaType::UNKNOWN, m_videoReq, m_gstWrapperMock, - m_glibWrapperMock, m_rdkGstreamerUtilsWrapperMock, - m_gstInitialiserMock, std::move(m_flushWatcher), - m_gstSrcFactoryMock, m_timerFactoryMock, - std::move(m_taskFactory), std::move(workerThreadFactory), - std::move(gstDispatcherThreadFactory), - m_gstProtectionMetadataFactoryMock), + EXPECT_THROW(m_gstPlayer = + std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, MediaType::UNKNOWN, + m_videoReq, m_kIsLive, m_gstWrapperMock, m_glibWrapperMock, + m_rdkGstreamerUtilsWrapperMock, m_gstInitialiserMock, + std::move(m_flushWatcher), m_gstSrcFactoryMock, + m_gstProfilerFactoryMock, m_timerFactoryMock, + std::move(m_taskFactory), std::move(workerThreadFactory), + std::move(gstDispatcherThreadFactory), + m_gstProtectionMetadataFactoryMock), std::runtime_error); EXPECT_EQ(m_gstPlayer, nullptr); } @@ -445,6 +459,7 @@ TEST_F(RialtoServerCreateGstGenericPlayerTest, PlaysinkNotFound) expectSetUri(); expectSetMessageCallback(); + expectCreateProfiler(); EXPECT_CALL(*m_gstSrcMock, initSrc()); EXPECT_CALL(m_workerThreadFactoryMock, createWorkerThread()).WillOnce(Return(ByMove(std::move(workerThread)))); @@ -457,10 +472,10 @@ TEST_F(RialtoServerCreateGstGenericPlayerTest, PlaysinkNotFound) EXPECT_NO_THROW( m_gstPlayer = - std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, m_type, m_videoReq, + std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, m_type, m_videoReq, m_kIsLive, m_gstWrapperMock, m_glibWrapperMock, m_rdkGstreamerUtilsWrapperMock, m_gstInitialiserMock, std::move(m_flushWatcher), m_gstSrcFactoryMock, - m_timerFactoryMock, std::move(m_taskFactory), + m_gstProfilerFactoryMock, m_timerFactoryMock, std::move(m_taskFactory), std::move(workerThreadFactory), std::move(gstDispatcherThreadFactory), m_gstProtectionMetadataFactoryMock)); EXPECT_NE(m_gstPlayer, nullptr); @@ -483,6 +498,7 @@ TEST_F(RialtoServerCreateGstGenericPlayerTest, SetNativeAudioForBrcmAudioSink) expectSetUri(); expectCheckPlaySink(); expectSetMessageCallback(); + expectCreateProfiler(); EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(&m_pipeline, GST_STATE_READY)) .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); @@ -493,10 +509,10 @@ TEST_F(RialtoServerCreateGstGenericPlayerTest, SetNativeAudioForBrcmAudioSink) EXPECT_NO_THROW( m_gstPlayer = - std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, m_type, m_videoReq, + std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, m_type, m_videoReq, m_kIsLive, m_gstWrapperMock, m_glibWrapperMock, m_rdkGstreamerUtilsWrapperMock, m_gstInitialiserMock, std::move(m_flushWatcher), m_gstSrcFactoryMock, - m_timerFactoryMock, std::move(m_taskFactory), + m_gstProfilerFactoryMock, m_timerFactoryMock, std::move(m_taskFactory), std::move(workerThreadFactory), std::move(gstDispatcherThreadFactory), m_gstProtectionMetadataFactoryMock)); EXPECT_NE(m_gstPlayer, nullptr); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/FlushOnPrerollControllerTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/FlushOnPrerollControllerTest.cpp index 803ae1eb3..ed5a0904b 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/FlushOnPrerollControllerTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/FlushOnPrerollControllerTest.cpp @@ -19,6 +19,7 @@ #include "FlushOnPrerollController.h" #include +#include using firebolt::rialto::MediaSourceType; using firebolt::rialto::server::FlushOnPrerollController; @@ -29,37 +30,65 @@ class FlushOnPrerollControllerTest : public ::testing::Test FlushOnPrerollController m_sut; }; -TEST_F(FlushOnPrerollControllerTest, shouldNotPostponeFlushWhenNoFlushSet) +TEST_F(FlushOnPrerollControllerTest, shouldNotWaithWhenNoFlushSet) { - EXPECT_FALSE(m_sut.shouldPostponeFlush(MediaSourceType::AUDIO)); + m_sut.waitIfRequired(MediaSourceType::AUDIO); + // No deadlock here } -TEST_F(FlushOnPrerollControllerTest, shouldNotPostponeFlushWhenNotPrerolled) +TEST_F(FlushOnPrerollControllerTest, shouldNotWaitWhenNotPrerolled) { - m_sut.setFlushing(MediaSourceType::AUDIO, GST_STATE_PLAYING); - EXPECT_FALSE(m_sut.shouldPostponeFlush(MediaSourceType::AUDIO)); + m_sut.setTargetState(GST_STATE_PLAYING); + m_sut.setFlushing(MediaSourceType::AUDIO); + m_sut.waitIfRequired(MediaSourceType::AUDIO); + // No deadlock here } -TEST_F(FlushOnPrerollControllerTest, shouldPostponeAudioFlush) +TEST_F(FlushOnPrerollControllerTest, shouldNotWaitWhenReset) { - m_sut.setFlushing(MediaSourceType::AUDIO, GST_STATE_PLAYING); + m_sut.setTargetState(GST_STATE_PLAYING); + m_sut.setFlushing(MediaSourceType::AUDIO); m_sut.stateReached(GST_STATE_PAUSED); - EXPECT_TRUE(m_sut.shouldPostponeFlush(MediaSourceType::AUDIO)); - EXPECT_FALSE(m_sut.shouldPostponeFlush(MediaSourceType::VIDEO)); + m_sut.reset(); + m_sut.waitIfRequired(MediaSourceType::AUDIO); + // No deadlock here } -TEST_F(FlushOnPrerollControllerTest, shouldNotPostponeAudioFlushWhenReset) +TEST_F(FlushOnPrerollControllerTest, shouldNotWaitWhenPrerolling) { - m_sut.setFlushing(MediaSourceType::AUDIO, GST_STATE_PLAYING); + m_sut.setTargetState(GST_STATE_PLAYING); + m_sut.setFlushing(MediaSourceType::AUDIO); + m_sut.setPrerolling(); + m_sut.waitIfRequired(MediaSourceType::AUDIO); + // No deadlock here +} + +TEST_F(FlushOnPrerollControllerTest, shouldNotWaitWhenPreviousProcedureIsFinished) +{ + m_sut.setTargetState(GST_STATE_PLAYING); + m_sut.setFlushing(MediaSourceType::AUDIO); m_sut.stateReached(GST_STATE_PAUSED); - m_sut.reset(); - EXPECT_FALSE(m_sut.shouldPostponeFlush(MediaSourceType::AUDIO)); + m_sut.stateReached(GST_STATE_PLAYING); + m_sut.waitIfRequired(MediaSourceType::AUDIO); + // No deadlock here +} + +TEST_F(FlushOnPrerollControllerTest, shouldNotWaitWithVideoFlushWhenOnlyAudioIsOngoing) +{ + m_sut.setTargetState(GST_STATE_PLAYING); + m_sut.setFlushing(MediaSourceType::AUDIO); + m_sut.stateReached(GST_STATE_PAUSED); + m_sut.waitIfRequired(MediaSourceType::VIDEO); + // No deadlock here } -TEST_F(FlushOnPrerollControllerTest, shouldNotPostponeAudioFlushWhenPreviousProcedureIsFinished) +TEST_F(FlushOnPrerollControllerTest, shouldWaitForAudioFlushFinish) { - m_sut.setFlushing(MediaSourceType::AUDIO, GST_STATE_PLAYING); + m_sut.setTargetState(GST_STATE_PLAYING); + m_sut.setFlushing(MediaSourceType::AUDIO); m_sut.stateReached(GST_STATE_PAUSED); + std::thread waitThread([this]() { m_sut.waitIfRequired(MediaSourceType::AUDIO); }); m_sut.stateReached(GST_STATE_PLAYING); - EXPECT_FALSE(m_sut.shouldPostponeFlush(MediaSourceType::AUDIO)); + waitThread.join(); + // No deadlock here } diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/GstDispatcherThreadClientTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/GstDispatcherThreadClientTest.cpp index ed883ce93..f0f455fad 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/GstDispatcherThreadClientTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/GstDispatcherThreadClientTest.cpp @@ -33,16 +33,17 @@ class GstDispatcherThreadClientTest : public GstGenericPlayerTestCommon protected: std::unique_ptr m_sut; VideoRequirements m_videoReq = {kMinPrimaryVideoWidth, kMinPrimaryVideoHeight}; + const bool m_kIsLive{false}; GstDispatcherThreadClientTest() { gstPlayerWillBeCreated(); m_sut = std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, MediaType::MSE, - m_videoReq, m_gstWrapperMock, m_glibWrapperMock, + m_videoReq, m_kIsLive, m_gstWrapperMock, m_glibWrapperMock, m_rdkGstreamerUtilsWrapperMock, m_gstInitialiserMock, - std::move(m_flushWatcher), m_gstSrcFactoryMock, m_timerFactoryMock, - std::move(m_taskFactory), std::move(workerThreadFactory), - std::move(gstDispatcherThreadFactory), + std::move(m_flushWatcher), m_gstSrcFactoryMock, + m_gstProfilerFactoryMock, m_timerFactoryMock, std::move(m_taskFactory), + std::move(workerThreadFactory), std::move(gstDispatcherThreadFactory), m_gstProtectionMetadataFactoryMock); } diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp index 44152f81c..4544074a8 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp @@ -84,6 +84,7 @@ constexpr bool kShowVideoWindow{true}; constexpr gint64 kPosition{123}; constexpr double kVolume{0.5}; constexpr firebolt::rialto::PlaybackInfo kPlaybackInfo{kPosition, kVolume}; +constexpr bool kIsLive{false}; } // namespace bool operator==(const GstRialtoProtectionData &lhs, const GstRialtoProtectionData &rhs) @@ -117,11 +118,11 @@ class GstGenericPlayerPrivateTest : public GstGenericPlayerTestCommon { gstPlayerWillBeCreated(); m_sut = std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, MediaType::MSE, - m_videoReq, m_gstWrapperMock, m_glibWrapperMock, + m_videoReq, kIsLive, m_gstWrapperMock, m_glibWrapperMock, m_rdkGstreamerUtilsWrapperMock, m_gstInitialiserMock, - std::move(m_flushWatcher), m_gstSrcFactoryMock, m_timerFactoryMock, - std::move(m_taskFactory), std::move(workerThreadFactory), - std::move(gstDispatcherThreadFactory), + std::move(m_flushWatcher), m_gstSrcFactoryMock, + m_gstProfilerFactoryMock, m_timerFactoryMock, std::move(m_taskFactory), + std::move(workerThreadFactory), std::move(gstDispatcherThreadFactory), m_gstProtectionMetadataFactoryMock); m_realElement = initRealElement(); } @@ -231,12 +232,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldScheduleEnoughDataData) TEST_F(GstGenericPlayerPrivateTest, shouldScheduleAudioUnderflowWithUnderflowEnabled) { - modifyContext( - [&](GenericPlayerContext &context) - { - context.isPlaying = true; - context.audioSourceRemoved = false; - }); + modifyContext([&](GenericPlayerContext &context) { context.isPlaying = true; }); std::unique_ptr task{std::make_unique>()}; EXPECT_CALL(dynamic_cast &>(*task), execute()); @@ -248,29 +244,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldScheduleAudioUnderflowWithUnderflowEna TEST_F(GstGenericPlayerPrivateTest, shouldScheduleAudioUnderflowWithUnderflowDisabledNotPlaying) { - modifyContext( - [&](GenericPlayerContext &context) - { - context.isPlaying = false; - context.audioSourceRemoved = false; - }); - - std::unique_ptr task{std::make_unique>()}; - EXPECT_CALL(dynamic_cast &>(*task), execute()); - EXPECT_CALL(m_taskFactoryMock, createUnderflow(_, _, false, MediaSourceType::AUDIO)) - .WillOnce(Return(ByMove(std::move(task)))); - - m_sut->scheduleAudioUnderflow(); -} - -TEST_F(GstGenericPlayerPrivateTest, shouldScheduleAudioUnderflowWithUnderflowDisabledRemoveSource) -{ - modifyContext( - [&](GenericPlayerContext &context) - { - context.isPlaying = true; - context.audioSourceRemoved = true; - }); + modifyContext([&](GenericPlayerContext &context) { context.isPlaying = false; }); std::unique_ptr task{std::make_unique>()}; EXPECT_CALL(dynamic_cast &>(*task), execute()); @@ -312,7 +286,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldNotSetVideoRectangleWhenVideoSinkIsNul TEST_F(GstGenericPlayerPrivateTest, shouldNotSetVideoRectangleWhenVideoSinkDoesNotHaveRectangleProperty) { - expectGetSink(kVideoSinkStr, m_realElement); + expectGetAVSink(kVideoSinkStr, m_realElement); EXPECT_CALL(*m_glibWrapperMock, gObjectClassFindProperty(_, StrEq("rectangle"))).WillOnce(Return(nullptr)); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)); EXPECT_FALSE(m_sut->setVideoSinkRectangle()); @@ -320,7 +294,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldNotSetVideoRectangleWhenVideoSinkDoesN TEST_F(GstGenericPlayerPrivateTest, shouldSetVideoRectangle) { - expectGetSink(kVideoSinkStr, m_realElement); + expectGetAVSink(kVideoSinkStr, m_realElement); EXPECT_CALL(*m_glibWrapperMock, gObjectClassFindProperty(_, StrEq("rectangle"))).WillOnce(Return(&m_rectangleSpec)); EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(m_realElement, StrEq("rectangle"))); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)); @@ -364,7 +338,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldFailToSetImmediateOutputIfPropertyDoes { modifyContext([&](GenericPlayerContext &context) { context.pendingImmediateOutputForVideo = true; }); - expectGetSink(kVideoSinkStr, m_realElement); + expectGetAVSink(kVideoSinkStr, m_realElement); expectPropertyDoesntExist(m_glibWrapperMock, m_gstWrapperMock, m_realElement, kImmediateOutputStr); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)).Times(1); @@ -375,7 +349,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldSetImmediateOutput) { modifyContext([&](GenericPlayerContext &context) { context.pendingImmediateOutputForVideo = true; }); - expectGetSink(kVideoSinkStr, m_realElement); + expectGetAVSink(kVideoSinkStr, m_realElement); expectSetProperty(m_glibWrapperMock, m_gstWrapperMock, m_realElement, kImmediateOutputStr, true); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)).Times(1); @@ -399,7 +373,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldFailToSetLowLatencyIfPropertyDoesntExi { modifyContext([&](GenericPlayerContext &context) { context.pendingLowLatency = true; }); - expectGetSink(kAudioSinkStr, m_realElement); + expectGetAVSink(kAudioSinkStr, m_realElement); expectPropertyDoesntExist(m_glibWrapperMock, m_gstWrapperMock, m_realElement, kLowLatencyStr); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)).Times(1); @@ -410,7 +384,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldSetLowLatency) { modifyContext([&](GenericPlayerContext &context) { context.pendingLowLatency = true; }); - expectGetSink(kAudioSinkStr, m_realElement); + expectGetAVSink(kAudioSinkStr, m_realElement); expectSetProperty(m_glibWrapperMock, m_gstWrapperMock, m_realElement, kLowLatencyStr, true); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)).Times(1); @@ -456,7 +430,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldFailToSetSyncIfPropertyDoesntExist) { modifyContext([&](GenericPlayerContext &context) { context.pendingSync = true; }); - expectGetSink(kAudioSinkStr, m_realElement); + expectGetAVSink(kAudioSinkStr, m_realElement); expectPropertyDoesntExist(m_glibWrapperMock, m_gstWrapperMock, m_realElement, kSyncStr); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)).Times(1); @@ -467,7 +441,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldSetSync) { modifyContext([&](GenericPlayerContext &context) { context.pendingSync = true; }); - expectGetSink(kAudioSinkStr, m_realElement); + expectGetAVSink(kAudioSinkStr, m_realElement); expectSetProperty(m_glibWrapperMock, m_gstWrapperMock, m_realElement, kSyncStr, true); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)).Times(1); @@ -628,7 +602,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldFailToSetRenderFrameIfPropertyDoesntEx { modifyContext([&](GenericPlayerContext &context) { context.pendingRenderFrame = true; }); - expectGetSink(kVideoSinkStr, m_realElement); + expectGetAVSink(kVideoSinkStr, m_realElement); expectPropertyDoesntExist(m_glibWrapperMock, m_gstWrapperMock, m_realElement, kFrameStepOnPrerollStr); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)).Times(1); @@ -639,7 +613,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldSetRenderFrame) { modifyContext([&](GenericPlayerContext &context) { context.pendingRenderFrame = true; }); - expectGetSink(kVideoSinkStr, m_realElement); + expectGetAVSink(kVideoSinkStr, m_realElement); expectSetProperty(m_glibWrapperMock, m_gstWrapperMock, m_realElement, kFrameStepOnPrerollStr, 1); @@ -1654,12 +1628,18 @@ TEST_F(GstGenericPlayerPrivateTest, shouldStartPositionReportingTimer) std::unique_ptr audioUnderflowTimerMock = std::make_unique>(); EXPECT_CALL(*m_timerFactoryMock, createTimer(kPositionReportTimerMs, _, common::TimerType::PERIODIC)) .WillOnce(Return(ByMove(std::move(audioUnderflowTimerMock)))); + + m_sut->startPositionReportingAndCheckAudioUnderflowTimer(); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldStartPlaybackInfoTimer) +{ willNotifyPlaybackInfo(); std::unique_ptr playbackInfoTimerMock = std::make_unique>(); EXPECT_CALL(*m_timerFactoryMock, createTimer(kPlaybackInfoTimerMs, _, common::TimerType::PERIODIC)) .WillOnce(Return(ByMove(std::move(playbackInfoTimerMock)))); - m_sut->startPositionReportingAndCheckAudioUnderflowTimer(); + m_sut->startNotifyPlaybackInfoTimer(); } TEST_F(GstGenericPlayerPrivateTest, shouldNotStartPositionReportingTimerWhenItIsActive) @@ -1668,14 +1648,22 @@ TEST_F(GstGenericPlayerPrivateTest, shouldNotStartPositionReportingTimerWhenItIs EXPECT_CALL(dynamic_cast &>(*timerMock), isActive()).WillOnce(Return(true)); EXPECT_CALL(*m_timerFactoryMock, createTimer(kPositionReportTimerMs, _, common::TimerType::PERIODIC)) .WillOnce(Return(ByMove(std::move(timerMock)))); - willNotifyPlaybackInfo(); - std::unique_ptr playbackInfoTimerMock = std::make_unique>(); - EXPECT_CALL(*m_timerFactoryMock, createTimer(kPlaybackInfoTimerMs, _, common::TimerType::PERIODIC)) - .WillOnce(Return(ByMove(std::move(playbackInfoTimerMock)))); + m_sut->startPositionReportingAndCheckAudioUnderflowTimer(); m_sut->startPositionReportingAndCheckAudioUnderflowTimer(); } +TEST_F(GstGenericPlayerPrivateTest, shouldNotStartPlaybackInfoTimerWhenItIsActive) +{ + std::unique_ptr timerMock = std::make_unique>(); + EXPECT_CALL(dynamic_cast &>(*timerMock), isActive()).WillOnce(Return(true)); + EXPECT_CALL(*m_timerFactoryMock, createTimer(kPlaybackInfoTimerMs, _, common::TimerType::PERIODIC)) + .WillOnce(Return(ByMove(std::move(timerMock)))); + willNotifyPlaybackInfo(); + m_sut->startNotifyPlaybackInfoTimer(); + m_sut->startNotifyPlaybackInfoTimer(); +} + TEST_F(GstGenericPlayerPrivateTest, shouldScheduleReportPositionWhenPositionReportingTimerIsFired) { std::unique_ptr timerMock = std::make_unique>(); @@ -1692,6 +1680,12 @@ TEST_F(GstGenericPlayerPrivateTest, shouldScheduleReportPositionWhenPositionRepo callback(); return std::move(timerMock); })); + m_sut->startPositionReportingAndCheckAudioUnderflowTimer(); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldSchedulePlaybackInfoWhenPlaybackInfoTimerIsFired) +{ + std::unique_ptr timerMock = std::make_unique>(); willNotifyPlaybackInfo(); std::unique_ptr playbackInfoTimerMock = std::make_unique>(); EXPECT_CALL(*m_timerFactoryMock, createTimer(kPlaybackInfoTimerMs, _, common::TimerType::PERIODIC)) @@ -1702,7 +1696,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldScheduleReportPositionWhenPositionRepo callback(); return std::move(playbackInfoTimerMock); })); - m_sut->startPositionReportingAndCheckAudioUnderflowTimer(); + m_sut->startNotifyPlaybackInfoTimer(); } TEST_F(GstGenericPlayerPrivateTest, shouldStopActivePositionReportingTimer) @@ -1712,14 +1706,22 @@ TEST_F(GstGenericPlayerPrivateTest, shouldStopActivePositionReportingTimer) EXPECT_CALL(dynamic_cast &>(*timerMock), cancel()); EXPECT_CALL(*m_timerFactoryMock, createTimer(kPositionReportTimerMs, _, common::TimerType::PERIODIC)) .WillOnce(Return(ByMove(std::move(timerMock)))); + + m_sut->startPositionReportingAndCheckAudioUnderflowTimer(); + m_sut->stopPositionReportingAndCheckAudioUnderflowTimer(); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldStopActivePlaybackInfoTimerTimer) +{ willNotifyPlaybackInfo(); std::unique_ptr playbackInfoTimerMock = std::make_unique>(); EXPECT_CALL(dynamic_cast &>(*playbackInfoTimerMock), isActive()).WillOnce(Return(true)); EXPECT_CALL(dynamic_cast &>(*playbackInfoTimerMock), cancel()); EXPECT_CALL(*m_timerFactoryMock, createTimer(kPlaybackInfoTimerMs, _, common::TimerType::PERIODIC)) .WillOnce(Return(ByMove(std::move(playbackInfoTimerMock)))); - m_sut->startPositionReportingAndCheckAudioUnderflowTimer(); - m_sut->stopPositionReportingAndCheckAudioUnderflowTimer(); + + m_sut->startNotifyPlaybackInfoTimer(); + m_sut->stopNotifyPlaybackInfoTimer(); } TEST_F(GstGenericPlayerPrivateTest, shouldNotStopInactivePositionReportingTimer) @@ -1728,13 +1730,21 @@ TEST_F(GstGenericPlayerPrivateTest, shouldNotStopInactivePositionReportingTimer) EXPECT_CALL(dynamic_cast &>(*timerMock), isActive()).WillOnce(Return(false)); EXPECT_CALL(*m_timerFactoryMock, createTimer(kPositionReportTimerMs, _, common::TimerType::PERIODIC)) .WillOnce(Return(ByMove(std::move(timerMock)))); + + m_sut->startPositionReportingAndCheckAudioUnderflowTimer(); + m_sut->stopPositionReportingAndCheckAudioUnderflowTimer(); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldNotStopInactivePlaybackInfoTimer) +{ willNotifyPlaybackInfo(); std::unique_ptr playbackInfoTimerMock = std::make_unique>(); EXPECT_CALL(dynamic_cast &>(*playbackInfoTimerMock), isActive()).WillOnce(Return(false)); EXPECT_CALL(*m_timerFactoryMock, createTimer(kPlaybackInfoTimerMs, _, common::TimerType::PERIODIC)) .WillOnce(Return(ByMove(std::move(playbackInfoTimerMock)))); - m_sut->startPositionReportingAndCheckAudioUnderflowTimer(); - m_sut->stopPositionReportingAndCheckAudioUnderflowTimer(); + + m_sut->startNotifyPlaybackInfoTimer(); + m_sut->stopNotifyPlaybackInfoTimer(); } TEST_F(GstGenericPlayerPrivateTest, shouldNotStopInactivePositionReportingTimerWhenThereIsNoTimer) @@ -1966,6 +1976,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachMpegAudioSource) GstCaps newGstCaps{}; GstCaps oldGstCaps{}; gchar capsStr[13]{"audio/x-eac3"}; + GstElement *fakeSink = gst_element_factory_make("fakesink", "fakesink"); setPipelineState(GST_STATE_PAUSED); firebolt::rialto::wrappers::PlaybackGroupPrivate *playbackGroup; modifyContext( @@ -1987,6 +1998,12 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachMpegAudioSource) EXPECT_CALL(*m_gstWrapperMock, gstStateLock(_)).WillOnce(Return()); EXPECT_CALL(*m_gstWrapperMock, gstElementQueryPosition(_, GST_FORMAT_TIME, _)).WillOnce(Return(TRUE)); EXPECT_CALL(*m_gstWrapperMock, gstStateUnlock(_)).WillOnce(Return()); + EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("audio-sink"), _)) + .WillOnce(Invoke([&](gpointer object, const gchar *first_property_name, void *element) + { *reinterpret_cast(element) = fakeSink; })); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(fakeSink)); + EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(fakeSink))).WillOnce(Return(kElementTypeName.c_str())); + EXPECT_CALL(*m_glibWrapperMock, gStrHasPrefix(StrEq("fakesink"), StrEq("amlhalasink"))).WillOnce(Return(FALSE)); EXPECT_CALL(*m_rdkGstreamerUtilsWrapperMock, performAudioTrackCodecChannelSwitch(playbackGroup, _, _, _, _, _, _, _, _, _, _, _, _)) .WillOnce(Return(true)); @@ -1995,6 +2012,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachMpegAudioSource) std::unique_ptr source = std::make_unique("audio/aac", false); EXPECT_TRUE(m_sut->reattachSource(source)); + gst_object_unref(fakeSink); } TEST_F(GstGenericPlayerPrivateTest, shouldReattachEac3AudioSource) @@ -2003,6 +2021,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachEac3AudioSource) GstCaps newGstCaps{}; GstCaps oldGstCaps{}; gchar capsStr[11]{"audio/mpeg"}; + GstElement *fakeSink = gst_element_factory_make("fakesink", "fakesink0"); setPipelineState(GST_STATE_PAUSED); firebolt::rialto::wrappers::PlaybackGroupPrivate *playbackGroup; modifyContext( @@ -2023,6 +2042,12 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachEac3AudioSource) EXPECT_CALL(*m_gstWrapperMock, gstStateLock(_)).WillOnce(Return()); EXPECT_CALL(*m_gstWrapperMock, gstElementQueryPosition(_, GST_FORMAT_TIME, _)).WillOnce(Return(TRUE)); EXPECT_CALL(*m_gstWrapperMock, gstStateUnlock(_)).WillOnce(Return()); + EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("audio-sink"), _)) + .WillOnce(Invoke([&](gpointer object, const gchar *first_property_name, void *element) + { *reinterpret_cast(element) = fakeSink; })); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(fakeSink)); + EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(fakeSink))).WillOnce(Return(kElementTypeName.c_str())); + EXPECT_CALL(*m_glibWrapperMock, gStrHasPrefix(_, StrEq("amlhalasink"))).WillOnce(Return(FALSE)); EXPECT_CALL(*m_rdkGstreamerUtilsWrapperMock, performAudioTrackCodecChannelSwitch(playbackGroup, _, _, _, _, _, _, _, _, _, _, _, _)) .WillOnce(Return(true)); @@ -2031,6 +2056,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachEac3AudioSource) std::unique_ptr source = std::make_unique("audio/x-eac3", false); EXPECT_TRUE(m_sut->reattachSource(source)); + gst_object_unref(fakeSink); } TEST_F(GstGenericPlayerPrivateTest, shouldReattachRawAudioSource) @@ -2039,6 +2065,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachRawAudioSource) GstCaps newGstCaps{}; GstCaps oldGstCaps{}; gchar capsStr[11]{"audio/mpeg"}; + GstElement *fakeSink = gst_element_factory_make("fakesink", "fakesink1"); setPipelineState(GST_STATE_PAUSED); firebolt::rialto::wrappers::PlaybackGroupPrivate *playbackGroup; modifyContext( @@ -2059,6 +2086,12 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachRawAudioSource) EXPECT_CALL(*m_gstWrapperMock, gstStateLock(_)).WillOnce(Return()); EXPECT_CALL(*m_gstWrapperMock, gstElementQueryPosition(_, GST_FORMAT_TIME, _)).WillOnce(Return(TRUE)); EXPECT_CALL(*m_gstWrapperMock, gstStateUnlock(_)).WillOnce(Return()); + EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("audio-sink"), _)) + .WillOnce(Invoke([&](gpointer object, const gchar *first_property_name, void *element) + { *reinterpret_cast(element) = fakeSink; })); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(fakeSink)); + EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(fakeSink))).WillOnce(Return(kElementTypeName.c_str())); + EXPECT_CALL(*m_glibWrapperMock, gStrHasPrefix(_, StrEq("amlhalasink"))).WillOnce(Return(FALSE)); EXPECT_CALL(*m_rdkGstreamerUtilsWrapperMock, performAudioTrackCodecChannelSwitch(playbackGroup, _, _, _, _, _, _, _, _, _, _, _, _)) .WillOnce(Return(true)); @@ -2067,6 +2100,189 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachRawAudioSource) std::unique_ptr source = std::make_unique("audio/x-raw", false); EXPECT_TRUE(m_sut->reattachSource(source)); + gst_object_unref(fakeSink); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldReattachAmlhalasinkAudioSourceNoCodecSwitch) +{ + GstAppSrc audioSrc{}; + GstCaps newGstCaps{}; + GstCaps oldGstCaps{}; + GstCaps configCaps{}; + GstEvent flushStartEvent{}; + GstEvent flushStopEvent{}; + gchar capsStr[11]{"audio/mpeg"}; // old caps was AAC + gchar configCapsStr[] = "audio/mpeg, mpegversion=4, enable-svp=(string)true"; + GstElement *fakeSink = gst_element_factory_make("fakesink", "amlhalasink0"); + setPipelineState(GST_STATE_PAUSED); + modifyContext( + [&](GenericPlayerContext &context) + { + context.streamInfo[firebolt::rialto::MediaSourceType::AUDIO].appSrc = GST_ELEMENT(&audioSrc); + context.playbackGroup.m_isAudioAAC = true; // current codec is AAC + }); + + // createCapsFromMediaSource for audio/aac + EXPECT_CALL(*m_gstWrapperMock, gstCapsNewEmptySimple(StrEq("audio/mpeg"))).WillOnce(Return(&newGstCaps)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsSetSimpleIntStub(&newGstCaps, StrEq("mpegversion"), G_TYPE_INT, 4)); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcGetCaps(GST_APP_SRC(&audioSrc))).WillOnce(Return(&oldGstCaps)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsIsEqual(&newGstCaps, &oldGstCaps)).WillOnce(Return(FALSE)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsToString(&oldGstCaps)).WillOnce(Return(capsStr)); + EXPECT_CALL(*m_glibWrapperMock, gFree(capsStr)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&oldGstCaps)); + // getPosition + EXPECT_CALL(*m_gstWrapperMock, gstElementGetState(_)).WillOnce(Return(GST_STATE_PAUSED)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStateReturn(_)).WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + EXPECT_CALL(*m_gstWrapperMock, gstStateLock(_)).WillOnce(Return()); + EXPECT_CALL(*m_gstWrapperMock, gstElementQueryPosition(_, GST_FORMAT_TIME, _)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstStateUnlock(_)).WillOnce(Return()); + // getSink + EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("audio-sink"), _)) + .WillOnce(Invoke([&](gpointer, const gchar *, void *element) + { *reinterpret_cast(element) = fakeSink; })); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(fakeSink)); + // getSinkChildIfAutoAudioSink + EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(fakeSink))).WillOnce(Return(kElementTypeName.c_str())); + // amlhalasink path + EXPECT_CALL(*m_glibWrapperMock, gStrHasPrefix(StrEq("amlhalasink0"), StrEq("amlhalasink"))).WillOnce(Return(TRUE)); + // performAudioTrackCodecChannelSwitch (AAC->AAC, no codec switch): + // configAudioCap unrefs original caps and creates new ones + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&newGstCaps)); + EXPECT_CALL(*m_glibWrapperMock, gStrdupPrintfStub(_)).WillOnce(Return(configCapsStr)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsFromString(configCapsStr)).WillOnce(Return(&configCaps)); + EXPECT_CALL(*m_glibWrapperMock, gFree(configCapsStr)); + // flush events + EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStart()).WillOnce(Return(&flushStartEvent)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&audioSrc), &flushStartEvent)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStop(kResetTime)).WillOnce(Return(&flushStopEvent)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&audioSrc), &flushStopEvent)).WillOnce(Return(TRUE)); + // no codec switch - just set new caps + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetCaps(GST_APP_SRC(&audioSrc), &configCaps)); + // end of reattachSource: caps was updated to configCaps + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&configCaps)); + + std::unique_ptr source = + std::make_unique("audio/aac", false); + EXPECT_TRUE(m_sut->reattachSource(source)); + gst_object_unref(fakeSink); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldReattachAmlhalasinkAudioSourceWithFirstTimeCodecSwitch) +{ + GstAppSrc audioSrc{}; + GstCaps newGstCaps{}; + GstCaps oldGstCaps{}; + GstCaps configCaps{}; + GstEvent flushStartEvent{}; + GstEvent flushStopEvent{}; + GstPad typefindSrcPad{}; + GstPad typefindSrcPeerPad{}; + GstElement newAudioDecoder{}; + GstElement newAudioParse{}; + GstElement newQueue{}; + GstPad newAudioDecoderSrcPad{}; + GstElement typefind{}; + GstElement decodeBin{}; + GstElement playsinkBin{}; + gchar capsStr[13]{"audio/x-eac3"}; // old caps was EAC3 (no "audio/mpeg" → audioAac=false) + gchar configCapsStr[] = "audio/mpeg, mpegversion=4, enable-svp=(string)true"; + GstElement *fakeSink = gst_element_factory_make("fakesink", "amlhalasink1"); + setPipelineState(GST_STATE_PAUSED); + modifyContext( + [&](GenericPlayerContext &context) + { + context.streamInfo[firebolt::rialto::MediaSourceType::AUDIO].appSrc = GST_ELEMENT(&audioSrc); + context.playbackGroup.m_isAudioAAC = false; // current codec is EAC3 + context.playbackGroup.m_curAudioDecoder = nullptr; // first time switch: no existing decoder + context.playbackGroup.m_curAudioTypefind = &typefind; + context.playbackGroup.m_curAudioDecodeBin = &decodeBin; + context.playbackGroup.m_curAudioPlaysinkBin = &playsinkBin; + }); + + // createCapsFromMediaSource for audio/aac + EXPECT_CALL(*m_gstWrapperMock, gstCapsNewEmptySimple(StrEq("audio/mpeg"))).WillOnce(Return(&newGstCaps)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsSetSimpleIntStub(&newGstCaps, StrEq("mpegversion"), G_TYPE_INT, 4)); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcGetCaps(GST_APP_SRC(&audioSrc))).WillOnce(Return(&oldGstCaps)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsIsEqual(&newGstCaps, &oldGstCaps)).WillOnce(Return(FALSE)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsToString(&oldGstCaps)).WillOnce(Return(capsStr)); + EXPECT_CALL(*m_glibWrapperMock, gFree(capsStr)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&oldGstCaps)); + // getPosition + EXPECT_CALL(*m_gstWrapperMock, gstElementGetState(_)).WillOnce(Return(GST_STATE_PAUSED)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStateReturn(_)).WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + EXPECT_CALL(*m_gstWrapperMock, gstStateLock(_)).WillOnce(Return()); + EXPECT_CALL(*m_gstWrapperMock, gstElementQueryPosition(_, GST_FORMAT_TIME, _)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstStateUnlock(_)).WillOnce(Return()); + // getSink + EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("audio-sink"), _)) + .WillOnce(Invoke([&](gpointer, const gchar *, void *element) + { *reinterpret_cast(element) = fakeSink; })); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(fakeSink)); + // getSinkChildIfAutoAudioSink + EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(fakeSink))).WillOnce(Return(kElementTypeName.c_str())); + // amlhalasink path + EXPECT_CALL(*m_glibWrapperMock, gStrHasPrefix(StrEq("amlhalasink1"), StrEq("amlhalasink"))).WillOnce(Return(TRUE)); + // performAudioTrackCodecChannelSwitch (EAC3->AAC, codec switch): + // configAudioCap unrefs original caps and creates new AAC caps + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&newGstCaps)); + EXPECT_CALL(*m_glibWrapperMock, gStrdupPrintfStub(_)).WillOnce(Return(configCapsStr)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsFromString(configCapsStr)).WillOnce(Return(&configCaps)); + EXPECT_CALL(*m_glibWrapperMock, gFree(configCapsStr)); + // flush events + EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStart()).WillOnce(Return(&flushStartEvent)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&audioSrc), &flushStartEvent)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStop(kResetTime)).WillOnce(Return(&flushStopEvent)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&audioSrc), &flushStopEvent)).WillOnce(Return(TRUE)); + // haltAudioPlayback + resumeAudioPlayback: playsinkBin and decodeBin each called twice + EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(&playsinkBin, GST_STATE_READY)) + .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetState(&playsinkBin, _, _, GST_CLOCK_TIME_NONE)).Times(2); + EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(&decodeBin, GST_STATE_PAUSED)) + .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetState(&decodeBin, _, _, GST_CLOCK_TIME_NONE)).Times(2); + // switchAudioCodec -> firstTimeSwitchFromAC3toAAC + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStaticPad(&typefind, StrEq("src"))).WillOnce(Return(&typefindSrcPad)); + EXPECT_CALL(*m_gstWrapperMock, gstPadGetPeer(&typefindSrcPad)).WillOnce(Return(&typefindSrcPeerPad)); + EXPECT_CALL(*m_gstWrapperMock, gstPadUnlink(&typefindSrcPad, &typefindSrcPeerPad)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryMake(StrEq("aacparse"), StrEq("aacparse"))) + .WillOnce(Return(&newAudioParse)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryMake(StrEq("avdec_aac"), StrEq("avdec_aac"))) + .WillOnce(Return(&newAudioDecoder)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryMake(StrEq("queue"), StrEq("aqueue"))).WillOnce(Return(&newQueue)); + EXPECT_CALL(*m_gstWrapperMock, gstBinAdd(_, &newAudioDecoder)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstBinAdd(_, &newAudioParse)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstBinAdd(_, &newQueue)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStaticPad(&newAudioDecoder, StrEq("src"))) + .WillOnce(Return(&newAudioDecoderSrcPad)); + EXPECT_CALL(*m_gstWrapperMock, gstPadLink(&newAudioDecoderSrcPad, &typefindSrcPeerPad)).WillOnce(Return(GST_PAD_LINK_OK)); + EXPECT_CALL(*m_gstWrapperMock, gstElementLink(&newAudioParse, &newQueue)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementLink(&newQueue, &newAudioDecoder)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(&typefind, GST_STATE_READY)).WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(&typefind, StrEq("force-caps"))); + EXPECT_CALL(*m_gstWrapperMock, gstElementSyncStateWithParent(&typefind)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetState(&typefind, _, _, GST_CLOCK_TIME_NONE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSyncStateWithParent(&newAudioDecoder)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetState(&newAudioDecoder, _, _, GST_CLOCK_TIME_NONE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSyncStateWithParent(&newQueue)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetState(&newQueue, _, _, GST_CLOCK_TIME_NONE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSyncStateWithParent(&newAudioParse)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetState(&newAudioParse, _, _, GST_CLOCK_TIME_NONE)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&typefindSrcPad)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&typefindSrcPeerPad)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&newAudioDecoderSrcPad)); + // gstAppSrcSetCaps after codec switch + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetCaps(GST_APP_SRC(&audioSrc), &configCaps)); + // resumeAudioPlayback + EXPECT_CALL(*m_gstWrapperMock, gstElementSyncStateWithParent(&playsinkBin)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSyncStateWithParent(&decodeBin)).WillOnce(Return(TRUE)); + // end of reattachSource: caps was updated to configCaps inside performAudioTrackCodecChannelSwitch + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&configCaps)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&playsinkBin)); + + std::unique_ptr source = + std::make_unique("audio/aac", false); + EXPECT_TRUE(m_sut->reattachSource(source)); + gst_object_unref(fakeSink); } TEST_F(GstGenericPlayerPrivateTest, shouldSetSourceFlushed) @@ -2090,7 +2306,7 @@ TEST_F(GstGenericPlayerPrivateTest, failToSetShowVideoWindowNoSink) TEST_F(GstGenericPlayerPrivateTest, failToSetShowVideoWindowNoProperty) { modifyContext([&](GenericPlayerContext &context) { context.pendingShowVideoWindow = true; }); - expectGetSink(kVideoSinkStr, m_realElement); + expectGetAVSink(kVideoSinkStr, m_realElement); EXPECT_CALL(*m_glibWrapperMock, gObjectClassFindProperty(_, StrEq("show-video-window"))).WillOnce(Return(nullptr)); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)); EXPECT_FALSE(m_sut->setShowVideoWindow()); @@ -2100,22 +2316,10 @@ TEST_F(GstGenericPlayerPrivateTest, shouldSetShowVideoWindow) { modifyContext([&](GenericPlayerContext &context) { context.pendingShowVideoWindow = true; }); - expectGetSink(kVideoSinkStr, m_realElement); + expectGetAVSink(kVideoSinkStr, m_realElement); EXPECT_CALL(*m_glibWrapperMock, gObjectClassFindProperty(_, StrEq("show-video-window"))) .WillOnce(Return(&m_showVideoWindowSpec)); EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(m_realElement, StrEq("show-video-window"))); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)); EXPECT_TRUE(m_sut->setShowVideoWindow()); } - -TEST_F(GstGenericPlayerPrivateTest, shouldExecutePostponedFlush) -{ - constexpr MediaSourceType kSourceType{MediaSourceType::AUDIO}; - constexpr bool kResetTime{true}; - m_sut->postponeFlush(kSourceType, kResetTime); - - std::unique_ptr task{std::make_unique>()}; - EXPECT_CALL(dynamic_cast &>(*task), execute()); - EXPECT_CALL(m_taskFactoryMock, createFlush(_, _, kSourceType, kResetTime)).WillOnce(Return(ByMove(std::move(task)))); - m_sut->executePostponedFlushes(); -} diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp index b2c5158b6..f4f65086a 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp @@ -41,6 +41,7 @@ class GstGenericPlayerTest : public GstGenericPlayerTestCommon protected: std::unique_ptr m_sut; VideoRequirements m_videoReq = {kMinPrimaryVideoWidth, kMinPrimaryVideoHeight}; + bool m_isLive{false}; GstElement *m_pipeline; GstIterator m_it{}; char m_dummy{0}; @@ -52,11 +53,11 @@ class GstGenericPlayerTest : public GstGenericPlayerTestCommon { gstPlayerWillBeCreated(); m_sut = std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, MediaType::MSE, - m_videoReq, m_gstWrapperMock, m_glibWrapperMock, + m_videoReq, m_isLive, m_gstWrapperMock, m_glibWrapperMock, m_rdkGstreamerUtilsWrapperMock, m_gstInitialiserMock, - std::move(m_flushWatcher), m_gstSrcFactoryMock, m_timerFactoryMock, - std::move(m_taskFactory), std::move(workerThreadFactory), - std::move(gstDispatcherThreadFactory), + std::move(m_flushWatcher), m_gstSrcFactoryMock, + m_gstProfilerFactoryMock, m_timerFactoryMock, std::move(m_taskFactory), + std::move(workerThreadFactory), std::move(gstDispatcherThreadFactory), m_gstProtectionMetadataFactoryMock); m_element = fakeElement(); } @@ -146,16 +147,6 @@ TEST_F(GstGenericPlayerTest, shouldAttachSource) m_sut->attachSource(source); } -TEST_F(GstGenericPlayerTest, shouldRemoveSource) -{ - std::unique_ptr task{std::make_unique>()}; - EXPECT_CALL(dynamic_cast &>(*task), execute()); - EXPECT_CALL(m_taskFactoryMock, createRemoveSource(_, _, MediaSourceType::AUDIO)) - .WillOnce(Return(ByMove(std::move(task)))); - - m_sut->removeSource(MediaSourceType::AUDIO); -} - TEST_F(GstGenericPlayerTest, shouldAllSourcesAttached) { std::unique_ptr task{std::make_unique>()}; @@ -165,13 +156,15 @@ TEST_F(GstGenericPlayerTest, shouldAllSourcesAttached) m_sut->allSourcesAttached(); } -TEST_F(GstGenericPlayerTest, shouldPlay) +TEST_F(GstGenericPlayerTest, shouldPlayOnWorkerThread) { + bool async = false; std::unique_ptr task{std::make_unique>()}; EXPECT_CALL(dynamic_cast &>(*task), execute()); EXPECT_CALL(m_taskFactoryMock, createPlay(_)).WillOnce(Return(ByMove(std::move(task)))); - m_sut->play(); + m_sut->play(async); + EXPECT_TRUE(async); } TEST_F(GstGenericPlayerTest, shouldPause) @@ -381,7 +374,7 @@ TEST_F(GstGenericPlayerTest, shouldGetImmediateOutputInPlayingState) const bool kTestImmediateOutputValue{true}; const std::string kPropertyStr{"immediate-output"}; - expectGetSink(kVideoSinkStr, m_element); + expectGetAVSink(kVideoSinkStr, m_element); willGetElementProperty(kPropertyStr, kTestImmediateOutputValue); bool immediateOutputState; @@ -395,7 +388,7 @@ TEST_F(GstGenericPlayerTest, shouldGetImmediateOutputInPlayingStateForAudio) const bool kTestImmediateOutputValue{true}; const std::string kPropertyStr{"immediate-output"}; - expectGetSink(kAudioSinkStr, m_element); + expectGetAVSink(kAudioSinkStr, m_element); willGetElementProperty(kPropertyStr, kTestImmediateOutputValue); bool immediateOutputState; @@ -426,7 +419,7 @@ TEST_F(GstGenericPlayerTest, shouldFailToGetImmediateOutputInPlayingStateIfPrope { setPipelineState(GST_STATE_PLAYING); - expectGetSink(kVideoSinkStr, m_element); + expectGetAVSink(kVideoSinkStr, m_element); EXPECT_CALL(*m_glibWrapperMock, gObjectClassFindProperty(_, StrEq("immediate-output"))).WillOnce(Return(nullptr)); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_element)).Times(1); @@ -443,7 +436,7 @@ TEST_F(GstGenericPlayerTest, shouldGetStatsInPlayingState) uint64_t returnedDroppedFrames{}; setPipelineState(GST_STATE_PLAYING); - expectGetSink(kVideoSinkStr, m_element); + expectGetAVSink(kVideoSinkStr, m_element); GstStructure testStructure; EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("stats"), _)) @@ -491,7 +484,7 @@ TEST_F(GstGenericPlayerTest, shouldFailToGetStatsInPlayingStateIfStructureNull) { setPipelineState(GST_STATE_PLAYING); - expectGetSink(kAudioSinkStr, m_element); + expectGetAVSink(kAudioSinkStr, m_element); // Fail to get GstStructure which should cause the getStats() call to return false EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("stats"), _)).Times(1); @@ -506,7 +499,7 @@ TEST_F(GstGenericPlayerTest, shouldFailToGetStatsInPlayingStateIfStructIncomplet { setPipelineState(GST_STATE_PLAYING); - expectGetSink(kVideoSinkStr, m_element); + expectGetAVSink(kVideoSinkStr, m_element); GstStructure testStructure; EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("stats"), _)) @@ -546,7 +539,7 @@ TEST_F(GstGenericPlayerTest, shouldGetVolumeWithNegativeFadeVolume) const gint kNegativeFadeVolume{-100}; const std::string kPropertyStr{"fade-volume"}; - expectGetSink(kAudioSinkStr, m_element); + expectGetAVSink(kAudioSinkStr, m_element); willGetElementProperty(kPropertyStr, kNegativeFadeVolume); constexpr double kVolume{0.5}; @@ -565,7 +558,7 @@ TEST_F(GstGenericPlayerTest, shouldGetVolumeWithPositiveFadeVolume) const gint kFadeVolume{70}; const std::string kPropertyStr{"fade-volume"}; - expectGetSink(kAudioSinkStr, m_element); + expectGetAVSink(kAudioSinkStr, m_element); willGetElementProperty(kPropertyStr, kFadeVolume); @@ -732,7 +725,7 @@ TEST_F(GstGenericPlayerTest, shouldGetSync) const bool kSyncValue{true}; const std::string kPropertyStr{"sync"}; - expectGetSink(kAudioSinkStr, m_element); + expectGetAVSink(kAudioSinkStr, m_element); willGetElementProperty(kPropertyStr, kSyncValue); bool sync; @@ -762,7 +755,7 @@ TEST_F(GstGenericPlayerTest, shouldFailToGetSyncIfStubNull) TEST_F(GstGenericPlayerTest, shouldFailToGetSyncIfPropertyDoesntExist) { - expectGetSink(kAudioSinkStr, m_element); + expectGetAVSink(kAudioSinkStr, m_element); EXPECT_CALL(*m_glibWrapperMock, gObjectClassFindProperty(_, StrEq("sync"))).WillOnce(Return(nullptr)); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_element)).Times(1); @@ -880,7 +873,7 @@ TEST_F(GstGenericPlayerTest, shouldFlush) bool isAsync{true}; std::unique_ptr task{std::make_unique>()}; EXPECT_CALL(m_flushWatcherMock, setFlushing(MediaSourceType::VIDEO, isAsync)); - expectGetSink(kVideoSinkStr, m_element); + expectGetAVSink(kVideoSinkStr, m_element); EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("async"), _)) .WillOnce(Invoke( [&](gpointer object, const gchar *first_property_name, void *val) @@ -890,12 +883,34 @@ TEST_F(GstGenericPlayerTest, shouldFlush) })); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_element)); EXPECT_CALL(dynamic_cast &>(*task), execute()); - EXPECT_CALL(m_taskFactoryMock, createFlush(_, _, MediaSourceType::VIDEO, kResetTime)) + EXPECT_CALL(m_taskFactoryMock, createFlush(_, _, MediaSourceType::VIDEO, kResetTime, isAsync)) .WillOnce(Return(ByMove(std::move(task)))); m_sut->flush(MediaSourceType::VIDEO, kResetTime, isAsync); } +TEST_F(GstGenericPlayerTest, shouldFlushSubtitles) +{ + constexpr bool kResetTime{true}; + bool isAsync{false}; + std::unique_ptr task{std::make_unique>()}; + EXPECT_CALL(m_flushWatcherMock, setFlushing(MediaSourceType::SUBTITLE, isAsync)); + expectGetSink(kTextSinkStr, m_element); + EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("async"), _)) + .WillOnce(Invoke( + [&](gpointer object, const gchar *first_property_name, void *val) + { + gboolean *returnVal = reinterpret_cast(val); + *returnVal = FALSE; + })); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_element)); + EXPECT_CALL(dynamic_cast &>(*task), execute()); + EXPECT_CALL(m_taskFactoryMock, createFlush(_, _, MediaSourceType::SUBTITLE, kResetTime, isAsync)) + .WillOnce(Return(ByMove(std::move(task)))); + + m_sut->flush(MediaSourceType::SUBTITLE, kResetTime, isAsync); +} + TEST_F(GstGenericPlayerTest, shouldSetSourcePosition) { constexpr int64_t kPosition{1234}; @@ -1087,3 +1102,25 @@ TEST_F(GstGenericPlayerTest, shouldSwitchSource) m_sut->switchSource(source); } + +TEST_F(GstGenericPlayerTest, shouldReturnInvalidDurationWhenQueryFails) +{ + int64_t targetDuration{}; + EXPECT_CALL(*m_gstWrapperMock, gstElementQueryDuration(_, GST_FORMAT_TIME, _)).WillOnce(Return(FALSE)); + EXPECT_FALSE(m_sut->getDuration(targetDuration)); +} + +TEST_F(GstGenericPlayerTest, shouldReturnDuration) +{ + constexpr gint64 kExpectedDuration{123}; + int64_t targetDuration{}; + EXPECT_CALL(*m_gstWrapperMock, gstElementQueryDuration(_, GST_FORMAT_TIME, _)) + .WillOnce(Invoke( + [&](GstElement *element, GstFormat format, gint64 *cur) + { + *cur = kExpectedDuration; + return TRUE; + })); + EXPECT_TRUE(m_sut->getDuration(targetDuration)); + EXPECT_EQ(kExpectedDuration, targetDuration); +} diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp index f40976337..5e26811cc 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp @@ -38,7 +38,6 @@ #include "tasks/generic/Play.h" #include "tasks/generic/ProcessAudioGap.h" #include "tasks/generic/ReadShmDataAndAttachSamples.h" -#include "tasks/generic/RemoveSource.h" #include "tasks/generic/RenderFrame.h" #include "tasks/generic/ReportPosition.h" #include "tasks/generic/SetBufferingLimit.h" @@ -139,6 +138,7 @@ constexpr uint64_t kStopPosition{4523}; const std::vector kStreamHeaderVector{1, 2, 3, 4}; constexpr bool kFramed{true}; constexpr uint64_t kDisplayOffset{35}; +constexpr bool kIsAsync{true}; firebolt::rialto::IMediaPipeline::MediaSegmentVector buildAudioSamples() { @@ -269,6 +269,7 @@ GenericTasksTestsBase::GenericTasksTestsBase() testContext->m_context.gstSrc = testContext->m_gstSrc; testContext->m_context.source = testContext->m_element; testContext->m_context.decryptionService = testContext->m_decryptionServiceMock.get(); + testContext->m_context.gstProfiler = std::move(testContext->m_gstProfilerMock); } GenericTasksTestsBase::~GenericTasksTestsBase() @@ -385,24 +386,11 @@ void GenericTasksTestsBase::setContextSourceNull() testContext->m_context.source = nullptr; } -void GenericTasksTestsBase::setContextAudioSourceRemoved() -{ - testContext->m_context.audioSourceRemoved = true; -} - void GenericTasksTestsBase::setContextStreamInfoEmpty() { testContext->m_context.streamInfo.clear(); } -void GenericTasksTestsBase::setContextNeedDataAudioOnly() -{ - auto audioStreamIt{testContext->m_context.streamInfo.find(firebolt::rialto::MediaSourceType::AUDIO)}; - ASSERT_NE(testContext->m_context.streamInfo.end(), audioStreamIt); - - audioStreamIt->second.isDataNeeded = true; -} - void GenericTasksTestsBase::setContextSetupSourceFinished() { testContext->m_context.setupSourceFinished = true; @@ -787,6 +775,21 @@ void GenericTasksTestsBase::shouldSetupAudioDecoderElementWithPendingBufferingLi expectSetupAudioDecoderElement(); } +void GenericTasksTestsBase::shouldSetupAudioDecoderElementWithIsLiveParameter() +{ + testContext->m_context.isLive = true; + EXPECT_CALL(*testContext->m_glibWrapper, gTypeName(G_OBJECT_TYPE(testContext->m_element))) + .WillOnce(Return(kElementTypeName.c_str())); + + EXPECT_CALL(*testContext->m_glibWrapper, + gObjectClassFindProperty(G_OBJECT_GET_CLASS(testContext->m_element), StrEq("enable-rate-correction"))) + .WillOnce(Return(&testContext->m_paramSpec)); + EXPECT_CALL(*testContext->m_glibWrapper, + gObjectSetStub(G_OBJECT(testContext->m_element), StrEq("enable-rate-correction"))); + + expectSetupAudioDecoderElement(); +} + void GenericTasksTestsBase::shouldSetupVideoSinkElementWithPendingRenderFrame() { testContext->m_context.pendingRenderFrame = true; @@ -1862,12 +1865,6 @@ void GenericTasksTestsBase::shouldReattachAudioSource() EXPECT_CALL(testContext->m_gstPlayer, reattachSource(_)).WillOnce(Return(true)); } -void GenericTasksTestsBase::shouldEnableAudioFlagsAndSendNeedData() -{ - EXPECT_CALL(testContext->m_gstPlayer, setPlaybinFlags(true)); - EXPECT_CALL(testContext->m_gstPlayer, notifyNeedMediaData(MediaSourceType::AUDIO)); -} - void GenericTasksTestsBase::shouldFailToReattachAudioSource() { EXPECT_CALL(testContext->m_gstPlayer, reattachSource(_)).WillOnce(Return(false)); @@ -1878,15 +1875,6 @@ void GenericTasksTestsBase::triggerReattachAudioSource() triggerAttachAudioSource(); } -void GenericTasksTestsBase::checkNewAudioSourceAttached() -{ - auto audioStreamIt{testContext->m_context.streamInfo.find(firebolt::rialto::MediaSourceType::AUDIO)}; - ASSERT_NE(testContext->m_context.streamInfo.end(), audioStreamIt); - - EXPECT_TRUE(audioStreamIt->second.isDataNeeded); - EXPECT_FALSE(testContext->m_context.audioSourceRemoved); -} - void GenericTasksTestsBase::shouldQueryPositionAndSetToZero() { EXPECT_CALL(testContext->m_gstPlayer, getPosition(NotNullMatcher())).WillOnce(Return(0)); @@ -1969,7 +1957,7 @@ void GenericTasksTestsBase::shouldNotRegisterCallbackWhenElementNameIsNotTypefin EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetName(testContext->m_element)) .WillOnce(Return(testContext->m_elementName)); EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_elementName, StrEq("typefind"))) - .WillOnce(Return(nullptr)); + .WillRepeatedly(Return(nullptr)); EXPECT_CALL(*testContext->m_glibWrapper, gFree(testContext->m_elementName)); } @@ -1980,7 +1968,7 @@ void GenericTasksTestsBase::shouldRegisterCallbackForTypefindElement() EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetName(testContext->m_element)) .WillOnce(Return(testContext->m_typefindElementName)); EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_typefindElementName, StrEq("typefind"))) - .WillOnce(Return(testContext->m_typefindElementName)); + .WillRepeatedly(Return(testContext->m_typefindElementName)); EXPECT_CALL(*testContext->m_glibWrapper, gSignalConnect(G_OBJECT(testContext->m_element), StrEq("have-type"), _, &testContext->m_gstPlayer)) .WillOnce(Return(kSignalId)); @@ -2010,9 +1998,19 @@ void GenericTasksTestsBase::shouldUpdatePlaybackGroupWhenCallbackIsCalled() void GenericTasksTestsBase::shouldSetTypefindElement() { - EXPECT_CALL(*testContext->m_gstWrapper, gstObjectCast(nullptr)).WillOnce(Return(nullptr)); - EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_typefindElementName, StrEq("audiosink"))) - .WillOnce(Return(nullptr)); + testContext->m_context.playbackGroup.m_curAudioDecodeBin = &testContext->m_audioDecodeBin; + EXPECT_CALL(*testContext->m_gstWrapper, gstObjectCast(&testContext->m_audioDecodeBin)) + .WillOnce(Return(&testContext->m_obj1)); + + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(FALSE)); } void GenericTasksTestsBase::triggerDeepElementAdded() @@ -2058,8 +2056,11 @@ void GenericTasksTestsBase::shouldSetParseElement() testContext->m_context.playbackGroup.m_curAudioDecodeBin = &testContext->m_audioDecodeBin; EXPECT_CALL(*testContext->m_gstWrapper, gstObjectCast(&testContext->m_audioDecodeBin)) .WillOnce(Return(&testContext->m_obj1)); - EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_parseElementName, StrEq("parse"))) - .WillOnce(Return(testContext->m_parseElementName)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(TRUE)); } void GenericTasksTestsBase::checkParsePlaybackGroupAdded() @@ -2084,10 +2085,15 @@ void GenericTasksTestsBase::shouldSetDecoderElement() testContext->m_context.playbackGroup.m_curAudioDecodeBin = &testContext->m_audioDecodeBin; EXPECT_CALL(*testContext->m_gstWrapper, gstObjectCast(&testContext->m_audioDecodeBin)) .WillOnce(Return(&testContext->m_obj1)); - EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_decoderElementName, StrEq("parse"))) - .WillOnce(Return(nullptr)); - EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_decoderElementName, StrEq("dec"))) - .WillOnce(Return(testContext->m_decoderElementName)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(TRUE)); } void GenericTasksTestsBase::checkDecoderPlaybackGroupAdded() @@ -2102,8 +2108,11 @@ void GenericTasksTestsBase::checkDecoderPlaybackGroupAdded() void GenericTasksTestsBase::shouldSetGenericElement() { EXPECT_CALL(*testContext->m_gstWrapper, gstObjectCast(nullptr)).WillOnce(Return(nullptr)); - EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_elementName, StrEq("audiosink"))) - .WillOnce(Return(nullptr)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(FALSE)); } void GenericTasksTestsBase::shouldSetAudioSinkElement() @@ -2117,8 +2126,11 @@ void GenericTasksTestsBase::shouldSetAudioSinkElement() EXPECT_CALL(*testContext->m_glibWrapper, gFree(testContext->m_audioSinkElementName)); EXPECT_CALL(*testContext->m_gstWrapper, gstObjectCast(nullptr)).WillOnce(Return(nullptr)); - EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_audioSinkElementName, StrEq("audiosink"))) - .WillOnce(Return(testContext->m_audioSinkElementName)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(TRUE)); } void GenericTasksTestsBase::shouldHaveNullParentSink() @@ -2126,6 +2138,7 @@ void GenericTasksTestsBase::shouldHaveNullParentSink() EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetParent(testContext->m_element)) .WillOnce(Return(GST_OBJECT(&testContext->m_audioParentSink))); EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetName(&testContext->m_audioParentSink)).WillOnce(Return(nullptr)); + EXPECT_CALL(*testContext->m_gstWrapper, gstObjectUnref(&testContext->m_audioParentSink)); EXPECT_CALL(*testContext->m_glibWrapper, gFree(nullptr)); } @@ -2136,11 +2149,16 @@ void GenericTasksTestsBase::shouldHaveNonBinParentSink() EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetName(&testContext->m_audioParentSink)) .WillOnce(Return(testContext->m_elementName)); EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_elementName, StrEq("bin"))).WillOnce(Return(nullptr)); + EXPECT_CALL(*testContext->m_gstWrapper, gstObjectUnref(&testContext->m_audioParentSink)); EXPECT_CALL(*testContext->m_glibWrapper, gFree(testContext->m_elementName)); } void GenericTasksTestsBase::shouldHaveBinParentSink() { + // Previous bin should be unrefed + testContext->m_context.playbackGroup.m_curAudioPlaysinkBin = &testContext->m_childElement; + EXPECT_CALL(*testContext->m_gstWrapper, gstObjectUnref(&testContext->m_childElement)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetParent(testContext->m_element)) .WillOnce(Return(GST_OBJECT(&testContext->m_audioParentSink))); EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetName(&testContext->m_audioParentSink)) @@ -2263,6 +2281,24 @@ void GenericTasksTestsBase::shouldTriggerSetUseBuffering() EXPECT_CALL(testContext->m_gstPlayer, setUseBuffering()).WillOnce(Return(true)); } +void GenericTasksTestsBase::shouldLinkTypefindAndParser() +{ + testContext->m_context.playbackGroup.m_linkTypefindParser = true; + testContext->m_context.playbackGroup.m_curAudioParse = &testContext->m_childElement; + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementLink(testContext->m_element, testContext->m_context.playbackGroup.m_curAudioParse)) + .WillOnce(Return(TRUE)); +} + +void GenericTasksTestsBase::shouldFailToLinkTypefindAndParser() +{ + testContext->m_context.playbackGroup.m_linkTypefindParser = true; + testContext->m_context.playbackGroup.m_curAudioParse = &testContext->m_childElement; + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementLink(testContext->m_element, testContext->m_context.playbackGroup.m_curAudioParse)) + .WillOnce(Return(FALSE)); +} + void GenericTasksTestsBase::shouldStopGstPlayer() { auto audioStreamIt{testContext->m_context.streamInfo.find(firebolt::rialto::MediaSourceType::AUDIO)}; @@ -2273,7 +2309,8 @@ void GenericTasksTestsBase::shouldStopGstPlayer() videoStreamIt->second.isDataNeeded = true; audioStreamIt->second.isDataNeeded = true; EXPECT_CALL(testContext->m_gstPlayer, stopPositionReportingAndCheckAudioUnderflowTimer()); - EXPECT_CALL(testContext->m_gstPlayer, changePipelineState(GST_STATE_NULL)); + EXPECT_CALL(testContext->m_gstPlayer, stopNotifyPlaybackInfoTimer()); + EXPECT_CALL(testContext->m_gstPlayer, changePipelineState(GST_STATE_NULL)).WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); } void GenericTasksTestsBase::triggerStop() @@ -2589,12 +2626,12 @@ void GenericTasksTestsBase::checkNoEos() void GenericTasksTestsBase::shouldChangeStatePlayingSuccess() { - EXPECT_CALL(testContext->m_gstPlayer, changePipelineState(GST_STATE_PLAYING)).WillOnce(Return(true)); + EXPECT_CALL(testContext->m_gstPlayer, changePipelineState(GST_STATE_PLAYING)).WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); } void GenericTasksTestsBase::shouldChangeStatePlayingFailure() { - EXPECT_CALL(testContext->m_gstPlayer, changePipelineState(GST_STATE_PLAYING)).WillOnce(Return(false)); + EXPECT_CALL(testContext->m_gstPlayer, changePipelineState(GST_STATE_PLAYING)).WillOnce(Return(GST_STATE_CHANGE_FAILURE)); } void GenericTasksTestsBase::triggerPlay() @@ -2612,7 +2649,7 @@ void GenericTasksTestsBase::triggerPing() void GenericTasksTestsBase::shouldPause() { EXPECT_CALL(testContext->m_gstPlayer, stopPositionReportingAndCheckAudioUnderflowTimer()); - EXPECT_CALL(testContext->m_gstPlayer, changePipelineState(GST_STATE_PAUSED)); + EXPECT_CALL(testContext->m_gstPlayer, changePipelineState(GST_STATE_PAUSED)).WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); } void GenericTasksTestsBase::triggerPause() @@ -3000,47 +3037,8 @@ void GenericTasksTestsBase::triggerRenderFrame() task.execute(); } -void GenericTasksTestsBase::shouldInvalidateActiveAudioRequests() -{ - EXPECT_CALL(testContext->m_gstPlayerClient, invalidateActiveRequests(firebolt::rialto::MediaSourceType::AUDIO)); -} - -void GenericTasksTestsBase::shouldDisableAudioFlag() -{ - EXPECT_CALL(testContext->m_gstPlayer, setPlaybinFlags(false)); -} - -void GenericTasksTestsBase::triggerRemoveSourceAudio() -{ - firebolt::rialto::server::tasks::generic::RemoveSource task{testContext->m_context, testContext->m_gstPlayer, - &testContext->m_gstPlayerClient, - testContext->m_gstWrapper, - firebolt::rialto::MediaSourceType::AUDIO}; - task.execute(); -} - -void GenericTasksTestsBase::triggerRemoveSourceVideo() -{ - firebolt::rialto::server::tasks::generic::RemoveSource task{testContext->m_context, testContext->m_gstPlayer, - &testContext->m_gstPlayerClient, - testContext->m_gstWrapper, - firebolt::rialto::MediaSourceType::VIDEO}; - task.execute(); -} - -void GenericTasksTestsBase::checkAudioSourceRemoved() -{ - EXPECT_TRUE(testContext->m_context.audioSourceRemoved); -} - -void GenericTasksTestsBase::checkAudioSourceNotRemoved() -{ - EXPECT_FALSE(testContext->m_context.audioSourceRemoved); -} - void GenericTasksTestsBase::shouldFlushAudioSrcSuccess() { - EXPECT_CALL(testContext->m_gstPlayer, stopPositionReportingAndCheckAudioUnderflowTimer()); EXPECT_CALL(*testContext->m_gstWrapper, gstEventNewFlushStart()).WillOnce(Return(&testContext->m_event)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementSendEvent(&testContext->m_appSrcAudio, &testContext->m_event)) .WillOnce(Return(TRUE)); @@ -3051,7 +3049,6 @@ void GenericTasksTestsBase::shouldFlushAudioSrcSuccess() void GenericTasksTestsBase::shouldFlushAudioSrcFailure() { - EXPECT_CALL(testContext->m_gstPlayer, stopPositionReportingAndCheckAudioUnderflowTimer()); EXPECT_CALL(*testContext->m_gstWrapper, gstEventNewFlushStart()).WillOnce(Return(&testContext->m_event)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementSendEvent(&testContext->m_appSrcAudio, &testContext->m_event)) .WillOnce(Return(FALSE)); @@ -3142,7 +3139,8 @@ void GenericTasksTestsBase::triggerFlush(firebolt::rialto::MediaSourceType sourc &testContext->m_gstPlayerClient, testContext->m_gstWrapper, sourceType, - kResetTime}; + kResetTime, + kIsAsync}; task.execute(); } @@ -3178,7 +3176,6 @@ void GenericTasksTestsBase::checkVideoFlushed() void GenericTasksTestsBase::shouldFlushVideoSrcSuccess() { - EXPECT_CALL(testContext->m_gstPlayer, stopPositionReportingAndCheckAudioUnderflowTimer()); EXPECT_CALL(*testContext->m_gstWrapper, gstEventNewFlushStart()).WillOnce(Return(&testContext->m_event)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementSendEvent(&testContext->m_appSrcVideo, &testContext->m_event)) .WillOnce(Return(TRUE)); @@ -3187,14 +3184,6 @@ void GenericTasksTestsBase::shouldFlushVideoSrcSuccess() .WillOnce(Return(TRUE)); } -void GenericTasksTestsBase::shouldPostponeVideoFlush() -{ - testContext->m_context.flushOnPrerollController.setFlushing(firebolt::rialto::MediaSourceType::VIDEO, - GST_STATE_PLAYING); - testContext->m_context.flushOnPrerollController.stateReached(GST_STATE_PAUSED); - EXPECT_CALL(testContext->m_gstPlayer, postponeFlush(firebolt::rialto::MediaSourceType::VIDEO, kResetTime)); -} - void GenericTasksTestsBase::shouldSetSubtitleSourcePosition() { EXPECT_CALL(*testContext->m_glibWrapper, gObjectSetStub(&testContext->m_textTrackSink, StrEq("position"))); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h index 646641e1d..7d212b97d 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h @@ -72,9 +72,7 @@ class GenericTasksTestsBase : public ::testing::Test void setContextVideoBuffer(); void setContextPlaybackRate(); void setContextSourceNull(); - void setContextAudioSourceRemoved(); void setContextStreamInfoEmpty(); - void setContextNeedDataAudioOnly(); void setContextSetupSourceFinished(); // SetupElement test methods @@ -88,6 +86,7 @@ class GenericTasksTestsBase : public ::testing::Test void shouldSetupAudioDecoderElementWithPendingStreamSyncMode(); void shouldSetupVideoParserElementWithPendingStreamSyncMode(); void shouldSetupAudioDecoderElementWithPendingBufferingLimit(); + void shouldSetupAudioDecoderElementWithIsLiveParameter(); void shouldSetupVideoSinkElementWithPendingRenderFrame(); void shouldSetupVideoSinkElementWithPendingShowVideoWindow(); void shouldSetupAudioElementAmlhalasinkWhenNoVideo(); @@ -190,10 +189,8 @@ class GenericTasksTestsBase : public ::testing::Test void shouldAttachVideoSourceWithDolbyVisionSource(); void triggerAttachVideoSourceWithDolbyVisionSource(); void shouldReattachAudioSource(); - void shouldEnableAudioFlagsAndSendNeedData(); void shouldFailToReattachAudioSource(); void triggerReattachAudioSource(); - void checkNewAudioSourceAttached(); void triggerFailToCastAudioSource(); void triggerFailToCastVideoSource(); void triggerFailToCastDolbyVisionSource(); @@ -239,6 +236,8 @@ class GenericTasksTestsBase : public ::testing::Test void checkPlaybackGroupAdded(); void setUseBufferingPending(); void shouldTriggerSetUseBuffering(); + void shouldLinkTypefindAndParser(); + void shouldFailToLinkTypefindAndParser(); // Stop test methods void shouldStopGstPlayer(); @@ -400,14 +399,6 @@ class GenericTasksTestsBase : public ::testing::Test void triggerReadShmDataAndAttachSamplesVideo(); void triggerReadShmDataAndAttachSamples(); - // RemoveSource test methods - void shouldInvalidateActiveAudioRequests(); - void shouldDisableAudioFlag(); - void triggerRemoveSourceAudio(); - void triggerRemoveSourceVideo(); - void checkAudioSourceRemoved(); - void checkAudioSourceNotRemoved(); - // Flush test methods void shouldFlushAudio(); void shouldFlushVideo(); @@ -415,7 +406,6 @@ class GenericTasksTestsBase : public ::testing::Test void checkAudioFlushed(); void checkVideoFlushed(); void shouldFlushVideoSrcSuccess(); - void shouldPostponeVideoFlush(); // Set Source Position test methods void shouldSetSubtitleSourcePosition(); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsContext.h b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsContext.h index e51d1095e..4918f723c 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsContext.h +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsContext.h @@ -26,6 +26,7 @@ #include "GlibWrapperMock.h" #include "GstGenericPlayerClientMock.h" #include "GstGenericPlayerPrivateMock.h" +#include "GstProfilerMock.h" #include "GstSrcMock.h" #include "GstTextTrackSinkFactoryMock.h" #include "GstWrapperMock.h" @@ -61,6 +62,8 @@ class GenericTasksTestsContext std::make_shared>()}; std::shared_ptr m_gstTextTrackSinkFactoryMock{ std::make_shared>()}; + std::unique_ptr<::testing::NiceMock> m_gstProfilerMock{ + std::make_unique<::testing::NiceMock>()}; // Gstreamer members GstElement *m_element{}; diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.cpp index 22c7b56b5..02faa219f 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.cpp @@ -44,6 +44,8 @@ void GstGenericPlayerTestCommon::gstPlayerWillBeCreated() EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(&m_pipeline, GST_STATE_READY)) .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); EXPECT_CALL(*m_gstSrcMock, initSrc()); + EXPECT_CALL(*m_gstProfilerFactoryMock, createGstProfiler(&m_pipeline, _, _)) + .WillOnce(Return(ByMove(std::move(m_gstProfiler)))); EXPECT_CALL(m_workerThreadFactoryMock, createWorkerThread()).WillOnce(Return(ByMove(std::move(workerThread)))); EXPECT_CALL(*m_gstProtectionMetadataFactoryMock, createProtectionMetadataWrapper(_)) .WillOnce(Return(ByMove(std::move(m_gstProtectionMetadataWrapper)))); @@ -199,7 +201,7 @@ void GstGenericPlayerTestCommon::expectCheckPlaySink() void GstGenericPlayerTestCommon::expectSetMessageCallback() { - EXPECT_CALL(m_gstDispatcherThreadFactoryMock, createGstDispatcherThread(_, _, _)) + EXPECT_CALL(m_gstDispatcherThreadFactoryMock, createGstDispatcherThread(_, _, _, _)) .WillOnce(Return(ByMove(std::move(gstDispatcherThread)))); } @@ -231,7 +233,7 @@ void GstGenericPlayerTestCommon::expectGetVideoParser(GstElement *element) EXPECT_CALL(*m_gstWrapperMock, gstIteratorFree(&m_it)); } -void GstGenericPlayerTestCommon::expectGetSink(const std::string &sinkName, GstElement *elementObj) +void GstGenericPlayerTestCommon::expectGetAVSink(const std::string &sinkName, GstElement *elementObj) { EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq(sinkName.c_str()), _)) .WillOnce(Invoke( @@ -243,6 +245,17 @@ void GstGenericPlayerTestCommon::expectGetSink(const std::string &sinkName, GstE EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(elementObj))).WillOnce(Return(kElementTypeName.c_str())); } +void GstGenericPlayerTestCommon::expectGetSink(const std::string &sinkName, GstElement *elementObj) +{ + EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq(sinkName.c_str()), _)) + .WillOnce(Invoke( + [elementObj](gpointer object, const gchar *first_property_name, void *element) + { + GstElement **elementPtr = reinterpret_cast(element); + *elementPtr = elementObj; + })); +} + void GstGenericPlayerTestCommon::expectNoDecoder() { EXPECT_CALL(*m_gstWrapperMock, gstBinIterateRecurse(GST_BIN(&m_pipeline))).WillOnce(Return(&m_it)); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.h b/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.h index 8f5e11dff..a3415bb7e 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.h +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.h @@ -29,6 +29,8 @@ #include "GstGenericPlayer.h" #include "GstGenericPlayerClientMock.h" #include "GstInitialiserMock.h" +#include "GstProfilerFactoryMock.h" +#include "GstProfilerMock.h" #include "GstProtectionMetadataHelperFactoryMock.h" #include "GstProtectionMetadataHelperMock.h" #include "GstSrcFactoryMock.h" @@ -47,6 +49,7 @@ using namespace firebolt::rialto; using namespace firebolt::rialto::server; using namespace firebolt::rialto::wrappers; +using ::testing::NiceMock; using ::testing::StrictMock; namespace @@ -55,6 +58,7 @@ namespace const std::string kElementTypeName{"GenericSink"}; const std::string kAudioSinkStr{"audio-sink"}; const std::string kVideoSinkStr{"video-sink"}; +const std::string kTextSinkStr{"text-sink"}; } // namespace class GstGenericPlayerTestCommon : public ::testing::Test @@ -74,6 +78,10 @@ class GstGenericPlayerTestCommon : public ::testing::Test std::make_shared>()}; std::shared_ptr> m_gstSrcFactoryMock{std::make_shared>()}; std::shared_ptr> m_gstSrcMock{std::make_shared>()}; + std::shared_ptr> m_gstProfilerFactoryMock{ + std::make_shared>()}; + std::unique_ptr> m_gstProfiler{std::make_unique>()}; + NiceMock *m_gstProfilerMock{m_gstProfiler.get()}; std::shared_ptr> m_timerFactoryMock{std::make_shared>()}; std::unique_ptr m_taskFactory{std::make_unique>()}; StrictMock &m_taskFactoryMock{ @@ -119,6 +127,7 @@ class GstGenericPlayerTestCommon : public ::testing::Test void expectSetMessageCallback(); void expectGetDecoder(GstElement *element); void expectGetVideoParser(GstElement *element); + void expectGetAVSink(const std::string &sinkName, GstElement *elementObj); void expectGetSink(const std::string &sinkName, GstElement *elementObj); void expectNoDecoder(); void expectNoParser(); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/AttachSourceTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/AttachSourceTest.cpp index 2b241b9fd..746827b33 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/AttachSourceTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/AttachSourceTest.cpp @@ -161,26 +161,16 @@ TEST_F(AttachSourceTest, shouldFailToAttachUnknownSource) triggerAttachUnknownSource(); } -TEST_F(AttachSourceTest, shouldSkipSwitchAudioSourceWhenSourceIsNotRemoved) +TEST_F(AttachSourceTest, shouldSwitchAudioSourceWhenSourceIsReattached) { setContextStreamInfo(firebolt::rialto::MediaSourceType::AUDIO); - triggerReattachAudioSource(); -} - -TEST_F(AttachSourceTest, shouldReattachAudioSource) -{ - setContextStreamInfo(firebolt::rialto::MediaSourceType::AUDIO); - setContextAudioSourceRemoved(); shouldReattachAudioSource(); - shouldEnableAudioFlagsAndSendNeedData(); triggerReattachAudioSource(); - checkNewAudioSourceAttached(); } -TEST_F(AttachSourceTest, shouldFailToReattachAudioSource) +TEST_F(AttachSourceTest, shouldFailToSwitchAudioSourceWhenSourceIsReattached) { setContextStreamInfo(firebolt::rialto::MediaSourceType::AUDIO); - setContextAudioSourceRemoved(); shouldFailToReattachAudioSource(); triggerReattachAudioSource(); } diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FlushTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FlushTest.cpp index 64017d24d..6603d681b 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FlushTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FlushTest.cpp @@ -108,9 +108,3 @@ TEST_F(FlushTest, ShouldFlushVideoWithNeedData) triggerFlush(firebolt::rialto::MediaSourceType::VIDEO); checkVideoFlushed(); } - -TEST_F(FlushTest, ShouldPostponeFlush) -{ - shouldPostponeVideoFlush(); - triggerFlush(firebolt::rialto::MediaSourceType::VIDEO); -} diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp index 7693164c0..70c305e22 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp @@ -45,7 +45,6 @@ #include "tasks/generic/Play.h" #include "tasks/generic/ProcessAudioGap.h" #include "tasks/generic/ReadShmDataAndAttachSamples.h" -#include "tasks/generic/RemoveSource.h" #include "tasks/generic/RenderFrame.h" #include "tasks/generic/ReportPosition.h" #include "tasks/generic/SetBufferingLimit.h" @@ -181,13 +180,6 @@ TEST_F(GenericPlayerTaskFactoryTest, ShouldCreateReadShmDataAndAttachSamples) EXPECT_NO_THROW(dynamic_cast(*task)); } -TEST_F(GenericPlayerTaskFactoryTest, ShouldCreateRemoveSource) -{ - auto task = m_sut.createRemoveSource(m_context, m_gstPlayer, firebolt::rialto::MediaSourceType::AUDIO); - EXPECT_NE(task, nullptr); - EXPECT_NO_THROW(dynamic_cast(*task)); -} - TEST_F(GenericPlayerTaskFactoryTest, ShouldCreateReportPosition) { auto task = m_sut.createReportPosition(m_context, m_gstPlayer); @@ -326,7 +318,7 @@ TEST_F(GenericPlayerTaskFactoryTest, ShouldCreatePing) TEST_F(GenericPlayerTaskFactoryTest, ShouldCreateFlush) { - auto task = m_sut.createFlush(m_context, m_gstPlayer, firebolt::rialto::MediaSourceType::AUDIO, true); + auto task = m_sut.createFlush(m_context, m_gstPlayer, firebolt::rialto::MediaSourceType::AUDIO, true, true); EXPECT_NE(task, nullptr); EXPECT_NO_THROW(dynamic_cast(*task)); } diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/HandleBusMessageTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/HandleBusMessageTest.cpp index 1295f2b9b..cf449908b 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/HandleBusMessageTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/HandleBusMessageTest.cpp @@ -23,6 +23,7 @@ #include "GlibWrapperMock.h" #include "GstGenericPlayerClientMock.h" #include "GstGenericPlayerPrivateMock.h" +#include "GstProfilerMock.h" #include "GstWrapperMock.h" #include "Matchers.h" #include @@ -56,6 +57,8 @@ class HandleBusMessageTest : public testing::Test std::shared_ptr m_glibWrapper{ std::make_shared>()}; StrictMock m_flushWatcherMock; + std::unique_ptr<::testing::NiceMock> m_gstProfilerMock{ + std::make_unique<::testing::NiceMock>()}; GstElement m_pipeline{}; GstAppSrc m_audioSrc{}; GstAppSrc m_videoSrc{}; @@ -72,6 +75,7 @@ class HandleBusMessageTest : public testing::Test m_context.pipeline = &m_pipeline; m_context.streamInfo.emplace(firebolt::rialto::MediaSourceType::AUDIO, GST_ELEMENT(&m_audioSrc)); m_context.streamInfo.emplace(firebolt::rialto::MediaSourceType::VIDEO, GST_ELEMENT(&m_videoSrc)); + m_context.gstProfiler = std::move(m_gstProfilerMock); } void expectFreeErrorMessage() @@ -227,8 +231,8 @@ TEST_F(HandleBusMessageTest, shouldNotHandleStateChangedMessageWhenGstPlayerClie EXPECT_CALL(*m_gstWrapper, gstMessageParseStateChanged(&m_message, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(2).WillRepeatedly(Return("Ready")); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(2).WillRepeatedly(Return("Null")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(1).WillRepeatedly(Return("Ready")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(1).WillRepeatedly(Return("Null")); EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(pending)).WillOnce(Return("Void")); EXPECT_CALL(*m_gstWrapper, gstDebugBinToDotFileWithTs(GST_BIN(&m_pipeline), _, _)); EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); @@ -250,8 +254,8 @@ TEST_F(HandleBusMessageTest, shouldHandleStateChangedToNullMessage) EXPECT_CALL(*m_gstWrapper, gstMessageParseStateChanged(&m_message, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(2).WillRepeatedly(Return("Ready")); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(2).WillRepeatedly(Return("Null")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(1).WillRepeatedly(Return("Ready")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(1).WillRepeatedly(Return("Null")); EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(pending)).WillOnce(Return("Void")); EXPECT_CALL(*m_gstWrapper, gstDebugBinToDotFileWithTs(GST_BIN(&m_pipeline), _, _)); EXPECT_CALL(m_gstPlayerClient, notifyPlaybackState(firebolt::rialto::PlaybackState::STOPPED)); @@ -276,12 +280,13 @@ TEST_F(HandleBusMessageTest, shouldHandleStateChangedToPausedMessage) EXPECT_CALL(*m_gstWrapper, gstMessageParseStateChanged(&m_message, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); EXPECT_CALL(m_gstPlayer, hasSourceType(firebolt::rialto::MediaSourceType::SUBTITLE)).WillOnce(Return(false)); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(2).WillRepeatedly(Return("Ready")); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(2).WillRepeatedly(Return("Paused")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(1).WillRepeatedly(Return("Ready")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(1).WillRepeatedly(Return("Paused")); EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(pending)).WillOnce(Return("Void")); EXPECT_CALL(*m_gstWrapper, gstDebugBinToDotFileWithTs(GST_BIN(&m_pipeline), _, _)); EXPECT_CALL(m_gstPlayerClient, notifyPlaybackState(firebolt::rialto::PlaybackState::PAUSED)); - EXPECT_CALL(m_gstPlayer, notifyPlaybackInfo()); + EXPECT_CALL(m_gstPlayer, stopPositionReportingAndCheckAudioUnderflowTimer()); + EXPECT_CALL(m_gstPlayer, startNotifyPlaybackInfoTimer()); EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); @@ -305,12 +310,13 @@ TEST_F(HandleBusMessageTest, shouldHandleStateChangedToPausedMessageWhenSyncFlus EXPECT_CALL(*m_gstWrapper, gstMessageParseStateChanged(&m_message, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); EXPECT_CALL(m_gstPlayer, hasSourceType(firebolt::rialto::MediaSourceType::SUBTITLE)).WillOnce(Return(false)); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(2).WillRepeatedly(Return("Ready")); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(2).WillRepeatedly(Return("Paused")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(1).WillRepeatedly(Return("Ready")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(1).WillRepeatedly(Return("Paused")); EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(pending)).WillOnce(Return("Void")); EXPECT_CALL(*m_gstWrapper, gstDebugBinToDotFileWithTs(GST_BIN(&m_pipeline), _, _)); EXPECT_CALL(m_gstPlayerClient, notifyPlaybackState(firebolt::rialto::PlaybackState::PAUSED)); - EXPECT_CALL(m_gstPlayer, notifyPlaybackInfo()); + EXPECT_CALL(m_gstPlayer, stopPositionReportingAndCheckAudioUnderflowTimer()); + EXPECT_CALL(m_gstPlayer, startNotifyPlaybackInfoTimer()); EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillRepeatedly(Return(kFlushOngoing)); EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); @@ -333,10 +339,12 @@ TEST_F(HandleBusMessageTest, shouldSkipHandlingStateChangedToPausedMessageWhenAs EXPECT_CALL(*m_gstWrapper, gstMessageParseStateChanged(&m_message, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(2).WillRepeatedly(Return("Ready")); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(2).WillRepeatedly(Return("Paused")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(1).WillRepeatedly(Return("Ready")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(1).WillRepeatedly(Return("Paused")); EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(pending)).WillOnce(Return("Void")); EXPECT_CALL(*m_gstWrapper, gstDebugBinToDotFileWithTs(GST_BIN(&m_pipeline), _, _)); + EXPECT_CALL(m_gstPlayer, stopPositionReportingAndCheckAudioUnderflowTimer()); + EXPECT_CALL(m_gstPlayer, startNotifyPlaybackInfoTimer()); EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillOnce(Return(kFlushOngoing)); EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillOnce(Return(kFlushOngoing)); @@ -359,10 +367,12 @@ TEST_F(HandleBusMessageTest, shouldSkipHandlingStateChangedToPausedMessageWhenAs EXPECT_CALL(*m_gstWrapper, gstMessageParseStateChanged(&m_message, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(2).WillRepeatedly(Return("Ready")); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(2).WillRepeatedly(Return("Paused")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(1).WillRepeatedly(Return("Ready")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(1).WillRepeatedly(Return("Paused")); EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(pending)).WillOnce(Return("Void")); EXPECT_CALL(*m_gstWrapper, gstDebugBinToDotFileWithTs(GST_BIN(&m_pipeline), _, _)); + EXPECT_CALL(m_gstPlayer, stopPositionReportingAndCheckAudioUnderflowTimer()); + EXPECT_CALL(m_gstPlayer, startNotifyPlaybackInfoTimer()); EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillOnce(Return(kFlushOngoing)); EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillOnce(Return(kNoFlushOngoing)).WillOnce(Return(kFlushOngoing)); @@ -386,6 +396,8 @@ TEST_F(HandleBusMessageTest, shouldHandleStateChangedToPausedAndPendingPausedMes EXPECT_CALL(m_gstPlayer, hasSourceType(firebolt::rialto::MediaSourceType::SUBTITLE)).WillOnce(Return(false)); EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(GST_STATE_PAUSED)).WillRepeatedly(Return("Paused")); EXPECT_CALL(*m_gstWrapper, gstDebugBinToDotFileWithTs(GST_BIN(&m_pipeline), _, _)); + EXPECT_CALL(m_gstPlayer, stopPositionReportingAndCheckAudioUnderflowTimer()); + EXPECT_CALL(m_gstPlayer, startNotifyPlaybackInfoTimer()); EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); @@ -408,8 +420,8 @@ TEST_F(HandleBusMessageTest, shouldHandleStateChangedToPlayingMessage) EXPECT_CALL(*m_gstWrapper, gstMessageParseStateChanged(&m_message, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); EXPECT_CALL(m_gstPlayer, hasSourceType(firebolt::rialto::MediaSourceType::SUBTITLE)).WillOnce(Return(false)); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(2).WillRepeatedly(Return("Ready")); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(2).WillRepeatedly(Return("Playing")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(1).WillRepeatedly(Return("Ready")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(1).WillRepeatedly(Return("Playing")); EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(pending)).WillOnce(Return("Void")); EXPECT_CALL(*m_gstWrapper, gstDebugBinToDotFileWithTs(GST_BIN(&m_pipeline), _, _)); EXPECT_CALL(m_gstPlayer, startPositionReportingAndCheckAudioUnderflowTimer()); @@ -417,7 +429,6 @@ TEST_F(HandleBusMessageTest, shouldHandleStateChangedToPlayingMessage) EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); - EXPECT_CALL(m_gstPlayer, executePostponedFlushes()); firebolt::rialto::server::tasks::generic::HandleBusMessage task{m_context, m_gstPlayer, &m_gstPlayerClient, m_gstWrapper, m_glibWrapper, &m_message, @@ -441,8 +452,8 @@ TEST_F(HandleBusMessageTest, shouldHandleStateChangedToPlayingMessageWhenSyncFlu EXPECT_CALL(*m_gstWrapper, gstMessageParseStateChanged(&m_message, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); EXPECT_CALL(m_gstPlayer, hasSourceType(firebolt::rialto::MediaSourceType::SUBTITLE)).WillOnce(Return(false)); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(2).WillRepeatedly(Return("Ready")); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(2).WillRepeatedly(Return("Playing")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(1).WillRepeatedly(Return("Ready")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(1).WillRepeatedly(Return("Playing")); EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(pending)).WillOnce(Return("Void")); EXPECT_CALL(*m_gstWrapper, gstDebugBinToDotFileWithTs(GST_BIN(&m_pipeline), _, _)); EXPECT_CALL(m_gstPlayer, startPositionReportingAndCheckAudioUnderflowTimer()); @@ -450,7 +461,6 @@ TEST_F(HandleBusMessageTest, shouldHandleStateChangedToPlayingMessageWhenSyncFlu EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillRepeatedly(Return(kIsFlushOngoing)); EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); - EXPECT_CALL(m_gstPlayer, executePostponedFlushes()); firebolt::rialto::server::tasks::generic::HandleBusMessage task{m_context, m_gstPlayer, &m_gstPlayerClient, m_gstWrapper, m_glibWrapper, &m_message, @@ -473,14 +483,13 @@ TEST_F(HandleBusMessageTest, shouldSkipHandlingStateChangedToPlayingMessageWhenA EXPECT_CALL(*m_gstWrapper, gstMessageParseStateChanged(&m_message, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(2).WillRepeatedly(Return("Ready")); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(2).WillRepeatedly(Return("Playing")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(1).WillRepeatedly(Return("Ready")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(1).WillRepeatedly(Return("Playing")); EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(pending)).WillOnce(Return("Void")); EXPECT_CALL(*m_gstWrapper, gstDebugBinToDotFileWithTs(GST_BIN(&m_pipeline), _, _)); EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillOnce(Return(kIsFlushOngoing)); EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillOnce(Return(kIsFlushOngoing)); - EXPECT_CALL(m_gstPlayer, executePostponedFlushes()); firebolt::rialto::server::tasks::generic::HandleBusMessage task{m_context, m_gstPlayer, &m_gstPlayerClient, m_gstWrapper, m_glibWrapper, &m_message, @@ -501,14 +510,13 @@ TEST_F(HandleBusMessageTest, shouldSkipHandlingStateChangedToPlayingMessageWhenA EXPECT_CALL(*m_gstWrapper, gstMessageParseStateChanged(&m_message, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(2).WillRepeatedly(Return("Ready")); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(2).WillRepeatedly(Return("Playing")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(1).WillRepeatedly(Return("Ready")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(1).WillRepeatedly(Return("Playing")); EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(pending)).WillOnce(Return("Void")); EXPECT_CALL(*m_gstWrapper, gstDebugBinToDotFileWithTs(GST_BIN(&m_pipeline), _, _)); EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillOnce(Return(kIsFlushOngoing)); EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillOnce(Return(kNoFlushOngoing)).WillOnce(Return(kIsFlushOngoing)); - EXPECT_CALL(m_gstPlayer, executePostponedFlushes()); firebolt::rialto::server::tasks::generic::HandleBusMessage task{m_context, m_gstPlayer, &m_gstPlayerClient, m_gstWrapper, m_glibWrapper, &m_message, @@ -528,8 +536,8 @@ TEST_F(HandleBusMessageTest, shouldHandleStateChangedToPlayingMessageAndSetPendi EXPECT_CALL(*m_gstWrapper, gstMessageParseStateChanged(&m_message, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); EXPECT_CALL(m_gstPlayer, hasSourceType(firebolt::rialto::MediaSourceType::SUBTITLE)).WillOnce(Return(false)); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(2).WillRepeatedly(Return("Ready")); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(2).WillRepeatedly(Return("Playing")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(1).WillRepeatedly(Return("Ready")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(1).WillRepeatedly(Return("Playing")); EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(pending)).WillOnce(Return("Void")); EXPECT_CALL(*m_gstWrapper, gstDebugBinToDotFileWithTs(GST_BIN(&m_pipeline), _, _)); EXPECT_CALL(m_gstPlayer, startPositionReportingAndCheckAudioUnderflowTimer()); @@ -538,7 +546,6 @@ TEST_F(HandleBusMessageTest, shouldHandleStateChangedToPlayingMessageAndSetPendi EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); - EXPECT_CALL(m_gstPlayer, executePostponedFlushes()); firebolt::rialto::server::tasks::generic::HandleBusMessage task{m_context, m_gstPlayer, &m_gstPlayerClient, m_gstWrapper, m_glibWrapper, &m_message, diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/NeedDataTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/NeedDataTest.cpp index af3ccd199..46e67cd0e 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/NeedDataTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/NeedDataTest.cpp @@ -70,16 +70,6 @@ TEST_F(NeedDataTest, shouldSkipToNotifyNeedAudioDataWhenAnotherOneIsPending) checkNeedDataPendingForAudioOnly(); } -TEST_F(NeedDataTest, shouldSkipToNotifyNeedAudioDataWhenAudioSourceIsRemoved) -{ - setContextStreamInfo(firebolt::rialto::MediaSourceType::AUDIO); - setContextStreamInfo(firebolt::rialto::MediaSourceType::VIDEO); - setContextAudioSourceRemoved(); - triggerNeedDataAudio(); - checkNeedDataForAudioOnly(); - checkNoNeedDataPendingForBothSources(); -} - TEST_F(NeedDataTest, shouldNotifyNeedVideoData) { setContextStreamInfo(firebolt::rialto::MediaSourceType::AUDIO); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/RemoveSourceTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/RemoveSourceTest.cpp deleted file mode 100644 index a9153ff75..000000000 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/RemoveSourceTest.cpp +++ /dev/null @@ -1,72 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2022 Sky UK - * - * 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. - */ - -#include "GenericTasksTestsBase.h" - -class RemoveSourceTest : public GenericTasksTestsBase -{ -protected: - RemoveSourceTest() - { - setContextStreamInfo(firebolt::rialto::MediaSourceType::AUDIO); - setContextStreamInfo(firebolt::rialto::MediaSourceType::VIDEO); - setContextNeedDataAudioOnly(); - setContextAudioBuffer(); - setContextNeedDataPendingAudioOnly(true); - } -}; - -TEST_F(RemoveSourceTest, shouldRemoveAudioSourceWithoutFlushing) -{ - setContextStreamInfoEmpty(); - shouldInvalidateActiveAudioRequests(); - triggerRemoveSourceAudio(); - checkAudioSourceRemoved(); -} - -TEST_F(RemoveSourceTest, shouldNotRemoveVideoSource) -{ - triggerRemoveSourceVideo(); - checkNeedDataForAudioOnly(); - checkNeedDataPendingForAudioOnly(); - checkAudioSourceNotRemoved(); -} - -TEST_F(RemoveSourceTest, shouldRemoveAudioSource) -{ - shouldInvalidateActiveAudioRequests(); - shouldDisableAudioFlag(); - shouldFlushAudioSrcSuccess(); - triggerRemoveSourceAudio(); - checkNoMoreNeedData(); - checkNoNeedDataPendingForBothSources(); - checkAudioSourceRemoved(); - checkBuffersEmpty(); -} - -TEST_F(RemoveSourceTest, shouldRemoveAudioSourceFlushEventError) -{ - shouldInvalidateActiveAudioRequests(); - shouldDisableAudioFlag(); - shouldFlushAudioSrcFailure(); - triggerRemoveSourceAudio(); - checkNoMoreNeedData(); - checkNoNeedDataPendingForBothSources(); - checkAudioSourceRemoved(); -} diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/SetupElementTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/SetupElementTest.cpp index 89b5bc979..6f2fa12df 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/SetupElementTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/SetupElementTest.cpp @@ -77,6 +77,12 @@ TEST_F(SetupElementTest, shouldSetupAudioElementWithPendingBufferingLimit) triggerSetupElement(); } +TEST_F(SetupElementTest, shouldSetupAudioElementWithIsLiveParameter) +{ + shouldSetupAudioDecoderElementWithIsLiveParameter(); + triggerSetupElement(); +} + TEST_F(SetupElementTest, shouldSetupVideoElementWithPendingRenderFrame) { shouldSetupVideoSinkElementWithPendingRenderFrame(); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/UpdatePlaybackGroupTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/UpdatePlaybackGroupTest.cpp index dfed351b4..74312e50f 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/UpdatePlaybackGroupTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/UpdatePlaybackGroupTest.cpp @@ -72,3 +72,19 @@ TEST_F(UpdatePlaybackGroupTest, shouldTriggerUseBuffering) triggerUpdatePlaybackGroup(); checkPlaybackGroupAdded(); } + +TEST_F(UpdatePlaybackGroupTest, shouldLinkTypefindWithParser) +{ + shouldSuccessfullyFindTypefindAndParent(); + shouldLinkTypefindAndParser(); + triggerUpdatePlaybackGroup(); + checkPlaybackGroupAdded(); +} + +TEST_F(UpdatePlaybackGroupTest, shouldFailToLinkTypefindWithParser) +{ + shouldSuccessfullyFindTypefindAndParent(); + shouldFailToLinkTypefindAndParser(); + triggerUpdatePlaybackGroup(); + checkPlaybackGroupAdded(); +} diff --git a/tests/unittests/media/server/gstplayer/profiler/GstProfilerTests.cpp b/tests/unittests/media/server/gstplayer/profiler/GstProfilerTests.cpp new file mode 100644 index 000000000..0a31abe16 --- /dev/null +++ b/tests/unittests/media/server/gstplayer/profiler/GstProfilerTests.cpp @@ -0,0 +1,563 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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. + */ + +#include "GlibWrapperMock.h" +#include "GstWrapperMock.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "GstProfiler.h" + +using namespace firebolt::rialto::server; +using namespace firebolt::rialto::wrappers; + +using ::testing::_; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::ReturnNull; +using ::testing::StrEq; +using ::testing::StrictMock; + +class GstProfilerTests : public ::testing::Test +{ +protected: + std::shared_ptr> m_gstWrapperMock; + std::shared_ptr> m_glibWrapperMock; + std::unique_ptr m_gstProfiler; + std::optional m_profilerEnabledEnv; + std::optional m_profilerDumpFileNameEnv; + GstElement *m_realElement{nullptr}; + + GstElement m_pipeline = {}; + GstElement m_element = {}; + GstPad m_pad = {}; + + void SetUp() override + { + saveEnv("PROFILER_ENABLED", m_profilerEnabledEnv); + saveEnv("PROFILER_DUMP_FILE_NAME", m_profilerDumpFileNameEnv); + + m_gstWrapperMock = std::make_shared>(); + m_glibWrapperMock = std::make_shared>(); + + gst_init(nullptr, nullptr); + m_realElement = gst_element_factory_make("fakesrc", "profiler-test-source"); + ASSERT_NE(m_realElement, nullptr); + } + + void TearDown() override + { + if (m_realElement) + { + gst_object_unref(m_realElement); + m_realElement = nullptr; + } + + if (m_profilerEnabledEnv) + setenv("PROFILER_ENABLED", m_profilerEnabledEnv->c_str(), 1); + else + unsetenv("PROFILER_ENABLED"); + + if (m_profilerDumpFileNameEnv) + setenv("PROFILER_DUMP_FILE_NAME", m_profilerDumpFileNameEnv->c_str(), 1); + else + unsetenv("PROFILER_DUMP_FILE_NAME"); + } + + void setProfilerEnabledEnv(bool enabled) { setenv("PROFILER_ENABLED", enabled ? "true" : "false", 1); } + void setProfilerDumpFileEnv(const std::string &path) { setenv("PROFILER_DUMP_FILE_NAME", path.c_str(), 1); } + + static void saveEnv(const char *name, std::optional &value) + { + if (const char *env = std::getenv(name)) + value = env; + else + value = std::nullopt; + } + + void createGstProfiler(GstElement *pipeline = nullptr) + { + const char *profilerEnabledEnv = std::getenv("PROFILER_ENABLED"); + const bool expectPipelineRef = pipeline && profilerEnabledEnv && std::string_view{profilerEnabledEnv} == "true"; + if (expectPipelineRef) + { + EXPECT_CALL(*m_gstWrapperMock, gstObjectRef(pipeline)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(pipeline)); + } + + EXPECT_NO_THROW(m_gstProfiler = std::make_unique(pipeline, m_gstWrapperMock, m_glibWrapperMock)); + ASSERT_NE(m_gstProfiler, nullptr); + } + + void expectElementRecognizedAsSource(GstElement *element) + { + EXPECT_CALL(*m_gstWrapperMock, gstElementClassGetMetadata(_, _)).WillOnce(Return("Source")); + + EXPECT_CALL(*m_glibWrapperMock, gStrrstr(_, _)).WillOnce(Return(const_cast("Source"))); + } +}; + +/** + * Test that GstProfiler can be created with null pipeline. + */ +TEST_F(GstProfilerTests, CreateWithNullPipeline) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + EXPECT_TRUE(m_gstProfiler->createRecord("CreateWithNullPipeline").has_value()); +} + +/** + * Test that GstProfiler can be created with valid pipeline. + */ +TEST_F(GstProfilerTests, CreateWithPipeline) +{ + setProfilerEnabledEnv(true); + createGstProfiler(&m_pipeline); + EXPECT_TRUE(m_gstProfiler->createRecord("CreateWithPipeline").has_value()); +} + +/** + * Test that GstProfiler can create a record with stage only. + */ +TEST_F(GstProfilerTests, CreateRecordStageOnly) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + + EXPECT_NO_THROW({ [[maybe_unused]] const auto id = m_gstProfiler->createRecord("PipelineCreated"); }); +} + +/** + * Test that GstProfiler can create a record with stage and info. + */ +TEST_F(GstProfilerTests, CreateRecordStageAndInfo) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + + EXPECT_NO_THROW({ [[maybe_unused]] const auto id = m_gstProfiler->createRecord("SourceAttached", "video"); }); +} + +/** + * Test that GstProfiler can log a record. + */ +TEST_F(GstProfilerTests, LogRecord) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + + EXPECT_NO_THROW(m_gstProfiler->logRecord(1)); +} + +TEST_F(GstProfilerTests, DumpToFileCreatesAndAppendsDump) +{ + setProfilerEnabledEnv(true); + const auto suffix = std::to_string(std::random_device{}()); + const std::string path = std::string{"/tmp/rialto_gst_profiler_ut_dump_"} + suffix + ".txt"; + setProfilerDumpFileEnv(path); + createGstProfiler(); + + ASSERT_TRUE(m_gstProfiler->createRecord("Pipeline Created").has_value()); + ASSERT_TRUE(m_gstProfiler->createRecord("Pipeline Terminated").has_value()); + + EXPECT_NO_THROW(m_gstProfiler->dumpToFile()); + EXPECT_NO_THROW(m_gstProfiler->dumpToFile()); + + std::ifstream in(path); + ASSERT_TRUE(in.good()); + + const std::string content((std::istreambuf_iterator(in)), std::istreambuf_iterator()); + EXPECT_NE(content.find("Pipeline Created"), std::string::npos); + EXPECT_NE(content.find("Pipeline Terminated"), std::string::npos); + EXPECT_NE(content.find("Pipeline Created"), content.rfind("Pipeline Created")); + + std::remove(path.c_str()); +} + +TEST_F(GstProfilerTests, DumpToFileUsesCachedEnvValue) +{ + setProfilerEnabledEnv(true); + const auto suffix = std::to_string(std::random_device{}()); + const std::string path = std::string{"/tmp/rialto_gst_profiler_cached_dump_"} + suffix + ".txt"; + setProfilerDumpFileEnv(path); + createGstProfiler(); + + unsetenv("PROFILER_DUMP_FILE_NAME"); + ASSERT_TRUE(m_gstProfiler->createRecord("CachedDumpStage").has_value()); + + EXPECT_NO_THROW(m_gstProfiler->dumpToFile()); + + std::ifstream in(path); + ASSERT_TRUE(in.good()); + + const std::string content((std::istreambuf_iterator(in)), std::istreambuf_iterator()); + EXPECT_NE(content.find("CachedDumpStage"), std::string::npos); + + std::remove(path.c_str()); +} + +/** + * Test that GstProfiler can log pipeline metrics. + */ +TEST_F(GstProfilerTests, LogPipelineSummary) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + + EXPECT_NO_THROW(m_gstProfiler->logPipelineSummary()); +} + +/** + * Test that disabled profiler prevents record creation and scheduling. + */ +TEST_F(GstProfilerTests, DisabledProfilerPreventsRecordCreationAndScheduling) +{ + setProfilerEnabledEnv(false); + createGstProfiler(); + + EXPECT_FALSE(m_gstProfiler->createRecord("Pipeline Created").has_value()); + + EXPECT_NO_THROW(m_gstProfiler->scheduleGstElementRecord(m_realElement)); +} + +/** + * Test that enabled profiler allows scheduling path to proceed up to pad lookup. + */ +TEST_F(GstProfilerTests, EnabledProfilerAllowsScheduling) +{ + setProfilerEnabledEnv(true); + EXPECT_NO_THROW(m_gstProfiler = std::make_unique(nullptr, m_gstWrapperMock, m_glibWrapperMock)); + ASSERT_NE(m_gstProfiler, nullptr); + ASSERT_TRUE(m_gstProfiler->createRecord("EnabledProfilerAllowsScheduling").has_value()); + + expectElementRecognizedAsSource(m_realElement); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStaticPad(m_realElement, StrEq("src"))).WillOnce(Return(nullptr)); + + EXPECT_NO_THROW(m_gstProfiler->scheduleGstElementRecord(m_realElement)); +} + +/** + * Test that scheduling record for null element is safe. + */ +TEST_F(GstProfilerTests, ScheduleNullElementRecord) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + + EXPECT_NO_THROW(m_gstProfiler->scheduleGstElementRecord(nullptr)); +} + +/** + * Test that scheduling record for valid element is safe. + */ +TEST_F(GstProfilerTests, ScheduleElementRecord) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + + auto *factory = reinterpret_cast(0x1); + + EXPECT_CALL(*m_gstWrapperMock, gstElementGetFactory(m_realElement)).WillOnce(Return(factory)).WillOnce(Return(factory)); + EXPECT_CALL(*m_gstWrapperMock, gstElementClassGetMetadata(_, _)).WillOnce(Return("Source")); + EXPECT_CALL(*m_glibWrapperMock, gStrrstr(_, _)).WillOnce(Return(const_cast("Source"))); + + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStaticPad(m_realElement, StrEq("src"))).WillOnce(Return(&m_pad)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListIsType(_, GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) + .WillOnce(Return(false)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListIsType(_, GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(false)); + + gchar *rawName = g_strdup("videoDecoder"); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetName(m_realElement)).WillOnce(Return(rawName)); + EXPECT_CALL(*m_glibWrapperMock, gFree(rawName)).WillOnce(Invoke([](gpointer ptr) { g_free(ptr); })); + + EXPECT_CALL(*m_gstWrapperMock, gstPadAddProbe(&m_pad, _, _, _, _)) + .WillOnce(Invoke( + [](GstPad *pad, GstPadProbeType mask, GstPadProbeCallback callback, gpointer userData, + GDestroyNotify destroyData) -> gulong + { + EXPECT_EQ(destroyData, nullptr); + return 1; + })); + + EXPECT_CALL(*m_gstWrapperMock, gstPadRemoveProbe(&m_pad, 1)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_pad)); + + EXPECT_NO_THROW(m_gstProfiler->scheduleGstElementRecord(m_realElement)); +} + +/** + * Test that scheduled probe creates record with normalized video info. + */ +TEST_F(GstProfilerTests, ScheduleElementRecordCreatesProbeRecordWithVideoInfo) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + + auto *factory = reinterpret_cast(0x1); + + EXPECT_CALL(*m_gstWrapperMock, gstElementGetFactory(m_realElement)).WillOnce(Return(factory)); + EXPECT_CALL(*m_gstWrapperMock, gstElementClassGetMetadata(_, _)).WillOnce(Return("Source")); + EXPECT_CALL(*m_glibWrapperMock, gStrrstr(_, _)).WillOnce(Return(const_cast("Source"))); + + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStaticPad(m_realElement, StrEq("src"))).WillOnce(Return(&m_pad)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListIsType(_, GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) + .WillOnce(Return(true)); + + GstPadProbeCallback callback = nullptr; + gpointer userData = nullptr; + EXPECT_CALL(*m_gstWrapperMock, gstPadAddProbe(&m_pad, _, _, _, _)) + .WillOnce(Invoke( + [&callback, &userData](GstPad *pad, GstPadProbeType mask, GstPadProbeCallback probeCallback, + gpointer probeUserData, GDestroyNotify destroyData) -> gulong + { + callback = probeCallback; + userData = probeUserData; + EXPECT_EQ(destroyData, nullptr); + + return 1; + })); + + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_pad)); + + m_gstProfiler->scheduleGstElementRecord(m_realElement); + + ASSERT_NE(callback, nullptr); + ASSERT_NE(userData, nullptr); + GstBuffer *buffer = gst_buffer_new(); + GstPadProbeInfo info{}; + info.type = GST_PAD_PROBE_TYPE_BUFFER; + info.data = buffer; + + EXPECT_EQ(callback(&m_pad, &info, userData), GST_PAD_PROBE_REMOVE); + + gst_buffer_unref(buffer); + + const auto records = m_gstProfiler->getRecords(); + ASSERT_FALSE(records.empty()); + EXPECT_EQ(records.back().stage, "Source FB Exit"); + EXPECT_EQ(records.back().info, "Video"); +} + +/** + * Test that scheduled probe falls back to element name classification when media type helpers fail. + */ +TEST_F(GstProfilerTests, ScheduleElementRecordCreatesProbeRecordWithVideoInfoFromNameFallback) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + + auto *factory = reinterpret_cast(0x1); + + EXPECT_CALL(*m_gstWrapperMock, gstElementGetFactory(m_realElement)).WillOnce(Return(factory)).WillOnce(Return(factory)); + EXPECT_CALL(*m_gstWrapperMock, gstElementClassGetMetadata(_, _)).WillOnce(Return("Source")); + EXPECT_CALL(*m_glibWrapperMock, gStrrstr(_, _)).WillOnce(Return(const_cast("Source"))); + + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStaticPad(m_realElement, StrEq("src"))).WillOnce(Return(&m_pad)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListIsType(_, GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) + .WillOnce(Return(false)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListIsType(_, GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(false)); + + gchar *rawName = g_strdup("videoDecoder"); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetName(m_realElement)).WillOnce(Return(rawName)); + EXPECT_CALL(*m_glibWrapperMock, gFree(rawName)).WillOnce(Invoke([](gpointer ptr) { g_free(ptr); })); + + GstPadProbeCallback callback = nullptr; + gpointer userData = nullptr; + EXPECT_CALL(*m_gstWrapperMock, gstPadAddProbe(&m_pad, _, _, _, _)) + .WillOnce(Invoke( + [&callback, &userData](GstPad *pad, GstPadProbeType mask, GstPadProbeCallback probeCallback, + gpointer probeUserData, GDestroyNotify destroyData) -> gulong + { + callback = probeCallback; + userData = probeUserData; + EXPECT_EQ(destroyData, nullptr); + + return 1; + })); + + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_pad)); + + m_gstProfiler->scheduleGstElementRecord(m_realElement); + + ASSERT_NE(callback, nullptr); + ASSERT_NE(userData, nullptr); + GstBuffer *buffer = gst_buffer_new(); + GstPadProbeInfo info{}; + info.type = GST_PAD_PROBE_TYPE_BUFFER; + info.data = buffer; + + EXPECT_EQ(callback(&m_pad, &info, userData), GST_PAD_PROBE_REMOVE); + + gst_buffer_unref(buffer); + + const auto records = m_gstProfiler->getRecords(); + ASSERT_FALSE(records.empty()); + EXPECT_EQ(records.back().stage, "Source FB Exit"); + EXPECT_EQ(records.back().info, "Video"); +} + +/** + * Test that scheduled probe falls back to raw element name when media type cannot be classified. + */ +TEST_F(GstProfilerTests, ScheduleElementRecordFallsBackToRawElementName) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + + auto *factory = reinterpret_cast(0x1); + + EXPECT_CALL(*m_gstWrapperMock, gstElementGetFactory(m_realElement)).WillOnce(Return(factory)).WillOnce(Return(factory)); + EXPECT_CALL(*m_gstWrapperMock, gstElementClassGetMetadata(_, _)).WillOnce(Return("Source")); + EXPECT_CALL(*m_glibWrapperMock, gStrrstr(_, _)).WillOnce(Return(const_cast("Source"))); + + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStaticPad(m_realElement, StrEq("src"))).WillOnce(Return(&m_pad)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListIsType(_, GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) + .WillOnce(Return(false)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListIsType(_, GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(false)); + + gchar *rawName = g_strdup("custom-element"); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetName(m_realElement)).WillOnce(Return(rawName)); + EXPECT_CALL(*m_glibWrapperMock, gFree(rawName)).WillOnce(Invoke([](gpointer ptr) { g_free(ptr); })); + + GstPadProbeCallback callback = nullptr; + gpointer userData = nullptr; + EXPECT_CALL(*m_gstWrapperMock, gstPadAddProbe(&m_pad, _, _, _, _)) + .WillOnce(Invoke( + [&callback, &userData](GstPad *pad, GstPadProbeType mask, GstPadProbeCallback probeCallback, + gpointer probeUserData, GDestroyNotify destroyData) -> gulong + { + callback = probeCallback; + userData = probeUserData; + EXPECT_EQ(destroyData, nullptr); + + return 1; + })); + + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_pad)); + + m_gstProfiler->scheduleGstElementRecord(m_realElement); + + ASSERT_NE(callback, nullptr); + ASSERT_NE(userData, nullptr); + GstBuffer *buffer = gst_buffer_new(); + GstPadProbeInfo info{}; + info.type = GST_PAD_PROBE_TYPE_BUFFER; + info.data = buffer; + + EXPECT_EQ(callback(&m_pad, &info, userData), GST_PAD_PROBE_REMOVE); + + gst_buffer_unref(buffer); + + const auto records = m_gstProfiler->getRecords(); + ASSERT_FALSE(records.empty()); + EXPECT_EQ(records.back().stage, "Source FB Exit"); + EXPECT_EQ(records.back().info, "custom-element"); +} + +/** + * Test that scheduling record handles missing src pad. + */ +TEST_F(GstProfilerTests, ScheduleElementRecordNoPad) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + + expectElementRecognizedAsSource(m_realElement); + + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStaticPad(m_realElement, StrEq("src"))).WillOnce(Return(nullptr)); + + EXPECT_NO_THROW(m_gstProfiler->scheduleGstElementRecord(m_realElement)); +} + +/** + * Test that scheduling record handles probe installation failure without leaking context ownership. + */ +TEST_F(GstProfilerTests, ScheduleElementRecordProbeAddFails) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + + auto *factory = reinterpret_cast(0x1); + + EXPECT_CALL(*m_gstWrapperMock, gstElementGetFactory(m_realElement)).WillOnce(Return(factory)).WillOnce(Return(factory)); + EXPECT_CALL(*m_gstWrapperMock, gstElementClassGetMetadata(_, _)).WillOnce(Return("Source")); + EXPECT_CALL(*m_glibWrapperMock, gStrrstr(_, _)).WillOnce(Return(const_cast("Source"))); + + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStaticPad(m_realElement, StrEq("src"))).WillOnce(Return(&m_pad)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListIsType(_, GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) + .WillOnce(Return(false)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListIsType(_, GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(false)); + + gchar *rawName = g_strdup("videoDecoder"); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetName(m_realElement)).WillOnce(Return(rawName)); + EXPECT_CALL(*m_glibWrapperMock, gFree(rawName)).WillOnce(Invoke([](gpointer ptr) { g_free(ptr); })); + + EXPECT_CALL(*m_gstWrapperMock, gstPadAddProbe(&m_pad, _, _, _, _)).WillOnce(Return(0)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_pad)); + + EXPECT_NO_THROW(m_gstProfiler->scheduleGstElementRecord(m_realElement)); +} + +/** + * Test that GstProfiler can be destroyed after creation with pipeline. + */ +TEST_F(GstProfilerTests, DestroyAfterCreateWithPipeline) +{ + setProfilerEnabledEnv(true); + createGstProfiler(&m_pipeline); + + EXPECT_NO_THROW(m_gstProfiler.reset()); +} + +/** + * Test that public API can be called multiple times. + */ +TEST_F(GstProfilerTests, MultipleCalls) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + + expectElementRecognizedAsSource(m_realElement); + + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStaticPad(m_realElement, StrEq("src"))).WillOnce(Return(nullptr)); + + EXPECT_NO_THROW({ + [[maybe_unused]] const auto id1 = m_gstProfiler->createRecord("PipelineCreated"); + [[maybe_unused]] const auto id2 = m_gstProfiler->createRecord("AllSourcesAttached", "audio+video"); + m_gstProfiler->logRecord(1); + m_gstProfiler->scheduleGstElementRecord(m_realElement); + m_gstProfiler->logPipelineSummary(); + }); +} diff --git a/tests/unittests/media/server/gstplayer/webAudioPlayer/CreateTest.cpp b/tests/unittests/media/server/gstplayer/webAudioPlayer/CreateTest.cpp index 0a61ceeb5..a0fa43a6b 100644 --- a/tests/unittests/media/server/gstplayer/webAudioPlayer/CreateTest.cpp +++ b/tests/unittests/media/server/gstplayer/webAudioPlayer/CreateTest.cpp @@ -500,7 +500,7 @@ TEST_F(RialtoServerCreateGstWebAudioPlayerTest, createGstDispatcherThreadFailure expectInitAppSrc(); expectAddElementsAutoAudioSink(); expectInitWorkerThread(); - EXPECT_CALL(m_gstDispatcherThreadFactoryMock, createGstDispatcherThread(_, _, _)).WillOnce(Return(nullptr)); + EXPECT_CALL(m_gstDispatcherThreadFactoryMock, createGstDispatcherThread(_, _, _, _)).WillOnce(Return(nullptr)); // Reset worker thread and pipeline on failure gstPlayerWillBeDestroyed(); diff --git a/tests/unittests/media/server/gstplayer/webAudioPlayer/common/GstWebAudioPlayerTestCommon.cpp b/tests/unittests/media/server/gstplayer/webAudioPlayer/common/GstWebAudioPlayerTestCommon.cpp index 96980db37..42e890c8d 100644 --- a/tests/unittests/media/server/gstplayer/webAudioPlayer/common/GstWebAudioPlayerTestCommon.cpp +++ b/tests/unittests/media/server/gstplayer/webAudioPlayer/common/GstWebAudioPlayerTestCommon.cpp @@ -107,7 +107,7 @@ void GstWebAudioPlayerTestCommon::expectInitWorkerThread() void GstWebAudioPlayerTestCommon::expectInitThreads() { expectInitWorkerThread(); - EXPECT_CALL(m_gstDispatcherThreadFactoryMock, createGstDispatcherThread(_, _, _)) + EXPECT_CALL(m_gstDispatcherThreadFactoryMock, createGstDispatcherThread(_, _, _, _)) .WillOnce(Return(ByMove(std::move(gstDispatcherThread)))); } diff --git a/tests/unittests/media/server/ipc/controlModuleService/ControlModuleServiceTests.cpp b/tests/unittests/media/server/ipc/controlModuleService/ControlModuleServiceTests.cpp index 89d61c1e7..d1b79780f 100644 --- a/tests/unittests/media/server/ipc/controlModuleService/ControlModuleServiceTests.cpp +++ b/tests/unittests/media/server/ipc/controlModuleService/ControlModuleServiceTests.cpp @@ -106,3 +106,12 @@ TEST_F(ControlModuleServiceTests, FactoryCreatesObject) { testFactoryCreatesObject(); } + +TEST_F(ControlModuleServiceTests, shouldRegisterClientWithoutSchemaVersion) +{ + clientWillConnect(); + sendClientConnected(); + controlServiceWillRegisterClient(); + sendRegisterClientRequestAndReceiveResponse(std::nullopt); + controlServiceWillRemoveControl(); +} diff --git a/tests/unittests/media/server/ipc/mediaKeysModuleService/MediaKeysModuleServiceTestsFixture.cpp b/tests/unittests/media/server/ipc/mediaKeysModuleService/MediaKeysModuleServiceTestsFixture.cpp index 44720cfae..e1b3dfd5b 100644 --- a/tests/unittests/media/server/ipc/mediaKeysModuleService/MediaKeysModuleServiceTestsFixture.cpp +++ b/tests/unittests/media/server/ipc/mediaKeysModuleService/MediaKeysModuleServiceTestsFixture.cpp @@ -39,7 +39,6 @@ namespace const std::string keySystem{"expectedKeySystem"}; constexpr int kHardcodedMediaKeysHandle{2}; constexpr firebolt::rialto::KeySessionType kKeySessionType{firebolt::rialto::KeySessionType::TEMPORARY}; -constexpr bool kIsLDL{false}; constexpr int kKeySessionId{3}; constexpr firebolt::rialto::MediaKeyErrorStatus kErrorStatus{firebolt::rialto::MediaKeyErrorStatus::FAIL}; constexpr firebolt::rialto::InitDataType kInitDataType{firebolt::rialto::InitDataType::CENC}; @@ -50,6 +49,7 @@ const std::vector kDrmHeader{6, 3, 8}; const std::vector kLicenseRequestMessage{3, 2, 1}; const std::vector kLicenseRenewalMessage{0, 4, 8}; const std::string kUrl{"http://"}; +constexpr firebolt::rialto::LimitedDurationLicense kLdlState{firebolt::rialto::LimitedDurationLicense::NOT_SPECIFIED}; } // namespace MATCHER_P4(LicenseRequestEventMatcher, kKeySessionId, mediaKeysHandle, requestMessage, kUrl, "") @@ -142,29 +142,31 @@ void MediaKeysModuleServiceTests::cdmServiceWillCreateKeySession() { expectRequestSuccess(); EXPECT_CALL(*m_controllerMock, getClient()).WillOnce(Return(m_clientMock)); - EXPECT_CALL(m_cdmServiceMock, createKeySession(kHardcodedMediaKeysHandle, kKeySessionType, _, kIsLDL, _)) - .WillOnce(DoAll(SetArgReferee<4>(kKeySessionId), Return(firebolt::rialto::MediaKeyErrorStatus::OK))); + EXPECT_CALL(m_cdmServiceMock, createKeySession(kHardcodedMediaKeysHandle, kKeySessionType, _, _)) + .WillOnce(DoAll(SetArgReferee<3>(kKeySessionId), Return(firebolt::rialto::MediaKeyErrorStatus::OK))); } void MediaKeysModuleServiceTests::cdmServiceWillFailToCreateKeySession() { expectRequestSuccess(); EXPECT_CALL(*m_controllerMock, getClient()).WillOnce(Return(m_clientMock)); - EXPECT_CALL(m_cdmServiceMock, createKeySession(kHardcodedMediaKeysHandle, kKeySessionType, _, kIsLDL, _)) + EXPECT_CALL(m_cdmServiceMock, createKeySession(kHardcodedMediaKeysHandle, kKeySessionType, _, _)) .WillOnce(Return(kErrorStatus)); } void MediaKeysModuleServiceTests::cdmServiceWillGenerateRequest() { expectRequestSuccess(); - EXPECT_CALL(m_cdmServiceMock, generateRequest(kHardcodedMediaKeysHandle, kKeySessionId, kInitDataType, kInitData)) + EXPECT_CALL(m_cdmServiceMock, + generateRequest(kHardcodedMediaKeysHandle, kKeySessionId, kInitDataType, kInitData, kLdlState)) .WillOnce(Return(firebolt::rialto::MediaKeyErrorStatus::OK)); } void MediaKeysModuleServiceTests::cdmServiceWillFailToGenerateRequest() { expectRequestSuccess(); - EXPECT_CALL(m_cdmServiceMock, generateRequest(kHardcodedMediaKeysHandle, kKeySessionId, kInitDataType, kInitData)) + EXPECT_CALL(m_cdmServiceMock, + generateRequest(kHardcodedMediaKeysHandle, kKeySessionId, kInitDataType, kInitData, kLdlState)) .WillOnce(Return(kErrorStatus)); } @@ -483,7 +485,6 @@ void MediaKeysModuleServiceTests::sendCreateKeySessionRequestAndReceiveResponse( request.set_media_keys_handle(kHardcodedMediaKeysHandle); request.set_session_type(convertKeySessionType(kKeySessionType)); - request.set_is_ldl(kIsLDL); m_service->createKeySession(m_controllerMock.get(), &request, &response, m_closureMock.get()); EXPECT_GE(response.key_session_id(), -1); @@ -497,7 +498,6 @@ void MediaKeysModuleServiceTests::sendCreateKeySessionRequestAndReceiveErrorResp request.set_media_keys_handle(kHardcodedMediaKeysHandle); request.set_session_type(convertKeySessionType(kKeySessionType)); - request.set_is_ldl(kIsLDL); m_service->createKeySession(m_controllerMock.get(), &request, &response, m_closureMock.get()); EXPECT_GE(response.key_session_id(), -1); @@ -511,7 +511,6 @@ void MediaKeysModuleServiceTests::sendCreateKeySessionRequestWithInvalidIpcAndRe request.set_media_keys_handle(kHardcodedMediaKeysHandle); request.set_session_type(convertKeySessionType(kKeySessionType)); - request.set_is_ldl(kIsLDL); m_service->createKeySession(m_invalidControllerMock.get(), &request, &response, m_closureMock.get()); } @@ -524,6 +523,7 @@ void MediaKeysModuleServiceTests::sendGenerateRequestRequestAndReceiveResponse() request.set_media_keys_handle(kHardcodedMediaKeysHandle); request.set_key_session_id(kKeySessionId); request.set_init_data_type(convertInitDataType(kInitDataType)); + request.set_ldl_state(convertLimitedDurationLicense(kLdlState)); for (auto it = kInitData.begin(); it != kInitData.end(); it++) { @@ -542,6 +542,7 @@ void MediaKeysModuleServiceTests::sendGenerateRequestRequestAndReceiveErrorRespo request.set_media_keys_handle(kHardcodedMediaKeysHandle); request.set_key_session_id(kKeySessionId); request.set_init_data_type(convertInitDataType(kInitDataType)); + request.set_ldl_state(convertLimitedDurationLicense(kLdlState)); for (auto it = kInitData.begin(); it != kInitData.end(); it++) { diff --git a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTests.cpp b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTests.cpp index dcad505bb..472dff026 100644 --- a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTests.cpp +++ b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTests.cpp @@ -249,6 +249,22 @@ TEST_F(MediaPipelineModuleServiceTests, shouldFailToGetPosition) sendGetPositionRequestAndReceiveResponseWithoutPositionMatch(); } +TEST_F(MediaPipelineModuleServiceTests, shouldGetDuration) +{ + mediaPipelineServiceWillCreateSession(); + sendCreateSessionRequestAndReceiveResponse(); + mediaPipelineServiceWillGetDuration(); + sendGetDurationRequestAndReceiveResponse(); +} + +TEST_F(MediaPipelineModuleServiceTests, shouldFailToGetDuration) +{ + mediaPipelineServiceWillCreateSession(); + sendCreateSessionRequestAndReceiveResponse(); + mediaPipelineServiceWillFailToGetDuration(); + sendGetDurationRequestAndReceiveResponseWithoutDurationMatch(); +} + TEST_F(MediaPipelineModuleServiceTests, shouldSetImmediateOutput) { mediaPipelineServiceWillCreateSession(); diff --git a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.cpp b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.cpp index ac3576a74..27cd7bd40 100644 --- a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.cpp +++ b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.cpp @@ -93,6 +93,7 @@ constexpr bool kUseBuffering{true}; constexpr uint64_t kStopPosition{2423}; constexpr bool kFramed{true}; constexpr bool kIsVideoMaster{true}; +constexpr bool kIsLive{true}; } // namespace MATCHER_P(AttachedSourceMatcher, source, "") @@ -284,13 +285,15 @@ void MediaPipelineModuleServiceTests::mediaPipelineServiceWillFailToDestroySessi void MediaPipelineModuleServiceTests::mediaPipelineServiceWillLoadSession() { expectRequestSuccess(); - EXPECT_CALL(m_mediaPipelineServiceMock, load(kHardcodedSessionId, kMediaType, kMimeType, kUrl)).WillOnce(Return(true)); + EXPECT_CALL(m_mediaPipelineServiceMock, load(kHardcodedSessionId, kMediaType, kMimeType, kUrl, kIsLive)) + .WillOnce(Return(true)); } void MediaPipelineModuleServiceTests::mediaPipelineServiceWillFailToLoadSession() { expectRequestFailure(); - EXPECT_CALL(m_mediaPipelineServiceMock, load(kHardcodedSessionId, kMediaType, kMimeType, kUrl)).WillOnce(Return(false)); + EXPECT_CALL(m_mediaPipelineServiceMock, load(kHardcodedSessionId, kMediaType, kMimeType, kUrl, kIsLive)) + .WillOnce(Return(false)); } void MediaPipelineModuleServiceTests::mediaPipelineServiceWillAttachSource() @@ -388,13 +391,13 @@ void MediaPipelineModuleServiceTests::mediaPipelineServiceWillFailAllSourcesAtta void MediaPipelineModuleServiceTests::mediaPipelineServiceWillPlay() { expectRequestSuccess(); - EXPECT_CALL(m_mediaPipelineServiceMock, play(kHardcodedSessionId)).WillOnce(Return(true)); + EXPECT_CALL(m_mediaPipelineServiceMock, play(kHardcodedSessionId, _)).WillOnce(Return(true)); } void MediaPipelineModuleServiceTests::mediaPipelineServiceWillFailToPlay() { expectRequestFailure(); - EXPECT_CALL(m_mediaPipelineServiceMock, play(kHardcodedSessionId)).WillOnce(Return(false)); + EXPECT_CALL(m_mediaPipelineServiceMock, play(kHardcodedSessionId, _)).WillOnce(Return(false)); } void MediaPipelineModuleServiceTests::mediaPipelineServiceWillPause() @@ -491,6 +494,24 @@ void MediaPipelineModuleServiceTests::mediaPipelineServiceWillFailToGetPosition( EXPECT_CALL(m_mediaPipelineServiceMock, getPosition(kHardcodedSessionId, _)).WillOnce(Return(false)); } +void MediaPipelineModuleServiceTests::mediaPipelineServiceWillGetDuration() +{ + expectRequestSuccess(); + EXPECT_CALL(m_mediaPipelineServiceMock, getDuration(kHardcodedSessionId, _)) + .WillOnce(Invoke( + [&](int, std::int64_t &pos) + { + pos = kDuration; + return true; + })); +} + +void MediaPipelineModuleServiceTests::mediaPipelineServiceWillFailToGetDuration() +{ + expectRequestFailure(); + EXPECT_CALL(m_mediaPipelineServiceMock, getDuration(kHardcodedSessionId, _)).WillOnce(Return(false)); +} + void MediaPipelineModuleServiceTests::mediaPipelineServiceWillSetImmediateOutput() { expectRequestSuccess(); @@ -919,6 +940,7 @@ void MediaPipelineModuleServiceTests::sendLoadRequestAndReceiveResponse() request.set_type(convertMediaType(kMediaType)); request.set_mime_type(kMimeType); request.set_url(kUrl); + request.set_is_live(kIsLive); m_service->load(m_controllerMock.get(), &request, &response, m_closureMock.get()); } @@ -1104,6 +1126,28 @@ void MediaPipelineModuleServiceTests::sendGetPositionRequestAndReceiveResponseWi m_service->getPosition(m_controllerMock.get(), &request, &response, m_closureMock.get()); } +void MediaPipelineModuleServiceTests::sendGetDurationRequestAndReceiveResponse() +{ + firebolt::rialto::GetDurationRequest request; + firebolt::rialto::GetDurationResponse response; + + request.set_session_id(kHardcodedSessionId); + + m_service->getDuration(m_controllerMock.get(), &request, &response, m_closureMock.get()); + + EXPECT_EQ(response.duration(), kDuration); +} + +void MediaPipelineModuleServiceTests::sendGetDurationRequestAndReceiveResponseWithoutDurationMatch() +{ + firebolt::rialto::GetDurationRequest request; + firebolt::rialto::GetDurationResponse response; + + request.set_session_id(kHardcodedSessionId); + + m_service->getDuration(m_controllerMock.get(), &request, &response, m_closureMock.get()); +} + void MediaPipelineModuleServiceTests::sendSetImmediateOutputRequestAndReceiveResponse() { firebolt::rialto::SetImmediateOutputRequest request; diff --git a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.h b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.h index 90f02fb4a..fe66ac0e1 100644 --- a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.h +++ b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.h @@ -74,6 +74,8 @@ class MediaPipelineModuleServiceTests : public testing::Test void mediaPipelineServiceWillFailToSetPlaybackRate(); void mediaPipelineServiceWillGetPosition(); void mediaPipelineServiceWillFailToGetPosition(); + void mediaPipelineServiceWillGetDuration(); + void mediaPipelineServiceWillFailToGetDuration(); void mediaPipelineServiceWillSetImmediateOutput(); void mediaPipelineServiceWillFailToSetImmediateOutput(); void mediaPipelineServiceWillGetImmediateOutput(); @@ -150,6 +152,8 @@ class MediaPipelineModuleServiceTests : public testing::Test void sendSetPositionRequestAndReceiveResponse(); void sendGetPositionRequestAndReceiveResponse(); void sendGetPositionRequestAndReceiveResponseWithoutPositionMatch(); + void sendGetDurationRequestAndReceiveResponse(); + void sendGetDurationRequestAndReceiveResponseWithoutDurationMatch(); void sendSetImmediateOutputRequestAndReceiveResponse(); void sendSetImmediateOutputRequestAndReceiveFail(); void sendGetImmediateOutputRequestAndReceiveResponse(); diff --git a/tests/unittests/media/server/main/CMakeLists.txt b/tests/unittests/media/server/main/CMakeLists.txt index 59d2ded8e..39943cbc4 100644 --- a/tests/unittests/media/server/main/CMakeLists.txt +++ b/tests/unittests/media/server/main/CMakeLists.txt @@ -68,7 +68,6 @@ add_gtests ( mediaKeys/GetDrmTimeTest.cpp mediaKeys/GetLastDrmErrorTest.cpp mediaKeys/SelectKeyIdTest.cpp - mediaKeys/IsNetflixPlayreadyKeySystemTest.cpp mediaKeys/PingTest.cpp mediaKeys/ReleaseKeySessionTest.cpp mediaKeys/GetMetricSystemDataTest.cpp @@ -91,7 +90,6 @@ add_gtests ( mediaKeySession/SetDrmHeaderTest.cpp mediaKeySession/GetLastDrmErrorTest.cpp mediaKeySession/SelectKeyIdTest.cpp - mediaKeySession/IsNetflixPlayreadyKeySystemTest.cpp sharedMemoryBuffer/SharedMemoryBufferTestsFixture.cpp sharedMemoryBuffer/SharedMemoryBufferTests.cpp diff --git a/tests/unittests/media/server/main/activeRequests/ActiveRequestsTests.cpp b/tests/unittests/media/server/main/activeRequests/ActiveRequestsTests.cpp index 171480c5c..89188d929 100644 --- a/tests/unittests/media/server/main/activeRequests/ActiveRequestsTests.cpp +++ b/tests/unittests/media/server/main/activeRequests/ActiveRequestsTests.cpp @@ -149,6 +149,29 @@ TEST_F(ActiveRequestsTests, shouldAddAndGetSegments) EXPECT_EQ(kSegments[0]->getType(), firebolt::rialto::MediaSourceType::AUDIO); } +TEST_F(ActiveRequestsTests, insertShouldHandleDuplicateId) +{ + EXPECT_EQ(0, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, 100)); + EXPECT_EQ(firebolt::rialto::MediaSourceType::AUDIO, m_sut.getType(0)); + + EXPECT_EQ(1, m_sut.insert(firebolt::rialto::MediaSourceType::VIDEO, 100)); + EXPECT_EQ(firebolt::rialto::MediaSourceType::VIDEO, m_sut.getType(1)); + + EXPECT_EQ(2, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, 100)); + EXPECT_EQ(firebolt::rialto::MediaSourceType::AUDIO, m_sut.getType(2)); +} + +TEST_F(ActiveRequestsTests, insertShouldResolveDuplicateIdCollision) +{ + EXPECT_EQ(0, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, 100)); + EXPECT_EQ(firebolt::rialto::MediaSourceType::AUDIO, m_sut.getType(0)); + + m_sut.insert(firebolt::rialto::MediaSourceType::VIDEO, 100); + + EXPECT_EQ(2, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, 100)); + EXPECT_EQ(firebolt::rialto::MediaSourceType::AUDIO, m_sut.getType(2)); +} + TEST_F(ActiveRequestsTests, shouldAddAndRemoveSegments) { std::vector data{'T', 'E', 'S', 'T'}; diff --git a/tests/unittests/media/server/main/mediaKeySession/CallbacksTest.cpp b/tests/unittests/media/server/main/mediaKeySession/CallbacksTest.cpp index 948ce3c90..03a4f2d2b 100644 --- a/tests/unittests/media/server/main/mediaKeySession/CallbacksTest.cpp +++ b/tests/unittests/media/server/main/mediaKeySession/CallbacksTest.cpp @@ -60,7 +60,7 @@ TEST_F(RialtoServerMediaKeySessionCallbacksTest, ProcessChallengeNoGenerateReque /** * Test that onProcessChallenge after a generateRequest for none Netflix key system notifies licenseRequest. */ -TEST_F(RialtoServerMediaKeySessionCallbacksTest, ProcessChallengeGenerateRequestNoneNetflix) +TEST_F(RialtoServerMediaKeySessionCallbacksTest, ProcessChallengeGenerateRequest) { generateRequest(); mainThreadWillEnqueueTask(); diff --git a/tests/unittests/media/server/main/mediaKeySession/CloseKeySessionTest.cpp b/tests/unittests/media/server/main/mediaKeySession/CloseKeySessionTest.cpp index 8552b6741..569ee760e 100644 --- a/tests/unittests/media/server/main/mediaKeySession/CloseKeySessionTest.cpp +++ b/tests/unittests/media/server/main/mediaKeySession/CloseKeySessionTest.cpp @@ -31,11 +31,14 @@ class RialtoServerMediaKeySessionCloseKeySessionTest : public MediaKeySessionTes TEST_F(RialtoServerMediaKeySessionCloseKeySessionTest, SuccessNetflix) { createKeySession(kNetflixKeySystem); + generateRequestPlayready(); EXPECT_CALL(*m_ocdmSessionMock, cancelChallengeData()).WillOnce(Return(MediaKeyErrorStatus::OK)); EXPECT_CALL(*m_ocdmSessionMock, cleanDecryptContext()).WillOnce(Return(MediaKeyErrorStatus::OK)); EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->closeKeySession()); + + EXPECT_CALL(*m_ocdmSessionMock, destructSession()).WillOnce(Return(MediaKeyErrorStatus::OK)); } /** @@ -56,11 +59,14 @@ TEST_F(RialtoServerMediaKeySessionCloseKeySessionTest, SuccessNoneNetflix) TEST_F(RialtoServerMediaKeySessionCloseKeySessionTest, OcdmSessionCancelChallengeDataFailure) { createKeySession(kNetflixKeySystem); + generateRequestPlayready(); EXPECT_CALL(*m_ocdmSessionMock, cancelChallengeData()).WillOnce(Return(MediaKeyErrorStatus::INVALID_STATE)); EXPECT_CALL(*m_ocdmSessionMock, cleanDecryptContext()).WillOnce(Return(MediaKeyErrorStatus::OK)); EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->closeKeySession()); + + EXPECT_CALL(*m_ocdmSessionMock, destructSession()).WillOnce(Return(MediaKeyErrorStatus::OK)); } /** @@ -69,11 +75,14 @@ TEST_F(RialtoServerMediaKeySessionCloseKeySessionTest, OcdmSessionCancelChalleng TEST_F(RialtoServerMediaKeySessionCloseKeySessionTest, OcdmSessionCleanDecryptContextFailure) { createKeySession(kNetflixKeySystem); + generateRequestPlayready(); EXPECT_CALL(*m_ocdmSessionMock, cancelChallengeData()).WillOnce(Return(MediaKeyErrorStatus::OK)); EXPECT_CALL(*m_ocdmSessionMock, cleanDecryptContext()).WillOnce(Return(MediaKeyErrorStatus::NOT_SUPPORTED)); EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->closeKeySession()); + + EXPECT_CALL(*m_ocdmSessionMock, destructSession()).WillOnce(Return(MediaKeyErrorStatus::OK)); } /** diff --git a/tests/unittests/media/server/main/mediaKeySession/CreateTest.cpp b/tests/unittests/media/server/main/mediaKeySession/CreateTest.cpp index 0e70388a2..a0b6c8da8 100644 --- a/tests/unittests/media/server/main/mediaKeySession/CreateTest.cpp +++ b/tests/unittests/media/server/main/mediaKeySession/CreateTest.cpp @@ -34,8 +34,7 @@ TEST_F(RialtoServerCreateMediaKeySessionTest, Create) EXPECT_NO_THROW(m_mediaKeySession = std::make_unique(kNetflixKeySystem, m_kKeySessionId, *m_ocdmSystemMock, m_keySessionType, - m_mediaKeysClientMock, m_isLDL, - m_mainThreadFactoryMock)); + m_mediaKeysClientMock, m_mainThreadFactoryMock)); EXPECT_NE(m_mediaKeySession, nullptr); destroyKeySession(); @@ -52,7 +51,7 @@ TEST_F(RialtoServerCreateMediaKeySessionTest, FactoryCreatesObject) EXPECT_CALL(*m_ocdmSystemMock, createSession(_)).WillOnce(Return(ByMove(std::move(m_ocdmSession)))); EXPECT_NE(factory->createMediaKeySession(kNetflixKeySystem, m_kKeySessionId, *m_ocdmSystemMock, m_keySessionType, - m_mediaKeysClientMock, m_isLDL), + m_mediaKeysClientMock), nullptr); } @@ -66,8 +65,7 @@ TEST_F(RialtoServerCreateMediaKeySessionTest, CreateMainThreadFailure) EXPECT_THROW(m_mediaKeySession = std::make_unique(kNetflixKeySystem, m_kKeySessionId, *m_ocdmSystemMock, m_keySessionType, - m_mediaKeysClientMock, m_isLDL, - m_mainThreadFactoryMock), + m_mediaKeysClientMock, m_mainThreadFactoryMock), std::runtime_error); } @@ -83,7 +81,6 @@ TEST_F(RialtoServerCreateMediaKeySessionTest, CreateOcdmSessionFailure) EXPECT_THROW(m_mediaKeySession = std::make_unique(kNetflixKeySystem, m_kKeySessionId, *m_ocdmSystemMock, m_keySessionType, - m_mediaKeysClientMock, m_isLDL, - m_mainThreadFactoryMock), + m_mediaKeysClientMock, m_mainThreadFactoryMock), std::runtime_error); } diff --git a/tests/unittests/media/server/main/mediaKeySession/GenerateRequestTest.cpp b/tests/unittests/media/server/main/mediaKeySession/GenerateRequestTest.cpp index 2e0cf179d..d967ac7de 100644 --- a/tests/unittests/media/server/main/mediaKeySession/GenerateRequestTest.cpp +++ b/tests/unittests/media/server/main/mediaKeySession/GenerateRequestTest.cpp @@ -22,28 +22,9 @@ using ::testing::DoAll; using ::testing::SetArgPointee; -MATCHER(nullptrMatcher, "") -{ - return arg == nullptr; -} - -MATCHER(notNullptrMatcher, "") -{ - return arg != nullptr; -} - -ACTION_P(memcpyChallenge, vec) -{ - memcpy(arg1, &vec[0], vec.size()); -} - class RialtoServerMediaKeySessionGenerateRequestTest : public MediaKeySessionTestBase { protected: - const InitDataType m_kInitDataType = InitDataType::CENC; - const std::vector m_kInitData{1, 2, 3}; - const std::vector m_kChallenge{'d', 'e', 'f'}; - ~RialtoServerMediaKeySessionGenerateRequestTest() { destroyKeySession(); } }; @@ -58,7 +39,7 @@ TEST_F(RialtoServerMediaKeySessionGenerateRequestTest, SuccessNoneNetflix) constructSession(m_keySessionType, m_kInitDataType, &m_kInitData[0], m_kInitData.size())) .WillOnce(Return(MediaKeyErrorStatus::OK)); - EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData)); + EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData, m_kLdlState)); // Close ocdm before destroying expectCloseKeySession(kWidevineKeySystem); @@ -71,18 +52,20 @@ TEST_F(RialtoServerMediaKeySessionGenerateRequestTest, SuccessNetflix) { createKeySession(kNetflixKeySystem); - EXPECT_CALL(*m_ocdmSessionMock, - constructSession(m_keySessionType, m_kInitDataType, &m_kInitData[0], m_kInitData.size())) - .WillOnce(Return(MediaKeyErrorStatus::OK)); - mainThreadWillEnqueueTask(); - EXPECT_CALL(*m_ocdmSessionMock, getChallengeData(m_isLDL, nullptrMatcher(), _)) - .WillOnce(DoAll(SetArgPointee<2>(m_kChallenge.size()), Return(MediaKeyErrorStatus::OK))); - EXPECT_CALL(*m_ocdmSessionMock, getChallengeData(m_isLDL, notNullptrMatcher(), _)) - .WillOnce(DoAll(memcpyChallenge(m_kChallenge), Return(MediaKeyErrorStatus::OK))); - mainThreadWillEnqueueTask(); - EXPECT_CALL(*m_mediaKeysClientMock, onLicenseRequest(m_kKeySessionId, m_kChallenge, _)); + generateRequestPlayready(); + + // Close ocdm before destroying + expectCloseKeySession(kNetflixKeySystem); +} + +/** + * Test that GenerateRequest can generate request successfully for a netflix keysystem. + */ +TEST_F(RialtoServerMediaKeySessionGenerateRequestTest, SuccessNetflixWithTwoGenerateChallengeCalls) +{ + createKeySession(kNetflixKeySystem); - EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData)); + generateRequestPlayreadyWithTwoCalls(); // Close ocdm before destroying expectCloseKeySession(kNetflixKeySystem); @@ -102,7 +85,9 @@ TEST_F(RialtoServerMediaKeySessionGenerateRequestTest, FailNetflixWhenChallengeD EXPECT_CALL(*m_ocdmSessionMock, getChallengeData(m_isLDL, nullptrMatcher(), _)) .WillOnce(Return(MediaKeyErrorStatus::OK)); - EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData)); + EXPECT_EQ(MediaKeyErrorStatus::OK, + m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData, + firebolt::rialto::LimitedDurationLicense::DISABLED)); // Close ocdm before destroying expectCloseKeySession(kNetflixKeySystem); @@ -124,7 +109,9 @@ TEST_F(RialtoServerMediaKeySessionGenerateRequestTest, FailNetflixWhenGettingCha EXPECT_CALL(*m_ocdmSessionMock, getChallengeData(m_isLDL, notNullptrMatcher(), _)) .WillOnce(DoAll(memcpyChallenge(m_kChallenge), Return(MediaKeyErrorStatus::FAIL))); - EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData)); + EXPECT_EQ(MediaKeyErrorStatus::OK, + m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData, + firebolt::rialto::LimitedDurationLicense::DISABLED)); // Close ocdm before destroying expectCloseKeySession(kNetflixKeySystem); @@ -140,10 +127,10 @@ TEST_F(RialtoServerMediaKeySessionGenerateRequestTest, SessionAlreadyConstructed EXPECT_CALL(*m_ocdmSessionMock, constructSession(m_keySessionType, m_kInitDataType, &m_kInitData[0], m_kInitData.size())) .WillOnce(Return(MediaKeyErrorStatus::OK)); - EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData)); + EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData, m_kLdlState)); // Generate request again should just return OK - EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData)); + EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData, m_kLdlState)); // OcdmSession will be closed on destruction expectCloseKeySession(kWidevineKeySystem); @@ -158,7 +145,8 @@ TEST_F(RialtoServerMediaKeySessionGenerateRequestTest, OcdmSessionFailure) EXPECT_CALL(*m_ocdmSessionMock, constructSession(m_keySessionType, m_kInitDataType, &m_kInitData[0], m_kInitData.size())) .WillOnce(Return(MediaKeyErrorStatus::NOT_SUPPORTED)); - EXPECT_EQ(MediaKeyErrorStatus::NOT_SUPPORTED, m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData)); + EXPECT_EQ(MediaKeyErrorStatus::NOT_SUPPORTED, + m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData, m_kLdlState)); } /** @@ -176,7 +164,7 @@ TEST_F(RialtoServerMediaKeySessionGenerateRequestTest, OnErrorFailure) return MediaKeyErrorStatus::OK; })); - EXPECT_EQ(MediaKeyErrorStatus::FAIL, m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData)); + EXPECT_EQ(MediaKeyErrorStatus::FAIL, m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData, m_kLdlState)); // OcdmSession will be closed on destruction expectCloseKeySession(kWidevineKeySystem); diff --git a/tests/unittests/media/server/main/mediaKeySession/IsNetflixPlayreadyKeySystemTest.cpp b/tests/unittests/media/server/main/mediaKeySession/IsNetflixPlayreadyKeySystemTest.cpp deleted file mode 100644 index 989a4ea2a..000000000 --- a/tests/unittests/media/server/main/mediaKeySession/IsNetflixPlayreadyKeySystemTest.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Sky UK - * - * 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. - */ - -#include "MediaKeySessionTestBase.h" - -class RialtoServerMediaKeySessionIsNetflixPlayreadyKeySystemTest : public MediaKeySessionTestBase -{ -protected: - ~RialtoServerMediaKeySessionIsNetflixPlayreadyKeySystemTest() { destroyKeySession(); } -}; - -/** - * Test that isNetflixPlayreadyKeySystem returns false for microsoft playready key system - */ -TEST_F(RialtoServerMediaKeySessionIsNetflixPlayreadyKeySystemTest, ReturnFalseForMsPlayready) -{ - createKeySession(kPlayreadyKeySystem); - - EXPECT_FALSE(m_mediaKeySession->isNetflixPlayreadyKeySystem()); -} - -/** - * Test that isNetflixPlayreadyKeySystem returns true for netflix key system - */ -TEST_F(RialtoServerMediaKeySessionIsNetflixPlayreadyKeySystemTest, ReturnTrueForNetflix) -{ - createKeySession(kNetflixKeySystem); - - EXPECT_TRUE(m_mediaKeySession->isNetflixPlayreadyKeySystem()); -} - -/** - * Test that isNetflixPlayreadyKeySystem returns false for widevine key system - */ -TEST_F(RialtoServerMediaKeySessionIsNetflixPlayreadyKeySystemTest, ReturnFalseForWidevine) -{ - createKeySession(kWidevineKeySystem); - - EXPECT_FALSE(m_mediaKeySession->isNetflixPlayreadyKeySystem()); -} diff --git a/tests/unittests/media/server/main/mediaKeySession/SetDrmHeaderTest.cpp b/tests/unittests/media/server/main/mediaKeySession/SetDrmHeaderTest.cpp index 18b027a32..7969dac84 100644 --- a/tests/unittests/media/server/main/mediaKeySession/SetDrmHeaderTest.cpp +++ b/tests/unittests/media/server/main/mediaKeySession/SetDrmHeaderTest.cpp @@ -19,6 +19,18 @@ #include "MediaKeySessionTestBase.h" +MATCHER_P(drmHeaderMatcher, header, "") +{ + for (size_t i = 0; i < header.size(); ++i) + { + if (arg[i] != header[i]) + { + return false; + } + } + return true; +} + class RialtoServerMediaKeySessionSetDrmHeaderTest : public MediaKeySessionTestBase { protected: @@ -33,10 +45,15 @@ TEST_F(RialtoServerMediaKeySessionSetDrmHeaderTest, Success) { createKeySession(kNetflixKeySystem); + generateRequestPlayready(); + EXPECT_CALL(*m_ocdmSessionMock, setDrmHeader(&m_kDrmHeader[0], m_kDrmHeader.size())) .WillOnce(Return(MediaKeyErrorStatus::OK)); EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->setDrmHeader(m_kDrmHeader)); + + // Close ocdm before destroying + expectCloseKeySession(kNetflixKeySystem); } /** @@ -46,10 +63,15 @@ TEST_F(RialtoServerMediaKeySessionSetDrmHeaderTest, OcdmSessionFailure) { createKeySession(kNetflixKeySystem); + generateRequestPlayready(); + EXPECT_CALL(*m_ocdmSessionMock, setDrmHeader(&m_kDrmHeader[0], m_kDrmHeader.size())) .WillOnce(Return(MediaKeyErrorStatus::FAIL)); EXPECT_EQ(MediaKeyErrorStatus::FAIL, m_mediaKeySession->setDrmHeader(m_kDrmHeader)); + + // Close ocdm before destroying + expectCloseKeySession(kNetflixKeySystem); } /** @@ -57,7 +79,9 @@ TEST_F(RialtoServerMediaKeySessionSetDrmHeaderTest, OcdmSessionFailure) */ TEST_F(RialtoServerMediaKeySessionSetDrmHeaderTest, OnErrorFailure) { - createKeySession(kWidevineKeySystem); + createKeySession(kNetflixKeySystem); + + generateRequestPlayready(); EXPECT_CALL(*m_ocdmSessionMock, setDrmHeader(&m_kDrmHeader[0], m_kDrmHeader.size())) .WillOnce(Invoke( @@ -68,4 +92,7 @@ TEST_F(RialtoServerMediaKeySessionSetDrmHeaderTest, OnErrorFailure) })); EXPECT_EQ(MediaKeyErrorStatus::FAIL, m_mediaKeySession->setDrmHeader(m_kDrmHeader)); + + // Close ocdm before destroying + expectCloseKeySession(kNetflixKeySystem); } diff --git a/tests/unittests/media/server/main/mediaKeySession/UpdateSessionTest.cpp b/tests/unittests/media/server/main/mediaKeySession/UpdateSessionTest.cpp index d6ae82c0e..7c826a14f 100644 --- a/tests/unittests/media/server/main/mediaKeySession/UpdateSessionTest.cpp +++ b/tests/unittests/media/server/main/mediaKeySession/UpdateSessionTest.cpp @@ -33,11 +33,15 @@ class RialtoServerMediaKeySessionUpdateSessionTest : public MediaKeySessionTestB TEST_F(RialtoServerMediaKeySessionUpdateSessionTest, SuccessNetflix) { createKeySession(kNetflixKeySystem); + generateRequestPlayready(); EXPECT_CALL(*m_ocdmSessionMock, storeLicenseData(&m_kResponseData[0], m_kResponseData.size())) .WillOnce(Return(MediaKeyErrorStatus::OK)); EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->updateSession(m_kResponseData)); + + // Close ocdm before destroying + expectCloseKeySession(kNetflixKeySystem); } /** @@ -59,11 +63,15 @@ TEST_F(RialtoServerMediaKeySessionUpdateSessionTest, SuccessNoneNetflix) TEST_F(RialtoServerMediaKeySessionUpdateSessionTest, OcdmSessionStoreLicenseDataFailure) { createKeySession(kNetflixKeySystem); + generateRequestPlayready(); EXPECT_CALL(*m_ocdmSessionMock, storeLicenseData(&m_kResponseData[0], m_kResponseData.size())) .WillOnce(Return(MediaKeyErrorStatus::INVALID_STATE)); EXPECT_EQ(MediaKeyErrorStatus::INVALID_STATE, m_mediaKeySession->updateSession(m_kResponseData)); + + // Close ocdm before destroying + expectCloseKeySession(kNetflixKeySystem); } /** diff --git a/tests/unittests/media/server/main/mediaKeySession/base/MediaKeySessionTestBase.cpp b/tests/unittests/media/server/main/mediaKeySession/base/MediaKeySessionTestBase.cpp index 05f9184cf..12eb74230 100644 --- a/tests/unittests/media/server/main/mediaKeySession/base/MediaKeySessionTestBase.cpp +++ b/tests/unittests/media/server/main/mediaKeySession/base/MediaKeySessionTestBase.cpp @@ -42,7 +42,7 @@ void MediaKeySessionTestBase::createKeySession(const std::string &keySystem) EXPECT_NO_THROW(m_mediaKeySession = std::make_unique(keySystem, m_kKeySessionId, *m_ocdmSystemMock, m_keySessionType, m_mediaKeysClientMock, - m_isLDL, m_mainThreadFactoryMock)); + m_mainThreadFactoryMock)); EXPECT_NE(m_mediaKeySession, nullptr); } @@ -74,7 +74,47 @@ void MediaKeySessionTestBase::generateRequest() EXPECT_CALL(*m_ocdmSessionMock, constructSession(m_keySessionType, m_initDataType, &m_initData[0], m_initData.size())) .WillOnce(Return(MediaKeyErrorStatus::OK)); - EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->generateRequest(m_initDataType, m_initData)); + EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->generateRequest(m_initDataType, m_initData, m_kLdlState)); +} + +void MediaKeySessionTestBase::generateRequestPlayready() +{ + EXPECT_CALL(*m_ocdmSessionMock, + constructSession(m_keySessionType, m_kInitDataType, &m_kInitData[0], m_kInitData.size())) + .WillOnce(Return(MediaKeyErrorStatus::OK)); + mainThreadWillEnqueueTask(); + EXPECT_CALL(*m_ocdmSessionMock, getChallengeData(m_isLDL, nullptrMatcher(), _)) + .WillOnce(DoAll(SetArgPointee<2>(m_kChallenge.size()), Return(MediaKeyErrorStatus::OK))); + EXPECT_CALL(*m_ocdmSessionMock, getChallengeData(m_isLDL, notNullptrMatcher(), _)) + .WillOnce(DoAll(memcpyChallenge(m_kChallenge), Return(MediaKeyErrorStatus::OK))); + mainThreadWillEnqueueTask(); + EXPECT_CALL(*m_mediaKeysClientMock, onLicenseRequest(m_kKeySessionId, m_kChallenge, _)); + + EXPECT_EQ(MediaKeyErrorStatus::OK, + m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData, + firebolt::rialto::LimitedDurationLicense::DISABLED)); +} + +void MediaKeySessionTestBase::generateRequestPlayreadyWithTwoCalls() +{ + EXPECT_CALL(*m_ocdmSessionMock, + constructSession(m_keySessionType, m_kInitDataType, &m_kInitData[0], m_kInitData.size())) + .WillOnce(Return(MediaKeyErrorStatus::OK)); + + EXPECT_EQ(MediaKeyErrorStatus::OK, + m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData, + firebolt::rialto::LimitedDurationLicense::NOT_SPECIFIED)); + mainThreadWillEnqueueTask(); + EXPECT_CALL(*m_ocdmSessionMock, getChallengeData(m_isLDL, nullptrMatcher(), _)) + .WillOnce(DoAll(SetArgPointee<2>(m_kChallenge.size()), Return(MediaKeyErrorStatus::OK))); + EXPECT_CALL(*m_ocdmSessionMock, getChallengeData(m_isLDL, notNullptrMatcher(), _)) + .WillOnce(DoAll(memcpyChallenge(m_kChallenge), Return(MediaKeyErrorStatus::OK))); + mainThreadWillEnqueueTask(); + EXPECT_CALL(*m_mediaKeysClientMock, onLicenseRequest(m_kKeySessionId, m_kChallenge, _)); + + EXPECT_EQ(MediaKeyErrorStatus::OK, + m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData, + firebolt::rialto::LimitedDurationLicense::DISABLED)); } void MediaKeySessionTestBase::mainThreadWillEnqueueTask() diff --git a/tests/unittests/media/server/main/mediaKeySession/base/MediaKeySessionTestBase.h b/tests/unittests/media/server/main/mediaKeySession/base/MediaKeySessionTestBase.h index b13514d76..5bf10e5c8 100644 --- a/tests/unittests/media/server/main/mediaKeySession/base/MediaKeySessionTestBase.h +++ b/tests/unittests/media/server/main/mediaKeySession/base/MediaKeySessionTestBase.h @@ -30,6 +30,7 @@ #include #include #include +#include using namespace firebolt::rialto; using namespace firebolt::rialto::server; @@ -38,10 +39,27 @@ using namespace firebolt::rialto::server::mock; using ::testing::_; using ::testing::ByMove; +using ::testing::DoAll; using ::testing::Invoke; using ::testing::Return; +using ::testing::SetArgPointee; using ::testing::StrictMock; +MATCHER(nullptrMatcher, "") +{ + return arg == nullptr; +} + +MATCHER(notNullptrMatcher, "") +{ + return arg != nullptr; +} + +ACTION_P(memcpyChallenge, vec) +{ + memcpy(arg1, &vec[0], vec.size()); +} + class MediaKeySessionTestBase : public ::testing::Test { public: @@ -65,11 +83,17 @@ class MediaKeySessionTestBase : public ::testing::Test const int32_t m_kMainThreadClientId = {5}; KeySessionType m_keySessionType = KeySessionType::PERSISTENT_RELEASE_MESSAGE; bool m_isLDL = false; + const InitDataType m_kInitDataType{InitDataType::CENC}; + const std::vector m_kInitData{1, 2, 3}; + const std::vector m_kChallenge{'d', 'e', 'f'}; + const LimitedDurationLicense m_kLdlState{LimitedDurationLicense::NOT_SPECIFIED}; void createKeySession(const std::string &keySystem); void destroyKeySession(); void expectCloseKeySession(const std::string &keySystem); void generateRequest(); + void generateRequestPlayready(); + void generateRequestPlayreadyWithTwoCalls(); void mainThreadWillEnqueueTask(); }; diff --git a/tests/unittests/media/server/main/mediaKeys/CreateKeySessionTest.cpp b/tests/unittests/media/server/main/mediaKeys/CreateKeySessionTest.cpp index 40ea6c374..4ab26341c 100644 --- a/tests/unittests/media/server/main/mediaKeys/CreateKeySessionTest.cpp +++ b/tests/unittests/media/server/main/mediaKeys/CreateKeySessionTest.cpp @@ -34,12 +34,11 @@ TEST_F(RialtoServerMediaKeysCreateKeySessionTest, Success) int32_t returnKeySessionId = -1; mainThreadWillEnqueueTaskAndWait(); - EXPECT_CALL(*m_mediaKeySessionFactoryMock, - createMediaKeySession(kNetflixKeySystem, _, _, m_keySessionType, _, m_isLDL)) + EXPECT_CALL(*m_mediaKeySessionFactoryMock, createMediaKeySession(kNetflixKeySystem, _, _, m_keySessionType, _)) .WillOnce(Return(ByMove(std::move(m_mediaKeySession)))); EXPECT_EQ(MediaKeyErrorStatus::OK, - m_mediaKeys->createKeySession(m_keySessionType, m_mediaKeysClientMock, m_isLDL, returnKeySessionId)); + m_mediaKeys->createKeySession(m_keySessionType, m_mediaKeysClientMock, returnKeySessionId)); EXPECT_GE(returnKeySessionId, -1); } @@ -51,10 +50,9 @@ TEST_F(RialtoServerMediaKeysCreateKeySessionTest, OcdmSystemFailure) int32_t returnKeySessionId = -1; mainThreadWillEnqueueTaskAndWait(); - EXPECT_CALL(*m_mediaKeySessionFactoryMock, - createMediaKeySession(kNetflixKeySystem, _, _, m_keySessionType, _, m_isLDL)) + EXPECT_CALL(*m_mediaKeySessionFactoryMock, createMediaKeySession(kNetflixKeySystem, _, _, m_keySessionType, _)) .WillOnce(Return(ByMove(nullptr))); EXPECT_EQ(MediaKeyErrorStatus::FAIL, - m_mediaKeys->createKeySession(m_keySessionType, m_mediaKeysClientMock, m_isLDL, returnKeySessionId)); + m_mediaKeys->createKeySession(m_keySessionType, m_mediaKeysClientMock, returnKeySessionId)); } diff --git a/tests/unittests/media/server/main/mediaKeys/GenerateRequestTest.cpp b/tests/unittests/media/server/main/mediaKeys/GenerateRequestTest.cpp index d69ac3cf8..50becd378 100644 --- a/tests/unittests/media/server/main/mediaKeys/GenerateRequestTest.cpp +++ b/tests/unittests/media/server/main/mediaKeys/GenerateRequestTest.cpp @@ -24,6 +24,7 @@ class RialtoServerMediaKeysGenerateRequestTest : public MediaKeysTestBase protected: InitDataType m_initDataType = InitDataType::CENC; std::vector m_initData{1, 2, 3}; + LimitedDurationLicense m_ldlState{LimitedDurationLicense::NOT_SPECIFIED}; RialtoServerMediaKeysGenerateRequestTest() { @@ -39,10 +40,11 @@ class RialtoServerMediaKeysGenerateRequestTest : public MediaKeysTestBase TEST_F(RialtoServerMediaKeysGenerateRequestTest, Success) { mainThreadWillEnqueueTaskAndWait(); - EXPECT_CALL(*m_mediaKeySessionMock, generateRequest(m_initDataType, m_initData)) + EXPECT_CALL(*m_mediaKeySessionMock, generateRequest(m_initDataType, m_initData, m_ldlState)) .WillOnce(Return(MediaKeyErrorStatus::OK)); - EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeys->generateRequest(m_kKeySessionId, m_initDataType, m_initData)); + EXPECT_EQ(MediaKeyErrorStatus::OK, + m_mediaKeys->generateRequest(m_kKeySessionId, m_initDataType, m_initData, m_ldlState)); } /** @@ -52,7 +54,7 @@ TEST_F(RialtoServerMediaKeysGenerateRequestTest, SessionDoesNotExistFailure) { mainThreadWillEnqueueTaskAndWait(); EXPECT_EQ(MediaKeyErrorStatus::BAD_SESSION_ID, - m_mediaKeys->generateRequest(m_kKeySessionId + 1, m_initDataType, m_initData)); + m_mediaKeys->generateRequest(m_kKeySessionId + 1, m_initDataType, m_initData, m_ldlState)); } /** @@ -61,9 +63,9 @@ TEST_F(RialtoServerMediaKeysGenerateRequestTest, SessionDoesNotExistFailure) TEST_F(RialtoServerMediaKeysGenerateRequestTest, SessionFailure) { mainThreadWillEnqueueTaskAndWait(); - EXPECT_CALL(*m_mediaKeySessionMock, generateRequest(m_initDataType, m_initData)) + EXPECT_CALL(*m_mediaKeySessionMock, generateRequest(m_initDataType, m_initData, m_ldlState)) .WillOnce(Return(MediaKeyErrorStatus::NOT_SUPPORTED)); EXPECT_EQ(MediaKeyErrorStatus::NOT_SUPPORTED, - m_mediaKeys->generateRequest(m_kKeySessionId, m_initDataType, m_initData)); + m_mediaKeys->generateRequest(m_kKeySessionId, m_initDataType, m_initData, m_ldlState)); } diff --git a/tests/unittests/media/server/main/mediaKeys/IsNetflixPlayreadyKeySystemTest.cpp b/tests/unittests/media/server/main/mediaKeys/IsNetflixPlayreadyKeySystemTest.cpp deleted file mode 100644 index f9d1fcbad..000000000 --- a/tests/unittests/media/server/main/mediaKeys/IsNetflixPlayreadyKeySystemTest.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2022 Sky UK - * - * 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. - */ - -#include "MediaKeysTestBase.h" - -class RialtoServerMediaKeysIsNetflixPlayreadyKeySystemTest : public MediaKeysTestBase -{ -protected: - RialtoServerMediaKeysIsNetflixPlayreadyKeySystemTest() = default; - ~RialtoServerMediaKeysIsNetflixPlayreadyKeySystemTest() { destroyMediaKeys(); } -}; - -/** - * Test that isNetflixPlayreadyKeySystem returns true. - */ -TEST_F(RialtoServerMediaKeysIsNetflixPlayreadyKeySystemTest, ReturnTrue) -{ - createMediaKeys(kNetflixKeySystem); - EXPECT_TRUE(m_mediaKeys->isNetflixPlayreadyKeySystem()); -} - -/** - * Test that isNetflixPlayreadyKeySystem returns false - */ -TEST_F(RialtoServerMediaKeysIsNetflixPlayreadyKeySystemTest, ReturnFalse) -{ - createMediaKeys(kWidevineKeySystem); - EXPECT_FALSE(m_mediaKeys->isNetflixPlayreadyKeySystem()); -} diff --git a/tests/unittests/media/server/main/mediaKeys/base/MediaKeysTestBase.cpp b/tests/unittests/media/server/main/mediaKeys/base/MediaKeysTestBase.cpp index d6a9f91f7..d12682c95 100644 --- a/tests/unittests/media/server/main/mediaKeys/base/MediaKeysTestBase.cpp +++ b/tests/unittests/media/server/main/mediaKeys/base/MediaKeysTestBase.cpp @@ -61,11 +61,11 @@ void MediaKeysTestBase::destroyMediaKeys() void MediaKeysTestBase::createKeySession(std::string keySystem) { mainThreadWillEnqueueTaskAndWait(); - EXPECT_CALL(*m_mediaKeySessionFactoryMock, createMediaKeySession(keySystem, _, _, m_keySessionType, _, m_isLDL)) + EXPECT_CALL(*m_mediaKeySessionFactoryMock, createMediaKeySession(keySystem, _, _, m_keySessionType, _)) .WillOnce(Return(ByMove(std::move(m_mediaKeySession)))); EXPECT_EQ(MediaKeyErrorStatus::OK, - m_mediaKeys->createKeySession(m_keySessionType, m_mediaKeysClientMock, m_isLDL, m_kKeySessionId)); + m_mediaKeys->createKeySession(m_keySessionType, m_mediaKeysClientMock, m_kKeySessionId)); } void MediaKeysTestBase::mainThreadWillEnqueueTaskAndWait() diff --git a/tests/unittests/media/server/main/mediaPipeline/CallbackTest.cpp b/tests/unittests/media/server/main/mediaPipeline/CallbackTest.cpp index 3abe1efe1..627f111d5 100644 --- a/tests/unittests/media/server/main/mediaPipeline/CallbackTest.cpp +++ b/tests/unittests/media/server/main/mediaPipeline/CallbackTest.cpp @@ -88,7 +88,7 @@ TEST_F(RialtoServerMediaPipelineCallbackTest, notifyNeedMediaDataInPrerollingSta { auto mediaSourceType = firebolt::rialto::MediaSourceType::VIDEO; int sourceId = attachSource(mediaSourceType, "video/h264"); - constexpr int kNumFrames{24}; + constexpr int kNumFrames{3}; expectNotifyNeedData(mediaSourceType, sourceId, kNumFrames); diff --git a/tests/unittests/media/server/main/mediaPipeline/FlushTest.cpp b/tests/unittests/media/server/main/mediaPipeline/FlushTest.cpp index c4ef4c947..a9ca62986 100644 --- a/tests/unittests/media/server/main/mediaPipeline/FlushTest.cpp +++ b/tests/unittests/media/server/main/mediaPipeline/FlushTest.cpp @@ -100,6 +100,6 @@ TEST_F(RialtoServerMediaPipelineFlushTest, FlushResetEos) EXPECT_TRUE(m_mediaPipeline->flush(sourceId, m_kResetTime, async)); // Expect need data notified to client - expectNotifyNeedData(firebolt::rialto::MediaSourceType::VIDEO, sourceId, 24); + expectNotifyNeedData(firebolt::rialto::MediaSourceType::VIDEO, sourceId, 3); m_gstPlayerCallback->notifyNeedMediaData(firebolt::rialto::MediaSourceType::VIDEO); } diff --git a/tests/unittests/media/server/main/mediaPipeline/HaveDataTest.cpp b/tests/unittests/media/server/main/mediaPipeline/HaveDataTest.cpp index b1980c220..8f0cc0094 100644 --- a/tests/unittests/media/server/main/mediaPipeline/HaveDataTest.cpp +++ b/tests/unittests/media/server/main/mediaPipeline/HaveDataTest.cpp @@ -28,7 +28,7 @@ using ::testing::Throw; class RialtoServerMediaPipelineHaveDataTest : public MediaPipelineTestBase { protected: - const uint32_t m_kNumFrames{24}; + const uint32_t m_kNumFrames{3}; const uint32_t m_kNeedDataRequestId{0}; const std::chrono::milliseconds m_kDefaultNeedMediaDataResendTimeout{15}; const std::chrono::milliseconds m_kLowLatencyNeedMediaDataResendTimeout{5}; diff --git a/tests/unittests/media/server/main/mediaPipeline/LoadTest.cpp b/tests/unittests/media/server/main/mediaPipeline/LoadTest.cpp index 131a647f1..0e961781b 100644 --- a/tests/unittests/media/server/main/mediaPipeline/LoadTest.cpp +++ b/tests/unittests/media/server/main/mediaPipeline/LoadTest.cpp @@ -32,6 +32,7 @@ class RialtoServerMediaPipelineLoadTest : public MediaPipelineTestBase MediaType m_type = MediaType::MSE; const std::string m_kMimeType = "mime"; const std::string m_kUrl = "mse://1"; + const bool m_kIsLive = true; RialtoServerMediaPipelineLoadTest() { createMediaPipeline(); } @@ -45,11 +46,12 @@ TEST_F(RialtoServerMediaPipelineLoadTest, Success) { mainThreadWillEnqueueTaskAndWait(); mainThreadWillEnqueueTask(); - EXPECT_CALL(*m_gstPlayerFactoryMock, createGstGenericPlayer(_, _, m_type, VideoRequirementsMatcher(m_videoReq), _)) + EXPECT_CALL(*m_gstPlayerFactoryMock, + createGstGenericPlayer(_, _, m_type, VideoRequirementsMatcher(m_videoReq), m_kIsLive, _)) .WillOnce(Return(ByMove(std::move(m_gstPlayer)))); EXPECT_CALL(*m_mediaPipelineClientMock, notifyNetworkState(NetworkState::BUFFERING)); - EXPECT_EQ(m_mediaPipeline->load(m_type, m_kMimeType, m_kUrl), true); + EXPECT_EQ(m_mediaPipeline->load(m_type, m_kMimeType, m_kUrl, m_kIsLive), true); } /** @@ -59,9 +61,10 @@ TEST_F(RialtoServerMediaPipelineLoadTest, Success) TEST_F(RialtoServerMediaPipelineLoadTest, CreateGstPlayerFailure) { mainThreadWillEnqueueTaskAndWait(); - EXPECT_CALL(*m_gstPlayerFactoryMock, createGstGenericPlayer(_, _, m_type, VideoRequirementsMatcher(m_videoReq), _)) + EXPECT_CALL(*m_gstPlayerFactoryMock, + createGstGenericPlayer(_, _, m_type, VideoRequirementsMatcher(m_videoReq), m_kIsLive, _)) .WillOnce(Return(ByMove(nullptr))); EXPECT_CALL(*m_mediaPipelineClientMock, notifyNetworkState(_)).Times(0); - EXPECT_EQ(m_mediaPipeline->load(m_type, m_kMimeType, m_kUrl), false); + EXPECT_EQ(m_mediaPipeline->load(m_type, m_kMimeType, m_kUrl, m_kIsLive), false); } diff --git a/tests/unittests/media/server/main/mediaPipeline/MiscellaneousFunctionsTest.cpp b/tests/unittests/media/server/main/mediaPipeline/MiscellaneousFunctionsTest.cpp index ce00bdee8..59289b9d3 100644 --- a/tests/unittests/media/server/main/mediaPipeline/MiscellaneousFunctionsTest.cpp +++ b/tests/unittests/media/server/main/mediaPipeline/MiscellaneousFunctionsTest.cpp @@ -28,6 +28,7 @@ class RialtoServerMediaPipelineMiscellaneousFunctionsTest : public MediaPipeline { protected: const int64_t m_kPosition{4028596027}; + const int64_t m_kDuration{8057192054}; const double m_kPlaybackRate{1.5}; uint64_t m_kRenderedFrames{3141}; uint64_t m_kDroppedFrames{95}; @@ -43,11 +44,12 @@ class RialtoServerMediaPipelineMiscellaneousFunctionsTest : public MediaPipeline */ TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, PlaySuccess) { + bool async{false}; loadGstPlayer(); mainThreadWillEnqueueTaskAndWait(); - EXPECT_CALL(*m_gstPlayerMock, play()); - EXPECT_TRUE(m_mediaPipeline->play()); + EXPECT_CALL(*m_gstPlayerMock, play(_)); + EXPECT_TRUE(m_mediaPipeline->play(async)); } /** @@ -55,8 +57,9 @@ TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, PlaySuccess) */ TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, PlayFailureDueToUninitializedPlayer) { + bool async{false}; mainThreadWillEnqueueTaskAndWait(); - EXPECT_FALSE(m_mediaPipeline->play()); + EXPECT_FALSE(m_mediaPipeline->play(async)); } /** @@ -219,6 +222,44 @@ TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetPositionSuccess) EXPECT_EQ(targetPosition, m_kPosition); } +/** + * Test that GetDuration returns failure if the gstreamer player is not initialized + */ +TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetDurationFailureDueToUninitializedPlayer) +{ + int64_t targetDuration{}; + EXPECT_FALSE(m_mediaPipeline->getDuration(targetDuration)); +} + +/** + * Test that GetDuration returns failure if the gstreamer API fails + */ +TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetDurationFailure) +{ + loadGstPlayer(); + int64_t targetDuration{}; + EXPECT_CALL(*m_gstPlayerMock, getDuration(_)).WillOnce(Return(false)); + EXPECT_FALSE(m_mediaPipeline->getDuration(targetDuration)); +} + +/** + * Test that GetDuration returns success if the gstreamer API succeeds and gets the duration + */ +TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetDurationSuccess) +{ + loadGstPlayer(); + int64_t targetDuration{}; + EXPECT_CALL(*m_gstPlayerMock, getDuration(_)) + .WillOnce(Invoke( + [&](int64_t &pos) + { + pos = m_kDuration; + return true; + })); + EXPECT_TRUE(m_mediaPipeline->getDuration(targetDuration)); + EXPECT_EQ(targetDuration, m_kDuration); +} + /** * Test that SetImmediateOutput returns failure if the gstreamer player is not initialized */ diff --git a/tests/unittests/media/server/main/mediaPipeline/SourceTest.cpp b/tests/unittests/media/server/main/mediaPipeline/SourceTest.cpp index 18717e95b..56eae34ee 100644 --- a/tests/unittests/media/server/main/mediaPipeline/SourceTest.cpp +++ b/tests/unittests/media/server/main/mediaPipeline/SourceTest.cpp @@ -97,7 +97,6 @@ TEST_F(RialtoServerMediaPipelineSourceTest, RemoveSourceSuccess) std::int32_t sourceId{mediaSource->getId()}; mainThreadWillEnqueueTaskAndWait(); - EXPECT_CALL(*m_gstPlayerMock, removeSource(MediaSourceType::AUDIO)); EXPECT_EQ(m_mediaPipeline->removeSource(sourceId), true); } @@ -137,7 +136,6 @@ TEST_F(RialtoServerMediaPipelineSourceTest, AttachRemoveAttachSourceDifferentId) std::int32_t firstSourceId{mediaSource->getId()}; mainThreadWillEnqueueTaskAndWait(); - EXPECT_CALL(*m_gstPlayerMock, removeSource(MediaSourceType::AUDIO)); EXPECT_EQ(m_mediaPipeline->removeSource(firstSourceId), true); mainThreadWillEnqueueTaskAndWait(); diff --git a/tests/unittests/media/server/main/mediaPipeline/base/MediaPipelineTestBase.cpp b/tests/unittests/media/server/main/mediaPipeline/base/MediaPipelineTestBase.cpp index 491aec0e1..398e7ea68 100644 --- a/tests/unittests/media/server/main/mediaPipeline/base/MediaPipelineTestBase.cpp +++ b/tests/unittests/media/server/main/mediaPipeline/base/MediaPipelineTestBase.cpp @@ -86,11 +86,11 @@ void MediaPipelineTestBase::loadGstPlayer() { mainThreadWillEnqueueTaskAndWait(); mainThreadWillEnqueueTask(); - EXPECT_CALL(*m_gstPlayerFactoryMock, createGstGenericPlayer(_, _, _, _, _)) + EXPECT_CALL(*m_gstPlayerFactoryMock, createGstGenericPlayer(_, _, _, _, _, _)) .WillOnce(DoAll(SaveArg<0>(&m_gstPlayerCallback), Return(ByMove(std::move(m_gstPlayer))))); EXPECT_CALL(*m_mediaPipelineClientMock, notifyNetworkState(NetworkState::BUFFERING)); - EXPECT_EQ(m_mediaPipeline->load(MediaType::MSE, "mime", "mse://1"), true); + EXPECT_EQ(m_mediaPipeline->load(MediaType::MSE, "mime", "mse://1", false), true); ASSERT_NE(m_gstPlayerCallback, nullptr); } diff --git a/tests/unittests/media/server/main/needMediaData/NeedMediaDataTests.cpp b/tests/unittests/media/server/main/needMediaData/NeedMediaDataTests.cpp index 746d78012..462f45d73 100644 --- a/tests/unittests/media/server/main/needMediaData/NeedMediaDataTests.cpp +++ b/tests/unittests/media/server/main/needMediaData/NeedMediaDataTests.cpp @@ -30,3 +30,9 @@ TEST_F(NeedMediaDataTests, shouldSendMessageInPlayingState) initialize(firebolt::rialto::PlaybackState::PLAYING); needMediaDataWillBeSentInPlayingState(); } + +TEST_F(NeedMediaDataTests, shouldSendMessageInPrerollingState) +{ + initialize(firebolt::rialto::PlaybackState::PAUSED); + needMediaDataWillBeSentBelowPlayingState(); +} diff --git a/tests/unittests/media/server/main/needMediaData/NeedMediaDataTestsFixture.cpp b/tests/unittests/media/server/main/needMediaData/NeedMediaDataTestsFixture.cpp index ac5fc88f6..2237002df 100644 --- a/tests/unittests/media/server/main/needMediaData/NeedMediaDataTestsFixture.cpp +++ b/tests/unittests/media/server/main/needMediaData/NeedMediaDataTestsFixture.cpp @@ -30,6 +30,7 @@ constexpr int kSourceId{1}; constexpr std::uint32_t kBufferLen{7 * 1024 * 1024}; constexpr std::uint32_t kMetadataOffset{1024}; constexpr int kRequestId{0}; +constexpr int kPrerollingNumFrames{3}; constexpr int kMaxFrames{24}; constexpr int kMaxMetadataBytes{2500}; } // namespace @@ -88,6 +89,19 @@ void NeedMediaDataTests::needMediaDataWillBeSentInPlayingState() EXPECT_TRUE(m_sut->send()); } +void NeedMediaDataTests::needMediaDataWillBeSentBelowPlayingState() +{ + std::shared_ptr expectedShmInfo{ + std::make_shared()}; + expectedShmInfo->maxMetadataBytes = kMaxMetadataBytes; + expectedShmInfo->metadataOffset = kMetadataOffset; + expectedShmInfo->mediaDataOffset = kMetadataOffset + kMaxMetadataBytes; + ASSERT_TRUE(m_sut); + EXPECT_CALL(activeRequestsMock, insert(kValidMediaSourceType, _)).WillOnce(Return(kRequestId)); + EXPECT_CALL(*m_clientMock, notifyNeedMediaData(kSourceId, kPrerollingNumFrames, kRequestId, expectedShmInfo)); + EXPECT_TRUE(m_sut->send()); +} + void NeedMediaDataTests::needMediaDataWillNotBeSent() { ASSERT_TRUE(m_sut); diff --git a/tests/unittests/media/server/main/needMediaData/NeedMediaDataTestsFixture.h b/tests/unittests/media/server/main/needMediaData/NeedMediaDataTestsFixture.h index 00dd9a1c1..6f725cf8c 100644 --- a/tests/unittests/media/server/main/needMediaData/NeedMediaDataTestsFixture.h +++ b/tests/unittests/media/server/main/needMediaData/NeedMediaDataTestsFixture.h @@ -41,6 +41,7 @@ class NeedMediaDataTests : public testing::Test void needMediaDataWillBeSentInPlayingState(); void needMediaDataWillNotBeSent(); + void needMediaDataWillBeSentBelowPlayingState(); private: std::unique_ptr m_sut; diff --git a/tests/unittests/media/server/mocks/gstplayer/FlushOnPrerollControllerMock.h b/tests/unittests/media/server/mocks/gstplayer/FlushOnPrerollControllerMock.h index 0a4d3fff1..b3daeec0e 100644 --- a/tests/unittests/media/server/mocks/gstplayer/FlushOnPrerollControllerMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/FlushOnPrerollControllerMock.h @@ -28,9 +28,11 @@ namespace firebolt::rialto::server class FlushOnPrerollControllerMock : public IFlushOnPrerollController { public: - MOCK_METHOD(bool, shouldPostponeFlush, (const MediaSourceType &type), (const, override)); - MOCK_METHOD(void, setFlushing, (const MediaSourceType &type, const GstState ¤tPipelineState), (override)); + MOCK_METHOD(void, waitIfRequired, (const MediaSourceType &type), (override)); + MOCK_METHOD(void, setFlushing, (const MediaSourceType &type), (override)); + MOCK_METHOD(void, setPrerolling, (), (override)); MOCK_METHOD(void, stateReached, (const GstState &newPipelineState), (override)); + MOCK_METHOD(void, setTargetState, (const GstState &state), (override)); MOCK_METHOD(void, reset, (), (override)); }; } // namespace firebolt::rialto::server diff --git a/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h b/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h index 902309e50..2cb7f0a35 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h @@ -62,10 +62,6 @@ class GenericPlayerTaskFactoryMock : public IGenericPlayerTaskFactory (GenericPlayerContext & context, IGstGenericPlayerPrivate &player, const std::shared_ptr &dataReader), (const, override)); - MOCK_METHOD(std::unique_ptr, createRemoveSource, - (GenericPlayerContext & context, (IGstGenericPlayerPrivate & player), - const firebolt::rialto::MediaSourceType &type), - (const, override)); MOCK_METHOD(std::unique_ptr, createReportPosition, (GenericPlayerContext & context, IGstGenericPlayerPrivate &player), (const, override)); MOCK_METHOD(std::unique_ptr, createCheckAudioUnderflow, @@ -119,7 +115,7 @@ class GenericPlayerTaskFactoryMock : public IGenericPlayerTaskFactory (const, override)); MOCK_METHOD(std::unique_ptr, createFlush, (GenericPlayerContext & context, IGstGenericPlayerPrivate &player, - const firebolt::rialto::MediaSourceType &type, bool resetTime), + const firebolt::rialto::MediaSourceType &type, bool resetTime, bool isAsync), (const, override)); MOCK_METHOD(std::unique_ptr, createSetSourcePosition, (GenericPlayerContext & context, const firebolt::rialto::MediaSourceType &type, std::int64_t position, diff --git a/tests/unittests/media/server/mocks/gstplayer/GstDispatcherThreadFactoryMock.h b/tests/unittests/media/server/mocks/gstplayer/GstDispatcherThreadFactoryMock.h index 95b1d947a..49b6675d0 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GstDispatcherThreadFactoryMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GstDispatcherThreadFactoryMock.h @@ -31,6 +31,7 @@ class GstDispatcherThreadFactoryMock : public IGstDispatcherThreadFactory public: MOCK_METHOD(std::unique_ptr, createGstDispatcherThread, (IGstDispatcherThreadClient & client, GstElement *pipeline, + const std::shared_ptr &flushOnPrerollController, const std::shared_ptr &gstWrapper), (const, override)); }; diff --git a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerFactoryMock.h b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerFactoryMock.h index 08a82a373..0139f54da 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerFactoryMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerFactoryMock.h @@ -34,7 +34,7 @@ class GstGenericPlayerFactoryMock : public IGstGenericPlayerFactory MOCK_METHOD(std::unique_ptr, createGstGenericPlayer, (IGstGenericPlayerClient * client, IDecryptionService &decryptionService, MediaType type, - const VideoRequirements &videoRequirements, + const VideoRequirements &videoRequirements, bool isLive, const std::shared_ptr &rdkGstreamerUtilsWrapperFactory), (override)); diff --git a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h index de4697646..35aa3d004 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h @@ -34,15 +34,15 @@ class GstGenericPlayerMock : public IGstGenericPlayer virtual ~GstGenericPlayerMock() = default; MOCK_METHOD(void, attachSource, (const std::unique_ptr &mediaSource), (override)); - MOCK_METHOD(void, removeSource, (const MediaSourceType &mediaSourceType), (override)); MOCK_METHOD(void, allSourcesAttached, (), (override)); - MOCK_METHOD(void, play, (), (override)); + MOCK_METHOD(void, play, (bool &async), (override)); MOCK_METHOD(void, pause, (), (override)); MOCK_METHOD(void, stop, (), (override)); MOCK_METHOD(void, attachSamples, (const IMediaPipeline::MediaSegmentVector &mediaSegments), (override)); MOCK_METHOD(void, attachSamples, (const std::shared_ptr &dataReader), (override)); MOCK_METHOD(void, setPosition, (std::int64_t position), (override)); MOCK_METHOD(bool, getPosition, (std::int64_t & position), (override)); + MOCK_METHOD(bool, getDuration, (std::int64_t & duration), (override)); MOCK_METHOD(bool, setImmediateOutput, (const MediaSourceType &mediaSourceType, bool immediateOutput), (override)); MOCK_METHOD(bool, getImmediateOutput, (const MediaSourceType &mediaSourceType, bool &immediateOutput), (override)); MOCK_METHOD(bool, getStats, diff --git a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h index ceab057ff..e28c70d8d 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h @@ -55,10 +55,12 @@ class GstGenericPlayerPrivateMock : public IGstGenericPlayerPrivate MOCK_METHOD(void, updateVideoCaps, (int32_t width, int32_t height, Fraction frameRate, const std::shared_ptr &codecData), (override)); - MOCK_METHOD(bool, changePipelineState, (GstState newState), (override)); + MOCK_METHOD(GstStateChangeReturn, changePipelineState, (GstState newState), (override)); MOCK_METHOD(int64_t, getPosition, (GstElement * element), (override)); MOCK_METHOD(void, startPositionReportingAndCheckAudioUnderflowTimer, (), (override)); MOCK_METHOD(void, stopPositionReportingAndCheckAudioUnderflowTimer, (), (override)); + MOCK_METHOD(void, startNotifyPlaybackInfoTimer, (), (override)); + MOCK_METHOD(void, stopNotifyPlaybackInfoTimer, (), (override)); MOCK_METHOD(void, stopWorkerThread, (), (override)); MOCK_METHOD(void, cancelUnderflow, (firebolt::rialto::MediaSourceType mediaSource), (override)); MOCK_METHOD(void, setPendingPlaybackRate, (), (override)); @@ -68,7 +70,6 @@ class GstGenericPlayerPrivateMock : public IGstGenericPlayerPrivate MOCK_METHOD(void, removeAutoVideoSinkChild, (GObject * object), (override)); MOCK_METHOD(void, removeAutoAudioSinkChild, (GObject * object), (override)); MOCK_METHOD(GstElement *, getSink, (const MediaSourceType &mediaSourceType), (const, override)); - MOCK_METHOD(void, setPlaybinFlags, (bool enableAudio), (override)); MOCK_METHOD(void, addAudioClippingToBuffer, (GstBuffer * buffer, uint64_t clippingStart, uint64_t clippingEnd), (const, override)); @@ -78,8 +79,6 @@ class GstGenericPlayerPrivateMock : public IGstGenericPlayerPrivate MOCK_METHOD(void, startSubtitleClockResyncTimer, (), (override)); MOCK_METHOD(void, stopSubtitleClockResyncTimer, (), (override)); MOCK_METHOD(bool, hasSourceType, (const MediaSourceType &mediaSourceType), (const, override)); - MOCK_METHOD(void, postponeFlush, (const MediaSourceType &mediaSourceType, bool resetTime), (override)); - MOCK_METHOD(void, executePostponedFlushes, (), (override)); MOCK_METHOD(void, notifyPlaybackInfo, (), (override)); }; } // namespace firebolt::rialto::server diff --git a/tests/unittests/media/server/mocks/gstplayer/GstProfilerFactoryMock.h b/tests/unittests/media/server/mocks/gstplayer/GstProfilerFactoryMock.h new file mode 100644 index 000000000..f8357fd55 --- /dev/null +++ b/tests/unittests/media/server/mocks/gstplayer/GstProfilerFactoryMock.h @@ -0,0 +1,46 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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. + */ + +#ifndef FIREBOLT_RIALTO_SERVER_GST_PROFILER_FACTORY_MOCK_H_ +#define FIREBOLT_RIALTO_SERVER_GST_PROFILER_FACTORY_MOCK_H_ + +#include "IGstProfiler.h" + +#include +#include + +namespace firebolt::rialto::server +{ +class GstProfilerFactoryMock : public IGstProfilerFactory +{ +public: + using IGstWrapper = firebolt::rialto::wrappers::IGstWrapper; + using IGlibWrapper = firebolt::rialto::wrappers::IGlibWrapper; + + GstProfilerFactoryMock() = default; + ~GstProfilerFactoryMock() override = default; + + MOCK_METHOD(std::unique_ptr, createGstProfiler, + (GstElement * pipeline, const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper), + (const, override)); +}; +} // namespace firebolt::rialto::server + +#endif // FIREBOLT_RIALTO_SERVER_GST_PROFILER_FACTORY_MOCK_H_ diff --git a/tests/unittests/media/server/mocks/gstplayer/GstProfilerMock.h b/tests/unittests/media/server/mocks/gstplayer/GstProfilerMock.h new file mode 100644 index 000000000..ef549dbc2 --- /dev/null +++ b/tests/unittests/media/server/mocks/gstplayer/GstProfilerMock.h @@ -0,0 +1,50 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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. + */ + +#ifndef FIREBOLT_RIALTO_SERVER_GST_PROFILER_MOCK_H_ +#define FIREBOLT_RIALTO_SERVER_GST_PROFILER_MOCK_H_ + +#include "IGstProfiler.h" + +#include +#include +#include +#include + +namespace firebolt::rialto::server +{ +class GstProfilerMock : public IGstProfiler +{ +public: + GstProfilerMock() = default; + ~GstProfilerMock() override = default; + + MOCK_METHOD(std::optional, createRecord, (const std::string &stage), (override)); + MOCK_METHOD(std::optional, createRecord, (const std::string &stage, const std::string &info), (override)); + + MOCK_METHOD(void, scheduleGstElementRecord, (GstElement * element), (override)); + MOCK_METHOD(std::vector, getRecords, (), (const, override)); + + MOCK_METHOD(void, logRecord, (RecordId id), (override)); + MOCK_METHOD(void, dumpToFile, (), (const, override)); + MOCK_METHOD(void, logPipelineSummary, (), (const, override)); +}; +} // namespace firebolt::rialto::server + +#endif // FIREBOLT_RIALTO_SERVER_GST_PROFILER_MOCK_H_ diff --git a/tests/unittests/media/server/mocks/main/DecryptionServiceMock.h b/tests/unittests/media/server/mocks/main/DecryptionServiceMock.h index 64f57c40c..c6a600510 100644 --- a/tests/unittests/media/server/mocks/main/DecryptionServiceMock.h +++ b/tests/unittests/media/server/mocks/main/DecryptionServiceMock.h @@ -30,7 +30,7 @@ class DecryptionServiceMock : public IDecryptionService { public: MOCK_METHOD(MediaKeyErrorStatus, decrypt, (int32_t keySessionId, GstBuffer *encrypted, GstCaps *caps), (override)); - MOCK_METHOD(bool, isNetflixPlayreadyKeySystem, (int32_t keySessionId), (override)); + MOCK_METHOD(bool, isExtendedInterfaceUsed, (int32_t keySessionId), (override)); MOCK_METHOD(MediaKeyErrorStatus, selectKeyId, (int32_t keySessionId, const std::vector &keyId), (override)); MOCK_METHOD(void, incrementSessionIdUsageCounter, (int32_t keySessionId), (override)); MOCK_METHOD(void, decrementSessionIdUsageCounter, (int32_t keySessionId), (override)); diff --git a/tests/unittests/media/server/mocks/main/MainThreadMock.h b/tests/unittests/media/server/mocks/main/MainThreadMock.h index acf9dc5e2..d8359bfde 100644 --- a/tests/unittests/media/server/mocks/main/MainThreadMock.h +++ b/tests/unittests/media/server/mocks/main/MainThreadMock.h @@ -32,9 +32,9 @@ class MainThreadMock : public IMainThread public: MOCK_METHOD(int32_t, registerClient, (), (override)); MOCK_METHOD(void, unregisterClient, (uint32_t clientId), (override)); - MOCK_METHOD(void, enqueueTask, (uint32_t clientId, Task task), (override)); - MOCK_METHOD(void, enqueueTaskAndWait, (uint32_t clientId, Task task), (override)); - MOCK_METHOD(void, enqueuePriorityTaskAndWait, (uint32_t clientId, Task task), (override)); + MOCK_METHOD(void, enqueueTask, (uint32_t clientId, const Task &task), (override)); + MOCK_METHOD(void, enqueueTaskAndWait, (uint32_t clientId, const Task &task), (override)); + MOCK_METHOD(void, enqueuePriorityTaskAndWait, (uint32_t clientId, const Task &task), (override)); }; } // namespace firebolt::rialto::server::mock diff --git a/tests/unittests/media/server/mocks/main/MediaKeySessionFactoryMock.h b/tests/unittests/media/server/mocks/main/MediaKeySessionFactoryMock.h index d7ada3a1c..fc2835f40 100644 --- a/tests/unittests/media/server/mocks/main/MediaKeySessionFactoryMock.h +++ b/tests/unittests/media/server/mocks/main/MediaKeySessionFactoryMock.h @@ -33,7 +33,7 @@ class MediaKeySessionFactoryMock : public IMediaKeySessionFactory MOCK_METHOD(std::unique_ptr, createMediaKeySession, (const std::string &keySystem, int32_t keySessionId, const firebolt::rialto::wrappers::IOcdmSystem &ocdmSystem, KeySessionType sessionType, - std::weak_ptr client, bool isLDL), + std::weak_ptr client), (const, override)); }; } // namespace firebolt::rialto::server diff --git a/tests/unittests/media/server/mocks/main/MediaKeySessionMock.h b/tests/unittests/media/server/mocks/main/MediaKeySessionMock.h index b813d80eb..8bbdc8284 100644 --- a/tests/unittests/media/server/mocks/main/MediaKeySessionMock.h +++ b/tests/unittests/media/server/mocks/main/MediaKeySessionMock.h @@ -30,7 +30,8 @@ namespace firebolt::rialto::server class MediaKeySessionMock : public IMediaKeySession { public: - MOCK_METHOD(MediaKeyErrorStatus, generateRequest, (InitDataType initDataType, const std::vector &initData), + MOCK_METHOD(MediaKeyErrorStatus, generateRequest, + (InitDataType initDataType, const std::vector &initData, const LimitedDurationLicense &ldlState), (override)); MOCK_METHOD(MediaKeyErrorStatus, loadSession, (), (override)); MOCK_METHOD(MediaKeyErrorStatus, updateSession, (const std::vector &responseData), (override)); @@ -42,7 +43,6 @@ class MediaKeySessionMock : public IMediaKeySession MOCK_METHOD(MediaKeyErrorStatus, setDrmHeader, (const std::vector &requestData), (override)); MOCK_METHOD(MediaKeyErrorStatus, getLastDrmError, (uint32_t & errorCode), (override)); MOCK_METHOD(MediaKeyErrorStatus, selectKeyId, (const std::vector &keyId), (override)); - MOCK_METHOD(bool, isNetflixPlayreadyKeySystem, (), (const, override)); }; } // namespace firebolt::rialto::server diff --git a/tests/unittests/media/server/mocks/main/MediaKeysServerInternalMock.h b/tests/unittests/media/server/mocks/main/MediaKeysServerInternalMock.h index 7333feede..4ee5df08f 100644 --- a/tests/unittests/media/server/mocks/main/MediaKeysServerInternalMock.h +++ b/tests/unittests/media/server/mocks/main/MediaKeysServerInternalMock.h @@ -37,10 +37,11 @@ class MediaKeysServerInternalMock : public IMediaKeysServerInternal MOCK_METHOD(MediaKeyErrorStatus, selectKeyId, (int32_t keySessionId, const std::vector &keyId), (override)); MOCK_METHOD(bool, containsKey, (int32_t keySessionId, const std::vector &keyId), (override)); MOCK_METHOD(MediaKeyErrorStatus, createKeySession, - (KeySessionType sessionType, std::weak_ptr client, bool isLDL, int32_t &keySessionId), - (override)); + (KeySessionType sessionType, std::weak_ptr client, int32_t &keySessionId), (override)); MOCK_METHOD(MediaKeyErrorStatus, generateRequest, - (int32_t keySessionId, InitDataType initDataType, const std::vector &initData), (override)); + (int32_t keySessionId, InitDataType initDataType, const std::vector &initData, + const LimitedDurationLicense &ldlState), + (override)); MOCK_METHOD(MediaKeyErrorStatus, loadSession, (int32_t keySessionId), (override)); MOCK_METHOD(MediaKeyErrorStatus, updateSession, (int32_t keySessionId, const std::vector &responseData), (override)); @@ -58,7 +59,6 @@ class MediaKeysServerInternalMock : public IMediaKeysServerInternal MOCK_METHOD(MediaKeyErrorStatus, getCdmKeySessionId, (int32_t keySessionId, std::string &cdmKeySessionId), (override)); MOCK_METHOD(MediaKeyErrorStatus, decrypt, (int32_t keySessionId, GstBuffer *encrypted, GstCaps *caps), (override)); - MOCK_METHOD(bool, isNetflixPlayreadyKeySystem, (), (const, override)); MOCK_METHOD(void, ping, (std::unique_ptr && heartbeatHandler), (override)); MOCK_METHOD(MediaKeyErrorStatus, releaseKeySession, (int32_t keySessionId), (override)); MOCK_METHOD(MediaKeyErrorStatus, getMetricSystemData, (std::vector & buffer), (override)); diff --git a/tests/unittests/media/server/mocks/main/MediaPipelineServerInternalMock.h b/tests/unittests/media/server/mocks/main/MediaPipelineServerInternalMock.h index 61ee32b61..fccee93d9 100644 --- a/tests/unittests/media/server/mocks/main/MediaPipelineServerInternalMock.h +++ b/tests/unittests/media/server/mocks/main/MediaPipelineServerInternalMock.h @@ -32,11 +32,12 @@ class MediaPipelineServerInternalMock : public IMediaPipelineServerInternal { public: MOCK_METHOD(std::weak_ptr, getClient, (), (override)); - MOCK_METHOD(bool, load, (MediaType type, const std::string &mimeType, const std::string &url), (override)); + MOCK_METHOD(bool, load, (MediaType type, const std::string &mimeType, const std::string &url, bool isLive), + (override)); MOCK_METHOD(bool, attachSource, (const std::unique_ptr &source), (override)); MOCK_METHOD(bool, removeSource, (int32_t id), (override)); MOCK_METHOD(bool, allSourcesAttached, (), (override)); - MOCK_METHOD(bool, play, (), (override)); + MOCK_METHOD(bool, play, (bool &async), (override)); MOCK_METHOD(bool, pause, (), (override)); MOCK_METHOD(bool, stop, (), (override)); MOCK_METHOD(bool, setPlaybackRate, (double rate), (override)); @@ -75,6 +76,7 @@ class MediaPipelineServerInternalMock : public IMediaPipelineServerInternal MOCK_METHOD(bool, setUseBuffering, (bool useBuffering), (override)); MOCK_METHOD(bool, getUseBuffering, (bool &useBuffering), (override)); MOCK_METHOD(bool, switchSource, (const std::unique_ptr &source), (override)); + MOCK_METHOD(bool, getDuration, (int64_t & duration), (override)); MOCK_METHOD(bool, setSubtitleOffset, (int32_t sourceId, int64_t position), (override)); }; } // namespace firebolt::rialto::server diff --git a/tests/unittests/media/server/mocks/service/CdmServiceMock.h b/tests/unittests/media/server/mocks/service/CdmServiceMock.h index 4c0699c46..9196ff498 100644 --- a/tests/unittests/media/server/mocks/service/CdmServiceMock.h +++ b/tests/unittests/media/server/mocks/service/CdmServiceMock.h @@ -37,11 +37,11 @@ class CdmServiceMock : public ICdmService MOCK_METHOD(bool, destroyMediaKeys, (int mediaKeysHandle), (override)); MOCK_METHOD(MediaKeyErrorStatus, createKeySession, (int mediaKeysHandle, KeySessionType sessionType, const std::shared_ptr &client, - bool isLDL, int32_t &keySessionId), + int32_t &keySessionId), (override)); MOCK_METHOD(MediaKeyErrorStatus, generateRequest, (int mediaKeysHandle, int32_t keySessionId, InitDataType initDataType, - const std::vector &initData), + const std::vector &initData, const LimitedDurationLicense &ldlState), (override)); MOCK_METHOD(MediaKeyErrorStatus, loadSession, (int mediaKeysHandle, int32_t keySessionId), (override)); MOCK_METHOD(MediaKeyErrorStatus, updateSession, diff --git a/tests/unittests/media/server/mocks/service/MediaPipelineServiceMock.h b/tests/unittests/media/server/mocks/service/MediaPipelineServiceMock.h index 69812124d..3273b14a0 100644 --- a/tests/unittests/media/server/mocks/service/MediaPipelineServiceMock.h +++ b/tests/unittests/media/server/mocks/service/MediaPipelineServiceMock.h @@ -34,16 +34,17 @@ class MediaPipelineServiceMock : public IMediaPipelineService MOCK_METHOD(bool, createSession, (int, const std::shared_ptr &, std::uint32_t, std::uint32_t), (override)); MOCK_METHOD(bool, destroySession, (int), (override)); - MOCK_METHOD(bool, load, (int, MediaType, const std::string &, const std::string &), (override)); + MOCK_METHOD(bool, load, (int, MediaType, const std::string &, const std::string &, bool), (override)); MOCK_METHOD(bool, attachSource, (int, const std::unique_ptr &), (override)); MOCK_METHOD(bool, removeSource, (int, std::int32_t), (override)); MOCK_METHOD(bool, allSourcesAttached, (int), (override)); - MOCK_METHOD(bool, play, (int), (override)); + MOCK_METHOD(bool, play, (int, bool &), (override)); MOCK_METHOD(bool, pause, (int), (override)); MOCK_METHOD(bool, stop, (int), (override)); MOCK_METHOD(bool, setPlaybackRate, (int, double), (override)); MOCK_METHOD(bool, setPosition, (int, int64_t), (override)); MOCK_METHOD(bool, getPosition, (int sessionId, int64_t &position), (override)); + MOCK_METHOD(bool, getDuration, (int sessionId, int64_t &duration), (override)); MOCK_METHOD(bool, setImmediateOutput, (int sessionId, int32_t sourceId, bool immediateOutput), (override)); MOCK_METHOD(bool, getImmediateOutput, (int sessionId, int32_t sourceId, bool &immediateOutput), (override)); MOCK_METHOD(bool, getStats, (int sessionId, int32_t sourceId, uint64_t &renderedFrames, uint64_t &droppedFrames), diff --git a/tests/unittests/media/server/service/cdmService/CdmServiceTests.cpp b/tests/unittests/media/server/service/cdmService/CdmServiceTests.cpp index 2c27cac62..a5f68acf9 100644 --- a/tests/unittests/media/server/service/cdmService/CdmServiceTests.cpp +++ b/tests/unittests/media/server/service/cdmService/CdmServiceTests.cpp @@ -81,7 +81,6 @@ TEST_F(CdmServiceTests, shouldCreateKeySession) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); destroyMediaKeysShouldSucceed(); @@ -108,7 +107,6 @@ TEST_F(CdmServiceTests, shouldFailToCreateKeySessionWhenMediaKeysClientExists) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); @@ -123,6 +121,20 @@ TEST_F(CdmServiceTests, shouldGenerateRequest) createMediaKeysShouldSucceed(); mediaKeysWillGenerateRequestWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); generateRequestShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus::OK); + isExtendedInterfaceUsedShouldReturn(false); + destroyMediaKeysShouldSucceed(); +} + +TEST_F(CdmServiceTests, shouldGenerateRequestWithLdlEnabled) +{ + triggerSwitchToActiveSuccess(); + mediaKeysFactoryWillCreateMediaKeys(); + createMediaKeysShouldSucceed(); + mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); + createKeySessionShouldSucceed(); + mediaKeysWillGenerateRequestLdlEnabledWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); + generateRequestWithLdlEnabledShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus::OK); + isExtendedInterfaceUsedShouldReturn(true); destroyMediaKeysShouldSucceed(); } @@ -199,7 +211,6 @@ TEST_F(CdmServiceTests, shouldCloseKeySession) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); mediaKeysWillCloseKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); @@ -212,7 +223,6 @@ TEST_F(CdmServiceTests, incrementSessionUsage) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); incrementSessionIdUsageCounter(); @@ -224,7 +234,6 @@ TEST_F(CdmServiceTests, deccrementSessionUsage) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); decrementSessionIdUsageCounter(); @@ -260,7 +269,6 @@ TEST_F(CdmServiceTests, shouldFailToCloseKeySessionWhenMediaKeysFails) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); mediaKeysWillCloseKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::INVALID_STATE); @@ -273,7 +281,6 @@ TEST_F(CdmServiceTests, shouldCloseKeySessionDeferred) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); incrementSessionIdUsageCounter(); @@ -288,7 +295,6 @@ TEST_F(CdmServiceTests, shouldCloseKeySessionDeferredWithFailurePrint) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); incrementSessionIdUsageCounter(); @@ -355,7 +361,6 @@ TEST_F(CdmServiceTests, shouldDecrypt) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); mediaKeysWillDecryptWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); @@ -374,7 +379,6 @@ TEST_F(CdmServiceTests, shouldFailToDecryptWhenMediaKeysFails) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); mediaKeysWillDecryptWithStatus(firebolt::rialto::MediaKeyErrorStatus::INVALID_STATE); @@ -396,11 +400,11 @@ TEST_F(CdmServiceTests, shouldSelectKeyId) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); mediaKeysWillSelectKeyIdWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); selectKeyIdShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus::OK); + isExtendedInterfaceUsedShouldReturn(true); destroyMediaKeysShouldSucceed(); } @@ -415,7 +419,6 @@ TEST_F(CdmServiceTests, shouldFailToSelectKeyIdWhenMediaKeysFails) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); mediaKeysWillSelectKeyIdWithStatus(firebolt::rialto::MediaKeyErrorStatus::INVALID_STATE); @@ -463,8 +466,11 @@ TEST_F(CdmServiceTests, shouldSetDrmHeader) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); + mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); + createKeySessionShouldSucceed(); mediaKeysWillSetDrmHeaderWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); setDrmHeaderShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus::OK); + isExtendedInterfaceUsedShouldReturn(true); destroyMediaKeysShouldSucceed(); } @@ -671,7 +677,6 @@ TEST_F(CdmServiceTests, shouldReleaseKeySession) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); mediaKeysWillReleaseKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); @@ -684,7 +689,6 @@ TEST_F(CdmServiceTests, shouldReleaseKeySessionDeferred) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); incrementSessionIdUsageCounter(); @@ -705,7 +709,6 @@ TEST_F(CdmServiceTests, shouldFailToReleaseKeySessionWhenMediaKeysFails) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); mediaKeysWillReleaseKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::INVALID_STATE); @@ -801,42 +804,29 @@ TEST_F(CdmServiceTests, shouldGetServerCertificateSupportedIfSupportedInActiveSt supportsServerCertificateReturnTrue(); } -TEST_F(CdmServiceTests, shouldCheckThatKeySystemIsPlayready) -{ - triggerSwitchToActiveSuccess(); - mediaKeysFactoryWillCreateMediaKeys(); - createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(true); - mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); - createKeySessionShouldSucceed(); - isNetflixPlayreadyKeySystemShouldReturn(true); - destroyMediaKeysShouldSucceed(); -} - -TEST_F(CdmServiceTests, shouldReturnFalseWhenCheckingPlayreadyKeySystemWhenNoMediaKeys) +TEST_F(CdmServiceTests, shouldReturnFalseWhenCheckingExtendedInterfaceWhenNoMediaKeys) { triggerSwitchToActiveSuccess(); - isNetflixPlayreadyKeySystemShouldReturn(false); + isExtendedInterfaceUsedShouldReturn(false); } -TEST_F(CdmServiceTests, shouldReturnFalseWhenCheckingPlayreadyKeySystemWhenMediaKeysFails) +TEST_F(CdmServiceTests, shouldReturnFalseWhenCheckingExtendedInterfaceWhenMediaKeysFails) { triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); - isNetflixPlayreadyKeySystemShouldReturn(false); + isExtendedInterfaceUsedShouldReturn(false); destroyMediaKeysShouldSucceed(); } -TEST_F(CdmServiceTests, shouldReturnFalseWhenCheckingPlayreadyKeySystemWhenMediaKeysIsNotFoundForSession) +TEST_F(CdmServiceTests, shouldReturnFalseWhenCheckingExtendedInterfaceWhenMediaKeysIsNotFoundForSession) { triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - isNetflixPlayreadyKeySystemShouldReturn(false); + isExtendedInterfaceUsedShouldReturn(false); destroyMediaKeysShouldSucceed(); } diff --git a/tests/unittests/media/server/service/cdmService/CdmServiceTestsFixture.cpp b/tests/unittests/media/server/service/cdmService/CdmServiceTestsFixture.cpp index 73e0a7a43..6243ec01d 100644 --- a/tests/unittests/media/server/service/cdmService/CdmServiceTestsFixture.cpp +++ b/tests/unittests/media/server/service/cdmService/CdmServiceTestsFixture.cpp @@ -35,7 +35,6 @@ const std::vector kKeySystems{"expectedKeySystem1", "expectedKeySys const std::string kVersion{"123"}; constexpr int kMediaKeysHandle{2}; constexpr firebolt::rialto::KeySessionType kKeySessionType{firebolt::rialto::KeySessionType::TEMPORARY}; -constexpr bool kIsLDL{false}; constexpr int kKeySessionId{3}; constexpr firebolt::rialto::InitDataType kInitDataType{firebolt::rialto::InitDataType::CENC}; const std::vector kInitData{6, 7, 2}; @@ -44,6 +43,8 @@ const std::vector keyId{1, 4, 7}; const std::vector kDrmHeader{4, 9, 3}; const uint32_t kSubSampleCount{2}; constexpr uint32_t kInitWithLast15{1}; +constexpr firebolt::rialto::LimitedDurationLicense kLdlState{firebolt::rialto::LimitedDurationLicense::NOT_SPECIFIED}; +constexpr firebolt::rialto::LimitedDurationLicense kLdlStateEnabled{firebolt::rialto::LimitedDurationLicense::ENABLED}; } // namespace CdmServiceTests::CdmServiceTests() @@ -126,13 +127,20 @@ void CdmServiceTests::mediaKeysFactoryWillReturnNullptr() void CdmServiceTests::mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus status) { - EXPECT_CALL(m_mediaKeysMock, createKeySession(kKeySessionType, _, kIsLDL, _)) - .WillOnce(DoAll(SetArgReferee<3>(kKeySessionId), Return(status))); + EXPECT_CALL(m_mediaKeysMock, createKeySession(kKeySessionType, _, _)) + .WillOnce(DoAll(SetArgReferee<2>(kKeySessionId), Return(status))); } void CdmServiceTests::mediaKeysWillGenerateRequestWithStatus(firebolt::rialto::MediaKeyErrorStatus status) { - EXPECT_CALL(m_mediaKeysMock, generateRequest(kKeySessionId, kInitDataType, kInitData)).WillOnce(Return(status)); + EXPECT_CALL(m_mediaKeysMock, generateRequest(kKeySessionId, kInitDataType, kInitData, kLdlState)) + .WillOnce(Return(status)); +} + +void CdmServiceTests::mediaKeysWillGenerateRequestLdlEnabledWithStatus(firebolt::rialto::MediaKeyErrorStatus status) +{ + EXPECT_CALL(m_mediaKeysMock, generateRequest(kKeySessionId, kInitDataType, kInitData, kLdlStateEnabled)) + .WillOnce(Return(status)); } void CdmServiceTests::mediaKeysWillLoadSessionWithStatus(firebolt::rialto::MediaKeyErrorStatus status) @@ -220,11 +228,6 @@ void CdmServiceTests::mediaKeysWillSelectKeyIdWithStatus(firebolt::rialto::Media EXPECT_CALL(m_mediaKeysMock, selectKeyId(kKeySessionId, keyId)).WillOnce(Return(status)); } -void CdmServiceTests::mediaKeysWillCheckIfKeySystemIsPlayready(bool result) -{ - EXPECT_CALL(m_mediaKeysMock, isNetflixPlayreadyKeySystem()).WillOnce(Return(result)); -} - void CdmServiceTests::mediaKeysWillPing() { EXPECT_CALL(*m_heartbeatProcedureMock, createHandler()); @@ -260,21 +263,25 @@ void CdmServiceTests::createKeySessionShouldSucceed() { int32_t returnKeySessionId = -1; EXPECT_EQ(firebolt::rialto::MediaKeyErrorStatus::OK, - m_sut.createKeySession(kMediaKeysHandle, kKeySessionType, m_mediaKeysClientMock, kIsLDL, - returnKeySessionId)); + m_sut.createKeySession(kMediaKeysHandle, kKeySessionType, m_mediaKeysClientMock, returnKeySessionId)); EXPECT_GE(returnKeySessionId, -1); } void CdmServiceTests::createKeySessionShouldFailWithReturnStatus(firebolt::rialto::MediaKeyErrorStatus status) { int32_t returnKeySessionId = -1; - EXPECT_EQ(status, m_sut.createKeySession(kMediaKeysHandle, kKeySessionType, m_mediaKeysClientMock, kIsLDL, - returnKeySessionId)); + EXPECT_EQ(status, + m_sut.createKeySession(kMediaKeysHandle, kKeySessionType, m_mediaKeysClientMock, returnKeySessionId)); } void CdmServiceTests::generateRequestShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus status) { - EXPECT_EQ(status, m_sut.generateRequest(kMediaKeysHandle, kKeySessionId, kInitDataType, kInitData)); + EXPECT_EQ(status, m_sut.generateRequest(kMediaKeysHandle, kKeySessionId, kInitDataType, kInitData, kLdlState)); +} + +void CdmServiceTests::generateRequestWithLdlEnabledShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus status) +{ + EXPECT_EQ(status, m_sut.generateRequest(kMediaKeysHandle, kKeySessionId, kInitDataType, kInitData, kLdlStateEnabled)); } void CdmServiceTests::loadSessionShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus status) @@ -370,9 +377,9 @@ void CdmServiceTests::releaseKeySessionShouldReturnStatus(firebolt::rialto::Medi EXPECT_EQ(status, m_sut.releaseKeySession(kMediaKeysHandle, kKeySessionId)); } -void CdmServiceTests::isNetflixPlayreadyKeySystemShouldReturn(bool result) +void CdmServiceTests::isExtendedInterfaceUsedShouldReturn(bool result) { - EXPECT_EQ(result, m_sut.isNetflixPlayreadyKeySystem(kKeySessionId)); + EXPECT_EQ(result, m_sut.isExtendedInterfaceUsed(kKeySessionId)); } void CdmServiceTests::getSupportedKeySystemsShouldSucceed() diff --git a/tests/unittests/media/server/service/cdmService/CdmServiceTestsFixture.h b/tests/unittests/media/server/service/cdmService/CdmServiceTestsFixture.h index 77939fd0b..36a8c3676 100644 --- a/tests/unittests/media/server/service/cdmService/CdmServiceTestsFixture.h +++ b/tests/unittests/media/server/service/cdmService/CdmServiceTestsFixture.h @@ -45,6 +45,7 @@ class CdmServiceTests : public testing::Test void mediaKeysFactoryWillReturnNullptr(); void mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus status); void mediaKeysWillGenerateRequestWithStatus(firebolt::rialto::MediaKeyErrorStatus status); + void mediaKeysWillGenerateRequestLdlEnabledWithStatus(firebolt::rialto::MediaKeyErrorStatus status); void mediaKeysWillLoadSessionWithStatus(firebolt::rialto::MediaKeyErrorStatus status); void mediaKeysWillUpdateSessionWithStatus(firebolt::rialto::MediaKeyErrorStatus status); void mediaKeysWillCloseKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus status); @@ -62,7 +63,6 @@ class CdmServiceTests : public testing::Test void mediaKeysWillReleaseKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus status); void mediaKeysWillDecryptWithStatus(firebolt::rialto::MediaKeyErrorStatus status); void mediaKeysWillSelectKeyIdWithStatus(firebolt::rialto::MediaKeyErrorStatus status); - void mediaKeysWillCheckIfKeySystemIsPlayready(bool result); void mediaKeysWillPing(); void mediaKeysWillGetMetricSystemDataWithStatus(firebolt::rialto::MediaKeyErrorStatus status); @@ -85,6 +85,7 @@ class CdmServiceTests : public testing::Test void createKeySessionShouldSucceed(); void createKeySessionShouldFailWithReturnStatus(firebolt::rialto::MediaKeyErrorStatus status); void generateRequestShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus status); + void generateRequestWithLdlEnabledShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus status); void loadSessionShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus status); void updateSessionShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus status); void closeKeySessionShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus status); @@ -102,7 +103,7 @@ class CdmServiceTests : public testing::Test void getLastDrmErrorShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus status); void getDrmTimeShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus status); void releaseKeySessionShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus status); - void isNetflixPlayreadyKeySystemShouldReturn(bool result); + void isExtendedInterfaceUsedShouldReturn(bool result); void incrementSessionIdUsageCounter(); void decrementSessionIdUsageCounter(); void getMetricSystemDataShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus status); diff --git a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTests.cpp b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTests.cpp index 4cd1f95d1..56643a42c 100644 --- a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTests.cpp +++ b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTests.cpp @@ -322,6 +322,26 @@ TEST_F(MediaPipelineServiceTests, shouldGetPosition) getPositionShouldSucceed(); } +TEST_F(MediaPipelineServiceTests, shouldFailToGetDurationForNotExistingSession) +{ + createMediaPipelineShouldSuccess(); + getDurationShouldFail(); +} + +TEST_F(MediaPipelineServiceTests, shouldFailToGetDuration) +{ + initSession(); + mediaPipelineWillFailToGetDuration(); + getDurationShouldFail(); +} + +TEST_F(MediaPipelineServiceTests, shouldGetDuration) +{ + initSession(); + mediaPipelineWillGetDuration(); + getDurationShouldSucceed(); +} + TEST_F(MediaPipelineServiceTests, shouldFailToSetImmediateOutputForNotExistingSession) { createMediaPipelineShouldSuccess(); diff --git a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.cpp b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.cpp index 9b9e441dd..ca43df569 100644 --- a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.cpp +++ b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.cpp @@ -69,6 +69,7 @@ const std::string kTextTrackIdentifier{"TextTrackIdentifier"}; constexpr uint32_t kBufferingLimit{4324}; constexpr bool kUseBuffering{true}; constexpr uint64_t kStopPosition{23412}; +constexpr bool kIsLive{false}; } // namespace namespace firebolt::rialto @@ -98,12 +99,12 @@ MediaPipelineServiceTests::MediaPipelineServiceTests() void MediaPipelineServiceTests::mediaPipelineWillLoad() { - EXPECT_CALL(m_mediaPipelineMock, load(kType, kMimeType, kUrl)).WillOnce(Return(true)); + EXPECT_CALL(m_mediaPipelineMock, load(kType, kMimeType, kUrl, kIsLive)).WillOnce(Return(true)); } void MediaPipelineServiceTests::mediaPipelineWillFailToLoad() { - EXPECT_CALL(m_mediaPipelineMock, load(kType, kMimeType, kUrl)).WillOnce(Return(false)); + EXPECT_CALL(m_mediaPipelineMock, load(kType, kMimeType, kUrl, kIsLive)).WillOnce(Return(false)); } void MediaPipelineServiceTests::mediaPipelineWillAttachSource() @@ -138,12 +139,12 @@ void MediaPipelineServiceTests::mediaPipelineWillFailToAllSourcesAttached() void MediaPipelineServiceTests::mediaPipelineWillPlay() { - EXPECT_CALL(m_mediaPipelineMock, play()).WillOnce(Return(true)); + EXPECT_CALL(m_mediaPipelineMock, play(_)).WillOnce(Return(true)); } void MediaPipelineServiceTests::mediaPipelineWillFailToPlay() { - EXPECT_CALL(m_mediaPipelineMock, play()).WillOnce(Return(false)); + EXPECT_CALL(m_mediaPipelineMock, play(_)).WillOnce(Return(false)); } void MediaPipelineServiceTests::mediaPipelineWillPause() @@ -222,6 +223,22 @@ void MediaPipelineServiceTests::mediaPipelineWillFailToGetPosition() EXPECT_CALL(m_mediaPipelineMock, getPosition(_)).WillOnce(Return(false)); } +void MediaPipelineServiceTests::mediaPipelineWillGetDuration() +{ + EXPECT_CALL(m_mediaPipelineMock, getDuration(_)) + .WillOnce(Invoke( + [&](int64_t &pos) + { + pos = kDuration; + return true; + })); +} + +void MediaPipelineServiceTests::mediaPipelineWillFailToGetDuration() +{ + EXPECT_CALL(m_mediaPipelineMock, getDuration(_)).WillOnce(Return(false)); +} + void MediaPipelineServiceTests::mediaPipelineWillSetImmediateOutput() { EXPECT_CALL(m_mediaPipelineMock, setImmediateOutput(_, _)).WillOnce(Return(true)); @@ -580,12 +597,12 @@ void MediaPipelineServiceTests::destroySessionShouldFail() void MediaPipelineServiceTests::loadShouldSucceed() { - EXPECT_TRUE(m_sut->load(kSessionId, kType, kMimeType, kUrl)); + EXPECT_TRUE(m_sut->load(kSessionId, kType, kMimeType, kUrl, kIsLive)); } void MediaPipelineServiceTests::loadShouldFail() { - EXPECT_FALSE(m_sut->load(kSessionId, kType, kMimeType, kUrl)); + EXPECT_FALSE(m_sut->load(kSessionId, kType, kMimeType, kUrl, kIsLive)); } void MediaPipelineServiceTests::attachSourceShouldSucceed() @@ -624,12 +641,14 @@ void MediaPipelineServiceTests::allSourcesAttachedShouldFail() void MediaPipelineServiceTests::playShouldSucceed() { - EXPECT_TRUE(m_sut->play(kSessionId)); + bool isAsync{false}; + EXPECT_TRUE(m_sut->play(kSessionId, isAsync)); } void MediaPipelineServiceTests::playShouldFail() { - EXPECT_FALSE(m_sut->play(kSessionId)); + bool isAsync{false}; + EXPECT_FALSE(m_sut->play(kSessionId, isAsync)); } void MediaPipelineServiceTests::pauseShouldSucceed() @@ -705,6 +724,19 @@ void MediaPipelineServiceTests::getPositionShouldFail() EXPECT_FALSE(m_sut->getPosition(kSessionId, targetPosition)); } +void MediaPipelineServiceTests::getDurationShouldSucceed() +{ + std::int64_t targetDuration{}; + EXPECT_TRUE(m_sut->getDuration(kSessionId, targetDuration)); + EXPECT_EQ(targetDuration, kDuration); +} + +void MediaPipelineServiceTests::getDurationShouldFail() +{ + std::int64_t targetDuration{}; + EXPECT_FALSE(m_sut->getDuration(kSessionId, targetDuration)); +} + void MediaPipelineServiceTests::getStatsShouldSucceed() { std::uint64_t renderedFrames; diff --git a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.h b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.h index ead87a642..347d64216 100644 --- a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.h +++ b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.h @@ -64,6 +64,8 @@ class MediaPipelineServiceTests : public testing::Test void mediaPipelineWillFailToHaveData(); void mediaPipelineWillGetPosition(); void mediaPipelineWillFailToGetPosition(); + void mediaPipelineWillGetDuration(); + void mediaPipelineWillFailToGetDuration(); void mediaPipelineWillSetImmediateOutput(); void mediaPipelineWillFailToSetImmediateOutput(); void mediaPipelineWillGetImmediateOutput(); @@ -156,6 +158,8 @@ class MediaPipelineServiceTests : public testing::Test void haveDataShouldFail(); void getPositionShouldSucceed(); void getPositionShouldFail(); + void getDurationShouldSucceed(); + void getDurationShouldFail(); void setImmediateOutputShouldSucceed(); void setImmediateOutputShouldFail(); void getImmediateOutputShouldSucceed(); diff --git a/tests/unittests/serverManager/mocks/SessionServerAppFactoryMock.h b/tests/unittests/serverManager/mocks/SessionServerAppFactoryMock.h index 4b500d482..deba36825 100644 --- a/tests/unittests/serverManager/mocks/SessionServerAppFactoryMock.h +++ b/tests/unittests/serverManager/mocks/SessionServerAppFactoryMock.h @@ -33,12 +33,12 @@ class SessionServerAppFactoryMock : public ISessionServerAppFactory SessionServerAppFactoryMock() = default; virtual ~SessionServerAppFactoryMock() = default; - MOCK_METHOD(std::unique_ptr, create, + MOCK_METHOD(std::shared_ptr, create, (const std::string &appId, const firebolt::rialto::common::SessionServerState &initialState, const firebolt::rialto::common::AppConfig &appConfig, SessionServerAppManager &sessionServerAppManager, std::unique_ptr &&namedSocket), (const, override)); - MOCK_METHOD(std::unique_ptr, create, + MOCK_METHOD(std::shared_ptr, create, (SessionServerAppManager & sessionServerAppManager, std::unique_ptr &&namedSocket), (const, override)); diff --git a/tests/unittests/serverManager/unittests/common/HealthcheckServiceTests.cpp b/tests/unittests/serverManager/unittests/common/HealthcheckServiceTests.cpp index 4289aa15b..70d6509c8 100644 --- a/tests/unittests/serverManager/unittests/common/HealthcheckServiceTests.cpp +++ b/tests/unittests/serverManager/unittests/common/HealthcheckServiceTests.cpp @@ -285,3 +285,22 @@ TEST_F(HealthcheckServiceTests, WillFailToFailPingWithWrongId) // There should be no error indication here. triggerOnPingFailed(pingId + 1); } + +TEST_F(HealthcheckServiceTests, WillSkipRestartingServerWhenAcksAreReceivedLater) +{ + int pingId{-1}; + timerWillBeCreated(); + createSut(); + pingWillBeSent(pingId); + triggerPingTimeout(); + const int firstPingId{pingId}; + triggerOnPingSent(pingId); + errorIndicationWillBeSent(); + pingWillBeSent(pingId); + triggerPingTimeout(); + triggerOnAckReceived(kServerId, firstPingId, kSuccess); + triggerOnPingSent(pingId); + errorIndicationWillBeSent(); + pingWillBeSent(pingId); + triggerPingTimeout(); +} diff --git a/tests/unittests/serverManager/unittests/common/SessionServerAppManagerTestsFixture.cpp b/tests/unittests/serverManager/unittests/common/SessionServerAppManagerTestsFixture.cpp index 3998a8a5e..83994a266 100644 --- a/tests/unittests/serverManager/unittests/common/SessionServerAppManagerTestsFixture.cpp +++ b/tests/unittests/serverManager/unittests/common/SessionServerAppManagerTestsFixture.cpp @@ -81,15 +81,13 @@ MATCHER_P(SmartPtrMatcher, expectedPtr, "") SessionServerAppManagerTests::SessionServerAppManagerTests() : m_controller{std::make_unique>()}, m_stateObserver{std::make_shared>()}, - m_sessionServerApp{std::make_unique>()}, + m_sessionServerAppMock{std::make_shared>()}, m_sessionServerAppFactory{ std::make_unique>()}, m_healthcheckServiceFactory{ std::make_unique>()}, m_healthcheckService{std::make_unique>()}, m_controllerMock{dynamic_cast &>(*m_controller)}, - m_sessionServerAppMock{ - dynamic_cast &>(*m_sessionServerApp)}, m_sessionServerAppFactoryMock{dynamic_cast &>( *m_sessionServerAppFactory)}, m_healthcheckServiceFactoryMock{ @@ -119,16 +117,16 @@ void SessionServerAppManagerTests::sessionServerLaunchWillFail(const firebolt::r EXPECT_CALL(m_namedSocketFactoryMock, createNamedSocket()).WillOnce(Return(ByMove(std::move(m_namedSocket)))); EXPECT_CALL(m_sessionServerAppFactoryMock, create(kAppName, state, kAppConfig, _, SmartPtrMatcher(&m_namedSocketMock))) - .WillOnce(Return(ByMove(std::move(m_sessionServerApp)))); - EXPECT_CALL(m_sessionServerAppMock, launch()).WillOnce(Return(false)); + .WillOnce(Return(m_sessionServerAppMock)); + EXPECT_CALL(*m_sessionServerAppMock, launch()).WillOnce(Return(false)); } void SessionServerAppManagerTests::preloadedSessionServerLaunchWillFail() { EXPECT_CALL(m_namedSocketFactoryMock, createNamedSocket()).WillOnce(Return(ByMove(std::move(m_namedSocket)))); EXPECT_CALL(m_sessionServerAppFactoryMock, create(_, SmartPtrMatcher(&m_namedSocketMock))) - .WillOnce(Return(ByMove(std::move(m_sessionServerApp)))); - EXPECT_CALL(m_sessionServerAppMock, launch()).WillOnce(Return(false)); + .WillOnce(Return(m_sessionServerAppMock)); + EXPECT_CALL(*m_sessionServerAppMock, launch()).WillOnce(Return(false)); } void SessionServerAppManagerTests::sessionServerConnectWillFail(const firebolt::rialto::common::SessionServerState &state) @@ -136,13 +134,13 @@ void SessionServerAppManagerTests::sessionServerConnectWillFail(const firebolt:: EXPECT_CALL(m_namedSocketFactoryMock, createNamedSocket()).WillOnce(Return(ByMove(std::move(m_namedSocket)))); EXPECT_CALL(m_sessionServerAppFactoryMock, create(kAppName, state, kAppConfig, _, SmartPtrMatcher(&m_namedSocketMock))) - .WillOnce(Return(ByMove(std::move(m_sessionServerApp)))); - EXPECT_CALL(m_sessionServerAppMock, launch()).WillOnce(Return(true)); - EXPECT_CALL(m_sessionServerAppMock, getAppName()).WillRepeatedly(ReturnRef(kAppName)); - EXPECT_CALL(m_sessionServerAppMock, getAppManagementSocketName()).WillOnce(Return(kAppMgmtSocket)); - EXPECT_CALL(m_sessionServerAppMock, getServerId()).WillRepeatedly(Return(kServerId)); + .WillOnce(Return(m_sessionServerAppMock)); + EXPECT_CALL(*m_sessionServerAppMock, launch()).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, getAppName()).WillRepeatedly(ReturnRef(kAppName)); + EXPECT_CALL(*m_sessionServerAppMock, getAppManagementSocketName()).WillOnce(Return(kAppMgmtSocket)); + EXPECT_CALL(*m_sessionServerAppMock, getServerId()).WillRepeatedly(Return(kServerId)); EXPECT_CALL(m_controllerMock, createClient(kServerId, kAppMgmtSocket)).WillOnce(Return(false)); - EXPECT_CALL(m_sessionServerAppMock, kill()); + EXPECT_CALL(*m_sessionServerAppMock, kill()); EXPECT_CALL(m_healthcheckServiceMock, onServerRemoved(kServerId)); } @@ -150,19 +148,23 @@ void SessionServerAppManagerTests::preloadedSessionServerConnectWillFail() { EXPECT_CALL(m_namedSocketFactoryMock, createNamedSocket()).WillOnce(Return(ByMove(std::move(m_namedSocket)))); EXPECT_CALL(m_sessionServerAppFactoryMock, create(_, SmartPtrMatcher(&m_namedSocketMock))) - .WillOnce(Return(ByMove(std::move(m_sessionServerApp)))); - EXPECT_CALL(m_sessionServerAppMock, launch()).WillOnce(Return(true)); - EXPECT_CALL(m_sessionServerAppMock, getServerId()).WillRepeatedly(Return(kServerId)); - EXPECT_CALL(m_sessionServerAppMock, getAppManagementSocketName()).WillOnce(Return(kAppMgmtSocket)); + .WillOnce(Return(m_sessionServerAppMock)); + EXPECT_CALL(*m_sessionServerAppMock, launch()).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, getServerId()).WillRepeatedly(Return(kServerId)); + EXPECT_CALL(*m_sessionServerAppMock, getAppManagementSocketName()).WillOnce(Return(kAppMgmtSocket)); EXPECT_CALL(m_controllerMock, createClient(kServerId, kAppMgmtSocket)).WillOnce(Return(false)); - EXPECT_CALL(m_sessionServerAppMock, kill()); + EXPECT_CALL(*m_sessionServerAppMock, kill()); EXPECT_CALL(m_healthcheckServiceMock, onServerRemoved(kServerId)); } void SessionServerAppManagerTests::sessionServerChangeStateWillFail(const firebolt::rialto::common::SessionServerState &state) { - EXPECT_CALL(m_sessionServerAppMock, getServerId()).WillRepeatedly(Return(kServerId)); - EXPECT_CALL(m_sessionServerAppMock, setExpectedState(state)); + EXPECT_CALL(*m_sessionServerAppMock, getServerId()).WillRepeatedly(Return(kServerId)); + EXPECT_CALL(*m_sessionServerAppMock, setExpectedState(state)); + if (firebolt::rialto::common::SessionServerState::NOT_RUNNING == state) + { + EXPECT_CALL(m_healthcheckServiceMock, onServerRemoved(kServerId)); + } EXPECT_CALL(m_controllerMock, performSetState(kServerId, state)).WillOnce(Return(false)); } @@ -171,11 +173,11 @@ void SessionServerAppManagerTests::sessionServerWillLaunch(const firebolt::rialt EXPECT_CALL(m_namedSocketFactoryMock, createNamedSocket()).WillOnce(Return(ByMove(std::move(m_namedSocket)))); EXPECT_CALL(m_sessionServerAppFactoryMock, create(kAppName, state, kAppConfig, _, SmartPtrMatcher(&m_namedSocketMock))) - .WillOnce(Return(ByMove(std::move(m_sessionServerApp)))); - EXPECT_CALL(m_sessionServerAppMock, launch()).WillOnce(Return(true)); - EXPECT_CALL(m_sessionServerAppMock, getAppName()).WillRepeatedly(ReturnRef(kAppName)); - EXPECT_CALL(m_sessionServerAppMock, getAppManagementSocketName()).WillOnce(Return(kAppMgmtSocket)); - EXPECT_CALL(m_sessionServerAppMock, getServerId()).WillRepeatedly(Return(kServerId)); + .WillOnce(Return(m_sessionServerAppMock)); + EXPECT_CALL(*m_sessionServerAppMock, launch()).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, getAppName()).WillRepeatedly(ReturnRef(kAppName)); + EXPECT_CALL(*m_sessionServerAppMock, getAppManagementSocketName()).WillOnce(Return(kAppMgmtSocket)); + EXPECT_CALL(*m_sessionServerAppMock, getServerId()).WillRepeatedly(Return(kServerId)); EXPECT_CALL(m_controllerMock, createClient(kServerId, kAppMgmtSocket)).WillOnce(Return(true)); } @@ -183,62 +185,60 @@ void SessionServerAppManagerTests::preloadedSessionServerWillLaunch() { EXPECT_CALL(m_namedSocketFactoryMock, createNamedSocket()).WillOnce(Return(ByMove(std::move(m_namedSocket)))); EXPECT_CALL(m_sessionServerAppFactoryMock, create(_, SmartPtrMatcher(&m_namedSocketMock))) - .WillOnce(Return(ByMove(std::move(m_sessionServerApp)))); - EXPECT_CALL(m_sessionServerAppMock, launch()).WillOnce(Return(true)); - EXPECT_CALL(m_sessionServerAppMock, getServerId()).WillRepeatedly(Return(kServerId)); - EXPECT_CALL(m_sessionServerAppMock, getAppManagementSocketName()).WillOnce(Return(kAppMgmtSocket)); + .WillOnce(Return(m_sessionServerAppMock)); + EXPECT_CALL(*m_sessionServerAppMock, launch()).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, getServerId()).WillRepeatedly(Return(kServerId)); + EXPECT_CALL(*m_sessionServerAppMock, getAppManagementSocketName()).WillOnce(Return(kAppMgmtSocket)); EXPECT_CALL(m_controllerMock, createClient(kServerId, kAppMgmtSocket)).WillOnce(Return(true)); } void SessionServerAppManagerTests::preloadedSessionServerWillFailToConfigure( const firebolt::rialto::common::SessionServerState &state) { - EXPECT_CALL(m_sessionServerAppMock, configure(kAppName, state, kAppConfig)).WillOnce(Return(false)); + EXPECT_CALL(*m_sessionServerAppMock, configure(kAppName, state, kAppConfig)).WillOnce(Return(false)); } void SessionServerAppManagerTests::preloadedSessionServerWillBeConfigured( const firebolt::rialto::common::SessionServerState &state) { - EXPECT_CALL(m_sessionServerAppMock, configure(kAppName, state, kAppConfig)).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, configure(kAppName, state, kAppConfig)).WillOnce(Return(true)); } void SessionServerAppManagerTests::preloadedSessionServerWillCloseWithError() { - m_secondSessionServerApp = std::make_unique>(); - auto &secondSessionServerAppMock{ - dynamic_cast &>(*m_secondSessionServerApp)}; - EXPECT_CALL(m_sessionServerAppMock, isPreloaded()).WillOnce(Return(true)).WillOnce(Return(false)); - EXPECT_CALL(m_sessionServerAppMock, isConnected()).WillOnce(Return(true)); - EXPECT_CALL(m_sessionServerAppMock, getAppName()).WillOnce(ReturnRef(kEmptyAppName)).RetiresOnSaturation(); + m_secondSessionServerAppMock = std::make_shared>(); + EXPECT_CALL(*m_sessionServerAppMock, isPreloaded()).WillOnce(Return(true)).WillOnce(Return(false)); + EXPECT_CALL(*m_sessionServerAppMock, isConnected()).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, getAppName()).WillOnce(ReturnRef(kEmptyAppName)).RetiresOnSaturation(); EXPECT_CALL(m_controllerMock, removeClient(kServerId)); - EXPECT_CALL(m_sessionServerAppMock, kill()); + EXPECT_CALL(*m_sessionServerAppMock, kill()); EXPECT_CALL(m_healthcheckServiceMock, onServerRemoved(kServerId)); EXPECT_CALL(m_namedSocketFactoryMock, createNamedSocket()).WillOnce(Return(ByMove(std::move(m_namedSocket)))); - EXPECT_CALL(m_sessionServerAppFactoryMock, create(_, _)).WillOnce(Return(ByMove(std::move(m_secondSessionServerApp)))); - EXPECT_CALL(secondSessionServerAppMock, launch()).WillOnce(Return(false)); + EXPECT_CALL(m_sessionServerAppFactoryMock, create(_, _)).WillOnce(Return(m_secondSessionServerAppMock)); + EXPECT_CALL(*m_secondSessionServerAppMock, launch()).WillOnce(Return(false)); } void SessionServerAppManagerTests::sessionServerWillChangeState(const firebolt::rialto::common::SessionServerState &state) { - EXPECT_CALL(m_sessionServerAppMock, setExpectedState(state)); + EXPECT_CALL(*m_sessionServerAppMock, setExpectedState(state)); EXPECT_CALL(m_controllerMock, performSetState(kServerId, state)).WillOnce(Return(true)); } void SessionServerAppManagerTests::sessionServerWillChangeStateToUninitialized() { - EXPECT_CALL(m_sessionServerAppMock, isNamedSocketInitialized()).WillOnce(Return(false)); - EXPECT_CALL(m_sessionServerAppMock, cancelStartupTimer()); - EXPECT_CALL(m_sessionServerAppMock, getInitialState()) + EXPECT_CALL(*m_sessionServerAppMock, isNamedSocketInitialized()).WillOnce(Return(false)); + EXPECT_CALL(*m_sessionServerAppMock, cancelStartupTimer()); + EXPECT_CALL(*m_sessionServerAppMock, getInitialState()) .WillOnce(Return(firebolt::rialto::common::SessionServerState::INACTIVE)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketName()).WillRepeatedly(Return(kSessionServerSocketName)); - EXPECT_CALL(m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); - EXPECT_CALL(m_sessionServerAppMock, getAppName()).WillOnce(ReturnRef(kAppName)).RetiresOnSaturation(); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketPermissions()).WillOnce(Return(kSocketPermissions)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketOwner()).WillOnce(Return(kSocketOwner)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketGroup()).WillOnce(Return(kSocketGroup)); - EXPECT_CALL(m_sessionServerAppMock, getMaxPlaybackSessions()).WillOnce(Return(kMaxSessions)); - EXPECT_CALL(m_sessionServerAppMock, getMaxWebAudioPlayers()).WillOnce(Return(kMaxWebAudioPlayers)); - EXPECT_CALL(m_sessionServerAppMock, isPreloaded()).WillOnce(Return(false)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketName()).WillRepeatedly(Return(kSessionServerSocketName)); + EXPECT_CALL(*m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); + EXPECT_CALL(*m_sessionServerAppMock, getAppName()).WillOnce(ReturnRef(kAppName)).RetiresOnSaturation(); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketPermissions()).WillOnce(Return(kSocketPermissions)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketOwner()).WillOnce(Return(kSocketOwner)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketGroup()).WillOnce(Return(kSocketGroup)); + EXPECT_CALL(*m_sessionServerAppMock, getMaxPlaybackSessions()).WillOnce(Return(kMaxSessions)); + EXPECT_CALL(*m_sessionServerAppMock, getMaxWebAudioPlayers()).WillOnce(Return(kMaxWebAudioPlayers)); + EXPECT_CALL(*m_sessionServerAppMock, isPreloaded()).WillOnce(Return(false)); EXPECT_CALL(m_controllerMock, performSetConfiguration(kServerId, firebolt::rialto::common::SessionServerState::INACTIVE, kSessionServerSocketName, kClientDisplayName, @@ -250,9 +250,9 @@ void SessionServerAppManagerTests::sessionServerWillChangeStateToUninitialized() void SessionServerAppManagerTests::preloadedSessionServerWillChangeStateToUninitialized() { - EXPECT_CALL(m_sessionServerAppMock, getAppName()).WillRepeatedly(ReturnRef(kEmptyAppName)); - EXPECT_CALL(m_sessionServerAppMock, cancelStartupTimer()); - EXPECT_CALL(m_sessionServerAppMock, isPreloaded()).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, getAppName()).WillRepeatedly(ReturnRef(kEmptyAppName)); + EXPECT_CALL(*m_sessionServerAppMock, cancelStartupTimer()); + EXPECT_CALL(*m_sessionServerAppMock, isPreloaded()).WillOnce(Return(true)); } void SessionServerAppManagerTests::sessionServerWillChangeStateToInactive() @@ -262,22 +262,20 @@ void SessionServerAppManagerTests::sessionServerWillChangeStateToInactive() void SessionServerAppManagerTests::preloadedSessionServerWillSetConfiguration() { - EXPECT_CALL(m_sessionServerAppMock, isNamedSocketInitialized()).WillOnce(Return(false)); - m_secondSessionServerApp = std::make_unique>(); - auto &secondSessionServerAppMock{ - dynamic_cast &>(*m_secondSessionServerApp)}; - EXPECT_CALL(m_sessionServerAppMock, isPreloaded()).WillOnce(Return(true)); - EXPECT_CALL(m_sessionServerAppMock, isConnected()).WillOnce(Return(true)); - EXPECT_CALL(m_sessionServerAppMock, getAppName()).WillRepeatedly(ReturnRef(kEmptyAppName)); - EXPECT_CALL(m_sessionServerAppMock, getInitialState()) + EXPECT_CALL(*m_sessionServerAppMock, isNamedSocketInitialized()).WillOnce(Return(false)); + m_secondSessionServerAppMock = std::make_shared>(); + EXPECT_CALL(*m_sessionServerAppMock, isPreloaded()).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, isConnected()).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, getAppName()).WillRepeatedly(ReturnRef(kEmptyAppName)); + EXPECT_CALL(*m_sessionServerAppMock, getInitialState()) .WillOnce(Return(firebolt::rialto::common::SessionServerState::INACTIVE)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketName()).WillRepeatedly(Return(kSessionServerSocketName)); - EXPECT_CALL(m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketPermissions()).WillOnce(Return(kSocketPermissions)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketOwner()).WillOnce(Return(kSocketOwner)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketGroup()).WillOnce(Return(kSocketGroup)); - EXPECT_CALL(m_sessionServerAppMock, getMaxPlaybackSessions()).WillOnce(Return(kMaxSessions)); - EXPECT_CALL(m_sessionServerAppMock, getMaxWebAudioPlayers()).WillOnce(Return(kMaxWebAudioPlayers)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketName()).WillRepeatedly(Return(kSessionServerSocketName)); + EXPECT_CALL(*m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketPermissions()).WillOnce(Return(kSocketPermissions)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketOwner()).WillOnce(Return(kSocketOwner)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketGroup()).WillOnce(Return(kSocketGroup)); + EXPECT_CALL(*m_sessionServerAppMock, getMaxPlaybackSessions()).WillOnce(Return(kMaxSessions)); + EXPECT_CALL(*m_sessionServerAppMock, getMaxWebAudioPlayers()).WillOnce(Return(kMaxWebAudioPlayers)); EXPECT_CALL(m_controllerMock, performSetConfiguration(kServerId, firebolt::rialto::common::SessionServerState::INACTIVE, kSessionServerSocketName, kClientDisplayName, @@ -285,48 +283,46 @@ void SessionServerAppManagerTests::preloadedSessionServerWillSetConfiguration() kSocketOwner, kSocketGroup, kEmptyAppName)) .WillOnce(Return(true)); EXPECT_CALL(m_namedSocketFactoryMock, createNamedSocket()).WillOnce(Return(ByMove(std::move(m_namedSocket)))); - EXPECT_CALL(m_sessionServerAppFactoryMock, create(_, _)).WillOnce(Return(ByMove(std::move(m_secondSessionServerApp)))); - EXPECT_CALL(secondSessionServerAppMock, launch()).WillOnce(Return(false)); + EXPECT_CALL(m_sessionServerAppFactoryMock, create(_, _)).WillOnce(Return(m_secondSessionServerAppMock)); + EXPECT_CALL(*m_secondSessionServerAppMock, launch()).WillOnce(Return(false)); } void SessionServerAppManagerTests::preloadedSessionServerWillSetConfigurationWithFd() { - EXPECT_CALL(m_sessionServerAppMock, isNamedSocketInitialized()).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, isNamedSocketInitialized()).WillOnce(Return(true)); EXPECT_CALL(m_namedSocketFactoryMock, createNamedSocket()).WillOnce(Return(ByMove(std::move(m_namedSocket)))); - m_secondSessionServerApp = std::make_unique>(); - auto &secondSessionServerAppMock{ - dynamic_cast &>(*m_secondSessionServerApp)}; - EXPECT_CALL(m_sessionServerAppMock, isPreloaded()).WillOnce(Return(true)); - EXPECT_CALL(m_sessionServerAppMock, isConnected()).WillOnce(Return(true)); - EXPECT_CALL(m_sessionServerAppMock, getAppName()).WillRepeatedly(ReturnRef(kEmptyAppName)); - EXPECT_CALL(m_sessionServerAppMock, getInitialState()) + m_secondSessionServerAppMock = std::make_shared>(); + EXPECT_CALL(*m_sessionServerAppMock, isPreloaded()).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, isConnected()).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, getAppName()).WillRepeatedly(ReturnRef(kEmptyAppName)); + EXPECT_CALL(*m_sessionServerAppMock, getInitialState()) .WillOnce(Return(firebolt::rialto::common::SessionServerState::INACTIVE)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketFd()).WillOnce(Return(kSessionServerSocketFd)); - EXPECT_CALL(m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); - EXPECT_CALL(m_sessionServerAppMock, getMaxPlaybackSessions()).WillOnce(Return(kMaxSessions)); - EXPECT_CALL(m_sessionServerAppMock, getMaxWebAudioPlayers()).WillOnce(Return(kMaxWebAudioPlayers)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketFd()).WillOnce(Return(kSessionServerSocketFd)); + EXPECT_CALL(*m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); + EXPECT_CALL(*m_sessionServerAppMock, getMaxPlaybackSessions()).WillOnce(Return(kMaxSessions)); + EXPECT_CALL(*m_sessionServerAppMock, getMaxWebAudioPlayers()).WillOnce(Return(kMaxWebAudioPlayers)); EXPECT_CALL(m_controllerMock, performSetConfiguration(kServerId, firebolt::rialto::common::SessionServerState::INACTIVE, kSessionServerSocketFd, kClientDisplayName, MaxResourceMatcher(kMaxSessions, kMaxWebAudioPlayers), kEmptyAppName)) .WillOnce(Return(true)); - EXPECT_CALL(m_sessionServerAppFactoryMock, create(_, _)).WillOnce(Return(ByMove(std::move(m_secondSessionServerApp)))); - EXPECT_CALL(secondSessionServerAppMock, launch()).WillOnce(Return(false)); + EXPECT_CALL(m_sessionServerAppFactoryMock, create(_, _)).WillOnce(Return(m_secondSessionServerAppMock)); + EXPECT_CALL(*m_secondSessionServerAppMock, launch()).WillOnce(Return(false)); } void SessionServerAppManagerTests::sessionServerWillFailToSetConfigurationWithFd() { - EXPECT_CALL(m_sessionServerAppMock, isNamedSocketInitialized()).WillOnce(Return(true)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketFd()).WillOnce(Return(kSessionServerSocketFd)); - EXPECT_CALL(m_sessionServerAppMock, cancelStartupTimer()); - EXPECT_CALL(m_sessionServerAppMock, getInitialState()) + EXPECT_CALL(*m_sessionServerAppMock, isNamedSocketInitialized()).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketFd()).WillOnce(Return(kSessionServerSocketFd)); + EXPECT_CALL(*m_sessionServerAppMock, cancelStartupTimer()); + EXPECT_CALL(*m_sessionServerAppMock, getInitialState()) .WillOnce(Return(firebolt::rialto::common::SessionServerState::INACTIVE)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketName()).WillRepeatedly(Return(kSessionServerSocketName)); - EXPECT_CALL(m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); - EXPECT_CALL(m_sessionServerAppMock, getAppName()).WillOnce(ReturnRef(kAppName)).RetiresOnSaturation(); - EXPECT_CALL(m_sessionServerAppMock, getMaxPlaybackSessions()).WillOnce(Return(kMaxSessions)); - EXPECT_CALL(m_sessionServerAppMock, getMaxWebAudioPlayers()).WillOnce(Return(kMaxWebAudioPlayers)); - EXPECT_CALL(m_sessionServerAppMock, isPreloaded()).WillRepeatedly(Return(false)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketName()).WillRepeatedly(Return(kSessionServerSocketName)); + EXPECT_CALL(*m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); + EXPECT_CALL(*m_sessionServerAppMock, getAppName()).WillOnce(ReturnRef(kAppName)).RetiresOnSaturation(); + EXPECT_CALL(*m_sessionServerAppMock, getMaxPlaybackSessions()).WillOnce(Return(kMaxSessions)); + EXPECT_CALL(*m_sessionServerAppMock, getMaxWebAudioPlayers()).WillOnce(Return(kMaxWebAudioPlayers)); + EXPECT_CALL(*m_sessionServerAppMock, isPreloaded()).WillRepeatedly(Return(false)); EXPECT_CALL(m_controllerMock, performSetConfiguration(kServerId, firebolt::rialto::common::SessionServerState::INACTIVE, kSessionServerSocketFd, kClientDisplayName, @@ -337,19 +333,19 @@ void SessionServerAppManagerTests::sessionServerWillFailToSetConfigurationWithFd void SessionServerAppManagerTests::sessionServerWillFailToSetConfiguration() { - EXPECT_CALL(m_sessionServerAppMock, isNamedSocketInitialized()).WillOnce(Return(false)); - EXPECT_CALL(m_sessionServerAppMock, cancelStartupTimer()); - EXPECT_CALL(m_sessionServerAppMock, getInitialState()) + EXPECT_CALL(*m_sessionServerAppMock, isNamedSocketInitialized()).WillOnce(Return(false)); + EXPECT_CALL(*m_sessionServerAppMock, cancelStartupTimer()); + EXPECT_CALL(*m_sessionServerAppMock, getInitialState()) .WillOnce(Return(firebolt::rialto::common::SessionServerState::INACTIVE)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketName()).WillRepeatedly(Return(kSessionServerSocketName)); - EXPECT_CALL(m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); - EXPECT_CALL(m_sessionServerAppMock, getAppName()).WillOnce(ReturnRef(kAppName)).RetiresOnSaturation(); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketPermissions()).WillOnce(Return(kSocketPermissions)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketOwner()).WillOnce(Return(kSocketOwner)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketGroup()).WillOnce(Return(kSocketGroup)); - EXPECT_CALL(m_sessionServerAppMock, getMaxPlaybackSessions()).WillOnce(Return(kMaxSessions)); - EXPECT_CALL(m_sessionServerAppMock, getMaxWebAudioPlayers()).WillOnce(Return(kMaxWebAudioPlayers)); - EXPECT_CALL(m_sessionServerAppMock, isPreloaded()).WillRepeatedly(Return(false)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketName()).WillRepeatedly(Return(kSessionServerSocketName)); + EXPECT_CALL(*m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); + EXPECT_CALL(*m_sessionServerAppMock, getAppName()).WillOnce(ReturnRef(kAppName)).RetiresOnSaturation(); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketPermissions()).WillOnce(Return(kSocketPermissions)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketOwner()).WillOnce(Return(kSocketOwner)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketGroup()).WillOnce(Return(kSocketGroup)); + EXPECT_CALL(*m_sessionServerAppMock, getMaxPlaybackSessions()).WillOnce(Return(kMaxSessions)); + EXPECT_CALL(*m_sessionServerAppMock, getMaxWebAudioPlayers()).WillOnce(Return(kMaxWebAudioPlayers)); + EXPECT_CALL(*m_sessionServerAppMock, isPreloaded()).WillRepeatedly(Return(false)); EXPECT_CALL(m_controllerMock, performSetConfiguration(kServerId, firebolt::rialto::common::SessionServerState::INACTIVE, kSessionServerSocketName, kClientDisplayName, @@ -361,17 +357,17 @@ void SessionServerAppManagerTests::sessionServerWillFailToSetConfiguration() void SessionServerAppManagerTests::preloadedSessionServerWillFailToSetConfiguration() { - EXPECT_CALL(m_sessionServerAppMock, isNamedSocketInitialized()).WillOnce(Return(false)); - EXPECT_CALL(m_sessionServerAppMock, getInitialState()) + EXPECT_CALL(*m_sessionServerAppMock, isNamedSocketInitialized()).WillOnce(Return(false)); + EXPECT_CALL(*m_sessionServerAppMock, getInitialState()) .WillOnce(Return(firebolt::rialto::common::SessionServerState::INACTIVE)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketName()).WillRepeatedly(Return(kSessionServerSocketName)); - EXPECT_CALL(m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); - EXPECT_CALL(m_sessionServerAppMock, getAppName()).WillOnce(ReturnRef(kAppName)).RetiresOnSaturation(); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketPermissions()).WillOnce(Return(kSocketPermissions)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketOwner()).WillOnce(Return(kSocketOwner)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketGroup()).WillOnce(Return(kSocketGroup)); - EXPECT_CALL(m_sessionServerAppMock, getMaxPlaybackSessions()).WillOnce(Return(kMaxSessions)); - EXPECT_CALL(m_sessionServerAppMock, getMaxWebAudioPlayers()).WillOnce(Return(kMaxWebAudioPlayers)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketName()).WillRepeatedly(Return(kSessionServerSocketName)); + EXPECT_CALL(*m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); + EXPECT_CALL(*m_sessionServerAppMock, getAppName()).WillOnce(ReturnRef(kAppName)).RetiresOnSaturation(); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketPermissions()).WillOnce(Return(kSocketPermissions)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketOwner()).WillOnce(Return(kSocketOwner)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketGroup()).WillOnce(Return(kSocketGroup)); + EXPECT_CALL(*m_sessionServerAppMock, getMaxPlaybackSessions()).WillOnce(Return(kMaxSessions)); + EXPECT_CALL(*m_sessionServerAppMock, getMaxWebAudioPlayers()).WillOnce(Return(kMaxWebAudioPlayers)); EXPECT_CALL(m_controllerMock, performSetConfiguration(kServerId, firebolt::rialto::common::SessionServerState::INACTIVE, kSessionServerSocketName, kClientDisplayName, @@ -392,7 +388,7 @@ void SessionServerAppManagerTests::sessionServerWillFailToSetLogLevels() void SessionServerAppManagerTests::clientWillBeRemoved() { - EXPECT_CALL(m_healthcheckServiceMock, onServerRemoved(kServerId)); + EXPECT_CALL(m_healthcheckServiceMock, onServerRemoved(kServerId)).RetiresOnSaturation(); EXPECT_CALL(m_controllerMock, removeClient(kServerId)); } @@ -404,12 +400,12 @@ void SessionServerAppManagerTests::sessionServerWillIndicateStateChange( void SessionServerAppManagerTests::sessionServerWillKillRunningApplication() { - EXPECT_CALL(m_sessionServerAppMock, kill()); + EXPECT_CALL(*m_sessionServerAppMock, kill()); } void SessionServerAppManagerTests::sessionServerWontBePreloaded() { - EXPECT_CALL(m_sessionServerAppMock, isPreloaded()).WillOnce(Return(false)); + EXPECT_CALL(*m_sessionServerAppMock, isPreloaded()).WillOnce(Return(false)); } void SessionServerAppManagerTests::healthcheckServiceWillHandleAck(bool success) @@ -431,27 +427,26 @@ void SessionServerAppManagerTests::pingSendToRunningAppsWillFail() void SessionServerAppManagerTests::newSessionServerWillBeLaunched() { - m_secondSessionServerApp = std::make_unique>(); - auto &secondSessionServerAppMock{ - dynamic_cast &>(*m_secondSessionServerApp)}; + m_secondSessionServerAppMock = std::make_shared>(); EXPECT_CALL(m_namedSocketFactoryMock, createNamedSocket()).WillOnce(Return(ByMove(std::move(m_namedSocket)))); - EXPECT_CALL(m_sessionServerAppMock, isPreloaded()).WillOnce(Return(true)); - EXPECT_CALL(m_sessionServerAppFactoryMock, create(_, _)).WillOnce(Return(ByMove(std::move(m_secondSessionServerApp)))); - EXPECT_CALL(secondSessionServerAppMock, launch()).WillOnce(Return(false)); + EXPECT_CALL(*m_sessionServerAppMock, isPreloaded()).WillOnce(Return(true)); + EXPECT_CALL(m_sessionServerAppFactoryMock, create(_, _)).WillOnce(Return(m_secondSessionServerAppMock)); + EXPECT_CALL(*m_secondSessionServerAppMock, launch()).WillOnce(Return(false)); } void SessionServerAppManagerTests::sessionServerWillReturnAppSocketName(const std::string &socketName) { - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketName()).WillOnce(Return(socketName)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketName()).WillOnce(Return(socketName)); } void SessionServerAppManagerTests::sessionServerWillBeRestarted(const firebolt::rialto::common::SessionServerState &state) { m_namedSocket = std::make_unique>(); - EXPECT_CALL(m_sessionServerAppMock, getExpectedState()).WillOnce(Return(state)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketName()).WillOnce(Return(kSessionServerSocketName)); - EXPECT_CALL(m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); - EXPECT_CALL(m_sessionServerAppMock, releaseNamedSocketRef()).WillOnce(ReturnRef(m_namedSocket)); + EXPECT_CALL(m_healthcheckServiceMock, onServerRemoved(kServerId)).RetiresOnSaturation(); + EXPECT_CALL(*m_sessionServerAppMock, getExpectedState()).WillOnce(Return(state)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketName()).WillOnce(Return(kSessionServerSocketName)); + EXPECT_CALL(*m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); + EXPECT_CALL(*m_sessionServerAppMock, releaseNamedSocketRef()).WillOnce(ReturnRef(m_namedSocket)); sessionServerWillKillRunningApplication(); sessionServerWillIndicateStateChange(firebolt::rialto::common::SessionServerState::NOT_RUNNING); clientWillBeRemoved(); @@ -468,17 +463,19 @@ void SessionServerAppManagerTests::sessionServerWillBeRestarted(const firebolt:: void SessionServerAppManagerTests::sessionServerWillRestartWillBeSkipped() { - EXPECT_CALL(m_sessionServerAppMock, getExpectedState()) + EXPECT_CALL(*m_sessionServerAppMock, getExpectedState()) .WillOnce(Return(firebolt::rialto::common::SessionServerState::UNINITIALIZED)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketName()).WillOnce(Return(kSessionServerSocketName)); - EXPECT_CALL(m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); - EXPECT_CALL(m_sessionServerAppMock, releaseNamedSocketRef()).WillOnce(ReturnRef(m_namedSocket)); + EXPECT_CALL(m_healthcheckServiceMock, onServerRemoved(kServerId)).RetiresOnSaturation(); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketName()).WillOnce(Return(kSessionServerSocketName)); + EXPECT_CALL(*m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); + EXPECT_CALL(*m_sessionServerAppMock, releaseNamedSocketRef()).WillOnce(ReturnRef(m_namedSocket)); } void SessionServerAppManagerTests::sessionServerWillHandleServerStartupTimeout() { sessionServerWillIndicateStateChange(firebolt::rialto::common::SessionServerState::ERROR); - EXPECT_CALL(m_sessionServerAppMock, isPreloaded()).WillRepeatedly(Return(false)); + EXPECT_CALL(*m_sessionServerAppMock, isPreloaded()).WillRepeatedly(Return(false)); + EXPECT_CALL(m_healthcheckServiceMock, onServerRemoved(kServerId)).RetiresOnSaturation(); sessionServerWillKillRunningApplication(); sessionServerWillIndicateStateChange(firebolt::rialto::common::SessionServerState::NOT_RUNNING); clientWillBeRemoved(); diff --git a/tests/unittests/serverManager/unittests/common/SessionServerAppManagerTestsFixture.h b/tests/unittests/serverManager/unittests/common/SessionServerAppManagerTestsFixture.h index 1bac26634..5dfa7f42a 100644 --- a/tests/unittests/serverManager/unittests/common/SessionServerAppManagerTestsFixture.h +++ b/tests/unittests/serverManager/unittests/common/SessionServerAppManagerTestsFixture.h @@ -92,13 +92,12 @@ class SessionServerAppManagerTests : public testing::Test private: std::unique_ptr m_controller; std::shared_ptr> m_stateObserver; - std::unique_ptr m_sessionServerApp; - std::unique_ptr m_secondSessionServerApp; + std::shared_ptr> m_sessionServerAppMock; + std::shared_ptr> m_secondSessionServerAppMock; std::unique_ptr m_sessionServerAppFactory; std::unique_ptr m_healthcheckServiceFactory; std::unique_ptr m_healthcheckService; StrictMock &m_controllerMock; - StrictMock &m_sessionServerAppMock; StrictMock &m_sessionServerAppFactoryMock; StrictMock &m_healthcheckServiceFactoryMock; StrictMock &m_healthcheckServiceMock; diff --git a/tests/unittests/serverManager/unittests/common/SessionServerAppTestsFixture.cpp b/tests/unittests/serverManager/unittests/common/SessionServerAppTestsFixture.cpp index 0d5a1c653..fd275bbbf 100644 --- a/tests/unittests/serverManager/unittests/common/SessionServerAppTestsFixture.cpp +++ b/tests/unittests/serverManager/unittests/common/SessionServerAppTestsFixture.cpp @@ -56,7 +56,7 @@ using testing::StrictMock; void SessionServerAppTests::createPreloadedAppSut() { - m_sut = std::make_unique(std::move(m_linuxWrapper), + m_sut = std::make_shared(std::move(m_linuxWrapper), m_timerFactoryMock, m_sessionServerAppManagerMock, kEnvironmentVariables, kSessionServerPath, @@ -79,7 +79,7 @@ void SessionServerAppTests::createPreloadedAppSut() void SessionServerAppTests::createAppSut(const firebolt::rialto::common::AppConfig &appConfig) { EXPECT_CALL(m_namedSocketMock, bind(_)).WillOnce(Return(true)); - m_sut = std::make_unique(kAppName, kInitialState, appConfig, + m_sut = std::make_shared(kAppName, kInitialState, appConfig, std::move(m_linuxWrapper), m_timerFactoryMock, m_sessionServerAppManagerMock, @@ -104,7 +104,7 @@ void SessionServerAppTests::createAppSut(const firebolt::rialto::common::AppConf void SessionServerAppTests::createAppSutWithDisabledTimer(const firebolt::rialto::common::AppConfig &appConfig) { EXPECT_CALL(m_namedSocketMock, bind(_)).WillOnce(Return(true)); - m_sut = std::make_unique(kAppName, kInitialState, appConfig, + m_sut = std::make_shared(kAppName, kInitialState, appConfig, std::move(m_linuxWrapper), m_timerFactoryMock, m_sessionServerAppManagerMock, @@ -177,7 +177,7 @@ void SessionServerAppTests::willKillAppOnDestruction() const EXPECT_CALL(*killTimer, cancel()); EXPECT_CALL(*m_timerFactoryMock, createTimer(kKillTimeout, _, firebolt::rialto::common::TimerType::ONE_SHOT)) .WillOnce(DoAll(InvokeArgument<1>(), Return(ByMove(std::move(killTimer))))); - EXPECT_CALL(m_linuxWrapperMock, waitpid(-1, nullptr, 0)) + EXPECT_CALL(m_linuxWrapperMock, waitpid(kPid, nullptr, 0)) .WillOnce(Return(-1)); // -1 here as pid, because we invoked timer with kill earlier. EXPECT_CALL(m_linuxWrapperMock, close(kSocketPair[0])).WillOnce(Return(0)); } diff --git a/tests/unittests/serverManager/unittests/common/SessionServerAppTestsFixture.h b/tests/unittests/serverManager/unittests/common/SessionServerAppTestsFixture.h index 00a5ae59b..30d15a0ea 100644 --- a/tests/unittests/serverManager/unittests/common/SessionServerAppTestsFixture.h +++ b/tests/unittests/serverManager/unittests/common/SessionServerAppTestsFixture.h @@ -70,7 +70,7 @@ class SessionServerAppTests : public testing::Test testing::StrictMock &m_namedSocketMock{*m_namedSocket}; protected: - std::unique_ptr m_sut; + std::shared_ptr m_sut; }; #endif // SESSION_SERVER_APP_TESTS_FIXTURE_H_ diff --git a/wrappers/CMakeLists.txt b/wrappers/CMakeLists.txt index 3be841fd1..d70a28037 100644 --- a/wrappers/CMakeLists.txt +++ b/wrappers/CMakeLists.txt @@ -110,6 +110,8 @@ target_include_directories( PRIVATE include + ../common/interface/ + ../logging/include/ ${GStreamerApp_INCLUDE_DIRS} $ ${WRAPPER_INCLUDES} diff --git a/wrappers/include/GstWrapper.h b/wrappers/include/GstWrapper.h index 055484c43..f09c67e65 100644 --- a/wrappers/include/GstWrapper.h +++ b/wrappers/include/GstWrapper.h @@ -188,6 +188,11 @@ class GstWrapper : public IGstWrapper return gst_element_query_position(element, format, cur); } + gboolean gstElementQueryDuration(GstElement *element, GstFormat format, gint64 *duration) override + { + return gst_element_query_duration(element, format, duration); + } + GstPad *gstGhostPadNew(const gchar *name, GstPad *target) override { return gst_ghost_pad_new(name, target); } void gstPadSetQueryFunction(GstPad *pad, GstPadQueryFunction query) override @@ -595,6 +600,30 @@ class GstWrapper : public IGstWrapper { gst_base_parse_set_pts_interpolation(parse, ptsInterpolate); } + + GstStateChangeReturn gstElementGetState(GstElement *element, GstState *state, GstState *pending, + GstClockTime timeout) override + { + return gst_element_get_state(element, state, pending, timeout); + } + + GstPad *gstPadGetPeer(GstPad *pad) override { return gst_pad_get_peer(pad); } + + gboolean gstPadUnlink(GstPad *srcpad, GstPad *sinkpad) override { return gst_pad_unlink(srcpad, sinkpad); } + + GstPadLinkReturn gstPadLink(GstPad *srcpad, GstPad *sinkpad) override { return gst_pad_link(srcpad, sinkpad); } + + gboolean gstBinRemove(GstBin *bin, GstElement *element) override { return gst_bin_remove(bin, element); } + + GstObject *gstPadGetParent(GstPad *pad) override { return gst_pad_get_parent(pad); } + + gulong gstPadAddProbe(GstPad *pad, GstPadProbeType mask, GstPadProbeCallback callback, gpointer userData, + GDestroyNotify destroyData) override + { + return gst_pad_add_probe(pad, mask, callback, userData, destroyData); + } + + void gstPadRemoveProbe(GstPad *pad, gulong id) override { gst_pad_remove_probe(pad, id); } }; }; // namespace firebolt::rialto::wrappers diff --git a/wrappers/include/OcdmSession.h b/wrappers/include/OcdmSession.h index 51841f701..c76716106 100644 --- a/wrappers/include/OcdmSession.h +++ b/wrappers/include/OcdmSession.h @@ -94,6 +94,7 @@ class OcdmSession : public IOcdmSession private: using OcdmGstSessionDecryptExFn = OpenCDMError (*)(struct OpenCDMSession *, GstBuffer *, GstBuffer *, const uint32_t, GstBuffer *, GstBuffer *, uint32_t, GstCaps *); + using OcdmGstSessionDecryptBufferOnceFn = OpenCDMError (*)(struct OpenCDMSession *, GstBuffer *, GstCaps *); /** * @brief The System handle. */ @@ -115,6 +116,7 @@ class OcdmSession : public IOcdmSession struct OpenCDMSession *m_session; static OcdmGstSessionDecryptExFn m_ocdmGstSessionDecryptEx; + static OcdmGstSessionDecryptBufferOnceFn m_ocdmGstSessionDecryptBufferOnce; /** * @brief Requests the processing of the challenge data. diff --git a/wrappers/interface/IGstWrapper.h b/wrappers/interface/IGstWrapper.h index 0eca162f7..05c9aaaf3 100644 --- a/wrappers/interface/IGstWrapper.h +++ b/wrappers/interface/IGstWrapper.h @@ -412,6 +412,17 @@ class IGstWrapper */ virtual gboolean gstElementQueryPosition(GstElement *element, GstFormat format, gint64 *cur) = 0; + /** + * @brief Queries an element (usually top-level pipeline or playbin element) for the total stream duration in nanoseconds. + * + * @param[in] element : a GstElement to invoke the duration query on. + * @param[in] format : the GstFormat requested + * @param[out] duration : A location in which to store the total duration, or NULL. + * + * @retval TRUE on success, FALSE otherwise. + */ + virtual gboolean gstElementQueryDuration(GstElement *element, GstFormat format, gint64 *duration) = 0; + /** * @brief Create a new ghost pad. * @@ -1402,6 +1413,91 @@ class IGstWrapper * @param ptsInterpolate : TRUE if parser should interpolate PTS timestamps */ virtual void gstBaseParseSetPtsInterpolation(GstBaseParse *parse, gboolean ptsInterpolate) const = 0; + + /** + * @brief Gets the state of the element (blocking version with timeout). + * + * @param[in] element : A GstElement to get state of. + * @param[out] state : A pointer to GstState to hold the state, or NULL. + * @param[out] pending : A pointer to GstState to hold the pending state, or NULL. + * @param[in] timeout : A GstClockTime to specify the timeout. + * + * @retval GST_STATE_CHANGE_SUCCESS if the element has no more pending state + * and the last state change succeeded, GST_STATE_CHANGE_ASYNC if the + * element is still performing a state change, or other values on failure. + */ + virtual GstStateChangeReturn gstElementGetState(GstElement *element, GstState *state, GstState *pending, + GstClockTime timeout) = 0; + + /** + * @brief Gets the peer pad of the given pad. + * + * @param[in] pad : A GstPad to get the peer of. + * + * @retval The peer GstPad. Unref after usage. NULL if pad has no peer. + */ + virtual GstPad *gstPadGetPeer(GstPad *pad) = 0; + + /** + * @brief Unlinks the source pad from the sink pad. + * + * @param[in] srcpad : The source GstPad to unlink. + * @param[in] sinkpad : The sink GstPad to unlink. + * + * @retval TRUE if the pads were unlinked, FALSE otherwise. + */ + virtual gboolean gstPadUnlink(GstPad *srcpad, GstPad *sinkpad) = 0; + + /** + * @brief Links the source pad to the sink pad. + * + * @param[in] srcpad : The source GstPad to link. + * @param[in] sinkpad : The sink GstPad to link. + * + * @retval A result code indicating if the connection worked or what went wrong. + */ + virtual GstPadLinkReturn gstPadLink(GstPad *srcpad, GstPad *sinkpad) = 0; + + /** + * @brief Removes an element from the bin. + * + * @param[in] bin : The bin to remove the element from. + * @param[in] element : The element to remove. + * + * @retval TRUE if the element could be removed, FALSE otherwise. + */ + virtual gboolean gstBinRemove(GstBin *bin, GstElement *element) = 0; + + /** + * @brief Gets the parent of a pad. + * + * @param[in] pad : The pad to get the parent of. + * + * @retval The parent GstObject. Unref after usage. NULL if pad has no parent. + */ + virtual GstObject *gstPadGetParent(GstPad *pad) = 0; + + /** + * @brief Adds probe to the pad. + * + * @param[in] pad : The GstPad to add the probe to. + * @param[in] mask : The probe mask. + * @param[in] callback : GstPadProbeCallback that will be called with notifications of the pad state. + * @param[in] userData : User data passed to the callback. + * @param[in] destroyData : GDestroyNotify for user_data. + * + * @retval An id or 0 if no probe is pending. The id can be used to remove the probe with gst_pad_remove_probe. + */ + virtual gulong gstPadAddProbe(GstPad *pad, GstPadProbeType mask, GstPadProbeCallback callback, gpointer userData, + GDestroyNotify destroyData) = 0; + + /** + * @brief Removes a probe from the pad. + * + * @param[in] pad : The GstPad to remove the probe from. + * @param[in] id : The probe id returned by gstPadAddProbe. + */ + virtual void gstPadRemoveProbe(GstPad *pad, gulong id) = 0; }; }; // namespace firebolt::rialto::wrappers diff --git a/wrappers/source/OcdmSession.cpp b/wrappers/source/OcdmSession.cpp index a236826a9..821cd3043 100644 --- a/wrappers/source/OcdmSession.cpp +++ b/wrappers/source/OcdmSession.cpp @@ -23,6 +23,8 @@ #include "opencdm/open_cdm_ext.h" #include #include +#include +#include "RialtoCommonLogging.h" namespace { @@ -109,6 +111,7 @@ const firebolt::rialto::KeyStatus convertKeyStatus(const KeyStatus &ocdmKeyStatu namespace firebolt::rialto::wrappers { OcdmSession::OcdmGstSessionDecryptExFn OcdmSession::m_ocdmGstSessionDecryptEx{nullptr}; +OcdmSession::OcdmGstSessionDecryptBufferOnceFn OcdmSession::m_ocdmGstSessionDecryptBufferOnce{nullptr}; OcdmSession::OcdmSession(struct OpenCDMSystem *systemHandle, IOcdmSessionClient *client) : m_systemHandle(systemHandle), m_ocdmSessionClient(client), m_session(nullptr) @@ -121,6 +124,11 @@ OcdmSession::OcdmSession(struct OpenCDMSystem *systemHandle, IOcdmSessionClient { m_ocdmGstSessionDecryptEx = (OcdmGstSessionDecryptExFn)dlsym(RTLD_DEFAULT, "opencdm_gstreamer_session_decrypt_ex"); + m_ocdmGstSessionDecryptBufferOnce = + (OcdmGstSessionDecryptBufferOnceFn)dlsym(RTLD_DEFAULT, "opencdm_gstreamer_session_decrypt_buffer_once"); + if(m_ocdmGstSessionDecryptBufferOnce != NULL){ + RIALTO_COMMON_LOG_ERROR("DEBUG PURPOSE : m_ocdmGstSessionDecryptBufferOnce exists\n"); + } }); } @@ -190,11 +198,68 @@ MediaKeyErrorStatus OcdmSession::update(const uint8_t response[], uint32_t respo MediaKeyErrorStatus OcdmSession::decryptBuffer(GstBuffer *encrypted, GstCaps *caps) { + RIALTO_COMMON_LOG_ERROR("DEBUG PURPOSE : OcdmSession::decryptBuffer()\n"); if (!m_session) { return MediaKeyErrorStatus::FAIL; } + if (m_ocdmGstSessionDecryptBufferOnce) + { + // Extract key ID from the buffer's protection metadata + std::vector keyId; + GstProtectionMeta *pm = reinterpret_cast(gst_buffer_get_protection_meta(encrypted)); + if (pm) + { + const GValue *kidValue = gst_structure_get_value(pm->info, "kid"); + if (kidValue) + { + GstBuffer *kidBuf = gst_value_get_buffer(kidValue); + if (kidBuf) + { + GstMapInfo kidMap; + if (gst_buffer_map(kidBuf, &kidMap, GST_MAP_READ)) + { + keyId.assign(kidMap.data, kidMap.data + kidMap.size); + gst_buffer_unmap(kidBuf, &kidMap); + } + } + } + } + + // Pre-decrypt key status check: return OUTPUT_RESTRICTED immediately (no sleep) so + // the caller (MediaKeysServerInternal::decrypt) can retry from the GStreamer thread. + if (!keyId.empty()) + { + const ::KeyStatus preStatus = + opencdm_session_status(m_session, keyId.data(), static_cast(keyId.size())); + if (preStatus == OutputRestricted || preStatus == OutputRestrictedHDCP22) + { + + RIALTO_COMMON_LOG_ERROR("DEBUG PURPOSE : OcdmSession::decryptBuffer() : returning MediaKeyErrorStatus::OUTPUT_RESTRICTED(Pre decrypt)\n"); + return MediaKeyErrorStatus::OUTPUT_RESTRICTED; + } + } + + OpenCDMError result = m_ocdmGstSessionDecryptBufferOnce(m_session, encrypted, caps); + + // Post-decrypt status check: a failed decrypt during HDCP reauth may not carry a + // specific error code, so confirm via key status before signalling the caller to retry. + if (result != ERROR_NONE && !keyId.empty()) + { + const ::KeyStatus postStatus = + opencdm_session_status(m_session, keyId.data(), static_cast(keyId.size())); + if (postStatus == OutputRestricted || postStatus == OutputRestrictedHDCP22) + { + RIALTO_COMMON_LOG_ERROR("DEBUG PURPOSE : OcdmSession::decryptBuffer() : returning MediaKeyErrorStatus::OUTPUT_RESTRICTED(Post decrypt)\n"); + return MediaKeyErrorStatus::OUTPUT_RESTRICTED; + } + } + + return convertOpenCdmError(result); + } + + // Fallback: adapter without _once handles retries internally. OpenCDMError status = opencdm_gstreamer_session_decrypt_buffer(m_session, encrypted, caps); return convertOpenCdmError(status); }