Skip to main content

Download System

Torream features a sophisticated multi-protocol download system that supports HTTP, HLS, and torrent downloads with advanced features like resume support, parallel downloading, and background execution.

Architecture Overview

┌─────────────────────────────────────────┐
│ UI Layer │
│ (DownloadFragment, ViewModel) │
└──────────────┬──────────────────────────┘


┌─────────────────────────────────────────┐
│ DownloadRepository │
│ (Coordinates download operations) │
└──────────────┬──────────────────────────┘


┌─────────────────────────────────────────┐
│ DownloadCoordinator │
│ (Routes to appropriate downloader) │
└───┬──────────┬──────────┬───────────────┘
│ │ │
↓ ↓ ↓
┌────────┐ ┌────────┐ ┌──────────┐
│ HTTP │ │ HLS │ │ Torrent │
│Download│ │Download│ │ Download │
└────────┘ └────────┘ └──────────┘
│ │ │
└──────────┴──────────┘


┌─────────────────────────────────────────┐
│ Storage Layer │
│ (File System + Download Database) │
└─────────────────────────────────────────┘

Download Types

1. HTTP Downloads

Location: download/http/HTTPDownloader.kt

Features

  • Direct HTTP/HTTPS downloads
  • Resume support via Range headers
  • Parallel chunk downloading
  • Progress tracking
  • Automatic retry on failure
  • Speed limiting (optional)

Implementation

class HTTPDownloader(
private val httpClient: OkHttpClient
) {
suspend fun download(
url: String,
destination: File,
onProgress: (Long, Long) -> Unit
): Result<File> {
// Check if file exists (resume support)
val existingSize = if (destination.exists()) {
destination.length()
} else 0L

val request = Request.Builder()
.url(url)
.apply {
if (existingSize > 0) {
addHeader("Range", "bytes=$existingSize-")
}
}
.build()

// Download with progress tracking
// ...
}
}

Use Cases

  • Direct video file downloads
  • Subtitle file downloads
  • Thumbnail downloads
  • APK updates

2. HLS Downloads

Location: download/hls/HLSDownloader.kt

Features

  • M3U8 manifest parsing
  • Segment downloading (TS files)
  • Parallel segment downloads
  • Segment merging
  • AES-128 decryption support
  • Quality selection

Implementation Flow

1. Parse M3U8 manifest
├─> Extract segment URLs
├─> Detect encryption (if any)
└─> Get total segments

2. Download segments in parallel
├─> Download worker pool
├─> Track progress per segment
└─> Handle encryption keys

3. Merge segments
├─> Concatenate TS files
├─> Generate final MP4/MKV
└─> Cleanup temporary files

4. Verify integrity
└─> Check file size and playability

Code Example

class HLSDownloader {
suspend fun downloadHLS(
manifestUrl: String,
outputFile: File
): Result<File> {
// 1. Parse manifest
val manifest = parseM3U8(manifestUrl)
val segments = manifest.segments

// 2. Download segments in parallel
val tempDir = createTempDir()
segments.mapIndexed { index, segment ->
async {
downloadSegment(segment, tempDir, index)
}
}.awaitAll()

// 3. Merge segments
mergeSegments(tempDir, outputFile)

// 4. Cleanup
tempDir.deleteRecursively()

return Result.success(outputFile)
}
}

Use Cases

  • Streaming video downloads
  • Live stream recording
  • Adaptive bitrate content

3. Torrent Downloads

Location: download/torrent/TorrentDownloader.kt

Features

  • LibTorrent4j integration
  • Magnet link support
  • Torrent file support
  • Sequential downloading (for streaming)
  • DHT (Distributed Hash Table)
  • Peer exchange (PEX)
  • Torrent streaming via NanoHTTPD
  • Resume/pause support
  • Speed limits

Architecture

class TorrentDownloader(
private val sessionManager: SessionManager
) {
fun startDownload(
magnetOrFile: String,
saveDir: File,
sequential: Boolean = false
): TorrentHandle {
val params = TorrentParams().apply {
savePath = saveDir.absolutePath

if (sequential) {
// Download pieces in order for streaming
flags = flags or TorrentFlags.SEQUENTIAL_DOWNLOAD
}
}

val handle = when {
magnetOrFile.startsWith("magnet:") -> {
sessionManager.download(magnetOrFile, params)
}
else -> {
val torrentInfo = TorrentInfo(File(magnetOrFile))
sessionManager.download(torrentInfo, params)
}
}

return handle
}
}

Torrent Streaming

Location: download/TorrentStreamingService.kt

Enables playback before download completion:

