[vlc-commits] demux: adaptive: enable timeshift for live content
Francois Cartegnie
git at videolan.org
Sun May 26 16:58:39 CEST 2019
vlc | branch: master | Francois Cartegnie <fcvlcdev at free.fr> | Mon May 20 18:45:54 2019 +0200| [a2de7e4fe782a5a27b32797efa4b5e5ad3bc3a81] | committer: Francois Cartegnie
demux: adaptive: enable timeshift for live content
> http://git.videolan.org/gitweb.cgi/vlc.git/?a=commit;h=a2de7e4fe782a5a27b32797efa4b5e5ad3bc3a81
---
modules/demux/adaptive/PlaylistManager.cpp | 131 ++++++++++++++-------
modules/demux/adaptive/PlaylistManager.h | 5 +-
modules/demux/adaptive/SegmentTracker.cpp | 12 +-
modules/demux/adaptive/SegmentTracker.hpp | 1 +
modules/demux/adaptive/Streams.cpp | 9 ++
modules/demux/adaptive/Streams.hpp | 3 +-
.../demux/adaptive/playlist/SegmentInformation.cpp | 86 +++++++++++++-
.../demux/adaptive/playlist/SegmentInformation.hpp | 2 +
.../demux/adaptive/playlist/SegmentTemplate.cpp | 7 +-
modules/demux/adaptive/playlist/SegmentTemplate.h | 2 +-
modules/demux/smooth/SmoothManager.cpp | 2 -
modules/demux/smooth/playlist/SmoothSegment.cpp | 12 ++
12 files changed, 212 insertions(+), 60 deletions(-)
diff --git a/modules/demux/adaptive/PlaylistManager.cpp b/modules/demux/adaptive/PlaylistManager.cpp
index 9b298ece52..9a67b7c8cb 100644
--- a/modules/demux/adaptive/PlaylistManager.cpp
+++ b/modules/demux/adaptive/PlaylistManager.cpp
@@ -77,6 +77,10 @@ PlaylistManager::PlaylistManager( demux_t *p_demux_,
cached.i_length = 0;
cached.f_position = 0.0;
cached.i_time = VLC_TICK_INVALID;
+ cached.rangeStart = 0;
+ cached.rangeEnd = 0;
+ cached.rangeLength = 0;
+ cached.lastupdate = 0;
}
PlaylistManager::~PlaylistManager ()
@@ -399,13 +403,6 @@ vlc_tick_t PlaylistManager::getCurrentPlaybackTime() const
return demux.i_nzpcr;
}
-void PlaylistManager::pruneLiveStream()
-{
- vlc_tick_t minValidPos = getResumeTime();
- if(minValidPos != VLC_TICK_INVALID)
- playlist->pruneByPlaybackTime(minValidPos);
-}
-
bool PlaylistManager::reactivateStream(AbstractStream *stream)
{
return stream->reactivate(getResumeTime());
@@ -511,12 +508,6 @@ int PlaylistManager::doControl(int i_query, va_list args)
switch (i_query)
{
case DEMUX_CAN_SEEK:
- {
- vlc_mutex_locker locker(&cached.lock);
- *(va_arg (args, bool *)) = ! cached.b_live;
- break;
- }
-
case DEMUX_CAN_CONTROL_PACE:
*(va_arg (args, bool *)) = true;
break;
@@ -546,7 +537,7 @@ int PlaylistManager::doControl(int i_query, va_list args)
case DEMUX_GET_LENGTH:
{
vlc_mutex_locker locker(&cached.lock);
- if(cached.b_live)
+ if(cached.b_live && cached.i_length == 0)
return VLC_EGENERIC;
*(va_arg (args, vlc_tick_t *)) = cached.i_length;
break;
@@ -555,7 +546,7 @@ int PlaylistManager::doControl(int i_query, va_list args)
case DEMUX_GET_POSITION:
{
vlc_mutex_locker locker(&cached.lock);
- if(cached.b_live)
+ if(cached.b_live && cached.i_length == 0)
return VLC_EGENERIC;
*(va_arg (args, double *)) = cached.f_position;
break;
@@ -564,18 +555,31 @@ int PlaylistManager::doControl(int i_query, va_list args)
case DEMUX_SET_POSITION:
{
setBufferingRunState(false); /* stop downloader first */
+ vlc_mutex_locker locker(&cached.lock);
- const vlc_tick_t i_duration = playlist->duration.Get();
- if(i_duration == 0 || playlist->isLive())
+ vlc_tick_t i_start, i_duration;
+ if(cached.b_live)
+ {
+ i_duration = cached.i_length;
+ if(cached.rangeStart < 0)
+ i_start = vlc_tick_from_sec(time(NULL)) - cached.i_length;
+ else
+ i_start = cached.rangeStart;
+ }
+ else
+ {
+ i_duration = playlist->duration.Get();
+ i_start = getFirstPlaybackTime();
+ }
+
+ if(i_duration == 0)
{
setBufferingRunState(true);
return VLC_EGENERIC;
}
- int64_t time = i_duration * va_arg(args, double);
- time += getFirstPlaybackTime();
-
- if(!setPosition(time))
+ vlc_tick_t seektime = i_start + i_duration * va_arg(args, double);
+ if(!setPosition(seektime))
{
setBufferingRunState(true);
return VLC_EGENERIC;
@@ -589,11 +593,6 @@ int PlaylistManager::doControl(int i_query, va_list args)
case DEMUX_SET_TIME:
{
setBufferingRunState(false); /* stop downloader first */
- if(playlist->isLive())
- {
- setBufferingRunState(true);
- return VLC_EGENERIC;
- }
vlc_tick_t time = va_arg(args, vlc_tick_t);// + getFirstPlaybackTime();
if(!setPosition(time))
@@ -602,6 +601,7 @@ int PlaylistManager::doControl(int i_query, va_list args)
return VLC_EGENERIC;
}
+ vlc_mutex_locker locker(&cached.lock);
demux.i_nzpcr = VLC_TICK_INVALID;
setBufferingRunState(true);
break;
@@ -691,31 +691,72 @@ void * PlaylistManager::managerThread(void *opaque)
void PlaylistManager::updateControlsPosition()
{
vlc_mutex_locker locker(&cached.lock);
- if(playlist->isLive())
- {
- cached.b_live = true;
- cached.i_length = 0;
- }
- else
- {
- cached.b_live = false;
- cached.i_length = playlist->duration.Get();
- }
- if(cached.i_length == 0)
+ time_t now = time(NULL);
+ if(now - cached.lastupdate < 1)
+ return;
+ cached.lastupdate = now;
+
+ cached.i_time = getCurrentPlaybackTime();
+ cached.b_live = playlist->isLive();
+ if(cached.b_live)
{
- cached.f_position = 0.0;
+ std::vector<AbstractStream *>::iterator it;
+ for(it=streams.begin(); it!=streams.end(); ++it)
+ {
+ AbstractStream *st = *it;
+ if(st->isValid() && !st->isDisabled() && st->isSelected())
+ {
+ if(st->getMediaPlaybackRange(&cached.rangeStart, &cached.rangeEnd,
+ &cached.rangeLength))
+ break;
+ }
+ }
+
+ if(cached.rangeStart != cached.rangeEnd)
+ {
+ if(cached.rangeStart < 0) /* Live template. Range start = now() - buffering depth */
+ cached.i_length = cached.rangeLength;
+ else
+ cached.i_length = cached.rangeEnd - cached.rangeStart;
+ }
+
+ vlc_tick_t start, end;
+ if(cached.rangeStart < 0) /* Live template. Range start = now() - buffering depth */
+ {
+ end = vlc_tick_from_sec(now);
+ start = end - cached.i_length;
+ }
+ else
+ {
+ end = cached.rangeEnd;
+ start = cached.rangeStart;
+ }
+
+ const vlc_tick_t currentTime = getCurrentPlaybackTime();
+ if(currentTime > start && currentTime <= end && cached.i_length)
+ {
+ cached.f_position = ((double)(currentTime - start)) / cached.i_length;
+ }
+ else
+ {
+ cached.f_position = 0.0;
+ }
}
else
{
- const vlc_tick_t i_length = getCurrentPlaybackTime() - getFirstPlaybackTime();
- cached.f_position = (double) i_length / cached.i_length;
+ cached.i_length = playlist->duration.Get();
+ cached.i_time -= getFirstPlaybackTime();
+ if(cached.i_length)
+ {
+ const vlc_tick_t i_length = getCurrentPlaybackTime() - getFirstPlaybackTime();
+ cached.f_position = (double) i_length / cached.i_length;
+ }
+ else
+ {
+ cached.f_position = 0.0;
+ }
}
-
- vlc_tick_t i_time = getCurrentPlaybackTime();
- if(!playlist->isLive())
- i_time -= getFirstPlaybackTime();
- cached.i_time = i_time;
}
AbstractAdaptationLogic *PlaylistManager::createLogic(AbstractAdaptationLogic::LogicType type, AbstractConnectionManager *conn)
diff --git a/modules/demux/adaptive/PlaylistManager.h b/modules/demux/adaptive/PlaylistManager.h
index 6d363f9ca9..03f799ac1d 100644
--- a/modules/demux/adaptive/PlaylistManager.h
+++ b/modules/demux/adaptive/PlaylistManager.h
@@ -81,7 +81,6 @@ namespace adaptive
virtual vlc_tick_t getFirstPlaybackTime() const;
vlc_tick_t getCurrentPlaybackTime() const;
- void pruneLiveStream();
virtual bool reactivateStream(AbstractStream *);
bool setupPeriod();
void unsetPeriod();
@@ -123,6 +122,10 @@ namespace adaptive
vlc_tick_t i_time;
double f_position;
mutable vlc_mutex_t lock;
+ vlc_tick_t rangeStart;
+ vlc_tick_t rangeEnd;
+ vlc_tick_t rangeLength;
+ time_t lastupdate;
} cached;
private:
diff --git a/modules/demux/adaptive/SegmentTracker.cpp b/modules/demux/adaptive/SegmentTracker.cpp
index d9d06296d8..583d977681 100644
--- a/modules/demux/adaptive/SegmentTracker.cpp
+++ b/modules/demux/adaptive/SegmentTracker.cpp
@@ -217,11 +217,7 @@ SegmentChunk * SegmentTracker::getNextChunk(bool switch_allowed,
}
if(b_updated)
- {
- if(!rep->consistentSegmentNumber())
- curRepresentation->pruneBySegmentNumber(curNumber);
curRepresentation->scheduleNextUpdate(next);
- }
if(rep->getStreamFormat() != format)
{
@@ -358,6 +354,14 @@ vlc_tick_t SegmentTracker::getPlaybackTime() const
return 0;
}
+bool SegmentTracker::getMediaPlaybackRange(vlc_tick_t *start, vlc_tick_t *end,
+ vlc_tick_t *length) const
+{
+ if(!curRepresentation)
+ return false;
+ return curRepresentation->getMediaPlaybackRange(start, end, length);
+}
+
vlc_tick_t SegmentTracker::getMinAheadTime() const
{
BaseRepresentation *rep = curRepresentation;
diff --git a/modules/demux/adaptive/SegmentTracker.hpp b/modules/demux/adaptive/SegmentTracker.hpp
index a7a837ce12..cb035d6200 100644
--- a/modules/demux/adaptive/SegmentTracker.hpp
+++ b/modules/demux/adaptive/SegmentTracker.hpp
@@ -129,6 +129,7 @@ namespace adaptive
bool setPositionByTime(vlc_tick_t, bool, bool);
void setPositionByNumber(uint64_t, bool);
vlc_tick_t getPlaybackTime() const; /* Current segment start time if selected */
+ bool getMediaPlaybackRange(vlc_tick_t *, vlc_tick_t *, vlc_tick_t *) const;
vlc_tick_t getMinAheadTime() const;
void notifyBufferingState(bool) const;
void notifyBufferingLevel(vlc_tick_t, vlc_tick_t, vlc_tick_t) const;
diff --git a/modules/demux/adaptive/Streams.cpp b/modules/demux/adaptive/Streams.cpp
index 26710e3338..1ad8815d56 100644
--- a/modules/demux/adaptive/Streams.cpp
+++ b/modules/demux/adaptive/Streams.cpp
@@ -213,6 +213,7 @@ bool AbstractStream::isSelected() const
bool AbstractStream::reactivate(vlc_tick_t basetime)
{
+ vlc_mutex_locker locker(&lock);
if(setPosition(basetime, false))
{
setDisabled(false);
@@ -275,11 +276,13 @@ void AbstractStream::setDisabled(bool b)
bool AbstractStream::isValid() const
{
+ vlc_mutex_locker locker(&lock);
return valid;
}
bool AbstractStream::isDisabled() const
{
+ vlc_mutex_locker locker(&lock);
return disabled;
}
@@ -559,6 +562,12 @@ vlc_tick_t AbstractStream::getPlaybackTime() const
return segmentTracker->getPlaybackTime();
}
+bool AbstractStream::getMediaPlaybackRange(vlc_tick_t *start, vlc_tick_t *end,
+ vlc_tick_t *length) const
+{
+ return segmentTracker->getMediaPlaybackRange(start, end, length);
+}
+
void AbstractStream::runUpdates()
{
if(valid && !disabled)
diff --git a/modules/demux/adaptive/Streams.hpp b/modules/demux/adaptive/Streams.hpp
index 919a65dfc9..3b3d88d61b 100644
--- a/modules/demux/adaptive/Streams.hpp
+++ b/modules/demux/adaptive/Streams.hpp
@@ -67,7 +67,6 @@ namespace adaptive
int esCount() const;
bool isSelected() const;
virtual bool reactivate(vlc_tick_t);
- void setDisabled(bool);
bool isDisabled() const;
bool isValid() const;
typedef enum {
@@ -90,6 +89,7 @@ namespace adaptive
bool decodersDrained();
virtual bool setPosition(vlc_tick_t, bool);
vlc_tick_t getPlaybackTime() const;
+ bool getMediaPlaybackRange(vlc_tick_t *, vlc_tick_t *, vlc_tick_t *) const;
void runUpdates();
/* Used by demuxers fake streams */
@@ -102,6 +102,7 @@ namespace adaptive
protected:
bool seekAble() const;
+ void setDisabled(bool);
virtual void setTimeOffset(vlc_tick_t);
virtual block_t *checkBlock(block_t *, bool) = 0;
AbstractDemuxer * createDemux(const StreamFormat &);
diff --git a/modules/demux/adaptive/playlist/SegmentInformation.cpp b/modules/demux/adaptive/playlist/SegmentInformation.cpp
index 6b10af1bb5..7fe3d07296 100644
--- a/modules/demux/adaptive/playlist/SegmentInformation.cpp
+++ b/modules/demux/adaptive/playlist/SegmentInformation.cpp
@@ -162,6 +162,22 @@ std::size_t SegmentInformation::getAllSegments(std::vector<ISegment *> &retSegme
return retSegments.size();
}
+uint64_t SegmentInformation::getLiveSegmentNumberByTime(uint64_t def, vlc_tick_t t) const
+{
+ if( mediaSegmentTemplate )
+ {
+ if( mediaSegmentTemplate->duration.Get() )
+ {
+ return mediaSegmentTemplate->getLiveTemplateNumber(t);
+ }
+ }
+
+ if(parent)
+ return parent->getLiveStartSegmentNumber(def);
+ else
+ return def;
+}
+
uint64_t SegmentInformation::getLiveStartSegmentNumber(uint64_t def) const
{
const vlc_tick_t i_max_buffering = getPlaylist()->getMaxBuffering() +
@@ -213,7 +229,7 @@ uint64_t SegmentInformation::getLiveStartSegmentNumber(uint64_t def) const
i_delay = getPlaylist()->getMinBuffering();
const uint64_t startnumber = mediaSegmentTemplate->inheritStartNumber();
- end = mediaSegmentTemplate->getCurrentLiveTemplateNumber();
+ end = mediaSegmentTemplate->getLiveTemplateNumber(vlc_tick_from_sec(time(NULL)));
const uint64_t count = timescale.ToScaled( i_delay ) / mediaSegmentTemplate->duration.Get();
if( startnumber + count >= end )
@@ -271,6 +287,72 @@ uint64_t SegmentInformation::getLiveStartSegmentNumber(uint64_t def) const
return def;
}
+bool SegmentInformation::getMediaPlaybackRange(vlc_tick_t *rangeBegin,
+ vlc_tick_t *rangeEnd,
+ vlc_tick_t *rangeLength) const
+{
+ if( mediaSegmentTemplate )
+ {
+ const Timescale timescale = mediaSegmentTemplate->inheritTimescale();
+ const SegmentTimeline *timeline = mediaSegmentTemplate->inheritSegmentTimeline();
+ if( timeline )
+ {
+ stime_t startTime, endTime, duration;
+ if(!timeline->getScaledPlaybackTimeDurationBySegmentNumber(timeline->minElementNumber(),
+ &startTime, &duration) ||
+ !timeline->getScaledPlaybackTimeDurationBySegmentNumber(timeline->maxElementNumber(),
+ &endTime, &duration))
+ return false;
+
+ *rangeBegin = timescale.ToTime(startTime);
+ *rangeEnd = timescale.ToTime(endTime+duration);
+ *rangeLength = timescale.ToTime(timeline->getTotalLength());
+ return true;
+ }
+ /* Else compute, current time and timeshiftdepth based */
+ else if( mediaSegmentTemplate->duration.Get() )
+ {
+ *rangeEnd = 0;
+ *rangeBegin = -1 * getPlaylist()->timeShiftBufferDepth.Get();
+ *rangeLength = getPlaylist()->timeShiftBufferDepth.Get();
+ return true;
+ }
+ }
+ else if ( segmentList && !segmentList->getSegments().empty() )
+ {
+ const Timescale timescale = segmentList->inheritTimescale();
+ const std::vector<ISegment *> list = segmentList->getSegments();
+
+ const ISegment *back = list.back();
+ const stime_t startTime = list.front()->startTime.Get();
+ const stime_t endTime = back->startTime.Get() + back->duration.Get();
+ *rangeBegin = timescale.ToTime(startTime);
+ *rangeEnd = timescale.ToTime(endTime);
+ *rangeLength = timescale.ToTime(segmentList->getTotalLength());
+ return true;
+ }
+ else if( segmentBase )
+ {
+ const std::vector<ISegment *> list = segmentBase->subSegments();
+ if(list.empty())
+ return false;
+
+ const Timescale timescale = inheritTimescale();
+ const ISegment *back = list.back();
+ const stime_t startTime = list.front()->startTime.Get();
+ const stime_t endTime = back->startTime.Get() + back->duration.Get();
+ *rangeBegin = timescale.ToTime(startTime);
+ *rangeEnd = timescale.ToTime(endTime);
+ *rangeLength = 0;
+ return true;
+ }
+
+ if(parent)
+ return parent->getMediaPlaybackRange(rangeBegin, rangeEnd, rangeLength);
+ else
+ return false;
+}
+
/* Returns wanted segment, or next in sequence if not found */
ISegment * SegmentInformation::getNextSegment(SegmentInfoType type, uint64_t i_pos,
uint64_t *pi_newpos, bool *pb_gap) const
@@ -372,7 +454,7 @@ bool SegmentInformation::getSegmentNumberByTime(vlc_tick_t time, uint64_t *ret)
{
if( getPlaylist()->isLive() )
{
- *ret = getLiveStartSegmentNumber( mediaSegmentTemplate->inheritStartNumber() );
+ *ret = getLiveSegmentNumberByTime( mediaSegmentTemplate->inheritStartNumber(), time );
}
else
{
diff --git a/modules/demux/adaptive/playlist/SegmentInformation.hpp b/modules/demux/adaptive/playlist/SegmentInformation.hpp
index 7970ce0e83..1be759ea43 100644
--- a/modules/demux/adaptive/playlist/SegmentInformation.hpp
+++ b/modules/demux/adaptive/playlist/SegmentInformation.hpp
@@ -82,7 +82,9 @@ namespace adaptive
ISegment * getNextSegment(SegmentInfoType, uint64_t, uint64_t *, bool *) const;
bool getSegmentNumberByTime(vlc_tick_t, uint64_t *) const;
bool getPlaybackTimeDurationBySegmentNumber(uint64_t, vlc_tick_t *, vlc_tick_t *) const;
+ uint64_t getLiveSegmentNumberByTime(uint64_t, vlc_tick_t) const;
uint64_t getLiveStartSegmentNumber(uint64_t) const;
+ bool getMediaPlaybackRange(vlc_tick_t *, vlc_tick_t *, vlc_tick_t *) const;
virtual void mergeWith(SegmentInformation *, vlc_tick_t);
virtual void mergeWithTimeline(SegmentTimeline *); /* ! don't use with global merge */
virtual void pruneBySegmentNumber(uint64_t);
diff --git a/modules/demux/adaptive/playlist/SegmentTemplate.cpp b/modules/demux/adaptive/playlist/SegmentTemplate.cpp
index c33151bc8c..cd45094810 100644
--- a/modules/demux/adaptive/playlist/SegmentTemplate.cpp
+++ b/modules/demux/adaptive/playlist/SegmentTemplate.cpp
@@ -148,7 +148,7 @@ SegmentTimeline * MediaSegmentTemplate::inheritSegmentTimeline() const
return NULL;
}
-uint64_t MediaSegmentTemplate::getCurrentLiveTemplateNumber() const
+uint64_t MediaSegmentTemplate::getLiveTemplateNumber(vlc_tick_t playbacktime) const
{
uint64_t number = inheritStartNumber();
/* live streams / templated */
@@ -156,11 +156,10 @@ uint64_t MediaSegmentTemplate::getCurrentLiveTemplateNumber() const
if(dur)
{
/* compute, based on current time */
- const time_t playbacktime = time(NULL);
const Timescale timescale = inheritTimescale();
time_t streamstart = parentSegmentInformation->getPlaylist()->availabilityStartTime.Get();
streamstart += parentSegmentInformation->getPeriodStart();
- stime_t elapsed = timescale.ToScaled(vlc_tick_from_sec(playbacktime - streamstart));
+ stime_t elapsed = timescale.ToScaled(playbacktime - vlc_tick_from_sec(streamstart));
number += elapsed / dur;
}
@@ -172,7 +171,7 @@ stime_t MediaSegmentTemplate::getMinAheadScaledTime(uint64_t number) const
if( segmentTimeline )
return segmentTimeline->getMinAheadScaledTime(number);
- uint64_t current = getCurrentLiveTemplateNumber();
+ uint64_t current = getLiveTemplateNumber(vlc_tick_from_sec(time(NULL)));
return (current - number) * inheritDuration();
}
diff --git a/modules/demux/adaptive/playlist/SegmentTemplate.h b/modules/demux/adaptive/playlist/SegmentTemplate.h
index 6fb669a179..74d5532363 100644
--- a/modules/demux/adaptive/playlist/SegmentTemplate.h
+++ b/modules/demux/adaptive/playlist/SegmentTemplate.h
@@ -55,7 +55,7 @@ namespace adaptive
void setSegmentTimeline( SegmentTimeline * );
void mergeWith( MediaSegmentTemplate *, vlc_tick_t );
virtual uint64_t getSequenceNumber() const; /* reimpl */
- uint64_t getCurrentLiveTemplateNumber() const;
+ uint64_t getLiveTemplateNumber(vlc_tick_t) const;
stime_t getMinAheadScaledTime(uint64_t) const;
void pruneByPlaybackTime(vlc_tick_t);
size_t pruneBySequenceNumber(uint64_t);
diff --git a/modules/demux/smooth/SmoothManager.cpp b/modules/demux/smooth/SmoothManager.cpp
index 7a3f243300..e5e0f0c84b 100644
--- a/modules/demux/smooth/SmoothManager.cpp
+++ b/modules/demux/smooth/SmoothManager.cpp
@@ -174,8 +174,6 @@ bool SmoothManager::updatePlaylist(bool forcemanifest)
else return false;
}
- pruneLiveStream();
-
return true;
}
diff --git a/modules/demux/smooth/playlist/SmoothSegment.cpp b/modules/demux/smooth/playlist/SmoothSegment.cpp
index 24d0665199..0b317e1b43 100644
--- a/modules/demux/smooth/playlist/SmoothSegment.cpp
+++ b/modules/demux/smooth/playlist/SmoothSegment.cpp
@@ -50,6 +50,18 @@ void SmoothSegmentChunk::onDownload(block_t **pp_block)
IndexReader br(rep->getPlaylist()->getVLCObject());
br.parseIndex(*pp_block, rep);
+
+ /* If timeshift depth is present, we use it for expiring segments
+ as we never update playlist itself */
+ if(rep->getPlaylist()->timeShiftBufferDepth.Get())
+ {
+ vlc_tick_t start, end, length;
+ if(rep->getMediaPlaybackRange(&start, &end, &length))
+ {
+ start = std::max(start, end - rep->getPlaylist()->timeShiftBufferDepth.Get());
+ rep->pruneByPlaybackTime(start);
+ }
+ }
}
SmoothSegment::SmoothSegment(SegmentInformation *parent) :
More information about the vlc-commits
mailing list