Skip to content

Commit

Permalink
proof-of-concept for server discovery emulation
Browse files Browse the repository at this point in the history
- currently always on
  • Loading branch information
Chaphasilor committed Sep 16, 2024
1 parent 872e1bc commit b265d9f
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/components/LoginScreen/login_flow.dart
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ class JellyfinServerClientDiscovery {
.finest("Received datagram: ${utf8.decode(datagram.data)}");
final response = ClientDiscoveryResponse.fromJson(
jsonDecode(utf8.decode(datagram.data)));
_clientDiscoveryLogger.fine("Received discovery response from ${datagram.address}:${datagram.port}: ${jsonEncode(response)}");
onServerFound(response);
}
}
Expand Down
2 changes: 2 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import 'package:finamp/services/keep_screen_on_helper.dart';
import 'package:finamp/services/offline_listen_helper.dart';
import 'package:finamp/services/playback_history_service.dart';
import 'package:finamp/services/queue_service.dart';
import 'package:finamp/services/server_discovery_emulation_service.dart';
import 'package:finamp/services/theme_provider.dart';
import 'package:audio_service/audio_service.dart';
import 'package:audio_session/audio_session.dart';
Expand Down Expand Up @@ -340,6 +341,7 @@ Future<void> _setupPlaybackServices() async {
await GetIt.instance<QueueService>().initializePlayer();
GetIt.instance.registerSingleton(PlaybackHistoryService());
GetIt.instance.registerSingleton(AudioServiceHelper());
GetIt.instance.registerSingleton(JellyfinServerDiscoveryEmulationService());
}

/// Migrates the old DownloadLocations list to a map
Expand Down
1 change: 1 addition & 0 deletions lib/models/jellyfin_models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3802,6 +3802,7 @@ class ClientDiscoveryResponse {

factory ClientDiscoveryResponse.fromJson(Map<String, dynamic> json) =>
_$ClientDiscoveryResponseFromJson(json);
Map<String, dynamic> toJson() => _$ClientDiscoveryResponseToJson(this);
}

/// LyricMetadata model.
Expand Down
69 changes: 69 additions & 0 deletions lib/services/server_discovery_emulation_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import 'dart:convert';
import 'dart:io';

import 'package:finamp/models/jellyfin_models.dart';
import 'package:finamp/services/finamp_user_helper.dart';
import 'package:get_it/get_it.dart';
import 'package:logging/logging.dart';

/// Used for discovering Jellyfin servers on the local network
/// https://jellyfin.org/docs/general/networking/#port-bindings
/// For some reason it's always being referred to as "client discovery" in the Jellyfin docs, even though we're actually discovering servers
class JellyfinServerDiscoveryEmulationService {
static final _serverDiscoveryEmulationLogger = Logger("JellyfinServerDiscoveryEmulation");

final _finampUserHelper = GetIt.instance<FinampUserHelper>();

late RawDatagramSocket socket;
bool isDisposed = false;

JellyfinServerDiscoveryEmulationService() {
advertiseServer();
}

void advertiseServer() async {

const discoveryMessage =
"who is JellyfinServer?"; // doesn't seem to be case sensitive, but the Kotlin SDK uses this capitalization
final broadcastAddress =
InternetAddress("255.255.255.255"); // UDP broadcast address
const discoveryPort = 7359; // Jellyfin client discovery port

socket = await RawDatagramSocket.bind(InternetAddress.anyIPv4, discoveryPort);
socket.broadcastEnabled =
true; // important to allow sending to broadcast address
socket.multicastHops = 5; // to account for weird network setups

_serverDiscoveryEmulationLogger.fine("Advertising server on port $discoveryPort");

socket.listen((event) {
if (event == RawSocketEvent.read) {
final datagram = socket.receive();
if (datagram != null) {
_serverDiscoveryEmulationLogger
.finest("Received datagram: ${utf8.decode(datagram.data)}");
final requestMessage = utf8.decode(datagram.data);
if (requestMessage.toLowerCase().contains(discoveryMessage.toLowerCase())) {
_serverDiscoveryEmulationLogger.fine("Received discovery message from ${datagram.address}:${datagram.port}");
// Respond with the server's information
final response = ClientDiscoveryResponse(
address: _finampUserHelper.currentUser?.baseUrl,
endpointAddress: _finampUserHelper.currentUser?.baseUrl,
id: _finampUserHelper.currentUser?.serverId,
name: "Jellyfin Server (provided by Finamp)",
);
final responseMessage = jsonEncode(response);
_serverDiscoveryEmulationLogger.finest("Sending discovery response: $responseMessage");
socket.send(utf8.encode(responseMessage), datagram.address, datagram.port);
_serverDiscoveryEmulationLogger.fine("Sent discovery response to ${datagram.address}:${datagram.port}");
}
}
}
});
}

void dispose() {
isDisposed = true;
socket.close();
}
}

0 comments on commit b265d9f

Please sign in to comment.