Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion inputstream.adaptive/addon.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
name="adaptive"
extension=""
tags="true"
listitemprops="drm_legacy|license_type|license_key|license_url|license_url_append|license_data|license_flags|manifest_type|server_certificate|manifest_update_parameter|manifest_upd_params|manifest_params|manifest_headers|stream_params|stream_headers|original_audio_language|play_timeshift_buffer|pre_init_data|stream_selection_type|chooser_bandwidth_max|chooser_resolution_max|chooser_resolution_secure_max|live_delay|internal_cookies|config|manifest_config"
listitemprops="drm_legacy|license_type|license_key|license_url|license_url_append|license_data|license_flags|manifest_type|server_certificate|manifest_update_parameter|manifest_upd_params|manifest_params|manifest_headers|stream_params|stream_headers|original_audio_language|play_timeshift_buffer|pre_init_data|stream_selection_type|chooser_bandwidth_max|chooser_resolution_max|chooser_resolution_secure_max|live_delay|live_offset|internal_cookies|config|manifest_config"
library_@PLATFORM@="@LIBRARY_FILENAME@"/>
<extension point="xbmc.addon.metadata">
<platform>@PLATFORM@</platform>
Expand Down
5 changes: 5 additions & 0 deletions src/CompKodiProps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ constexpr std::string_view PROP_STREAM_HEADERS = "inputstream.adaptive.stream_he
constexpr std::string_view PROP_AUDIO_LANG_ORIG = "inputstream.adaptive.original_audio_language";
constexpr std::string_view PROP_PLAY_TIMESHIFT_BUFFER = "inputstream.adaptive.play_timeshift_buffer";
constexpr std::string_view PROP_LIVE_DELAY = "inputstream.adaptive.live_delay";
constexpr std::string_view PROP_LIVE_OFFSET = "inputstream.adaptive.live_offset";
constexpr std::string_view PROP_PRE_INIT_DATA = "inputstream.adaptive.pre_init_data";

