/*
* 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.net.InetAddress;
import java.util.*;
import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.config.ParameterListener;
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.peer.PEPeerManagerStats;
import org.gudy.azureus2.core3.peer.PEPeerStats;
import org.gudy.azureus2.core3.peer.PEPiece;
import org.gudy.azureus2.core3.torrent.TOTorrent;
import org.gudy.azureus2.core3.tracker.client.TRTrackerScraperResponse;
import org.gudy.azureus2.core3.util.AEDiagnostics;
import org.gudy.azureus2.core3.util.AEDiagnosticsLogger;
import org.gudy.azureus2.core3.util.ConcurrentHasher;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.DisplayFormatters;
import org.gudy.azureus2.core3.util.SystemTime;
import org.gudy.azureus2.plugins.download.Download;
import org.gudy.azureus2.pluginsimpl.local.PluginCoreUtils;
import com.aelitis.azureus.core.peer.cache.CacheDiscovery;
import com.aelitis.azureus.core.peer.cache.CachePeer;
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.ui.swt.utils.PublishUtils;
import com.aelitis.azureus.util.Constants;
import com.aelitis.azureus.util.DownloadUtils;
public class
EnhancedDownloadManager
{
public static int DEFAULT_MINIMUM_INITIAL_BUFFER_SECS_FOR_ETA = 30;
public static int WMP_MINIMUM_INITIAL_BUFFER_SECS_FOR_ETA = 60;
// 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 SPEED_CONTROL_INITIAL_DELAY = 10*1000;
public static final int SPEED_INCREASE_GRACE_PERIOD = 3*1000;
public static final int PEER_INJECT_GRACE_PERIOD = 3*1000;
public static final int IDLE_PEER_DISCONNECT_PERIOD = 60*1000;
public static final int IDLE_SEED_DISCONNECT_PERIOD = 60*1000;
public static final int MIN_SEED_CONNECTION_TIME = 60*1000;
public static final int IDLE_SEED_DISCONNECT_SECS = IDLE_SEED_DISCONNECT_PERIOD/1000;
public static final int CACHE_RECONNECT_MIN_PERIOD = 15*60*1000;
public static final int CACHE_REQUERY_MIN_PERIOD = 60*60*1000;
public static final int TARGET_SPEED_EXCESS_MARGIN = 2*1024;
public static final int DISCONNECT_CHECK_PERIOD = 10*1000;
public static final int DISCONNECT_CHECK_TICKS = DISCONNECT_CHECK_PERIOD/DownloadManagerEnhancer.TICK_PERIOD;
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 String TRACKER_PROG_PREFIX = "azprog";
private static final String PM_SEED_TIME_KEY = "EnhancedDownloadManager:seedtime";
private static final String PEER_CACHE_KEY = "EnhancedDownloadManager:cachepeer";
private static int internal_content_stream_bps_increase_ratio = 5;
private static int internal_content_stream_bps_increase_absolute = 0;
// these are here to allow other components (e.g. a plugin) to modify behaviour
// while we verify that things work ok
public static void
setInternalContentStreamBPSIncreaseRatio(
String caller_id,
int ratio )
{
internal_content_stream_bps_increase_ratio = ratio;
}
public static void
setInternalContentStreamBPSIncreaseAbsolute(
String caller_id,
int abs )
{
internal_content_stream_bps_increase_absolute = abs;
}
private DownloadManagerEnhancer enhancer;
private DownloadManager download_manager;
private boolean platform_content;
private transient PiecePicker current_piece_pickler;
private boolean progressive_active = false;
private long content_min_delivery_bps;
private int minimum_initial_buffer_secs_for_eta;
private int explicit_minimum_buffer_bytes;
private bufferETAProvider buffer_provider = new bufferETAProvider();
private boostETAProvider boost_provider = new boostETAProvider();
private progressiveStats progressive_stats;
private boolean progressive_informed = false;
private long time_download_started;
private Average download_speed_average = AverageFactory.MovingImmediateAverage( 5 );
private boolean marked_active;
private boolean destroyed;
private DownloadManagerListener dmListener;
private static final int STALLED_TIMEOUT = 2*60*1000;
private boolean publish_handling_complete;
private long publish_sent = -1;
private long publish_sent_time;
private EnhancedDownloadManagerFile[] enhanced_files;
private EnhancedDownloadManagerFile primary_file;
// ********* reset these in resetVars ***********
private long last_speed_increase;
private long last_peer_inject;
private long last_lookup_time;
private LinkedList new_peers;
private List cache_peers;
private List disconnected_cache_peers;
private CachePeer[] lookup_peers;
private void
resetVars()
{
last_speed_increase = 0;
last_peer_inject = 0;
last_lookup_time = 0;
new_peers = null;
cache_peers = null;
disconnected_cache_peers = null;
lookup_peers = null;
}
protected
EnhancedDownloadManager(
DownloadManagerEnhancer _enhancer,
DownloadManager _download_manager )
{
enhancer = _enhancer;
download_manager = _download_manager;
DiskManagerFileInfo[] files = download_manager.getDiskManagerFileInfo();
// hack - we don't know the actual player until play starts so we just use the file name
// TODO: we can probably read the registry to work out what player is associated with
// the file extension?
boolean found_wmv = false;
for (int i=0;i<files.length;i++){
String file_name = files[i].getFile(true).getName().toLowerCase();
if ( file_name.endsWith( ".wmv" )){
found_wmv = true;
break;
}
}
if ( found_wmv ){
minimum_initial_buffer_secs_for_eta = WMP_MINIMUM_INITIAL_BUFFER_SECS_FOR_ETA;
}else{
minimum_initial_buffer_secs_for_eta = DEFAULT_MINIMUM_INITIAL_BUFFER_SECS_FOR_ETA;
}
TOTorrent torrent = download_manager.getTorrent();
if ( torrent != null ){
content_min_delivery_bps = PlatformTorrentUtils.getContentMinimumSpeedBps( torrent );
platform_content = PlatformTorrentUtils.isContent( torrent, true );
enhanced_files = new EnhancedDownloadManagerFile[files.length];
Map meta_data = PlatformTorrentUtils.getFileMetaData( torrent );
Map files_info = meta_data==null?null:(Map)meta_data.get( "files" );
long offset = 0;
for (int i=0;i<files.length;i++){
DiskManagerFileInfo f = files[i];
Map file_info = files_info==null?null:(Map)files_info.get( "" + i );
enhanced_files[i] = new EnhancedDownloadManagerFile( f, offset, file_info );
offset += f.getLength();
}
int primary_index = PlatformTorrentUtils.getContentPrimaryFileIndex( download_manager.getTorrent());
if ( primary_index >= 0 && primary_index < files.length ){
primary_file = enhanced_files[primary_index];
}else{
primary_file = enhanced_files[0];
}
}else{
enhanced_files = new EnhancedDownloadManagerFile[0];
}
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 ){
time_download_started = SystemTime.getCurrentTime();
current_piece_pickler = manager.getPiecePicker();
if ( progressive_active && current_piece_pickler != null ){
buffer_provider.activate( current_piece_pickler );
boost_provider.activate( current_piece_pickler );
}
resetVars();
}
}
public void
peerManagerRemoved(
PEPeerManager manager )
{
synchronized( EnhancedDownloadManager.this ){
time_download_started = 0;
progressive_active = false;
if ( current_piece_pickler != null ){
buffer_provider.deactivate( current_piece_pickler );
boost_provider.deactivate( current_piece_pickler );
current_piece_pickler = null;
}
resetVars();
}
}
public void
peerAdded(
PEPeer peer )
{
if ( platform_content ){
synchronized( EnhancedDownloadManager.this ){
if ( new_peers == null ){
new_peers = new LinkedList();
}
new_peers.add( peer );
}
}
}
public void
peerRemoved(
PEPeer peer )
{
if ( platform_content ){
synchronized( EnhancedDownloadManager.this ){
if ( new_peers != null ){
new_peers.remove( peer );
if ( new_peers.size() == 0 ){
new_peers = null;
}
}
if ( cache_peers != null ){
cache_peers.remove( peer );
if ( cache_peers.size() == 0 ){
cache_peers = null;
}
}
CachePeer cache_peer = (CachePeer)peer.getData( PEER_CACHE_KEY );
if ( cache_peer == null ){
// we can disconnect before getting peer-id etc
if ( lookup_peers != null ){
for (int i=0;i<lookup_peers.length;i++){
CachePeer cp = lookup_peers[i];
if ( cp.getAddress().getHostAddress().equals( peer.getIp()) &&
cp.getPort() == peer.getPort()){
cache_peer = cp;
}
}
}
}
if ( cache_peer != null &&
cache_peer.getType() == CachePeer.PT_CACHE_LOGIC &&
( disconnected_cache_peers == null || !disconnected_cache_peers.contains( cache_peer ))){
// lost connection very early - sign that the cache doesn't support
// us
if ( !peer.hasReceivedBitField()){
cache_peer.setAutoReconnect( false );
}
}
}
}
}
public void
pieceAdded(
PEPiece piece )
{
}
public void
pieceRemoved(
PEPiece piece )
{
}
});
}
public String
getName()
{
return( download_manager.getDisplayName());
}
public boolean
isPlatform()
{
TOTorrent torrent = download_manager.getTorrent();
if ( torrent != null ){
return( PlatformTorrentUtils.isContent( torrent, true ));
}
return( false );
}
public EnhancedDownloadManagerFile[]
getFiles()
{
return( enhanced_files );
}
public void
setMinimumBufferBytes(
int min )
{
log( "Explicit min buffer set to " + min );
explicit_minimum_buffer_bytes = min;
}
protected void
refreshMetaData()
{
progressive_stats.refreshMetaData();
}
protected long
getTimeRunning()
{
if ( time_download_started == 0 ){
return( 0 );
}
long now = SystemTime.getCurrentTime();
if ( now < time_download_started ){
time_download_started = now;
}
return( now - time_download_started );
}
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 void
updateStats(
int tick_count )
{
updateProgressiveStats( tick_count );
if ( !platform_content ){
return;
}
int state = download_manager.getState();
if ( state != DownloadManager.STATE_SEEDING && state != DownloadManager.STATE_DOWNLOADING ){
return;
}
PEPeerManager pm = download_manager.getPeerManager();
if ( pm == null ){
return;
}
long now = SystemTime.getCurrentTime();
long target_speed = getTargetSpeed();
PEPeerManagerStats stats = pm.getStats();
long download_speed = stats.getDataReceiveRate();
download_speed_average.update( download_speed );
long time_downloading = getTimeRunning();
int secs_since_last_up = pm.getStats().getTimeSinceLastDataSentInSeconds();
// deal with -1 -> infinite
if ( secs_since_last_up == -1 ){
Long seed_time = (Long)pm.getData( PM_SEED_TIME_KEY );
if ( seed_time == null ){
seed_time = new Long( now );
pm.setData( PM_SEED_TIME_KEY, seed_time );
}
secs_since_last_up = (int)(( now - seed_time.longValue()) / 1000);
}
List peers_to_kick = new ArrayList();
synchronized( this ){
if ( new_peers != null ){
Iterator it = new_peers.iterator();
while( it.hasNext()){
PEPeer peer = (PEPeer)it.next();
CachePeer cache_peer = (CachePeer)peer.getData( PEER_CACHE_KEY );
if ( cache_peer == null ){
byte[] peer_id = peer.getId();
if ( peer_id != null ){
try{
cache_peer = CacheDiscovery.categorisePeer(
peer_id,
InetAddress.getByName( peer.getIp()),
peer.getPort());
peer.setData( PEER_CACHE_KEY, cache_peer );
if ( cache_peer.getType() == CachePeer.PT_CACHE_LOGIC ){
if ( state == DownloadManager.STATE_SEEDING ){
if ( now - cache_peer.getCreateTime( now ) >= MIN_SEED_CONNECTION_TIME &&
secs_since_last_up >= IDLE_SEED_DISCONNECT_SECS ){
peers_to_kick.add( peer );
addToDisconnectedCachePeers( cache_peer );
}else{
if ( cache_peers == null ){
cache_peers = new LinkedList();
}
cache_peers.add( peer );
}
}else{
// cache logic rely on timely have messages to control both
// piece allocation and client-speed
peer.setHaveAggregationEnabled( false );
if ( target_speed <= 0 ){
setPeerSpeed( peer, -1, now );
peers_to_kick.add( peer );
addToDisconnectedCachePeers( cache_peer );
}else{
long current_speed = (long)download_speed_average.getAverage();
// if we are already exceeding required speed, block
// the cache peer download
if ( current_speed + TARGET_SPEED_EXCESS_MARGIN > target_speed ){
setPeerSpeed( peer, -1, now );
}
if ( cache_peers == null ){
cache_peers = new LinkedList();
}
cache_peers.add( peer );
}
}
}
}catch( Throwable e ){
Debug.printStackTrace(e);
}
it.remove();
}
}else{
it.remove();
}
}
if ( new_peers.size() == 0 ){
new_peers = null;
}
}
}
for (int i=0;i<peers_to_kick.size();i++){
pm.removePeer((PEPeer)peers_to_kick.get(i), "Cache peer not required" );
}
if ( state == DownloadManager.STATE_DOWNLOADING ){
if ( time_downloading > SPEED_CONTROL_INITIAL_DELAY ){
long current_average = (long)download_speed_average.getAverage();
if ( current_average < target_speed ){
long current_speed = getCurrentSpeed();
// increase cache peer contribution
// due to latencies we need to give speed increases a time to take
// effect to see if the limits can be reached
long difference = target_speed - current_speed;
if ( last_speed_increase > now || now - last_speed_increase > SPEED_INCREASE_GRACE_PERIOD ){
synchronized( this ){
if ( cache_peers != null ){
Iterator it = cache_peers.iterator();
while( it.hasNext() && difference > 0 ){
PEPeer peer = (PEPeer)it.next();
PEPeerStats peer_stats = peer.getStats();
long peer_limit = peer_stats.getDownloadRateLimitBytesPerSecond();
// try simple approach - find first cache peer that is limited
// to less than the target
if ( peer_limit == 0 ){
}else{
if ( peer_limit < target_speed ){
setPeerSpeed( peer, (int)target_speed, now );
last_speed_increase = now;
difference = 0;
}
}
}
}
}
if ( difference > 0 &&
last_peer_inject > now || now - last_peer_inject > PEER_INJECT_GRACE_PERIOD ){
Set connected_peers = new HashSet();
List peers_to_try = new ArrayList();
if ( cache_peers != null ){
Iterator it = cache_peers.iterator();
while( it.hasNext() && difference > 0 ){
PEPeer peer = (PEPeer)it.next();
connected_peers.add( peer.getIp() + ":" + peer.getPort());
}
}
// if we explicitly disconnected peers in the past then reuse them first
if ( disconnected_cache_peers != null ){
while( disconnected_cache_peers.size() > 0 ){
CachePeer cp = (CachePeer)disconnected_cache_peers.remove(0);
if ( !connected_peers.contains( cp.getAddress().getHostAddress() + ":" + cp.getPort())){
// check that this peer isn't already available as a lookup result
if ( lookup_peers != null ){
for (int i=0;i<lookup_peers.length;i++){
CachePeer l_cp = lookup_peers[i];
if ( l_cp.sameAs( cp )){
cp = null;
break;
}
}
}
if ( cp != null ){
peers_to_try.add( cp );
break;
}
}
}
if ( disconnected_cache_peers.size() == 0 ){
disconnected_cache_peers = null;
}
}
if ( peers_to_try.size() == 0 ){
// can't do the job with existing cache peers, try to find some more
if ( lookup_peers == null ||
now < last_lookup_time ||
now - last_lookup_time > CACHE_REQUERY_MIN_PERIOD ){
last_lookup_time = now;
lookup_peers = CacheDiscovery.lookup( download_manager.getTorrent());
}
for (int i=0;i<lookup_peers.length;i++){
CachePeer cp = lookup_peers[i];
if ( cp.getAutoReconnect() && now - cp.getInjectTime(now) > CACHE_RECONNECT_MIN_PERIOD ){
if ( !connected_peers.contains( cp.getAddress().getHostAddress() + ":" + cp.getPort())){
peers_to_try.add( cp );
}
}
}
}
if ( peers_to_try.size() > 0 ){
CachePeer peer = (CachePeer)peers_to_try.get((int)( Math.random() * peers_to_try.size()));
// System.out.println( "Injecting cache peer " + peer.getAddress() + ":" + peer.getPort());
peer.setInjectTime( now );
pm.addPeer( peer.getAddress().getHostAddress(), peer.getPort(), 0, false );
last_peer_inject = now;
}
}
}
}else if ( current_average > target_speed + TARGET_SPEED_EXCESS_MARGIN){
long current_speed = getCurrentSpeed();
// decrease cache peer contribution
long difference = current_speed - ( target_speed + TARGET_SPEED_EXCESS_MARGIN );
synchronized( this ){
if ( cache_peers != null ){
Iterator it = cache_peers.iterator();
while( it.hasNext() && difference > 0 ){
PEPeer peer = (PEPeer)it.next();
PEPeerStats peer_stats = peer.getStats();
long peer_rate = peer_stats.getDataReceiveRate();
long peer_limit = peer_stats.getDownloadRateLimitBytesPerSecond();
if ( peer_limit == -1 ){
// blocked, take into account adjustment in progress
difference -= peer_rate;
}else if ( peer_limit != 0 && peer_rate > peer_limit ){
// adjusting
difference -= peer_rate - peer_limit;
}else{
if ( peer_rate > difference ){
setPeerSpeed( peer, (int)( peer_rate - difference ), now );
difference = 0;
}else{
setPeerSpeed( peer, -1, now );
difference -= peer_rate;
}
}
}
}
}
}
}
}
if ( tick_count % DISCONNECT_CHECK_TICKS == 0 ){
peers_to_kick.clear();
synchronized( this ){
if ( cache_peers != null ){
Iterator it = cache_peers.iterator();
while( it.hasNext()){
PEPeer peer = (PEPeer)it.next();
CachePeer cache_peer = (CachePeer)peer.getData( PEER_CACHE_KEY );
if ( state == DownloadManager.STATE_SEEDING ){
if ( now - cache_peer.getCreateTime( now ) >= MIN_SEED_CONNECTION_TIME &&
secs_since_last_up >= IDLE_SEED_DISCONNECT_SECS ){
peers_to_kick.add( peer );
addToDisconnectedCachePeers( cache_peer );
}
}else{
PEPeerStats peer_stats = peer.getStats();
if ( peer_stats.getDownloadRateLimitBytesPerSecond() == -1 ){
long time = cache_peer.getSpeedChangeTime( now );
if ( now - time > IDLE_PEER_DISCONNECT_PERIOD ){
peers_to_kick.add( peer );
addToDisconnectedCachePeers( cache_peer );
}
}
}
}
}
}
for (int i=0;i<peers_to_kick.size();i++){
pm.removePeer((PEPeer)peers_to_kick.get(i), "Cache peer disconnect-on-idle" );
}
}
}
protected void
addToDisconnectedCachePeers(
CachePeer cache_peer )
{
if ( disconnected_cache_peers == null ){
disconnected_cache_peers = new ArrayList();
}
for (int i=0;i<disconnected_cache_peers.size();i++){
CachePeer p = (CachePeer)disconnected_cache_peers.get(i);
if ( p.sameAs( cache_peer )){
return;
}
}
disconnected_cache_peers.add( cache_peer );
}
protected void
setPeerSpeed(
PEPeer peer,
int speed,
long time )
{
CachePeer cache_peer = (CachePeer)peer.getData( PEER_CACHE_KEY );
cache_peer.setSpeedChangeTime( time );
peer.getStats().setDownloadRateLimitBytesPerSecond( speed );
}
protected long
getCurrentSpeed()
{
// gets instantaneous speed instead of longer term average
PEPeerManager pm = download_manager.getPeerManager();
long result = 0;
if ( pm != null ){
Iterator it = pm.getPeers().iterator();
while( it.hasNext()){
result += ((PEPeer)it.next()).getStats().getDataReceiveRate();
}
}
return( result );
}
public boolean
supportsProgressiveMode()
{
TOTorrent torrent = download_manager.getTorrent();
if ( torrent == null ){
return( false );
}
return( enhancer.isProgressiveAvailable() && PlatformTorrentUtils.isContentProgressive( torrent ));
}
public void
setProgressiveMode(
boolean active )
{
TOTorrent torrent = download_manager.getTorrent();
if ( torrent == null ){
return;
}
synchronized( this ){
if ( progressive_active == active ){
return;
}
if (active && !supportsProgressiveMode()) {
return;
}
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) {
gm.resumeDownloads();
}
};
}
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)
&& PlatformTorrentUtils.getAdId(dmCheck.getTorrent()) == null) {
int state = dmCheck.getState();
if (state == DownloadManager.STATE_DOWNLOADING
|| state == DownloadManager.STATE_QUEUED) {
dmCheck.pause();
}
EnhancedDownloadManager edmCheck = enhancer.getEnhancedDownload(dmCheck);
if (edmCheck != null && edmCheck.getProgressiveMode()) {
edmCheck.setProgressiveMode(false);
}
}
}
if (download_manager.isPaused()) {
download_manager.resume();
}
// 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);
gm.resumeDownloads();
}
progressive_active = active;
if ( current_piece_pickler != null ){
if ( progressive_active ){
buffer_provider.activate( current_piece_pickler );
boost_provider.activate( current_piece_pickler );
progressive_stats.update( 0 );
}else{
buffer_provider.deactivate( current_piece_pickler );
boost_provider.deactivate( current_piece_pickler );
progressive_stats = createProgressiveStats( download_manager, primary_file );
}
}else{
progressive_stats = createProgressiveStats( download_manager, primary_file );
}
}
if ( active && !progressive_informed ){
progressive_informed = true;
// tell tracker we're progressive so it can, if required, schedule more seeds
Download plugin_dl = PluginCoreUtils.wrap( download_manager );
DownloadUtils.addTrackerExtension( plugin_dl, TRACKER_PROG_PREFIX, "y" );
download_manager.requestTrackerAnnounce( true );
}
}
public boolean
getProgressiveMode()
{
return( progressive_active );
}
public long
getProgressivePlayETA()
{
progressiveStats stats = getProgressiveStats();
long eta = stats.getETA();
return( eta );
}
protected progressiveStats
getProgressiveStats()
{
synchronized( this ){
return( progressive_stats.getCopy());
}
}
protected progressiveStats
createProgressiveStats(
DownloadManager dm,
EnhancedDownloadManagerFile file )
{
TOTorrent torrent = download_manager.getTorrent();
if ( torrent != null && PlatformTorrentUtils.useEMP( torrent )){
return( new progressiveStatsInternal( dm, file ));
}else{
return( new progressiveStatsExternal( dm, file ));
}
}
protected void
updateProgressiveStats(
int tick_count )
{
if ( !progressive_active ){
return;
}
synchronized( this ){
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 );
}
}
protected void
setRTA(
boolean active )
{
synchronized( this ){
if ( marked_active && !active ){
marked_active = false;
ConcurrentHasher.getSingleton().removeRealTimeTask();
}
if ( destroyed ){
return;
}
if ( !marked_active && active ){
marked_active = true;
ConcurrentHasher.getSingleton().addRealTimeTask();
}
}
}
protected void
checkPublishing()
{
if ( publish_handling_complete ){
return;
}
if ( PublishUtils.isPublished( download_manager )){
if ( PublishUtils.isPublishComplete( download_manager )){
publish_handling_complete = true;
}else{
TRTrackerScraperResponse scrape = download_manager.getTrackerScrapeResponse();
if ( scrape == null || scrape.getStatus() != TRTrackerScraperResponse.ST_ONLINE ){
return;
}
if ( scrape.getSeeds() >= 2 ){
PublishUtils.setPublishComplete( download_manager );
publish_handling_complete = true;
}else{
PEPeerManager pm = download_manager.getPeerManager();
if ( pm != null ){
long now = SystemTime.getCurrentTime();
long pub_sent = download_manager.getStats().getTotalDataBytesSent();
if ( pub_sent != publish_sent ){
publish_sent = pub_sent;
publish_sent_time = now;
}
if ( publish_sent_time > now ){
publish_sent_time = now;
}
if ( now - publish_sent_time > STALLED_TIMEOUT ){
publish_sent_time = now;
log( "Publish: upload stalled - switching transports" );
// no data uploded recently.
pm.setPreferUDP( !pm.getPreferUDP());
List peers = pm.getPeers();
for (int i=0;i<peers.size();i++){
PEPeer peer = (PEPeer)peers.get(i);
pm.removePeer( peer, "Transport switch" );
}
download_manager.requestTrackerAnnounce( true );
}else if ( pm.getNbPeers() == 0 ){
log( "Publish: no connected peers, forcing announce" );
download_manager.requestTrackerAnnounce( true );
}
}
}
}
}else{
// we've only got to handle the possible small delay here between a download being
// added and the flag being set
if ( SystemTime.getCurrentTime() - time_download_started > 120*1000 ){
publish_handling_complete = true;
}
}
}
public DiskManagerFileInfo
getPrimaryFile()
{
return( primary_file.getFile());
}
public long
getContiguousAvailableBytes(
DiskManagerFileInfo file )
{
return( getContiguousAvailableBytes( file, 0 ));
}
public long
getContiguousAvailableBytes(
DiskManagerFileInfo file,
int file_start_offset )
{
DiskManager dm = download_manager.getDiskManager();
if ( dm == null ){
return( -1 );
}
int piece_size = dm.getPieceLength();
DiskManagerFileInfo[] files = dm.getFiles();
long start_index = file_start_offset;
for (int i=0;i<files.length;i++){
if ( files[i].getIndex() == file.getIndex()){
break;
}
start_index += files[i].getLength();
}
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++){
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 void
setViewerPosition(
DiskManagerFileInfo file_info,
long bytes)
{
int index = file_info.getIndex();
if ( index < enhanced_files.length ){
bytes += enhanced_files[index].getByteOffestInTorrent();
}
progressive_stats.setViewerBytePosition( bytes );
}
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.getDisplayName() + ": " + str;
if ( to_file ){
AEDiagnosticsLogger diag_logger = AEDiagnostics.getLogger("v3.Stream");
diag_logger.log(str);
}
if ( Constants.DIAG_TO_STDOUT ) {
System.out.println(Thread.currentThread().getName() + "|"
+ System.currentTimeMillis() + "] " + str);
}
}
protected class
bufferETAProvider
implements PieceRTAProvider
{
private long[] piece_rtas;
private boolean active;
protected void
activate(
PiecePicker picker )
{
log( "Activating buffer provider" );
synchronized( EnhancedDownloadManager.this ){
active = true;
piece_rtas = new long[ picker.getNumberOfPieces()];
long now = SystemTime.getCurrentTime();
for (int i=0;i<piece_rtas.length;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);
}
picker.addRTAProvider( this );
}
}
protected void
deactivate(
PiecePicker picker )
{
if ( active ){
log( "Deactivating buffer provider" );
}
synchronized( EnhancedDownloadManager.this ){
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 )
{
// be force linear downloading until we have enough to allow the user to
// potentially start playing. If they don't do so immediately then until that
// time we'll be doing normal BT download
DiskManager dm = download_manager.getDiskManager();
if ( dm != null ){
if ( getProgressivePlayETA() <= 0 ){
deactivate( picker );
}
}
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 seconds )
{
}
public String
getUserAgent()
{
return( null );
}
}
protected class
boostETAProvider
implements PieceRTAProvider
{
private long[] piece_rtas;
private long last_recalc;
private boolean active;
protected void
activate(
PiecePicker picker )
{
if ( supportsProgressiveMode()){
log( "Activating boost provider" );
synchronized( EnhancedDownloadManager.this ){
active = true;
picker.addRTAProvider( this );
}
}
}
protected void
deactivate(
PiecePicker picker )
{
if ( active ){
log( "Deactivating boost provider" );
}
synchronized( EnhancedDownloadManager.this ){
picker.removeRTAProvider( this );
piece_rtas = null;
active = false;
}
}
public long[]
updateRTAs(
PiecePicker picker )
{
long now = SystemTime.getCurrentTime();
if ( now < last_recalc || now - last_recalc > 2500 ){
last_recalc = now;
DiskManager disk_manager = download_manager.getDiskManager();
// if it'll take less time to download than watch then the channel-based rta logic
// will do the job.
progressiveStats stats = getProgressiveStats();
long max_bps = stats.getStreamBytesPerSecondMax();
if ( disk_manager == null ||
!stats.isProviderActive() ||
stats.getETA() < -MINIMUM_INITIAL_BUFFER_SECS ||
max_bps == 0 ){
if ( piece_rtas != null ){
log( "Suspending boost provider" );
}
piece_rtas = null;
}else{
if ( piece_rtas == null ){
log( "Resuming boost provider" );
}
piece_rtas = new long[disk_manager.getNbPieces()];
// need to force piece order - set RTAs for all outstanding pieces
long piece_size = disk_manager.getPieceLength();
int start_piece = (int)( stats.getBytePosition() / piece_size );
long bytes_offset = 0;
for ( int i=start_piece;i<piece_rtas.length;i++ ){
piece_rtas[i] = now + ( 1000* ( bytes_offset / max_bps ));
bytes_offset += piece_size;
}
}
}
return( piece_rtas );
}
public long
getCurrentPosition()
{
return( 0 );
}
public long
getBlockingPosition()
{
return( 0 );
}
public long
getStartTime()
{
return( 0 );
}
public long
getStartPosition()
{
return( 0 );
}
public void
setBufferMillis(
long seconds )
{
}
public String
getUserAgent()
{
return( null );
}
}
protected abstract class
progressiveStats
implements Cloneable
{
protected abstract boolean
isProviderActive();
protected abstract long
getBytePosition();
protected abstract long
getStreamBytesPerSecondMax();
protected abstract long
getStreamBytesPerSecondMin();
protected abstract long
getETA();
protected abstract void
setViewerBytePosition(
long bytes );
protected abstract long
getViewerBytePosition();
protected abstract void
update(
int tick_count );
protected abstract void
refreshMetaData();
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 abstract class
progressiveStatsCommon
extends progressiveStats
{
private PieceRTAProvider current_provider;
private String current_user_agent;
protected long total_file_length = download_manager.getSize();
private Average 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;
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 )
{
calculateSpeeds( dm, primary_file );
setRTA( false );
log( download_manager,
"content_stream_bps=" + getStreamBytesPerSecondMin() +
",primary=" + (primary_file==null?"null":primary_file.getString()),
true );
}
protected void
refreshMetaData()
{
calculateSpeeds( download_manager, primary_file );
}
protected abstract void
calculateSpeeds(
DownloadManager dm,
EnhancedDownloadManagerFile primary_file );
protected void
updateCurrentProvider(
PieceRTAProvider provider )
{
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 = 0;
provider_byte_position = 0;
provider_blocking_byte_position = 0;
provider_last_byte_position = -1;
}else{
provider_initial_position = 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 = 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
getProviderBytePosition()
{
return( provider_byte_position );
}
protected long
getProviderLifeSecs()
{
return( provider_life_secs );
}
protected void
update(
int tick_count )
{
long download_rate = download_manager.getStats().getDataReceiveRate();
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 );
// System.out.println( "prov_ini=" + provider_initial_position + ", life=" + provider_life_secs + ", pos=" + provider_byte_position );
updateViewerPosition();
if ( best_provider != null ){
// only report buffer if we have a bit of slack
long buffer_secs = getViewerBufferSeconds();
if ( buffer_secs < 10 ){
// no point in having a very small buffer as we end up with
// too much discard. Given we're doing a long-term stream here the
// aggressiveness applied when rta gets close to "now" isn't needed
buffer_secs = 10;
}
best_provider.setBufferMillis( 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();
for (int i=(int)(provider_byte_position/piece_size);i<pieces.length;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 abstract void
updateViewerPosition();
protected abstract long
getInitialBufferBytes(
long dl_rate );
protected long
getETA()
{
DiskManager dm = download_manager.getDiskManager();
if ( dm == null ){
return( Long.MAX_VALUE );
}
if ( dm.getRemainingExcludingDND() == 0 ){
return( 0 );
}
long download_rate = (long)download_rate_average.getAverage();
if ( download_rate <= 0 ){
return( Long.MAX_VALUE );
}
long min_dl = getInitialBufferBytes( download_rate );
long initial_downloaded = getInitialBytesDownloaded( min_dl );
long rem_dl = min_dl - initial_downloaded; // ok as initial dl is forced in order byte buffer-rta
long rem_secs = rem_dl / download_rate;
long secs_to_download = getSecondsToDownload();
// increase time to download a bit so we don't start streaming too soon
// we'll always lose time due to speed variations, discards, hashfails...
secs_to_download = secs_to_download + (secs_to_download/10);
long eta = secs_to_download - getSecondsToWatch();
if ( rem_secs > eta ){
eta = rem_secs;
}
return( eta );
}
public long
getInitialBytesDownloaded(
long stop_counting_after )
{
DiskManager dm = download_manager.getDiskManager();
if ( dm == null ){
return( 0 );
}
long initial_downloaded = 0;
DiskManagerPiece[] pieces = dm.getPieces();
for (int i=0;i<pieces.length;i++){
DiskManagerPiece piece = pieces[i];
if ( piece.isDone()){
initial_downloaded += piece.getLength();
}else{
boolean[] blocks = piece.getWritten();
if ( blocks == null ){
break;
}else{
for (int j=0;j<blocks.length;j++){
if ( blocks[j] ){
initial_downloaded += piece.getBlockSize( j );
}else{
break;
}
}
break;
}
}
if ( initial_downloaded >= stop_counting_after ){
break;
}
}
return( initial_downloaded );
}
protected long
getSecondsToDownload()
{
long download_rate = (long)download_rate_average.getAverage();
if ( download_rate == 0 ){
return( Long.MAX_VALUE );
}
return( weighted_bytes_to_download / download_rate );
}
protected long
getSecondsToWatch()
{
return((total_file_length - getViewerBytePosition()) / getStreamBytesPerSecondMin());
}
protected long
getBytePosition()
{
return( getViewerBytePosition());
}
protected long
getViewerBufferSeconds()
{
return((provider_byte_position - getViewerBytePosition() ) / getStreamBytesPerSecondMax() );
}
protected String
getString()
{
long dl_rate = (long)download_rate_average.getAverage();
long init_bytes = getInitialBufferBytes(dl_rate);
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()) +
", init_done=" + getInitialBytesDownloaded(init_bytes) + ", init_buff=" + init_bytes +
", viewer: byte=" + formatBytes( getViewerBytePosition()) + " secs=" + ( getViewerBytePosition()/getStreamBytesPerSecondMin() ) +
", prov: byte=" + formatBytes( provider_byte_position ) + " secs=" + ( provider_byte_position/getStreamBytesPerSecondMin()) + " speed=" + formatSpeed((long)provider_speed_average.getAverage()) +
" block= " + formatBytes( provider_blocking_byte_position ) + " buffer=" + formatBytes( provider_byte_position - getViewerBytePosition() ) + "/" + getViewerBufferSeconds());
}
}
protected class
progressiveStatsExternal
extends progressiveStatsCommon
{
private long content_stream_bps_min;
private long content_stream_bps_max;
private long viewer_byte_position;
protected
progressiveStatsExternal(
DownloadManager download_manager,
EnhancedDownloadManagerFile primary_file )
{
super( download_manager, primary_file );
}
protected void
calculateSpeeds(
DownloadManager download_manager,
EnhancedDownloadManagerFile primary_file )
{
TOTorrent torrent = download_manager.getTorrent();
if ( torrent == null ){
return;
}
content_stream_bps_min = 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_max = content_stream_bps_min + ( content_stream_bps_min / 5 );
}
protected long
getStreamBytesPerSecondMax()
{
return( content_stream_bps_max );
}
protected long
getStreamBytesPerSecondMin()
{
return( content_stream_bps_min );
}
public long
getInitialBufferBytes(
long download_rate )
{
long min_dl = minimum_initial_buffer_secs_for_eta * getStreamBytesPerSecondMax();
// factor in any explicit minimum buffer bytes
min_dl = Math.max( min_dl, explicit_minimum_buffer_bytes );
// see if we have any stream-specific advice
long advice = primary_file.getInitialBufferBytes( download_rate );
min_dl = Math.max( advice, min_dl );
return( min_dl );
}
protected void
updateViewerPosition()
{
viewer_byte_position = getInitialProviderPosition() + (getStreamBytesPerSecondMax() * getProviderLifeSecs());
if ( viewer_byte_position > total_file_length ){
viewer_byte_position = total_file_length;
}
if ( viewer_byte_position > getProviderBytePosition()){
viewer_byte_position = getProviderBytePosition();
}
}
protected void
setViewerBytePosition(
long bytes)
{
// nothing for external viewer case as this doesn't get called
}
protected long
getViewerBytePosition()
{
return( viewer_byte_position );
}
}
protected class
progressiveStatsInternal
extends progressiveStatsCommon
{
private long content_stream_bps_min;
private long content_stream_bps_max;
private long viewer_byte_position;
private long viewer_byte_position_set_time;
private long last_warning;
protected
progressiveStatsInternal(
DownloadManager dm,
EnhancedDownloadManagerFile primary_file )
{
super( dm, primary_file );
}
protected void
calculateSpeeds(
DownloadManager download_manager,
EnhancedDownloadManagerFile primary_file )
{
TOTorrent torrent = download_manager.getTorrent();
if ( torrent == null ){
return;
}
content_stream_bps_min = 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 += internal_content_stream_bps_increase_absolute;
content_stream_bps_max = content_stream_bps_min + ( content_stream_bps_min / internal_content_stream_bps_increase_ratio );
}
protected long
getStreamBytesPerSecondMax()
{
return( content_stream_bps_max );
}
protected long
getStreamBytesPerSecondMin()
{
return( content_stream_bps_min );
}
public long
getInitialBufferBytes(
long download_rate )
{
long min_dl = explicit_minimum_buffer_bytes;
// see if we have any stream-specific advice
long advice = primary_file.getInitialBufferBytes( download_rate );
if ( advice == 0 ){
// no advice, fall back to computed min
advice = minimum_initial_buffer_secs_for_eta * getStreamBytesPerSecondMax();
}else{
// currently the player will auto-pause if the buffer falls below the
// explicit minimum so we need to add the explicit to the advice to
// get a value that will prevent a stall
advice += explicit_minimum_buffer_bytes;
}
min_dl = Math.max( advice, min_dl );
return( min_dl );
}
protected void
updateViewerPosition()
{
}
protected void
setViewerBytePosition(
long bytes)
{
viewer_byte_position_set_time = SystemTime.getCurrentTime();
viewer_byte_position = bytes;
}
protected long
getViewerBytePosition()
{
long now = SystemTime.getCurrentTime();
if ( now < viewer_byte_position_set_time ){
viewer_byte_position_set_time = now;
}else if ( now - viewer_byte_position_set_time > 10000 ){
if ( viewer_byte_position != 0 ){
if ( now < last_warning || now - last_warning >= 1000 ){
last_warning = now;
log( "No recent viewer position update (current=" + viewer_byte_position + ")" );
}
}
}
return( viewer_byte_position );
}
}
}
|