/* * Streams.cpp ***************************************************************************** * Copyright (C) 2014 - 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 "Streams.hpp" #include "logic/AbstractAdaptationLogic.h" #include "http/HTTPConnection.hpp" #include "http/HTTPConnectionManager.h" #include "playlist/BaseAdaptationSet.h" #include "playlist/BaseRepresentation.h" #include "playlist/SegmentChunk.hpp" #include "plumbing/SourceStream.hpp" #include "plumbing/CommandsQueue.hpp" #include "tools/Debug.hpp" #include #include #include using namespace adaptive; using namespace adaptive::http; AbstractStream::AbstractStream(demux_t * demux_) { p_realdemux = demux_; format = StreamFormat::Type::Unknown; currentChunk = nullptr; eof = false; valid = true; disabled = false; contiguous = true; segmentgap = false; discontinuity = false; needrestart = false; inrestart = false; demuxfirstchunk = false; segmentTracker = nullptr; demuxersource = nullptr; demuxer = nullptr; fakeesout = nullptr; notfound_sequence = 0; mightalwaysstartfromzero = false; last_buffer_status = BufferingStatus::Lessthanmin; currentrep.width = 0; currentrep.height = 0; vlc_mutex_init(&lock); } bool AbstractStream::init(const StreamFormat &format_, SegmentTracker *tracker) { /* Don't even try if not supported or already init */ if(format_ == StreamFormat::Type::Unsupported || demuxersource) return false; demuxersource = new (std::nothrow) BufferedChunksSourceStream( VLC_OBJECT(p_realdemux), this ); if(demuxersource) { CommandsFactory *factory = new (std::nothrow) CommandsFactory(); AbstractCommandsQueue *commandsqueue = new (std::nothrow) CommandsQueue(); if(factory && commandsqueue) { fakeesout = new (std::nothrow) FakeESOut(p_realdemux->out, commandsqueue, factory); if(fakeesout) { /* All successful */ fakeesout->setExtraInfoProvider( this ); const Role & streamRole = tracker->getStreamRole(); if(streamRole.isDefault() && streamRole.autoSelectable()) fakeesout->setPriority(ES_PRIORITY_MIN + 10); else if(!streamRole.autoSelectable()) fakeesout->setPriority(ES_PRIORITY_NOT_DEFAULTABLE); format = format_; segmentTracker = tracker; segmentTracker->registerListener(this); segmentTracker->notifyBufferingState(true); if(mightalwaysstartfromzero) fakeesout->setExpectedTimestamp(VLC_TICK_0 + segmentTracker->getPlaybackTime()); declaredCodecs(); return true; } } delete factory; delete commandsqueue; delete demuxersource; } return false; } AbstractStream::~AbstractStream() { delete currentChunk; if(segmentTracker) segmentTracker->notifyBufferingState(false); delete segmentTracker; delete demuxer; delete demuxersource; delete fakeesout; vlc_mutex_destroy(&lock); } void AbstractStream::prepareRestart(bool b_discontinuity) { if(demuxer) { /* Enqueue Del Commands for all current ES */ demuxer->drain(); fakeEsOut()->resetTimestamps(); /* Enqueue Del Commands for all current ES */ fakeEsOut()->scheduleAllForDeletion(); if(b_discontinuity) fakeEsOut()->schedulePCRReset(); fakeEsOut()->commandsQueue()->Commit(); /* ignoring demuxer's own Del commands */ fakeEsOut()->commandsQueue()->setDrop(true); delete demuxer; fakeEsOut()->commandsQueue()->setDrop(false); demuxer = nullptr; } } bool AbstractStream::resetForNewPosition(vlc_tick_t seekMediaTime) { // clear eof flag before restartDemux() to prevent readNextBlock() fail eof = false; demuxfirstchunk = true; notfound_sequence = 0; last_buffer_status = BufferingStatus::Lessthanmin; inrestart = false; needrestart = false; discontinuity = false; if(!demuxer || demuxer->needsRestartOnSeek()) /* needs (re)start */ { delete currentChunk; currentChunk = nullptr; needrestart = false; segmentgap = false; fakeEsOut()->resetTimestamps(); fakeEsOut()->commandsQueue()->Abort( true ); startTimeContext = SegmentTimes(); currentTimeContext = SegmentTimes(); prevEndTimeContext = SegmentTimes(); currentChunk = getNextChunk(); if(mightalwaysstartfromzero) fakeEsOut()->setExpectedTimestamp(VLC_TICK_0 + seekMediaTime); if( !restartDemux() ) { msg_Info(p_realdemux, "Restart demux failed"); eof = true; valid = false; return false; } else { fakeEsOut()->commandsQueue()->setEOF(false); } } else fakeEsOut()->commandsQueue()->Abort( true ); return true; } void AbstractStream::setLanguage(const std::string &lang) { language = lang; } void AbstractStream::setDescription(const std::string &desc) { description = desc; } vlc_tick_t AbstractStream::getMinAheadTime() const { if(!segmentTracker) return 0; return segmentTracker->getMinAheadTime(); } Times AbstractStream::getFirstTimes() const { vlc_mutex_locker locker(&lock); if(!valid || disabled) return Times(); Times times = fakeEsOut()->commandsQueue()->getFirstTimes(); if(times.continuous == VLC_TICK_INVALID) times = fakeEsOut()->commandsQueue()->getPCR(); return times; } int AbstractStream::esCount() const { return fakeEsOut()->esCount(); } bool AbstractStream::seekAble() const { bool restarting = fakeEsOut()->restarting(); bool draining = fakeEsOut()->commandsQueue()->isDraining(); bool eof = fakeEsOut()->commandsQueue()->isEOF(); msg_Dbg(p_realdemux, "demuxer %p, fakeesout restarting %d, " "discontinuity %d, commandsqueue draining %d, commandsqueue eof %d", static_cast(demuxer), restarting, discontinuity, draining, eof); if(!valid || restarting || discontinuity || (!eof && draining)) { msg_Warn(p_realdemux, "not seekable"); return false; } else { return true; } } bool AbstractStream::isSelected() const { return fakeEsOut()->hasSelectedEs(); } AbstractStream::StreamPosition::StreamPosition() { number = std::numeric_limits::max(); pos = -1; } bool AbstractStream::reactivate(const StreamPosition &pos) { vlc_mutex_locker locker(&lock); if(setPosition(pos, false)) { setDisabled(false); return true; } else { eof = true; /* can't reactivate */ return false; } } bool AbstractStream::startDemux() { if(demuxer) return false; if(!currentChunk) { segmentgap = false; currentChunk = getNextChunk(); needrestart = false; discontinuity = false; } demuxersource->Reset(); demuxfirstchunk = true; demuxer = createDemux(format); if(!demuxer && format != StreamFormat()) msg_Err(p_realdemux, "Failed to create demuxer %p %s", (void *)demuxer, format.str().c_str()); return !!demuxer; } bool AbstractStream::restartDemux() { bool b_ret = true; if(!demuxer) { fakeesout->recycleAll(); b_ret = startDemux(); } else if(demuxer->needsRestartOnSeek()) { inrestart = true; /* Push all ES as recycling candidates */ fakeEsOut()->recycleAll(); /* Restart with ignoring es_Del pushes to queue when terminating demux */ fakeEsOut()->commandsQueue()->setDrop(true); demuxer->destroy(); fakeEsOut()->commandsQueue()->setDrop(false); b_ret = demuxer->create(); inrestart = false; } else { fakeEsOut()->commandsQueue()->Commit(); } return b_ret; } void AbstractStream::setDisabled(bool b) { if(disabled != b) segmentTracker->notifyBufferingState(!b); disabled = b; } bool AbstractStream::isValid() const { vlc_mutex_locker locker(&lock); return valid; } bool AbstractStream::isDisabled() const { vlc_mutex_locker locker(&lock); return disabled; } void AbstractStream::setLivePause(bool b) { vlc_mutex_locker locker(&lock); if(!b) { segmentTracker->setPosition(segmentTracker->getStartPosition(), !demuxer || demuxer->needsRestartOnSeek()); } } bool AbstractStream::decodersDrained() { return fakeEsOut()->decodersDrained(); } vlc_tick_t AbstractStream::getDemuxedAmount(Times from) const { vlc_tick_t i_demuxed = fakeEsOut()->commandsQueue()->getDemuxedAmount(from).continuous; if(contiguous) { vlc_tick_t i_media_demuxed = fakeEsOut()->commandsQueue()->getDemuxedMediaAmount(from).segment.media; if(i_media_demuxed > i_demuxed) i_demuxed = i_media_demuxed; } return i_demuxed; } AbstractStream::BufferingStatus AbstractStream::getBufferAndStatus(const Times &deadline, vlc_tick_t i_min_buffering, vlc_tick_t i_max_buffering, vlc_tick_t *pi_demuxed) { if(last_buffer_status == BufferingStatus::End) return BufferingStatus::End; *pi_demuxed = getDemuxedAmount(deadline); if(*pi_demuxed < i_max_buffering) /* need to read more */ { if(*pi_demuxed < i_min_buffering) return BufferingStatus::Lessthanmin; /* high prio */ return BufferingStatus::Ongoing; } return BufferingStatus::Full; } AbstractStream::BufferingStatus AbstractStream::bufferize(Times deadline, vlc_tick_t i_min_buffering, vlc_tick_t i_extra_buffering, vlc_tick_t i_target_buffering, bool b_keep_alive) { last_buffer_status = doBufferize(deadline, i_min_buffering, i_extra_buffering, i_target_buffering, b_keep_alive); return last_buffer_status; } AbstractStream::BufferingStatus AbstractStream::doBufferize(Times deadline, vlc_tick_t i_min_buffering, vlc_tick_t i_max_buffering, vlc_tick_t i_target_buffering, bool b_keep_alive) { vlc_mutex_lock(&lock); /* Ensure it is configured */ if(!segmentTracker || !valid) { vlc_mutex_unlock(&lock); return BufferingStatus::End; } /* Disable streams that are not selected (alternate streams) */ if(esCount() && !isSelected() && !fakeEsOut()->restarting() && !b_keep_alive) { setDisabled(true); segmentTracker->reset(); fakeEsOut()->commandsQueue()->Abort(false); msg_Dbg(p_realdemux, "deactivating %s stream %s", format.str().c_str(), description.c_str()); vlc_mutex_unlock(&lock); return BufferingStatus::End; } if(fakeEsOut()->commandsQueue()->isDraining()) { vlc_mutex_unlock(&lock); return BufferingStatus::Suspended; } segmentTracker->setStartPosition(); /* Reached end of live playlist */ if(!segmentTracker->bufferingAvailable()) { vlc_mutex_unlock(&lock); return BufferingStatus::Suspended; } if(!contiguous) { if(!fakeEsOut()->hasSynchronizationReference()) { if(!demuxer) { /* We always need a prepared chunk info for querying a syncref */ if(!currentChunk) { currentChunk = getNextChunk(); if(!currentChunk) { vlc_mutex_unlock(&lock); return BufferingStatus::End; } segmentgap = false; needrestart = false; discontinuity = false; } } SynchronizationReference r; if(segmentTracker->getSynchronizationReference(currentSequence, startTimeContext.media, r)) { fakeEsOut()->setSynchronizationReference(r); } else { msg_Dbg(p_realdemux, "Waiting sync reference for seq %ld", currentSequence); vlc_mutex_unlock(&lock); return BufferingStatus::Suspended; } } } if(!demuxer) { if(!startDemux()) { valid = false; /* Prevent further retries */ fakeEsOut()->commandsQueue()->setEOF(true); vlc_mutex_unlock(&lock); return BufferingStatus::End; } } vlc_tick_t i_demuxed = fakeEsOut()->commandsQueue()->getDemuxedAmount(deadline).continuous; if(!contiguous && prevEndTimeContext.media != VLC_TICK_INVALID && deadline.segment.media != VLC_TICK_INVALID) { vlc_tick_t i_mediaamount = fakeEsOut()->commandsQueue()->getDemuxedMediaAmount(deadline).segment.media; if(i_mediaamount > i_demuxed) i_demuxed = i_mediaamount; } segmentTracker->notifyBufferingLevel(i_min_buffering, i_max_buffering, i_demuxed, i_target_buffering); if(i_demuxed < i_max_buffering) /* not already demuxed */ { Times extdeadline = fakeEsOut()->commandsQueue()->getBufferingLevel(); extdeadline.offsetBy((i_max_buffering - i_demuxed) / 4); Times newdeadline = deadline; newdeadline.offsetBy(CLOCK_FREQ); if(extdeadline.continuous < newdeadline.continuous) deadline = extdeadline; else deadline = newdeadline; /* need to read, demuxer still buffering, ... */ vlc_mutex_unlock(&lock); Demuxer::Status demuxStatus = demuxer->demux(deadline.continuous); fakeEsOut()->scheduleNecessaryMilestone(); vlc_mutex_lock(&lock); if(demuxStatus != Demuxer::Status::Success) { if(discontinuity || needrestart) { msg_Dbg(p_realdemux, "Restarting demuxer %d %d", needrestart, discontinuity); prepareRestart(discontinuity); if(discontinuity) { msg_Dbg(p_realdemux, "Draining on discontinuity"); fakeEsOut()->commandsQueue()->setDraining(); fakeEsOut()->setSegmentStartTimes(startTimeContext); assert(startTimeContext.media); } assert(startTimeContext.media); if(!fakeEsOut()->hasSegmentStartTimes()) fakeEsOut()->setSegmentStartTimes(startTimeContext); if(!fakeEsOut()->hasSynchronizationReference()) { SynchronizationReference r(currentSequence, Times()); fakeEsOut()->setSynchronizationReference(r); } discontinuity = false; needrestart = false; vlc_mutex_unlock(&lock); return BufferingStatus::Ongoing; } fakeEsOut()->commandsQueue()->setEOF(true); vlc_mutex_unlock(&lock); return BufferingStatus::End; } if(deadline.continuous != VLC_TICK_INVALID) { i_demuxed = fakeEsOut()->commandsQueue()->getDemuxedAmount(deadline).continuous; segmentTracker->notifyBufferingLevel(i_min_buffering, i_max_buffering, i_demuxed, i_target_buffering); } else { /* On initial pass, there's no demux time known, we need to fake it */ if(fakeEsOut()->commandsQueue()->getBufferingLevel().continuous != VLC_TICK_INVALID) i_demuxed = i_min_buffering; } } vlc_mutex_unlock(&lock); Times first = fakeEsOut()->commandsQueue()->getFirstTimes(); if(contiguous && first.continuous != VLC_TICK_INVALID && first.segment.demux != VLC_TICK_INVALID) segmentTracker->updateSynchronizationReference(currentSequence, first); if(i_demuxed < i_max_buffering) /* need to read more */ { if(i_demuxed < i_min_buffering) return BufferingStatus::Lessthanmin; /* high prio */ return BufferingStatus::Ongoing; } return BufferingStatus::Full; } AbstractStream::Status AbstractStream::dequeue(Times deadline, Times *times) { vlc_mutex_locker locker(&lock); if(fakeEsOut()->commandsQueue()->isDraining()) { AdvDebug(vlc_tick_t pcrvalue = fakeEsOut()->commandsQueue()->getPCR().continuous; vlc_tick_t dtsvalue = fakeEsOut()->commandsQueue()->getFirstTimes().continuous; vlc_tick_t bufferingLevel = fakeEsOut()->commandsQueue()->getBufferingLevel().continuous; msg_Dbg(p_realdemux, "Stream pcr %" PRId64 " dts %" PRId64 " deadline %" PRId64 " buflevel %" PRId64 "(+%" PRId64 ") [DRAINING] :%s", pcrvalue, dtsvalue, deadline.continuous, bufferingLevel, pcrvalue ? bufferingLevel - pcrvalue : 0, description.c_str())); *times = fakeEsOut()->commandsQueue()->Process(deadline); if(!fakeEsOut()->commandsQueue()->isEmpty()) return Status::Demuxed; if(!fakeEsOut()->commandsQueue()->isEOF()) { fakeEsOut()->commandsQueue()->Abort(true); /* reset buffering level and flags */ return Status::Discontinuity; } } if(!valid || disabled || fakeEsOut()->commandsQueue()->isEOF()) { *times = deadline; return Status::Eof; } vlc_tick_t bufferingLevel = fakeEsOut()->commandsQueue()->getBufferingLevel().continuous; AdvDebug(vlc_tick_t pcrvalue = fakeEsOut()->commandsQueue()->getPCR().continuous; vlc_tick_t dtsvalue = fakeEsOut()->commandsQueue()->getFirstTimes().continuous; msg_Dbg(p_realdemux, "Stream pcr %" PRId64 " dts %" PRId64 " deadline %" PRId64 " buflevel %" PRId64 "(+%" PRId64 "): %s", pcrvalue, dtsvalue, deadline.continuous, bufferingLevel, pcrvalue ? bufferingLevel - pcrvalue : 0, description.c_str())); if(deadline.continuous <= bufferingLevel) /* demuxed */ { *times = fakeEsOut()->commandsQueue()->Process(deadline); return Status::Demuxed; } else if(!contiguous && fakeEsOut()->commandsQueue()->getDemuxedMediaAmount(deadline).segment.media > 0) { *times = deadline; fakeEsOut()->commandsQueue()->Process(Times()); /* handle untimed events (es add) */ return Status::Demuxed; } return Status::Buffering; } ChunkInterface * AbstractStream::getNextChunk() const { const bool b_restarting = fakeEsOut()->restarting(); ChunkInterface *ck = segmentTracker->getNextChunk(!b_restarting); if(ck && !fakeEsOut()->hasSegmentStartTimes()) fakeEsOut()->setSegmentStartTimes(startTimeContext); if(ck && !fakeEsOut()->hasSynchronizationReference()) { assert(fakeEsOut()->hasSegmentStartTimes()); SynchronizationReference r; if(segmentTracker->getSynchronizationReference(currentSequence, startTimeContext.media, r)) fakeEsOut()->setSynchronizationReference(r); } return ck; } block_t * AbstractStream::readNextBlock() { if (currentChunk == nullptr && !eof) { segmentgap = false; currentChunk = getNextChunk(); } if(demuxfirstchunk) { /* clear up discontinuity on demux start (discontinuity on start segment bug) */ discontinuity = false; needrestart = false; } else if(discontinuity || needrestart) { msg_Info(p_realdemux, "Ending demuxer stream. %s%s", discontinuity ? "[discontinuity]" : "", needrestart ? "[needrestart]" : ""); /* Force stream/demuxer to end for this call */ return nullptr; } if(currentChunk == nullptr) { eof = true; return nullptr; } const bool b_segment_head_chunk = (currentChunk->getBytesRead() == 0); block_t *block = currentChunk->readBlock(); if(block == nullptr) { if(currentChunk->getRequestStatus() == RequestStatus::NotFound && ++notfound_sequence < 3) { segmentgap = true; } delete currentChunk; currentChunk = nullptr; return nullptr; } else notfound_sequence = 0; demuxfirstchunk = false; if (!currentChunk->hasMoreData()) { delete currentChunk; currentChunk = nullptr; } block = checkBlock(block, b_segment_head_chunk); return block; } bool AbstractStream::setPosition(const StreamPosition &pos, bool tryonly) { if(!seekAble()) return false; bool b_needs_restart = demuxer ? demuxer->needsRestartOnSeek() : true; bool ret = segmentTracker->setPositionByTime(pos.times.segment.media, b_needs_restart, tryonly); if(!tryonly && ret) { // in some cases, media time seek != sent dts // es_out_Control(p_realdemux->out, ES_OUT_SET_NEXT_DISPLAY_TIME, // VLC_TICK_0 + time); } return ret; } bool AbstractStream::getMediaPlaybackTimes(vlc_tick_t *start, vlc_tick_t *end, vlc_tick_t *length) const { return segmentTracker->getMediaPlaybackRange(start, end, length); } bool AbstractStream::getMediaAdvanceAmount(vlc_tick_t *duration) const { if(startTimeContext.media == VLC_TICK_INVALID) return false; *duration = currentTimeContext.media - startTimeContext.media; return true; } bool AbstractStream::runUpdates(bool) { if(!valid) return false; if(!disabled) return segmentTracker->updateSelected(); else return false; } void AbstractStream::fillExtraFMTInfo( es_format_t *p_fmt ) const { if(!p_fmt->psz_language && !language.empty()) p_fmt->psz_language = strdup(language.c_str()); if(!p_fmt->psz_description && !description.empty()) p_fmt->psz_description = strdup(description.c_str()); if( p_fmt->i_cat == VIDEO_ES && p_fmt->video.i_visible_width == 0) { p_fmt->video.i_visible_width = currentrep.width; p_fmt->video.i_visible_height = currentrep.height; } } AbstractDemuxer * AbstractStream::createDemux(const StreamFormat &format) { AbstractDemuxer *ret = newDemux( VLC_OBJECT(p_realdemux), format, (es_out_t *)fakeEsOut(), demuxersource ); if(ret && !ret->create()) { delete ret; ret = nullptr; } else fakeEsOut()->commandsQueue()->Commit(); return ret; } AbstractDemuxer *AbstractStream::newDemux(vlc_object_t *p_obj, const StreamFormat &format, es_out_t *out, AbstractSourceStream *source) const { AbstractDemuxer *ret = nullptr; switch(format) { case StreamFormat::Type::MP4: ret = new Demuxer(p_obj, "mp4", out, source); break; case StreamFormat::Type::MPEG2TS: ret = new Demuxer(p_obj, "ts", out, source); break; default: case StreamFormat::Type::Unsupported: break; } return ret; } void AbstractStream::trackerEvent(const TrackerEvent &ev) { switch(ev.getType()) { case TrackerEvent::Type::Discontinuity: { const DiscontinuityEvent &event = static_cast(ev); discontinuity = true; currentSequence = event.discontinuitySequenceNumber; } break; case TrackerEvent::Type::SegmentGap: segmentgap = true; prevEndTimeContext = SegmentTimes(); currentTimeContext = SegmentTimes(); /* fired before segmentchanged */ break; case TrackerEvent::Type::FormatChange: { const FormatChangedEvent &event = static_cast(ev); /* Check if our current demux is still valid */ if(*event.format != format) { /* Format has changed between segments, we need to drain and change demux */ msg_Info(p_realdemux, "Changing stream format %s -> %s", format.str().c_str(), event.format->str().c_str()); format = *event.format; needrestart = true; } } break; case TrackerEvent::Type::RepresentationSwitch: { const RepresentationSwitchEvent &event = static_cast(ev); if(demuxer && !inrestart && event.prev) { if(!demuxer->bitstreamSwitchCompatible() || /* HLS variants can move from TS to Raw AAC */ format == StreamFormat(StreamFormat::Type::Unknown) || (event.next && !event.next->getAdaptationSet()->isBitSwitchable())) needrestart = true; } AdvDebug(msg_Dbg(p_realdemux, "Stream %s switching %s %s to %s %s", description.c_str(), event.prev ? event.prev->getID().str().c_str() : "", event.prev ? event.prev->getStreamFormat().str().c_str() : "", event.next ? event.next->getID().str().c_str() : "", event.next ? event.next->getStreamFormat().str().c_str() : "")); if(event.next) { currentrep.width = event.next->getWidth() > 0 ? event.next->getWidth() : 0; currentrep.height = event.next->getHeight() > 0 ? event.next->getHeight() : 0; } else { currentrep.width = 0; currentrep.height = 0; } } break; case TrackerEvent::Type::RepresentationUpdated: { if(last_buffer_status == BufferingStatus::Suspended) last_buffer_status = BufferingStatus::Lessthanmin; } break; case TrackerEvent::Type::RepresentationUpdateFailed: { fakeEsOut()->commandsQueue()->setEOF(true); msg_Err(p_realdemux, "Could not update %s anymore, disabling", description.c_str()); } break; case TrackerEvent::Type::SegmentChange: { const SegmentChangedEvent &event = static_cast(ev); if(demuxer && demuxer->needsRestartOnEachSegment() && !inrestart) { needrestart = true; } prevEndTimeContext = currentTimeContext; prevEndTimeContext.offsetBy(currentDuration); fakeEsOut()->setSegmentProgressTimes(prevEndTimeContext); currentTimeContext.media = event.starttime; currentTimeContext.display = event.displaytime; currentSequence = event.sequence; currentDuration = event.duration; if(startTimeContext.media == VLC_TICK_INVALID) startTimeContext = currentTimeContext; } break; case TrackerEvent::Type::PositionChange: { const PositionChangedEvent &event = static_cast(ev); resetForNewPosition(event.resumeTime); } break; default: break; } } void AbstractStream::declaredCodecs() { CodecDescriptionList descs; segmentTracker->getCodecsDesc(&descs); for(auto it = descs.cbegin(); it != descs.cend(); ++it) { const es_format_t *fmt = (*it)->getFmt(); if(fmt->i_cat != UNKNOWN_ES) fakeEsOut()->declareEs(fmt); } } FakeESOut::LockedFakeEsOut AbstractStream::fakeEsOut() { return fakeesout->WithLock(); } FakeESOut::LockedFakeEsOut AbstractStream::fakeEsOut() const { return fakeesout->WithLock(); }