UPnPCast
đ Modern Android DLNA/UPnP casting library - Clean replacement for discontinued Cling project
Install / Use
/learn @yinnho/UPnPCastREADME
UPnPCast
đ A modern, clean Android DLNA/UPnP casting library designed as a drop-in replacement for the discontinued Cling project.
ä¸ćć楣 | English Documentation
⨠What's New in v1.1.2
đŻ Enhanced Volume Control & Millisecond-Level Progress Management
- đ Complete Volume Control System: Added
getVolume(),setVolume(), andsetMute()APIs for comprehensive volume management - ⥠Millisecond-Level Progress Control: Intelligent caching with 3-second cache duration and real-time interpolation
- đ Smart Cache Management: Volume cache (5-second validity) and progress cache with async refresh mechanisms
- đŻ Real-time Progress Tracking:
getProgressRealtime()for force refresh without cache dependency - đ Manual Cache Control: Exposed cache refresh and clearing methods for advanced control
- đ Enhanced State Management: Improved
getState()with integrated volume and mute status
Features
- đ Device Discovery: Automatic DLNA/UPnP device discovery with SSDP protocol
- đş Media Casting: Cast photos, videos, and audio to DLNA-compatible devices
- đŽ Playback Controls: Play, pause, stop, seek, volume control, and mute functionality
- đ Advanced Volume Control: Get/set volume, mute control with intelligent caching
- ⥠Millisecond Precision: Real-time progress tracking with smart interpolation
- đą Easy Integration: Simple API with intuitive callback mechanisms
- đ Modern Architecture: Built with Kotlin, Coroutines, and Android best practices
- đ§ Highly Compatible: Tested with major TV brands (Xiaomi, Samsung, LG, Sony)
- ⥠Lightweight: Minimal dependencies, optimized performance
Quick Start
Installation
Option 1: Maven Central (Recommended - Official Release!)
Add to your app's build.gradle:
dependencies {
implementation 'com.yinnho.upnpcast:upnpcast:1.1.2'
}
Option 2: JitPack (Alternative)
Add to your root build.gradle:
allprojects {
repositories {
google()
mavenCentral()
maven { url 'https://jitpack.io' }
}
}
Add dependency:
dependencies {
implementation 'com.github.yinnho:UPnPCast:1.1.2'
}
Basic Usage
import com.yinnho.upnpcast.DLNACast
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Initialize
DLNACast.init(this)
// Use coroutines for all operations
lifecycleScope.launch {
searchDevices()
performSmartCast()
}
}
private suspend fun searchDevices() {
try {
// Device discovery with timeout
val devices = DLNACast.search(timeout = 5000)
Log.d("DLNA", "Found ${devices.size} devices")
// Display devices
devices.forEach { device ->
val icon = if (device.isTV) "đş" else "đą"
Log.d("DLNA", "$icon ${device.name} (${device.address})")
}
} catch (e: Exception) {
Log.e("DLNA", "Search failed: ${e.message}")
}
}
private suspend fun performSmartCast() {
try {
// Smart cast - automatically finds and selects best device
val success = DLNACast.cast("http://your-video.mp4", "Video Title")
if (success) {
Log.d("DLNA", "Smart casting started!")
controlPlayback()
} else {
Log.e("DLNA", "Cast failed")
}
} catch (e: Exception) {
Log.e("DLNA", "Cast error: ${e.message}")
}
}
private suspend fun controlPlayback() {
try {
// Control playback
val pauseSuccess = DLNACast.control(DLNACast.MediaAction.PAUSE)
Log.d("DLNA", "Paused: $pauseSuccess")
// Get current state
val state = DLNACast.getState()
Log.d("DLNA", "Connected: ${state.isConnected}, Playing: ${state.isPlaying}")
// Seek to 30 seconds
val seekSuccess = DLNACast.seek(30000)
Log.d("DLNA", "Seeked to 30 seconds: $seekSuccess")
} catch (e: Exception) {
Log.e("DLNA", "Control error: ${e.message}")
}
}
override fun onDestroy() {
super.onDestroy()
DLNACast.cleanup()
}
}
API Reference
đ Core Methods (All Suspend Functions)
// Initialize the library (call once in onCreate)
DLNACast.init(context: Context)
// Search for devices (returns list of discovered devices)
suspend fun DLNACast.search(timeout: Long = 5000): List<Device>
// Smart cast - automatically selects best available device
suspend fun DLNACast.cast(url: String, title: String? = null): Boolean
// Cast to specific device
suspend fun DLNACast.castToDevice(device: Device, url: String, title: String): Boolean
// Cast local video files
suspend fun DLNACast.castLocalFile(device: Device, video: LocalVideo): Boolean
// Scan for local video files
suspend fun DLNACast.scanLocalVideos(): List<LocalVideo>
// Media control operations
suspend fun DLNACast.control(action: MediaAction): Boolean
// Seek to specific position (in milliseconds)
suspend fun DLNACast.seek(positionMs: Long): Boolean
đ State Management
// Get current casting state (synchronous)
fun DLNACast.getState(): State
// Get playback progress (synchronous)
fun DLNACast.getProgress(): Progress
// Get volume information (synchronous)
fun DLNACast.getVolume(): Volume
// Clean up resources (call in onDestroy)
fun DLNACast.cleanup()
đ Data Types
// Device information
data class Device(
val id: String, // Unique device identifier
val name: String, // Display name (e.g., "Living Room TV")
val address: String, // IP address
val isTV: Boolean // Whether this is a TV device
)
// Local video file information
data class LocalVideo(
val path: String, // Full file path
val name: String, // Display name
val size: Long, // File size in bytes
val duration: Long // Duration in milliseconds
)
// Media control actions
enum class MediaAction {
PLAY, PAUSE, STOP
}
// Playback states
enum class PlaybackState {
IDLE, // Not connected or no media
PLAYING, // Currently playing
PAUSED, // Playback paused
STOPPED, // Playback stopped
BUFFERING, // Loading/buffering
ERROR // Error state
}
// Current casting state
data class State(
val isConnected: Boolean, // Connected to a device
val currentDevice: Device?, // Current target device
val playbackState: PlaybackState, // Current playback state
val isPlaying: Boolean, // Whether media is playing
val isPaused: Boolean, // Whether media is paused
val volume: Int, // Current volume (0-100)
val isMuted: Boolean // Whether audio is muted
)
// Playback progress information
data class Progress(
val currentMs: Long, // Current position in milliseconds
val totalMs: Long, // Total duration in milliseconds
val percentage: Float // Progress as percentage (0.0-1.0)
)
// Volume information
data class Volume(
val level: Int, // Volume level (0-100)
val isMuted: Boolean // Mute status
)
đĽ Advanced Usage Examples
Cast to Specific Device
lifecycleScope.launch {
try {
// First, search for devices
val devices = DLNACast.search(timeout = 5000)
// Find your preferred device
val targetDevice = devices.firstOrNull { it.name.contains("Living Room") }
if (targetDevice != null) {
// Cast to specific device
val success = DLNACast.castToDevice(
device = targetDevice,
url = "http://your-video.mp4",
title = "My Movie"
)
if (success) {
Log.d("DLNA", "Successfully cast to ${targetDevice.name}")
}
}
} catch (e: Exception) {
Log.e("DLNA", "Cast failed: ${e.message}")
}
}
Local File Casting
lifecycleScope.launch {
try {
// Scan for local video files
val localVideos = DLNACast.scanLocalVideos()
// Find a video to cast
val videoToPlay = localVideos.firstOrNull { it.name.contains("movie") }
if (videoToPlay != null) {
// Get available devices
val devices = DLNACast.search()
val device = devices.firstOrNull()
if (device != null) {
// Cast local file
val success = DLNACast.castLocalFile(device, videoToPlay)
Log.d("DLNA", "Local cast success: $s
Related Skills
node-connect
334.1kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
82.1kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
334.1kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
82.1kCommit, push, and open a PR
