|
|
|
|
Quelle Parser.cpp
Sprache: C
|
|
/*
* Parser.cpp
*****************************************************************************
* Copyright © 2015 - VideoLAN and VLC Authors
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "Parser.hpp"
#include "HLSSegment.hpp"
#include "HLSRepresentation.hpp"
#include "../../adaptive/SharedResources.hpp"
#include "../../adaptive/playlist/BasePeriod.h"
#include "../../adaptive/playlist/BaseAdaptationSet.h"
#include "../../adaptive/playlist/SegmentList.h"
#include "../../adaptive/encryption/Keyring.hpp"
#include "../../adaptive/tools/Retrieve.hpp"
#include "../../adaptive/tools/Helper.h"
#include "../../adaptive/tools/Conversions.hpp"
#include "../../adaptive/tools/FormatNamespace.hpp"
#include "M3U8.hpp"
#include "Tags.hpp"
#include <vlc_strings.h>
#include <vlc_stream.h>
#include <cstdio>
#include <sstream>
#include <array>
#include <unordered_map>
#include <cctype>
#include <algorithm>
#include <limits>
using namespace adaptive;
using namespace adaptive::playlist;
using namespace hls::playlist;
M3U8Parser::M3U8Parser(SharedResources *res)
{
resources = res;
}
M3U8Parser::~M3U8Parser ()
{
}
static std::list<Tag *> getTagsFromList(std::list<Tag *> &list, int tag)
{
std::list<Tag *> ret;
std::list<Tag *>::const_iterator it;
for(it = list.begin(); it != list.end(); ++it)
{
if( (*it)->getType() == tag )
ret.push_back(*it);
}
return ret;
}
static Tag * getTagFromList(std::list<Tag *> &list, int tag)
{
std::list<Tag *>::const_iterator it;
for(it = list.begin(); it != list.end(); ++it)
{
if( (*it)->getType() == tag )
return *it;
}
return nullptr;
}
static void releaseTagsList(std::list<Tag *> &list)
{
std::list<Tag *>::const_iterator it;
for(it = list.begin(); it != list.end(); ++it)
delete *it;
list.clear();
}
HLSRepresentation * M3U8Parser::createRepresentation(BaseAdaptationSet *adaptSet, const AttributesTag * tag)
{
const Attribute *uriAttr = tag->getAttributeByName("URI");
const Attribute *bwAttr = tag->getAttributeByName("AVERAGE-BANDWIDTH");
if(!bwAttr)
bwAttr = tag->getAttributeByName("BANDWIDTH");
const Attribute *resAttr = tag->getAttributeByName("RESOLUTION");
HLSRepresentation *rep = new (std::nothrow) HLSRepresentation(adaptSet);
if(rep)
{
rep->addAttribute(new TimescaleAttr(Timescale(CLOCK_FREQ)));
if(uriAttr)
{
std::string uri;
if(tag->getType() == AttributesTag::EXTXMEDIA)
{
uri = uriAttr->quotedString();
}
else
{
uri = uriAttr->value;
}
rep->setID(uri);
rep->setPlaylistUrl(uri);
if(uri.find('/') != std::string::npos)
{
uri = Helper::getDirectoryPath(uri);
if(!uri.empty())
rep->baseUrl = new Url(uri.append("/"));
}
}
if(bwAttr)
rep->setBandwidth(bwAttr->decimal());
if(resAttr)
{
std::pair<int, int> res = resAttr->getResolution();
if(res.first && res.second)
{
rep->setWidth(res.first);
rep->setHeight(res.second);
}
}
const Attribute *rateAttr = tag->getAttributeByName("FRAME-RATE");
if(rateAttr)
{
unsigned num, den;
vlc_ureduce(&num, &den, rateAttr->floatingPoint() * 1000, 1000, 0);
rep->setFrameRate(Rate(num, den));
}
}
return rep;
}
void M3U8Parser::createAndFillRepresentation(vlc_object_t *p_obj, BaseAdaptationSet *adaptSet,
const AttributesTag *tag,
const std::list<Tag *> &tagslist)
{
HLSRepresentation *rep = createRepresentation(adaptSet, tag);
if(rep)
{
parseSegments(p_obj, rep, tagslist);
adaptSet->addRepresentation(rep);
}
}
void M3U8Parser::fillRepresentationFromMediainfo(const AttributesTag *mediatag,
const std::string &type,
HLSRepresentation *rep)
{
if(type == "AUDIO")
{
const Attribute *channelsAttr = mediatag->getAttributeByName("CHANNELS");
if(channelsAttr)
rep->setChannelsCount(std::atoi(channelsAttr->quotedString().c_str()));
}
if(type != "AUDIO" && type != "VIDEO" && type != "SUBTITLES")
{
rep->streamFormat = StreamFormat(StreamFormat::Type::Unsupported);
}
}
void M3U8Parser::fillAdaptsetFromMediainfo(const AttributesTag *mediatag,
const std::string &type,
const std::string &group,
BaseAdaptationSet *altAdaptSet)
{
if(mediatag->getAttributeByName("DEFAULT"))
{
if(mediatag->getAttributeByName("DEFAULT")->value == "YES")
altAdaptSet->setRole(Role(Role::Value::Main));
else
altAdaptSet->setRole(Role(Role::Value::Alternate));
}
if(mediatag->getAttributeByName("AUTOSELECT"))
{
if(mediatag->getAttributeByName("AUTOSELECT")->value == "NO" &&
!mediatag->getAttributeByName("DEFAULT"))
altAdaptSet->setRole(Role(Role::Value::Supplementary));
}
/* Subtitles unsupported for now */
if(type == "SUBTITLES")
{
altAdaptSet->setRole(Role(Role::Value::Subtitle));
}
if(mediatag->getAttributeByName("LANGUAGE"))
altAdaptSet->setLang(mediatag->getAttributeByName("LANGUAGE")->quotedString());
std::string desc = group;
const Attribute *nameAttr = mediatag->getAttributeByName("NAME");
if(nameAttr)
{
if(!desc.empty())
desc += " ";
desc += nameAttr->quotedString();
}
if(!desc.empty())
{
altAdaptSet->description = desc;
altAdaptSet->setID(ID(desc));
}
}
bool M3U8Parser::appendSegmentsFromPlaylistURI(vlc_object_t *p_obj, HLSRepresentation *rep)
{
block_t *p_block = Retrieve::HTTP(resources, ChunkType::Playlist, rep->getPlaylistUrl().toString());
if(p_block)
{
stream_t *substream = vlc_stream_MemoryNew(p_obj, p_block->p_buffer, p_block->i_buffer, true);
if(substream)
{
std::list<Tag *> tagslist = parseEntries(substream);
vlc_stream_Delete(substream);
parseSegments(p_obj, rep, tagslist);
releaseTagsList(tagslist);
}
block_Release(p_block);
return true;
}
return false;
}
static bool parseEncryption(const AttributesTag *keytag, const Url &playlistUrl,
CommonEncryption &encryption)
{
if( keytag->getAttributeByName("METHOD") &&
keytag->getAttributeByName("METHOD")->value == "AES-128" &&
keytag->getAttributeByName("URI") )
{
encryption.method = CommonEncryption::Method::AES_128;
encryption.uri.clear();
Url keyurl(keytag->getAttributeByName("URI")->quotedString());
if(!keyurl.hasScheme())
{
keyurl.prepend(Helper::getDirectoryPath(playlistUrl.toString()).append("/"));
}
encryption.uri = keyurl.toString();
if(keytag->getAttributeByName("IV"))
{
encryption.iv.clear();
encryption.iv = keytag->getAttributeByName("IV")->hexSequence();
}
return true;
}
else
{
/* unsupported or invalid */
encryption.method = CommonEncryption::Method::None;
encryption.uri.clear();
encryption.iv.clear();
return false;
}
}
void M3U8Parser::parseSegments(vlc_object_t *, HLSRepresentation *rep, const std::list<Tag *> &tagslist)
{
bool b_pdt = tagslist.cend() != std::find_if(tagslist.cbegin(), tagslist.cend(),
[](const Tag *t){return t->getType() == SingleValueTag::EXTXPROGRAMDATETIME;});
bool b_vod = tagslist.size() && tagslist.back()->getType() == SingleValueTag::EXTXENDLIST;
SegmentList *segmentList = new SegmentList(rep, !b_vod && !b_pdt);
const Timescale timescale = rep->inheritTimescale();
rep->b_loaded = true;
rep->b_live = !b_vod;
vlc_tick_t totalduration = 0;
vlc_tick_t nzStartTime = 0;
vlc_tick_t absReferenceTime = VLC_TICK_INVALID;
uint64_t sequenceNumber = 0;
uint64_t discontinuitySequence = 0;
bool discontinuity = false;
std::size_t prevbyterangeoffset = 0;
const SingleValueTag *ctx_byterange = nullptr;
CommonEncryption encryption;
const ValuesListTag *ctx_extinf = nullptr;
std::list<HLSSegment *> segmentstoappend;
std::list<Tag *>::const_iterator it;
for(it = tagslist.begin(); it != tagslist.end(); ++it)
{
const Tag *tag = *it;
switch(tag->getType())
{
/* using static cast as attribute type permits avoiding class check */
case SingleValueTag::EXTXMEDIASEQUENCE:
{
sequenceNumber = (static_cast<const SingleValueTag*>(tag))->getValue().decimal();
}
break;
case ValuesListTag::EXTINF:
{
ctx_extinf = static_cast<const ValuesListTag *>(tag);
}
break;
case SingleValueTag::URI:
{
const SingleValueTag *uritag = static_cast<const SingleValueTag *>(tag);
if(uritag->getValue().value.empty())
{
ctx_extinf = nullptr;
ctx_byterange = nullptr;
break;
}
HLSSegment *segment = new (std::nothrow) HLSSegment(rep, sequenceNumber++);
if(!segment)
break;
segment->setSourceUrl(uritag->getValue().value);
/* Need to use EXTXTARGETDURATION as default as some can't properly set segment one */
vlc_tick_t nzDuration = vlc_tick_from_sec(rep->targetDuration);
if(ctx_extinf)
{
const Attribute *durAttribute = ctx_extinf->getAttributeByName("DURATION");
if(durAttribute)
nzDuration = vlc_tick_from_sec(durAttribute->floatingPoint());
ctx_extinf = nullptr;
}
segment->duration = timescale.ToScaled(nzDuration);
segment->startTime = timescale.ToScaled(nzStartTime);
nzStartTime += nzDuration;
totalduration += nzDuration;
if(absReferenceTime != VLC_TICK_INVALID)
{
segment->setDisplayTime(absReferenceTime);
absReferenceTime += nzDuration;
}
segmentstoappend.push_back(segment);
if(ctx_byterange)
{
ByteRange range = ctx_byterange->getValue().getByteRange();
if(!range.first.has_value()) /* first == offset, second = length */
range.first = prevbyterangeoffset;
prevbyterangeoffset = *range.first + range.second;
segment->setByteRange(*range.first, prevbyterangeoffset - 1);
ctx_byterange = nullptr;
}
segment->setDiscontinuitySequenceNumber(discontinuitySequence);
segment->discontinuity = discontinuity;
discontinuity = false;
if(encryption.method != CommonEncryption::Method::None)
segment->setEncryption(encryption);
}
break;
case SingleValueTag::EXTXTARGETDURATION:
rep->targetDuration = static_cast<const SingleValueTag *>(tag)->getValue().decimal();
break;
case SingleValueTag::EXTXPLAYLISTTYPE:
rep->b_live = !b_vod && (static_cast<const SingleValueTag *>(tag)->getValue().value != "VOD");
break;
case SingleValueTag::EXTXBYTERANGE:
ctx_byterange = static_cast<const SingleValueTag *>(tag);
break;
case SingleValueTag::EXTXPROGRAMDATETIME:
absReferenceTime = VLC_TICK_0 +
UTCTime(static_cast<const SingleValueTag *>(tag)->getValue().value);
/* Reverse apply UTC timespec from first discont */
if(segmentstoappend.size() && segmentstoappend.back()->getDisplayTime() == VLC_TICK_INVALID)
{
vlc_tick_t tempTime = absReferenceTime;
for(auto sit = segmentstoappend.crbegin(); sit != segmentstoappend.crend(); ++sit)
{
vlc_tick_t duration = timescale.ToTime((*sit)->duration);
if( duration < tempTime - VLC_TICK_0 )
tempTime -= duration;
else
tempTime = VLC_TICK_0;
(*sit)->setDisplayTime(tempTime);
}
}
break;
case AttributesTag::EXTXKEY:
parseEncryption(static_cast<const AttributesTag *>(tag),
rep->getPlaylistUrl(), encryption);
break;
case AttributesTag::EXTXMAP:
{
const AttributesTag *keytag = static_cast<const AttributesTag *>(tag);
const Attribute *uriAttr;
if(keytag && (uriAttr = keytag->getAttributeByName("URI")) &&
!segmentList->initialisationSegment) /* FIXME: handle discontinuities */
{
InitSegment *initSegment = new (std::nothrow) InitSegment(rep);
if(initSegment)
{
initSegment->setSourceUrl(uriAttr->quotedString());
const Attribute *byterangeAttr = keytag->getAttributeByName("BYTERANGE");
if(byterangeAttr)
{
const ByteRange range = byterangeAttr->unescapeQuotes().getByteRange();
initSegment->setByteRange(range.first.value_or(0), range.first.value_or(0) + range.second - 1);
}
segmentList->initialisationSegment = initSegment;
}
}
}
break;
case SingleValueTag::EXTXDISCONTINUITYSEQUENCE:
discontinuitySequence = static_cast<const SingleValueTag *>(tag)->getValue().decimal();
break;
case Tag::EXTXDISCONTINUITY:
discontinuity = true;
discontinuitySequence++;
break;
case Tag::EXTXENDLIST:
break;
}
}
for(HLSSegment *seg : segmentstoappend)
segmentList->addSegment(seg);
segmentstoappend.clear();
if(rep->isLive())
{
rep->getPlaylist()->duration = 0;
}
else if(totalduration > rep->getPlaylist()->duration)
{
rep->getPlaylist()->duration = totalduration;
}
rep->updateSegmentList(segmentList, true);
}
M3U8 * M3U8Parser::parse(vlc_object_t *p_object, stream_t *p_stream, const std::string &playlisturl)
{
char *psz_line = vlc_stream_ReadLine(p_stream);
if(!psz_line || strncmp(psz_line, "#EXTM3U", 7) ||
(psz_line[7] && !std::isspace(psz_line[7])))
{
free(psz_line);
return nullptr;
}
free(psz_line);
M3U8 *playlist = new (std::nothrow) M3U8(p_object);
if(!playlist)
return nullptr;
if(!playlisturl.empty())
playlist->setPlaylistUrl( Helper::getDirectoryPath(playlisturl).append("/") );
BasePeriod *period = new (std::nothrow) BasePeriod( playlist );
if(!period)
return playlist;
std::list<Tag *> tagslist = parseEntries(p_stream);
std::list<Tag *> streaminfotags = getTagsFromList(tagslist, AttributesTag::EXTXSTREAMINF);
bool b_masterplaylist = !streaminfotags.empty();
if(b_masterplaylist)
{
std::list<Tag *>::const_iterator it;
std::list<BaseAdaptationSet *> setstoadd;
/* Preload Session Key */
Tag *sessionKey = getTagFromList(tagslist, AttributesTag::EXTXSESSIONKEY);
if(sessionKey)
{
CommonEncryption sessionEncryption;
if(parseEncryption(static_cast<const AttributesTag *>(sessionKey),
playlist->getUrlSegment(), sessionEncryption) &&
!sessionEncryption.uri.empty())
{
resources->getKeyring()->getKey(resources, sessionEncryption.uri);
}
}
/* We'll need to create an adaptation set for each media group / alternative rendering
* we create a list of playlist being and alternative/group */
struct typecat_s
{
const char *type;
es_format_category_e cat;
};
std::array<struct typecat_s, 3> const typescats =
{{
{ "AUDIO", AUDIO_ES },
{ "VIDEO", VIDEO_ES },
{ "SUBTITLES", SPU_ES },
}};
struct StreamCodec
{
std::string codec;
es_format_category_e cat;
};
struct StreamInfos
{
const AttributesTag *tag;
std::string uri;
HLSRepresentation *rep;
std::list<struct StreamCodec> codecs;
};
std::list<struct StreamInfos> streaminfolist;
struct MediaInfos
{
const AttributesTag *tag;
std::string uri;
std::string group;
};
using CodecStats = std::unordered_map<std::string, unsigned>;
std::unordered_map<std::string, CodecStats> groupsmap;
std::list<struct MediaInfos> mediainfos;
/* create group info */
std::list<Tag *> mediainfotags = getTagsFromList(tagslist, AttributesTag::EXTXMEDIA);
for(it = mediainfotags.begin(); it != mediainfotags.end(); ++it)
{
AttributesTag *tag = dynamic_cast<AttributesTag *>(*it);
if(!tag)
continue;
const Attribute *groupid = tag->getAttributeByName("GROUP-ID");
if(!groupid) /* invalid */
continue;
const Attribute *uri = tag->getAttributeByName("URI");
MediaInfos entry;
entry.tag = tag;
entry.uri = uri ? uri->quotedString() : std::string();
entry.group = groupid->quotedString();
groupsmap.insert(std::pair<std::string, CodecStats>(entry.group, CodecStats()));
mediainfos.push_back(entry);
}
/* Gather info from EXT-X-STREAMINF */
for(it = streaminfotags.begin(); it != streaminfotags.end(); ++it)
{
AttributesTag *tag = dynamic_cast<AttributesTag *>(*it);
if(!tag)
continue;
const Attribute *uri = tag->getAttributeByName("URI");
if(!uri)
continue;
StreamInfos entry;
entry.tag = tag;
entry.uri = uri->quotedString();
entry.rep = nullptr;
const Attribute *codecsAttr = tag->getAttributeByName("CODECS");
if(codecsAttr)
{
auto codecs = Helper::tokenize(codecsAttr->quotedString(), ',');
for(auto codec : codecs)
{
FormatNamespace fns(codec);
struct StreamCodec s;
s.cat = fns.getFmt()->i_cat;
s.codec = codec;
entry.codecs.push_front(s);
}
/* create codec reference count info per group */
std::list<std::string> mediasCodecs;
for(auto typecat : typescats)
{
if(tag->getAttributeByName(typecat.type))
{
for(auto & codec : entry.codecs)
{
if(codec.cat == typecat.cat)
{
auto mit = groupsmap.find(tag->getAttributeByName(typecat.type)->quotedString());
if(mit != groupsmap.cend())
{
auto eit = (*mit).second.find(codec.codec);
if(eit != (*mit).second.end())
++(*eit).second;
else
(*mit).second.insert(std::pair<std::string, unsigned>(codec.codec, 0));
mediasCodecs.push_front(codec.codec);
}
break;
}
}
}
}
/* remove most frequent group codecs from streaminfo */
for(auto codec : mediasCodecs)
entry.codecs.remove_if([codec, entry](struct StreamCodec &v)
{ return v.codec == codec && entry.codecs.size() > 1; });
/* deduplicate codecs by category as variants can have different profile */
entry.codecs.sort([](const struct StreamCodec &a, const struct StreamCodec &b)
{ return a.cat < b.cat ; });
entry.codecs.unique([](const struct StreamCodec &a, const struct StreamCodec &b)
{ return a.cat == b.cat ; });
}
streaminfolist.push_back(entry);
}
/* process all EXT-X-STREAMINF and add them */
BaseAdaptationSet *adaptSet = new (std::nothrow) BaseAdaptationSet(period);
if(adaptSet)
{
/* adaptSet->setSegmentAligned(true); FIXME: based on streamformat */
for(auto &info : streaminfolist)
{
if(info.uri.empty())
continue;
HLSRepresentation *rep = createRepresentation(adaptSet, info.tag);
if(!rep)
continue;
for(auto codec: info.codecs)
rep->addCodecs(codec.codec);
if(adaptSet->description.empty() &&
info.tag->getAttributeByName("NAME"))
{
adaptSet->description = info.tag->getAttributeByName("NAME")->quotedString();
}
adaptSet->addRepresentation(rep);
info.rep = rep;
}
if(adaptSet->getRepresentations().empty())
{
delete adaptSet;
adaptSet = nullptr;
}
else setstoadd.push_front(adaptSet);
}
/* Finally add all EXT-X-MEDIA or propagate their attributes */
for(auto mediainfo : mediainfos)
{
const Attribute *typeattr = mediainfo.tag->getAttributeByName("TYPE");
if(!typeattr)
continue;
const std::string &mediatype = typeattr->value;
const StreamInfos *matchedstreaminf = nullptr;
if(!mediainfo.uri.empty())
{
auto sit = std::find_if(streaminfolist.begin(), streaminfolist.end(),
[mediainfo] (StreamInfos &si)
{ return si.uri == mediainfo.uri; });
if(sit != streaminfolist.end())
matchedstreaminf = & (*sit);
};
if(mediainfo.uri.empty() || matchedstreaminf) /* Attributes do apply to group STREAMINF members */
{
if(mediatype == "AUDIO" || mediatype == "VIDEO")
for(StreamInfos &si : streaminfolist)
{
if(matchedstreaminf && matchedstreaminf != &si)
continue;
const Attribute *groupattr = si.tag->getAttributeByName(mediatype.c_str());
if(groupattr && groupattr->quotedString() == mediainfo.group)
{
if(si.rep)
{
fillAdaptsetFromMediainfo(mediainfo.tag, typeattr->value,
mediainfo.group, si.rep->getAdaptationSet());
if(!matchedstreaminf || matchedstreaminf == &si)
fillRepresentationFromMediainfo(mediainfo.tag, typeattr->value, si.rep);
}
}
}
}
else /* This is an alternative in the group */
{
BaseAdaptationSet *altAdaptSet = new (std::nothrow) BaseAdaptationSet(period);
if(altAdaptSet)
{
fillAdaptsetFromMediainfo(mediainfo.tag, typeattr->value, mediainfo.group, altAdaptSet);
HLSRepresentation *rep = createRepresentation(altAdaptSet, mediainfo.tag);
if(!rep)
{
delete altAdaptSet;
continue;
}
fillRepresentationFromMediainfo(mediainfo.tag, typeattr->value, rep);
/* assign group codecs to adaptset */
auto groupmapit = groupsmap.find(mediainfo.group);
if(groupmapit != groupsmap.end())
{
for(auto & p : (*groupmapit).second)
{
FormatNamespace fns(p.first);
for(auto typecat : typescats)
{
if((fns.getFmt()->i_cat == typecat.cat && typeattr->value == typecat.type))
{
rep->addCodecs(p.first);
break;
}
}
}
}
altAdaptSet->addRepresentation(rep);
setstoadd.push_front(altAdaptSet);
}
}
}
/* late add to keep it ordered */
unsigned set_id = 1;
for(auto set : setstoadd)
{
if(!set->getID().isValid())
set->setID(ID(set_id++));
period->addAdaptationSet(set);
}
}
else /* Non master playlist (opened directly subplaylist or HLS v1) */
{
BaseAdaptationSet *adaptSet = new (std::nothrow) BaseAdaptationSet(period);
if(adaptSet)
{
AttributesTag *tag = new AttributesTag(AttributesTag::EXTXSTREAMINF, "");
tag->addAttribute(new Attribute("URI", playlisturl));
createAndFillRepresentation(p_object, adaptSet, tag, tagslist);
if(!adaptSet->getRepresentations().empty())
{
adaptSet->getRepresentations().front()->
scheduleNextUpdate(std::numeric_limits<uint64_t>::max(), true);
period->addAdaptationSet(adaptSet);
}
else
delete adaptSet;
delete tag;
}
}
playlist->addPeriod(period);
auto xstart = std::find_if(tagslist.cbegin(), tagslist.cend(),
[](const Tag * t) {return t->getType() == AttributesTag::EXTXSTART;});
if(xstart != tagslist.end())
{
auto xstartTag = static_cast<const AttributesTag *>(*xstart);
if(xstartTag->getAttributeByName("TIME-OFFSET"))
{
float offset = xstartTag->getAttributeByName("TIME-OFFSET")->floatingPoint();
if(offset > 0 && vlc_tick_from_sec(offset) <= playlist->duration)
playlist->presentationStartOffset = CLOCK_FREQ * offset;
else if(offset < 0 && vlc_tick_from_sec(-offset) <= playlist->duration)
playlist->presentationStartOffset = playlist->duration +
CLOCK_FREQ * offset;
}
}
releaseTagsList(tagslist);
playlist->debug();
return playlist;
}
std::list<Tag *> M3U8Parser::parseEntries(stream_t *stream)
{
std::list<Tag *> entrieslist;
Tag *lastTag = nullptr;
char *psz_line;
while((psz_line = vlc_stream_ReadLine(stream)))
{
if(*psz_line == '#')
{
if(!strncmp(psz_line, "#EXT", 4)) //tag
{
std::string key;
std::string attributes;
const char *split = strchr(psz_line, ':');
if(split)
{
key = std::string(psz_line + 1, split - psz_line - 1);
attributes = std::string(split + 1);
}
else
{
key = std::string(psz_line + 1);
}
if(!key.empty())
{
Tag *tag = TagFactory::createTagByName(key, attributes);
if(tag)
entrieslist.push_back(tag);
lastTag = tag;
}
}
}
else if(*psz_line)
{
/* URI */
if(lastTag && lastTag->getType() == AttributesTag::EXTXSTREAMINF)
{
AttributesTag *streaminftag = static_cast<AttributesTag *>(lastTag);
/* master playlist uri, merge as attribute */
Attribute *uriAttr = new (std::nothrow) Attribute("URI", std::string(psz_line));
if(uriAttr)
streaminftag->addAttribute(uriAttr);
}
else /* playlist tag, will take modifiers */
{
Tag *tag = TagFactory::createTagByName("", std::string(psz_line));
if(tag)
entrieslist.push_back(tag);
}
lastTag = nullptr;
}
else // drop
{
lastTag = nullptr;
}
free(psz_line);
}
return entrieslist;
}
¤ Dauer der Verarbeitung: 0.5 Sekunden
(vorverarbeitet am 2026-11-04)
¤
*© Formatika GbR, Deutschland
|
|
2026-11-01
|
|
|
|
|
Neuigkeiten |
| Aktuelles |
| Motto des Tages |
|
Open Source Software |
|
|
|
Jenseits des Üblichen ....
|
|
Besucherstatistik |
|
|
| Statistik |
| #Sources=1104856 |
| #Domains=417801 |
|
|