constexpr std::string_view PROP_CONFIG = "inputstream.adaptive.config";
Expand Down Expand Up @@ -200,6 +201,10 @@ ADP::KODI_PROPS::CCompKodiProps::CCompKodiProps(const std::map<std::string, std:
{
m_liveDelay = STRING::ToUint64(prop.second); //! @todo: move to PROP_MANIFEST_CONFIG
}
else if (prop.first == PROP_LIVE_OFFSET)
{
m_liveOffset = STRING::ToUint64(prop.second); //! @todo: move to PROP_MANIFEST_CONFIG
}
else if (prop.first == PROP_PRE_INIT_DATA)
{
m_drmPreInitData = prop.second;
Expand Down
3 changes: 3 additions & 0 deletions src/CompKodiProps.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ class ATTR_DLL_LOCAL CCompKodiProps
bool IsPlayTimeshift() const { return m_playTimeshiftBuffer; }
// \brief Get a custom delay from LIVE edge in seconds
uint64_t GetLiveDelay() const { return m_liveDelay; }
// \brief Get a custom offset from LIVE start in seconds
uint64_t GetLiveOffset() const { return m_liveOffset; }

/*
* \brief Get data to "pre-initialize" the DRM, if set is represented as a string
Expand Down Expand Up @@ -162,6 +164,7 @@ class ATTR_DLL_LOCAL CCompKodiProps
std::string m_audioLanguageOrig;
bool m_playTimeshiftBuffer{false};
uint64_t m_liveDelay{0};
uint64_t m_liveOffset{0};
std::string m_drmPreInitData;
ChooserProps m_chooserProps;
Config m_config;
Expand Down
61 changes: 46 additions & 15 deletions src/Session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "utils/log.h"

#include <array>
#include <limits>

#include <kodi/addon-instance/Inputstream.h>

Expand Down Expand Up @@ -1441,15 +1442,18 @@ int CSession::GetChapter() const
}
}
}
return -1;
return 0;
}

int CSession::GetChapterCount() const
{
if (m_adaptiveTree && m_adaptiveTree->m_periods.size() > 1)
return static_cast<int>(m_adaptiveTree->m_periods.size());
if (!m_adaptiveTree)
return 0;

return 0;
int count = static_cast<int>(m_adaptiveTree->m_periods.size());
if (count > 0 && m_adaptiveTree->IsLive())
count += 1;
return count;
}

std::string CSession::GetChapterName(int ch) const
Expand All @@ -1459,6 +1463,8 @@ std::string CSession::GetChapterName(int ch) const
--ch;
if (ch >= 0 && ch < static_cast<int>(m_adaptiveTree->m_periods.size()))
return m_adaptiveTree->m_periods[ch]->GetId().data();
if (ch == static_cast<int>(m_adaptiveTree->m_periods.size()) && m_adaptiveTree->IsLive())
return "Live";
}

return "[Unknown]";
Expand All @@ -1469,6 +1475,14 @@ int64_t CSession::GetChapterPos(int ch) const
int64_t sum{0};
--ch;

if (m_adaptiveTree && ch == static_cast<int>(m_adaptiveTree->m_periods.size()) &&
m_adaptiveTree->IsLive())
return static_cast<int64_t>(m_adaptiveTree->m_totalTime / 1000);

// Chapter 1 of a live stream: return configured start offset
if (ch == 0 && m_adaptiveTree && m_adaptiveTree->IsLive())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This returns liveOffset as the position for any live stream's chapter 1, including multi-period live streams. For period 2+, this is wrong — GetChapterPos(1) when the user is in period 2 should return 0 (start of that period), not the absolute stream-start offset. Consider guarding with a check that the stream has only one period, or only applying the offset when ch == 0 and m_adaptiveTree->m_periods.size() == 1.

return static_cast<int64_t>(CSrvBroker::GetKodiProps().GetLiveOffset());

for (; ch; --ch)
{
sum += (m_adaptiveTree->m_periods[ch - 1]->GetDuration() * STREAM_TIME_BASE) /
Expand Down Expand Up @@ -1516,23 +1530,40 @@ bool CSession::SeekChapter(int ch)
return true;

--ch;
if (ch >= 0 && ch < static_cast<int>(m_adaptiveTree->m_periods.size()) &&
m_adaptiveTree->m_periods[ch].get() != m_adaptiveTree->m_currentPeriod)

if (ch == static_cast<int>(m_adaptiveTree->m_periods.size()) && m_adaptiveTree->IsLive())
{
CPeriod* nextPeriod = m_adaptiveTree->m_periods[ch].get();
m_adaptiveTree->m_nextPeriod = nextPeriod;
LOG::LogF(LOGDEBUG, "Switching to new Period (id=%s, start=%llu, seq=%u)",
nextPeriod->GetId().data(), nextPeriod->GetStart(), nextPeriod->GetSequence());
LOG::LogF(LOGDEBUG, "Seeking to live edge (virtual chapter)");
SeekTime(std::numeric_limits<double>::max(), 0, false);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using DBL_MAX as a sentinel for "seek to live edge" is fragile. SeekTime clamps this via the if (m_adaptiveTree->IsLive()) block, but if IsLive() returns false inside SeekTime (e.g. live→VOD transition during a manifest update), seekTime stays at DBL_MAX and static_cast<uint64_t>(seekTime * STREAM_TIME_BASE) is undefined behavior (overflow).

Suggested change
SeekTime(std::numeric_limits<double>::max(), 0, false);
// Seek to the maximum buffered position (live edge)
uint64_t maxTime{0};
for (auto& stream : m_streams)
{
if (stream->m_isEnabled)
{
uint64_t curTime = stream->m_adStream.getMaxTimeMs();
if (curTime > maxTime)
maxTime = curTime;
}
}
SeekTime(static_cast<double>(maxTime) / 1000, 0, false);

return true;
}

for (auto& stream : m_streams)
if (ch >= 0 && ch < static_cast<int>(m_adaptiveTree->m_periods.size()))
{
CPeriod* targetPeriod = m_adaptiveTree->m_periods[ch].get();
if (targetPeriod != m_adaptiveTree->m_currentPeriod)
{
ISampleReader* sr{stream->GetReader()};
if (sr)
m_adaptiveTree->m_nextPeriod = targetPeriod;
LOG::LogF(LOGDEBUG, "Switching to new Period (id=%s, start=%llu, seq=%u)",
targetPeriod->GetId().data(), targetPeriod->GetStart(),
targetPeriod->GetSequence());

for (auto& stream : m_streams)
{
sr->WaitReadSampleAsyncComplete();
sr->Reset(true);
ISampleReader* sr{stream->GetReader()};
if (sr)
{
sr->WaitReadSampleAsyncComplete();
sr->Reset(true);
}
}
}
else
{
double startTime = GetChapterPos(ch + 1);
LOG::LogF(LOGDEBUG, "Seeking to start of current Period (startTime=%.3f)", startTime);
SeekTime(startTime, 0, false);
}
return true;
}
return false;
Expand Down
11 changes: 10 additions & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -495,12 +495,21 @@ bool CInputStreamAdaptive::IsRealTimeStream()
#if INPUTSTREAM_VERSION_LEVEL > 1
int CInputStreamAdaptive::GetChapter()
{
// Provide the current chapter number
// chapter numbering starts from 1, specify 0 for no chapter
return m_session ? m_session->GetChapter() : 0;
}

int CInputStreamAdaptive::GetChapterCount()
{
return m_session ? m_session->GetChapterCount() : 0;
if (m_session)
{
const int count = m_session->GetChapterCount();
if (count > 1)
return count;
}
// Return 0 to prevent Kodi core from handling chapters
return 0;
}

const char* CInputStreamAdaptive::GetChapterName(int ch)
Expand Down
Loading