Exemple #1
0
 def _initConnection(self, url):
     '''
     Initializes connection with url (only with persistent connection)
     '''
     if self.connection:
         self.connection.stop()
     debug(DEBUG + 1, '%s _initConnection: %s', self, url)
     self.connection = ClientFactory(url)
     self.connection.connect('connection-made', self._onConnectionMade)
     self.connection.connect('connection-lost', self._onConnectionLost)
     self.connection.connect('data-received', self._onDataReceived)
     self.connection.connect('data-receiving', self._onDataReceiving)
Exemple #2
0
 def _initConnection(self, url):
     '''
     Initializes connection with url (only with persistent connection)
     '''
     if self.connection:
         self.connection.stop()
     debug(DEBUG+1, '%s _initConnection: %s', self, url)
     self.connection = ClientFactory(url)
     self.connection.connect('connection-made', self._onConnectionMade)
     self.connection.connect('connection-lost', self._onConnectionLost)
     self.connection.connect('data-received', self._onDataReceived)
     self.connection.connect('data-receiving', self._onDataReceiving)
Exemple #3
0
class TapasPlayer(object):

    def __init__(self, controller, parser, media_engine, 
        log_sub_dir='', log_period=0.1,
        max_buffer_time=60,
        inactive_cycle=1, initial_level=1,
        use_persistent_connection=True,
        check_warning_buffering=True,
        stress_test=False):

        # player components
        self.controller = controller
        self.parser = parser
        self.media_engine = media_engine
        # log
        self.logger = None
        self.log_file = None
        self.log_dir='logs'
        self.log_sub_dir=log_sub_dir
        self.log_period = log_period
        self.log_prefix='' 
        self.log_comment='' 
        #
        self.max_buffer_time = max_buffer_time
        self.inactive_cycle = inactive_cycle #active control action after inactive_cycle+1 segments
        #
        self.use_persistent_connection = use_persistent_connection
        self.connection = None
        #
        self.cur_level = initial_level
        self.cur_index = 0
        self.enable_stress_test = stress_test #flag to enable stress test, switch level every segment cyclically 
        #
        self.check_warning_buffering = check_warning_buffering #flag to enable check for warning buffering
        self.rate_calc = RateCalc(period=3.0, alpha=0.0)
        self.remaining_data = 0
        #
        self.bwe = 0
        self.downloaded_bytes = 0
        self.downloaded_segments = 0
        self.last_fragment_size = 0
        self.start_segment_request = -1.0
        self.stop_segment_request = -1.0
        self.last_downloaded_time = -1.0
        self.t_paused = time.time()
        self.paused_time = 0.0
        self.queuedBytes = 0
        self.queuedTime = 0.0
        #
        self.proc_stats = ProcessStats()

        #Initialize the parameters passed at the controller after the download of every segment
        self.feedback = dict(queued_bytes=0,
           queued_time=0.0,
           max_buffer_time=self.max_buffer_time,
           bwe=0.0,
           level=self.cur_level,
           max_level=-1,
           cur_rate=0.0,
           max_rate=0.0,
           min_rate=0.0,
           player_status=0,
           paused_time=0.0,
           last_fragment_size=0,
           last_download_time=0.0,
           downloaded_bytes=0,
           fragment_duration=0.0,
           rates=[]
        )
        self.controller.setPlayerFeedback(self.feedback)
        
    def __repr__(self):
        return '<TapasPlayer-%d>' %id(self)

    def play(self):
        '''
        Starts Parser, creates Logger, initializes MediaEngine, and fetches the first segment when the parser has finished
        '''
        self.parser.loadPlaylist()
        def _on_done(res): 
            playlists = self.parser.getPlaylists()
            levels = self.parser.getLevels()
            fragment_duration = self.parser.getFragmentDuration()
            caps = self.parser._getCapsDemuxer()
            self.controller.setIdleDuration(fragment_duration)  #Default pause interval when isBuffering return False
            if self.getCurrentLevel() > self.getMaxLevel() or self.getCurrentLevel() == -1:
                self.setCurrentLevel(self.getMaxLevel())
            #opts for Logger
            opts = [
                ('enqueued_b', int, ''),                        #2
                ('enqueued_t', float, 'visible=1,subplot=2'),   #3
                ('bwe', float, 'visible=1,subplot=1'),          #4
                ('cur', int, 'visible=1,subplot=1'),            #5
                ('level', int, 'visible=1,subplot=3'),          #6
                ('max_level', int, ''),                         #7
                ('player_status', int, 'visible=1,subplot=3'),  #8
                ('paused_time', float, ''),                     #9
                ('downloaded_bytes', int, ''),                  #10
                ('cpu', float, 'visible=1,subplot=4'),          #11
                ('mem', float, 'visible=1,subplot=5'),          #12
                ('rss', float, ''),                             #13
                ('vms', float, ''),                             #14
                ('ts_start_req', float, ''),                    #15
                ('ts_stop_req', float, ''),                     #16
            ]
            for i in range(0,len(levels)):
                opts.append(('q%d' %i, int, 'visible=0'))
            if self.log_sub_dir:     
                self.log_dir = self.log_dir + '/'+ self.log_sub_dir
                #Create Logger
                self.logger = Logger(opts, log_period=self.log_period, 
                    log_prefix=self.log_prefix, comment=self.log_comment, 
                    log_dir=self.log_dir)
                debug(DEBUG+1, 'levels: %s', levels)
                debug(DEBUG+1, 'playlists: %s', playlists)
            if self.enable_stress_test:
                self.inactive_cycle = 0
            if self.check_warning_buffering:
                self.rate_calc.start()
                self.rate_calc.connect('update', self.checkBuffering)
            #Init media_engine
            self.media_engine.setVideoContainer(self.parser.getVideoContainer())
            self.media_engine.connect('status-changed', self._onStatusChanged)
            self.media_engine.start()
            #start logger
            reactor.callLater(self.log_period, self.log)
            #
            self.fetchNextSegment()
        self.parser.deferred.addCallback(_on_done)

    def getMaxBufferTime(self):
        '''
        Gets max buffer in seconds under which the playback is considered in Buffering by default
        '''
        return self.max_buffer_time

    def getCurrentLevel(self):
        '''
        Gets index of current level starting from 0 for the lowest video quality level
        '''
        return self.cur_level

    def setCurrentLevel(self,level):
        '''
        Sets index of current level starting from 0 for the lowest video quality level

        :param level: the level index
        '''
        self.cur_level = level

    def getMaxLevel(self):
        '''
        Gets index of maximum level starting from 0 for the lowest video quality level
        '''
        return len(self.parser.getLevels())-1

    def getCurrentSegmentIndex(self):
        '''
        Gets index of the current segment of the sub-playlist
        '''
        return self.cur_index

    def setCurrentSegmentIndex(self,index):
        '''
        Sets index of the current segment of the sub-playlist

        :param index: segment index
        '''
        self.cur_index = index

    def getCurrentRate(self):
        '''
        Gets current video quality level rate in B/s
        '''
        levels = self.parser.getLevels()
        cur_rate = float(levels[self.getCurrentLevel()]['rate'])
        return cur_rate

    def getMaxRate(self):
        '''
        Gets maximum video quality level rate in B/s
        '''
        levels = self.parser.getLevels()
        rates = [float(i['rate']) for i in levels]
        return max(rates)

    def getMinRate(self):
        '''
        Gets minimum video quality level rate in B/s
        '''
        levels = self.parser.getLevels()
        rates = [float(i['rate']) for i in levels]
        return min(rates)

    def getLevelRates(self):
        '''
        Gets a list of video quality level rates in B/s
        '''
        levels = self.parser.getLevels()
        _r= [float(i['rate']) for i in levels]
        rates = []
        for i in range(0,len(_r)):
            rates.append(_r[i])
        return rates

    def getLevelResolutions(self):
        '''
        Gets a list of available video resolutions
        '''
        levels = self.parser.getLevels()
        resolutions = [i['resolution'] for i in levels]
        return resolutions

    def getLastDownloadedTime(self):
        '''
        Gets time spent to download the last segment
        '''
        return self.last_downloaded_time

    def getStartSegmentRequest(self):
        '''
        Gets timestamp when starts the download of the last segment
        '''
        return self.start_segment_request  

    def getStopSegmentRequest(self):
        '''
        Gets timestamp when stops the download of the last segment
        '''
        return self.stop_segment_request  

    def getLastFragmentBytes(self):
        '''
        Gets the last fragment size in B
        '''
        return self.last_fragment_size

    def getDownloadedBytes(self):
        '''
        Gets total downloaded bytes in B
        '''
        return self.downloaded_bytes

    def getDownloadedSegments(self):
        '''
        Gets total number of downloaded segments
        '''
        return self.downloaded_segments

    def getBandwidth(self):
        '''
        Gets last estimated available bandwidth in B/s
        '''
        return self.bwe

    def getPausedTime(self):
        '''
        Gets time spent on pause
        '''
        return self.paused_time

    def getInactiveCycles(self):
        '''
        Gets the number of inactive cycles before activate the control action
        '''
        return self.inactive_cycle
    
    def getLogFileName(self):
        '''
        Gets log file name
        '''
        return self.log_file  

    def fetchNextSegment(self): 
        '''
        Schedules the download of the next segment at current level
        '''
        playlist = self.parser.playlists[self.getCurrentLevel()]
        debug(DEBUG+1, '%s fetchNextSegment level: %d cur_index: %d', self, self.getCurrentLevel(), self.getCurrentSegmentIndex())
        #
        if self.getCurrentSegmentIndex() < playlist['start_index']:
            self.setCurrentSegmentIndex(playlist['start_index'])
        if self.getCurrentSegmentIndex() > playlist['end_index']:
            # else live video (ONLY HLS!!)
            if playlist['is_live'] and self.parser.getPlaylistType() == 'HLS':
                debug(DEBUG, '%s fetchNextSegment cur_index %d', self, self.getCurrentSegmentIndex())
                self.parser.updateLevelSegmentsList(self.getCurrentLevel()).addCallback(self._updatePlaylistDone)
            # if video is vod
            else:
                debug(DEBUG, '%s fetchNextSegment last index', self)
            return
        cur_index=self.getCurrentSegmentIndex()
        levels = self.parser.getLevels()
        url_segment = playlist['segments'][cur_index]['url']
        byterange = playlist['segments'][cur_index]['byterange']
        if byterange != '':
            debug(DEBUG, '%s fetchNextSegment level: %d (%s/s) %d/%d : %s (byterange=%s)', self,
                self.getCurrentLevel(), 
                format_bytes(float(levels[self.getCurrentLevel()]['rate'])),
                self.getCurrentSegmentIndex(), 
                playlist['end_index'], url_segment, byterange)
        else:
            debug(DEBUG, '%s fetchNextSegment level: %d (%s/s) %d/%d : %s', self,
                self.getCurrentLevel(), 
                format_bytes(float(levels[self.getCurrentLevel()]['rate'])),
                self.getCurrentSegmentIndex(), 
                playlist['end_index'], url_segment)
        if self.controller.isBuffering():
            idle_duration = 0.0 #fetch segment after the last segment download is completed
        else:
            idle_duration = self.controller.getIdleDuration()
        # load the next segment
        reactor.callLater(idle_duration, self.startDownload, url_segment, byterange)

    def startDownload(self, url, byterange=''):
        '''
        Starts the segment download and set the timestamp of start segment download

        :param url: segment url
        :param byterange: segment byterange (logical segmentation of video level)
        '''
        debug(DEBUG+1, '%s startDownload %s (byterange %s)', self, url, byterange)
        # start download
        if self.use_persistent_connection:
            # start a new connection
            if not self.connection:
                self._initConnection(url)
                return
            if not self.connection.client:
                return
            _, _, path = parse_url(url)
            self.connection.makeRequest(path, byterange)
        else:
            if byterange == '':
                d = getPage(url, agent=USER_AGENT)
            else:
                d = getPage(url, agent=USER_AGENT, headers=dict(range='bytes='+byterange))
            d.deferred.addCallback(self.playNextGotRequest, d)
            d.deferred.addErrback(self.playNextGotError, d)
        self.start_segment_request = time.time()
        
    # callback if do not use persistent connection
    def playNextGotRequest(self, data, factory):
        '''
        Updates feedbacks, calculates the control action and sets level of the next segment.

        :param data: downloaded data
        :param factory: the twisted factory (used without persistent connection)
        '''
        self.stop_segment_request = time.time()
        download_time = (self.stop_segment_request - self.start_segment_request)
        self.last_downloaded_time = download_time
        self.bwe = len(data)/download_time
        self.last_fragment_size = len(data)
        self.downloaded_bytes += len(data)
        self.downloaded_segments += 1
        debug(DEBUG, '%s __got_request: bwe: %s/s (fragment size: %s)', self, 
            format_bytes(self.bwe), format_bytes(len(data)))
        self.queuedTime = self.media_engine.getQueuedTime() + self.parser.getFragmentDuration()
        self.queuedBytes = self.media_engine.getQueuedBytes() + len(data)
        self.media_engine.pushData(data, self.parser.getFragmentDuration(), self.getCurrentLevel(), self.parser._getCapsDemuxer())
        del data
        self.cur_index += 1
        #Do something before calculating new control action
        self._onNewSegment()
        #Passing player parameters at the controller to calculate the control action
        self.updateFeedback(flag_check_buffering=False)
        #calc control action
        self.controller.setControlAction(self.controller.calcControlAction())
        # set new level
        if self.getDownloadedSegments() > self.getInactiveCycles():
            if self.enable_stress_test:
                self.stressTest()
            else:
                self.setLevel(self.controller.getControlAction())
        self.fetchNextSegment()

    # error handling if do not use persistent connection
    def playNextGotError(self, error, factory):
        '''
        Handles error when download a segment without persistent connection

        :param error: the occurred error
        :param factory: the twisted factory (used without persistent connection)
        '''
        debug(0, '%s playNextGotError url: %s error: %s', self, factory.url, error)
        # update playlist
        if self.parser.getPlaylistType()=='HLS':
            self.parser.updateLevelSegmentsList(self.cur_level).addCallback(self._updatePlaylistDone)

    def setLevel(self, rate):
        '''
        Sets the level corresponding to the rate specified in B/s

        :param rate: rate in B/s that determines the level. The level is the one whose rate is the highest below ``rate``.
        '''
        new_level = self.controller.quantizeRate(rate)
        if new_level != self.getCurrentLevel():
            debug(DEBUG, "%s setLevel: level: %d", self, new_level)
            self.setCurrentLevel(new_level)
            #self.onLevelChanged()
        return new_level

    def stressTest(self):
        '''
        Switches the video quality level cyclically every segment        
        '''
        self.check_warning_buffering = False
        if self.getCurrentLevel() == self.getMaxLevel():
           new_level = 0
        else:
            new_level = self.getCurrentLevel() + 1
        self.setCurrentLevel(new_level)
        #self.onLevelChanged()
        return new_level

    def checkBuffering(self, _arg):
        '''
        Checks if the playback is going to buffering.
        Estimates the time required to complete the download of the current segment and verifies that it is less than the playout buffer lenght.

        In the case of "warning buffering", it deletes the current segment download, calculates the control action and sets the new level.
        This feature is available only with persistent connection.
        '''
        #FIXME Can't use it without persistent connection
        if self.rate_calc.rate and self.cur_index > self.inactive_cycle:
            remaining_secs = float(self.remaining_data/self.rate_calc.rate)
            debug(DEBUG+1,"%s checkBuffering: rate %s/s, remaining_data %s, remaining_secs %.3f, queued_time %.2f ", self, 
                format_bytes(self.rate_calc.rate), format_bytes(self.remaining_data), remaining_secs, self.media_engine.getQueuedTime()) 
            #Can cancel download only if the current level is greater than 0
            if self.media_engine.getQueuedTime() < remaining_secs and self.getCurrentLevel() > 0: 
                self.connection.stop()
                self.stop_segment_request = time.time() #update stop reqest when warning buffering occurs
                self.bwe = self.rate_calc.rate
                #Passing player parameters at the controller to calculate the control action
                self.updateFeedback(flag_check_buffering=True)
                #calc control action
                self.controller.setControlAction(self.controller.calcControlAction())
                # set new level
                if self.cur_index > self.inactive_cycle:
                    self.setLevel(self.controller.getControlAction())
                    debug(0,"%s WARNING BUFFERING!!! Delete and reload segment at level: %d", self, self.getCurrentLevel())
        else:
            return
       
    def updateFeedback(self, flag_check_buffering):
        '''
        Updates dictionary of feedbacks before passing it to the controller.

        :param flag_check_buffering: true if this method is called from ``checkBuffering``. False otherwise.
        '''
        self.feedback = dict(queued_bytes=self.media_engine.getQueuedBytes(),
           queued_time=self.media_engine.getQueuedTime(),
           max_buffer_time=self.getMaxBufferTime(),
           bwe=self.getBandwidth(),
           level=self.getCurrentLevel(),
           max_level=self.getMaxLevel(),
           cur_rate=self.getCurrentRate(),
           max_rate=self.getMaxRate(),
           min_rate=self.getMinRate(),
           player_status=self.media_engine.getStatus(),
           paused_time=self.getPausedTime(),
           last_fragment_size=self.getLastFragmentBytes(),
           last_download_time=self.getLastDownloadedTime(),
           downloaded_bytes=self.getDownloadedBytes(),
           fragment_duration=self.parser.getFragmentDuration(),
           rates=self.getLevelRates(),
           is_check_buffering=flag_check_buffering
        )
        self.controller.setPlayerFeedback(self.feedback)
        
    def log(self):
        '''
        Logs useful metrics every ``log_period`` seconds
        '''
        if not self.logger:
           return
        stats = self.proc_stats.getStats()
        opts = dict(
           enqueued_b=self.media_engine.getQueuedBytes(),   #2
           enqueued_t=self.media_engine.getQueuedTime(),    #3
           bwe=self.getBandwidth(),                         #4
           cur=self.getCurrentRate(),                       #5
           level=self.getCurrentLevel(),                    #6
           max_level=self.getMaxLevel(),                    #7
           player_status=self.media_engine.getStatus(),     #8
           paused_time=self.getPausedTime(),                #9
           downloaded_bytes=self.getDownloadedBytes(),      #10
           cpu=stats["cpu_percent"],                        #11
           mem=stats["memory_percent"],                     #12    
           rss=stats["memory_rss"],                         #13
           vms=stats["memory_vms"],                         #14
           ts_start_req=self.getStartSegmentRequest(),      #15                      
           ts_stop_req=self.getStopSegmentRequest(),        #16                         
        )
        levels = self.parser.getLevels()
        for i in range(0,len(self.getLevelRates())):
           opts['q%d' %i] = float(levels[i]['rate'])
        self.logger.log(opts)
        del opts
        reactor.callLater(self.log_period, self.log)

    def _onNewSegment(self):
        '''
        Does something before calculating new control action
        '''
        pass

    # callback
    def _onDataReceiving(self, connection, data_diff, remaining_data):
        '''
        Does something before segment download is completed (used with persistent connection)
        '''
        self.remaining_data = remaining_data
        debug(DEBUG+1, '%s _onDataReceiving: %s %s', self, format_bytes(data_diff), format_bytes(remaining_data))
        self.rate_calc.update(data_diff)

    # callback
    def _onDataReceived(self, connection, data):
        '''
        Does something when segment download is completed (used with persistent connection)
        '''

        debug(DEBUG+1, '%s _onDataReceived: %s', self, format_bytes(len(data)))  
        self.playNextGotRequest(data, None)
        
    def _initConnection(self, url):
        '''
        Initializes connection with url (only with persistent connection)
        '''
        if self.connection:
            self.connection.stop()
        debug(DEBUG+1, '%s _initConnection: %s', self, url)
        self.connection = ClientFactory(url)
        self.connection.connect('connection-made', self._onConnectionMade)
        self.connection.connect('connection-lost', self._onConnectionLost)
        self.connection.connect('data-received', self._onDataReceived)
        self.connection.connect('data-receiving', self._onDataReceiving)

    def _onConnectionMade(self, connection, host):
        '''
        Does something when connection with host is established (only with persistent connection).
        '''
        debug(DEBUG+1, '%s _onConnectionMade: %s', self, host)
        if self.logger:
            self.logger.log_comment('Host: %s' %host)
        reactor.callLater(0.1, self.fetchNextSegment)

    def _onConnectionLost(self, connection):
        '''
        Does something when connection with host is lost (only with persistent connection).
        '''
        self.connection = None
        if self.parser.getPlaylistType()=='HLS':
            debug(0, '%s _onConnectionLost', self)
            self.parser.updateLevelSegmentsList(self.cur_level).addCallback(self._updatePlaylistDone)
        else:   #only for youtube (for check buffering)
            debug(DEBUG, '%s _onConnectionLost', self)
            self.fetchNextSegment()

    #When status changes calls 'onPlaying' and 'onPaused' hooks
    #callback
    def _onStatusChanged(self, media_engine):
        '''
        Does something when player status change from play to pause and viceversa
        '''
        if media_engine.status == media_engine.PLAYING:
            self.controller.onPlaying()
            self.paused_time += time.time() - self.t_paused 
        else:
            self.controller.onPaused()
            self.t_paused = time.time()

    #callback
    def _updatePlaylistDone(self, data):
        '''
        Called when the playlist for the current level is update
        '''
        playlist = self.parser.playlists[self.getCurrentLevel()]
        debug(DEBUG+1, '%s playlist: %s', self, pformat(playlist))
        # start play if playlist has more than 2 fragments
        if len(playlist['segments']) > 2 and self.getCurrentSegmentIndex() < playlist['end_index']:
            self.fetchNextSegment()
        else:
            reactor.callLater(self.parser.getFragmentDuration(), self.fetchNextSegment)
