Open-source project
devin1014/DLNA-Cast avatar
devin1014/DLNA-Cast

DLNA-Cast wraps the Cling stack into three Android libraries

基于Cling库封装的DLNA投屏库,支持DMC、DMS、DMR功能。

537 stars150 forksKotlinLicense varies

At a glance

What is it?
DLNA-Cast is a Kotlin library that packages the Apache Cling UPnP stack for Android into three separate artifacts, a DMC for controlling devices, a DMR for receiving content and a DMS for sharing content, published through JitPack. It suits Android developers who want screen casting without writing UPnP by hand, and the renderer side is an example rather than a finished player.
Who is it for?
Android developers who want to add screen casting to an app and would rather build on the Cling stack than implement UPnP discovery, AVTransport control and rendering themselves are the people DLNA-Cast is for, and the fact that the three roles ship as separate artifacts means an app that only casts to a television does not have to depend on the server side.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly Kotlin, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Three roles, three artifacts, and you only take what you need

The project is a wrapper, and the wrapper is the whole of it. The description says it is a DLNA casting library based on the Cling library, and the readme lists three capabilities: supporting discovery and control of casting devices from a mobile device, which is DMC; supporting the player role on a television, which is DMR; and supporting sharing content from a server device, which is DMS. Each of those is published as its own artifact, so an application that only pushes video to a television takes the dmc module and nothing else, while a television application built on the same repository takes the dmr module. The version numbering follows the same split, since the release history carries a tag named for the dmc module at version 1.0.0 from 2021 and a later v2.0.0 tag covering the current set. The underlying stack is Cling version 2.1.1, with the core and support manuals linked from the readme as the reference documentation, which tells you where the real documentation lives: Cling's own manuals, not a separate manual for the wrapper.

Adding the dependency means two maven repositories and three coordinates

The installation has two halves, and both are needed. The root project gradle file needs the Cling repository and JitPack added, and the module gradle file then declares the three artifacts. The root block is short.

bash
allprojects {
    repositories {
		...
        maven { 
            url 'http://4thline.org/m2' 
            allowInsecureProtocol = true
        }
		maven { url 'https://jitpack.io' }
	}
}

The module block names the three coordinates.

bash
implementation 'com.github.devin1014.DLNA-Cast:dmc:v2.0.0' 
implementation 'com.github.devin1014.DLNA-Cast:dmr:v2.0.0'
implementation 'com.github.devin1014.DLNA-Cast:dms:v2.0.0'

The group id is the JitPack convention of a github user and repository, which is why the download badge in the readme points at jitpack.io rather than at a package registry. Two details are worth flagging before you start. The Cling repository is addressed over plain http and therefore needs allowInsecureProtocol set to true, which is a deliberate deviation from the default and one a corporate build environment may refuse. And the readme notes that you can also reference the aar files directly, so an offline or air-gapped build is possible if you vendor the artifacts yourself. The repository layout mirrors the artifact split, with dlna-core, dlna-dmc, dlna-dmr and dlna-dms directories alongside app, tv and server sample projects.

Five permissions and three services, and the service names are fixed

Because the library uses multicast discovery over the local network, the manifest work is not optional. Five permissions are listed in the readme.

xml
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>

The multicast state permission is the one that explains the rest, since SSDP discovery is a multicast protocol and Android blocks that on a locked Wi-Fi interface by default. WAKE_LOCK is there because a discovery service has to keep running while the screen is off or the device is idle. Then three services are declared, one per role.

xml
<service android:name="com.android.cast.dlna.dmc.DLNACastService"/>
<service android:name="com.android.cast.dlna.dmr.DLNARendererService"/>
<service android:name="com.android.cast.dlna.dms.DLNAContentSercice"/>

The names are fixed class names inside the library, so a host application cannot rename them, and one of them carries a spelling irregularity that is worth knowing when you grep a stack trace: the content service class is spelled DLNAContentSercice. That is a small thing, but a service name that does not match what you expect is exactly the kind of detail that costs an afternoon when a manifest merge fails.

Binding in the lifecycle, and searching happens for you

The entry point is a singleton manager called DLNACastManager, and the readme shows binding and unbinding in the lifecycle of an Activity or a Fragment.

java
@Override
protected void onStart() {        
    DLNACastManager.getInstance().bindCastService(this);
}

@Override
protected void onStop() {
    DLNACastManager.getInstance().unbindCastService(this);
}

The bind call takes a context, so it can be called from a Fragment as well as an Activity, and the pattern mirrors a bound Android service where the caller holds a reference for as long as it is in the foreground. Once bound, the library searches for devices automatically, and a manual search is available when you want to trigger it yourself, for example from a pull-to-refresh control.

java
DLNACastManager.getInstance().search();

Device arrival is handled through a listener registered on the manager.

java
DLNACastManager.getInstance().registerDeviceListener(listener);
DLNACastManager.getInstance().unregisterListener(listener);

The readme is specific about one threading guarantee, which is the kind of detail that saves a race condition later: the OnDeviceRegistryListener callbacks are always invoked on the main thread. That means you can update a list adapter directly from the callback without posting to the main looper, and a team that knows this avoids writing a redundant dispatch.

Connecting gives you a DeviceControl with a wide surface

Connecting to a device is a single call on the manager that returns a DeviceControl and takes a callback.

java
deviceControl: DeviceControl = DLNACastManager.connectDevice(device, callback)

