Skip to content

Commit

Permalink
Merge pull request #64 from phunkyfish/rtmp-read-pause-timer
Browse files Browse the repository at this point in the history
Rtmp read pause timer - Inputstream API 2.2.0
  • Loading branch information
phunkyfish committed Apr 16, 2020
2 parents e957406 + 4d88dfc commit d4b259f
Show file tree
Hide file tree
Showing 8 changed files with 549 additions and 11 deletions.
7 changes: 6 additions & 1 deletion CMakeLists.txt
Expand Up @@ -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}
Expand Down
59 changes: 59 additions & 0 deletions build-install-mac.sh
@@ -0,0 +1,59 @@
#!/bin/bash

set -e

if [ "$#" -ne 1 ] || ! [ -d "$1" ]; then
echo "Usage: $0 <XBMC-SRC-DIR>" >&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"
2 changes: 1 addition & 1 deletion inputstream.rtmp/addon.xml.in
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon
id="inputstream.rtmp"
version="3.0.2"
version="3.0.3"
name="RTMP Input"
provider-name="spiff">
<requires>@ADDON_DEPENDS@</requires>
Expand Down
50 changes: 41 additions & 9 deletions src/RTMPStream.cpp
Expand Up @@ -16,6 +16,9 @@
*
*/

#include "utils/Log.h"
#include "timer/Timer.h"

#include <iostream>
#include <map>
#include <string.h>
Expand Down Expand Up @@ -45,7 +48,8 @@ std::map<std::string, AVal> options =
}

class ATTRIBUTE_HIDDEN CInputStreamRTMP
: public kodi::addon::CInstanceInputStream
: public kodi::addon::CInstanceInputStream,
public rtmpstream::ITimerCallback
{
public:
CInputStreamRTMP(KODI_HANDLE instance);
Expand All @@ -58,24 +62,28 @@ 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)
{
}

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);
Expand Down Expand Up @@ -111,13 +119,18 @@ bool CInputStreamRTMP::OpenStream(int streamid)

void CInputStreamRTMP::Close()
{
m_readPauseDetectTimer.Stop();

if (m_session)
{
std::unique_lock<std::recursive_mutex> 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)
Expand Down Expand Up @@ -145,17 +158,36 @@ void CInputStreamRTMP::EnableStream(int streamid, bool enable)

int CInputStreamRTMP::ReadStream(uint8_t* buf, unsigned int size)
{
std::unique_lock<std::recursive_mutex> lock(m_critSection);
if (m_readPauseDetected)
{
m_readPauseDetected = false;
RTMP_Pause(m_session, false);
rtmpstream::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<char*>(buf), size);
}

void CInputStreamRTMP::PauseStream(double time)
void CInputStreamRTMP::OnTimeout()
{
m_paused = !m_paused;
RTMP_Pause(m_session, m_paused);
std::unique_lock<std::recursive_mutex> lock(m_critSection);
m_readPauseDetected = true;

rtmpstream::Log(ADDON_LOG_DEBUG, "InputStream.rtmp: Read pause detected");

RTMP_Pause(m_session, true);
}

bool CInputStreamRTMP::PosTime(int ms)
{
std::unique_lock<std::recursive_mutex> lock(m_critSection);

return RTMP_SendSeek(m_session, ms);
}

Expand Down
133 changes: 133 additions & 0 deletions 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 <algorithm>

namespace rtmpstream
{

CTimer::CTimer(std::function<void()> 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<float>(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<int64_t>(std::chrono::duration<double>(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 */
57 changes: 57 additions & 0 deletions 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 <functional>

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<void()> 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<void()> 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 */

0 comments on commit d4b259f

Please sign in to comment.