/* * Created on 1 Nov 2006 * Created by Paul Gardner * Copyright (C) 2006 Aelitis, All Rights Reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * AELITIS, SAS au capital de 63.529,40 euros * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France. * */ package com.aelitis.azureus.core.download; import java.util.List; import org.gudy.azureus2.core3.config.COConfigurationManager; import org.gudy.azureus2.core3.config.ParameterListener; import org.gudy.azureus2.core3.config.impl.TransferSpeedValidator; import org.gudy.azureus2.core3.disk.DiskManager; import org.gudy.azureus2.core3.disk.DiskManagerFileInfo; import org.gudy.azureus2.core3.disk.DiskManagerPiece; import org.gudy.azureus2.core3.download.DownloadManager; import org.gudy.azureus2.core3.download.DownloadManagerListener; import org.gudy.azureus2.core3.download.DownloadManagerPeerListener; import org.gudy.azureus2.core3.download.impl.DownloadManagerAdapter; import org.gudy.azureus2.core3.global.GlobalManager; import org.gudy.azureus2.core3.peer.PEPeer; import org.gudy.azureus2.core3.peer.PEPeerManager; import org.gudy.azureus2.core3.torrent.TOTorrent; import org.gudy.azureus2.core3.util.*; import com.aelitis.azureus.core.peermanager.piecepicker.PiecePicker; import com.aelitis.azureus.core.peermanager.piecepicker.PieceRTAProvider; import com.aelitis.azureus.core.torrent.PlatformTorrentUtils; import com.aelitis.azureus.core.util.average.Average; import com.aelitis.azureus.core.util.average.AverageFactory; import com.aelitis.azureus.util.ConstantsVuze; public class EnhancedDownloadManager { public static int DEFAULT_MINIMUM_INITIAL_BUFFER_SECS_FOR_ETA = 30; // number of seconds of buffer required before we fall back to normal bt mode public static int MINIMUM_INITIAL_BUFFER_SECS; static{ COConfigurationManager.addAndFireParameterListeners( new String[]{ "filechannel.rt.buffer.millis" }, new ParameterListener() { public void parameterChanged( String parameterName ) { int channel_buffer_millis = COConfigurationManager.getIntParameter( "filechannel.rt.buffer.millis" ); MINIMUM_INITIAL_BUFFER_SECS = (2 * channel_buffer_millis )/1000; } }); } public static final int REACTIVATE_PROVIDER_PERIOD = 5*1000; public static final int REACTIVATE_PROVIDER_PERIOD_TICKS = REACTIVATE_PROVIDER_PERIOD/DownloadManagerEnhancer.TICK_PERIOD; public static final int LOG_PROG_STATS_PERIOD = 10*1000; public static final int LOG_PROG_STATS_TICKS = LOG_PROG_STATS_PERIOD/DownloadManagerEnhancer.TICK_PERIOD; private static final int content_stream_bps_min_increase_ratio = 5; private static final int content_stream_bps_max_increase_ratio = 8; private DownloadManagerEnhancer enhancer; private DownloadManager download_manager; private boolean explicit_progressive; private volatile PiecePicker current_piece_pickler; private volatile boolean progressive_active = false; private long content_min_delivery_bps; private int minimum_initial_buffer_secs_for_eta; private bufferETAProvider buffer_provider = new bufferETAProvider(); private progressiveStats progressive_stats; private boolean marked_active; private boolean destroyed; private DownloadManagerListener dmListener; private EnhancedDownloadManagerFile[] enhanced_files; protected EnhancedDownloadManager( DownloadManagerEnhancer _enhancer, DownloadManager _download_manager ) { enhancer = _enhancer; download_manager = _download_manager; DiskManagerFileInfo[] files = download_manager.getDiskManagerFileInfo(); minimum_initial_buffer_secs_for_eta = DEFAULT_MINIMUM_INITIAL_BUFFER_SECS_FOR_ETA; enhanced_files = new EnhancedDownloadManagerFile[files.length]; long offset = 0; for (int i=0;i<files.length;i++){ DiskManagerFileInfo f = files[i]; enhanced_files[i] = new EnhancedDownloadManagerFile( f, offset ); offset += f.getLength(); } int primary_index = getPrimaryFileIndex(); EnhancedDownloadManagerFile primary_file = enhanced_files[primary_index==-1?0:primary_index]; progressive_stats = createProgressiveStats( download_manager, primary_file ); download_manager.addPeerListener( new DownloadManagerPeerListener() { public void peerManagerWillBeAdded( PEPeerManager peer_manager ) { } public void peerManagerAdded( PEPeerManager manager ) { synchronized( EnhancedDownloadManager.this ){ current_piece_pickler = manager.getPiecePicker(); if ( progressive_active && current_piece_pickler != null ){ buffer_provider.activate( current_piece_pickler ); } } } public void peerManagerRemoved( PEPeerManager manager ) { synchronized( EnhancedDownloadManager.this ){ if ( progressive_active ){ setProgressiveMode( false ); } } } public void peerAdded( PEPeer peer ) { } public void peerRemoved( PEPeer peer ) { } }); } public int getPrimaryFileIndex() { DiskManagerFileInfo info = download_manager.getDownloadState().getPrimaryFile(); if ( info == null ){ return( -1 ); } return( info.getIndex()); } public void setExplicitProgressive( int min_initial_buffer_secs, long min_bps, int file_index ) { if ( file_index >= 0 && file_index < enhanced_files.length ){ explicit_progressive = true; minimum_initial_buffer_secs_for_eta = min_initial_buffer_secs; content_min_delivery_bps = min_bps; EnhancedDownloadManagerFile primary_file = enhanced_files[file_index]; progressive_stats = createProgressiveStats( download_manager, primary_file ); } } public String getName() { return( download_manager.getDisplayName()); } public byte[] getHash() { TOTorrent t = download_manager.getTorrent(); if ( t == null ){ return( null ); } try{ return( t.getHash()); }catch( Throwable e ){ return( null ); } } public boolean isPlatform() { TOTorrent torrent = download_manager.getTorrent(); if ( torrent != null ){ return( PlatformTorrentUtils.isContent( torrent, true )); } return( false ); } public EnhancedDownloadManagerFile[] getFiles() { return( enhanced_files ); } protected long getTargetSpeed() { long target_speed = progressive_active?progressive_stats.getStreamBytesPerSecondMax():content_min_delivery_bps; if ( target_speed < content_min_delivery_bps ){ target_speed = content_min_delivery_bps; } return( target_speed ); } protected boolean updateStats( int tick_count ) { return( updateProgressiveStats( tick_count )); } public boolean supportsProgressiveMode() { TOTorrent torrent = download_manager.getTorrent(); if ( torrent == null ){ return( false ); } return( enhancer.isProgressiveAvailable() && ( PlatformTorrentUtils.isContentProgressive( torrent ) || explicit_progressive )); } public void prepareForProgressiveMode( boolean active ) { enhancer.prepareForProgressiveMode( download_manager, active ); } public boolean setProgressiveMode( boolean active ) { return( setProgressiveMode( active, false )); } protected boolean setProgressiveMode( boolean active, boolean switching_progressive_downloads ) { TOTorrent torrent = download_manager.getTorrent(); DiskManagerFileInfo primaryFile = download_manager.getDownloadState().getPrimaryFile(); if ( torrent == null || primaryFile == null ){ return( false ); } EnhancedDownloadManagerFile enhanced_file = enhanced_files[primaryFile.getIndex()]; synchronized( this ){ if ( progressive_active == active ){ return( true ); } if (active && !supportsProgressiveMode()) { Debug.out( "Attempt to set progress mode on non-progressible content - " + getName()); return( false ); } log( "Progressive mode changed to " + active ); final GlobalManager gm = download_manager.getGlobalManager(); if (active) { if (dmListener == null) { dmListener = new DownloadManagerAdapter() { public void downloadComplete(DownloadManager manager) { enhancer.resume(); } }; } download_manager.addListener(dmListener); // Check existing downloading torrents and turn off any // existing progressive/downloading Object[] dms = gm.getDownloadManagers().toArray(); for (int i = 0; i < dms.length; i++) { DownloadManager dmCheck = (DownloadManager) dms[i]; if (dmCheck.equals(download_manager)) { continue; } if (!dmCheck.isDownloadComplete(false)) { int state = dmCheck.getState(); if (state == DownloadManager.STATE_DOWNLOADING || state == DownloadManager.STATE_QUEUED) { enhancer.pause( dmCheck ); } EnhancedDownloadManager edmCheck = enhancer.getEnhancedDownload(dmCheck); if (edmCheck != null && edmCheck.getProgressiveMode()) { edmCheck.setProgressiveMode(false, true); } } } if (download_manager.isPaused()) { enhancer.resume( download_manager ); } // Make sure download can start by moving out of stop state // and putting at top if (download_manager.getState() == DownloadManager.STATE_STOPPED) { download_manager.setStateWaiting(); } if (download_manager.getPosition() != 1) { download_manager.getGlobalManager().moveTo(download_manager, 1); } } else { download_manager.removeListener(dmListener); if ( !switching_progressive_downloads ){ enhancer.resume(); } } progressive_active = active; if ( progressive_active ){ enhancer.progressiveActivated(); } if ( current_piece_pickler != null ){ if ( progressive_active ){ buffer_provider.activate( current_piece_pickler ); progressive_stats.update( 0 ); }else{ buffer_provider.deactivate( current_piece_pickler ); progressive_stats = createProgressiveStats( download_manager, enhanced_file ); } }else{ progressive_stats = createProgressiveStats( download_manager, enhanced_file ); } if ( !switching_progressive_downloads ){ if ( active ){ RealTimeInfo.setProgressiveActive( progressive_stats.getStreamBytesPerSecondMax()); }else{ RealTimeInfo.setProgressiveInactive(); } } } return( true ); } public boolean getProgressiveMode() { return( progressive_active ); } public long getProgressivePlayETA() { progressiveStats stats = getProgressiveStats(); if ( stats == null ){ return( Long.MAX_VALUE ); } long eta = stats.getETA(); return( eta ); } protected progressiveStats getProgressiveStats() { synchronized( this ){ if ( progressive_stats == null ){ return( null ); } return( progressive_stats.getCopy()); } } protected progressiveStats createProgressiveStats( DownloadManager dm, EnhancedDownloadManagerFile file ) { return( new progressiveStatsCommon( dm, file )); } protected boolean updateProgressiveStats( int tick_count ) { if ( !progressive_active ){ return( false ); } synchronized( this ){ if ( !progressive_active || progressive_stats == null ){ return( false ); } if ( tick_count % REACTIVATE_PROVIDER_PERIOD_TICKS == 0 ){ PiecePicker piece_picker = current_piece_pickler; if ( piece_picker != null ){ buffer_provider.checkActivation( piece_picker ); } } progressive_stats.update( tick_count ); long current_max = progressive_stats.getStreamBytesPerSecondMax(); if ( RealTimeInfo.getProgressiveActiveBytesPerSec() != current_max ){ RealTimeInfo.setProgressiveActive( current_max ); } } return( true ); } protected void setRTA( boolean active ) { synchronized( this ){ if ( marked_active && !active ){ marked_active = false; RealTimeInfo.removeRealTimeTask(); } if ( destroyed ){ return; } if ( !marked_active && active ){ marked_active = true; RealTimeInfo.addRealTimeTask(); } } } public long getContiguousAvailableBytes( int file_index, long file_start_offset, long stop_counting_after ) { if ( file_index < 0 || file_index >= enhanced_files.length ) { return( -1 ); } EnhancedDownloadManagerFile efile = enhanced_files[ file_index ]; DiskManagerFileInfo file = efile.getFile(); DiskManager dm = download_manager.getDiskManager(); if ( dm == null ){ if ( file.getDownloaded() == file.getLength()){ return( file.getLength() - file_start_offset ); } return( -1 ); } int piece_size = dm.getPieceLength(); long start_index = efile.getByteOffestInTorrent() + file_start_offset; int first_piece_index = (int)( start_index / piece_size ); int first_piece_offset = (int)( start_index % piece_size ); int last_piece_index = file.getLastPieceNumber(); DiskManagerPiece[] pieces = dm.getPieces(); DiskManagerPiece first_piece = pieces[first_piece_index]; long available = 0; if ( !first_piece.isDone()){ boolean[] blocks = first_piece.getWritten(); if ( blocks == null ){ if ( first_piece.isDone()){ available = first_piece.getLength() - first_piece_offset; } }else{ int piece_offset = 0; for (int j=0;j<blocks.length;j++){ if ( blocks[j] ){ int block_size = first_piece.getBlockSize( j ); piece_offset = piece_offset + block_size; if ( available == 0 ){ if ( piece_offset > first_piece_offset ){ available = piece_offset - first_piece_offset; } }else{ available += block_size; } }else{ break; } } } }else{ available = first_piece.getLength() - first_piece_offset; for (int i=first_piece_index+1;i<=last_piece_index;i++){ if ( stop_counting_after > 0 && available >= stop_counting_after ){ break; } DiskManagerPiece piece = pieces[i]; if ( piece.isDone()){ available += piece.getLength(); }else{ boolean[] blocks = piece.getWritten(); if ( blocks == null ){ if ( piece.isDone()){ available += piece.getLength(); }else{ break; } }else{ for (int j=0;j<blocks.length;j++){ if ( blocks[j] ){ available += piece.getBlockSize( j ); }else{ break; } } } break; } } } long max_available = file.getLength() - file_start_offset; if ( available > max_available ){ available = max_available; } return( available ); } public DownloadManager getDownloadManager() { return download_manager; } protected void destroy() { synchronized( this ){ setRTA( false ); destroyed = true; } } protected void log( String str ) { log( str, true ); } protected void log( String str, boolean to_file ) { log( download_manager, str, to_file ); } protected void log( DownloadManager dm, String str, boolean to_file ) { str = dm.toString() + ": " + str; if ( to_file ){ AEDiagnosticsLogger diag_logger = AEDiagnostics.getLogger("v3.Stream"); diag_logger.log(str); } if ( ConstantsVuze.DIAG_TO_STDOUT ) { System.out.println(Thread.currentThread().getName() + "|" + System.currentTimeMillis() + "] " + str); } } protected class bufferETAProvider implements PieceRTAProvider { private boolean is_buffering = true; private long[] piece_rtas; private long last_buffer_size; private long last_buffer_size_time; private boolean active; private long last_recalc; protected void activate( PiecePicker picker ) { synchronized( EnhancedDownloadManager.this ){ if ( !active ){ log( "Activating RTA provider" ); active = true; picker.addRTAProvider( this ); } } } protected void deactivate( PiecePicker picker ) { synchronized( EnhancedDownloadManager.this ){ if ( active ){ log( "Deactivating RTA provider" ); picker.removeRTAProvider( this ); } piece_rtas = null; active = false; } } protected void checkActivation( PiecePicker picker ) { // might need to re-enable the buffer provider if speeds change if ( getProgressivePlayETA() > 0 ){ synchronized( EnhancedDownloadManager.this ){ if ( piece_rtas == null ){ activate( picker ); } } } } public long[] updateRTAs( PiecePicker picker ) { long mono_now = SystemTime.getMonotonousTime(); if ( mono_now - last_recalc < 500 ){ return( piece_rtas ); } last_recalc = mono_now; DiskManager disk_manager = download_manager.getDiskManager(); progressiveStats stats = progressive_stats; if ( disk_manager == null || stats == null || stats.getFile().isComplete()){ deactivate( picker ); return( null ); } EnhancedDownloadManagerFile file = stats.getFile(); long abs_provider_pos = stats.getCurrentProviderPosition( true ); long rel_provider_pos = stats.getCurrentProviderPosition( false ); long buffer_bytes = stats.getBufferBytes(); boolean buffering = getProgressivePlayETA() >= 0; if ( buffering ){ long buffer_size = getContiguousAvailableBytes( file.getIndex(), rel_provider_pos, buffer_bytes ); if ( buffer_size == buffer_bytes ){ buffering = false; } } if ( buffering != is_buffering ){ if ( buffering ){ log( "Switching to buffer mode" ); }else{ log( "Switching to RTA mode" ); } is_buffering = buffering; } long piece_size = disk_manager.getPieceLength(); int start_piece = (int)( abs_provider_pos / piece_size ); int end_piece = file.getFile().getLastPieceNumber(); piece_rtas = new long[ picker.getNumberOfPieces()]; long now = SystemTime.getCurrentTime(); if ( is_buffering ){ for (int i=start_piece;i<=end_piece;i++){ // not bothered about times here but need to use large increments to ensure // that pieces are picked in order even for slower peers piece_rtas[i] = now+(i*60000); } long buffer_size = getContiguousAvailableBytes( file.getIndex(), rel_provider_pos, 0 ); if ( last_buffer_size != buffer_size ){ last_buffer_size = buffer_size; last_buffer_size_time = now; }else{ if ( now < last_buffer_size_time ){ last_buffer_size_time = now; }else{ long stalled_for = now - last_buffer_size_time; long dl_speed = progressive_stats.getDownloadBytesPerSecond(); if ( dl_speed > 0 ){ long block_time = (DiskManager.BLOCK_SIZE * 1000) / dl_speed; if ( stalled_for > Math.max( 5000, 5*block_time )){ long target_rta = now + block_time; int blocked_piece_index = (int)((abs_provider_pos + buffer_size ) / disk_manager.getPieceLength()); DiskManagerPiece[] pieces = disk_manager.getPieces(); if ( blocked_piece_index < pieces.length ){ if ( pieces[blocked_piece_index].isDone()){ blocked_piece_index++; if ( blocked_piece_index < pieces.length ){ if ( pieces[blocked_piece_index].isDone()){ blocked_piece_index = -1; } }else{ blocked_piece_index = -1; } } } if ( blocked_piece_index >= 0 ){ piece_rtas[blocked_piece_index] = target_rta; log( "Buffer provider: reprioritising lagging piece " + blocked_piece_index + " with rta " + block_time ); } } } } } }else{ long bytes_offset = 0; long max_bps = stats.getStreamBytesPerSecondMax(); for (int i=start_piece;i<=end_piece;i++){ piece_rtas[i] = now + ( 1000 * ( bytes_offset / max_bps )); bytes_offset += piece_size; if ( bytes_offset > buffer_bytes ){ break; } } } return( piece_rtas ); } public long getCurrentPosition() { return( 0 ); } public long getStartTime() { return( 0 ); } public long getStartPosition() { return( 0 ); } public long getBlockingPosition() { return( 0 ); } public void setBufferMillis( long millis, long delay_millis ) { } public String getUserAgent() { return( null ); } } protected abstract class progressiveStats implements Cloneable { protected abstract EnhancedDownloadManagerFile getFile(); protected abstract boolean isProviderActive(); protected abstract long getCurrentProviderPosition( boolean absolute ); protected abstract long getStreamBytesPerSecondMax(); protected abstract long getStreamBytesPerSecondMin(); protected abstract long getDownloadBytesPerSecond(); protected abstract long getETA(); public abstract long getBufferBytes(); protected abstract long getSecondsToDownload(); protected abstract long getSecondsToWatch(); protected abstract void update( int tick_count ); protected progressiveStats getCopy() { try{ return((progressiveStats)clone()); }catch( CloneNotSupportedException e ){ Debug.printStackTrace(e); return( null ); } } protected String formatBytes( long l ) { return( DisplayFormatters.formatByteCountToKiBEtc( l )); } protected String formatSpeed( long l ) { return( DisplayFormatters.formatByteCountToKiBEtcPerSec( l )); } } protected class progressiveStatsCommon extends progressiveStats { private EnhancedDownloadManagerFile primary_file; private PieceRTAProvider current_provider; private String current_user_agent; private long content_stream_bps_min; private long content_stream_bps_max; private Average capped_download_rate_average = AverageFactory.MovingImmediateAverage( 10 ); private Average discard_rate_average = AverageFactory.MovingImmediateAverage( 10 ); private long last_discard_bytes = download_manager.getStats().getDiscarded(); private long actual_bytes_to_download; private long weighted_bytes_to_download; // gives less weight to incomplete pieces private long provider_life_secs; private long provider_initial_position; private long provider_byte_position; private long provider_last_byte_position = -1; private long provider_blocking_byte_position; private Average provider_speed_average = AverageFactory.MovingImmediateAverage( 10 ); protected progressiveStatsCommon( DownloadManager _dm, EnhancedDownloadManagerFile _primary_file ) { primary_file = _primary_file; TOTorrent torrent = download_manager.getTorrent(); content_stream_bps_min = explicit_progressive?content_min_delivery_bps:PlatformTorrentUtils.getContentStreamSpeedBps( torrent ); if ( content_stream_bps_min == 0 ){ // hack in some test values for torrents that don't have a bps in them yet long size = torrent.getSize(); if ( size < 200*1024*1024 ){ content_stream_bps_min = 30*1024; }else if ( size < 1000*1024*1024L ){ content_stream_bps_min = 200*1024; }else{ content_stream_bps_min = 400*1024; } } // bump it up by a bit to be conservative to deal with fluctuations, discards etc. content_stream_bps_min += content_stream_bps_min / content_stream_bps_min_increase_ratio; content_stream_bps_max = content_stream_bps_min + ( content_stream_bps_min / content_stream_bps_max_increase_ratio ); setRTA( false ); log( download_manager, "content_stream_bps=" + getStreamBytesPerSecondMin() + ",primary=" + primary_file.getFile().getIndex(), true ); } protected void updateCurrentProvider( PieceRTAProvider provider ) { long file_start = primary_file.getByteOffestInTorrent(); if ( current_provider != provider || provider == null ){ current_provider = provider; current_user_agent = null; provider_speed_average = AverageFactory.MovingImmediateAverage( 10 ); if ( current_provider == null ){ provider_life_secs = 0; provider_initial_position = file_start; provider_byte_position = file_start; provider_blocking_byte_position = -1; provider_last_byte_position = -1; }else{ provider_initial_position = Math.max( file_start, current_provider.getStartPosition()); provider_byte_position = provider_initial_position; provider_last_byte_position = provider_initial_position; provider_blocking_byte_position = current_provider.getBlockingPosition(); provider_life_secs = ( SystemTime.getCurrentTime() - current_provider.getStartTime()) / 1000; if ( provider_life_secs < 0 ){ provider_life_secs = 0; } } setRTA( current_provider != null ); }else{ provider_life_secs++; if ( current_user_agent == null ){ current_user_agent = current_provider.getUserAgent(); if ( current_user_agent != null ){ log( "Provider user agent = " + current_user_agent ); } } provider_byte_position = Math.max( file_start, current_provider.getCurrentPosition()); provider_blocking_byte_position = current_provider.getBlockingPosition(); long bytes_read = provider_byte_position - provider_last_byte_position; provider_speed_average.update( bytes_read ); provider_last_byte_position = provider_byte_position; } } protected boolean isProviderActive() { return( current_provider != null ); } protected long getInitialProviderPosition() { return( provider_initial_position ); } protected long getCurrentProviderPosition( boolean absolute ) { long res = provider_byte_position; if ( absolute ){ if ( res == 0 ){ res = primary_file.getByteOffestInTorrent(); } }else{ res -= primary_file.getByteOffestInTorrent(); if ( res < 0 ){ res = 0; } } return( res ); } protected long getProviderLifeSecs() { return( provider_life_secs ); } protected void update( int tick_count ) { long download_rate = download_manager.getStats().getDataReceiveRate(); capped_download_rate_average.update( download_rate ); long discards = download_manager.getStats().getDiscarded(); discard_rate_average.update( discards - last_discard_bytes ); last_discard_bytes = discards; DiskManager disk_manager = download_manager.getDiskManager(); PiecePicker picker = current_piece_pickler; if ( getStreamBytesPerSecondMin() > 0 && disk_manager != null && picker != null ){ List providers = picker.getRTAProviders(); long max_cp = 0; PieceRTAProvider best_provider = null; for (int i=0;i<providers.size();i++){ PieceRTAProvider provider = (PieceRTAProvider)providers.get(i); if ( provider.getStartTime() > 0 ){ long cp = provider.getCurrentPosition(); if ( cp >= max_cp ){ best_provider = provider; max_cp = cp; } } } updateCurrentProvider( best_provider ); if ( best_provider != null ){ // the file channel provider will try best-effort-RTA based which will result // in high discard - back it off based on how much slack we have long relative_pos = getCurrentProviderPosition( false ); long buffer_bytes = getContiguousAvailableBytes( primary_file.getIndex(), relative_pos, getStreamBytesPerSecondMin() * 60 ); long buffer_secs = buffer_bytes / getStreamBytesPerSecondMin(); // don't be too aggresive with small buffers buffer_secs = Math.max( 10, buffer_secs ); best_provider.setBufferMillis( 15*1000, buffer_secs * 1000 ); } DiskManagerPiece[] pieces = disk_manager.getPieces(); actual_bytes_to_download = 0; weighted_bytes_to_download = 0; int first_incomplete_piece = -1; int piece_size = disk_manager.getPieceLength(); int last_piece_number = primary_file.getFile().getLastPieceNumber(); for (int i=(int)(provider_byte_position/piece_size);i<=last_piece_number;i++){ DiskManagerPiece piece = pieces[i]; if ( piece.isDone()){ continue; } if ( first_incomplete_piece == -1 ){ first_incomplete_piece = i; } boolean[] blocks = piece.getWritten(); int bytes_this_piece = 0; if ( blocks == null ){ bytes_this_piece = piece.getLength(); }else{ for (int j=0;j<blocks.length;j++){ if ( !blocks[j] ){ bytes_this_piece += piece.getBlockSize( j ); } } } if ( bytes_this_piece > 0 ){ actual_bytes_to_download += bytes_this_piece; int diff = i - first_incomplete_piece; if ( diff == 0 ){ weighted_bytes_to_download += bytes_this_piece; }else{ int weighted_bytes_done = piece.getLength() - bytes_this_piece; weighted_bytes_done = ( weighted_bytes_done * ( pieces.length - i )) / (pieces.length - first_incomplete_piece); weighted_bytes_to_download += piece.getLength() - weighted_bytes_done; } } } } log( getString(), tick_count % LOG_PROG_STATS_TICKS == 0 ); } protected long getETA() { DiskManagerFileInfo file = primary_file.getFile(); if ( file.getLength() == file.getDownloaded()){ return( 0 ); } long download_rate = getDownloadBytesPerSecond(); if ( download_rate <= 0 ){ return( Long.MAX_VALUE ); } long buffer_bytes = getBufferBytes(); long buffer_done = getContiguousAvailableBytes( file.getIndex(), getCurrentProviderPosition( false ), buffer_bytes ); long rem_buffer = buffer_bytes - buffer_done; // ok as initial dl is forced in order byte buffer-rta long rem_secs = (rem_buffer<=0)?0:(rem_buffer / download_rate); long secs_to_download = getSecondsToDownload(); long secs_to_watch = getSecondsToWatch(); long eta = secs_to_download - secs_to_watch; if ( rem_secs > eta && rem_secs > 0 ){ eta = rem_secs; } return( eta ); } protected long getStreamBytesPerSecondMax() { return( content_stream_bps_max ); } protected long getStreamBytesPerSecondMin() { return( content_stream_bps_min ); } public long getBufferBytes() { long min_dl = minimum_initial_buffer_secs_for_eta * getStreamBytesPerSecondMax(); return( min_dl ); } protected EnhancedDownloadManagerFile getFile() { return( primary_file ); } protected long getDownloadBytesPerSecond() { long original = (long)capped_download_rate_average.getAverage(); long current = original; int dl_limit = download_manager.getStats().getDownloadRateLimitBytesPerSecond(); if ( dl_limit > 0 ){ current = Math.min( current, dl_limit ); } int global_limit = TransferSpeedValidator.getGlobalDownloadRateLimitBytesPerSecond(); if ( global_limit > 0 ){ current = Math.min( current, global_limit ); } return( current ); } protected long getSecondsToDownload() { long download_rate = getDownloadBytesPerSecond(); if ( download_rate == 0 ){ return( Long.MAX_VALUE ); } return( weighted_bytes_to_download / download_rate ); } public long getSecondsToWatch() { return(( primary_file.getLength() - getCurrentProviderPosition( false )) / getStreamBytesPerSecondMin()); } protected String getString() { long dl_rate = getDownloadBytesPerSecond(); long buffer_bytes = getBufferBytes(); long buffer_done = getContiguousAvailableBytes( primary_file.getIndex(), getCurrentProviderPosition( false ), buffer_bytes ); return( "play_eta=" + getETA() + "/d=" + getSecondsToDownload() + "/w=" + getSecondsToWatch()+ ", dl_rate=" + formatSpeed(dl_rate)+ ", download_rem=" + formatBytes(weighted_bytes_to_download) + "/" + formatBytes(actual_bytes_to_download) + ", discard_rate=" + formatSpeed((long)discard_rate_average.getAverage()) + ", buffer: " + buffer_bytes + "/" + buffer_done + ", prov: byte=" + formatBytes( provider_byte_position ) + " secs=" + ( provider_byte_position/getStreamBytesPerSecondMin()) + " speed=" + formatSpeed((long)provider_speed_average.getAverage()) + " block= " + formatBytes( provider_blocking_byte_position )); } } }