Exemple #4
0
class TapasPlayer(object):
    def __init__(
            self,
            controller,
            parser,
            media_engine,
            log_sub_dir='',
            log_period=0.1,
            max_buffer_time=60,
            inactive_cycle=1,
            initial_level=0,
            #FIXME reset to TRUE again!!
            use_persistent_connection=True,
            #changed by susanna
            check_warning_buffering=False,
            stress_test=False):

        # player components
        self.controller = controller
        self.parser = parser
        self.media_engine = media_engine

        # log
        self.logger = None
        self.log_file = None
        self.log_dir = 'logs'
        self.log_sub_dir = log_sub_dir
        self.log_period = log_period
        self.log_prefix = ''
        self.log_comment = ''
        #
        self.max_buffer_time = max_buffer_time
        self.inactive_cycle = inactive_cycle  #active control action after inactive_cycle+1 segments
        #
        self.use_persistent_connection = use_persistent_connection
        self.connection = None
        #
        self.cur_level = initial_level
        self.cur_index = 0
        self.enable_stress_test = stress_test  #flag to enable stress test, switch level every segment cyclically
        #
        self.check_warning_buffering = check_warning_buffering  #flag to enable check for warning buffering
        self.rate_calc = RateCalc(period=3.0, alpha=0.0)
        self.remaining_data = 0
        #
        self.bwe = 0
        self.downloaded_bytes = 0
        self.downloaded_segments = 0
        self.last_fragment_size = 0
        self.start_segment_request = -1.0
        self.stop_segment_request = -1.0
        self.last_downloaded_time = -1.0
        self.t_paused = time.time()
        self.paused_time = 0.0
        self.queuedBytes = 0
        self.queuedTime = 0.0
        #
        self.proc_stats = ProcessStats()

        self.prio = 0

        #Initialize the parameters passed at the controller after the download of every segment
        self.feedback = dict(queued_bytes=0,
                             queued_time=0.0,
                             max_buffer_time=self.max_buffer_time,
                             bwe=0.0,
                             level=self.cur_level,
                             max_level=-1,
                             cur_rate=0.0,
                             max_rate=0.0,
                             min_rate=0.0,
                             player_status=0,
                             paused_time=0.0,
                             last_fragment_size=0,
                             last_download_time=0.0,
                             downloaded_bytes=0,
                             fragment_duration=0.0,
                             rates=[])
        self.controller.setPlayerFeedback(self.feedback)

    def __repr__(self):
        return '<TapasPlayer-%d>' % id(self)

    def play(self):
        '''
        Starts Parser, creates Logger, initializes MediaEngine, and fetches the first segment when the parser has finished
        '''
        self.parser.loadPlaylist()

        def _on_done(res):
            playlists = self.parser.getPlaylists()
            levels = self.parser.getLevels()
            fragment_duration = self.parser.getFragmentDuration()
            caps = self.parser._getCapsDemuxer()
            self.controller.setIdleDuration(
                fragment_duration
            )  #Default pause interval when isBuffering return False
            if self.getCurrentLevel() > self.getMaxLevel(
            ) or self.getCurrentLevel() == -1:
                self.setCurrentLevel(self.getMaxLevel())
            #opts for Logger
            opts = [
                ('enqueued_b', int, ''),  #2
                ('enqueued_t', float, 'visible=1,subplot=2'),  #3
                ('bwe', float, 'visible=1,subplot=1'),  #4
                ('cur', int, 'visible=1,subplot=1'),  #5
                ('level', int, 'visible=1,subplot=3'),  #6
                ('max_level', int, ''),  #7
                ('player_status', int, 'visible=1,subplot=3'),  #8
                ('paused_time', float, ''),  #9
                ('downloaded_bytes', int, ''),  #10
                ('cpu', float, 'visible=1,subplot=4'),  #11
                ('mem', float, 'visible=1,subplot=5'),  #12
                ('rss', float, ''),  #13
                ('vms', float, ''),  #14
                ('ts_start_req', float, ''),  #15
                ('ts_stop_req', float, ''),  #16
            ]
            for i in range(0, len(levels)):
                opts.append(('q%d' % i, int, 'visible=0'))
            if self.log_sub_dir:
                self.log_dir = self.log_dir + '/' + self.log_sub_dir
                #Create Logger
                self.logger = Logger(opts,
                                     log_period=self.log_period,
                                     log_prefix=self.log_prefix,
                                     comment=self.log_comment,
                                     log_dir=self.log_dir)
                debug(DEBUG + 1, 'levels: %s', levels)
                debug(DEBUG + 1, 'playlists: %s', playlists)
            if self.enable_stress_test:
                self.inactive_cycle = 0
            if self.check_warning_buffering:
                self.rate_calc.start()
                self.rate_calc.connect('update', self.checkBuffering)
            #Init media_engine
            self.media_engine.setVideoContainer(
                self.parser.getVideoContainer())
            self.media_engine.connect('status-changed', self._onStatusChanged)
            self.media_engine.start()
            #start logger
            reactor.callLater(self.log_period, self.log)
            #
            print colored("[play()] I call fetchNextSegment() [1]", 'red')
            self.fetchNextSegment()
            print colored("[play()] I called fetchNextSegment() [2]", 'red')

        self.parser.deferred.addCallback(_on_done)

    def getMaxBufferTime(self):
        '''
        Gets max buffer in seconds under which the playback is considered in Buffering by default
        '''
        return self.max_buffer_time

    def getCurrentLevel(self):
        '''
        Gets index of current level starting from 0 for the lowest video quality level
        '''
        return self.cur_level

    def setCurrentLevel(self, level):
        '''
        Sets index of current level starting from 0 for the lowest video quality level

        :param level: the level index
        '''
        self.cur_level = level

    def setPrio(self, prio):
        '''
	define whether next segment will be prioritized or not
	'''
        self.prio = prio

    def getPrio(self):
        '''
	Returns 1 if next segment is priorizied, 0 if next segment is not prioritized
	'''
        return self.prio

    def getMaxLevel(self):
        '''
        Gets index of maximum level starting from 0 for the lowest video quality level
        '''
        return len(self.parser.getLevels()) - 1

    def getCurrentSegmentIndex(self):
        '''
        Gets index of the current segment of the sub-playlist
        '''
        return self.cur_index

    def setCurrentSegmentIndex(self, index):
        '''
        Sets index of the current segment of the sub-playlist

        :param index: segment index
        '''
        self.cur_index = index

    def getCurrentRate(self):
        '''
        Gets current video quality level rate in B/s
        '''
        levels = self.parser.getLevels()
        cur_rate = float(levels[self.getCurrentLevel()]['rate'])
        return cur_rate

    def getMaxRate(self):
        '''
        Gets maximum video quality level rate in B/s
        '''
        levels = self.parser.getLevels()
        rates = [float(i['rate']) for i in levels]
        return max(rates)

    def getMinRate(self):
        '''
        Gets minimum video quality level rate in B/s
        '''
        levels = self.parser.getLevels()
        rates = [float(i['rate']) for i in levels]
        return min(rates)

    def getLevelRates(self):
        '''
        Gets a list of video quality level rates in B/s
        '''
        levels = self.parser.getLevels()
        _r = [float(i['rate']) for i in levels]
        rates = []
        for i in range(0, len(_r)):
            rates.append(_r[i])
        return rates

    def getLevelResolutions(self):
        '''
        Gets a list of available video resolutions
        '''
        levels = self.parser.getLevels()
        resolutions = [i['resolution'] for i in levels]
        return resolutions

    def getLastDownloadedTime(self):
        '''
        Gets time spent to download the last segment
        '''
        return self.last_downloaded_time

    def getStartSegmentRequest(self):
        '''
        Gets timestamp when starts the download of the last segment
        '''
        return self.start_segment_request

    def getStopSegmentRequest(self):
        '''
        Gets timestamp when stops the download of the last segment
        '''
        return self.stop_segment_request

    def getLastFragmentBytes(self):
        '''
        Gets the last fragment size in B
        '''
        return self.last_fragment_size

    def getDownloadedBytes(self):
        '''
        Gets total downloaded bytes in B
        '''
        return self.downloaded_bytes

    def getDownloadedSegments(self):
        '''
        Gets total number of downloaded segments
        '''
        return self.downloaded_segments

    def getBandwidth(self):
        '''
        Gets last estimated available bandwidth in B/s
        '''
        return self.bwe

    def getPausedTime(self):
        '''
        Gets time spent on pause
        '''
        return self.paused_time

    def getInactiveCycles(self):
        '''
        Gets the number of inactive cycles before activate the control action
        '''
        return self.inactive_cycle

    def getLogFileName(self):
        '''
        Gets log file name
        '''
        return self.log_file

    def fetchNextSegment(self):
        '''
        Schedules the download of the next segment at current level
        '''

        playlist = self.parser.playlists[self.getCurrentLevel()]
        debug(DEBUG + 1, '%s fetchNextSegment level: %d cur_index: %d', self,
              self.getCurrentLevel(), self.getCurrentSegmentIndex())
        #
        if self.getCurrentSegmentIndex() < playlist['start_index']:
            self.setCurrentSegmentIndex(playlist['start_index'])
        if self.getCurrentSegmentIndex() > playlist['end_index']:
            # else live video (ONLY HLS!!)
            if playlist['is_live'] and self.parser.getPlaylistType() == 'HLS':
                debug(DEBUG, '%s fetchNextSegment cur_index %d', self,
                      self.getCurrentSegmentIndex())
                self.parser.updateLevelSegmentsList(
                    self.getCurrentLevel()).addCallback(
                        self._updatePlaylistDone)
            # if video is vod
            else:
                debug(DEBUG, '%s fetchNextSegment last index', self)
            return
        cur_index = self.getCurrentSegmentIndex()
        levels = self.parser.getLevels()
        url_segment = playlist['segments'][cur_index]['url']
        byterange = playlist['segments'][cur_index]['byterange']
        if byterange != '':
            debug(
                DEBUG,
                '%s fetchNextSegment level: %d (%s/s) %d/%d : %s (byterange=%s)',
                self, self.getCurrentLevel(),
                format_bytes(float(levels[self.getCurrentLevel()]['rate'])),
                self.getCurrentSegmentIndex(), playlist['end_index'],
                url_segment, byterange)
        else:
            debug(DEBUG, '%s fetchNextSegment level: %d (%s/s) %d/%d : %s',
                  self, self.getCurrentLevel(),
                  format_bytes(float(levels[self.getCurrentLevel()]['rate'])),
                  self.getCurrentSegmentIndex(), playlist['end_index'],
                  url_segment)
        if self.controller.isBuffering():
            idle_duration = 0.0  #fetch segment after the last segment download is completed
        else:
            idle_duration = self.controller.getIdleDuration()
        # load the next segment
        reactor.callLater(idle_duration, self.startDownload, url_segment,
                          byterange)

    def startDownload(self, url, byterange=''):
        '''
        Starts the segment download and set the timestamp of start segment download

        :param url: segment url
        :param byterange: segment byterange (logical segmentation of video level)
        '''
        debug(DEBUG + 1, '%s startDownload %s (byterange %s)', self, url,
              byterange)
        # start download
        if self.use_persistent_connection:
            # start a new connection
            if not self.connection:
                self._initConnection(url)
                return
            if not self.connection.client:
                return

            _, _, path = parse_url(url)
            segSize = float(float(self.getLastFragmentBytes() * 8)) / 1000
            self.connection.makeRequest(
                path + "?buffer=" + str(self.media_engine.getQueuedTime()) +
                "?bwestim=" + str(self.getBandwidth()) + "?SegmentDur=" +
                str(self.parser.getFragmentDuration()) + "?SegmentSizeLa=" +
                str(segSize) + "?quali=" + str(self.getCurrentRate()) +
                "?pid=" + str(os.getpid()), byterange)

        else:
            if byterange == '':
                d = getPage(url, agent=USER_AGENT)
            else:
                d = getPage(url,
                            agent=USER_AGENT,
                            headers=dict(range='bytes=' + byterange))
            d.deferred.addCallback(self.playNextGotRequest, d)
            d.deferred.addErrback(self.playNextGotError, d)
        print("CURRENT BUFFER IS " + str(self.media_engine.getQueuedTime()))
        self.start_segment_request = time.time()

    # callback if do not use persistent connection
    def playNextGotRequest(self, data, factory):
        '''
        Updates feedbacks, calculates the control action and sets level of the next segment.

        :param data: downloaded data
        :param factory: the twisted factory (used without persistent connection)
        '''
        self.stop_segment_request = time.time()
        download_time = (self.stop_segment_request -
                         self.start_segment_request)
        print colored("DOWNLOAD COMPLETETD ")
        self.last_downloaded_time = download_time
        self.bwe = len(data) / download_time
        self.last_fragment_size = len(data)
        self.downloaded_bytes += len(data)
        self.downloaded_segments += 1
        debug(DEBUG, '%s __got_request: bwe: %s/s (fragment size: %s)', self,
              format_bytes(self.bwe), format_bytes(len(data)))
        self.queuedTime = self.media_engine.getQueuedTime(
        ) + self.parser.getFragmentDuration()
        self.queuedBytes = self.media_engine.getQueuedBytes() + len(data)
        self.media_engine.pushData(data, self.parser.getFragmentDuration(),
                                   self.getCurrentLevel(),
                                   self.parser._getCapsDemuxer())
        del data
        self.cur_index += 1
        #Do something before calculating new control action
        self._onNewSegment()
        #Passing player parameters at the controller to calculate the control action
        self.updateFeedback(flag_check_buffering=False)
        #calc control action
        self.controller.setControlAction(self.controller.calcControlAction())
        # set new level
        if self.getDownloadedSegments() > self.getInactiveCycles():
            if self.enable_stress_test:
                self.stressTest()
            else:
                self.setLevel(self.controller.getControlAction())
        print colored("[playNextGotRequest()] I call fetchNextSegment() [1]",
                      'red')
        self.fetchNextSegment()
        print colored("[playNextGotRequest()] I called fetchNextSegment() [2]",
                      'red')

    # error handling if do not use persistent connection
    def playNextGotError(self, error, factory):
        '''
        Handles error when download a segment without persistent connection

        :param error: the occurred error
        :param factory: the twisted factory (used without persistent connection)
        '''
        debug(0, '%s playNextGotError url: %s error: %s', self, factory.url,
              error)
        # update playlist
        if self.parser.getPlaylistType() == 'HLS':
            self.parser.updateLevelSegmentsList(self.cur_level).addCallback(
                self._updatePlaylistDone)

    def setLevel(self, rate):
        '''
        Sets the level corresponding to the rate specified in B/s

        :param rate: rate in B/s that determines the level. The level is the one whose rate is the highest below ``rate``.
        '''

        if self.getPrio() == 1:
            new_level = 0
            self.setCurrentLevel(new_level)

            return new_level

        else:

            new_level = self.controller.quantizeRate(rate)
            if new_level != self.getCurrentLevel():
                debug(DEBUG, "%s setLevel: level: %d", self, new_level)
                self.setCurrentLevel(new_level)

                #self.onLevelChanged()
            return new_level

    def stressTest(self):
        '''
        Switches the video quality level cyclically every segment        
        '''
        self.check_warning_buffering = False
        if self.getCurrentLevel() == self.getMaxLevel():
            new_level = 0
        else:
            new_level = self.getCurrentLevel() + 1
        self.setCurrentLevel(new_level)
        #self.onLevelChanged()
        return new_level

    def checkBuffering(self, _arg):
        #exactly what we are doing?!?!?!?
        '''
        Checks if the playback is going to buffering.
        Estimates the time required to complete the download of the current segment and verifies that it is less than the playout buffer lenght.

        In the case of "warning buffering", it deletes the current segment download, calculates the control action and sets the new level.
        This feature is available only with persistent connection.
        '''
        #FIXME Can't use it without persistent connection
        if self.rate_calc.rate and self.cur_index > self.inactive_cycle:
            remaining_secs = float(self.remaining_data / self.rate_calc.rate)
            debug(
                DEBUG + 1,
                "%s checkBuffering: rate %s/s, remaining_data %s, remaining_secs %.3f, queued_time %.2f ",
                self, format_bytes(self.rate_calc.rate),
                format_bytes(self.remaining_data), remaining_secs,
                self.media_engine.getQueuedTime())
            #Can cancel download only if the current level is greater than 0
            if self.media_engine.getQueuedTime(
            ) < remaining_secs and self.getCurrentLevel() > 0:
                self.connection.stop()
                self.stop_segment_request = time.time(
                )  #update stop reqest when warning buffering occurs
                self.bwe = self.rate_calc.rate
                #Passing player parameters at the controller to calculate the control action
                self.updateFeedback(flag_check_buffering=True)
                #calc control action
                self.controller.setControlAction(
                    self.controller.calcControlAction())
                # set new level
                if self.cur_index > self.inactive_cycle:
                    self.setLevel(self.controller.getControlAction())
                    debug(
                        0,
                        "%s WARNING BUFFERING!!! Delete and reload segment at level: %d",
                        self, self.getCurrentLevel())
        else:
            return

    def updateFeedback(self, flag_check_buffering):
        '''
        Updates dictionary of feedbacks before passing it to the controller.

        :param flag_check_buffering: true if this method is called from ``checkBuffering``. False otherwise.
        '''
        self.feedback = dict(
            queued_bytes=self.media_engine.getQueuedBytes(),
            queued_time=self.media_engine.getQueuedTime(),
            max_buffer_time=self.getMaxBufferTime(),
            bwe=self.getBandwidth(),
            level=self.getCurrentLevel(),
            max_level=self.getMaxLevel(),
            cur_rate=self.getCurrentRate(),
            max_rate=self.getMaxRate(),
            min_rate=self.getMinRate(),
            player_status=self.media_engine.getStatus(),
            paused_time=self.getPausedTime(),
            last_fragment_size=self.getLastFragmentBytes(),
            last_download_time=self.getLastDownloadedTime(),
            downloaded_bytes=self.getDownloadedBytes(),
            fragment_duration=self.parser.getFragmentDuration(),
            rates=self.getLevelRates(),
            is_check_buffering=flag_check_buffering)
        self.controller.setPlayerFeedback(self.feedback)

    def log(self):
        '''
        Logs useful metrics every ``log_period`` seconds
        '''
        if not self.logger:
            return
        stats = self.proc_stats.getStats()
        opts = dict(
            enqueued_b=self.media_engine.getQueuedBytes(),  #2
            enqueued_t=self.media_engine.getQueuedTime(),  #3
            bwe=self.getBandwidth(),  #4
            cur=self.getCurrentRate(),  #5
            level=self.getCurrentLevel(),  #6
            max_level=self.getMaxLevel(),  #7
            player_status=self.media_engine.getStatus(),  #8
            paused_time=self.getPausedTime(),  #9
            downloaded_bytes=self.getDownloadedBytes(),  #10
            cpu=stats["cpu_percent"],  #11
            mem=stats["memory_percent"],  #12    
            rss=stats["memory_rss"],  #13
            vms=stats["memory_vms"],  #14
            ts_start_req=self.getStartSegmentRequest(
            ),  #15                      
            ts_stop_req=self.getStopSegmentRequest(
            ),  #16                         
        )
        levels = self.parser.getLevels()
        for i in range(0, len(self.getLevelRates())):
            opts['q%d' % i] = float(levels[i]['rate'])
        self.logger.log(opts)
        del opts
        reactor.callLater(self.log_period, self.log)

    def _onNewSegment(self):
        '''
        Does something before calculating new control action
        '''
        pass

    # callback
    def _onDataReceiving(self, connection, data_diff, remaining_data):
        '''
        Does something before segment download is completed (used with persistent connection)
        '''
        self.remaining_data = remaining_data
        debug(DEBUG + 1, '%s _onDataReceiving: %s %s', self,
              format_bytes(data_diff), format_bytes(remaining_data))
        self.rate_calc.update(data_diff)

    # callback
    def _onDataReceived(self, connection, data):
        '''
        Does something when segment download is completed (used with persistent connection)
        '''

        debug(DEBUG + 1, '%s _onDataReceived: %s', self,
              format_bytes(len(data)))
        self.playNextGotRequest(data, None)

    def _initConnection(self, url):
        '''
        Initializes connection with url (only with persistent connection)
        '''
        if self.connection:
            self.connection.stop()
        debug(DEBUG + 1, '%s _initConnection: %s', self, url)
        self.connection = ClientFactory(url)
        self.connection.connect('connection-made', self._onConnectionMade)
        self.connection.connect('connection-lost', self._onConnectionLost)
        self.connection.connect('data-received', self._onDataReceived)
        self.connection.connect('data-receiving', self._onDataReceiving)

    def _onConnectionMade(self, connection, host):
        '''
        Does something when connection with host is established (only with persistent connection).
        '''
        debug(DEBUG + 1, '%s _onConnectionMade: %s', self, host)
        if self.logger:
            self.logger.log_comment('Host: %s' % host)
        print colored("[_onConnectionMade] I call fetchNextSegment() [1]",
                      'red')
        reactor.callLater(0.1, self.fetchNextSegment)
        print colored("[_onConnectionMade] I call fetchNextSegment() [2]",
                      'red')

    def _onConnectionLost(self, connection):
        '''
        Does something when connection with host is lost (only with persistent connection).
        '''
        self.connection = None
        if self.parser.getPlaylistType() == 'HLS':
            debug(0, '%s _onConnectionLost', self)
            self.parser.updateLevelSegmentsList(self.cur_level).addCallback(
                self._updatePlaylistDone)
        else:  #only for youtube (for check buffering)
            debug(DEBUG, '%s _onConnectionLost', self)
            print colored("[_onConnectionLost] I call fetchNextSegment() [1]",
                          'red')
            self.fetchNextSegment()
            print colored("[_onConnectionLost] I call fetchNextSegment() [2]",
                          'red')

    #When status changes calls 'onPlaying' and 'onPaused' hooks
    #callback
    def _onStatusChanged(self, media_engine):
        '''
        Does something when player status change from play to pause and viceversa
        '''
        if media_engine.status == media_engine.PLAYING:
            self.controller.onPlaying()
            self.paused_time += time.time() - self.t_paused
        else:
            self.controller.onPaused()
            self.t_paused = time.time()

    #callback
    def _updatePlaylistDone(self, data):
        '''
        Called when the playlist for the current level is update
        '''
        playlist = self.parser.playlists[self.getCurrentLevel()]
        debug(DEBUG + 1, '%s playlist: %s', self, pformat(playlist))
        # start play if playlist has more than 2 fragments
        if len(playlist['segments']) > 2 and self.getCurrentSegmentIndex(
        ) < playlist['end_index']:
            print colored("[updatePlaylistDone] I call fetchNextSegment() [1]",
                          'red')
            self.fetchNextSegment()
            print colored(
                "[updatePlaylistDone] I called fetchNextSegment() [2]", 'red')
        else:
            print colored("[updatePlaylistDone] I call fetchNextSegment() [1]",
                          'red')
            reactor.callLater(self.parser.getFragmentDuration(),
                              self.fetchNextSegment)
            print colored(
                "[updatePlaylistDone] I called fetchNextSegment() [2]", 'red')
