A Flutter plugin for DLNA/UPnP device discovery and media casting, powered by Rust.
- 🔍 Device Discovery - Scan for DLNA/UPnP devices on your local network
- 📺 Media Casting - Cast video URLs to smart TVs and projectors
▶️ Playback Control - Play, pause, stop, and seek- 🔊 Volume Control - Get/set volume and mute
- 📊 Status Monitoring - Get playback position and transport state
- 🌐 Wake on LAN - Remote wake devices by MAC address
- Android
- iOS
- macOS
- Linux
- Windows
Add to your pubspec.yaml:
dependencies:
rusty_dlna: ^0.0.1import 'package:rusty_dlna/api/cast.dart';
import 'package:rusty_dlna/frb_generated.dart';
void main() async {
await RustLib.init();
runApp(MyApp());
}Add network permissions to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>Add network entitlements to macos/Runner/DebugProfile.entitlements and macos/Runner/Release.entitlements:
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>Add to ios/Runner/Info.plist:
<key>NSLocalNetworkUsageDescription</key>
<string>This app needs local network access to discover DLNA devices</string>
<key>NSBonjourServices</key>
<array>
<string>_ssdp._udp</string>
</array>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Location permission is required to access WiFi information</string>Important for iOS 14+: The local network permission dialog will appear automatically when you first attempt to scan for devices. To ensure the permission is properly requested, you can use network_info_plus package to trigger the permission dialog before scanning:
import 'package:network_info_plus/network_info_plus.dart';
// Trigger permission dialog by accessing WiFi info
final info = NetworkInfo();
await info.getWifiName(); // This triggers the local network permission dialog
// Now you can scan for devices
final devices = await scanProjectors(timeoutSecs: BigInt.from(5));final devices = await scanProjectors(timeoutSecs: BigInt.from(5));
for (final device in devices) {
print('Found: ${device.friendlyName} at ${device.ip}');
}final device = devices.first;
await device.castVideo(videoUrl: 'http://example.com/video.mp4');await device.play();
await device.pause();
await device.stop();
await device.seek(targetTime: '00:05:00'); // HH:MM:SS formatawait device.setVolume(volume: 50);
final currentVolume = await device.getVolume();
await device.setMute(mute: true);// Get position as formatted strings (HH:MM:SS)
final (current, total) = await device.getPositionInfo();
print('Progress: $current / $total');
// Get position in seconds
final (currentSec, totalSec) = await device.getPositionInfoSec();
// Get transport state
final state = await device.getTransportInfo();
// TransportState: Playing, Paused, Stopped, Transitioning, NoMedia, Unknownawait wakeOnLan(macAddress: 'AA:BB:CC:11:22:33');| Property | Type | Description |
|---|---|---|
friendlyName |
String |
Device display name |
ip |
String |
Device IP address |
locationXmlUrl |
String |
UPnP description URL |
avTransportUrl |
String? |
AVTransport control URL |
renderingControlUrl |
String? |
RenderingControl URL |
| Method | Description |
|---|---|
castVideo(videoUrl) |
Cast video and start playback |
play() |
Resume playback |
pause() |
Pause playback |
stop() |
Stop playback |
seek(targetTime) |
Seek to position (HH:MM:SS) |
setVolume(volume) |
Set volume (0-100) |
getVolume() |
Get current volume |
setMute(mute) |
Set mute state |
getPositionInfo() |
Get position as strings |
getPositionInfoSec() |
Get position in seconds |
getTransportInfo() |
Get playback state |
| Function | Description |
|---|---|
scanProjectors(timeoutSecs) |
Discover DLNA devices |
wakeOnLan(macAddress) |
Send Wake-on-LAN packet |
This error typically occurs when:
- Device not connected to network - Ensure your device is connected to WiFi or Ethernet
- Missing network permissions - Check platform-specific setup above
- Firewall blocking - System firewall may block UDP multicast traffic
- VPN interference - Try disabling VPN temporarily
- Different networks - Device and DLNA devices must be on the same local network
Solutions:
- iOS 14+: The local network permission dialog must be triggered before scanning. See iOS Setup Guide for detailed instructions. Quick fix:
// Add network_info_plus to pubspec.yaml // Trigger permission dialog before scanning if (Platform.isIOS) { await NetworkInfo().getWifiName(); // Triggers permission dialog } final devices = await scanProjectors(timeoutSecs: BigInt.from(5));
- Android: Verify all permissions in AndroidManifest.xml are added
- macOS: Ensure network entitlements are properly configured
- All platforms: Try connecting to the same WiFi network as your DLNA device
- Increase scan timeout:
scanProjectors(timeoutSecs: BigInt.from(10)) - Ensure DLNA device is powered on and connected to network
- Check if device supports UPnP/DLNA protocol
- Try scanning multiple times (some devices respond slowly)
- Verify the video URL is accessible from the DLNA device's network
- Some devices only support specific video formats (MP4, MKV, etc.)
- Check if device's
avTransportUrlis not null
See the example directory for a complete demo app.
MIT License - see LICENSE for details.