class TorrentStreamingService : Service() {
private val httpServer = NanoHTTPD(8080)

fun streamTorrent(handle: TorrentHandle): String {
// Enable sequential download
handle.setFlags(TorrentFlags.SEQUENTIAL_DOWNLOAD)

// Prioritize first and last pieces
prioritizeStreamingPieces(handle)

// Serve via HTTP
val streamUrl = "http://127.0.0.1:8080/stream"

httpServer.start()
return streamUrl
}
}

Use Cases

  • Large video file downloads
  • Distributed content
  • Streaming before complete download
  • P2P content sharing

Download Management

DownloadCoordinator

Purpose: Route downloads to appropriate handlers

Location: download/DownloadCoordinator.kt

class DownloadCoordinator @Inject constructor(
private val httpDownloader: HTTPDownloader,
private val hlsDownloader: HLSDownloader,
private val torrentDownloader: TorrentDownloader,
private val downloadDao: DownloadDao
) {
suspend fun startDownload(
url: String,
title: String
): String {
val type = detectDownloadType(url)
val taskId = UUID.randomUUID().toString()

// Create download task
val task = DownloadTask(
id = taskId,
url = url,
title = title,
type = type,
status = DownloadStatus.Queued
)

downloadDao.insert(task)

// Route to appropriate downloader
when (type) {
DownloadType.HTTP -> enqueueHttpDownload(task)
DownloadType.HLS -> enqueueHlsDownload(task)
DownloadType.TORRENT -> enqueueTorrentDownload(task)
}

return taskId
}

private fun detectDownloadType(url: String): DownloadType {
return when {
url.endsWith(".m3u8") -> DownloadType.HLS
url.startsWith("magnet:") -> DownloadType.TORRENT
url.endsWith(".torrent") -> DownloadType.TORRENT
else -> DownloadType.HTTP
}
}
}

DownloadRepository

Purpose: Single source of truth for downloads

Location: download/DownloadRepository.kt

interface DownloadRepository {
// Start/Control
suspend fun startDownload(url: String, title: String): String
suspend fun pauseDownload(taskId: String)
suspend fun resumeDownload(taskId: String)
suspend fun cancelDownload(taskId: String)
suspend fun deleteDownload(taskId: String)

// Observe
fun observeDownload(taskId: String): Flow<DownloadState>
fun observeAllDownloads(): Flow<List<DownloadTask>>
fun getActiveDownloads(): Flow<List<DownloadTask>>

// Query
suspend fun getDownloadById(taskId: String): DownloadTask?
suspend fun getDownloadsByStatus(status: DownloadStatus): List<DownloadTask>
}

Download State

Location: download/DownloadState.kt

sealed class DownloadStatus {
object Queued : DownloadStatus()

data class Downloading(
val bytesDownloaded: Long,
val totalBytes: Long,
val speed: Long // bytes per second
) : DownloadStatus()

data class Paused(
val bytesDownloaded: Long,
val totalBytes: Long
) : DownloadStatus()

object Completed : DownloadStatus()

data class Failed(
val error: String,
val canRetry: Boolean = true
) : DownloadStatus()
}

data class DownloadTask(
val id: String,
val url: String,
val title: String,
val type: DownloadType,
val status: DownloadStatus,
val createdAt: Long,
val completedAt: Long? = null,
val filePath: String? = null
)

Background Execution

DownloadService

Purpose: Foreground service for active downloads

Location: download/DownloadService.kt

Features:

  • Persistent notification with progress
  • Wake lock for uninterrupted downloads
  • Network state monitoring
  • Battery optimization handling
class DownloadService : Service() {
private val notification: NotificationCompat.Builder
private var wakeLock: PowerManager.WakeLock? = null

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// Acquire wake lock
acquireWakeLock()

// Start foreground with notification
startForeground(NOTIFICATION_ID, buildNotification())

// Start download
val taskId = intent?.getStringExtra("taskId")
taskId?.let { executeDownload(it) }

return START_STICKY
}

private fun updateProgress(progress: Int, speed: Long) {
val notification = buildNotification()
.setProgress(100, progress, false)
.setContentText("${progress}% • ${formatSpeed(speed)}")

notificationManager.notify(NOTIFICATION_ID, notification.build())
}
}

WorkManager Integration

Location: download/worker/DownloadWorker.kt

Features:

  • Constraint-based execution
  • Automatic retry with backoff
  • Long-running work support