Exemple #5
0
class TapasPlayer(object):
    def __init__(self,
                 controller,
                 view_controller,
                 parser,
                 media_engine,
                 HMDEmulator,
                 vr,
                 log_sub_dir='',
                 log_period=0.1,
                 max_buffer_time=60,
                 inactive_cycle=1,
                 initial_level=0,
                 initial_view=0,
                 use_persistent_connection=True,
                 check_warning_buffering=True,
                 save_chunks=False,
                 stress_test=False):

        # player components
        self.initial_view = initial_view
        self.n_views = 1
        self.controller = controller
        self.viewController = view_controller
        self.parser = parser
        self.media_engine = media_engine
        # log
        self.logger = None
        self.log_file = None
        self.log_dir = 'logs'
        self.log_sub_dir = log_sub_dir
        self.log_period = log_period
        self.log_prefix = ''
        self.log_comment = ''
        self.save_chunks = save_chunks
        self.chunkFolder = os.getpid()
        #
        self.max_buffer_time = max_buffer_time
        self.inactive_cycle = inactive_cycle  #active control action after inactive_cycle+1 segments
        #
        self.use_persistent_connection = use_persistent_connection
        self.connection = None
        self.last_redirect_host = None
        # self.conn_pool = ConnectionsPool()
        #
        self.cur_level = initial_level
        self.cur_view = initial_view
        self.cur_index = 0
        self.enable_stress_test = stress_test  #flag to enable stress test, switch level every segment cyclically
        #
        self.check_warning_buffering = check_warning_buffering  #flag to enable check for warning buffering
        self.rate_calc = RateCalc(period=3.0, alpha=0.0)
        self.remaining_data = 0
        #
        self.bwe = 0
        self.downloaded_bytes = 0
        self.downloaded_segments = 0
        self.last_fragment_size = 0
        self.start_segment_request = -1.0
        self.stop_segment_request = -1.0
        self.last_downloaded_time = -1.0
        self.t_paused = time.time()
        self.t_experiment_started = time.time(
        )  #Timestamp marking the beginning of the experiment
        self.paused_time = 0.0
        self.queuedBytes = 0
        self.queuedTime = 0.0
        #
        self.proc_stats = ProcessStats()

        #vr
        self.HMDEmulator = HMDEmulator
        self.cur_angles = [0, 0, 0]
        self.vr = vr

        #Initialize the parameters passed at the controller after the download of every segment
        self.feedback = dict(
            queued_bytes=0,
            queued_time=0.0,
            max_buffer_time=self.max_buffer_time,
            bwe=0.0,
            level=self.cur_level,
            max_level=-1,
            cur_rate=0.0,
            max_rate=0.0,
            min_rate=0.0,
            player_status=0,
            paused_time=0.0,
            last_fragment_size=0,
            last_download_time=0.0,
            start_segment_request=0.0,
            stop_segment_request=time.time(),
            downloaded_bytes=0,
            fragment_duration=0.0,
            rates=[],
            #parameters of the views
            view_angles=[0.0, 0.0, 0.0],
            n_views=self.n_views,
            cur_view=self.cur_view,
            roi_angle=120,
            #Set of yaw angles associated to the different views (angular position of the ROI center of the i-th view)
            delta=20,
            yaw_angles=self.getYawAngles(1),
            threshold_angle=180)
        self.controller.setPlayerFeedback(self.feedback)
        #self.viewController.setPlayerFeedback(self.feedback)

    def __repr__(self):
        return '<TapasPlayer-%d>' % id(self)

    def play(self):
        '''
        Starts Parser, creates Logger, initializes MediaEngine, and fetches the first segment when the parser has finished
        '''
        self.parser.loadPlaylist()

        def _on_done(res):
            playlists = self.parser.getPlaylists()
            levels = self.parser.getLevels()
            fragment_duration = self.parser.getFragmentDuration()
            caps = self.parser._getCapsDemuxer()
            self.controller.setIdleDuration(
                fragment_duration
            )  #Default pause interval when isBuffering return False
            if self.getCurrentLevel() > self.getMaxLevel(
            ) or self.getCurrentLevel() == -1:
                self.setCurrentLevel(self.getMaxLevel())
            #opts for Logger
            opts = [
                ('enqueued_b', int, ''),  #2
                ('enqueued_t', float, ''),  #3
                ('bwe', float, ''),  #4
                ('cur', int, ''),  #5
                ('level', int, ''),  #6
                ('max_level', int, ''),  #7
                ('player_status', int, ''),  #8
                ('paused_time', float, ''),  #9
                ('downloaded_bytes', int, ''),  #10
                ('cpu', float, ''),  #11
                ('mem', float, ''),  #12
                ('rss', float, ''),  #13
                ('vms', float, ''),  #14
                ('ts_start_req', float, ''),  #15
                ('ts_stop_req', float, ''),  #16
                ('alpha', int, ''),  #17
                ('view', int, '')  #18
            ]

            for i in range(0, len(levels)):
                opts.append(('q%d' % i, int, ''))

            if self.log_sub_dir:
                self.log_dir = self.log_dir + os.path.sep + self.log_sub_dir
                if self.chunkFolder:
                    self.log_dir = "{0!s}{1!s}{2!s}".format(
                        self.log_dir, os.path.sep, self.chunkFolder)

                #Create Logger
                self.logger = Logger(opts,
                                     log_period=self.log_period,
                                     log_prefix=self.log_prefix,
                                     comment=self.log_comment,
                                     log_dir=self.log_dir)
                debug(DEBUG + 1, 'levels: %s', levels)
                debug(DEBUG + 1, 'playlists: %s', playlists)

            if self.enable_stress_test:
                self.inactive_cycle = 0

            if self.check_warning_buffering:
                self.rate_calc.start()
                self.rate_calc.connect('update', self.checkBuffering)

            #Init media_engine
            self.media_engine.setVideoContainer(
                self.parser.getVideoContainer())
            self.media_engine.connect('status-changed', self._onStatusChanged)
            self.media_engine.setViews(self.parser.getViews())

            if (self.vr):
                self.HMDEmulator.start(self.t_experiment_started)
            try:
                #if the media engine is gst
                if self.media_engine.pipeline == None:
                    self.media_engine.start()
                    #self.media_engine.start()
            except Exception as e:
                self.media_engine.start()

            #start logger
            reactor.callLater(self.log_period, self.log)

            #Start playing
            self.fetchNextSegment()

        self.parser.deferred.addCallback(_on_done)

    def getMaxBufferTime(self):
        '''
        Gets max buffer in seconds under which the playback is considered in Buffering by default
        '''
        return self.max_buffer_time

    def getCurrentLevel(self):
        '''
        Gets index of current level starting from 0 for the lowest video quality level
        '''
        return self.cur_level

    def setCurrentLevel(self, level):
        '''
        Sets index of current level starting from 0 for the lowest video quality level

        :param level: the level index
        '''
        self.cur_level = level

    def getMaxLevel(self):
        '''
        Gets index of maximum level starting from 0 for the lowest video quality level
        '''
        return len(self.parser.getLevels()) - 1

    def getCurrentSegmentIndex(self):
        '''
        Gets index of the current segment of the sub-playlist
        '''
        return self.cur_index

    def setCurrentSegmentIndex(self, index):
        '''
        Sets index of the current segment of the sub-playlist

        :param index: segment index
        '''
        self.cur_index = index

    def getCurrentRate(self):
        '''
        Gets current video quality level rate in B/s
        '''
        levels = self.parser.getLevels()
        cur_rate = float(levels[self.getCurrentLevel()]['rate'])
        return cur_rate

    def getMaxRate(self):
        '''
        Gets maximum video quality level rate in B/s
        '''
        levels = self.parser.getLevels()
        rates = [float(i['rate']) for i in levels]
        return max(rates)

    def getMinRate(self):
        '''
        Gets minimum video quality level rate in B/s
        '''
        levels = self.parser.getLevels()
        rates = [float(i['rate']) for i in levels]
        return min(rates)

    def getLevelRates(self):
        '''
        Gets a list of video quality level rates in B/s
        '''
        levels = self.parser.getLevels()
        _r = [float(i['rate']) for i in levels]
        rates = []
        for i in range(0, len(_r)):
            rates.append(_r[i])
        return rates

    def getLevelResolutions(self):
        '''
        Gets a list of available video resolutions
        '''
        levels = self.parser.getLevels()
        resolutions = [i['resolution'] for i in levels]
        return resolutions

    def getLastDownloadedTime(self):
        '''
        Gets time spent to download the last segment
        '''
        return self.last_downloaded_time

    def getStartSegmentRequest(self):
        '''
        Gets timestamp when starts the download of the last segment
        '''
        return self.start_segment_request

    def getStopSegmentRequest(self):
        '''
        Gets timestamp when stops the download of the last segment
        '''
        return self.stop_segment_request

    def getLastFragmentBytes(self):
        '''
        Gets the last fragment size in B
        '''
        return self.last_fragment_size

    def getDownloadedBytes(self):
        '''
        Gets total downloaded bytes in B
        '''
        return self.downloaded_bytes

    def getDownloadedSegments(self):
        '''
        Gets total number of downloaded segments
        '''
        return self.downloaded_segments

    def getBandwidth(self):
        '''
        Gets last estimated available bandwidth in B/s
        '''
        return self.bwe

    def getPausedTime(self):
        '''
        Gets time spent on pause
        '''
        return self.paused_time

    def getInactiveCycles(self):
        '''
        Gets the number of inactive cycles before activate the control action
        '''
        return self.inactive_cycle

    def getLogFileName(self):
        '''
        Gets log file name
        '''
        return self.log_file

    def getNumViews(self):
        return self.parser.getViews()

    def setView(self):
        '''
        Sets the view corresponding to the yaw angle read from csv file 

        '''
        if (self.feedback['n_views'] == 1):
            self.setCurrentView(self.initial_view)
            return self.initial_view
        else:
            self.cur_angles[0] = math.degrees(
                self.HMDEmulator.getPitchAngle()[0]) * math.pi
            self.cur_angles[1] = math.degrees(
                self.HMDEmulator.getPitchAngle()[1]) * math.pi
            self.cur_angles[2] = math.degrees(
                self.HMDEmulator.getPitchAngle()[2]) * math.pi
            new_view = self.viewController.getView(self.getCurrentView(),
                                                   self.getCurrentAngles())
            if new_view != self.getCurrentView():
                debug(DEBUG, "%s Angle: %d, setView: view: %d", self,
                      self.getCurrentAngles()[0], new_view)
                self.setCurrentView(new_view)
        return new_view

    def setCurrentView(self, view):
        '''
        Sets index of current view 

        :param view: the view index
        '''

        if view > self.getNumViews() or view < 0:
            raise ValueError('View index out of bounds')
        self.cur_view = view

    def getCurrentView(self):
        '''
        Gets index of current view
        
        '''
        return self.cur_view

    def getCurrentAngles(self):

        return self.cur_angles

    def getYawAngles(self, views):
        '''
        Gets the yaw angles vector 

        :param views: the number of views
        
        '''
        v = []
        v.insert(0, 0)  #Roi angle of the first view at 0 degree
        for i in range(views):
            if (i >= 1):
                v.insert(i, v[i - 1] +
                         360 / views)  #divide equally the circumference
        return v

    def fetchNextSegment(self):
        '''
        Schedules the download of the next segment at current level and view
        '''
        playlist = self.parser.getSinglePlaylist(self.getCurrentLevel(),
                                                 self.getCurrentView())
        debug(
            DEBUG + 1,
            '%s fetchNextSegment level: %d angles: %d view: %d cur_index: %d',
            self.getCurrentLevel(),
            self.getCurrentAngles()[0], self.getCurrentView(),
            self.getCurrentSegmentIndex())
        #
        #print str("self.getCurrentSegmentIndex = {}, playlist = {}".format(self.getCurrentSegmentIndex(), playlist['start_index']))

        if self.getCurrentSegmentIndex() < playlist['start_index']:
            self.setCurrentSegmentIndex(playlist['start_index'])

        if self.getCurrentSegmentIndex() > playlist['end_index']:
            # else live video (ONLY HLS!!)
            if playlist['is_live'] and self.parser.getPlaylistType() == 'HLS':
                print("playlist['is_live'] :" + str(playlist['is_live']))
                debug(DEBUG, '%s fetchNextSegment cur_index %d', self,
                      self.getCurrentSegmentIndex())
                self.parser.updateLevelSegmentsList(
                    self.getCurrentLevel(), self.getCurrentView()).addCallback(
                        self._updatePlaylistDone)
            # if video is vod
            else:
                debug(DEBUG, '%s fetchNextSegment last index', self)

            # set max video duration for stopping media_engine
            self.media_engine.setVideoDuration(playlist['duration'])
            #print("STOP PLAYING.")
            return

        cur_index = self.getCurrentSegmentIndex()
        levels = self.parser.getLevels()
        url_segment = playlist['segments'][cur_index]['url']
        byterange = playlist['segments'][cur_index]['byterange']
        if byterange != '':
            debug(
                DEBUG,
                '%s fetchNextSegment level: %d (%s/s) %d/%d : %s (byterange=%s) angles:%d view: %d',
                self, self.getCurrentLevel(),
                format_bytes(float(levels[self.getCurrentLevel()]['rate'])),
                self.getCurrentSegmentIndex(), playlist['end_index'],
                url_segment, byterange, self.getCurrentAngles(),
                self.getCurrentView())
        else:
            debug(
                DEBUG,
                '%s fetchNextSegment level: %d (%s/s) %d/%d angles:%d view %d: %s',
                self, self.getCurrentLevel(),
                format_bytes(float(levels[self.getCurrentLevel()]['rate'])),
                self.getCurrentSegmentIndex(), playlist['end_index'],
                self.getCurrentAngles()[0], self.getCurrentView(), url_segment)

        if self.controller.isBuffering():
            idle_duration = 0.0  #fetch segment after the last segment download is completed
        else:
            idle_duration = self.controller.getIdleDuration()
        # load the next segment
        if self.last_redirect_host:
            host, port, path = parse_url(url_segment)
            url_segment = 'http://' + self.last_redirect_host + path

        reactor.callLater(idle_duration, self.startDownload, url_segment,
                          byterange)

    def startDownload(self, url, byterange=''):
        '''
        Starts the segment download and set the timestamp of start segment download

        :param url: segment url
        :param byterange: segment byterange (logical segmentation of video level)
        '''
        debug(DEBUG + 1, '%s startDownload %s (byterange %s)', self, url,
              byterange)
        # start download
        if self.use_persistent_connection:
            # start a new connection
            if not self.connection:
                self._initConnection(url)
                return
            if not self.connection.client:
                return
            _, _, path = parse_url(url)
            self.connection.makeRequest(path, byterange)
        else:
            if byterange == '':
                d = getPage(url, agent=USER_AGENT)
            else:
                d = getPage(url,
                            agent=USER_AGENT,
                            headers=dict(range='bytes=' + byterange))
            d.deferred.addCallback(self.playNextGotRequest, d)
            d.deferred.addErrback(self.playNextGotError, d)
        self.start_segment_request = time.time()

    # callback if do not use persistent connection
    def playNextGotRequest(self, data, factory):
        '''
        Updates feedbacks, calculates the control action and sets level of the next segment.

        :param data: downloaded data
        :param factory: the twisted factory (used without persistent connection)
        '''

        self.stop_segment_request = time.time()
        download_time = (self.stop_segment_request -
                         self.start_segment_request)
        self.last_downloaded_time = download_time
        self.bwe = len(data) / download_time
        self.last_fragment_size = len(data)
        self.downloaded_bytes += len(data)
        self.downloaded_segments += 1
        debug(DEBUG, '%s __got_request: bwe: %s/s (fragment size: %s)', self,
              format_bytes(self.bwe), format_bytes(len(data)))

        self.queuedTime = self.media_engine.getQueuedTime(
        ) + self.parser.getFragmentDuration()
        self.queuedBytes = self.media_engine.getQueuedBytes() + len(data)

        playlist = self.parser.getSinglePlaylist(self.getCurrentLevel(),
                                                 self.getCurrentView())
        if ("initURL" in playlist):
            #print("HLS on mp4")
            # download init segment (DASH BASED CODING)
            if ("initSegment" in playlist):
                #push next segment
                #print("LEVEL: " + str(self.cur_level) + "\nVIEW: " + str(self.cur_view) + "\nInit URL:   " + str(playlist["initURL"]) + "\nFragment Id: " + str(self.cur_index))
                self.media_engine.setOriginalSideWidth(
                    playlist['central_width'])
                self.media_engine.setScaledSideWidth(playlist['side_width'])
                self.media_engine.pushData(playlist["initSegment"] + data,
                                           self.parser.getFragmentDuration(),
                                           self.getCurrentLevel(),
                                           self.getCurrentView(),
                                           self.parser._getCapsDemuxer())

            else:
                # ERROR
                print("ERROR: missing init file")
        else:
            # push next segment
            #print("HLS on mpegts")
            self.media_engine.pushData(data, self.parser.getFragmentDuration(),
                                       self.getCurrentLevel(),
                                       self.getCurrentView(),
                                       self.parser._getCapsDemuxer())

        # flush segment on file
        if (self.save_chunks):
            filename = "{0!s}_{1!s}_{2!s}.mp4".format(self.downloaded_segments,
                                                      self.getCurrentLevel(),
                                                      self.getCurrentView())
            self.logger.logData(filename, data)

        del data
        self.cur_index = self.media_engine.getNextVideoSegmentToBeFetched(
            self.cur_index)
        #Do something before calculating new control action
        self._onNewSegment()
        #Passing player parameters at the controller to calculate the control action
        self.updateFeedback(flag_check_buffering=False)
        #calc control action
        self.controller.setControlAction(self.controller.calcControlAction())
        # set new level
        if self.getDownloadedSegments() > self.getInactiveCycles():
            if (self.getNumViews() > 1):
                self.setView()
            if self.enable_stress_test:
                self.stressTest()
            else:
                self.setLevel(self.controller.getControlAction())
        self.fetchNextSegment()

    # error handling if do not use persistent connection
    def playNextGotError(self, error, factory):
        '''
        Handles error when download a segment without persistent connection

        :param error: the occurred error
        :param factory: the twisted factory (used without persistent connection)
        '''
        debug(0, '%s playNextGotError url: %s error: %s', self, factory.url,
              error)
        # update playlist
        if self.parser.getPlaylistType() == 'HLS':
            self.parser.updateLevelSegmentsList(
                self.getCurrentLevel(),
                self.getCurrentView()).addCallback(self._updatePlaylistDone)

    def setLevel(self, rate):
        '''
        Sets the level corresponding to the rate specified in B/s

        :param rate: rate in B/s that determines the level. The level is the one whose rate is the highest below ``rate``.
        '''
        new_level = self.controller.quantizeRate(rate)
        if new_level != self.getCurrentLevel():
            debug(DEBUG, "%s setLevel: level: %d", self, new_level)
            self.setCurrentLevel(new_level)
            #self.onLevelChanged()
        return new_level

    def stressTest(self):
        '''
        Switches the video quality level cyclically every segment        
        '''
        self.check_warning_buffering = False
        if self.getCurrentLevel() == self.getMaxLevel():
            new_level = 0
        else:
            new_level = self.getCurrentLevel() + 1
        self.setCurrentLevel(new_level)
        #self.onLevelChanged()
        return new_level

    def checkBuffering(self, _arg):
        '''
        Checks if the playback is going to buffering.
        Estimates the time required to complete the download of the current segment and verifies that it is less than the playout buffer lenght.

        In the case of "warning buffering", it deletes the current segment download, calculates the control action and sets the new level.
        This feature is available only with persistent connection.
        '''
        #FIXME Can't use it without persistent connection
        if self.rate_calc.rate and self.cur_index > self.inactive_cycle:
            remaining_secs = float(self.remaining_data / self.rate_calc.rate)
            debug(
                DEBUG + 1,
                "%s checkBuffering: rate %s/s, remaining_data %s, remaining_secs %.3f, queued_time %.2f ",
                self, format_bytes(self.rate_calc.rate),
                format_bytes(self.remaining_data), remaining_secs,
                self.media_engine.getQueuedTime())
            #Can cancel download only if the current level is greater than 0
            if self.media_engine.getQueuedTime(
            ) < remaining_secs and self.getCurrentLevel() > 0:
                # print '---------------STOPPING DUE TO CHECK BUFFERING'
                self.connection.stop()
                self.stop_segment_request = time.time(
                )  #update stop reqest when warning buffering occurs
                self.bwe = self.rate_calc.rate
                #Passing player parameters at the controller to calculate the control action
                self.updateFeedback(flag_check_buffering=True)
                #calc control action
                self.controller.setControlAction(
                    self.controller.calcControlAction())
                # set new level
                if self.cur_index > self.inactive_cycle:
                    self.setLevel(self.controller.getControlAction())
                    self.setView()
                    debug(
                        0,
                        "%s WARNING BUFFERING!!! Delete and reload segment at level: %d",
                        self, self.getCurrentLevel())
        else:
            return

    def updateFeedback(self, flag_check_buffering):
        '''
        Updates dictionary of feedbacks before passing it to the controller.

        :param flag_check_buffering: true if this method is called from ``checkBuffering``. False otherwise.
        '''
        self.feedback = dict(
            queued_bytes=self.media_engine.getQueuedBytes(),
            queued_time=self.media_engine.getQueuedTime(),
            max_buffer_time=self.getMaxBufferTime(),
            bwe=self.getBandwidth(),
            level=self.getCurrentLevel(),
            max_level=self.getMaxLevel(),
            cur_rate=self.getCurrentRate(),
            max_rate=self.getMaxRate(),
            min_rate=self.getMinRate(),
            player_status=self.media_engine.getStatus(),
            paused_time=self.getPausedTime(),
            last_fragment_size=self.getLastFragmentBytes(),
            last_download_time=self.getLastDownloadedTime(),
            start_segment_request=self.getStartSegmentRequest(),
            stop_segment_request=self.getStopSegmentRequest(),
            downloaded_bytes=self.getDownloadedBytes(),
            fragment_duration=self.parser.getFragmentDuration(),
            rates=self.getLevelRates(),
            is_check_buffering=flag_check_buffering,
            #parameters of the view
            view_angles=self.getCurrentAngles(),
            n_views=self.getNumViews(),
            cur_view=self.getCurrentView(),
            #Set of yaw angles associated to the different views (angular position of the ROI center of the i-th view)
            delta=20,
            yaw_angles=self.getYawAngles(self.getNumViews()),
            threshold_angle=60)
        self.controller.setPlayerFeedback(self.feedback)
        if self.feedback['n_views'] > 1:
            self.viewController.setPlayerFeedback(self.feedback)

    def log(self):
        '''
        Logs useful metrics every ``log_period`` seconds
        '''
        if not self.logger:
            return
        stats = self.proc_stats.getStats()
        opts = dict(
            enqueued_b=self.media_engine.getQueuedBytes(),  #2
            enqueued_t=self.media_engine.getQueuedTime(),  #3
            bwe=self.getBandwidth(),  #4
            cur=self.getCurrentRate(),  #5
            level=self.getCurrentLevel(),  #6
            max_level=self.getMaxLevel(),  #7
            player_status=self.media_engine.getStatus(),  #8
            paused_time=self.getPausedTime(),  #9
            downloaded_bytes=self.getDownloadedBytes(),  #10
            cpu=stats["cpu_percent"],  #11
            mem=stats["memory_percent"],  #12    
            rss=stats["memory_rss"],  #13
            vms=stats["memory_vms"],  #14
            ts_start_req=self.getStartSegmentRequest(
            ),  #15                      
            ts_stop_req=self.getStopSegmentRequest(),  #16  
            alpha=self.getCurrentAngles()[0],  #17
            view=self.getCurrentView()  #18
        )
        levels = self.parser.getLevels()
        for i in range(0, len(self.getLevelRates())):
            opts['q%d' % i] = float(levels[i]['rate'])
        self.logger.log(opts)
        del opts
        reactor.callLater(self.log_period, self.log)

    def _onNewSegment(self):
        '''
        Does something before calculating new control action
        '''
        pass

    # callback
    def _onDataReceiving(self, connection, data_diff, remaining_data):
        '''
        Does something before segment download is completed (used with persistent connection)
        '''
        self.remaining_data = remaining_data
        debug(DEBUG + 1, '%s _onDataReceiving: %s %s', self,
              format_bytes(data_diff), format_bytes(remaining_data))
        self.rate_calc.update(data_diff)

    # callback
    def _onDataReceived(self, connection, data):
        '''
        Does something when segment download is completed (used with persistent connection)
        '''
        debug(DEBUG + 1, '%s _onDataReceived: %s', self,
              format_bytes(len(data)))
        self.connection.redirect = False
        self.playNextGotRequest(data, None)

    def _initConnection(self, url, redirect=False):
        '''
        Initializes connection with url (only with persistent connection)
        '''
        if self.connection:
            self.connection.stop()
        debug(DEBUG + 1, '%s _initConnection: %s', self, url)
        self.connection = ClientFactory(url, redirect)
        self.connection.connect('connection-made', self._onConnectionMade)
        self.connection.connect('connection-lost', self._onConnectionLost)
        self.connection.connect('data-received', self._onDataReceived)
        self.connection.connect('data-receiving', self._onDataReceiving)
        self.connection.connect('url-redirect', self._urlRedirect)

    def _urlRedirect(self, connection, url):
        # print '_urlRedirect called with url: ' + url
        self.connection.redirect = True
        host, port, path = parse_url(url)
        print('Redirect host ' + host)
        self.last_redirect_host = host
        # self.connection.stop()
        self._initConnection(url, True)

    def _onConnectionMade(self, connection, host):
        '''
        Does something when connection with host is established (only with persistent connection).
        '''
        debug(DEBUG + 1, '%s _onConnectionMade: %s', self, host)
        if self.logger:
            self.logger.log_comment('Host: %s' % host)
        if not connection.redirect:
            # print 'Calling fetchDownload'
            reactor.callLater(0.01, self.fetchNextSegment)  # LDC: was 0.1
        else:
            # print 'Calling startDownload with url:' + connection.url
            self.connection = connection
            reactor.callLater(0.01, self.startDownload, connection.url)

    def _onConnectionLost(self, connection):
        '''
        Does something when connection with host is lost (only with persistent connection).
        '''
        self.connection = None
        if self.parser.getPlaylistType() == 'HLS':
            debug(0, '%s _onConnectionLost', self)
            self.parser.updateLevelSegmentsList(
                self.getCurrentLevel(),
                self.getCurrentView()).addCallback(self._updatePlaylistDone)
        else:  #only for youtube (for check buffering)
            if not connection.redirect:
                debug(DEBUG, '%s _onConnectionLost Youtube', self)
                self.fetchNextSegment()
            else:
                debug(DEBUG,
                      '%s _onConnectionLost YouTube REDIRECT. Do nothing',
                      self)

    #When status changes calls 'onPlaying' and 'onPaused' hooks
    #callback
    def _onStatusChanged(self, media_engine):
        '''
        Does something when player status change from play to pause and viceversa
        '''
        if media_engine.status == media_engine.PLAYING:
            self.controller.onPlaying()
            self.paused_time += time.time() - self.t_paused
        else:
            self.controller.onPaused()
            self.t_paused = time.time()

    #callback
    def _updatePlaylistDone(self, data):
        '''
        Called when the playlist for the current level is update
        '''
        playlist = self.parser.playlists[self.getCurrentLevel()]
        debug(DEBUG + 1, '%s playlist: %s', self, pformat(playlist))
        # start play if playlist has more than 2 fragments
        if len(playlist['segments']) > 2 and self.getCurrentSegmentIndex(
        ) < playlist['end_index']:
            self.fetchNextSegment()
        else:
            reactor.callLater(self.parser.getFragmentDuration(),
                              self.fetchNextSegment)
