def pushData(self, data, fragment_duration, level, view, caps_data): debug(DEBUG, '%s pushData: pushed %s of data for level %s', self, format_bytes(len(data)), level) self.evicted_segment = 0 self.checkGstbuffer() # check view change for playout buffer eviction if (view != self.current_view): # check if gstreamer has enough data (for limiting rebuffering events) self.checkGstbuffer() # flush playout buffer byte = 0 sec = 0 self.evicted_segment = len(self.pushed_segments) for segment in self.pushed_segments: byte += segment["len_segment"] sec += segment["dur_segment"] self.queue['byte'] -= byte self.queue['sec'] -= sec del self.pushed_segments self.pushed_segments = [] self.playtime -= sec # set new current view self.current_view = view # fill playout buffer self.queue['byte'] +=len(data) self.queue['sec'] +=fragment_duration self.pushed_segments.append(dict(len_segment=len(data), dur_segment=fragment_duration, data=data))
def __harmonic_mean(v): '''Computes the harmonic mean of vector v''' x = numpy.array(v) debug(DEBUG+1, "%s __harmonic_mean: Bwe vect: %s", self, str(x)) m = 1.0/(sum(1.0/x)/len(x)) debug(DEBUG, "%s__harmonic_mean: Harmonic mean: %s/s", self, format_bytes(m)) return m
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)
def pushData(self, data, fragment_duration, level, caps_data): debug(DEBUG, '%s pushData: pushed %s of data for level %s', self, format_bytes(len(data)), level) self.queue['byte']+=len(data) self.queue['sec']+=fragment_duration self.pushed_segments.append(dict(len_segment=len(data),dur_segment=fragment_duration))
def fetchNextSegment(self,calledWithPort): ''' Schedules the download of the next segment at current level ''' #print ("MY PID IS "+str(os.getpid())) test=calledWithPort; 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 print colored('GETTING AN IDLE DURATION OF '+str(idle_duration),'green') else: #FIXME #uncomment idle_duration again! #idle_duration =0.0 idle_duration = self.controller.getIdleDuration() print colored('GETTING AN IDLE DURATION OF '+str(idle_duration),'green') # load the next segment #if calledWithPort: # if calledWithPort==getLocalport(): # reactor.callLater(idle_duration, self.startDownload, url_segment, byterange) # print("port seen from fetchNextSegment= "+str(getLocalport())) # print('called with port was '+ str(getLocalport())) # else: # print('RACE CONDITION!!!!!!') # print("port seen from fetchNextSegment= "+str(getLocalport())) # return #else: # print('fetchNextSegment was not called from ZMQ') print("calling start download with test= "+str(test)) reactor.callLater(idle_duration, self.startDownload, url_segment, byterange)
def pushData(self, data, fragment_duration, level, caps_data): debug(DEBUG, '%s pushData: pushed %s of data for level %s', self, format_bytes(len(data)), level) self.queue['byte'] += len(data) self.queue['sec'] += fragment_duration self.pushed_segments.append( dict(len_segment=len(data), dur_segment=fragment_duration))
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 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()
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) print("Prio next segment= true ") print("!!!PRIO") return new_level else: print("Prio next segment= false ") print("My socket is "+str(getLocalport())) 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 _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 calcControlAction(self): video_rates = self.feedback['rates'] T_tilde = self.feedback['last_download_time'] tau = self.feedback['fragment_duration'] x_tilde = self.feedback['cur_rate'] * self.feedback[ 'fragment_duration'] / T_tilde #bandwidth estimate sample on the last downloaded segment T_hat_old = self.getIdleDuration() #Actual inter-request time T = max(T_hat_old, T_tilde) self.T_old = T k = self.k w = self.w #in B/s beta = self.beta x_hat = max( 0, self.x_hat_last + T * k * (w - max(0, self.x_hat_last - x_tilde))) self.x_hat_last = x_hat control_action = self.__ewma_filter(x_hat) r = self.quantizeSpecialRate(control_action) B = self.feedback['queued_time'] T_hat = r * float(tau) / control_action + beta * (float(B) - float(self.Q)) self.setIdleDuration(T_hat - T_tilde) debug( DEBUG, "%s calcControlAction: ca: %s/s r: %s/s x_tilde: %s/s x_hat: %s/s T_tilde: %.2f T_hat: %.2f T: %.2f", self, format_bytes(control_action), format_bytes(r), format_bytes(x_tilde), format_bytes(x_hat), T_tilde, T_hat, T) return r
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 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()
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()
def updateLevelSegmentsList(self, level): '''Updater playlist for current level''' playlist = self.playlists[level] playlist = self.deletePlaylist(playlist) #Only for VOD and Live. Not for Live+REC c = defer.Deferred() debug(DEBUG-1, '%s updateLevelSegmentsList: %s', self, playlist['url']) # page callback def got_playlist(data, factory): debug(DEBUG+1, 'updateLevelSegmentsList: %s', data) cur_index = start_index = 0 segment_duration=4 # FIXME for live streams #cur_index = playlist.get('end_index', -1) + 1 for line in data.split('\n'): line = line.strip() if not line: continue if line.startswith('#EXT-X-TARGETDURATION:'): self.fragment_duration = 4 #setIdleDuration(fragment_duration) elif line.startswith('#EXTINF:'): line = line.replace('#EXTINF:', '') segment_duration = 4 elif line.startswith('#EXT-X-MEDIA-SEQUENCE:'): line = line.replace('#EXT-X-MEDIA-SEQUENCE:', '') cur_index = start_index = 1 elif not line.startswith('#'): try: index = hasGetIndex(line) except Exception: index = cur_index cur_index += 1 # first segments, set start_time if len(playlist['segments']) == 0: playlist['start_index'] = index self.start_time = max(self.start_time, index*self.fragment_duration) #playlist['duration'] = self.start_time if index > playlist['end_index']: if not line.startswith('http'): line = os.path.join(os.path.dirname(factory.url), line) _c = dict(url=line,byterange='',dur=segment_duration) playlist['segments'][index] = _c playlist['end_index'] = index playlist['duration'] += segment_duration elif line.startswith('#EXT-X-ENDLIST'): duration = playlist['duration'] playlist['is_live'] = True #print pformat(playlist) self.playlists[level] = playlist c.callback(1) # error handling def got_error(e, factory): debug(0, '%s updateLevelSegmentsList url: %s error: %s', self, factory.url, e) reactor.callLater(self.fragment_duration*0.5, self.updateLevelSegmentsList, level) d = getPage(playlist['url'], agent=USER_AGENT) d.deferred.addCallback(got_playlist, d) d.deferred.addErrback(got_error, d) return c
def on_data_received(self, connection, data, level): debug(DEBUG+1, '%s on_data_received: %s', self, format_bytes(len(data))) self.fd[level] = StringIO.StringIO(data) #print "level", level self.playlists[level]["header_data"]=data self.parse_atom(0,len(data)-1, level) self.deferredList[level].callback(1) self.connection_list[level].stop()
def init_connection(self, url, byterange, level): if self.connection_list[level]: self.connection_list[level].stop() debug(DEBUG+1, '%s init_connection: %s', self, url) self.connection_list[level] = ClientFactory(url) self.connection_list[level].connect('connection-made', self.on_connection_made, url, byterange, level) self.connection_list[level].connect('connection-lost', self.on_connection_lost) self.connection_list[level].connect('data-received', self.on_data_received, level)
def stop(self): ''' Stops the media engine. ''' if not self.is_runnning: return debug(DEBUG, '%s stop', self) self.is_runnning = False
def start(self): ''' Starts the media engine. ''' if self.is_runnning: return debug(DEBUG, '%s start', self) self.is_runnning = 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) reactor.callLater(0.1, self.fetchNextSegment)
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 pushData(self, data, fragment_duration, level, caps_data): buf = gst.Buffer(data) buf.duration = long(fragment_duration * 1e9) debug(DEBUG, '%s pushData: pushed %s of data (duration= %.2fs) for level %s', self, format_bytes(len(data)), fragment_duration, level) self.pipeline.get_by_name('src').emit('push-buffer', buf) del buf
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) self.terminated = True #process = subprocess.Popen(os.getpid()) #process.send_signal(signal.SIGINT) #sys.exit() 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.feedback['queued_time'] < self.controller.feedback[ 'max_buffer_time']: idle_duration = 0.0 #fetch segment after the last segment download is completed else: idle_duration = max( 0.0, self.controller.feedback['queued_time'] - self.controller.feedback['max_buffer_time'] - self.controller.feedback['last_download_time']) # if bandwidth is varried per segment set new bandwidth if self.mu is not None and self.sigma is not None: self.setNewBandwidth() # load the next segment reactor.callLater(idle_duration, self.startDownload, url_segment, cur_index, byterange)
def updateLevelSegmentsList(self, level): '''Updater playlist for current level''' playlist = self.playlists[level] playlist = self.deletePlaylist(playlist) #Only for VOD and Live. Not for Live+REC c = defer.Deferred() debug(DEBUG-1, '%s updateLevelSegmentsList: %s', self, playlist['url']) # page callback def got_playlist(data, factory): debug(DEBUG+1, 'updateLevelSegmentsList: %s', data) cur_index = start_index = 0 # FIXME for live streams #cur_index = playlist.get('end_index', -1) + 1 for line in data.split('\n'): line = line.strip() if not line: continue if line.startswith('#EXT-X-TARGETDURATION:'): self.fragment_duration = float(line.replace('#EXT-X-TARGETDURATION:', '')) #setIdleDuration(fragment_duration) elif line.startswith('#EXTINF:'): line = line.replace('#EXTINF:', '') segment_duration = float(line.split(',')[0]) elif line.startswith('#EXT-X-MEDIA-SEQUENCE:'): line = line.replace('#EXT-X-MEDIA-SEQUENCE:', '') cur_index = start_index = int(line) elif not line.startswith('#'): try: index = hasGetIndex(line) except Exception: index = cur_index cur_index += 1 # first segments, set start_time if len(playlist['segments']) == 0: playlist['start_index'] = index self.start_time = max(self.start_time, index*self.fragment_duration) #playlist['duration'] = self.start_time if index > playlist['end_index']: if not line.startswith('http'): line = os.path.join(os.path.dirname(factory.url), line) _c = dict(url=line,byterange='',dur=segment_duration) playlist['segments'][index] = _c playlist['end_index'] = index playlist['duration'] += segment_duration elif line.startswith('#EXT-X-ENDLIST'): duration = playlist['duration'] playlist['is_live'] = True #print pformat(playlist) self.playlists[level] = playlist c.callback(1) # error handling def got_error(e, factory): debug(0, '%s updateLevelSegmentsList url: %s error: %s', self, factory.url, e) reactor.callLater(self.fragment_duration*0.5, self.updateLevelSegmentsList, level) d = getPage(playlist['url'], agent=USER_AGENT) d.deferred.addCallback(got_playlist, d) d.deferred.addErrback(got_error, d) return c
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')
def pushData(self, data, fragment_duration, level, caps_data): buf = gst.Buffer(data) buf.duration = long(fragment_duration*1e9) debug(DEBUG, '%s pushData: pushed %s of data (duration= %ds) for level %s', self, format_bytes(len(data)), fragment_duration, level) self.pipeline.get_by_name('src').emit('push-buffer', buf) del buf
def calcControlAction(self): T = self.feedback['last_download_time'] cur = self.feedback['cur_rate'] tau = self.feedback['fragment_duration'] x = cur * tau / T y = self.__ewma_filter(x) self.setIdleDuration(tau-T) debug(DEBUG, "%s calcControlAction: y: %s/s x: %s/s T: %.2f", self, format_bytes(y), format_bytes(x), T) return y
def setIdleDuration(self, idle): ''' Sets idle duration when in steady state :param idle: seconds of idle between two consecutive downloads. ''' if idle < 0: idle = 0 debug(DEBUG, '%s setting Idle duration: %.2f', self, idle) self.idle_duration = idle
def startDownload(self, url, byterange, level): # start download debug(DEBUG+1, '%s startDownload %s %s', self, url, byterange) # start a new connection if not self.connection_list[level]: self.init_connection(url, byterange, level) return if not self.connection_list[level].client: return _, _, path = parse_url(url) self.connection_list[level].makeRequest(path, byterange)
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 _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()
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)
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()
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 _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 _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 got_page(data, factory): debug(DEBUG, '%s loadHlsPlaylist from %s:\n%s', self, factory.url, data) cur = None for line in data.split('\n'): line = line.strip() line = line.replace(", ",","); #print line if line.startswith('#EXT-X-STREAM-INF:'): line = line.replace('#EXT-X-STREAM-INF:', '') vr = None res = None for field in line.split(','): if field.startswith('BANDWIDTH='): field = field.replace('BANDWIDTH=', '') vr = int(field) #in B/s elif field.startswith('RESOLUTION='): field = field.replace('RESOLUTION=', '') res = field width=(res.split("x")[0]) print "width="+str(width) height=(res.split("x")[1]) print "height="+str(height) vr=float(vr/1000) vr=float(vr)/float(float(height)*float(width)) self.levels.append(dict(rate=vr,resolution=res)) print(self.levels) cur = dict(url='', is_live=True, segments=[], start_index=-1, end_index=-1, duration=0.0) continue elif cur: if not line.startswith('http'): line = os.path.join(os.path.dirname(factory.url), line) cur['url'] = line self.playlists.append(cur) cur = None #if self.cur_level >= len(self.playlists): # self.cur_level = max(self.cur_level, len(self.playlists)-1) deferredList = [] for i in range(len(self.levels)): print "unsorted "+str(self.levels[i]) for i in range(len(self.levels)): print "sorted "+str(self.levels[i]) deferredList.append(self.updateLevelSegmentsList(i)) dl = defer.DeferredList(deferredList) def _on_done(res): self.deferred.callback(True) dl.addCallback(_on_done)
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)
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() from if [1]",'red'); self.fetchNextSegment('') else: print colored("[_updatePlaylistDone] I call fetchNextSegment() from if [1]",'red'); reactor.callLater(self.parser.getFragmentDuration(), self.fetchNextSegment,calledWithPort='')
def onRunning(self): if self.getQueuedTime( ) >= self.min_queue_time and self.status == self.PAUSED: self.pipeline.set_state(gst.STATE_PLAYING) self.status = self.PLAYING debug(DEBUG, '%s running', self) self.emit('status-changed') elif self.getQueuedTime() == 0 and self.status == self.PLAYING: self.pipeline.set_state(gst.STATE_PAUSED) self.status = self.PAUSED debug(DEBUG, '%s underrun', self) self.emit('status-changed') reactor.callLater(0.1, self.onRunning)
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()
def got_playlist(data, factory): debug(DEBUG+1, 'updateLevelSegmentsList: %s', data) cur_index = start_index = 0 # FIXME for live streams #cur_index = playlist.get('end_index', -1) + 1 for line in data.split('\n'): line = line.strip() if not line: continue if line.startswith('#EXT-X-TARGETDURATION:'): self.fragment_duration = float(line.replace('#EXT-X-TARGETDURATION:', '')) #setIdleDuration(fragment_duration) elif line.startswith('#EXTINF:'): line = line.replace('#EXTINF:', '') segment_duration = float(line.split(',')[0]) elif line.startswith('#EXT-X-MEDIA-SEQUENCE:'): line = line.replace('#EXT-X-MEDIA-SEQUENCE:', '') cur_index = start_index = int(line) elif not line.startswith('#'): try: index = hasGetIndex(line) except Exception: index = cur_index cur_index += 1 # first segments, set start_time if len(playlist['segments']) == 0: playlist['start_index'] = index self.start_time = max(self.start_time, index*self.fragment_duration) #playlist['duration'] = self.start_time if index > playlist['end_index']: if not line.startswith('http'): line = os.path.join(os.path.dirname(factory.url), line) _c = dict(url=line,byterange='',dur=segment_duration) playlist['segments'][index] = _c playlist['end_index'] = index playlist['duration'] += segment_duration elif line.startswith('#EXT-X-ENDLIST'): duration = playlist['duration'] playlist['is_live'] = True #print pformat(playlist) self.playlists[level] = playlist c.callback(1)
def quantizeRate(self,rate): video_rates = self.feedback['rates'] cur = self.feedback['cur_rate'] level = self.feedback['level'] D_up = self.eps*rate D_down = 0 r_up = self.__levelLessThanRate(rate - D_up) r_down = self.__levelLessThanRate(rate - D_down) new_level = 0 if level < r_up: new_level = r_up elif r_up <= level and level <= r_down: new_level = level else: new_level = r_down debug(DEBUG, "%s quantizeRate: rate: %s/s cur: %s/s D_up: %s/s D_down: %s/s r_up: %d r_down: %d new_level: %d", self, format_bytes(rate), format_bytes(cur), format_bytes(D_up), format_bytes(D_down), r_up, r_down, new_level) debug(DEBUG, "%s quantizeRate: rates: %s", self, video_rates) return new_level
def start(self): BaseMediaEngine.start(self) # q = 0 #int(self.min_queue_time*1e9) #min-threshold-time v_sink = 'fakesink sync=true' if self.getVideoContainer() == 'MP4': demux = self.DEMUX_MP4 parse = self.PARSE_H264 elif self.getVideoContainer() == 'MPEGTS': demux = self.DEMUX_MPEGTS parse = self.PARSE_H264 elif self.getVideoContainer() == 'WEBM': demux = self.DEMUX_WEBM parse = self.PARSE_WEBM else: debug(0, '%s Cannot play: video/%s', self, self.getVideoContainer()) return debug(DEBUG, '%s Playing type: video/%s', self, self.getVideoContainer()) if self.decode_video: if not self.getVideoContainer() == 'WEBM': v_sink = self.DEC_VIDEO_H264 else: v_sink = self.DEC_VIDEO_VP8 desc = self.PIPELINE %(demux) + self.VIDEO_DEC %(parse, q, v_sink) else: desc = self.PIPELINE %(demux) + self.VIDEO_NODEC %(q, v_sink) debug(DEBUG, '%s pipeline: %s', self, desc) self.pipeline = gst.parse_launch(desc) self.queue = self.pipeline.get_by_name('queue_v') self.status = self.PAUSED self.pipeline.set_state(gst.STATE_PAUSED) self.onRunning()
def got_page(data, factory): debug(DEBUG, '%s loadHlsPlaylist from %s:\n%s', self, factory.url, data) cur = None for line in data.split('\n'): line = line.strip() line = line.replace(", ",","); #print line if line.startswith('#EXT-X-STREAM-INF:'): line = line.replace('#EXT-X-STREAM-INF:', '') vr = None res = None for field in line.split(','): if field.startswith('BANDWIDTH='): field = field.replace('BANDWIDTH=', '') vr = int(field)/8. #in B/s elif field.startswith('RESOLUTION='): field = field.replace('RESOLUTION=', '') res = field self.levels.append(dict(rate=vr,resolution=res)) cur = dict(url='', is_live=True, segments=[], start_index=-1, end_index=-1, duration=0.0) continue elif cur: if not line.startswith('http'): line = os.path.join(os.path.dirname(factory.url), line) cur['url'] = line self.playlists.append(cur) cur = None #if self.cur_level >= len(self.playlists): # self.cur_level = max(self.cur_level, len(self.playlists)-1) deferredList = [] for i in range(len(self.playlists)): deferredList.append(self.updateLevelSegmentsList(i)) dl = defer.DeferredList(deferredList) def _on_done(res): self.deferred.callback(True) dl.addCallback(_on_done)
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 calcControlAction(self): # call the actual adaptation algorithm next_level, Bdelay = self.adaptationAlgorithm() # debug prints debug(DEBUG, "%s feedback %s", self, self.feedback) debug(DEBUG, "%s fast start mode = %d", self, self.runningFastStart) debug(DEBUG, "%s next_level = %d, Bdelay = %f", self, next_level, Bdelay) # The algorithm returns Bdelay which represents the minimum buffer level # in seconds of playback when the next download must be started. # TAPAS wants to know how long it has to wait until the download of the # next segment. # Therefore we assume the that the buffer fill state falls below Bdelay # after (buffered seconds - Bdelay) seconds. self.setIdleDuration(0 if Bdelay == 0 else (self.feedback["queued_time"] - Bdelay)) return self.feedback['rates'][next_level]
def got_error(e, factory): debug(0, '%s updateLevelSegmentsList url: %s error: %s', self, factory.url, e) reactor.callLater(self.fragment_duration*0.5, self.updateLevelSegmentsList, level)
def got_error(e): debug(0, '%s loadHlsPlaylist error: %s', self, e)