@HiltWorker
class DownloadWorker @AssistedInject constructor(
@Assisted context: Context,
@Assisted params: WorkerParameters,
private val downloadRepository: DownloadRepository
) : CoroutineWorker(context, params) {

override suspend fun doWork(): Result {
val taskId = inputData.getString("taskId") ?: return Result.failure()

return try {
// Set foreground for long-running work
setForeground(createForegroundInfo())

// Execute download
downloadRepository.executeDownload(taskId)

Result.success()
} catch (e: Exception) {
if (runAttemptCount < 3) {
Result.retry() // Automatic retry
} else {
Result.failure()
}
}
}

private fun createForegroundInfo(): ForegroundInfo {
val notification = createNotification()
return ForegroundInfo(NOTIFICATION_ID, notification)
}
}

Enqueue Download Worker:

fun enqueueDownload(taskId: String) {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()

val downloadWork = OneTimeWorkRequestBuilder<DownloadWorker>()
.setConstraints(constraints)
.setInputData(workDataOf("taskId" to taskId))
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
WorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS
)
.build()

WorkManager.getInstance(context)
.enqueueUniqueWork(
"download_$taskId",
ExistingWorkPolicy.KEEP,
downloadWork
)
}

Advanced Features

1. Download Recovery

Location: download/DownloadRecoveryManager.kt

Purpose: Recover interrupted downloads on app restart

class DownloadRecoveryManager @Inject constructor(
private val downloadDao: DownloadDao,
private val downloadCoordinator: DownloadCoordinator
) {
suspend fun recoverInterruptedDownloads() {
val interrupted = downloadDao.getDownloadsByStatus(
DownloadStatus.Downloading
)

interrupted.forEach { task ->
// Resume download
downloadCoordinator.resumeDownload(task.id)
}
}
}

2. Parallel Downloads

Support for multiple concurrent downloads:

class DownloadScheduler {
private val maxConcurrentDownloads = 3
private val activeDownloads = mutableSetOf<String>()

fun canStartDownload(): Boolean {
return activeDownloads.size < maxConcurrentDownloads
}

suspend fun scheduleDownload(taskId: String) {
if (canStartDownload()) {
activeDownloads.add(taskId)
startDownload(taskId)
} else {
queueDownload(taskId)
}
}
}

3. Speed Limiting

User-controlled download speed:

class SpeedLimiter(private val maxBytesPerSecond: Long) {
private var lastCheckTime = System.currentTimeMillis()
private var bytesThisSecond = 0L

suspend fun acquire(bytes: Long) {
val now = System.currentTimeMillis()

if (now - lastCheckTime >= 1000) {
// Reset counter
lastCheckTime = now
bytesThisSecond = 0
}

bytesThisSecond += bytes

if (bytesThisSecond > maxBytesPerSecond) {
// Delay to limit speed
val delayMs = 1000 - (now - lastCheckTime)
delay(delayMs)
}
}
}

4. Network Monitoring

Auto-pause on network loss:

class NetworkMonitor @Inject constructor(
private val connectivityManager: ConnectivityManager
) {
fun observeNetworkState(): Flow<NetworkState> = callbackFlow {
val callback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
trySend(NetworkState.Available)
}

override fun onLost(network: Network) {
trySend(NetworkState.Lost)
}
}

connectivityManager.registerDefaultNetworkCallback(callback)

awaitClose {
connectivityManager.unregisterNetworkCallback(callback)
}
}
}

UI Integration

DownloadViewModel

Location: ui/download/DownloadViewModel.kt

@HiltViewModel
class DownloadViewModel @Inject constructor(
private val downloadRepository: DownloadRepository
) : ViewModel() {

val downloads = downloadRepository
.observeAllDownloads()
.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5000),
emptyList()
)

fun startDownload(url: String, title: String) {
viewModelScope.launch {
downloadRepository.startDownload(url, title)
}
}

fun pauseDownload(taskId: String) {
viewModelScope.launch {
downloadRepository.pauseDownload(taskId)
}
}

fun resumeDownload(taskId: String) {
viewModelScope.launch {
downloadRepository.resumeDownload(taskId)
}
}

fun cancelDownload(taskId: String) {
viewModelScope.launch {
downloadRepository.cancelDownload(taskId)
}
}
}

Summary

Torream's download system provides:

Multi-Protocol Support: HTTP, HLS, Torrent ✅ Resume Capability: Pick up where you left off ✅ Background Execution: Download while using other apps ✅ Parallel Downloads: Multiple downloads at once ✅ Torrent Streaming: Watch before download completes ✅ Network Awareness: Auto-pause on connection loss ✅ Progress Tracking: Real-time download status ✅ Reliability: Automatic retry and recovery ✅ Efficiency: Optimized for battery and data usage

The system is designed to be:

  • Robust: Handles network failures gracefully
  • Efficient: Minimal battery and data usage
  • User-friendly: Clear progress and controls
  • Extensible: Easy to add new download protocols