The interface it returns is the real API of the library, and it is wide because it covers the AVTransport and RenderingControl services of the UPnP specification. It can set the AVTransport URI for the current video, set the URI for the next video so that playback continues automatically when the current one ends, and then it exposes play, pause and stop. There is a seek by millisecond position, next and previous for tracks, a getPositionInfo call that reports the current time and the total time, a getMediaInfo call for the current video's information, and a getTransportInfo call for the current playback state. Volume can be set and read as an integer, mute can be set and read as a boolean, and there are browse and search calls that query the device by objectId or by container with a search criteria, a filter and paging limits, returning DIDLContent. The readme notes that each operation carries parameters and an event callback so you can tell whether the operation succeeded, which is the right shape for a network protocol where a call can fail silently.

The renderer side is an example, and the author says so

The most important caveat in the repository is stated by the author rather than inferred from the code. The readme says there will be updates to the library over time, that many people ask about DMR problems, and that what is here is only the simplest possible VideoView example, so in practice you need to integrate your own player and implement the various stream formats yourself. That is a candid admission that the dmr artifact demonstrates the protocol rather than shipping a finished receiver. It also means the repository's own tv directory should be read as a demonstration of how to wire the renderer service, not as a player you would ship. If your requirement is a television application that renders HLS, DASH or whatever else your content uses, the control side of this library is the reusable part and the receiving side is the part you write.

Stream format differences are documented, and they are the practical trap

The readme also records what happened when the author tried to cast to specific streaming applications, and this is the section worth reading before you promise support for a target device. When casting to the television clients of iQiyi, Youku and Tencent, the m3u8 format fails, and the URL needs to carry parameters, and with those parameters in place casting works, though the author suspects the parameters have a validity period and does not state the mechanism. Many domestic television applications do not support multi-bitrate streams, and using a single bitrate is enough to play, but iQiyi is an exception and appears to have its own restriction. These are observations rather than guarantees, and the author does not claim to have solved them. The practical reading is that the library handles the discovery and control protocol and leaves the content negotiation to you, so the set of televisions and applications your product supports is decided by the formats you can serve and the URL shapes they accept, not by the wrapper.

Where DLNA-Cast is the wrong choice

The first wrong case is a project that needs a production DLNA receiver, since the renderer here is described as the simplest VideoView example and you have to bring your own player and format handling. The second is a build environment that cannot allow a plain HTTP maven repository, because the Cling dependency is fetched over http and requires allowInsecureProtocol to be set, and a security policy that blocks that is a blocker rather than an inconvenience. The third is a team that needs a stable, versioned API surface, because the library carries a beta tag from 2023 and a v2.0.0 tag from 2026 and the author describes it as something that will be updated over time, so upstream may change underneath you. The fourth is a multiplatform product, since the library is Android-only by construction, with permissions, services and a bound-service lifecycle in the manifest. The fifth is a team that wants a hosted or licensed dependency, because the repository does not state a licence, so there is nothing to point a legal review at. And the sixth is anyone assuming the wrapping removes the hard parts of DLNA, since the hard parts on the receiving side and in stream format negotiation are precisely the parts left to the caller.

Editorial conclusion

Android developers who want to add screen casting to an app and would rather build on the Cling stack than implement UPnP discovery, AVTransport control and rendering themselves are the people DLNA-Cast is for, and the fact that the three roles ship as separate artifacts means an app that only casts to a television does not have to depend on the server side. It is the wrong tool if you need a production DLNA receiver, because the renderer artifact in this repository is described as the simplest possible VideoView example and the author states that you have to integrate your own player to handle different stream formats. Check three things before you commit: that the Cling repository is reachable over plain HTTP from your build machine, since the dependency setup requires allowInsecureProtocol for the 4thline.org maven host, that your target television accepts the stream format you intend to send, since the author documents specific failures with m3u8 against some streaming apps and notes that many television apps do not accept multi-bitrate streams, and that the licence terms suit you, since the repository does not state a licence. The library was written by LIUWEI, the last push was on 2026-04-14, and the newest release is v2.0.0 from 2026-04-09.

Frequently asked questions

What is DLNA-Cast and what does it wrap?

It is a Kotlin library that packages the Cling UPnP stack, version 2.1.1, for Android into three artifacts: dmc for controlling and casting to devices, dmr for the television side player role, and dms for sharing content from a server device.

How do I add DLNA-Cast to an Android project?

Add the 4thline.org maven repository with allowInsecureProtocol set to true and the jitpack.io repository in the root gradle file, then declare the artifacts in your module, for example implementation 'com.github.devin1014.DLNA-Cast:dmc:v2.0.0', along with the dmr and dms coordinates you need. The readme also notes you can reference the aar files directly.

Which manifest permissions and services does DLNA-Cast need?

Five permissions, INTERNET, ACCESS_NETWORK_STATE, ACCESS_WIFI_STATE, CHANGE_WIFI_MULTICAST_STATE and WAKE_LOCK, plus three service declarations: com.android.cast.dlna.dmc.DLNACastService, com.android.cast.dlna.dmr.DLNARendererService and com.android.cast.dlna.dms.DLNAContentSercice.

How do I bind the service and discover devices in DLNA-Cast?

Call DLNACastManager.getInstance().bindCastService(this) in onStart and unbindCastService(this) in onStop. Once bound the library searches automatically, DLNACastManager.getInstance().search() triggers a manual search, and registerDeviceListener and unregisterListener manage device callbacks, which are always delivered on the main thread.

What can the DeviceControl interface do in DLNA-Cast?

It sets the AVTransport URI for the current and the next video, plays, pauses and stops, seeks, moves to the next or previous track, reads position, media and transport information, sets and reads volume and mute, and browses or searches the device by objectId or container, returning DIDLContent. Every operation takes a callback so you can see whether it succeeded.

Official sources

  1. devin1014/DLNA-Cast on GitHub
  2. Issues
  3. README
  4. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/devin1014-dlna-cast.svg)](https://hysenlabs.com/projects/devin1014-dlna-cast)

Was this useful?