From 9885bad0316b40fc52ba538e2d782ad48950b661 Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Tue, 14 Apr 2020 17:01:46 +0100 Subject: [PATCH 1/4] add build script for mac if using xbmc source dir --- build-install-mac.sh | 59 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100755 build-install-mac.sh diff --git a/build-install-mac.sh b/build-install-mac.sh new file mode 100755 index 0000000..51b9451 --- /dev/null +++ b/build-install-mac.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +set -e + +if [ "$#" -ne 1 ] || ! [ -d "$1" ]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +if [[ "$OSTYPE" != "darwin"* ]]; then + echo "Error: Script only for use on MacOSX" >&2 + exit 1 +fi + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +if [[ "$1" = /* ]] +then + #absolute path + SCRIPT_DIR="" +else + #relative + SCRIPT_DIR="$SCRIPT_DIR/" +fi + +BINARY_ADDONS_TARGET_DIR="$1/tools/depends/target/binary-addons" +MACOSX_BINARY_ADDONS_TARGET_DIR="" +KODI_ADDONS_DIR="$HOME/Library/Application Support/Kodi/addons" +ADDON_NAME=`basename -s .git \`git config --get remote.origin.url\`` + +if [ ! -d "$BINARY_ADDONS_TARGET_DIR" ]; then + echo "Error: Could not find binary addons directory at: $BINARY_ADDONS_TARGET_DIR" >&2 + exit 1 +fi + +for DIR in "$BINARY_ADDONS_TARGET_DIR/"macosx*; do + if [ -d "${DIR}" ]; then + MACOSX_BINARY_ADDONS_TARGET_DIR="${DIR}" + break + fi +done + +if [ -z "$MACOSX_BINARY_ADDONS_TARGET_DIR" ]; then + echo "Error: Could not find binary addons build directory at: $BINARY_ADDONS_TARGET_DIR/macosx*" >&2 + exit 1 +fi + +if [ ! -d "$KODI_ADDONS_DIR" ]; then + echo "Error: Kodi addons dir does not exist at: $KODI_ADDONS_DIR" >&2 + exit 1 +fi + +cd "$MACOSX_BINARY_ADDONS_TARGET_DIR" +make + +XBMC_BUILD_ADDON_INSTALL_DIR=$(cd "$SCRIPT_DIR$1/addons/$ADDON_NAME" 2> /dev/null && pwd -P) +rm -rf "$KODI_ADDONS_DIR/$ADDON_NAME" +echo "Removed previous addon build from: $KODI_ADDONS_DIR" +cp -rf "$XBMC_BUILD_ADDON_INSTALL_DIR" "$KODI_ADDONS_DIR" +echo "Copied new addon build to: $KODI_ADDONS_DIR" From e377281980942bbceb23f020feb8a628de5f98b2 Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Wed, 15 Apr 2020 17:17:16 +0100 Subject: [PATCH 2/4] Remove pause and add read pause detect timer for Inputstream API 2.2.0 --- CMakeLists.txt | 7 +- src/RTMPStream.cpp | 47 ++++++++-- src/timer/Timer.cpp | 133 +++++++++++++++++++++++++++ src/timer/Timer.h | 57 ++++++++++++ src/timer/TimerHelper.h | 199 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 434 insertions(+), 9 deletions(-) create mode 100644 src/timer/Timer.cpp create mode 100644 src/timer/Timer.h create mode 100644 src/timer/TimerHelper.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ce79e5d..1ec90a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,12 @@ find_package(OpenSSL REQUIRED) find_package(ZLIB REQUIRED) find_package(RTMP REQUIRED) -set(RTMP_SOURCES src/RTMPStream.cpp) +set(RTMP_SOURCES src/RTMPStream.cpp + src/timer/Timer.cpp) + +set(RTMP_HEADERS src/utils/Log.h + src/timer/Timer.h + src/timer/TimerHelper.h) include_directories(${INCLUDES} ${RTMP_INCLUDE_DIRS} diff --git a/src/RTMPStream.cpp b/src/RTMPStream.cpp index a9b6b99..4b29411 100644 --- a/src/RTMPStream.cpp +++ b/src/RTMPStream.cpp @@ -16,6 +16,8 @@ * */ +#include "timer/Timer.h" + #include #include #include @@ -45,7 +47,8 @@ std::map options = } class ATTRIBUTE_HIDDEN CInputStreamRTMP - : public kodi::addon::CInstanceInputStream + : public kodi::addon::CInstanceInputStream, + public rtmpstream::ITimerCallback { public: CInputStreamRTMP(KODI_HANDLE instance); @@ -58,18 +61,22 @@ class ATTRIBUTE_HIDDEN CInputStreamRTMP void EnableStream(int streamid, bool enable) override; bool OpenStream(int streamid) override; int ReadStream(uint8_t* buffer, unsigned int bufferSize) override; - void PauseStream(double time) override; bool PosTime(int ms) override; int GetTotalTime() override { return 20; } int GetTime() override { return 0; } private: + void OnTimeout() override; + RTMP* m_session = nullptr; - bool m_paused = false; + bool m_readPauseDetected = false; + mutable std::recursive_mutex m_critSection; + rtmpstream::CTimer m_readPauseDetectTimer; }; CInputStreamRTMP::CInputStreamRTMP(KODI_HANDLE instance) - : CInstanceInputStream(instance) + : CInstanceInputStream(instance), + m_readPauseDetectTimer(this) { } @@ -111,13 +118,18 @@ bool CInputStreamRTMP::OpenStream(int streamid) void CInputStreamRTMP::Close() { + m_readPauseDetectTimer.Stop(); + if (m_session) { + std::unique_lock lock(m_critSection); + RTMP_Close(m_session); RTMP_Free(m_session); } + m_session = nullptr; - m_paused = false; + m_readPauseDetected = false; } void CInputStreamRTMP::GetCapabilities(INPUTSTREAM_CAPABILITIES &caps) @@ -145,17 +157,36 @@ void CInputStreamRTMP::EnableStream(int streamid, bool enable) int CInputStreamRTMP::ReadStream(uint8_t* buf, unsigned int size) { + std::unique_lock lock(m_critSection); + if (m_readPauseDetected) + { + m_readPauseDetected = false; + RTMP_Pause(m_session, false); + kodi::Log(ADDON_LOG_DEBUG, "InputStream.rtmp: Read resume detected"); + } + + if (m_readPauseDetectTimer.IsRunning()) + m_readPauseDetectTimer.RestartAsync(2 * 1000); + else + m_readPauseDetectTimer.Start(2 * 1000); + return RTMP_Read(m_session, reinterpret_cast(buf), size); } -void CInputStreamRTMP::PauseStream(double time) +void CInputStreamRTMP::OnTimeout() { - m_paused = !m_paused; - RTMP_Pause(m_session, m_paused); + std::unique_lock lock(m_critSection); + m_readPauseDetected = true; + + kodi::Log(ADDON_LOG_DEBUG, "InputStream.rtmp: Read pause detected"); + + RTMP_Pause(m_session, true); } bool CInputStreamRTMP::PosTime(int ms) { + std::unique_lock lock(m_critSection); + return RTMP_SendSeek(m_session, ms); } diff --git a/src/timer/Timer.cpp b/src/timer/Timer.cpp new file mode 100644 index 0000000..f8481f3 --- /dev/null +++ b/src/timer/Timer.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#include "Timer.h" + +#include + +namespace rtmpstream +{ + +CTimer::CTimer(std::function const& callback) + : m_callback(callback), + m_timeout(0), + m_interval(false), + m_endTime(0) +{ } + +CTimer::CTimer(ITimerCallback *callback) + : CTimer(std::bind(&ITimerCallback::OnTimeout, callback)) +{ } + +CTimer::~CTimer() +{ + Stop(); +} + +bool CTimer::Start(uint32_t timeout, bool interval /* = false */) +{ + if (m_callback == nullptr || timeout == 0 || IsRunning()) + return false; + + m_timeout = timeout; + m_interval = interval; + + m_eventTimeout.Reset(); + + m_running = true; + m_thread = std::thread{&CTimer::Process, this}; + + m_thread.detach(); + + return true; +} + +bool CTimer::Stop() +{ + if (!m_running) + return false; + + m_running = false; + m_eventTimeout.Signal(); + + return true; +} + +void CTimer::RestartAsync(uint32_t timeout) +{ + m_timeout = timeout; + m_endTime = SystemClockMillis() + timeout; + m_eventTimeout.Signal(); +} + +bool CTimer::Restart() +{ + if (!IsRunning()) + return false; + + Stop(); + return Start(m_timeout, m_interval); +} + +float CTimer::GetElapsedSeconds() +{ + return GetElapsedMilliseconds() / 1000.0f; +} + +float CTimer::GetElapsedMilliseconds() +{ + if (!IsRunning()) + return 0.0f; + + return static_cast(m_timer.TimeLeft()); +} + +void CTimer::Process() +{ + while (m_running) + { + uint32_t currentTime = SystemClockMillis(); + m_endTime = currentTime + m_timeout; + m_timer.Init(m_timeout); + + // wait the necessary time + if (!m_eventTimeout.Wait(m_timeout)) + { + currentTime = SystemClockMillis(); + if (m_running && m_endTime <= currentTime) + { + // execute OnTimeout() callback + m_callback(); + + // continue if this is an interval timer, or if it was restarted during callback + if (!m_interval && m_endTime <= currentTime) + break; + } + } + } + + m_running = false; +} + +unsigned int CTimer::SystemClockMillis() +{ + uint64_t now_time; + static uint64_t start_time = 0; + static bool start_time_set = false; + + now_time = static_cast(std::chrono::duration(std::chrono::high_resolution_clock::now().time_since_epoch()).count() * 1000.0); + + if (!start_time_set) + { + start_time = now_time; + start_time_set = true; + } + return (unsigned int)(now_time - start_time); +} + +} /* namespace rtmpstream */ diff --git a/src/timer/Timer.h b/src/timer/Timer.h new file mode 100644 index 0000000..65c2fa8 --- /dev/null +++ b/src/timer/Timer.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#pragma once + +#include "TimerHelper.h" + +#include + +namespace rtmpstream +{ + +class ATTRIBUTE_HIDDEN ITimerCallback +{ +public: + virtual ~ITimerCallback() = default; + + virtual void OnTimeout() = 0; +}; + +class ATTRIBUTE_HIDDEN CTimer +{ +public: + explicit CTimer(ITimerCallback *callback); + explicit CTimer(std::function const& callback); + ~CTimer(); + + bool Start(uint32_t timeout, bool interval = false); + bool Stop(); + bool Restart(); + void RestartAsync(uint32_t timeout); + + bool IsRunning() { return m_running; } + + float GetElapsedSeconds(); + float GetElapsedMilliseconds(); + +private: + void Process(); + unsigned int SystemClockMillis(); + + std::function m_callback; + uint32_t m_timeout; + bool m_interval; + uint32_t m_endTime; + CEvent m_eventTimeout; + CTimeout m_timer; + std::thread m_thread; + bool m_running = false; +}; + +} /* namespace rtmpstream */ diff --git a/src/timer/TimerHelper.h b/src/timer/TimerHelper.h new file mode 100644 index 0000000..7f57ae2 --- /dev/null +++ b/src/timer/TimerHelper.h @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2020 Team Kodi (https://kodi.tv) + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace rtmpstream +{ + +inline std::string NowToString() +{ + std::chrono::system_clock::time_point p = std::chrono::system_clock::now(); + time_t t = std::chrono::system_clock::to_time_t(p); + return std::ctime(&t); +} + +class ATTRIBUTE_HIDDEN CTimeout +{ +public: + CTimeout(void) : m_iTarget(0) + { + } + + CTimeout(uint32_t iTimeout) + { + Init(iTimeout); + } + + bool IsSet(void) const + { + return m_iTarget > 0; + } + + void Init(uint32_t iTimeout) + { + m_iTarget = static_cast(std::chrono::duration(std::chrono::high_resolution_clock::now().time_since_epoch()).count() * 1000.0) + iTimeout; + } + + uint32_t TimeLeft(void) const + { + uint64_t iNow = static_cast(std::chrono::duration(std::chrono::high_resolution_clock::now().time_since_epoch()).count() * 1000.0); + return (iNow > m_iTarget) ? 0 : (uint32_t)(m_iTarget - iNow); + } + +private: + uint64_t m_iTarget; +}; + +typedef bool (*PredicateCallback) (void *param); + +template +class ATTRIBUTE_HIDDEN CCondition +{ +private: + static bool _PredicateCallbackDefault ( void *param ) + { + _Predicate *p = (_Predicate*)param; + return (*p); + } +public: + inline CCondition(void) {} + inline ~CCondition(void) + { + m_condition.notify_all(); + } + + inline void Broadcast(void) + { + m_condition.notify_all(); + } + + inline void Signal(void) + { + m_condition.notify_one(); + } + + inline bool Wait(std::recursive_mutex& mutex, uint32_t iTimeout) + { + std::unique_lock lck(mutex); + return m_condition.wait_for(lck, std::chrono::milliseconds(iTimeout)) != std::cv_status::timeout; + } + + inline bool Wait(std::recursive_mutex &mutex, PredicateCallback callback, void* param, uint32_t iTimeout) + { + bool bReturn(false); + CTimeout timeout(iTimeout); + + while (!bReturn) + { + if ((bReturn = callback(param)) == true) + break; + uint32_t iMsLeft = timeout.TimeLeft(); + if ((iTimeout != 0) && (iMsLeft == 0)) + break; + std::unique_lock lck(mutex); + m_condition.wait_for(lck, std::chrono::milliseconds(iMsLeft)); + } + + return bReturn; + } + + inline bool Wait(std::recursive_mutex &mutex, _Predicate &predicate, uint32_t iTimeout = 0) + { + return Wait(mutex, _PredicateCallbackDefault, (void*)&predicate, iTimeout); + } + +private: + std::condition_variable_any m_condition; +}; + +class ATTRIBUTE_HIDDEN CEvent +{ +public: + CEvent(bool bAutoReset = true) : + m_bSignaled(false), + m_bBroadcast(false), + m_iWaitingThreads(0), + m_bAutoReset(bAutoReset) {} + virtual ~CEvent(void) {} + + void Broadcast(void) + { + Set(true); + m_condition.Broadcast(); + } + + void Signal(void) + { + Set(false); + m_condition.Signal(); + } + + bool Wait(void) + { + std::unique_lock lck(m_mutex); + ++m_iWaitingThreads; + + bool bReturn = m_condition.Wait(m_mutex, m_bSignaled); + return ResetAndReturn() && bReturn; + } + + bool Wait(uint32_t iTimeout) + { + if (iTimeout == 0) + return Wait(); + + std::unique_lock lck(m_mutex); + ++m_iWaitingThreads; + bool bReturn = m_condition.Wait(m_mutex, m_bSignaled, iTimeout); + return ResetAndReturn() && bReturn; + } + + static void Sleep(uint32_t iTimeout) + { + CEvent event; + event.Wait(iTimeout); + } + + void Reset(void) + { + m_bSignaled = false; + } + +private: + void Set(bool bBroadcast = false) + { + m_bSignaled = true; + m_bBroadcast = bBroadcast; + } + + bool ResetAndReturn(void) + { + std::unique_lock lck(m_mutex); + bool bReturn(m_bSignaled); + if (bReturn && (--m_iWaitingThreads == 0 || !m_bBroadcast) && m_bAutoReset) + m_bSignaled = false; + return bReturn; + } + + volatile bool m_bSignaled; + CCondition m_condition; + std::recursive_mutex m_mutex; + volatile bool m_bBroadcast; + unsigned int m_iWaitingThreads; + bool m_bAutoReset; +}; + +} /* namespace rtmpstream */ From f3472f18d795c898e303222e4cdc663991f13920 Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Wed, 15 Apr 2020 17:16:50 +0100 Subject: [PATCH 3/4] Add custom debug logger --- src/RTMPStream.cpp | 7 +++--- src/utils/Log.h | 53 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 src/utils/Log.h diff --git a/src/RTMPStream.cpp b/src/RTMPStream.cpp index 4b29411..ae03350 100644 --- a/src/RTMPStream.cpp +++ b/src/RTMPStream.cpp @@ -16,6 +16,7 @@ * */ +#include "utils/Log.h" #include "timer/Timer.h" #include @@ -82,7 +83,7 @@ CInputStreamRTMP::CInputStreamRTMP(KODI_HANDLE instance) bool CInputStreamRTMP::Open(INPUTSTREAM& props) { - kodi::Log(ADDON_LOG_DEBUG, "InputStream.rtmp: OpenStream()"); + rtmpstream::Log(ADDON_LOG_DEBUG, "InputStream.rtmp: OpenStream()"); m_session = RTMP_Alloc(); RTMP_Init(m_session); @@ -162,7 +163,7 @@ int CInputStreamRTMP::ReadStream(uint8_t* buf, unsigned int size) { m_readPauseDetected = false; RTMP_Pause(m_session, false); - kodi::Log(ADDON_LOG_DEBUG, "InputStream.rtmp: Read resume detected"); + rtmpstream::Log(ADDON_LOG_DEBUG, "InputStream.rtmp: Read resume detected"); } if (m_readPauseDetectTimer.IsRunning()) @@ -178,7 +179,7 @@ void CInputStreamRTMP::OnTimeout() std::unique_lock lock(m_critSection); m_readPauseDetected = true; - kodi::Log(ADDON_LOG_DEBUG, "InputStream.rtmp: Read pause detected"); + rtmpstream::Log(ADDON_LOG_DEBUG, "InputStream.rtmp: Read pause detected"); RTMP_Pause(m_session, true); } diff --git a/src/utils/Log.h b/src/utils/Log.h new file mode 100644 index 0000000..82e7d81 --- /dev/null +++ b/src/utils/Log.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2020 Team Kodi (https://kodi.tv) + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#pragma once + +#include + +namespace rtmpstream +{ + +inline const char* kodiTranslateLogLevel(const AddonLog logLevel) +{ + switch (logLevel) + { + case ADDON_LOG_DEBUG: + return "LOG_DEBUG: "; + case ADDON_LOG_INFO: + return "LOG_INFO: "; + case ADDON_LOG_NOTICE: + return "LOG_NOTICE: "; + case ADDON_LOG_WARNING: + return "LOG_WARNING: "; + case ADDON_LOG_ERROR: + return "LOG_ERROR: "; + case ADDON_LOG_SEVERE: + return "LOG_SEVERE: "; + case ADDON_LOG_FATAL: + return "LOG_FATAL: "; + default: + break; + } + return "LOG_UNKNOWN: "; +} + +inline void Log(const AddonLog logLevel, const char* format, ...) +{ + char buffer[16384]; + va_list args; + va_start(args, format); + vsprintf(buffer, format, args); + va_end(args); + + kodi::Log(logLevel, buffer); +#ifdef DEBUG + fprintf(stderr, "%s%s\n", kodiTranslateLogLevel(logLevel), buffer); +#endif +} + +} /* namespace rtmpstream */ From 4d88dfcb0ebcd6808e68a5b752fd2daaf1eb6951 Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Tue, 14 Apr 2020 17:43:28 +0100 Subject: [PATCH 4/4] changelog and version 3.0.3 --- inputstream.rtmp/addon.xml.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inputstream.rtmp/addon.xml.in b/inputstream.rtmp/addon.xml.in index f1c8ab6..3504682 100644 --- a/inputstream.rtmp/addon.xml.in +++ b/inputstream.rtmp/addon.xml.in @@ -1,7 +1,7 @@ @ADDON_DEPENDS@