/* * PlaylistManager.cpp ***************************************************************************** * Copyright © 2010 - 2011 Klagenfurt University * 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 "PlaylistManager.h" #include "SegmentTracker.hpp" #include "SharedResources.hpp" #include "playlist/AbstractPlaylist.hpp" #include "playlist/BasePeriod.h" #include "playlist/BaseAdaptationSet.h" #include "playlist/BaseRepresentation.h" #include "http/HTTPConnectionManager.h" #include "logic/AlwaysBestAdaptationLogic.h" #include "logic/RateBasedAdaptationLogic.h" #include "logic/AlwaysLowestAdaptationLogic.hpp" #include "logic/PredictiveAdaptationLogic.hpp" #include "logic/NearOptimalAdaptationLogic.hpp" #include "tools/Debug.hpp" #include #include #include #include #include using namespace adaptive::http; using namespace adaptive::logic; using namespace adaptive; PlaylistManager::PlaylistManager( demux_t *p_demux_, SharedResources *res, AbstractPlaylist *pl, AbstractStreamFactory *factory, AbstractAdaptationLogic::LogicType type ) : conManager ( NULL ), logicType ( type ), logic ( NULL ), playlist ( pl ), streamFactory ( factory ), p_demux ( p_demux_ ) { currentPeriod = playlist->getFirstPeriod(); resources = res; failedupdates = 0; b_thread = false; b_buffering = false; nextPlaylistupdate = 0; demux.i_nzpcr = VLC_TICK_INVALID; demux.i_firstpcr = VLC_TICK_INVALID; vlc_mutex_init(&demux.lock); vlc_cond_init(&demux.cond); vlc_mutex_init(&lock); vlc_cond_init(&waitcond); vlc_mutex_init(&cached.lock); cached.b_live = false; cached.i_length = 0; cached.f_position = 0.0; cached.i_time = VLC_TICK_INVALID; } PlaylistManager::~PlaylistManager () { delete streamFactory; unsetPeriod(); delete playlist; delete conManager; delete logic; delete resources; vlc_cond_destroy(&waitcond); vlc_mutex_destroy(&lock); vlc_mutex_destroy(&demux.lock); vlc_cond_destroy(&demux.cond); vlc_mutex_destroy(&cached.lock); } void PlaylistManager::unsetPeriod() { std::vector::iterator it; for(it=streams.begin(); it!=streams.end(); ++it) delete *it; streams.clear(); } bool PlaylistManager::setupPeriod() { if(!currentPeriod) return false; if(!logic && !(logic = createLogic(logicType, conManager))) return false; std::vector sets = currentPeriod->getAdaptationSets(); std::vector::iterator it; for(it=sets.begin();it!=sets.end();++it) { BaseAdaptationSet *set = *it; if(set && streamFactory) { SegmentTracker *tracker = new SegmentTracker(resources, logic, set); if(!tracker) continue; AbstractStream *st = streamFactory->create(p_demux, set->getStreamFormat(), tracker, conManager); if(!st) { delete tracker; continue; } streams.push_back(st); /* Generate stream description */ if(!set->getLang().empty()) st->setLanguage(set->getLang()); if(!set->description.Get().empty()) st->setDescription(set->description.Get()); } } return true; } bool PlaylistManager::init() { if(!conManager && !(conManager = new (std::nothrow) HTTPConnectionManager(VLC_OBJECT(p_demux->s), resources->getAuthStorage())) ) return false; if(!setupPeriod()) return false; playlist->playbackStart.Set(time(NULL)); nextPlaylistupdate = playlist->playbackStart.Get(); updateControlsContentType(); updateControlsPosition(); return true; } bool PlaylistManager::start() { if(b_thread || !conManager) return false; b_thread = !vlc_clone(&thread, managerThread, static_cast(this), VLC_THREAD_PRIORITY_INPUT); if(!b_thread) return false; setBufferingRunState(true); return true; } bool PlaylistManager::started() const { return b_thread; } void PlaylistManager::stop() { if(b_thread) { vlc_cancel(thread); vlc_join(thread, NULL); b_thread = false; } } struct PrioritizedAbstractStream { AbstractStream::buffering_status status; vlc_tick_t demuxed_amount; AbstractStream *st; }; static bool streamCompare(const PrioritizedAbstractStream &a, const PrioritizedAbstractStream &b) { if( a.status >= b.status ) /* Highest prio is higer value in enum */ { if ( a.status == b.status ) /* Highest prio is lowest buffering */ return a.demuxed_amount < b.demuxed_amount; else return true; } return false; } AbstractStream::buffering_status PlaylistManager::bufferize(vlc_tick_t i_nzdeadline, vlc_tick_t i_min_buffering, vlc_tick_t i_extra_buffering) { AbstractStream::buffering_status i_return = AbstractStream::buffering_end; /* First reorder by status >> buffering level */ std::vector prioritized_streams(streams.size()); std::vector::iterator it = prioritized_streams.begin(); std::vector::iterator sit = streams.begin(); for( ; sit!=streams.end(); ++sit) { PrioritizedAbstractStream &p = *it; p.st = *sit; p.status = p.st->getLastBufferStatus(); p.demuxed_amount = p.st->getDemuxedAmount(); ++it; } std::sort(prioritized_streams.begin(), prioritized_streams.end(), streamCompare); for(it=prioritized_streams.begin(); it!=prioritized_streams.end(); ++it) { AbstractStream *st = (*it).st; if (st->isDisabled() && (!st->isSelected() || !st->canActivate() || !reactivateStream(st))) continue; AbstractStream::buffering_status i_ret = st->bufferize(i_nzdeadline, i_min_buffering, i_extra_buffering); if(i_return != AbstractStream::buffering_ongoing) /* Buffering streams need to keep going */ { if(i_ret > i_return) i_return = i_ret; } /* Bail out, will start again (high prio could be same starving stream) */ if( i_return == AbstractStream::buffering_lessthanmin ) break; } vlc_mutex_lock(&demux.lock); if(demux.i_nzpcr == VLC_TICK_INVALID && i_return != AbstractStream::buffering_lessthanmin /* prevents starting before buffering is reached */ ) { demux.i_nzpcr = getFirstDTS(); } vlc_mutex_unlock(&demux.lock); return i_return; } AbstractStream::status PlaylistManager::dequeue(vlc_tick_t i_floor, vlc_tick_t *pi_nzbarrier) { AbstractStream::status i_return = AbstractStream::status_eof; const vlc_tick_t i_nzdeadline = *pi_nzbarrier; std::vector::iterator it; for(it=streams.begin(); it!=streams.end(); ++it) { AbstractStream *st = *it; vlc_tick_t i_pcr; AbstractStream::status i_ret = st->dequeue(i_nzdeadline, &i_pcr); if( i_ret > i_return ) i_return = i_ret; if( i_pcr > i_floor ) *pi_nzbarrier = std::min( *pi_nzbarrier, i_pcr - VLC_TICK_0 ); } return i_return; } void PlaylistManager::drain() { for(;;) { bool b_drained = true; std::vector::iterator it; for(it=streams.begin(); it!=streams.end(); ++it) { AbstractStream *st = *it; if (st->isDisabled()) continue; b_drained &= st->decodersDrained(); } if(b_drained) break; vlc_tick_sleep(VLC_TICK_FROM_MS(20)); /* ugly, but we have no way to get feedback */ } es_out_Control(p_demux->out, ES_OUT_RESET_PCR); } vlc_tick_t PlaylistManager::getResumeTime() const { vlc_mutex_locker locker(const_cast(&demux.lock)); return demux.i_nzpcr; } vlc_tick_t PlaylistManager::getFirstDTS() const { vlc_tick_t mindts = VLC_TICK_INVALID; std::vector::const_iterator it; for(it=streams.begin(); it!=streams.end(); ++it) { const vlc_tick_t dts = (*it)->getFirstDTS(); if(mindts == VLC_TICK_INVALID) mindts = dts; else if(dts != VLC_TICK_INVALID) mindts = std::min(mindts, dts); } return mindts; } vlc_tick_t PlaylistManager::getDuration() const { if (playlist->isLive()) return 0; else return playlist->duration.Get(); } bool PlaylistManager::setPosition(vlc_tick_t time) { bool ret = true; bool hasValidStream = false; for(int real = 0; real < 2; real++) { /* Always probe if we can seek first */ std::vector::iterator it; for(it=streams.begin(); it!=streams.end(); ++it) { AbstractStream *st = *it; if(!st->isDisabled()) { hasValidStream = true; ret &= st->setPosition(time, !real); } } if(!ret) break; } if(!hasValidStream) { msg_Warn(p_demux, "there is no valid streams"); ret = false; } return ret; } bool PlaylistManager::needsUpdate() const { return playlist->needsUpdates() && playlist->isLive() && (failedupdates < 3); } void PlaylistManager::scheduleNextUpdate() { } bool PlaylistManager::updatePlaylist() { std::vector::const_iterator it; for(it=streams.begin(); it!=streams.end(); ++it) (*it)->runUpdates(); updateControlsContentType(); updateControlsPosition(); return true; } vlc_tick_t PlaylistManager::getFirstPlaybackTime() const { return 0; } 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()); } #define DEMUX_INCREMENT VLC_TICK_FROM_MS(50) int PlaylistManager::demux_callback(demux_t *p_demux) { PlaylistManager *manager = reinterpret_cast(p_demux->p_sys); if(!manager->started() && !manager->start()) return VLC_DEMUXER_EOF; return manager->doDemux(DEMUX_INCREMENT); } int PlaylistManager::doDemux(vlc_tick_t increment) { vlc_mutex_lock(&demux.lock); if(demux.i_nzpcr == VLC_TICK_INVALID) { bool b_dead = true; std::vector::const_iterator it; for(it=streams.begin(); it!=streams.end(); ++it) b_dead &= !(*it)->canActivate(); if(!b_dead) vlc_cond_timedwait(&demux.cond, &demux.lock, vlc_tick_now() + VLC_TICK_FROM_MS(50)); vlc_mutex_unlock(&demux.lock); return (b_dead) ? AbstractStream::status_eof : AbstractStream::status_buffering; } if(demux.i_firstpcr == VLC_TICK_INVALID) demux.i_firstpcr = demux.i_nzpcr; vlc_tick_t i_nzbarrier = demux.i_nzpcr + increment; vlc_mutex_unlock(&demux.lock); AbstractStream::status status = dequeue(demux.i_nzpcr, &i_nzbarrier); updateControlsContentType(); updateControlsPosition(); switch(status) { case AbstractStream::status_eof: { /* might be end of current period */ if(currentPeriod) { setBufferingRunState(false); BasePeriod *nextPeriod = playlist->getNextPeriod(currentPeriod); if(!nextPeriod) return VLC_DEMUXER_EOF; unsetPeriod(); currentPeriod = nextPeriod; if (!setupPeriod()) return VLC_DEMUXER_EOF; demux.i_nzpcr = VLC_TICK_INVALID; demux.i_firstpcr = VLC_TICK_INVALID; es_out_Control(p_demux->out, ES_OUT_RESET_PCR); setBufferingRunState(true); } } break; case AbstractStream::status_buffering: vlc_mutex_lock(&demux.lock); vlc_cond_timedwait(&demux.cond, &demux.lock, vlc_tick_now() + VLC_TICK_FROM_MS(50)); vlc_mutex_unlock(&demux.lock); break; case AbstractStream::status_discontinuity: vlc_mutex_lock(&demux.lock); demux.i_nzpcr = VLC_TICK_INVALID; demux.i_firstpcr = VLC_TICK_INVALID; es_out_Control(p_demux->out, ES_OUT_RESET_PCR); vlc_mutex_unlock(&demux.lock); break; case AbstractStream::status_demuxed: vlc_mutex_lock(&demux.lock); if( demux.i_nzpcr != VLC_TICK_INVALID && i_nzbarrier != demux.i_nzpcr ) { demux.i_nzpcr = i_nzbarrier; vlc_tick_t pcr = VLC_TICK_0 + std::max(INT64_C(0), demux.i_nzpcr - VLC_TICK_FROM_MS(100)); es_out_Control(p_demux->out, ES_OUT_SET_GROUP_PCR, 0, pcr); } vlc_mutex_unlock(&demux.lock); break; } return VLC_DEMUXER_SUCCESS; } int PlaylistManager::control_callback(demux_t *p_demux, int i_query, va_list args) { PlaylistManager *manager = reinterpret_cast(p_demux->p_sys); return manager->doControl(i_query, args); } 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; case DEMUX_CAN_PAUSE: { /* Always return true then fail late. * See demux.c/demux_vaControl, * misleading and should be DEMUX_CAN_CONTROL_PAUSE */ *(va_arg (args, bool *)) = true; break; } case DEMUX_SET_PAUSE_STATE: { vlc_mutex_locker locker(&cached.lock); return cached.b_live ? VLC_EGENERIC : VLC_SUCCESS; } case DEMUX_GET_TIME: { vlc_mutex_locker locker(&cached.lock); *(va_arg (args, vlc_tick_t *)) = cached.i_time; break; } case DEMUX_GET_LENGTH: { vlc_mutex_locker locker(&cached.lock); if(cached.b_live) return VLC_EGENERIC; *(va_arg (args, vlc_tick_t *)) = cached.i_length; break; } case DEMUX_GET_POSITION: { vlc_mutex_locker locker(&cached.lock); if(cached.b_live) return VLC_EGENERIC; *(va_arg (args, double *)) = cached.f_position; break; } case DEMUX_SET_POSITION: { setBufferingRunState(false); /* stop downloader first */ const vlc_tick_t i_duration = getDuration(); if(i_duration == 0) /* == playlist->isLive() */ { setBufferingRunState(true); return VLC_EGENERIC; } int64_t time = i_duration * va_arg(args, double); time += getFirstPlaybackTime(); if(!setPosition(time)) { setBufferingRunState(true); return VLC_EGENERIC; } demux.i_nzpcr = VLC_TICK_INVALID; setBufferingRunState(true); break; } 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)) { setBufferingRunState(true); return VLC_EGENERIC; } demux.i_nzpcr = VLC_TICK_INVALID; setBufferingRunState(true); break; } case DEMUX_GET_PTS_DELAY: *va_arg (args, vlc_tick_t *) = VLC_TICK_FROM_SEC(1); break; default: return VLC_EGENERIC; } return VLC_SUCCESS; } void PlaylistManager::setBufferingRunState(bool b) { vlc_mutex_lock(&lock); b_buffering = b; vlc_cond_signal(&waitcond); vlc_mutex_unlock(&lock); } void PlaylistManager::Run() { vlc_mutex_lock(&lock); const vlc_tick_t i_min_buffering = playlist->getMinBuffering(); const vlc_tick_t i_extra_buffering = playlist->getMaxBuffering() - i_min_buffering; while(1) { mutex_cleanup_push(&lock); while(!b_buffering) vlc_cond_wait(&waitcond, &lock); vlc_testcancel(); vlc_cleanup_pop(); if(needsUpdate()) { int canc = vlc_savecancel(); if(updatePlaylist()) scheduleNextUpdate(); else failedupdates++; vlc_restorecancel(canc); } vlc_mutex_lock(&demux.lock); vlc_tick_t i_nzpcr = demux.i_nzpcr; vlc_mutex_unlock(&demux.lock); int canc = vlc_savecancel(); AbstractStream::buffering_status i_return = bufferize(i_nzpcr, i_min_buffering, i_extra_buffering); vlc_restorecancel( canc ); if(i_return != AbstractStream::buffering_lessthanmin) { vlc_tick_t i_deadline = vlc_tick_now(); if(i_return == AbstractStream::buffering_ongoing) i_deadline += VLC_TICK_FROM_MS(10); else if(i_return == AbstractStream::buffering_full) i_deadline += VLC_TICK_FROM_MS(100); else if(i_return == AbstractStream::buffering_end) i_deadline += VLC_TICK_FROM_SEC(1); else /*if(i_return == AbstractStream::buffering_suspended)*/ i_deadline += VLC_TICK_FROM_MS(250); vlc_mutex_lock(&demux.lock); vlc_cond_signal(&demux.cond); vlc_mutex_unlock(&demux.lock); mutex_cleanup_push(&lock); while(b_buffering && vlc_cond_timedwait(&waitcond, &lock, i_deadline) == 0 && i_deadline > vlc_tick_now()); vlc_cleanup_pop(); } } vlc_mutex_unlock(&lock); } void * PlaylistManager::managerThread(void *opaque) { static_cast(opaque)->Run(); return NULL; } void PlaylistManager::updateControlsPosition() { vlc_mutex_locker locker(&cached.lock); const vlc_tick_t i_duration = cached.i_length; if(i_duration == 0) { cached.f_position = 0.0; } else { const vlc_tick_t i_length = getCurrentPlaybackTime() - getFirstPlaybackTime(); cached.f_position = (double) i_length / i_duration; } vlc_tick_t i_time = getCurrentPlaybackTime(); if(!playlist->isLive()) i_time -= getFirstPlaybackTime(); cached.i_time = i_time; } void PlaylistManager::updateControlsContentType() { 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 = getDuration(); } } AbstractAdaptationLogic *PlaylistManager::createLogic(AbstractAdaptationLogic::LogicType type, AbstractConnectionManager *conn) { vlc_object_t *obj = VLC_OBJECT(p_demux); AbstractAdaptationLogic *logic = NULL; switch(type) { case AbstractAdaptationLogic::FixedRate: { size_t bps = var_InheritInteger(p_demux, "adaptive-bw") * 8192; logic = new (std::nothrow) FixedRateAdaptationLogic(obj, bps); break; } case AbstractAdaptationLogic::AlwaysLowest: logic = new (std::nothrow) AlwaysLowestAdaptationLogic(obj); break; case AbstractAdaptationLogic::AlwaysBest: logic = new (std::nothrow) AlwaysBestAdaptationLogic(obj); break; case AbstractAdaptationLogic::RateBased: { RateBasedAdaptationLogic *ratelogic = new (std::nothrow) RateBasedAdaptationLogic(obj); if(ratelogic) conn->setDownloadRateObserver(ratelogic); logic = ratelogic; break; } case AbstractAdaptationLogic::Default: case AbstractAdaptationLogic::NearOptimal: { NearOptimalAdaptationLogic *noplogic = new (std::nothrow) NearOptimalAdaptationLogic(obj); if(noplogic) conn->setDownloadRateObserver(noplogic); logic = noplogic; break; } case AbstractAdaptationLogic::Predictive: { AbstractAdaptationLogic *predictivelogic = new (std::nothrow) PredictiveAdaptationLogic(obj); if(predictivelogic) conn->setDownloadRateObserver(predictivelogic); logic = predictivelogic; } default: break; } if(logic) { logic->setMaxDeviceResolution( var_InheritInteger(p_demux, "adaptive-maxwidth"), var_InheritInteger(p_demux, "adaptive-maxheight") ); } return logic; }