Exemple #6
0
class TapasPlayer(object):
    def __init__(
            self,
            controller,
            parser,
            media_engine,
            log_sub_dir='',
            log_period=0.1,
            max_buffer_time=60,
            inactive_cycle=1,
            initial_level=1,
            #FIXME reset to TRUE again!!
            use_persistent_connection=True,
            #changed by susanna
            check_warning_buffering=False,
            stress_test=False,
            ip_address='0',
            port='0',
            resolution='0'):

        # player components
        self.controller = controller
        self.parser = parser
        self.media_engine = media_engine

        # log
        self.logger = None
        self.log_file = None
        self.log_dir = 'logs'
        self.log_sub_dir = log_sub_dir
        self.log_period = log_period
        self.log_prefix = ''
        self.log_comment = ''
        #
        self.max_buffer_time = max_buffer_time
        self.inactive_cycle = inactive_cycle  #active control action after inactive_cycle+1 segments
        #
        self.use_persistent_connection = use_persistent_connection
        self.connection = None
        #
        self.cur_level = initial_level
        self.cur_index = 0
        self.enable_stress_test = stress_test  #flag to enable stress test, switch level every segment cyclically
        #
        self.check_warning_buffering = check_warning_buffering  #flag to enable check for warning buffering
        self.rate_calc = RateCalc(period=3.0, alpha=0.0)
        self.remaining_data = 0
        #
        self.bwe = 0
        self.downloaded_bytes = 0
        self.downloaded_segments = 0
        self.last_fragment_size = 0
        self.start_segment_request = -1.0
        self.stop_segment_request = -1.0
        self.last_downloaded_time = -1.0
        self.t_paused = time.time()
        self.paused_time = 0.0
        self.queuedBytes = 0
        self.queuedTime = 0.0
        #
        self.proc_stats = ProcessStats()

        self.prio = 0
        self.delegatedLevel = 0
        self.first = 1

        #Initialize the parameters passed at the controller after the download of every segment
        self.feedback = dict(queued_bytes=0,
                             queued_time=0.0,
                             max_buffer_time=self.max_buffer_time,
                             bwe=0.0,
                             level=self.cur_level,
                             max_level=-1,
                             cur_rate=0.0,
                             max_rate=0.0,
                             min_rate=0.0,
                             player_status=0,
                             paused_time=0.0,
                             last_fragment_size=0,
                             last_download_time=0.0,
                             downloaded_bytes=0,
                             fragment_duration=0.0,
                             rates=[])

        #own parameters for several tapas instances communicating with zmq
        self.ip_address = ip_address
        self.port = port
        self.resolution = resolution

        self.controller.setPlayerFeedback(self.feedback)
        self.connectZMQ_pub()
        test_thread = threading.Thread(target=self.connectZMQ)
        test_thread.daemon = True
        test_thread.start()
        #self.connectZMQ();

    #connect to zmq to receive bitrate guidance
    def connectZMQ(self):
        context = zmq.Context()
        socket = context.socket(zmq.SUB)
        print("Tapas Player connection with zmq-Entity")

        socket.connect("tcp://192.168.1.2:4444")
        #listenTo="192.168.1.10:"+str(os.getpid())
        #print colored('im listening to '+str(listenTo),'green')
        listenTo = str(self.ip_address) + ":" + str(os.getpid())
        print("I LISTEN TO " + str(listenTo))

        if isinstance(listenTo, bytes):
            listenTo = listenTo.decode('ascii')
            socket.setsockopt_string(zmq.SUBSCRIBE, listenTo)
        while True:
            try:
                string_received = socket.recv_string()
                listenTo, delegated_level = string_received.split()
                print("MESSAGE RECEIVED")
                print colored("received prio status", 'red')
                print colored("[3] execute fetchNExtSegment() from here",
                              'blue')
                print colored("[!!!!!!] level delegate i received: " +
                              str(delegated_level))
                self.setDelegatedLevel(int((delegated_level)))
                #self.fetchNextSegment()

            except KeyboardInterrupt:
                break

    #connect to zmq as a publisher to send app params
    def connectZMQ_pub(self):
        print("AUFRUF von connectZMQ_pub")
        context_send_app_param = zmq.Context()
        global socket_send_app_param
        socket_send_app_param = context_send_app_param.socket(zmq.PUB)
        socket_send_app_param.bind("tcp://*:" + str(self.port))

    def __repr__(self):
        return '<TapasPlayer-%d>' % id(self)

    def play(self):
        '''
        Starts Parser, creates Logger, initializes MediaEngine, and fetches the first segment when the parser has finished
        '''
        self.parser.loadPlaylist()

        def _on_done(res):

            playlists = self.parser.getPlaylists()
            levels = self.parser.getLevels()
            fragment_duration = self.parser.getFragmentDuration()
            caps = self.parser._getCapsDemuxer()
            self.controller.setIdleDuration(
                fragment_duration
            )  #Default pause interval when isBuffering return False
            if self.getCurrentLevel() > self.getMaxLevel(
            ) or self.getCurrentLevel() == -1:
                self.setCurrentLevel(self.getMaxLevel())
            #opts for Logger
            opts = [
                ('enqueued_b', int, ''),  #2
                ('enqueued_t', float, 'visible=1,subplot=2'),  #3
                ('bwe', float, 'visible=1,subplot=1'),  #4
                ('cur', int, 'visible=1,subplot=1'),  #5
                ('level', int, 'visible=1,subplot=3'),  #6
                ('max_level', int, ''),  #7
                ('player_status', int, 'visible=1,subplot=3'),  #8
                ('paused_time', float, ''),  #9
                ('downloaded_bytes', int, ''),  #10
                ('cpu', float, 'visible=1,subplot=4'),  #11
                ('mem', float, 'visible=1,subplot=5'),  #12
                ('rss', float, ''),  #13
                ('vms', float, ''),  #14
                ('ts_start_req', float, ''),  #15
                ('ts_stop_req', float, ''),  #16
            ]
            for i in range(0, len(levels)):
                print(i)
            #opts.append(('q%d' %i, int, 'visible=0'))
            if self.log_sub_dir:
                self.log_dir = self.log_dir + '/' + self.log_sub_dir
                #Create Logger
                self.logger = Logger(opts,
                                     log_period=self.log_period,
                                     log_prefix=self.log_prefix,
                                     comment=self.log_comment,
                                     log_dir=self.log_dir)
                debug(DEBUG + 1, 'levels: %s', levels)
                debug(DEBUG + 1, 'playlists: %s', playlists)
            if self.enable_stress_test:
                self.inactive_cycle = 0
            if self.check_warning_buffering:
                self.rate_calc.start()
                self.rate_calc.connect('update', self.checkBuffering)
            #Init media_engine
            self.media_engine.setVideoContainer(
                self.parser.getVideoContainer())
            self.media_engine.connect('status-changed', self._onStatusChanged)
            self.media_engine.start()
            #start logger
            reactor.callLater(self.log_period, self.log)
            #
            self.fetchNextSegment()

        self.parser.deferred.addCallback(_on_done)

    def getMaxBufferTime(self):
        '''
        Gets max buffer in seconds under which the playback is considered in Buffering by default
        '''
        return self.max_buffer_time

    def getCurrentLevel(self):
        '''
        Gets index of current level starting from 0 for the lowest video quality level
        '''
        return self.delegatedLevel

    def setCurrentLevel(self, level):
        '''
        Sets index of current level starting from 0 for the lowest video quality level

        :param level: the level index
        '''
        self.cur_level = level

    def setPrio(self, prio):
        '''
	define whether next segment will be prioritized or not
	'''
        self.prio = prio

    def setDelegatedLevel(self, delegatedLevel):
        '''
	define whether next segment will be prioritized or not
	'''
        print colored("updated the delegated Level to " + str(delegatedLevel),
                      "yellow")
        self.delegatedLevel = delegatedLevel

    def getDelegatedLevel(self):
        return self.delegatedLevel

    def getPrio(self):
        '''
	Returns 1 if next segment is priorizied, 0 if next segment is not prioritized
	'''
        return self.prio

    def getMaxLevel(self):
        '''
        Gets index of maximum level starting from 0 for the lowest video quality level
        '''
        return len(self.parser.getLevels()) - 1

    def getCurrentSegmentIndex(self):
        '''
        Gets index of the current segment of the sub-playlist
        '''
        return self.cur_index

    def setCurrentSegmentIndex(self, index):
        '''
        Sets index of the current segment of the sub-playlist

        :param index: segment index
        '''
        self.cur_index = index

    def getCurrentRate(self):
        '''
        Gets current video quality level rate in B/s
        '''
        levels = self.parser.getLevels()
        cur_rate = float(levels[self.getCurrentLevel()]['rate'])
        return cur_rate

    def getMaxRate(self):
        '''
        Gets maximum video quality level rate in B/s
        '''
        levels = self.parser.getLevels()
        rates = [float(i['rate']) for i in levels]
        return max(rates)

    def getMinRate(self):
        '''
        Gets minimum video quality level rate in B/s
        '''
        levels = self.parser.getLevels()
        rates = [float(i['rate']) for i in levels]
        return min(rates)

    def getLevelRates(self):
        '''
        Gets a list of video quality level rates in B/s
        '''
        levels = self.parser.getLevels()
        _r = [float(i['rate']) for i in levels]
        rates = []
        for i in range(0, len(_r)):
            rates.append(_r[i])
        return rates

    def getLevelResolutions(self):
        '''
        Gets a list of available video resolutions
        '''
        levels = self.parser.getLevels()
        resolutions = [i['resolution'] for i in levels]
        return resolutions

    def getLastDownloadedTime(self):
        '''
        Gets time spent to download the last segment
        '''
        return self.last_downloaded_time

    def getStartSegmentRequest(self):
        '''
        Gets timestamp when starts the download of the last segment
        '''
        return self.start_segment_request

    def getStopSegmentRequest(self):
        '''
        Gets timestamp when stops the download of the last segment
        '''
        return self.stop_segment_request

    def getLastFragmentBytes(self):
        '''
        Gets the last fragment size in B
        '''
        return self.last_fragment_size

    def getDownloadedBytes(self):
        '''
        Gets total downloaded bytes in B
        '''
        return self.downloaded_bytes

    def getDownloadedSegments(self):
        '''
        Gets total number of downloaded segments
        '''
        return self.downloaded_segments

    def getBandwidth(self):
        '''
        Gets last estimated available bandwidth in B/s
        '''
        return self.bwe

    def getPausedTime(self):
        '''
        Gets time spent on pause
        '''
        return self.paused_time

    def getInactiveCycles(self):
        '''
        Gets the number of inactive cycles before activate the control action
        '''
        return self.inactive_cycle

    def getLogFileName(self):
        '''
        Gets log file name
        '''
        return self.log_file

    def fetchNextSegment(self):
        '''
        Schedules the download of the next segment at current level
        '''
        print("FETCH THE NEXT SEGMENT IN LEVEL: " +
              str(self.getCurrentLevel()))
        #print ("MY PID IS "+str(os.getpid()))

        playlist = self.parser.playlists[self.getCurrentLevel()]
        debug(DEBUG + 1, '%s fetchNextSegment level: %d cur_index: %d', self,
              self.getCurrentLevel(), self.getCurrentSegmentIndex())
        #
        if self.getCurrentSegmentIndex() < playlist['start_index']:
            self.setCurrentSegmentIndex(playlist['start_index'])
        if self.getCurrentSegmentIndex() > playlist['end_index']:
            # else live video (ONLY HLS!!)
            if playlist['is_live'] and self.parser.getPlaylistType() == 'HLS':
                debug(DEBUG, '%s fetchNextSegment cur_index %d', self,
                      self.getCurrentSegmentIndex())
                self.parser.updateLevelSegmentsList(
                    self.getCurrentLevel()).addCallback(
                        self._updatePlaylistDone)
            # if video is vod
            else:
                debug(DEBUG, '%s fetchNextSegment last index', self)
            return
        cur_index = self.getCurrentSegmentIndex()
        levels = self.parser.getLevels()
        url_segment = playlist['segments'][cur_index]['url']
        byterange = playlist['segments'][cur_index]['byterange']
        if byterange != '':
            debug(
                DEBUG,
                '%s fetchNextSegment level: %d (%s/s) %d/%d : %s (byterange=%s)',
                self, self.getCurrentLevel(),
                format_bytes(float(levels[self.getCurrentLevel()]['rate'])),
                self.getCurrentSegmentIndex(), playlist['end_index'],
                url_segment, byterange)
        else:
            debug(DEBUG, '%s fetchNextSegment level: %d (%s/s) %d/%d : %s',
                  self, self.getCurrentLevel(),
                  format_bytes(float(levels[self.getCurrentLevel()]['rate'])),
                  self.getCurrentSegmentIndex(), playlist['end_index'],
                  url_segment)
        if self.controller.isBuffering():
            idle_duration = 0.0  #fetch segment after the last segment download is completed
        else:
            idle_duration = self.controller.getIdleDuration()
        # load the next segment
        clientInfo = socket.gethostbyname(socket.gethostname())
        segSize = float(float(self.getLastFragmentBytes() * 8)) / 1000
        print colored("SENDING A SEGMENT SIZE OF " + str(segSize), 'blue')
        print["MESSAGE SEND TO ORCHESTRATOR"]
        print colored("[delegated level is ]" + str(self.delegatedLevel),
                      'green')
        print "-----------------------------------------------------------------------"
        print colored("my buffer is " + str(self.media_engine.getQueuedTime()),
                      'red')
        print "-----------------------------------------------------------------------"
        if self.first == 1:
            socket_send_app_param.send_string(
                "%s %s %s %s %s %s %s %s %s %s" %
                ("2", "start", str(self.getBandwidth()), str(segSize),
                 str(clientInfo), str(getLocalport()), str(
                     os.getpid()), str(self.resolution), 'empt', 'empt'))
            self.first = 0

        else:
            print("my idle time is " + str(idle_duration))
            print("my dl time is " + str(self.last_downloaded_time))
            socket_send_app_param.send_string(
                "%s %s %s %s %s %s %s %s %s %s" %
                ("2", "client_update", str(self.getBandwidth()), str(segSize),
                 str(clientInfo), str(getLocalport()), str(
                     os.getpid()), str(self.resolution), str(idle_duration),
                 str(self.last_downloaded_time)))
        print("sent info to zmq-entity")
        reactor.callLater(idle_duration, self.startDownload, url_segment,
                          byterange)

    def startDownload(self, url, byterange=''):
        '''
        Starts the segment download and set the timestamp of start segment download

        :param url: segment url
        :param byterange: segment byterange (logical segmentation of video level)
        '''
        debug(DEBUG + 1, '%s startDownload %s (byterange %s)', self, url,
              byterange)
        # start download
        if self.use_persistent_connection:
            # start a new connection
            if not self.connection:
                self._initConnection(url)
                return
            if not self.connection.client:
                return

            _, _, path = parse_url(url)
            print("~~~> now requesting new segment")
            self.connection.makeRequest(path, byterange)

        else:
            if byterange == '':
                d = getPage(url, agent=USER_AGENT)
            else:
                d = getPage(url,
                            agent=USER_AGENT,
                            headers=dict(range='bytes=' + byterange))
            d.deferred.addCallback(self.playNextGotRequest, d)
            d.deferred.addErrback(self.playNextGotError, d)
        self.start_segment_request = time.time()
        print colored("in startDownload", 'red')
        print colored("[5] get request performed", 'blue')
        print colored(
            "current buffer: " + str(self.media_engine.getQueuedTime()),
            'yellow')
        print colored(
            '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~...Downloading...~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
            'yellow')

    def makeRequest(self):
        clientInfo = socket.gethostbyname(socket.gethostname())
        segSize = float(float(self.getLastFragmentBytes() * 8)) / 1000
        if self.first == 1:
            socket_send_app_param.send_string(
                "%s %s %s %s %s %s %s %s %s %s" %
                ("2", "start", str(self.getBandwidth()), str(segSize),
                 str(clientInfo), str(getLocalport()), str(
                     os.getpid()), str(self.resolution), 'empt', 'empt'))
            self.first = 0

        else:
            print("my idle time is " + str(idle_duration))
            print("my dl time is " + str(self.last_downloaded_time))
            socket_send_app_param.send_string(
                "%s %s %s %s %s %s %s %s %s %s" %
                ("2", "client_update", str(self.getBandwidth()), str(segSize),
                 str(clientInfo), str(getLocalport()), str(
                     os.getpid()), str(self.resolution), str(idle_duration),
                 str(self.last_downloaded_time)))

    # callback if do not use persistent connection
    def playNextGotRequest(self, data, factory):
        '''
        Updates feedbacks, calculates the control action and sets level of the next segment.

        :param data: downloaded data
        :param factory: the twisted factory (used without persistent connection)
        '''
        self.stop_segment_request = time.time()
        download_time = (self.stop_segment_request -
                         self.start_segment_request)
        self.last_downloaded_time = download_time
        print("---------> DOWNLOAD TIME WAS " + str(download_time))
        self.bwe = len(data) / download_time
        self.last_fragment_size = len(data)
        self.downloaded_bytes += len(data)
        self.downloaded_segments += 1
        debug(DEBUG, '%s __got_request: bwe: %s/s (fragment size: %s)', self,
              format_bytes(self.bwe), format_bytes(len(data)))
        self.queuedTime = self.media_engine.getQueuedTime(
        ) + self.parser.getFragmentDuration()
        self.queuedBytes = self.media_engine.getQueuedBytes() + len(data)
        self.media_engine.pushData(data, self.parser.getFragmentDuration(),
                                   self.getCurrentLevel(),
                                   self.parser._getCapsDemuxer())
        del data
        self.cur_index += 1
        #Do something before calculating new control action
        self._onNewSegment()
        #Passing player parameters at the controller to calculate the control action
        self.updateFeedback(flag_check_buffering=False)
        #calc control action
        self.controller.setControlAction(self.controller.calcControlAction())
        # set new level
        if self.getDownloadedSegments() > self.getInactiveCycles():
            if self.enable_stress_test:
                self.stressTest()
            else:
                self.setLevel(self.controller.getControlAction())
