/*****************************************************************************
*
*****************************************************************************
* Copyright (C) 2022 VideoLabs, 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 "../SegmentTracker.hpp"
#include "../SharedResources.hpp"
#include "../logic/AbstractAdaptationLogic.h"
#include "../logic/BufferingLogic.hpp"
#include "../Time.hpp"
#include "../playlist/BasePlaylist.hpp"
#include "../playlist/BasePeriod.h"
#include "../playlist/BaseAdaptationSet.h"
#include "../playlist/BaseRepresentation.h"
#include "../../hls/playlist/HLSRepresentation.hpp"
#include "../playlist/SegmentList.h"
#include "../playlist/Segment.h"
#include "../http/HTTPConnectionManager.h"
#include "test.hpp"
#include <vlc_block.h>
#include <limits>
#include <vector>
#include <map>
#include <set>
#include <cassert>
#include <cstring>
using namespace adaptive;
using namespace adaptive::http;
using namespace hls::playlist;
class DummyLogic : public AbstractAdaptationLogic
{
public:
DummyLogic() : AbstractAdaptationLogic(nullptr), repindex(0) {}
virtual ~DummyLogic() = default;
BaseRepresentation* getNextRepresentation(BaseAdaptationSet *set,
BaseRepresentation *) override
{
if(set->getRepresentations().size() <= repindex)
return nullptr;
return set->getRepresentations().at(repindex);
}
unsigned repindex;
};
class DummyChunkSource : public AbstractChunkSource
{
public:
DummyChunkSource(ChunkType t, const BytesRange &range, const std::vector<uint8_t> &v,
const std::string &content)
: AbstractChunkSource(t, range), data(v), offset(0), contentType(content) {}
virtual ~DummyChunkSource() = default;
void recycle() override { delete this; }
const std::string & getContentType () const override
{
return contentType;
}
RequestStatus getRequestStatus() const override
{
return data.size() ? RequestStatus::Success : RequestStatus::GenericError;
}
block_t * readBlock () override
{
std::size_t remain = data.size() - offset;
block_t *b = block_Alloc(remain);
if(b)
std::memcpy(b->p_buffer, &data[offset], remain);
offset += remain;
return b;
}
block_t * read (size_t sz) override
{
std::size_t remain = data.size() - offset;
if(remain < sz)
sz = remain;
block_t *b = block_Alloc(sz);
if(b)
std::memcpy(b->p_buffer, &data[offset], sz);
offset += sz;
return b;
}
bool hasMoreData () const override { return offset < data.size(); }
size_t getBytesRead () const override { return offset; }
private:
std::vector<uint8_t> data;
std::size_t offset;
std::string contentType;
};
class DummyConnectionManager : public AbstractConnectionManager
{
public:
DummyConnectionManager() : AbstractConnectionManager(nullptr) {}
virtual ~DummyConnectionManager() = default;
void closeAllConnections () override {}
AbstractConnection * getConnection(ConnectionParams &) override { return nullptr; }
AbstractChunkSource *makeSource(const std::string &uri,
const ID &, ChunkType t,
const BytesRange &br) override
{
DummyChunkSource *d;
auto it = data.find(uri);
if(it == data.end())
d = new DummyChunkSource(t, br, std::vector<uint8_t>(), uri);
else
d = new DummyChunkSource(t, br, it->second, uri);
return d;
}
void recycleSource(AbstractChunkSource *) override {}
void start(AbstractChunkSource *) override {}
void cancel(AbstractChunkSource *) override {}
std::map<std::string, std::vector<uint8_t>> data;
};
using mapentry = std::pair<std::string, std::vector<uint8_t>>;
class SegmentTrackerListener : public SegmentTrackerListenerInterface
{
public:
SegmentTrackerListener() : SegmentTrackerListenerInterface()
{
reset();
}
virtual ~SegmentTrackerListener() = default;
void trackerEvent(const TrackerEvent &event) override
{
switch(event.getType())
{
case TrackerEvent::Type::SegmentChange:
{
const SegmentChangedEvent *e =
static_cast<const SegmentChangedEvent *>(&event);
segmentchanged.displaytime = e->displaytime;
segmentchanged.duration = e->duration;
segmentchanged.sequence = e->sequence;
segmentchanged.starttime = e->starttime;
}
break;
case TrackerEvent::Type::FormatChange:
{
const FormatChangedEvent *e =
static_cast<const FormatChangedEvent *>(&event);
formatchanged.format = *e->format;
}
break;
case TrackerEvent::Type::RepresentationSwitch:
{
const RepresentationSwitchEvent *e =
static_cast<const RepresentationSwitchEvent *>(&event);
representationchanged.prev = e->prev;
representationchanged.next = e->next;
}
break;
case TrackerEvent::Type::Discontinuity:
{
discontinuity = true;
}
break;
case TrackerEvent::Type::PositionChange:
{
const PositionChangedEvent *e =
static_cast<const PositionChangedEvent *>(&event);
positionchanged.resumeTime = e->resumeTime;
}
break;
default:
return;
}
events.insert(event.getType());
}
bool occured(TrackerEvent::Type t) const
{
return events.find(t) != events.end();
}
void reset()
{
events.clear();
segmentchanged.sequence = std::numeric_limits<uint64_t>::max() - 1;
segmentchanged.displaytime = -1;
segmentchanged.starttime = -1;
segmentchanged.duration = -1;
formatchanged.format = StreamFormat();
representationchanged.prev = (BaseRepresentation *) 0xdeadbeef;
representationchanged.next = (BaseRepresentation *) 0xdeadbeef;
discontinuity = false;
positionchanged.resumeTime = -1;
}
std::set<TrackerEvent::Type> events;
struct
{
uint64_t sequence;
vlc_tick_t displaytime;
vlc_tick_t starttime;
vlc_tick_t duration;
} segmentchanged;
struct
{
StreamFormat format;
} formatchanged;
struct
{
const BaseRepresentation *prev;
const BaseRepresentation *next;
} representationchanged;
bool discontinuity;
struct
{
vlc_tick_t resumeTime;
} positionchanged;
};
class DummyRepresentation : public BaseRepresentation
{
public:
DummyRepresentation(BaseAdaptationSet *set) : BaseRepresentation(set) {}
virtual ~DummyRepresentation() = default;
StreamFormat getStreamFormat() const override { return StreamFormat::Type::Unknown; }
};
class DummyHLSRepresentation : public HLSRepresentation
{
public:
DummyHLSRepresentation(BaseAdaptationSet *set, time_t duration) :
HLSRepresentation(set) { targetDuration = duration; }
virtual ~DummyHLSRepresentation() = default;
StreamFormat getStreamFormat() const override { return StreamFormat::Type::Unknown; }
bool needsUpdate(uint64_t) const override { return false; }
bool runLocalUpdates(SharedResources *) override { return false; }
};
static BaseAdaptationSet *CreatePlaylistPeriodAdaptationSet()
{
BaseAdaptationSet *set = nullptr;
BasePlaylist *pl= nullptr;
try
{
pl = new BasePlaylist(nullptr);
BasePeriod *period = new BasePeriod(pl);
pl->addPeriod(period);
set = new BaseAdaptationSet(period);
period->addAdaptationSet(set);
} catch( ... ) {
delete pl;
}
return set;
}
static int SegmentTracker_check_formats(BaseAdaptationSet *adaptSet,
DummyLogic *,
SegmentTracker *tracker,
SegmentTrackerListener &events)
{
const stime_t START = 1337;
Timescale timescale(100);
ChunkInterface *currentChunk = nullptr;
try
{
DummyRepresentation *rep0 = new DummyRepresentation(adaptSet);
adaptSet->addRepresentation(rep0);
rep0->setID(ID("0"));
SegmentList *segmentList = nullptr;
try
{
segmentList = new SegmentList(rep0);
segmentList->addAttribute(new TimescaleAttr(timescale));
for(int i=0; i<3; i++)
{
Segment *seg = new Segment(rep0);
seg->setSequenceNumber(123 + i);
seg->setDiscontinuitySequenceNumber(456);
seg->startTime = START + 100 * i;
seg->duration = 100;
seg->setSourceUrl(i < 2 ? "sample/aac" : "sample/ac3");
segmentList->addSegment(seg);
}
} catch (...) {
delete segmentList;
std::rethrow_exception(std::current_exception());
}
rep0->addAttribute(segmentList);
/* No start pos */
SegmentTracker::Position pos = tracker->getStartPosition();
Expect(pos.isValid());
Expect(pos.number == 123);
currentChunk = tracker->getNextChunk(true);
Expect(currentChunk == nullptr);
/* Start pos ok */
events.reset();
Expect(tracker->setStartPosition() == true);
currentChunk = tracker->getNextChunk(true);
Expect(currentChunk);
Expect(events.occured(TrackerEvent::Type::SegmentChange) == true);
Expect(events.occured(TrackerEvent::Type::FormatChange) == true);
Expect(events.segmentchanged.starttime == timescale.ToTime(START) + VLC_TICK_0);
Expect(events.segmentchanged.duration == timescale.ToTime(100));
Expect(events.segmentchanged.sequence == 456);
Expect(events.formatchanged.format == StreamFormat::Type::PackedAAC);
delete currentChunk;
currentChunk = nullptr;
events.reset();
currentChunk = tracker->getNextChunk(true);
Expect(currentChunk);
Expect(events.segmentchanged.starttime == timescale.ToTime(START + 100 * 1) + VLC_TICK_0);
Expect(events.segmentchanged.duration == timescale.ToTime(100));
Expect(events.occured(TrackerEvent::Type::FormatChange) == false);
Expect(events.occured(TrackerEvent::Type::SegmentChange) == true);
delete currentChunk;
currentChunk = nullptr;
events.reset();
currentChunk = tracker->getNextChunk(true);
Expect(currentChunk);
Expect(events.segmentchanged.starttime == timescale.ToTime(START + 100 * 2) + VLC_TICK_0);
Expect(events.segmentchanged.duration == timescale.ToTime(100));
Expect(events.occured(TrackerEvent::Type::FormatChange) == true);
Expect(events.occured(TrackerEvent::Type::SegmentChange) == true);
Expect(events.formatchanged.format == StreamFormat::Type::PackedAC3);
delete currentChunk;
currentChunk = nullptr;
/* Should fail */
events.reset();
currentChunk = tracker->getNextChunk(true);
Expect(currentChunk == nullptr);
Expect(events.occured(TrackerEvent::Type::SegmentChange) == false);
} catch( ... ) {
delete currentChunk;
return 1;
}
return 0;
}
/****** check position/alignment ******/
static int SegmentTracker_check_seeks(BaseAdaptationSet *adaptSet,
DummyLogic *,
SegmentTracker *tracker,
SegmentTrackerListener &events)
{
const stime_t START = 1337;
Timescale timescale(100);
ChunkInterface *currentChunk = nullptr;
try
{
DummyRepresentation *rep0 = new DummyRepresentation(adaptSet);
adaptSet->addRepresentation(rep0);
rep0->setID(ID("0"));
SegmentList *segmentList = nullptr;
try
{
segmentList = new SegmentList(rep0);
segmentList->addAttribute(new TimescaleAttr(timescale));
for(int i=0; i<10; i++)
{
Segment *seg = new Segment(rep0);
seg->setSequenceNumber(123 + i);
seg->setDiscontinuitySequenceNumber(456);
seg->startTime = START + 100 * i;
seg->duration = 100;
seg->setSourceUrl("sample/aac");
segmentList->addSegment(seg);
}
} catch (...) {
delete segmentList;
std::rethrow_exception(std::current_exception());
}
rep0->addAttribute(segmentList);
events.reset();
Expect(tracker->setPositionByTime(VLC_TICK_0 + timescale.ToTime(START + 250), false, true) == true);
Expect(events.occured(TrackerEvent::Type::PositionChange) == false);
Expect(tracker->getPlaybackTime() == 0);
events.reset();
Expect(tracker->setPositionByTime(VLC_TICK_0 + timescale.ToTime(START + 250), false, false) == true);
currentChunk = tracker->getNextChunk(true);
Expect(currentChunk);
Expect(events.occured(TrackerEvent::Type::PositionChange) == true);
Expect(events.positionchanged.resumeTime == timescale.ToTime(START + 200));
Expect(tracker->getPlaybackTime() == timescale.ToTime(START + 200));
delete currentChunk;
currentChunk = nullptr;
/* past playlist, align to end */
events.reset();
Expect(tracker->setPositionByTime(VLC_TICK_0 + timescale.ToTime(START + 9999), false, false) == true);
currentChunk = tracker->getNextChunk(true);
Expect(currentChunk);
Expect(events.occured(TrackerEvent::Type::PositionChange) == true);
delete currentChunk;
currentChunk = nullptr;
/* out of playlist, we need to fail */
Expect(tracker->setPositionByTime(VLC_TICK_0 + timescale.ToTime(START / 2), false, true) == false);
Expect(tracker->setPositionByTime(VLC_TICK_0 + timescale.ToTime(START / 2), false, false) == false);
/* restart playlist from startpos */
events.reset();
SegmentTracker::Position startpos = tracker->getStartPosition();
Expect(startpos.isValid());
tracker->setPosition(startpos, false);
currentChunk = tracker->getNextChunk(true);
Expect(currentChunk);
Expect(events.occured(TrackerEvent::Type::PositionChange) == true);
Expect(events.positionchanged.resumeTime == timescale.ToTime(START));
Expect(tracker->getPlaybackTime() == timescale.ToTime(START));
delete currentChunk;
currentChunk = nullptr;
/* go to unaligned pos */
events.reset();
Expect(tracker->setPositionByTime(VLC_TICK_0 + timescale.ToTime(START + 250), false, false) == true);
currentChunk = tracker->getNextChunk(true);
Expect(currentChunk);
Expect(events.occured(TrackerEvent::Type::PositionChange) == true);
Expect(events.positionchanged.resumeTime == timescale.ToTime(START + 2*100));
Expect(tracker->getPlaybackTime() == timescale.ToTime(START + 2*100));
delete currentChunk;
currentChunk = nullptr;
} catch( ... ) {
delete currentChunk;
return 1;
}
return 0;
}
/****** test with variants ******/
static int SegmentTracker_check_switches(BaseAdaptationSet *adaptSet,
DummyLogic *logic,
SegmentTracker *tracker,
SegmentTrackerListener &events)
{
const stime_t START = 1337;
Timescale timescale(100);
ChunkInterface *currentChunk = nullptr;
try
{
DummyRepresentation *rep0 = new DummyRepresentation(adaptSet);
adaptSet->addRepresentation(rep0);
rep0->setID(ID("0"));
SegmentList *segmentList = nullptr;
try
{
segmentList = new SegmentList(rep0);
segmentList->addAttribute(new TimescaleAttr(timescale));
for(int i=0; i<5; i++)
{
Segment *seg = new Segment(rep0);
seg->setSequenceNumber(123 + i);
seg->setDiscontinuitySequenceNumber(456);
seg->startTime = START + 100 * i;
seg->duration = 100;
seg->setSourceUrl("sample/aac");
segmentList->addSegment(seg);
}
} catch (...) {
delete segmentList;
std::rethrow_exception(std::current_exception());
}
rep0->addAttribute(segmentList);
segmentList->getSegments().at(4)->discontinuity = true;
segmentList->getSegments().at(4)->setDiscontinuitySequenceNumber(457);
DummyRepresentation *rep1 = new DummyRepresentation(adaptSet);
adaptSet->addRepresentation(rep1);
rep1->setID(ID("1"));
try
{
segmentList = new SegmentList(rep1);
segmentList->addAttribute(new TimescaleAttr(timescale));
for(int i=0; i<5; i++)
{
Segment *seg = new Segment(rep1);
seg->setSequenceNumber(123 + i);
seg->setDiscontinuitySequenceNumber(456);
seg->startTime = START + 100 * i;
seg->duration = 100;
seg->setSourceUrl("sample/aac");
segmentList->addSegment(seg);
}
} catch (...) {
delete segmentList;
std::rethrow_exception(std::current_exception());
}
rep1->addAttribute(segmentList);
/* have some init segment on rep1 */
InitSegment *initSegment = new InitSegment(rep1);
initSegment->setSourceUrl("sample/aacinit");
segmentList->initialisationSegment = initSegment;
Expect(adaptSet->isSegmentAligned());
Expect(adaptSet->getRepresentations().size() == 2);
/* initial switch notification */
events.reset();
Expect(tracker->setStartPosition() == true);
currentChunk = tracker->getNextChunk(true);
Expect(currentChunk);
Expect(events.occured(TrackerEvent::Type::RepresentationSwitch) == true);
Expect(events.representationchanged.prev == nullptr);
Expect(events.representationchanged.next == rep0);
delete currentChunk;
currentChunk = nullptr;
/* ask logic to switch */
logic->repindex = 1;
/* switch event should not trigger here */
events.reset();
currentChunk = tracker->getNextChunk(false);
Expect(currentChunk);
Expect(events.occured(TrackerEvent::Type::RepresentationSwitch) == false);
delete currentChunk;
currentChunk = nullptr;
events.reset();
currentChunk = tracker->getNextChunk(true);
Expect(currentChunk);
Expect(events.occured(TrackerEvent::Type::RepresentationSwitch) == true);
Expect(events.representationchanged.prev == rep0);
Expect(events.representationchanged.next == rep1);
/* check returned init */
Expect(currentChunk->getContentType() == "sample/aacinit");
Expect(events.segmentchanged.starttime == timescale.ToTime(START + 100 * 2) + VLC_TICK_0);
Expect(events.segmentchanged.duration == timescale.ToTime(100));
Expect(events.segmentchanged.sequence == 456);
delete currentChunk;
currentChunk = nullptr;
/* check segment */
events.reset();
currentChunk = tracker->getNextChunk(true);
Expect(currentChunk);
Expect(events.occured(TrackerEvent::Type::RepresentationSwitch) == false);
Expect(currentChunk->getContentType() == "sample/aac");
/* time should remain the same */
Expect(events.segmentchanged.starttime == timescale.ToTime(START + 100 * 2) + VLC_TICK_0);
Expect(events.segmentchanged.duration == timescale.ToTime(100));
Expect(events.segmentchanged.sequence == 456);
delete currentChunk;
currentChunk = nullptr;
/* ask logic to switch back */
logic->repindex = 0;
events.reset();
currentChunk = tracker->getNextChunk(true);
Expect(currentChunk);
Expect(events.occured(TrackerEvent::Type::RepresentationSwitch) == true);
Expect(events.representationchanged.prev == rep1);
Expect(events.representationchanged.next == rep0);
delete currentChunk;
currentChunk = nullptr;
/* check continuity & sequence handling */
events.reset();
currentChunk = tracker->getNextChunk(true);
Expect(currentChunk);
Expect(events.occured(TrackerEvent::Type::Discontinuity) == true);
Expect(events.segmentchanged.sequence == 457);
Expect(events.discontinuity == true);
delete currentChunk;
currentChunk = nullptr;
/* reset playlist to rep1 */
logic->repindex = 1;
events.reset();
SegmentTracker::Position startpos = tracker->getStartPosition();
Expect(startpos.isValid());
tracker->setPosition(startpos, false);
currentChunk = tracker->getNextChunk(true);
Expect(currentChunk);
Expect(events.occured(TrackerEvent::Type::RepresentationSwitch) == true);
Expect(events.representationchanged.prev == nullptr);
Expect(events.representationchanged.next == rep1);
Expect(currentChunk->getContentType() == "sample/aacinit");
delete currentChunk;
currentChunk = nullptr;
/* We must not allow repA init to unmatched repB X */
logic->repindex = 0;
events.reset();
currentChunk = tracker->getNextChunk(true);
Expect(currentChunk);
Expect(events.occured(TrackerEvent::Type::RepresentationSwitch) == false);
Expect(currentChunk->getContentType() == "sample/aac");
delete currentChunk;
currentChunk = nullptr;
/* should now allow switch on next segment */
events.reset();
currentChunk = tracker->getNextChunk(true);
Expect(currentChunk);
Expect(events.occured(TrackerEvent::Type::RepresentationSwitch) == true);
delete currentChunk;
currentChunk = nullptr;
} catch( ... ) {
delete currentChunk;
return 1;
}
return 0;
}
/****** check position/alignment with segment translation ******/
static int SegmentTracker_check_HLSseeks(BaseAdaptationSet *adaptSet,
DummyLogic *logic,
SegmentTracker *tracker,
SegmentTrackerListener &events)
{
const stime_t START = 1337;
Timescale timescale(100);
ChunkInterface *currentChunk = nullptr;
try
{
DummyHLSRepresentation *rep0 = new DummyHLSRepresentation(adaptSet, 1);
adaptSet->addRepresentation(rep0);
rep0->setID(ID("0"));
SegmentList *segmentList = nullptr;
try
{
segmentList = new SegmentList(rep0);
segmentList->addAttribute(new TimescaleAttr(timescale));
for(int i=0; i<10; i++)
{
Segment *seg = new Segment(rep0);
seg->setSequenceNumber(123 + i);
seg->startTime = START + 100 * i;
seg->duration = 100;
seg->setSourceUrl("sample/aac");
segmentList->addSegment(seg);
}
} catch (...) {
delete segmentList;
std::rethrow_exception(std::current_exception());
}
rep0->addAttribute(segmentList);
DummyHLSRepresentation *rep1 = new DummyHLSRepresentation(adaptSet, 3);
adaptSet->addRepresentation(rep1);
rep1->setID(ID("1"));
segmentList = nullptr;
try
{
segmentList = new SegmentList(rep1);
segmentList->addAttribute(new TimescaleAttr(timescale));
for(int i=0; i<4; i++)
{
Segment *seg = new Segment(rep1);
seg->setSequenceNumber(123 + i);
seg->startTime = START + 250 * i;
seg->duration = 250;
seg->setSourceUrl("sample/aac");
segmentList->addSegment(seg);
}
} catch (...) {
delete segmentList;
std::rethrow_exception(std::current_exception());
}
rep1->addAttribute(segmentList);
/* on rep0 */
Expect(tracker->setPositionByTime(VLC_TICK_0 + timescale.ToTime(START + 300), false, false) == true);
currentChunk = tracker->getNextChunk(true);
Expect(currentChunk);
Expect(events.occured(TrackerEvent::Type::PositionChange) == true);
Expect(events.positionchanged.resumeTime == timescale.ToTime(START + 300));
Expect(tracker->getPlaybackTime() == timescale.ToTime(START + 300));
delete currentChunk;
currentChunk = nullptr;
/* on rep1 */
logic->repindex = 1;
events.reset();
currentChunk = tracker->getNextChunk(true);
Expect(currentChunk);
Expect(events.occured(TrackerEvent::Type::RepresentationSwitch) == true);
Expect(tracker->getPlaybackTime() == timescale.ToTime(START + 250)); // FIXME
delete currentChunk;
currentChunk = nullptr;
} catch( ... ) {
delete currentChunk;
return 1;
}
return 0;
}
typedef decltype(SegmentTracker_check_formats) testfunc;
static int Prepare_test(testfunc func)
{
DummyConnectionManager *connManager = nullptr;
try
{
connManager = new DummyConnectionManager;
} catch( ... ) { return 1; }
connManager->data.insert(mapentry("sample/aac", std::vector<uint8_t>({ 0xFF, 0xF1, 0, 0 })));
connManager->data.insert(mapentry("sample/ac3", std::vector<uint8_t>({ 0x0b, 0x77, 0, 0, 0, 0 })));
connManager->data.insert(mapentry("sample/aacinit", std::vector<uint8_t>({ 0xFF, 0xF1, 0, 0 })));
SharedResources sharedRes(nullptr, nullptr, connManager);
DefaultBufferingLogic bufLogic;
SynchronizationReferences syncRefs;
BaseAdaptationSet *adaptSet = CreatePlaylistPeriodAdaptationSet();
if(!adaptSet)
return 1;
BasePlaylist *playlist = adaptSet->getPlaylist();
SegmentTracker *tracker;
DummyLogic *logic = nullptr;
try
{
logic = new DummyLogic();
tracker = new SegmentTracker(&sharedRes, logic, &bufLogic, adaptSet, &syncRefs);
} catch(...) {
delete playlist;
delete logic;
return 1;
}
SegmentTrackerListener events;
tracker->registerListener(&events);
int ret = func(adaptSet, logic, tracker, events);
delete tracker;
delete logic;
delete playlist;
return ret;
}
int SegmentTracker_test()
{
return
Prepare_test(SegmentTracker_check_formats) ||
Prepare_test(SegmentTracker_check_seeks) ||
Prepare_test(SegmentTracker_check_switches) ||
Prepare_test(SegmentTracker_check_HLSseeks) ||
0;
}
¤ Dauer der Verarbeitung: 0.14 Sekunden
¤
*© Formatika GbR, Deutschland
|
|