jellyfin-sdk-kotlin
jellyfin-sdk-kotlin copied to clipboard
Use multicast for server discovery
Use 224.0.0.1 multicast address instead of the broadcast address to find Jellyfin servers on the local network.
Pros | Cons |
---|---|
A single implementation for both Android and JVM | Maybe some routers block multicast traffic? (I have personally never encountered this) |
Drops the requirement for ACCESS_WIFI_STATE permission on Android | IPv4 only. (seems like the Jellyfin server discovery doesn't even listen on IPv6 anyway) The IPv6 equivalent multicast address is ff02::1 |
Removes some code |
I have tested this on two different networks with both the Android SDK and the JVM library.
FYI you need to run the "apiDump" Gradle task to generate the .api files and have the binary compatibility job succeed. See https://github.com/Kotlin/binary-compatibility-validator for docs.
I started out by testing with the kotlin-cli sample and it immediately failed. Command is ./gradlew :samples:kotlin-cli:run --args "discover local" -Dlogback.debug=true
, OS is Windows 10 (which I suspect might be the problem).
Starting local network discovery
[main] INFO org.jellyfin.sdk.discovery.LocalServerDiscovery - Starting discovery with timeout of 500ms
[main] ERROR org.jellyfin.sdk.discovery.LocalServerDiscovery - Unable to send discovery message to /224.0.0.1
java.net.NoRouteToHostException: No route to host: no further information
at java.base/sun.nio.ch.DatagramChannelImpl.send0(Native Method)
at java.base/sun.nio.ch.DatagramChannelImpl.sendFromNativeBuffer(DatagramChannelImpl.java:901)
at java.base/sun.nio.ch.DatagramChannelImpl.send(DatagramChannelImpl.java:863)
at java.base/sun.nio.ch.DatagramChannelImpl.send(DatagramChannelImpl.java:821)
at java.base/sun.nio.ch.DatagramChannelImpl.blockingSend(DatagramChannelImpl.java:853)
at java.base/sun.nio.ch.DatagramSocketAdaptor.send(DatagramSocketAdaptor.java:218)
at java.base/java.net.DatagramSocket.send(DatagramSocket.java:664)
at org.jellyfin.sdk.discovery.LocalServerDiscovery.discoverAddress(LocalServerDiscovery.kt:45)
at org.jellyfin.sdk.discovery.LocalServerDiscovery.access$discoverAddress(LocalServerDiscovery.kt:26)
at org.jellyfin.sdk.discovery.LocalServerDiscovery$discover$1.invokeSuspend(LocalServerDiscovery.kt:100)
at org.jellyfin.sdk.discovery.LocalServerDiscovery$discover$1.invoke(LocalServerDiscovery.kt)
at org.jellyfin.sdk.discovery.LocalServerDiscovery$discover$1.invoke(LocalServerDiscovery.kt)
at kotlinx.coroutines.flow.SafeFlow.collectSafely(Builders.kt:61)
at kotlinx.coroutines.flow.AbstractFlow.collect(Flow.kt:230)
at kotlinx.coroutines.flow.FlowKt__TransformKt$onEach$$inlined$unsafeTransform$1.collect(SafeCollector.common.kt:113)
at kotlinx.coroutines.flow.FlowKt__CollectKt.collect(Collect.kt:30)
at kotlinx.coroutines.flow.FlowKt.collect(Unknown Source)
at org.jellyfin.sample.cli.command.Discover.runLocal(Discover.kt:30)
at org.jellyfin.sample.cli.command.Discover.access$runLocal(Discover.kt:11)
at org.jellyfin.sample.cli.command.Discover$run$1.invokeSuspend(Discover.kt:20)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:284)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at org.jellyfin.sample.cli.command.Discover.run(Discover.kt:19)
at com.github.ajalt.clikt.parsers.Parser.parse(Parser.kt:198)
at com.github.ajalt.clikt.parsers.Parser.parse(Parser.kt:211)
at com.github.ajalt.clikt.parsers.Parser.parse(Parser.kt:18)
at com.github.ajalt.clikt.core.CliktCommand.parse(CliktCommand.kt:400)
at com.github.ajalt.clikt.core.CliktCommand.parse$default(CliktCommand.kt:397)
at com.github.ajalt.clikt.core.CliktCommand.main(CliktCommand.kt:415)
at com.github.ajalt.clikt.core.CliktCommand.main(CliktCommand.kt:440)
at org.jellyfin.sample.cli.MainKt.main(Main.kt:33)
Same test with master works fine:
Starting local network discovery
Server <name> was found at address <address>:
ServerDiscoveryInfo(address=<address>, id=<id>, name=<name>, endpointAddress=null)
[main] INFO org.jellyfin.sdk.discovery.LocalServerDiscovery - Starting discovery with timeout of 500ms
Deprecated Gradle features were used in this build, making it incompatible with Gradle 8.0.
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
See https://docs.gradle.org/7.5.1/userguide/command_line_interface.html#sec:command_line_warnings
BUILD SUCCESSFUL in 7s
I will take a look at the issues later today.
I tested with the same command ./gradlew :samples:kotlin-cli:run --args "discover local" -Dlogback.debug=true
on my Windows 11 machine and it worked perfectly. Even without a network connection, no errors.
(My original testings was on Linux)
Could it be a specific issue on your network?
Does the command route print -4
show you a route to 224.0.0.0
?
Do you get the same "No route to host error" error when you ping 224.0.0.1
?
I hope this issue can be solved :confused:
I don't have much time today so just a quick reply;
Does the command route print -4 show you a route to 224.0.0.0?
Yes
Do you get the same "No route to host error" error when you ping 224.0.0.1?
Pinging 224.0.0.1 with 32 bytes of data:
PING: transmit failed. General failure.
PING: transmit failed. General failure.
PING: transmit failed. General failure.
Ping statistics for 224.0.0.1:
Packets: Sent = 3, Received = 0, Lost = 3 (100% loss),
Control-C
Sorry for the delay.
I just tested on an other network and it also works. There could be an issue with your specific machine or network. And I don't think you will merge this unless it works on your device. I'm not sure how to proceed with this one. Any suggestions?
I did some additional testing. My Windows machine still doesn't work and I have no clue why that is. Testing on my laptop with Ubuntu doesn't throw the NoRouteToHostException but also doesn't find the Jellyfin server. Lastly, I started the Android TV app with the SDK version set to this PR on my Nvidia Shield and it also does not find the server.
So in all my testing this PR doesn't appear to work for me. All devices work with the current discovery implementation.
It might be good to mention, with the existing discovery code it uses "255.255.255.255" as address. This PR does work when I change the multicastAddress to it.
After some more research and testing, using the special broadcast address 255.255.255.255
seems like the better option.
Would it be okay if I change this PR to use that address instead? (with the title and description also updated ofc)
Yup that's fine, make sure to rebase for the CI changes that have been made since.