diff --git a/src/Session.cpp b/src/Session.cpp index d2f90efcf..ebf8c8b97 100644 --- a/src/Session.cpp +++ b/src/Session.cpp @@ -360,8 +360,7 @@ bool CSession::InitializeDRM(bool addDefaultKID /* = false */) m_cdmSessions.resize(m_adaptiveTree->m_currentPeriod->GetPSSHSets().size()); // Try to initialize an SingleSampleDecryptor - if (m_adaptiveTree->m_currentPeriod->GetEncryptionState() != - EncryptionState::UNENCRYPTED) + if (m_adaptiveTree->m_currentPeriod->GetEncryptionState() == EncryptionState::ENCRYPTED_DRM) { std::string_view licenseKey = CSrvBroker::GetKodiProps().GetLicenseKey(); @@ -581,14 +580,14 @@ bool CSession::InitializePeriod(bool isSessionOpened /* = false */) isPsshChanged = !(m_adaptiveTree->m_currentPeriod->GetPSSHSets() == m_adaptiveTree->m_nextPeriod->GetPSSHSets()); isReusePssh = !isPsshChanged && m_adaptiveTree->m_nextPeriod->GetEncryptionState() == - EncryptionState::ENCRYPTED_SUPPORTED; + EncryptionState::ENCRYPTED_DRM; m_adaptiveTree->m_currentPeriod = m_adaptiveTree->m_nextPeriod; m_adaptiveTree->m_nextPeriod = nullptr; } m_chapterStartTime = GetChapterStartTime(); - if (m_adaptiveTree->m_currentPeriod->GetEncryptionState() == EncryptionState::ENCRYPTED) + if (m_adaptiveTree->m_currentPeriod->GetEncryptionState() == EncryptionState::NOT_SUPPORTED) { LOG::LogF(LOGERROR, "Unhandled encrypted stream."); return false; @@ -949,7 +948,7 @@ void CSession::PrepareStream(CStream* stream) } if (startEvent != EVENT_TYPE::REP_CHANGE && - stream->m_adStream.getPeriod()->GetEncryptionState() == EncryptionState::ENCRYPTED_SUPPORTED) + stream->m_adStream.getPeriod()->GetEncryptionState() == EncryptionState::ENCRYPTED_DRM) { InitializeDRM(); } diff --git a/src/common/AdaptationSet.cpp b/src/common/AdaptationSet.cpp index 2d75c2e04..5e525ea6c 100644 --- a/src/common/AdaptationSet.cpp +++ b/src/common/AdaptationSet.cpp @@ -70,7 +70,6 @@ void PLAYLIST::CAdaptationSet::CopyHLSData(const CAdaptationSet* other) m_baseUrl = other->m_baseUrl; m_streamType = other->m_streamType; - m_startNumber = other->m_startNumber; m_isImpaired = other->m_isImpaired; m_isOriginal = other->m_isOriginal; m_isDefault = other->m_isDefault; diff --git a/src/common/AdaptiveUtils.h b/src/common/AdaptiveUtils.h index 7becdc25c..addeda387 100644 --- a/src/common/AdaptiveUtils.h +++ b/src/common/AdaptiveUtils.h @@ -48,8 +48,9 @@ constexpr uint64_t KODI_VP_BUFFER_SECS = 8; enum class EncryptionState { UNENCRYPTED, - ENCRYPTED, // Unhandled/unsupported encrypted stream - ENCRYPTED_SUPPORTED, // Supported encrypted stream + ENCRYPTED_DRM, // DRM encrypted + ENCRYPTED_CK, // ClearKey encrypted (e.g. AES-128) + NOT_SUPPORTED, // Unsupported encryption }; enum class EncryptionType @@ -58,7 +59,6 @@ enum class EncryptionType CLEAR, AES128, WIDEVINE, - UNKNOWN, }; enum class ContainerType diff --git a/src/common/Period.cpp b/src/common/Period.cpp index 271c26d11..0095407bc 100644 --- a/src/common/Period.cpp +++ b/src/common/Period.cpp @@ -36,9 +36,7 @@ void PLAYLIST::CPeriod::CopyHLSData(const CPeriod* other) m_baseUrl = other->m_baseUrl; m_id = other->m_id; m_timescale = other->m_timescale; - m_encryptionState = other->m_encryptionState; m_includedStreamType = other->m_includedStreamType; - m_isSecureDecoderNeeded = other->m_isSecureDecoderNeeded; } uint16_t PLAYLIST::CPeriod::InsertPSSHSet(const PSSHSet& psshSet) diff --git a/src/common/Representation.cpp b/src/common/Representation.cpp index e60ea1e53..79b811b3f 100644 --- a/src/common/Representation.cpp +++ b/src/common/Representation.cpp @@ -49,8 +49,6 @@ void PLAYLIST::CRepresentation::CopyHLSData(const CRepresentation* other) m_isIncludedStream = other->m_isIncludedStream; m_isEnabled = other->m_isEnabled; - m_isWaitForSegment = other->m_isWaitForSegment; - m_initSegment = other->m_initSegment; } void PLAYLIST::CRepresentation::SetScaling() diff --git a/src/parser/DASHTree.cpp b/src/parser/DASHTree.cpp index 5ae1bdfc4..a4a91d8ab 100644 --- a/src/parser/DASHTree.cpp +++ b/src/parser/DASHTree.cpp @@ -639,7 +639,7 @@ void adaptive::CDashTree::ParseTagAdaptationSet(pugi::xml_node nodeAdp, PLAYLIST // Parse child tags if (nodeAdp.child("ContentProtection")) { - period->SetEncryptionState(EncryptionState::ENCRYPTED); + period->SetEncryptionState(EncryptionState::NOT_SUPPORTED); ParseTagContentProtection(nodeAdp, adpSet->ProtectionSchemes()); period->SetSecureDecodeNeeded(ParseTagContentProtectionSecDec(nodeAdp)); } @@ -947,7 +947,7 @@ void adaptive::CDashTree::ParseTagRepresentation(pugi::xml_node nodeRepr, // Parse child tags if (nodeRepr.child("ContentProtection")) { - period->SetEncryptionState(EncryptionState::ENCRYPTED); + period->SetEncryptionState(EncryptionState::NOT_SUPPORTED); ParseTagContentProtection(nodeRepr, repr->ProtectionSchemes()); } @@ -961,7 +961,7 @@ void adaptive::CDashTree::ParseTagRepresentation(pugi::xml_node nodeRepr, if (m_isCustomInitPssh || GetProtectionData(adpSet->ProtectionSchemes(), repr->ProtectionSchemes(), pssh, kid)) { - period->SetEncryptionState(EncryptionState::ENCRYPTED_SUPPORTED); + period->SetEncryptionState(EncryptionState::ENCRYPTED_DRM); uint16_t psshSetPos = InsertPsshSet(adpSet->GetStreamType(), period, adpSet, pssh, kid); diff --git a/src/parser/HLSTree.cpp b/src/parser/HLSTree.cpp index e03215042..77282465a 100644 --- a/src/parser/HLSTree.cpp +++ b/src/parser/HLSTree.cpp @@ -491,7 +491,7 @@ void adaptive::CHLSTree::FixDiscSequence(std::stringstream& streamData, uint32_t if (tagName == "#EXT-X-KEY" && !isSkipUntilDiscont) { auto attribs = ParseTagAttributes(tagValue); - + // NOTE: Multiple EXT-X-KEYs can be parsed sequentially switch (ProcessEncryption(rep->GetBaseUrl(), attribs)) { case EncryptionType::CLEAR: @@ -499,26 +499,34 @@ void adaptive::CHLSTree::FixDiscSequence(std::stringstream& streamData, uint32_t period->SetEncryptionState(EncryptionState::UNENCRYPTED); psshSetPos = PSSHSET_POS_DEFAULT; break; - case EncryptionType::UNKNOWN: - currentEncryptionType = EncryptionType::UNKNOWN; - period->SetEncryptionState(EncryptionState::ENCRYPTED); - break; - case EncryptionType::NOT_SUPPORTED: - currentEncryptionType = EncryptionType::NOT_SUPPORTED; - period->SetEncryptionState(EncryptionState::ENCRYPTED); - break; case EncryptionType::AES128: - currentEncryptionType = EncryptionType::AES128; - period->SetEncryptionState(EncryptionState::UNENCRYPTED); - psshSetPos = PSSHSET_POS_DEFAULT; + if (period->GetEncryptionState() != EncryptionState::ENCRYPTED_DRM) + { + currentEncryptionType = EncryptionType::AES128; + period->SetEncryptionState(EncryptionState::ENCRYPTED_CK); + psshSetPos = PSSHSET_POS_DEFAULT; + } break; case EncryptionType::WIDEVINE: - currentEncryptionType = EncryptionType::WIDEVINE; - period->SetEncryptionState(EncryptionState::ENCRYPTED_SUPPORTED); - - rep->m_psshSetPos = InsertPsshSet(adp->GetStreamType(), period, adp, m_currentPssh, - m_currentDefaultKID, m_currentKidUrl, m_currentIV); + if (period->GetEncryptionState() != EncryptionState::ENCRYPTED_CK) + { + currentEncryptionType = EncryptionType::WIDEVINE; + period->SetEncryptionState(EncryptionState::ENCRYPTED_DRM); + rep->m_psshSetPos = InsertPsshSet(adp->GetStreamType(), period, adp, m_currentPssh, + m_currentDefaultKID, m_currentKidUrl, m_currentIV); + } + break; + case EncryptionType::NOT_SUPPORTED: + // Set only if a supported encryption has not previously been parsed + if (period->GetEncryptionState() != EncryptionState::ENCRYPTED_DRM && + period->GetEncryptionState() != EncryptionState::ENCRYPTED_CK) + { + currentEncryptionType = EncryptionType::NOT_SUPPORTED; + period->SetEncryptionState(EncryptionState::NOT_SUPPORTED); + } + break; default: + LOG::LogF(LOGFATAL, "Unhandled EncryptionType"); break; } } @@ -775,44 +783,51 @@ void adaptive::CHLSTree::FixDiscSequence(std::stringstream& streamData, uint32_t isSkipUntilDiscont = false; ++discontCount; - // Create a new period or update an existing one + mediaSequenceNbr += rep->SegmentTimeline().GetSize(); + currentSegNumber = mediaSequenceNbr; - CPeriod* foundPeriod = FindDiscontinuityPeriod(m_discontSeq + discontCount); - if (foundPeriod) // Update existing period + CPeriod* newPeriod = FindDiscontinuityPeriod(m_discontSeq + discontCount); + + if (!newPeriod) // Create new period { - period = foundPeriod; + auto newPeriodPtr = CPeriod::MakeUniquePtr(); + + // Clone same data structure from previous period (no segment will be copied) + newPeriodPtr->CopyHLSData(period); + newPeriod = newPeriodPtr.get(); + m_periods.push_back(std::move(newPeriodPtr)); } - else // Create new period - { - auto newPeriod = CPeriod::MakeUniquePtr(); - // CopyHLSData will copy also the init segment in the representations - // that must persist to next period until overrided by new EXT-X-MAP tag - newPeriod->CopyHLSData(m_currentPeriod); - period = newPeriod.get(); - period->SetStart(0); + newPeriod->SetStart(0); - m_periods.push_back(std::move(newPeriod)); - } + CAdaptationSet* newAdpSet = newPeriod->GetAdaptationSets()[adpSetPos].get(); + CRepresentation* newRep = newAdpSet->GetRepresentations()[reprPos].get(); - mediaSequenceNbr += rep->SegmentTimeline().GetSize(); - currentSegNumber = mediaSequenceNbr; + // Copy the base url from previous period/representation + newRep->SetBaseUrl(rep->GetBaseUrl()); - adp = period->GetAdaptationSets()[adpSetPos].get(); - // When we switch to a repr of another period we need to set current base url - CRepresentation* switchRep = adp->GetRepresentations()[reprPos].get(); - switchRep->SetBaseUrl(rep->GetBaseUrl()); - rep = switchRep; + // Copy init segment from previous period/representation + // it must persist until overrided by a new EXT-X-MAP tag + if (rep->GetInitSegment().has_value()) + { + newRep->SetInitSegment(*rep->GetInitSegment()); + newRep->SetContainerType(rep->GetContainerType()); + } + // Copy encryption data from previous period/representation + // it must persist until overrided by a new EXT-X-KEY tag + newPeriod->SetEncryptionState(period->GetEncryptionState()); if (currentEncryptionType == EncryptionType::WIDEVINE) { - rep->m_psshSetPos = InsertPsshSet(adp->GetStreamType(), period, adp, m_currentPssh, - m_currentDefaultKID, m_currentKidUrl, m_currentIV); - period->SetEncryptionState(EncryptionState::ENCRYPTED_SUPPORTED); + newRep->m_psshSetPos = + InsertPsshSet(newAdpSet->GetStreamType(), newPeriod, newAdpSet, m_currentPssh, + m_currentDefaultKID, m_currentKidUrl, m_currentIV); } - if (rep->HasInitSegment()) - rep->SetContainerType(ContainerType::MP4); + // Set the new period as current + period = newPeriod; + adp = newAdpSet; + rep = newRep; } else if (tagName == "#EXT-X-ENDLIST") { @@ -939,7 +954,7 @@ void adaptive::CHLSTree::OnDataArrived(uint64_t segNum, size_t segBufferSize, bool isLastChunk) { - if (psshSet && m_currentPeriod->GetEncryptionState() != EncryptionState::ENCRYPTED_SUPPORTED) + if (psshSet && m_currentPeriod->GetEncryptionState() == EncryptionState::ENCRYPTED_CK) { std::lock_guard lckUpdTree(GetTreeUpdMutex()); @@ -1222,7 +1237,9 @@ PLAYLIST::EncryptionType adaptive::CHLSTree::ProcessEncryption( } // WIDEVINE - if (STRING::CompareNoCase(attribs["KEYFORMAT"], "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed")) + if (STRING::CompareNoCase(attribs["KEYFORMAT"], + "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed") && + STRING::CompareNoCase(attribs["KEYFORMAT"], m_supportedKeySystem)) { m_currentPssh = uriData; @@ -1256,14 +1273,9 @@ PLAYLIST::EncryptionType adaptive::CHLSTree::ProcessEncryption( return EncryptionType::WIDEVINE; } - // KNOWN UNSUPPORTED - if (STRING::CompareNoCase(attribs["KEYFORMAT"], "com.apple.streamingkeydelivery")) - { - LOG::LogF(LOGDEBUG, "Keyformat %s not supported", attribs["KEYFORMAT"].c_str()); - return EncryptionType::NOT_SUPPORTED; - } - - return EncryptionType::UNKNOWN; + // Unsupported encryption + LOG::Log(LOGDEBUG, "Unsupported EXT-X-KEY keyformat \"%s\"", attribs["KEYFORMAT"].c_str()); + return EncryptionType::NOT_SUPPORTED; } bool adaptive::CHLSTree::GetUriByteData(std::string_view uri, std::vector& data) @@ -1354,6 +1366,7 @@ bool adaptive::CHLSTree::ParseMultivariantPlaylist(const std::string& data) { std::stringstream streamData{data}; MultivariantPlaylist pl; + std::vector encryptionTypes; // Parse text data @@ -1462,26 +1475,21 @@ bool adaptive::CHLSTree::ParseMultivariantPlaylist(const std::string& data) else if (tagName == "#EXT-X-SESSION-KEY") { auto attribs = ParseTagAttributes(tagValue); - - switch (ProcessEncryption(base_url_, attribs)) - { - case EncryptionType::NOT_SUPPORTED: - return false; - case EncryptionType::AES128: - case EncryptionType::WIDEVINE: - // #EXT-X-SESSION-KEY is meant for preparing DRM without - // loading sub-playlist. As long our workflow is serial, we - // don't profite and therefore do not any action. - break; - case EncryptionType::UNKNOWN: - LOG::LogF(LOGWARNING, "Unknown encryption type"); - break; - default: - break; - } + encryptionTypes.emplace_back(ProcessEncryption(base_url_, attribs)); } } + if (!encryptionTypes.empty()) + { + // Check if there is at least one EXT-X-SESSION-KEY supported + bool isNotSupported = + std::all_of(encryptionTypes.begin(), encryptionTypes.end(), + [](EncryptionType type) { return type == EncryptionType::NOT_SUPPORTED; }); + + if (isNotSupported) + return false; + } + // Create Period / Adaptation sets / Representations std::unique_ptr period = CPeriod::MakeUniquePtr(); diff --git a/src/parser/SmoothTree.cpp b/src/parser/SmoothTree.cpp index 12b0a5bfc..543284fab 100644 --- a/src/parser/SmoothTree.cpp +++ b/src/parser/SmoothTree.cpp @@ -93,7 +93,7 @@ bool adaptive::CSmoothTree::ParseManifest(const std::string& data) xml_node nodeProt = nodeSSM.child("Protection"); if (nodeProt) { - period->SetEncryptionState(EncryptionState::ENCRYPTED); + period->SetEncryptionState(EncryptionState::NOT_SUPPORTED); period->SetSecureDecodeNeeded(true); pugi::xml_node nodeProtHead = nodeProt.child("ProtectionHeader"); @@ -105,7 +105,7 @@ bool adaptive::CSmoothTree::ParseManifest(const std::string& data) { if (protParser.ParseHeader(nodeProtHead.child_value())) { - period->SetEncryptionState(EncryptionState::ENCRYPTED_SUPPORTED); + period->SetEncryptionState(EncryptionState::ENCRYPTED_DRM); m_licenseUrl = protParser.GetLicenseURL(); } } diff --git a/src/test/TestHLSTree.cpp b/src/test/TestHLSTree.cpp index f4793f551..1539c15ca 100644 --- a/src/test/TestHLSTree.cpp +++ b/src/test/TestHLSTree.cpp @@ -37,14 +37,15 @@ class HLSTreeTest : public ::testing::Test OpenTestFileMaster(filePath, "http://foo.bar/" + filePath); } - void OpenTestFileMaster(std::string filePath, std::string url) + bool OpenTestFileMaster(std::string filePath, std::string url) { - OpenTestFileMaster(filePath, url, {}); + return OpenTestFileMaster(filePath, url, {}, ""); } - void OpenTestFileMaster(std::string filePath, + bool OpenTestFileMaster(std::string filePath, std::string url, - std::map manifestHeaders) + std::map manifestHeaders, + std::string_view supportedKeySystem) { testHelper::testFile = filePath; @@ -55,7 +56,7 @@ class HLSTreeTest : public ::testing::Test if (!testHelper::DownloadFile(url, {}, {}, resp)) { LOG::Log(LOGERROR, "Cannot download \"%s\" DASH manifest file.", url.c_str()); - exit(1); + return false; } ADP::KODI_PROPS::ChooserProps chooserProps; @@ -63,17 +64,19 @@ class HLSTreeTest : public ::testing::Test // We set the download speed to calculate the initial network bandwidth m_reprChooser->SetDownloadSpeed(500000); - tree->Configure(m_reprChooser, "urn:uuid:EDEF8BA9-79D6-4ACE-A3C8-27DCD51D21ED", ""); + tree->Configure(m_reprChooser, supportedKeySystem, ""); // Parse the manifest if (!tree->Open(resp.effectiveUrl, resp.headers, resp.data)) { LOG::Log(LOGERROR, "Cannot open \"%s\" HLS manifest.", url.c_str()); - exit(1); + return false; } + tree->PostOpen(); tree->m_currentAdpSet = tree->m_periods[0]->GetAdaptationSets()[0].get(); tree->m_currentRepr = tree->m_currentAdpSet->GetRepresentations()[0].get(); + return true; } bool OpenTestFileVariant(std::string filePath, @@ -309,3 +312,89 @@ TEST_F(HLSTreeTest, PtsSetInMultiPeriod) EXPECT_EQ(adp1rep0seg1.startPTS_, 0); } } + +TEST_F(HLSTreeTest, MultipleEncryptionSequence) +{ + testHelper::effectiveUrl = "https://foo.bar/hls/video/stream_name/master.m3u8"; + + OpenTestFileMaster("hls/encrypt_master.m3u8", "https://baz.qux/hls/video/stream_name/master.m3u8"); + std::string var_download_url = + tree->m_currentPeriod->GetAdaptationSets()[0]->GetRepresentations()[0]->GetSourceUrl(); + + bool ret = OpenTestFileVariant("hls/encrypt_seq_stream.m3u8", var_download_url, + tree->m_currentPeriod, tree->m_currentAdpSet, tree->m_currentRepr); + + EXPECT_EQ(ret, true); + auto& periods = tree->m_periods; + EXPECT_EQ(periods.size(), 3); + + // Check if each period has the period encryption state + EXPECT_EQ(periods[0]->GetEncryptionState(), PLAYLIST::EncryptionState::UNENCRYPTED); + EXPECT_EQ(periods[1]->GetEncryptionState(), PLAYLIST::EncryptionState::ENCRYPTED_CK); + EXPECT_EQ(periods[2]->GetEncryptionState(), PLAYLIST::EncryptionState::UNENCRYPTED); +} + +TEST_F(HLSTreeTest, MultipleEncryptionSequenceDrmNoKSMaster) +{ + // Open the master manifest without any supported key system + // OpenTestFileMaster must return false to prevent ISA to process child manifests + // since master manifest contains EXT-X-SESSION-KEY used to check supported ks's + testHelper::effectiveUrl = "https://foo.bar/hls/video/stream_name/master.m3u8"; + + bool ret = OpenTestFileMaster("hls/encrypt_master_drm.m3u8", + "https://baz.qux/hls/video/stream_name/master.m3u8", {}, ""); + EXPECT_EQ(ret, false); +} + +TEST_F(HLSTreeTest, MultipleEncryptionSequenceDrmNoKS) +{ + // Open the master manifest without any supported key system + // OpenTestFileMaster must return true and process child manifests + // since master manifest DO NOT contains EXT-X-SESSION-KEY + testHelper::effectiveUrl = "https://foo.bar/hls/video/stream_name/master.m3u8"; + + bool ret = OpenTestFileMaster("hls/encrypt_master.m3u8", + "https://baz.qux/hls/video/stream_name/master.m3u8", {}, ""); + + EXPECT_EQ(ret, true); + + std::string var_download_url = + tree->m_currentPeriod->GetAdaptationSets()[0]->GetRepresentations()[0]->GetSourceUrl(); + + ret = OpenTestFileVariant("hls/encrypt_seq_stream_drm.m3u8", var_download_url, + tree->m_currentPeriod, tree->m_currentAdpSet, tree->m_currentRepr); + + EXPECT_EQ(ret, true); + auto& periods = tree->m_periods; + EXPECT_EQ(periods.size(), 2); + + // Check if each period has the period encryption state + EXPECT_EQ(periods[0]->GetEncryptionState(), PLAYLIST::EncryptionState::NOT_SUPPORTED); + EXPECT_EQ(periods[1]->GetEncryptionState(), PLAYLIST::EncryptionState::NOT_SUPPORTED); +} + +TEST_F(HLSTreeTest, MultipleEncryptionSequenceDrm) +{ + // Open the master manifest with the supported Widevine key system + // OpenTestFileMaster must return true and process child manifests + testHelper::effectiveUrl = "https://foo.bar/hls/video/stream_name/master.m3u8"; + + bool ret = OpenTestFileMaster("hls/encrypt_master_drm.m3u8", + "https://baz.qux/hls/video/stream_name/master.m3u8", {}, UUID_WIDEVINE); + + EXPECT_EQ(ret, true); + + std::string var_download_url = + tree->m_currentPeriod->GetAdaptationSets()[0]->GetRepresentations()[0]->GetSourceUrl(); + + ret = OpenTestFileVariant("hls/encrypt_seq_stream_drm.m3u8", var_download_url, + tree->m_currentPeriod, tree->m_currentAdpSet, tree->m_currentRepr); + + EXPECT_EQ(ret, true); + auto& periods = tree->m_periods; + EXPECT_EQ(periods.size(), 2); + + // Check if each period has the period encryption state + EXPECT_EQ(periods[0]->GetEncryptionState(), PLAYLIST::EncryptionState::ENCRYPTED_DRM); + EXPECT_EQ(periods[1]->GetEncryptionState(), PLAYLIST::EncryptionState::ENCRYPTED_DRM); +} diff --git a/src/test/TestHelper.h b/src/test/TestHelper.h index 1745a1412..fce52332e 100644 --- a/src/test/TestHelper.h +++ b/src/test/TestHelper.h @@ -25,6 +25,8 @@ // this shortens the conversion needed using STR = std::string; +constexpr std::string_view UUID_WIDEVINE = "urn:uuid:EDEF8BA9-79D6-4ACE-A3C8-27DCD51D21ED"; + std::string GetEnv(const std::string& var); void SetFileName(std::string& file, const std::string name); diff --git a/src/test/manifests/hls/encrypt_master.m3u8 b/src/test/manifests/hls/encrypt_master.m3u8 new file mode 100644 index 000000000..e21c4c3bf --- /dev/null +++ b/src/test/manifests/hls/encrypt_master.m3u8 @@ -0,0 +1,5 @@ +#EXTM3U +#EXT-X-VERSION:3 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-STREAM-INF:BANDWIDTH=2977920,AVERAGE-BANDWIDTH=2745600,CODECS="avc1.64001f,mp4a.40.2",RESOLUTION=1280x720,FRAME-RATE=30.000 +child_playlist.m3u8 diff --git a/src/test/manifests/hls/encrypt_master_drm.m3u8 b/src/test/manifests/hls/encrypt_master_drm.m3u8 new file mode 100644 index 000000000..783f2d6fd --- /dev/null +++ b/src/test/manifests/hls/encrypt_master_drm.m3u8 @@ -0,0 +1,10 @@ +#EXTM3U +#EXT-X-VERSION:3 +#EXT-X-INDEPENDENT-SEGMENTS + +#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES-CTR,KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed",KEYFORMATVERSIONS="1",CHARACTERISTICS="com.dss.ctr.hd",URI="data:text/plain;base64,AAAAMnBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABISEDMaOPkkyUtflN5hPeeZLjg=" +#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES-CTR,KEYFORMAT="com.microsoft.playready",KEYFORMATVERSIONS="1",CHARACTERISTICS="com.dss.ctr.hd",URI="data:text/plain;charset=UTF-16;base64,xAEAAAEAAQC6ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AKwBUAGcAYQBNADgAawBrAFgAMAB1AFUAMwBtAEUAOQA1ADUAawB1AE8AQQA9AD0APAAvAEsASQBEAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=" +#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES-CTR,KEYFORMAT="PRMNAGRA",KEYFORMATVERSIONS="1",CHARACTERISTICS="com.dss.ctr.hd",URI="data:text/plain;base64,eyJrZXktaWQiOiIzMzFhMzhmOS0yNGM5LTRiNWYtOTRkZS02MTNkZTc5OTJlMzgiLCJlbWkiOiJjdHIiLCJwcm0iOiJleUpqYjI1MFpXNTBTV1FpT2lKamRISWlMQ0pyWlhsSlpDSTZJak16TVdFek9HWTVMVEkwWXprdE5HSTFaaTA1TkdSbExUWXhNMlJsTnprNU1tVXpPQ0o5In0=" + +#EXT-X-STREAM-INF:BANDWIDTH=2977920,AVERAGE-BANDWIDTH=2745600,CODECS="avc1.64001f,mp4a.40.2",RESOLUTION=1280x720,FRAME-RATE=30.000 +child_playlist.m3u8 diff --git a/src/test/manifests/hls/encrypt_seq_stream.m3u8 b/src/test/manifests/hls/encrypt_seq_stream.m3u8 new file mode 100644 index 000000000..5f5cb30ec --- /dev/null +++ b/src/test/manifests/hls/encrypt_seq_stream.m3u8 @@ -0,0 +1,34 @@ +#EXTM3U +#EXT-X-VERSION:3 +#EXT-X-TARGETDURATION:5 +#EXT-X-DISCONTINUITY-SEQUENCE:0 +#EXT-X-MEDIA-SEQUENCE:46 +#EXT-X-PROGRAM-DATE-TIME:2024-05-03T07:58:09.047Z +#EXT-X-KEY:METHOD=NONE +#EXTINF:5, +http://testurl/master_120240503T075646_2701588.ts +#EXTINF:5, +http://testurl/master_120240503T075651_2701589.ts +#EXTINF:5, +http://testurl/master_120240503T075656_2701590.ts +#EXTINF:4, +http://testurl/master_120240503T075701_2701591.ts +#EXT-X-DISCONTINUITY +#EXT-X-PROGRAM-DATE-TIME:2024-05-03T07:58:28.047Z +#EXT-X-KEY:METHOD=AES-128,URI="http://testurl/hls/hls_2400_keyfile_0.key",IV=0x00000000000000000000000000000001 +#EXTINF:5, +http://testurl/hls/hls_2400-00000.ts +#EXT-X-KEY:METHOD=AES-128,URI="http://testurl/hls/hls_2400_keyfile_0.key",IV=0x00000000000000000000000000000002 +#EXTINF:5, +http://testurl/hls/hls_2400-00001.ts +#EXT-X-DISCONTINUITY +#EXT-X-PROGRAM-DATE-TIME:2024-05-03T07:58:38.047Z +#EXT-X-KEY:METHOD=NONE +#EXTINF:1, +http://testurl/master_120240503T075935_2701623.ts +#EXTINF:5, +http://testurl/master_120240503T075936_2701624.ts +#EXTINF:5, +http://testurl/master_120240503T075941_2701625.ts +#EXTINF:5, +http://testurl/master_120240503T075946_2701626.ts diff --git a/src/test/manifests/hls/encrypt_seq_stream_drm.m3u8 b/src/test/manifests/hls/encrypt_seq_stream_drm.m3u8 new file mode 100644 index 000000000..cd3e5c927 --- /dev/null +++ b/src/test/manifests/hls/encrypt_seq_stream_drm.m3u8 @@ -0,0 +1,20 @@ +#EXTM3U +#EXT-X-VERSION:6 +#EXT-X-TARGETDURATION:9 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-PROGRAM-DATE-TIME:2019-01-01T00:00:00.000Z + +#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed",KEYFORMATVERSIONS="1",URI="data:text/plain;base64,AAAAMnBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABISEDMaOPkkyUtflN5hPeeZLjg=" +#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,KEYFORMAT="com.microsoft.playready",KEYFORMATVERSIONS="1",URI="data:text/plain;charset=UTF-16;base64,xAEAAAEAAQC6ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AKwBUAGcAYQBNADgAawBrAFgAMAB1AFUAMwBtAEUAOQA1ADUAawB1AE8AQQA9AD0APAAvAEsASQBEAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=" +#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,KEYFORMAT="PRMNAGRA",KEYFORMATVERSIONS="1",URI="data:text/plain;base64,eyJrZXktaWQiOiIzMzFhMzhmOS0yNGM5LTRiNWYtOTRkZS02MTNkZTc5OTJlMzgiLCJlbWkiOiJjdHIiLCJwcm0iOiJleUpqYjI1MFpXNTBTV1FpT2lKamRISWlMQ0pyWlhsSlpDSTZJak16TVdFek9HWTVMVEkwWXprdE5HSTFaaTA1TkdSbExUWXhNMlJsTnprNU1tVXpPQ0o5In0=" + +#EXT-X-MAP:URI="39e9646a-1c59-4bed-bf1e-499f240f3a3e/4b19-MAIN/aa9b74ab-6e0a-46a5-a708-8ce0724af8ea/map.mp4" +#EXTINF:8.008, +39e9646a-1c59-4bed-bf1e-499f240f3a3e/4b19-MAIN/aa9b74ab-6e0a-46a5-a708-8ce0724af8ea/00/00/00_000.mp4 +#EXTINF:8.008, +39e9646a-1c59-4bed-bf1e-499f240f3a3e/4b19-MAIN/aa9b74ab-6e0a-46a5-a708-8ce0724af8ea/00/00/08_007.mp4 +#EXT-X-DISCONTINUITY +#EXT-X-MAP:URI="ba4051e1-1d92-4a2b-a3cd-c88b2e97da61/29db-DUB_CARD/f019e17f-2e43-4d4d-af78-e243c6b8831a/map.mp4" +#EXTINF:8.008, +ba4051e1-1d92-4a2b-a3cd-c88b2e97da61/29db-DUB_CARD/f019e17f-2e43-4d4d-af78-e243c6b8831a/00/00/00_000.mp4