#print colored("play next got request",'red')
#print colored("[1] request for request",'blue')
        self.fetchNextSegment()

    # error handling if do not use persistent connection
    def playNextGotError(self, error, factory):
        '''
        Handles error when download a segment without persistent connection

        :param error: the occurred error
        :param factory: the twisted factory (used without persistent connection)
        '''
        debug(0, '%s playNextGotError url: %s error: %s', self, factory.url,
              error)
        # update playlist
        if self.parser.getPlaylistType() == 'HLS':
            self.parser.updateLevelSegmentsList(self.cur_level).addCallback(
                self._updatePlaylistDone)

    def setLevel(self, rate):
        '''
        Sets the level corresponding to the rate specified in B/s

        :param rate: rate in B/s that determines the level. The level is the one whose rate is the highest below ``rate``.
        '''
        print("my level DELEGATED IS " + str(self.getDelegatedLevel()))
        return self.getDelegatedLevel()

    def stressTest(self):
        '''
        Switches the video quality level cyclically every segment        
        '''
        self.check_warning_buffering = False
        if self.getCurrentLevel() == self.getMaxLevel():
            new_level = 0
        else:
            new_level = self.getCurrentLevel() + 1
        self.setCurrentLevel(new_level)
        #self.onLevelChanged()
        return new_level

    def checkBuffering(self, _arg):
        #exactly what we are doing?!?!?!?
        '''
        Checks if the playback is going to buffering.
        Estimates the time required to complete the download of the current segment and verifies that it is less than the playout buffer lenght.

        In the case of "warning buffering", it deletes the current segment download, calculates the control action and sets the new level.
        This feature is available only with persistent connection.
        '''
        #FIXME Can't use it without persistent connection
        if self.rate_calc.rate and self.cur_index > self.inactive_cycle:
            remaining_secs = float(self.remaining_data / self.rate_calc.rate)
            debug(
                DEBUG + 1,
                "%s checkBuffering: rate %s/s, remaining_data %s, remaining_secs %.3f, queued_time %.2f ",
                self, format_bytes(self.rate_calc.rate),
                format_bytes(self.remaining_data), remaining_secs,
                self.media_engine.getQueuedTime())
            #Can cancel download only if the current level is greater than 0
            if self.media_engine.getQueuedTime(
            ) < remaining_secs and self.getCurrentLevel() > 0:
                self.connection.stop()
                self.stop_segment_request = time.time(
                )  #update stop reqest when warning buffering occurs
                self.bwe = self.rate_calc.rate
                #Passing player parameters at the controller to calculate the control action
                self.updateFeedback(flag_check_buffering=True)
                #calc control action
                self.controller.setControlAction(
                    self.controller.calcControlAction())
                # set new level
                if self.cur_index > self.inactive_cycle:
                    self.setLevel(self.controller.getControlAction())
                    debug(
                        0,
                        "%s WARNING BUFFERING!!! Delete and reload segment at level: %d",
                        self, self.getCurrentLevel())
        else:
            return

    def updateFeedback(self, flag_check_buffering):
        '''
        Updates dictionary of feedbacks before passing it to the controller.

        :param flag_check_buffering: true if this method is called from ``checkBuffering``. False otherwise.
        '''
        self.feedback = dict(
            queued_bytes=self.media_engine.getQueuedBytes(),
            queued_time=self.media_engine.getQueuedTime(),
            max_buffer_time=self.getMaxBufferTime(),
            bwe=self.getBandwidth(),
            level=self.getCurrentLevel(),
            max_level=self.getMaxLevel(),
            cur_rate=self.getCurrentRate(),
            max_rate=self.getMaxRate(),
            min_rate=self.getMinRate(),
            player_status=self.media_engine.getStatus(),
            paused_time=self.getPausedTime(),
            last_fragment_size=self.getLastFragmentBytes(),
            last_download_time=self.getLastDownloadedTime(),
            downloaded_bytes=self.getDownloadedBytes(),
            fragment_duration=self.parser.getFragmentDuration(),
            rates=self.getLevelRates(),
            is_check_buffering=flag_check_buffering)
        self.controller.setPlayerFeedback(self.feedback)

    def log(self):
        '''
        Logs useful metrics every ``log_period`` seconds
        '''
        if not self.logger:
            return
        stats = self.proc_stats.getStats()
        opts = dict(
            enqueued_b=self.media_engine.getQueuedBytes(),  #2
            enqueued_t=self.media_engine.getQueuedTime(),  #3
            bwe=self.getBandwidth(),  #4
            cur=self.getCurrentRate(),  #5
            level=self.getCurrentLevel(),  #6
            max_level=self.getMaxLevel(),  #7
            player_status=self.media_engine.getStatus(),  #8
            paused_time=self.getPausedTime(),  #9
            downloaded_bytes=self.getDownloadedBytes(),  #10
            cpu=stats["cpu_percent"],  #11
            mem=stats["memory_percent"],  #12    
            rss=stats["memory_rss"],  #13
            vms=stats["memory_vms"],  #14
            ts_start_req=self.getStartSegmentRequest(
            ),  #15                      
            ts_stop_req=self.getStopSegmentRequest(
            ),  #16                         
        )
        levels = self.parser.getLevels()
        for i in range(0, len(self.getLevelRates())):
            opts['q%d' % i] = float(levels[i]['rate'])
        self.logger.log(opts)
        del opts
        reactor.callLater(self.log_period, self.log)

    def _onNewSegment(self):
        '''
        Does something before calculating new control action
        '''
        pass

    # callback
    def _onDataReceiving(self, connection, data_diff, remaining_data):
        '''
        Does something before segment download is completed (used with persistent connection)
        '''
        self.remaining_data = remaining_data
        debug(DEBUG + 1, '%s _onDataReceiving: %s %s', self,
              format_bytes(data_diff), format_bytes(remaining_data))
        self.rate_calc.update(data_diff)

    # callback
    def _onDataReceived(self, connection, data):
        '''
        Does something when segment download is completed (used with persistent connection)
        '''

        debug(DEBUG + 1, '%s _onDataReceived: %s', self,
              format_bytes(len(data)))
        self.playNextGotRequest(data, None)

    def _initConnection(self, url):
        '''
        Initializes connection with url (only with persistent connection)
        '''
        if self.connection:
            self.connection.stop()
        debug(DEBUG + 1, '%s _initConnection: %s', self, url)
        self.connection = ClientFactory(url)
        self.connection.connect('connection-made', self._onConnectionMade)
        self.connection.connect('connection-lost', self._onConnectionLost)
        self.connection.connect('data-received', self._onDataReceived)
        self.connection.connect('data-receiving', self._onDataReceiving)

    def _onConnectionMade(self, connection, host):
        '''
        Does something when connection with host is established (only with persistent connection).
        '''
        debug(DEBUG + 1, '%s _onConnectionMade: %s', self, host)
        if self.logger:
            self.logger.log_comment('Host: %s' % host)
        reactor.callLater(0.1, self.fetchNextSegment)

    def _onConnectionLost(self, connection):
        '''
        Does something when connection with host is lost (only with persistent connection).
        '''
        self.connection = None
        if self.parser.getPlaylistType() == 'HLS':
            debug(0, '%s _onConnectionLost', self)
            self.parser.updateLevelSegmentsList(self.cur_level).addCallback(
                self._updatePlaylistDone)
        else:  #only for youtube (for check buffering)
            debug(DEBUG, '%s _onConnectionLost', self)
            self.fetchNextSegment()

    #When status changes calls 'onPlaying' and 'onPaused' hooks
    #callback
    def _onStatusChanged(self, media_engine):
        '''
        Does something when player status change from play to pause and viceversa
        '''
        if media_engine.status == media_engine.PLAYING:
            self.controller.onPlaying()
            self.paused_time += time.time() - self.t_paused
        else:
            self.controller.onPaused()
            self.t_paused = time.time()

    #callback
    def _updatePlaylistDone(self, data):
        '''
        Called when the playlist for the current level is update
        '''
        playlist = self.parser.playlists[self.getCurrentLevel()]
        debug(DEBUG + 1, '%s playlist: %s', self, pformat(playlist))
        # start play if playlist has more than 2 fragments
        if len(playlist['segments']) > 2 and self.getCurrentSegmentIndex(
        ) < playlist['end_index']:
            self.fetchNextSegment()
        else:
            reactor.callLater(self.parser.getFragmentDuration(),
                              self.fetchNextSegment)