3 *****************************************************************************
4 * Copyright © 2010 - 2011 Klagenfurt University
5 * 2015 VideoLAN and VLC Authors
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as published
9 * by the Free Software Foundation; either version 2.1 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this program; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
20 *****************************************************************************/
26 #include "PlaylistManager.h"
27 #include "SegmentTracker.hpp"
28 #include "SharedResources.hpp"
29 #include "playlist/AbstractPlaylist.hpp"
30 #include "playlist/BasePeriod.h"
31 #include "playlist/BaseAdaptationSet.h"
32 #include "playlist/BaseRepresentation.h"
33 #include "http/HTTPConnectionManager.h"
34 #include "logic/AlwaysBestAdaptationLogic.h"
35 #include "logic/RateBasedAdaptationLogic.h"
36 #include "logic/AlwaysLowestAdaptationLogic.hpp"
37 #include "logic/PredictiveAdaptationLogic.hpp"
38 #include "logic/NearOptimalAdaptationLogic.hpp"
39 #include "tools/Debug.hpp"
40 #include <vlc_stream.h>
41 #include <vlc_demux.h>
42 #include <vlc_threads.h>
47 using namespace adaptive::http
;
48 using namespace adaptive::logic
;
49 using namespace adaptive
;
51 PlaylistManager::PlaylistManager( demux_t
*p_demux_
,
54 AbstractStreamFactory
*factory
,
55 AbstractAdaptationLogic::LogicType type
) :
59 streamFactory ( factory
),
62 currentPeriod
= playlist
->getFirstPeriod();
68 nextPlaylistupdate
= 0;
69 demux
.i_nzpcr
= VLC_TICK_INVALID
;
70 demux
.i_firstpcr
= VLC_TICK_INVALID
;
71 vlc_mutex_init(&demux
.lock
);
72 vlc_cond_init(&demux
.cond
);
73 vlc_mutex_init(&lock
);
74 vlc_cond_init(&waitcond
);
75 vlc_mutex_init(&cached
.lock
);
76 cached
.b_live
= false;
77 cached
.f_position
= 0.0;
78 cached
.i_time
= VLC_TICK_INVALID
;
79 cached
.playlistStart
= 0;
80 cached
.playlistEnd
= 0;
81 cached
.playlistLength
= 0;
82 cached
.lastupdate
= 0;
85 PlaylistManager::~PlaylistManager ()
94 void PlaylistManager::unsetPeriod()
96 std::vector
<AbstractStream
*>::iterator it
;
97 for(it
=streams
.begin(); it
!=streams
.end(); ++it
)
102 bool PlaylistManager::setupPeriod()
107 if(!logic
&& !(logic
= createLogic(logicType
, resources
->getConnManager())))
110 std::vector
<BaseAdaptationSet
*> sets
= currentPeriod
->getAdaptationSets();
111 std::vector
<BaseAdaptationSet
*>::iterator it
;
112 for(it
=sets
.begin();it
!=sets
.end();++it
)
114 BaseAdaptationSet
*set
= *it
;
115 if(set
&& streamFactory
)
117 SegmentTracker
*tracker
= new SegmentTracker(resources
, logic
, set
);
121 AbstractStream
*st
= streamFactory
->create(p_demux
, set
->getStreamFormat(),
122 tracker
, resources
->getConnManager());
129 streams
.push_back(st
);
131 /* Generate stream description */
132 if(!set
->getLang().empty())
133 st
->setLanguage(set
->getLang());
135 if(!set
->description
.Get().empty())
136 st
->setDescription(set
->description
.Get());
142 bool PlaylistManager::init()
147 playlist
->playbackStart
.Set(time(NULL
));
148 nextPlaylistupdate
= playlist
->playbackStart
.Get();
150 updateControlsPosition();
155 bool PlaylistManager::start()
160 b_thread
= !vlc_clone(&thread
, managerThread
,
161 static_cast<void *>(this), VLC_THREAD_PRIORITY_INPUT
);
165 setBufferingRunState(true);
170 bool PlaylistManager::started() const
175 void PlaylistManager::stop()
179 vlc_mutex_lock(&lock
);
181 vlc_cond_signal(&waitcond
);
182 vlc_mutex_unlock(&lock
);
184 vlc_join(thread
, NULL
);
189 struct PrioritizedAbstractStream
191 AbstractStream::buffering_status status
;
192 vlc_tick_t demuxed_amount
;
196 static bool streamCompare(const PrioritizedAbstractStream
&a
, const PrioritizedAbstractStream
&b
)
198 if( a
.status
>= b
.status
) /* Highest prio is higer value in enum */
200 if ( a
.status
== b
.status
) /* Highest prio is lowest buffering */
201 return a
.demuxed_amount
< b
.demuxed_amount
;
208 AbstractStream::buffering_status
PlaylistManager::bufferize(vlc_tick_t i_nzdeadline
,
209 vlc_tick_t i_min_buffering
, vlc_tick_t i_extra_buffering
)
211 AbstractStream::buffering_status i_return
= AbstractStream::buffering_end
;
213 /* First reorder by status >> buffering level */
214 std::vector
<PrioritizedAbstractStream
> prioritized_streams(streams
.size());
215 std::vector
<PrioritizedAbstractStream
>::iterator it
= prioritized_streams
.begin();
216 std::vector
<AbstractStream
*>::iterator sit
= streams
.begin();
217 for( ; sit
!=streams
.end(); ++sit
)
219 PrioritizedAbstractStream
&p
= *it
;
221 p
.status
= p
.st
->getLastBufferStatus();
222 p
.demuxed_amount
= p
.st
->getDemuxedAmount();
225 std::sort(prioritized_streams
.begin(), prioritized_streams
.end(), streamCompare
);
227 for(it
=prioritized_streams
.begin(); it
!=prioritized_streams
.end(); ++it
)
229 AbstractStream
*st
= (*it
).st
;
236 if (st
->isDisabled() &&
237 (!st
->isSelected() || !reactivateStream(st
)))
245 AbstractStream::buffering_status i_ret
= st
->bufferize(i_nzdeadline
, i_min_buffering
, i_extra_buffering
);
246 if(i_return
!= AbstractStream::buffering_ongoing
) /* Buffering streams need to keep going */
252 /* Bail out, will start again (high prio could be same starving stream) */
253 if( i_return
== AbstractStream::buffering_lessthanmin
)
257 vlc_mutex_lock(&demux
.lock
);
258 if(demux
.i_nzpcr
== VLC_TICK_INVALID
&&
259 i_return
!= AbstractStream::buffering_lessthanmin
/* prevents starting before buffering is reached */ )
261 demux
.i_nzpcr
= getFirstDTS();
263 vlc_mutex_unlock(&demux
.lock
);
268 AbstractStream::status
PlaylistManager::dequeue(vlc_tick_t i_floor
, vlc_tick_t
*pi_nzbarrier
)
270 AbstractStream::status i_return
= AbstractStream::status_eof
;
272 const vlc_tick_t i_nzdeadline
= *pi_nzbarrier
;
274 std::vector
<AbstractStream
*>::iterator it
;
275 for(it
=streams
.begin(); it
!=streams
.end(); ++it
)
277 AbstractStream
*st
= *it
;
280 AbstractStream::status i_ret
= st
->dequeue(i_nzdeadline
, &i_pcr
);
281 if( i_ret
> i_return
)
284 if( i_pcr
> i_floor
)
285 *pi_nzbarrier
= std::min( *pi_nzbarrier
, i_pcr
- VLC_TICK_0
);
291 void PlaylistManager::drain()
295 bool b_drained
= true;
296 std::vector
<AbstractStream
*>::iterator it
;
297 for(it
=streams
.begin(); it
!=streams
.end(); ++it
)
299 AbstractStream
*st
= *it
;
301 if (!st
->isValid() || st
->isDisabled())
304 b_drained
&= st
->decodersDrained();
310 vlc_tick_sleep(VLC_TICK_FROM_MS(20)); /* ugly, but we have no way to get feedback */
312 es_out_Control(p_demux
->out
, ES_OUT_RESET_PCR
);
315 vlc_tick_t
PlaylistManager::getResumeTime() const
317 vlc_mutex_locker
locker(&demux
.lock
);
318 return demux
.i_nzpcr
;
321 vlc_tick_t
PlaylistManager::getFirstDTS() const
323 vlc_tick_t mindts
= VLC_TICK_INVALID
;
324 std::vector
<AbstractStream
*>::const_iterator it
;
325 for(it
=streams
.begin(); it
!=streams
.end(); ++it
)
327 const vlc_tick_t dts
= (*it
)->getFirstDTS();
328 if(mindts
== VLC_TICK_INVALID
)
330 else if(dts
!= VLC_TICK_INVALID
)
331 mindts
= std::min(mindts
, dts
);
336 bool PlaylistManager::setPosition(vlc_tick_t time
)
339 bool hasValidStream
= false;
340 for(int real
= 0; real
< 2; real
++)
342 /* Always probe if we can seek first */
343 std::vector
<AbstractStream
*>::iterator it
;
344 for(it
=streams
.begin(); it
!=streams
.end(); ++it
)
346 AbstractStream
*st
= *it
;
347 if(st
->isValid() && !st
->isDisabled())
349 hasValidStream
= true;
350 ret
&= st
->setPosition(time
, !real
);
358 msg_Warn(p_demux
, "there is no valid streams");
364 bool PlaylistManager::needsUpdate() const
366 return playlist
->needsUpdates() &&
367 playlist
->isLive() && (failedupdates
< 3);
370 void PlaylistManager::scheduleNextUpdate()
375 bool PlaylistManager::updatePlaylist()
377 std::vector
<AbstractStream
*>::const_iterator it
;
378 for(it
=streams
.begin(); it
!=streams
.end(); ++it
)
381 updateControlsPosition();
385 vlc_tick_t
PlaylistManager::getFirstPlaybackTime() const
390 vlc_tick_t
PlaylistManager::getCurrentDemuxTime() const
392 vlc_mutex_locker
locker(&demux
.lock
);
393 return demux
.i_nzpcr
;
396 bool PlaylistManager::reactivateStream(AbstractStream
*stream
)
398 return stream
->reactivate(getResumeTime());
401 #define DEMUX_INCREMENT VLC_TICK_FROM_MS(50)
402 int PlaylistManager::demux_callback(demux_t
*p_demux
)
404 PlaylistManager
*manager
= reinterpret_cast<PlaylistManager
*>(p_demux
->p_sys
);
405 if(!manager
->started() && !manager
->start())
406 return VLC_DEMUXER_EOF
;
407 return manager
->doDemux(DEMUX_INCREMENT
);
410 int PlaylistManager::doDemux(vlc_tick_t increment
)
412 vlc_mutex_lock(&demux
.lock
);
413 if(demux
.i_nzpcr
== VLC_TICK_INVALID
)
416 bool b_all_disabled
= true;
417 std::vector
<AbstractStream
*>::const_iterator it
;
418 for(it
=streams
.begin(); it
!=streams
.end(); ++it
)
420 b_dead
&= !(*it
)->isValid();
421 b_all_disabled
&= (*it
)->isDisabled();
424 vlc_cond_timedwait(&demux
.cond
, &demux
.lock
, vlc_tick_now() + VLC_TICK_FROM_MS(50));
425 vlc_mutex_unlock(&demux
.lock
);
426 return (b_dead
|| b_all_disabled
) ? AbstractStream::status_eof
: AbstractStream::status_buffering
;
429 if(demux
.i_firstpcr
== VLC_TICK_INVALID
)
430 demux
.i_firstpcr
= demux
.i_nzpcr
;
432 vlc_tick_t i_nzbarrier
= demux
.i_nzpcr
+ increment
;
433 vlc_mutex_unlock(&demux
.lock
);
435 AbstractStream::status status
= dequeue(demux
.i_nzpcr
, &i_nzbarrier
);
437 updateControlsPosition();
441 case AbstractStream::status_eof
:
443 /* might be end of current period */
446 setBufferingRunState(false);
447 BasePeriod
*nextPeriod
= playlist
->getNextPeriod(currentPeriod
);
449 return VLC_DEMUXER_EOF
;
451 currentPeriod
= nextPeriod
;
453 return VLC_DEMUXER_EOF
;
455 demux
.i_nzpcr
= VLC_TICK_INVALID
;
456 demux
.i_firstpcr
= VLC_TICK_INVALID
;
457 es_out_Control(p_demux
->out
, ES_OUT_RESET_PCR
);
459 setBufferingRunState(true);
463 case AbstractStream::status_buffering
:
464 vlc_mutex_lock(&demux
.lock
);
465 vlc_cond_timedwait(&demux
.cond
, &demux
.lock
, vlc_tick_now() + VLC_TICK_FROM_MS(50));
466 vlc_mutex_unlock(&demux
.lock
);
468 case AbstractStream::status_discontinuity
:
469 vlc_mutex_lock(&demux
.lock
);
470 demux
.i_nzpcr
= VLC_TICK_INVALID
;
471 demux
.i_firstpcr
= VLC_TICK_INVALID
;
472 es_out_Control(p_demux
->out
, ES_OUT_RESET_PCR
);
473 vlc_mutex_unlock(&demux
.lock
);
475 case AbstractStream::status_demuxed
:
476 vlc_mutex_lock(&demux
.lock
);
477 if( demux
.i_nzpcr
!= VLC_TICK_INVALID
&& i_nzbarrier
!= demux
.i_nzpcr
)
479 demux
.i_nzpcr
= i_nzbarrier
;
480 vlc_tick_t pcr
= VLC_TICK_0
+ std::max(INT64_C(0), demux
.i_nzpcr
- VLC_TICK_FROM_MS(100));
481 es_out_Control(p_demux
->out
, ES_OUT_SET_GROUP_PCR
, 0, pcr
);
483 vlc_mutex_unlock(&demux
.lock
);
487 return VLC_DEMUXER_SUCCESS
;
490 int PlaylistManager::control_callback(demux_t
*p_demux
, int i_query
, va_list args
)
492 PlaylistManager
*manager
= reinterpret_cast<PlaylistManager
*>(p_demux
->p_sys
);
493 return manager
->doControl(i_query
, args
);
496 int PlaylistManager::doControl(int i_query
, va_list args
)
501 case DEMUX_CAN_CONTROL_PACE
:
502 *(va_arg (args
, bool *)) = true;
505 case DEMUX_CAN_PAUSE
:
507 /* Always return true then fail late.
508 * See demux.c/demux_vaControl,
509 * misleading and should be DEMUX_CAN_CONTROL_PAUSE */
510 *(va_arg (args
, bool *)) = true;
514 case DEMUX_SET_PAUSE_STATE
:
516 vlc_mutex_locker
locker(&cached
.lock
);
517 return cached
.b_live
? VLC_EGENERIC
: VLC_SUCCESS
;
522 vlc_mutex_locker
locker(&cached
.lock
);
523 *(va_arg (args
, vlc_tick_t
*)) = cached
.i_time
;
527 case DEMUX_GET_LENGTH
:
529 vlc_mutex_locker
locker(&cached
.lock
);
530 if(cached
.b_live
&& cached
.playlistLength
== 0)
532 *(va_arg (args
, vlc_tick_t
*)) = cached
.playlistLength
;
536 case DEMUX_GET_POSITION
:
538 vlc_mutex_locker
locker(&cached
.lock
);
539 if(cached
.b_live
&& cached
.playlistLength
== 0)
541 *(va_arg (args
, double *)) = cached
.f_position
;
545 case DEMUX_SET_POSITION
:
547 setBufferingRunState(false); /* stop downloader first */
548 vlc_mutex_locker
locker(&cached
.lock
);
550 if(cached
.playlistLength
== 0)
552 setBufferingRunState(true);
556 double pos
= va_arg(args
, double);
557 vlc_tick_t seekTime
= cached
.playlistStart
+ cached
.playlistLength
* pos
;
559 SeekDebug(msg_Dbg(p_demux
, "Seek %f to %ld plstart %ld duration %ld",
560 pos
, seekTime
, cached
.playlistEnd
, cached
.playlistLength
));
562 if(!setPosition(seekTime
))
564 setBufferingRunState(true);
568 demux
.i_nzpcr
= VLC_TICK_INVALID
;
569 cached
.lastupdate
= 0;
570 setBufferingRunState(true);
576 setBufferingRunState(false); /* stop downloader first */
578 vlc_tick_t time
= va_arg(args
, vlc_tick_t
);// + getFirstPlaybackTime();
579 if(!setPosition(time
))
581 setBufferingRunState(true);
585 vlc_mutex_locker
locker(&cached
.lock
);
586 demux
.i_nzpcr
= VLC_TICK_INVALID
;
587 cached
.lastupdate
= 0;
588 setBufferingRunState(true);
592 case DEMUX_GET_PTS_DELAY
:
593 *va_arg (args
, vlc_tick_t
*) = VLC_TICK_FROM_SEC(1);
602 void PlaylistManager::setBufferingRunState(bool b
)
604 vlc_mutex_lock(&lock
);
606 vlc_cond_signal(&waitcond
);
607 vlc_mutex_unlock(&lock
);
610 void PlaylistManager::Run()
612 vlc_mutex_lock(&lock
);
613 const vlc_tick_t i_min_buffering
= playlist
->getMinBuffering();
614 const vlc_tick_t i_extra_buffering
= playlist
->getMaxBuffering() - i_min_buffering
;
617 while(!b_buffering
&& !b_canceled
)
618 vlc_cond_wait(&waitcond
, &lock
);
624 int canc
= vlc_savecancel();
626 scheduleNextUpdate();
629 vlc_restorecancel(canc
);
632 vlc_mutex_lock(&demux
.lock
);
633 vlc_tick_t i_nzpcr
= demux
.i_nzpcr
;
634 vlc_mutex_unlock(&demux
.lock
);
636 int canc
= vlc_savecancel();
637 AbstractStream::buffering_status i_return
= bufferize(i_nzpcr
, i_min_buffering
, i_extra_buffering
);
638 vlc_restorecancel( canc
);
640 if(i_return
!= AbstractStream::buffering_lessthanmin
)
642 vlc_tick_t i_deadline
= vlc_tick_now();
643 if(i_return
== AbstractStream::buffering_ongoing
)
644 i_deadline
+= VLC_TICK_FROM_MS(10);
645 else if(i_return
== AbstractStream::buffering_full
)
646 i_deadline
+= VLC_TICK_FROM_MS(100);
647 else if(i_return
== AbstractStream::buffering_end
)
648 i_deadline
+= VLC_TICK_FROM_SEC(1);
649 else /*if(i_return == AbstractStream::buffering_suspended)*/
650 i_deadline
+= VLC_TICK_FROM_MS(250);
652 vlc_mutex_lock(&demux
.lock
);
653 vlc_cond_signal(&demux
.cond
);
654 vlc_mutex_unlock(&demux
.lock
);
657 vlc_cond_timedwait(&waitcond
, &lock
, i_deadline
) == 0 &&
658 i_deadline
> vlc_tick_now() &&
664 vlc_mutex_unlock(&lock
);
667 void * PlaylistManager::managerThread(void *opaque
)
669 static_cast<PlaylistManager
*>(opaque
)->Run();
673 void PlaylistManager::updateControlsPosition()
675 vlc_mutex_locker
locker(&cached
.lock
);
677 time_t now
= time(NULL
);
678 if(now
- cached
.lastupdate
< 1)
680 cached
.lastupdate
= now
;
682 vlc_tick_t rapPlaylistStart
= 0;
683 vlc_tick_t rapDemuxStart
= 0;
684 std::vector
<AbstractStream
*>::iterator it
;
685 for(it
=streams
.begin(); it
!=streams
.end(); ++it
)
687 AbstractStream
*st
= *it
;
688 if(st
->isValid() && !st
->isDisabled() && st
->isSelected())
690 if(st
->getMediaPlaybackTimes(&cached
.playlistStart
, &cached
.playlistEnd
,
691 &cached
.playlistLength
,
692 &rapPlaylistStart
, &rapDemuxStart
))
699 * -> Elapsed demux time (current demux time - first demux time)
700 * Since PlaylistTime != DemuxTime (HLS crap, TS):
701 * -> Use Playlist<->Demux time offset provided by EsOut
702 * to convert to elapsed playlist time.
703 * But this diff is not available until we have demuxed data...
704 * Fallback on relative seek from playlist start in that case
705 * But also playback might not have started at beginning of playlist
706 * -> Apply relative start from seek point (set on EsOut as ExpectedTime)
708 * All seeks need to be done in playlist time !
711 vlc_tick_t currentDemuxTime
= getCurrentDemuxTime();
712 cached
.b_live
= playlist
->isLive();
714 SeekDebug(msg_Dbg(p_demux
, "playlist Start/End %ld/%ld len %ld"
715 "rap pl/demux (%ld/%ld)",
716 cached
.playlistStart
, cached
.playlistEnd
, cached
.playlistEnd
,
717 rapPlaylistStart
, rapDemuxStart
));
721 /* Special case for live until we can provide relative start to fully match
722 the above description */
723 cached
.i_time
= currentDemuxTime
;
725 if(cached
.playlistStart
!= cached
.playlistEnd
)
727 if(cached
.playlistStart
< 0) /* Live template. Range start = now() - buffering depth */
729 cached
.playlistEnd
= vlc_tick_from_sec(now
);
730 cached
.playlistStart
= cached
.playlistEnd
- cached
.playlistLength
;
733 const vlc_tick_t currentTime
= getCurrentDemuxTime();
734 if(currentTime
> cached
.playlistStart
&&
735 currentTime
<= cached
.playlistEnd
&& cached
.playlistLength
)
737 cached
.f_position
= ((double)(currentTime
- cached
.playlistStart
)) / cached
.playlistLength
;
741 cached
.f_position
= 0.0;
746 if(playlist
->duration
.Get() > cached
.playlistLength
)
747 cached
.playlistLength
= playlist
->duration
.Get();
749 if(cached
.playlistLength
&& currentDemuxTime
)
751 /* convert to playlist time */
752 vlc_tick_t rapRelOffset
= currentDemuxTime
- rapDemuxStart
; /* offset from start/seek */
753 vlc_tick_t absPlaylistTime
= rapPlaylistStart
+ rapRelOffset
; /* converted as abs playlist time */
754 vlc_tick_t relMediaTime
= absPlaylistTime
- cached
.playlistStart
; /* elapsed, in playlist time */
755 cached
.i_time
= absPlaylistTime
;
756 cached
.f_position
= (double) relMediaTime
/ cached
.playlistLength
;
760 cached
.f_position
= 0.0;
764 SeekDebug(msg_Dbg(p_demux
, "cached.i_time (%ld) cur %ld rap start (pl %ld/dmx %ld)",
765 cached
.i_time
, currentDemuxTime
, rapPlaylistStart
, rapDemuxStart
));
768 AbstractAdaptationLogic
*PlaylistManager::createLogic(AbstractAdaptationLogic::LogicType type
, AbstractConnectionManager
*conn
)
770 vlc_object_t
*obj
= VLC_OBJECT(p_demux
);
771 AbstractAdaptationLogic
*logic
= NULL
;
774 case AbstractAdaptationLogic::FixedRate
:
776 size_t bps
= var_InheritInteger(p_demux
, "adaptive-bw") * 8192;
777 logic
= new (std::nothrow
) FixedRateAdaptationLogic(obj
, bps
);
780 case AbstractAdaptationLogic::AlwaysLowest
:
781 logic
= new (std::nothrow
) AlwaysLowestAdaptationLogic(obj
);
783 case AbstractAdaptationLogic::AlwaysBest
:
784 logic
= new (std::nothrow
) AlwaysBestAdaptationLogic(obj
);
786 case AbstractAdaptationLogic::RateBased
:
788 RateBasedAdaptationLogic
*ratelogic
=
789 new (std::nothrow
) RateBasedAdaptationLogic(obj
);
791 conn
->setDownloadRateObserver(ratelogic
);
795 case AbstractAdaptationLogic::Default
:
796 case AbstractAdaptationLogic::NearOptimal
:
798 NearOptimalAdaptationLogic
*noplogic
=
799 new (std::nothrow
) NearOptimalAdaptationLogic(obj
);
801 conn
->setDownloadRateObserver(noplogic
);
805 case AbstractAdaptationLogic::Predictive
:
807 AbstractAdaptationLogic
*predictivelogic
=
808 new (std::nothrow
) PredictiveAdaptationLogic(obj
);
810 conn
->setDownloadRateObserver(predictivelogic
);
811 logic
= predictivelogic
;
820 logic
->setMaxDeviceResolution( var_InheritInteger(p_demux
, "adaptive-maxwidth"),
821 var_InheritInteger(p_demux
, "adaptive-maxheight") );