From 048d2c716e6bbd94ba163bbb81b5890fbc50bba8 Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Fri, 4 Oct 2024 15:53:15 +0100 Subject: [PATCH 1/2] Correctly set inputstream properties for media entries --- src/IptvSimple.cpp | 5 +- src/iptvsimple/Media.cpp | 7 + src/iptvsimple/Media.h | 1 + src/iptvsimple/StreamManager.cpp | 4 +- src/iptvsimple/data/MediaEntry.cpp | 9 ++ src/iptvsimple/utilities/StreamUtils.cpp | 169 +++++++++++++++++------ src/iptvsimple/utilities/StreamUtils.h | 14 +- 7 files changed, 158 insertions(+), 51 deletions(-) diff --git a/src/IptvSimple.cpp b/src/IptvSimple.cpp index a0d77418d..2c9fe8ba4 100644 --- a/src/IptvSimple.cpp +++ b/src/IptvSimple.cpp @@ -412,11 +412,12 @@ PVR_ERROR IptvSimple::GetRecordings(bool deleted, kodi::addon::PVRRecordingsResu PVR_ERROR IptvSimple::GetRecordingStreamProperties(const kodi::addon::PVRRecording& recording, std::vector& properties) { + auto mediaEntry = m_media.GetMediaEntry(recording); std::string url = m_media.GetMediaEntryURL(recording); - if (!url.empty()) + if (!mediaEntry.GetMediaEntryId().empty() && !url.empty()) { - properties.emplace_back(PVR_STREAM_PROPERTY_STREAMURL, url); + StreamUtils::SetAllStreamProperties(properties, mediaEntry, url, m_settings); return PVR_ERROR_NO_ERROR; } diff --git a/src/iptvsimple/Media.cpp b/src/iptvsimple/Media.cpp index 430706962..97968b64a 100644 --- a/src/iptvsimple/Media.cpp +++ b/src/iptvsimple/Media.cpp @@ -136,6 +136,13 @@ bool Media::IsInVirtualMediaEntryFolder(const MediaEntry& mediaEntryToCheck) con return false; } +const MediaEntry Media::GetMediaEntry(const kodi::addon::PVRRecording& recording) +{ + Logger::Log(LEVEL_INFO, "%s", __func__); + + return GetMediaEntry(recording.GetRecordingId()); +} + const std::string Media::GetMediaEntryURL(const kodi::addon::PVRRecording& recording) { Logger::Log(LEVEL_INFO, "%s", __func__); diff --git a/src/iptvsimple/Media.h b/src/iptvsimple/Media.h index 81cc05daa..57a58032c 100644 --- a/src/iptvsimple/Media.h +++ b/src/iptvsimple/Media.h @@ -25,6 +25,7 @@ namespace iptvsimple void GetMedia(std::vector& kodiRecordings); int GetNumMedia() const; void Clear(); + const data::MediaEntry GetMediaEntry(const kodi::addon::PVRRecording& mediaEntry); const std::string GetMediaEntryURL(const kodi::addon::PVRRecording& mediaEntry); const iptvsimple::data::MediaEntry* FindMediaEntry(const std::string& id, const std::string& displayName) const; diff --git a/src/iptvsimple/StreamManager.cpp b/src/iptvsimple/StreamManager.cpp index f022e91b7..f2ba7c702 100644 --- a/src/iptvsimple/StreamManager.cpp +++ b/src/iptvsimple/StreamManager.cpp @@ -70,9 +70,9 @@ StreamEntry StreamManager::StreamEntryLookup(const Channel& channel, const std:: if (!streamEntry) { - StreamType streamType = StreamUtils::GetStreamType(streamTestUrl, channel); + StreamType streamType = StreamUtils::GetStreamType(streamTestUrl, channel.GetProperty(PVR_STREAM_PROPERTY_MIMETYPE), channel.IsCatchupTSStream()); if (streamType == StreamType::OTHER_TYPE) - streamType = StreamUtils::InspectStreamType(streamTestUrl, channel); + streamType = StreamUtils::InspectStreamType(streamTestUrl, channel.GetCatchupMode()); streamEntry = std::make_shared(); streamEntry->SetStreamKey(streamKey); diff --git a/src/iptvsimple/data/MediaEntry.cpp b/src/iptvsimple/data/MediaEntry.cpp index ebcec7598..b01df07fa 100644 --- a/src/iptvsimple/data/MediaEntry.cpp +++ b/src/iptvsimple/data/MediaEntry.cpp @@ -311,3 +311,12 @@ void MediaEntry::UpdateTo(kodi::addon::PVRRecording& left, bool isInVirtualMedia left.SetDirectory(newDirectory); } + +std::string MediaEntry::GetProperty(const std::string& propName) const +{ + auto propPair = m_properties.find(propName); + if (propPair != m_properties.end()) + return propPair->second; + + return {}; +} diff --git a/src/iptvsimple/utilities/StreamUtils.cpp b/src/iptvsimple/utilities/StreamUtils.cpp index f22613ad0..22ce4423b 100644 --- a/src/iptvsimple/utilities/StreamUtils.cpp +++ b/src/iptvsimple/utilities/StreamUtils.cpp @@ -58,18 +58,29 @@ void StreamUtils::SetAllStreamProperties(std::vectorAlwaysEnableTimeshiftModeIfMissing()) + { + properties.emplace_back("inputstream.ffmpegdirect.stream_mode", "timeshift"); + if (channel.GetProperty("inputstream.ffmpegdirect.is_realtime_stream").empty()) + properties.emplace_back("inputstream.ffmpegdirect.is_realtime_stream", "true"); + } + } } else { - StreamType streamType = StreamUtils::GetStreamType(streamURL, channel); + StreamType streamType = StreamUtils::GetStreamType(streamURL, channel.GetProperty(PVR_STREAM_PROPERTY_MIMETYPE), channel.IsCatchupTSStream()); if (streamType == StreamType::OTHER_TYPE) - streamType = StreamUtils::InspectStreamType(streamURL, channel); + streamType = StreamUtils::InspectStreamType(streamURL, channel.GetCatchupMode()); // Using kodi's built in inputstreams if (!isISAdaptiveSet && StreamUtils::UseKodiInputstreams(streamType, settings)) { - std::string ffmpegStreamURL = StreamUtils::GetURLWithFFmpegReconnectOptions(streamURL, streamType, channel, settings); + std::string ffmpegStreamURL = StreamUtils::GetURLWithFFmpegReconnectOptions(streamURL, streamType, channel.GetProperty(PVR_STREAM_PROPERTY_INPUTSTREAM), channel.GetProperty("http-reconnect") == "true" , settings); properties.emplace_back(PVR_STREAM_PROPERTY_STREAMURL, streamURL); if (!channel.HasMimeType() && StreamUtils::HasMimeType(streamType)) @@ -83,7 +94,7 @@ void StreamUtils::SetAllStreamProperties(std::vector& properties, const iptvsimple::data::MediaEntry& mediaEntry, const std::string& streamURL, std::shared_ptr& settings) +{ + // Check if the media entry has explicitly set up the use of inputstream.adaptive, + // if so, the best behaviour for media services is: + // - Always add mimetype to prevent kodi core to make an HTTP HEADER requests + // this because in some cases services refuse this request and can also deny downloads + // - If requested by settings, always add the "user-agent" header to ISA properties + const bool isISAdaptiveSet = + mediaEntry.GetProperty(PVR_STREAM_PROPERTY_INPUTSTREAM) == INPUTSTREAM_ADAPTIVE; + + if (!isISAdaptiveSet && !mediaEntry.GetInputStreamName().empty()) + { + // Media Entry has an inputstream class set so we only set the stream URL + properties.emplace_back(PVR_STREAM_PROPERTY_STREAMURL, streamURL); + + if (mediaEntry.GetInputStreamName() != PVR_STREAM_PROPERTY_VALUE_INPUTSTREAMFFMPEG) + CheckInputstreamInstalledAndEnabled(mediaEntry.GetInputStreamName()); + + if (mediaEntry.GetInputStreamName() == INPUTSTREAM_FFMPEGDIRECT) + InspectAndSetFFmpegDirectStreamProperties(properties, mediaEntry.GetMimeType(), mediaEntry.GetProperty("inputstream.ffmpegdirect.manifest_type"), CatchupMode::DISABLED, false, streamURL, settings); + } + else + { + StreamType streamType = StreamUtils::GetStreamType(streamURL, mediaEntry.GetProperty(PVR_STREAM_PROPERTY_MIMETYPE), false); + if (streamType == StreamType::OTHER_TYPE) + streamType = StreamUtils::InspectStreamType(streamURL, CatchupMode::DISABLED); + + // Using kodi's built in inputstreams + if (!isISAdaptiveSet && StreamUtils::UseKodiInputstreams(streamType, settings)) + { + std::string ffmpegStreamURL = StreamUtils::GetURLWithFFmpegReconnectOptions(streamURL, streamType, mediaEntry.GetProperty(PVR_STREAM_PROPERTY_INPUTSTREAM), mediaEntry.GetProperty("http-reconnect") == "true" , settings); + + properties.emplace_back(PVR_STREAM_PROPERTY_STREAMURL, streamURL); + if (!mediaEntry.HasMimeType() && StreamUtils::HasMimeType(streamType)) + properties.emplace_back(PVR_STREAM_PROPERTY_MIMETYPE, StreamUtils::GetMimeType(streamType)); + + if (streamType == StreamType::HLS || streamType == StreamType::TS) + { + properties.emplace_back(PVR_STREAM_PROPERTY_INPUTSTREAM, PVR_STREAM_PROPERTY_VALUE_INPUTSTREAMFFMPEG); + } + } + else // inputstream.adaptive + { + CheckInputstreamInstalledAndEnabled(INPUTSTREAM_ADAPTIVE); + + bool streamUrlSet = false; + + // If no media headers are explicitly set for inputstream.adaptive, + // strip the headers from streamURL and put it to media headers property + + if (mediaEntry.GetProperty("inputstream.adaptive.manifest_headers").empty() && + mediaEntry.GetProperty("inputstream.adaptive.stream_headers").empty()) + { + // No stream headers declared by property, check if stream URL has any + std::string url; + std::string encodedProtocolOptions; + if (SplitUrlProtocolOpts(streamURL, url, encodedProtocolOptions)) + { + // Set stream URL without headers and encoded headers as property + properties.emplace_back(PVR_STREAM_PROPERTY_STREAMURL, url); + properties.emplace_back("inputstream.adaptive.manifest_headers", encodedProtocolOptions); + properties.emplace_back("inputstream.adaptive.stream_headers", encodedProtocolOptions); + streamUrlSet = true; + } + } + + // Set intact stream URL if not previously set + if (!streamUrlSet) + properties.emplace_back(PVR_STREAM_PROPERTY_STREAMURL, streamURL); + + if (!isISAdaptiveSet) + properties.emplace_back(PVR_STREAM_PROPERTY_INPUTSTREAM, INPUTSTREAM_ADAPTIVE); + + if (streamType == StreamType::HLS || streamType == StreamType::DASH || + streamType == StreamType::SMOOTH_STREAMING) + properties.emplace_back(PVR_STREAM_PROPERTY_MIMETYPE, StreamUtils::GetMimeType(streamType)); + } + } + + if (!mediaEntry.GetProperties().empty()) + { + for (auto& prop : mediaEntry.GetProperties()) + properties.emplace_back(prop.first, prop.second); + } +} + bool StreamUtils::CheckInputstreamInstalledAndEnabled(const std::string& inputstreamName) { std::string version; @@ -173,38 +271,29 @@ bool StreamUtils::CheckInputstreamInstalledAndEnabled(const std::string& inputst return true; } -void StreamUtils::InspectAndSetFFmpegDirectStreamProperties(std::vector& properties, const iptvsimple::data::Channel& channel, const std::string& streamURL, bool isChannelURL, std::shared_ptr& settings) +void StreamUtils::InspectAndSetFFmpegDirectStreamProperties(std::vector& properties, const std::string& mimeType, const std::string& manifestType, CatchupMode catchupMode, bool isCatchupTSStream, const std::string& streamURL, std::shared_ptr& settings) { // If there is no MIME type and no manifest type (BOTH!) set then potentially inspect the stream and set them - if (!channel.HasMimeType() && !channel.GetProperty("inputstream.ffmpegdirect.manifest_type").empty()) + if (!mimeType.empty() && !manifestType.empty()) { - StreamType streamType = StreamUtils::GetStreamType(streamURL, channel); + StreamType streamType = StreamUtils::GetStreamType(streamURL, mimeType, isCatchupTSStream); if (streamType == StreamType::OTHER_TYPE) - streamType = StreamUtils::InspectStreamType(streamURL, channel); + streamType = StreamUtils::InspectStreamType(streamURL, catchupMode); - if (!channel.HasMimeType() && StreamUtils::HasMimeType(streamType)) + if (mimeType.empty() && StreamUtils::HasMimeType(streamType)) properties.emplace_back(PVR_STREAM_PROPERTY_MIMETYPE, StreamUtils::GetMimeType(streamType)); - SetFFmpegDirectManifestTypeStreamProperty(properties, channel, streamURL, streamType); - } - - if (channel.SupportsLiveStreamTimeshifting() && isChannelURL && - channel.GetProperty("inputstream.ffmpegdirect.stream_mode").empty() && - settings->AlwaysEnableTimeshiftModeIfMissing()) - { - properties.emplace_back("inputstream.ffmpegdirect.stream_mode", "timeshift"); - if (channel.GetProperty("inputstream.ffmpegdirect.is_realtime_stream").empty()) - properties.emplace_back("inputstream.ffmpegdirect.is_realtime_stream", "true"); + SetFFmpegDirectManifestTypeStreamProperty(properties, manifestType, streamURL, streamType); } } -void StreamUtils::SetFFmpegDirectManifestTypeStreamProperty(std::vector& properties, const iptvsimple::data::Channel& channel, const std::string& streamURL, const StreamType& streamType) +void StreamUtils::SetFFmpegDirectManifestTypeStreamProperty(std::vector& properties, const std::string& manifestType, const std::string& streamURL, const StreamType& streamType) { - std::string manifestType = channel.GetProperty("inputstream.ffmpegdirect.manifest_type"); + std::string newManifestType; if (manifestType.empty()) - manifestType = StreamUtils::GetManifestType(streamType); - if (!manifestType.empty()) - properties.emplace_back("inputstream.ffmpegdirect.manifest_type", manifestType); + newManifestType = StreamUtils::GetManifestType(streamType); + if (!newManifestType.empty()) + properties.emplace_back("inputstream.ffmpegdirect.manifest_type", newManifestType); } std::string StreamUtils::GetEffectiveInputStreamName(const StreamType& streamType, const iptvsimple::data::Channel& channel, std::shared_ptr& settings) @@ -232,13 +321,11 @@ std::string StreamUtils::GetEffectiveInputStreamName(const StreamType& streamTyp return inputStreamName; } -const StreamType StreamUtils::GetStreamType(const std::string& url, const Channel& channel) +const StreamType StreamUtils::GetStreamType(const std::string& url, const std::string& mimeType, bool isCatchupTSStream) { if (StringUtils::StartsWith(url, "plugin://")) return StreamType::PLUGIN; - std::string mimeType = channel.GetProperty(PVR_STREAM_PROPERTY_MIMETYPE); - if (url.find(".m3u8") != std::string::npos || mimeType == "application/x-mpegURL" || mimeType == "application/vnd.apple.mpegurl") @@ -251,7 +338,7 @@ const StreamType StreamUtils::GetStreamType(const std::string& url, const Channe !(url.find(".ismv") != std::string::npos || url.find(".isma") != std::string::npos)) return StreamType::SMOOTH_STREAMING; - if (mimeType == "video/mp2t" || channel.IsCatchupTSStream()) + if (mimeType == "video/mp2t" || isCatchupTSStream) return StreamType::TS; // it has a MIME type but not one we recognise @@ -261,7 +348,7 @@ const StreamType StreamUtils::GetStreamType(const std::string& url, const Channe return StreamType::OTHER_TYPE; } -const StreamType StreamUtils::InspectStreamType(const std::string& url, const Channel& channel) +const StreamType StreamUtils::InspectStreamType(const std::string& url, CatchupMode catchupMode) { if (!FileUtils::FileExists(url)) return StreamType::OTHER_TYPE; @@ -282,10 +369,10 @@ const StreamType StreamUtils::InspectStreamType(const std::string& url, const Ch } // If we can't inspect the stream type the only option left for default, append or shift mode is TS - if (channel.GetCatchupMode() == CatchupMode::DEFAULT || - channel.GetCatchupMode() == CatchupMode::APPEND || - channel.GetCatchupMode() == CatchupMode::SHIFT || - channel.GetCatchupMode() == CatchupMode::TIMESHIFT) + if (catchupMode == CatchupMode::DEFAULT || + catchupMode == CatchupMode::APPEND || + catchupMode == CatchupMode::SHIFT || + catchupMode == CatchupMode::TIMESHIFT) return StreamType::TS; return StreamType::OTHER_TYPE; @@ -328,12 +415,12 @@ bool StreamUtils::HasMimeType(const StreamType& streamType) return streamType != StreamType::OTHER_TYPE && streamType != StreamType::SMOOTH_STREAMING; } -std::string StreamUtils::GetURLWithFFmpegReconnectOptions(const std::string& streamUrl, const StreamType& streamType, const iptvsimple::data::Channel& channel, std::shared_ptr& settings) +std::string StreamUtils::GetURLWithFFmpegReconnectOptions(const std::string& streamUrl, const StreamType& streamType, const std::string& inputstreamName, bool hasHTTPReconnect, std::shared_ptr& settings) { std::string newStreamUrl = streamUrl; - if (WebUtils::IsHttpUrl(streamUrl) && SupportsFFmpegReconnect(streamType, channel) && - (channel.GetProperty("http-reconnect") == "true" || settings->UseFFmpegReconnect())) + if (WebUtils::IsHttpUrl(streamUrl) && SupportsFFmpegReconnect(streamType, inputstreamName) && + (hasHTTPReconnect|| settings->UseFFmpegReconnect())) { newStreamUrl = AddHeaderToStreamUrl(newStreamUrl, "reconnect", "1"); if (streamType != StreamType::HLS) @@ -390,10 +477,10 @@ bool StreamUtils::ChannelSpecifiesInputstream(const iptvsimple::data::Channel& c return !channel.GetInputStreamName().empty(); } -bool StreamUtils::SupportsFFmpegReconnect(const StreamType& streamType, const iptvsimple::data::Channel& channel) +bool StreamUtils::SupportsFFmpegReconnect(const StreamType& streamType, const std::string& inputstreamName) { return streamType == StreamType::HLS || - channel.GetProperty(PVR_STREAM_PROPERTY_INPUTSTREAM) == PVR_STREAM_PROPERTY_VALUE_INPUTSTREAMFFMPEG; + inputstreamName == PVR_STREAM_PROPERTY_VALUE_INPUTSTREAMFFMPEG; } std::string StreamUtils::GetUrlEncodedProtocolOptions(const std::string& protocolOptions) diff --git a/src/iptvsimple/utilities/StreamUtils.h b/src/iptvsimple/utilities/StreamUtils.h index 93115d1b3..0d3860ae5 100644 --- a/src/iptvsimple/utilities/StreamUtils.h +++ b/src/iptvsimple/utilities/StreamUtils.h @@ -8,6 +8,7 @@ #pragma once #include "../data/Channel.h" +#include "../data/MediaEntry.h" #include "../data/StreamEntry.h" #include @@ -29,12 +30,13 @@ namespace iptvsimple { public: static void SetAllStreamProperties(std::vector& properties, const iptvsimple::data::Channel& channel, const std::string& streamUrl, bool isChannelURL, std::map& catchupProperties, std::shared_ptr& settings); - static const StreamType GetStreamType(const std::string& url, const iptvsimple::data::Channel& channel); - static const StreamType InspectStreamType(const std::string& url, const iptvsimple::data::Channel& channel); + static void SetAllStreamProperties(std::vector& properties, const iptvsimple::data::MediaEntry& mediaEntry, const std::string& streamUrl, std::shared_ptr& settings); + static const StreamType GetStreamType(const std::string& url, const std::string& mimeType, bool isCatchupTSStream); + static const StreamType InspectStreamType(const std::string& url, iptvsimple::CatchupMode catchupMode); static const std::string GetManifestType(const StreamType& streamType); static const std::string GetMimeType(const StreamType& streamType); static bool HasMimeType(const StreamType& streamType); - static std::string GetURLWithFFmpegReconnectOptions(const std::string& streamUrl, const StreamType& streamType, const iptvsimple::data::Channel& channel, std::shared_ptr& settings); + static std::string GetURLWithFFmpegReconnectOptions(const std::string& streamUrl, const StreamType& streamType, const std::string& inputstreamName, bool hasHTTPReconnect, std::shared_ptr& settings); static std::string AddHeader(const std::string& headerTarget, const std::string& headerName, const std::string& headerValue, bool encodeHeaderValue); static std::string AddHeaderToStreamUrl(const std::string& streamUrl, const std::string& headerName, const std::string& headerValue); static bool UseKodiInputstreams(const StreamType& streamType, std::shared_ptr& settings); @@ -43,9 +45,9 @@ namespace iptvsimple static std::string GetEffectiveInputStreamName(const StreamType& streamType, const iptvsimple::data::Channel& channel, std::shared_ptr& settings); private: - static bool SupportsFFmpegReconnect(const StreamType& streamType, const iptvsimple::data::Channel& channel); - static void InspectAndSetFFmpegDirectStreamProperties(std::vector& properties, const iptvsimple::data::Channel& channel, const std::string& streamUrl, bool isChannelURL, std::shared_ptr& settings); - static void SetFFmpegDirectManifestTypeStreamProperty(std::vector& properties, const iptvsimple::data::Channel& channel, const std::string& streamURL, const StreamType& streamType); + static bool SupportsFFmpegReconnect(const StreamType& streamType, const std::string& inputstreamName); + static void InspectAndSetFFmpegDirectStreamProperties(std::vector& properties, const std::string& mimeType, const std::string& manifestType, iptvsimple::CatchupMode catchupMode, bool isCatchupTSStream, const std::string& streamUrl, std::shared_ptr& settings); + static void SetFFmpegDirectManifestTypeStreamProperty(std::vector& properties, const std::string& manifestType, const std::string& streamURL, const StreamType& streamType); static bool CheckInputstreamInstalledAndEnabled(const std::string& inputstreamName); }; From dd1d8243717ae3d852910b9e035a0cf6acd82944 Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Fri, 4 Oct 2024 15:54:35 +0100 Subject: [PATCH 2/2] changelog and version v22.2.0 --- pvr.iptvsimple/addon.xml.in | 2 +- pvr.iptvsimple/changelog.txt | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pvr.iptvsimple/addon.xml.in b/pvr.iptvsimple/addon.xml.in index cf055160d..712b22b26 100644 --- a/pvr.iptvsimple/addon.xml.in +++ b/pvr.iptvsimple/addon.xml.in @@ -1,7 +1,7 @@ @ADDON_DEPENDS@ diff --git a/pvr.iptvsimple/changelog.txt b/pvr.iptvsimple/changelog.txt index 9fde9ea13..5116ada36 100644 --- a/pvr.iptvsimple/changelog.txt +++ b/pvr.iptvsimple/changelog.txt @@ -1,3 +1,6 @@ +v22.2.0 +- Correctly set inputstream properties for media entries + v22.1.2 - Fix #KODIPROP parsing from playlists