diff --git a/.gitignore b/.gitignore
index ed65b2ea..12f3f8f0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+reports
.kiro
doxygen
.bundle
diff --git a/README.md b/README.md
index 1c332492..54a89c1a 100644
--- a/README.md
+++ b/README.md
@@ -15,8 +15,15 @@ tobexyz
alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/de.yaacc/)
-
-
+
+
+
+
+)
+
+
+
+
## Description
@@ -29,10 +36,15 @@ in order to play media from your device on any UPnP renderer in your network.
* UPnP/DLNA Server - share files of your device in the network
* UPnP/DLNA Client - receive media from other devices on your device
* UPnP/DLNA Controller - control media renderer in the network
+* Live streaming - stream system audio and screen to network devices (Android 10+)
+* SAF support - browse and share files from external storage, USB drives, SD cards
+* Lock screen controls - hardware volume buttons and media controls
+* Receiver controls - play/pause/stop directly from receiver tab with status display
* Control multiple media renderer
* Allow download files to the device
* Allow sharing of URLs and sending them to the current media renderers
* Use your device as an proxy if your media renderer can't process https media URLs
+* Battery optimized with smart WiFi lock and foreground service management
## Configuration
diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock
index eb2d0726..8acb38da 100644
--- a/docs/Gemfile.lock
+++ b/docs/Gemfile.lock
@@ -1,31 +1,34 @@
GEM
remote: https://rubygems.org/
specs:
- activesupport (7.1.3.2)
+ activesupport (7.2.3.1)
base64
+ benchmark (>= 0.3)
bigdecimal
- concurrent-ruby (~> 1.0, >= 1.0.2)
+ concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
- minitest (>= 5.1)
- mutex_m
- tzinfo (~> 2.0)
- addressable (2.8.6)
- public_suffix (>= 2.0.2, < 6.0)
- base64 (0.2.0)
- bigdecimal (3.1.6)
+ logger (>= 1.4.2)
+ minitest (>= 5.1, < 6)
+ securerandom (>= 0.3)
+ tzinfo (~> 2.0, >= 2.0.5)
+ addressable (2.9.0)
+ public_suffix (>= 2.0.2, < 8.0)
+ base64 (0.3.0)
+ benchmark (0.5.0)
+ bigdecimal (4.0.1)
coffee-script (2.4.1)
coffee-script-source
execjs
coffee-script-source (1.12.2)
colorator (1.1.0)
commonmarker (0.23.10)
- concurrent-ruby (1.2.3)
- connection_pool (2.4.1)
+ concurrent-ruby (1.3.6)
+ connection_pool (3.0.2)
dnsruby (1.70.0)
simpleidn (~> 0.2.1)
- drb (2.2.1)
+ drb (2.2.3)
em-websocket (0.5.3)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0)
@@ -97,7 +100,7 @@ GEM
activesupport (>= 2)
nokogiri (>= 1.4)
http_parser.rb (0.8.0)
- i18n (1.14.4)
+ i18n (1.14.8)
concurrent-ruby (~> 1.0)
jekyll (3.9.5)
addressable (~> 2.4)
@@ -207,7 +210,7 @@ GEM
gemoji (>= 3, < 5)
html-pipeline (~> 2.2)
jekyll (>= 3.0, < 5.0)
- json (2.18.1)
+ json (2.19.2)
kramdown (2.4.0)
rexml
kramdown-parser-gfm (1.1.0)
@@ -222,8 +225,7 @@ GEM
jekyll (>= 3.5, < 5.0)
jekyll-feed (~> 0.9)
jekyll-seo-tag (~> 2.1)
- minitest (5.22.2)
- mutex_m (0.2.0)
+ minitest (5.27.0)
net-http (0.9.1)
uri (>= 0.11.1)
nokogiri (1.19.1-x86_64-linux-gnu)
@@ -233,7 +235,7 @@ GEM
sawyer (~> 0.9)
pathutil (0.16.2)
forwardable-extended (~> 2.6)
- public_suffix (5.0.4)
+ public_suffix (5.1.1)
racc (1.8.1)
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
@@ -250,6 +252,7 @@ GEM
sawyer (0.9.2)
addressable (>= 2.3.5)
faraday (>= 0.17.3, < 3)
+ securerandom (0.4.1)
simpleidn (0.2.1)
unf (~> 0.1.4)
terminal-table (1.8.0)
diff --git a/fastlane/metadata/android/en-US/changelogs/50001.txt b/fastlane/metadata/android/en-US/changelogs/50001.txt
new file mode 100644
index 00000000..f56e11fe
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/50001.txt
@@ -0,0 +1,3 @@
+Version 5.0.1
+- fixed crash on startup if no network device is found
+- fixed problems with device lookup
diff --git a/yaacc/src/main/AndroidManifest.xml b/yaacc/src/main/AndroidManifest.xml
index c0a75e7a..2e01afec 100644
--- a/yaacc/src/main/AndroidManifest.xml
+++ b/yaacc/src/main/AndroidManifest.xml
@@ -1,8 +1,8 @@
+ android:versionCode="50001"
+ android:versionName="5.0.1">
diff --git a/yaacc/src/main/java/de/yaacc/browser/ServerListFragment.java b/yaacc/src/main/java/de/yaacc/browser/ServerListFragment.java
index 14ef02b1..464f5484 100644
--- a/yaacc/src/main/java/de/yaacc/browser/ServerListFragment.java
+++ b/yaacc/src/main/java/de/yaacc/browser/ServerListFragment.java
@@ -22,7 +22,6 @@
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
-import de.yaacc.util.YaaccLogger;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -53,6 +52,7 @@
import de.yaacc.util.FormatHelper;
import de.yaacc.util.ShutdownTimerListener;
import de.yaacc.util.ThemeHelper;
+import de.yaacc.util.YaaccLogger;
/**
* Activity for browsing devices and folders. Represents the entrypoint for the whole application.
@@ -81,7 +81,7 @@ private SharedPreferences getPreferences() {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
+
// Register callback for MediaProjection stop events
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
de.yaacc.upnp.server.media.MediaProjectionHelper.setStopCallback(() -> {
@@ -90,17 +90,17 @@ public void onCreate(Bundle savedInstanceState) {
getActivity().runOnUiThread(() -> {
android.content.SharedPreferences prefs = androidx.preference.PreferenceManager.getDefaultSharedPreferences(requireContext());
prefs.edit()
- .putBoolean(getString(R.string.settings_local_server_serve_system_audio_chkbx), false)
- .putBoolean(getString(R.string.settings_local_server_serve_screen_cast_chkbx), false)
- .apply();
-
+ .putBoolean(getString(R.string.settings_local_server_serve_system_audio_chkbx), false)
+ .putBoolean(getString(R.string.settings_local_server_serve_screen_cast_chkbx), false)
+ .apply();
+
de.yaacc.browser.BrowseDeviceAdapter.setAudioStreaming(false);
de.yaacc.browser.BrowseDeviceAdapter.setVideoStreaming(false);
-
+
if (bDeviceAdapter != null) {
bDeviceAdapter.notifyDataSetChanged();
}
-
+
YaaccLogger.i(getClass().getName(), "Streaming disabled - MediaProjection stopped");
});
}
@@ -192,6 +192,10 @@ public void receiverDeviceAdded(Device, ?, ?> device) {
@Override
public void onResume() {
super.onResume();
+ // Trigger UPnP discovery to find new devices
+ if (upnpClient != null && upnpClient.isInitialized()) {
+ upnpClient.searchDevices();
+ }
//refresh device list
Thread thread = new Thread(this::populateDeviceList);
thread.start();
@@ -345,21 +349,21 @@ public void setShutdownTimerRemainingTime(String s) {
public void onTick(long millisUntilFinished) {
setShutdownTimerRemainingTime(FormatHelper.parseMillisToTimeStringTo(millisUntilFinished));
}
-
+
@Override
public void onActivityResult(int requestCode, int resultCode, android.content.Intent data) {
super.onActivityResult(requestCode, resultCode, data);
-
+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q &&
- requestCode == de.yaacc.upnp.server.media.MediaProjectionHelper.REQUEST_CODE_MEDIA_PROJECTION) {
-
+ requestCode == de.yaacc.upnp.server.media.MediaProjectionHelper.REQUEST_CODE_MEDIA_PROJECTION) {
+
if (de.yaacc.upnp.server.media.MediaProjectionHelper.handlePermissionResult(requireContext(), resultCode, data)) {
// Permission granted - enable only the button that requested it
YaaccLogger.i(getClass().getName(), "MediaProjection permission granted");
-
+
android.content.SharedPreferences prefs = androidx.preference.PreferenceManager.getDefaultSharedPreferences(requireContext());
android.content.SharedPreferences.Editor editor = prefs.edit();
-
+
// Enable only the button that requested permission
if (de.yaacc.browser.BrowseDeviceAdapter.isPendingAudioRequest()) {
editor.putBoolean(getString(R.string.settings_local_server_serve_system_audio_chkbx), true);
@@ -370,10 +374,10 @@ public void onActivityResult(int requestCode, int resultCode, android.content.In
de.yaacc.browser.BrowseDeviceAdapter.setVideoStreaming(true);
}
editor.apply();
-
+
// Clear pending flags
de.yaacc.browser.BrowseDeviceAdapter.clearPendingRequests();
-
+
// Refresh the adapter to update button states
if (bDeviceAdapter != null) {
bDeviceAdapter.notifyDataSetChanged();
@@ -381,15 +385,15 @@ public void onActivityResult(int requestCode, int resultCode, android.content.In
} else {
// Permission denied - disable both and clear pending
YaaccLogger.w(getClass().getName(), "MediaProjection permission denied");
-
+
de.yaacc.browser.BrowseDeviceAdapter.clearPendingRequests();
-
+
android.content.SharedPreferences prefs = androidx.preference.PreferenceManager.getDefaultSharedPreferences(requireContext());
prefs.edit()
- .putBoolean(getString(R.string.settings_local_server_serve_system_audio_chkbx), false)
- .putBoolean(getString(R.string.settings_local_server_serve_screen_cast_chkbx), false)
- .apply();
-
+ .putBoolean(getString(R.string.settings_local_server_serve_system_audio_chkbx), false)
+ .putBoolean(getString(R.string.settings_local_server_serve_screen_cast_chkbx), false)
+ .apply();
+
// Update adapter state
if (bDeviceAdapter != null) {
de.yaacc.browser.BrowseDeviceAdapter.setAudioStreaming(false);
diff --git a/yaacc/src/main/java/de/yaacc/upnp/protocol/UpnpProtocolHandler.java b/yaacc/src/main/java/de/yaacc/upnp/protocol/UpnpProtocolHandler.java
index f26f3f42..bcf35d57 100644
--- a/yaacc/src/main/java/de/yaacc/upnp/protocol/UpnpProtocolHandler.java
+++ b/yaacc/src/main/java/de/yaacc/upnp/protocol/UpnpProtocolHandler.java
@@ -162,7 +162,8 @@ public Namespace getNamespace() {
public ReceivingSync createReceivingSync(StreamRequestMessage message) throws ProtocolCreationException {
YaaccLogger.v(getClass().getName(), "Creating protocol for incoming synchronous: " + message);
- if (message.getOperation().getMethod().equals(UpnpRequest.Method.GET)) {
+ if (message.getOperation().getMethod().equals(UpnpRequest.Method.GET) ||
+ message.getOperation().getMethod().equals(UpnpRequest.Method.HEAD)) {
return createReceivingRetrieval(message);
diff --git a/yaacc/src/main/java/de/yaacc/upnp/protocol/sync/ReceivingRetrieval.java b/yaacc/src/main/java/de/yaacc/upnp/protocol/sync/ReceivingRetrieval.java
index 784f2804..cf5f3ac5 100644
--- a/yaacc/src/main/java/de/yaacc/upnp/protocol/sync/ReceivingRetrieval.java
+++ b/yaacc/src/main/java/de/yaacc/upnp/protocol/sync/ReceivingRetrieval.java
@@ -101,6 +101,13 @@ protected StreamResponseMessage executeSync() throws IOException {
}
}
+ // HEAD requests don't need content, just return headers
+ if (getInputMessage().getOperation().getMethod().equals(org.fourthline.cling.model.message.UpnpRequest.Method.HEAD)) {
+ StreamResponseMessage response = new StreamResponseMessage(UpnpResponse.Status.OK);
+ response.getHeaders().add(UpnpHeader.Type.SERVER, new ServerHeader());
+ return response;
+ }
+
return createResponse(requestedURI, foundResource);
}
diff --git a/yaacc/src/main/java/de/yaacc/upnp/registry/LocalItems.java b/yaacc/src/main/java/de/yaacc/upnp/registry/LocalItems.java
index 4d2f942f..c77ac635 100644
--- a/yaacc/src/main/java/de/yaacc/upnp/registry/LocalItems.java
+++ b/yaacc/src/main/java/de/yaacc/upnp/registry/LocalItems.java
@@ -52,6 +52,7 @@
import java.util.concurrent.Executors;
import de.yaacc.upnp.protocol.SendingAsync;
+import de.yaacc.upnp.protocol.UpnpProtocolHandler;
import de.yaacc.util.YaaccLogger;
/**
@@ -321,12 +322,18 @@ public void run() {
} catch (InterruptedException ex) {
YaaccLogger.e(getClass().getName(), "Background execution interrupted: " + ex.getMessage());
}
- registry.getUpnpProtocolHandler().createSendingNotificationAlive(localDevice).run();
+ UpnpProtocolHandler handler = registry.getUpnpProtocolHandler();
+ if (handler != null) {
+ handler.createSendingNotificationAlive(localDevice).run();
+ }
}
});
}
protected void advertiseByebye(final LocalDevice localDevice, boolean asynchronous) {
+ if (registry.getUpnpProtocolHandler() == null) {
+ return;
+ }
final SendingAsync prot = registry.getUpnpProtocolHandler().createSendingNotificationByebye(localDevice);
if (asynchronous) {
registry.executeAsyncProtocol(prot);
diff --git a/yaacc/src/main/java/de/yaacc/upnp/server/NetworkDeviceListener.java b/yaacc/src/main/java/de/yaacc/upnp/server/NetworkDeviceListener.java
index 7fdd0baa..78af8d21 100644
--- a/yaacc/src/main/java/de/yaacc/upnp/server/NetworkDeviceListener.java
+++ b/yaacc/src/main/java/de/yaacc/upnp/server/NetworkDeviceListener.java
@@ -29,18 +29,18 @@
import androidx.preference.PreferenceManager;
import de.yaacc.R;
-import de.yaacc.util.YaaccLogger;
-
import de.yaacc.upnp.protocol.UpnpProtocolHandler;
import de.yaacc.upnp.registry.Registry;
import de.yaacc.upnp.server.http.HttpRequestSender;
import de.yaacc.upnp.server.udp.MulticastReceiver;
import de.yaacc.upnp.server.udp.UdpTransiver;
+import de.yaacc.util.YaaccLogger;
public class NetworkDeviceListener {
private final Context context;
private final WifiManager wifiManager;
private final Registry registry;
+ private final YaaccUpnpServerService service;
private HttpRequestSender httpRequestSender;
private WifiManager.MulticastLock multicastLock;
private WifiManager.WifiLock wifiLock;
@@ -54,7 +54,8 @@ public class NetworkDeviceListener {
private UpnpProtocolHandler upnpProtocolHandler;
- public NetworkDeviceListener(Context context, Registry registry) throws IllegalStateException {
+ public NetworkDeviceListener(Context context, Registry registry, YaaccUpnpServerService service) throws IllegalStateException {
+ this.service = service;
this.context = context;
this.registry = registry;
this.wifiManager = ((WifiManager) context.getSystemService(Context.WIFI_SERVICE));
@@ -80,6 +81,15 @@ public void onAvailable(@NonNull Network network) {
YaaccLogger.d(getClass().getName(), String.format("Network disabled %s", currentNetwork));
}
currentNetwork = network;
+ if (multicastReceiver == null) {
+ multicastReceiver = new MulticastReceiver();
+ }
+ if (udpTransiver == null) {
+ udpTransiver = new UdpTransiver();
+ }
+ if (httpRequestSender == null) {
+ httpRequestSender = new HttpRequestSender();
+ }
enable();
YaaccLogger.d(getClass().getName(), String.format("Network enabled %s", currentNetwork));
}
@@ -113,6 +123,7 @@ public void enable() {
multicastReceiver.execute();
udpTransiver.init(context, upnpProtocolHandler);
udpTransiver.execute();
+ service.updateNotification();
}
public void disable() {
@@ -126,6 +137,7 @@ public void disable() {
upnpProtocolHandler = null;
multicastReceiver.cancel();
udpTransiver.cancel();
+ service.updateNotification();
}
private boolean isWifi() {
@@ -197,24 +209,24 @@ public boolean isWifiLockHeld() {
private boolean shouldHoldWifiLock() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
-
+
// Hold lock if server enabled
boolean serverEnabled = prefs.getBoolean(context.getString(R.string.settings_local_server_chkbx), false);
-
+
// Hold lock if renderer enabled
boolean rendererEnabled = prefs.getBoolean(context.getString(R.string.settings_local_server_receiver_chkbx), false);
-
+
// Hold lock if app in foreground (for discovery)
boolean needsForDiscovery = isAppInForeground;
-
+
boolean shouldHold = serverEnabled || rendererEnabled || needsForDiscovery;
-
- YaaccLogger.d(getClass().getName(),
- "WiFi lock decision: server=" + serverEnabled +
- ", renderer=" + rendererEnabled +
- ", foreground=" + isAppInForeground +
- " -> " + (shouldHold ? "HOLD" : "RELEASE"));
-
+
+ YaaccLogger.d(getClass().getName(),
+ "WiFi lock decision: server=" + serverEnabled +
+ ", renderer=" + rendererEnabled +
+ ", foreground=" + isAppInForeground +
+ " -> " + (shouldHold ? "HOLD" : "RELEASE"));
+
return shouldHold;
}
diff --git a/yaacc/src/main/java/de/yaacc/upnp/server/YaaccUpnpServerService.java b/yaacc/src/main/java/de/yaacc/upnp/server/YaaccUpnpServerService.java
index 3e9c14fb..ed3c9ef9 100644
--- a/yaacc/src/main/java/de/yaacc/upnp/server/YaaccUpnpServerService.java
+++ b/yaacc/src/main/java/de/yaacc/upnp/server/YaaccUpnpServerService.java
@@ -224,12 +224,31 @@ public int onStartCommand(Intent intent, int flags, int startId) {
registry = new RegistryImpl();
}
if (networkDeviceListener == null) {
- networkDeviceListener = new NetworkDeviceListener(getApplicationContext(), registry);
+ networkDeviceListener = new NetworkDeviceListener(getApplicationContext(), registry, this);
registry.setUpnpProtocolHandler(networkDeviceListener.getUpnpProtocolHandler());
}
// App is active when service starts
networkDeviceListener.setAppInForeground(true);
+ // Trigger UPnP discovery when service starts
+ YaaccLogger.d(getClass().getName(), "Triggering UPnP discovery on service start");
+ if (networkDeviceListener.isInitalized()) {
+ new Thread(() -> {
+ try {
+ Thread.sleep(1000); // Wait a bit for network to stabilize
+ UpnpClient client = ((Yaacc) getApplicationContext()).getUpnpClient();
+ if (client != null && client.isInitialized()) {
+ client.searchDevices();
+ YaaccLogger.d(getClass().getName(), "UPnP discovery triggered");
+ }
+ } catch (Exception e) {
+ YaaccLogger.e(getClass().getName(), "Error triggering UPnP discovery", e);
+ }
+ }).start();
+ } else {
+ YaaccLogger.w(getClass().getName(), "NetworkDeviceListener not initialized, discovery will be triggered when network becomes available");
+ }
+
locaDeviceUuid = preferences.getString(getApplicationContext().getString(R.string.settings_local_device_uuid_key), null);
if (locaDeviceUuid == null) {
locaDeviceUuid = UUID.randomUUID().toString();
@@ -305,8 +324,8 @@ public void onTaskRemoved(Intent rootIntent) {
YaaccLogger.d(getClass().getName(), "Task removed - app backgrounded");
if (networkDeviceListener != null) {
networkDeviceListener.setAppInForeground(false);
- updateNotification(); // WiFi lock may have changed
}
+ updateNotification(); // WiFi lock may have changed
}
/**
@@ -332,6 +351,22 @@ private void showNotification() {
statusBuilder.append("⚠ HTTP");
}
+ // Network interface info
+ if (networkDeviceListener != null && networkDeviceListener.isInitalized()) {
+ try {
+ String[] iface = InterfaceResolutionHelper.getIfAndIpAddress(this);
+ if (!"0.0.0.0".equals(iface[0])) {
+ statusBuilder.append(" | ").append(iface[1]).append(":").append(iface[0]);
+ } else {
+ statusBuilder.append(" | No usable network interface found");
+ }
+ } catch (Exception e) {
+ YaaccLogger.d(getClass().getName(), "Failed to get network interface info", e);
+ }
+ } else {
+ statusBuilder.append(" | No usable network interface found");
+ }
+
// Server/Renderer status
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
boolean serverEnabled = preferences.getBoolean(getString(R.string.settings_local_server_chkbx), false);
@@ -379,7 +414,7 @@ private void showNotification() {
/**
* Update notification with current server status.
*/
- private void updateNotification() {
+ public void updateNotification() {
showNotification();
}
@@ -405,6 +440,20 @@ private void initialize() {
YaaccLogger.i(getClass().getName(), "initialize() called");
+ // Wait for NetworkDeviceListener to be initialized
+ if (!networkDeviceListener.isInitalized()) {
+ YaaccLogger.w(getClass().getName(), "NetworkDeviceListener not initialized, waiting...");
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ if (!networkDeviceListener.isInitalized()) {
+ YaaccLogger.e(getClass().getName(), "NetworkDeviceListener still not initialized, aborting");
+ return;
+ }
+ }
+
// Try to create HTTP server with retries
boolean serverStarted = false;
for (int attempt = 1; attempt <= HTTP_SERVER_MAX_RETRIES && !serverStarted; attempt++) {
@@ -500,6 +549,7 @@ private void createUpnpDevice() {
if (localDevice != null && registry.getDevices().contains(localDevice)) {
YaaccLogger.d(this.getClass().getName(), "Removing old device before creating new one");
registry.removeDevice(localDevice);
+
}
try {
@@ -528,45 +578,32 @@ private void createUpnpDevice() {
DeviceIdentity identity = new DeviceIdentity(new UDN(locaDeviceUuid));
- // If both server and renderer are enabled, create TWO separate devices
+ // If both server and renderer are enabled, create nested device structure
if (serverEnabled && providerEnabled && rendererEnabled) {
- // Create MediaServer device
- DeviceDetails serverDetails = new DeviceDetails(
- getLocalServerName() + " - Server",
- new ManufacturerDetails("yaacc.de", "https://www.yaacc.de"),
- new ModelDetails(getLocalServerName() + " - UpnP Server", "Free Android UPnP/DLNA, GNU GPL", versionName),
- URI.create("http://" + InterfaceResolutionHelper.getIpAddress(getApplicationContext()) + ":" + PORT)
- );
-
- LocalDevice serverDevice = new LocalDevice(
- new DeviceIdentity(new UDN(locaDeviceUuid + "-server")),
- new UDADeviceType("MediaServer"),
- serverDetails,
- createDeviceIcons(),
- createMediaServerServices()
- );
-
- // Create MediaRenderer device
- DeviceDetails rendererDetails = new DeviceDetails(
- getLocalServerName() + " - Renderer",
- new ManufacturerDetails("yaacc.de", "https://www.yaacc.de"),
- new ModelDetails(getLocalServerName() + " - UpnP Renderer", "Free Android UPnP/DLNA, GNU GPL", versionName),
- URI.create("http://" + InterfaceResolutionHelper.getIpAddress(getApplicationContext()) + ":" + PORT)
- );
-
+ // Create MediaServer as root with embedded MediaRenderer
LocalDevice rendererDevice = new LocalDevice(
new DeviceIdentity(new UDN(locaDeviceUuid + "-renderer")),
new UDADeviceType("MediaRenderer"),
- rendererDetails,
+ yaaccDetails,
createDeviceIcons(),
createMediaRendererServices()
);
- // Register BOTH devices as separate top-level devices
+ List> serverServices = new ArrayList<>();
+ serverServices.addAll(Arrays.asList(createCoreServices()));
+ serverServices.addAll(Arrays.asList(createMediaServerServices()));
+
+ LocalDevice serverDevice = new LocalDevice(
+ identity,
+ new UDADeviceType("MediaServer"),
+ yaaccDetails,
+ createDeviceIcons(),
+ serverServices.toArray(new LocalService>[0]),
+ new LocalDevice[]{rendererDevice}
+ );
+
registry.addDevice(serverDevice);
- registry.addDevice(rendererDevice);
-
- localDevice = serverDevice; // Track server device for reference
+ localDevice = serverDevice;
} else {
// Single device type
if (serverEnabled && providerEnabled) {
@@ -1259,7 +1296,7 @@ private void startCombinedCapture() {
return;
}
- android.media.projection.MediaProjection projection = MediaProjectionHelper.getMediaProjection();
+ MediaProjection projection = MediaProjectionHelper.getMediaProjection();
if (projection == null) {
YaaccLogger.w(getClass().getName(), "Cannot start combined capture: no MediaProjection");
diff --git a/yaacc/src/main/java/de/yaacc/upnp/server/udp/UdpTransiver.java b/yaacc/src/main/java/de/yaacc/upnp/server/udp/UdpTransiver.java
index edd4e270..b982a199 100644
--- a/yaacc/src/main/java/de/yaacc/upnp/server/udp/UdpTransiver.java
+++ b/yaacc/src/main/java/de/yaacc/upnp/server/udp/UdpTransiver.java
@@ -1,9 +1,7 @@
package de.yaacc.upnp.server.udp;
import android.content.Context;
-import de.yaacc.util.YaaccLogger;
-import de.yaacc.util.Exceptions;
import org.fourthline.cling.model.UnsupportedDataException;
import org.fourthline.cling.model.message.IncomingDatagramMessage;
import org.fourthline.cling.model.message.OutgoingDatagramMessage;
@@ -18,7 +16,9 @@
import de.yaacc.upnp.protocol.ProtocolCreationException;
import de.yaacc.upnp.protocol.ReceivingAsync;
import de.yaacc.upnp.protocol.UpnpProtocolHandler;
+import de.yaacc.util.Exceptions;
import de.yaacc.util.InterfaceResolutionHelper;
+import de.yaacc.util.YaaccLogger;
public class UdpTransiver {
@@ -46,15 +46,13 @@ public void init(Context context, UpnpProtocolHandler protocolHandler) {
private void initSocket() {
InterfaceResolutionHelper.InterfaceHolder usableInterface = InterfaceResolutionHelper.getNetworkInterface(context);
try {
-
- // TODO: UPNP VIOLATION: The spec does not prohibit using the 1900 port here again, however, the
- // Netgear ReadyNAS miniDLNA implementation will no longer answer if it has to send search response
- // back via UDP unicast to port 1900... so we use an ephemeral port
YaaccLogger.v(getClass().getName(), "Creating bound socket (for datagram input/output) on: " + usableInterface.inetAddress);
InetSocketAddress localAddress = new InetSocketAddress(usableInterface.inetAddress, 0);
socket = new MulticastSocket(localAddress);
socket.setTimeToLive(TTL);
socket.setReceiveBufferSize(262144); // Keep a backlog of incoming datagrams if we are not fast enough
+ socket.setReuseAddress(true);
+
YaaccLogger.v(getClass().getName(), "Socket created and bound to: " + socket.getLocalSocketAddress() + " on interface: " + usableInterface.networkInterface.getDisplayName());
} catch (Exception ex) {
throw new IllegalStateException("Could not initialize " + getClass().getSimpleName() + ": " + ex);
diff --git a/yaacc/src/main/java/de/yaacc/util/InterfaceResolutionHelper.java b/yaacc/src/main/java/de/yaacc/util/InterfaceResolutionHelper.java
index c73afcc3..6f4bd5db 100644
--- a/yaacc/src/main/java/de/yaacc/util/InterfaceResolutionHelper.java
+++ b/yaacc/src/main/java/de/yaacc/util/InterfaceResolutionHelper.java
@@ -20,7 +20,6 @@
import android.content.Context;
import android.content.SharedPreferences;
-import de.yaacc.util.YaaccLogger;
import androidx.preference.PreferenceManager;
@@ -91,8 +90,13 @@ public static String[] getIfAndIpAddress(Context context) {
String hostAddress = null;
String[] result = new String[2];
InterfaceHolder useableInterface = getNetworkInterface(context);
+ if (useableInterface.inetAddress == null || useableInterface.networkInterface == null) {
+ // maybe wifi is off we have to use the loopback device
+ result[0] = "0.0.0.0";
+ result[1] = "lo";
+ return result;
+ }
hostAddress = useableInterface.inetAddress.getHostAddress();
-
// maybe wifi is off we have to use the loopback device
hostAddress = hostAddress == null ? "0.0.0.0" : hostAddress;
result[0] = hostAddress;
@@ -103,7 +107,7 @@ public static String[] getIfAndIpAddress(Context context) {
public static InterfaceHolder getNetworkInterface(Context context) {
InterfaceHolder result = new InterfaceHolder();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
- List interfaces = new ArrayList<>(List.of(preferences.getString(context.getString(R.string.settings_local_server_if_filter_key), "lo,dummy,rmnet,ccmni").split(",")));
+ List interfaces = new ArrayList<>(List.of(preferences.getString(context.getString(R.string.settings_upnp_if_filter_key), "lo,dummy,rmnet,ccmni,tun").split(",")));
interfaces.remove(""); //remove empty string, if there, otherwise we got into trouble finding an network interface in code below
try {
for (Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); networkInterfaces.hasMoreElements(); ) {
diff --git a/yaacc/src/main/java/org/fourthline/cling/model/message/UpnpRequest.java b/yaacc/src/main/java/org/fourthline/cling/model/message/UpnpRequest.java
index 248752e2..8b53cb8c 100644
--- a/yaacc/src/main/java/org/fourthline/cling/model/message/UpnpRequest.java
+++ b/yaacc/src/main/java/org/fourthline/cling/model/message/UpnpRequest.java
@@ -16,11 +16,11 @@
package org.fourthline.cling.model.message;
import java.net.URI;
-import java.net.URL;
import java.net.URISyntaxException;
+import java.net.URL;
import java.util.HashMap;
-import java.util.Map;
import java.util.Locale;
+import java.util.Map;
/**
* A request message, with a method (GET, POST, NOTIFY, etc).
@@ -32,6 +32,7 @@ public class UpnpRequest extends UpnpOperation {
public static enum Method {
GET("GET"),
+ HEAD("HEAD"),
POST("POST"),
NOTIFY("NOTIFY"),
MSEARCH("M-SEARCH"),
@@ -57,7 +58,7 @@ public String getHttpName() {
public static Method getByHttpName(String httpName) {
if (httpName == null) return UNKNOWN;
- Method m = byName.get(httpName.toUpperCase(Locale.ROOT));
+ Method m = byName.get(httpName.toUpperCase(Locale.ROOT));
return m != null ? m : UNKNOWN;
}
}
diff --git a/yaacc/src/main/res/values-de/setting_strings.xml b/yaacc/src/main/res/values-de/setting_strings.xml
index 28a6c783..653e87cc 100644
--- a/yaacc/src/main/res/values-de/setting_strings.xml
+++ b/yaacc/src/main/res/values-de/setting_strings.xml
@@ -42,6 +42,7 @@
Standardanzeigedauer
Pausendauer
Lokale Serverkonfiguration
+ UPnP-Server und Netzwerk
Lokaler Serverstatus
Lokaler server ist gestartet
Lokaler server ist gestoppt
@@ -111,4 +112,5 @@
Screencast-Stream
Die Bildschirmübertragung ist deaktiviert.
Bildschirmübertragung ist aktiviert
+ Sperrliste für die UPnP-Netzwerkschnittstellenerkennung
diff --git a/yaacc/src/main/res/values-de/strings.xml b/yaacc/src/main/res/values-de/strings.xml
index 4492b950..7cc47949 100644
--- a/yaacc/src/main/res/values-de/strings.xml
+++ b/yaacc/src/main/res/values-de/strings.xml
@@ -38,6 +38,7 @@
Während des Überfliegens ist ein Fehler aufgetreten
Es ist folgender Fehler aufgetreten:
Kein Empfänger gefunden
+ Es ist kein Empfängergerät ausgewählt. Bitte wählen Sie zuerst einen Empfänger im Reiter „Empfänger“ aus.
Keine Aktivität zum Abspielen vom Typ \'%1$s\' gefunden
Dauer der Bildanzeige
Keine gültigen Uri-Informationen zum Anzeigen gefunden
@@ -45,6 +46,7 @@
stoppe
pausiere
vorheriges
+ Bild
nächstes
Beenden
Albumbild
diff --git a/yaacc/src/main/res/values-es/setting_strings.xml b/yaacc/src/main/res/values-es/setting_strings.xml
index e322879e..dbd0b2f8 100644
--- a/yaacc/src/main/res/values-es/setting_strings.xml
+++ b/yaacc/src/main/res/values-es/setting_strings.xml
@@ -57,6 +57,7 @@
Configure la cantidad de subprocesos utilizados para cargar contenido desde el servidor UPnP. Más subprocesos provocan una carga más rápida pero aumentan la posibilidad de errores. Los cambios entran en vigor después del reinicio de yaacc.
no se encontró contenido para navegar
configuración del servidor local
+ Servidor UPnP y redes
estado del servidor local
se inicia el servidor local
el servidor local está detenido
@@ -111,4 +112,5 @@
transmisión de pantalla
La transmisión de pantalla está deshabilitada
La transmisión de pantalla está habilitada
+ Interfaces incluidas en la lista negra para la detección de interfaces de red UPnP
\ No newline at end of file
diff --git a/yaacc/src/main/res/values-es/strings.xml b/yaacc/src/main/res/values-es/strings.xml
index 48ab852b..ce92ee96 100644
--- a/yaacc/src/main/res/values-es/strings.xml
+++ b/yaacc/src/main/res/values-es/strings.xml
@@ -38,6 +38,7 @@
Ocurrió un error desconocido mientras navegaba
Se produjo el siguiente error:
No se encontró ningún dispositivo receptor
+ No se ha seleccionado ningún dispositivo receptor. Seleccione primero un receptor en la pestaña Receptor.
No se encontró actividad para jugar tipo MIME \'%1$s\'
Duración de la estancia de la foto
Configuración de YAACC
@@ -47,6 +48,7 @@
detener
pausa
Previo
+ Imagen
próximo
Salida
reproductor de música yaac
diff --git a/yaacc/src/main/res/values-fr/setting_strings.xml b/yaacc/src/main/res/values-fr/setting_strings.xml
index a73c6ee3..101519a5 100644
--- a/yaacc/src/main/res/values-fr/setting_strings.xml
+++ b/yaacc/src/main/res/values-fr/setting_strings.xml
@@ -57,6 +57,7 @@
Configurez le nombre de threads utilisés pour charger le contenu à partir du serveur UPnP. Plus de threads entraînent un chargement plus rapide mais augmentent le risque d\'erreurs. Les modifications prennent effet après le redémarrage de yaacc.
aucun contenu trouvé pour la navigation
configuration du serveur local
+ Serveur UPnP et réseau
état du serveur local
le serveur local est démarré
le serveur local est arrêté
@@ -111,4 +112,5 @@
flux de diffusion d\'écran
La diffusion d\'écran est désactivée.
La diffusion d\'écran est activée
+ interfaces mises sur liste noire pour la découverte d\'interfaces réseau UPnP
\ No newline at end of file
diff --git a/yaacc/src/main/res/values-fr/strings.xml b/yaacc/src/main/res/values-fr/strings.xml
index e38d7096..90b7f016 100644
--- a/yaacc/src/main/res/values-fr/strings.xml
+++ b/yaacc/src/main/res/values-fr/strings.xml
@@ -38,6 +38,7 @@
Une erreur inconnue s\'est produite lors de la navigation
L\'erreur suivante s\'est produite :
Aucun appareil récepteur trouvé
+ Aucun périphérique récepteur n\'est sélectionné. Veuillez d\'abord sélectionner un récepteur dans l\'onglet Récepteur.
Aucune activité trouvée pour la lecture du type MIME \'%1$s\'
Durée du séjour photo
Paramètres YAACC
@@ -47,6 +48,7 @@
arrêt
pause
précédent
+ Image
suivant
Sortie
Lecteur de musique YAACC
diff --git a/yaacc/src/main/res/values-nl/setting_strings.xml b/yaacc/src/main/res/values-nl/setting_strings.xml
index 7cd24ee3..40b6522a 100644
--- a/yaacc/src/main/res/values-nl/setting_strings.xml
+++ b/yaacc/src/main/res/values-nl/setting_strings.xml
@@ -57,6 +57,7 @@
Configureer het aantal threads dat wordt gebruikt voor het laden van inhoud van de UPnP-server. Meer threads zorgen voor sneller laden, maar vergroten de kans op fouten. Wijzigingen zijn van kracht na het opnieuw opstarten van yaacc.
geen inhoud gevonden om te browsen
lokale serverconfiguratie
+ UPnP-server en netwerken
lokale serverstatus
lokale server wordt gestart
lokale server is gestopt
@@ -111,4 +112,5 @@
schermcaststream
Schermcaststream is uitgeschakeld
Schermcaststream is ingeschakeld
+ Interfaces die op de zwarte lijst staan voor UPnP-netwerkinterfacedetectie
\ No newline at end of file
diff --git a/yaacc/src/main/res/values-nl/strings.xml b/yaacc/src/main/res/values-nl/strings.xml
index 0d591ccb..e5438286 100644
--- a/yaacc/src/main/res/values-nl/strings.xml
+++ b/yaacc/src/main/res/values-nl/strings.xml
@@ -38,6 +38,7 @@
Er is een onbekende fout opgetreden tijdens het browsen
De volgende fout is opgetreden:
Geen ontvangstapparaat gevonden
+ Er is geen ontvanger geselecteerd. Selecteer eerst een ontvanger in het tabblad Ontvanger.
Geen activiteit gevonden voor het spelen van mimetype \'%1$s\'
Duur van het beeldverblijf
YAACC-instellingen
@@ -47,6 +48,7 @@
stop
pauze
vorig
+ Afbeelding
volgende
Uitgang
YAACC-muziekspeler
diff --git a/yaacc/src/main/res/values-pt/setting_strings.xml b/yaacc/src/main/res/values-pt/setting_strings.xml
index 00361bd8..38e4e056 100644
--- a/yaacc/src/main/res/values-pt/setting_strings.xml
+++ b/yaacc/src/main/res/values-pt/setting_strings.xml
@@ -57,6 +57,7 @@
Configure o número de threads usados para carregar conteúdo do servidor UPnP. Mais threads causam um carregamento mais rápido, mas aumentam a possibilidade de erros. As alterações entram em vigor após a reinicialização do yaacc.
nenhum conteúdo encontrado para navegar
configuração do servidor local
+ Servidor UPnP e redes
estado do servidor local
o servidor local está iniciado
o servidor local está parado
@@ -111,4 +112,5 @@
fluxo de transmissão do ecrã
A transmissão de vídeo do ecrã está desativada.
A transmissão de vídeo do ecrã está ativada.
+ Interfaces bloqueadas para descoberta de interfaces de rede UPnP
\ No newline at end of file
diff --git a/yaacc/src/main/res/values-pt/strings.xml b/yaacc/src/main/res/values-pt/strings.xml
index c28d82f2..3e4e50e5 100644
--- a/yaacc/src/main/res/values-pt/strings.xml
+++ b/yaacc/src/main/res/values-pt/strings.xml
@@ -39,6 +39,7 @@
Ocorreu um erro desconhecido durante a navegação
Ocorreu o seguinte erro:
Nenhum dispositivo receptor encontrado
+ Nenhum dispositivo recetor selecionado. Selecione primeiro um recetor no separador Receptor.
Nenhuma atividade encontrada para jogar o tipo MIME \'%1$s\'
Duração da permanência da imagem
Configurações YAACC
@@ -48,6 +49,7 @@
parar
pausa
anterior
+ Imagem
próximo
Saída
Leitor de música YAACC
diff --git a/yaacc/src/main/res/values-zh/setting_strings.xml b/yaacc/src/main/res/values-zh/setting_strings.xml
index ee476729..94e743f2 100644
--- a/yaacc/src/main/res/values-zh/setting_strings.xml
+++ b/yaacc/src/main/res/values-zh/setting_strings.xml
@@ -57,6 +57,7 @@
配置用于加载来自 UPnP 服务器内容的线程数。线程数多加载速度快,但增加错误的可能性。重启 yaacc 后更改生效。
未找到浏览内容
本地服务器配置
+ UPnP 服务器和网络
本地服务器状态
已启动本地服务器
本地服务器已停止
@@ -111,4 +112,5 @@
屏幕投射流
屏幕投射流已禁用
屏幕投射流已启用
+ UPnP 網路介面發現的黑名單接口
diff --git a/yaacc/src/main/res/values-zh/strings.xml b/yaacc/src/main/res/values-zh/strings.xml
index b1407c0c..c9640df1 100644
--- a/yaacc/src/main/res/values-zh/strings.xml
+++ b/yaacc/src/main/res/values-zh/strings.xml
@@ -38,6 +38,7 @@
浏览时发生未知错误
出现下列错误:
未找到接收设备
+ 未選擇接收器設備。請先在「接收器」標籤中選擇接收器。
未找到播放 mimetype \'%1$s\' 的活动
图片停留时长
YAACC 设置
@@ -47,6 +48,7 @@
停止
暂停
上一个
+ 图像
下一个
退出
YAACC 音乐播放器
diff --git a/yaacc/src/main/res/values/setting_strings.xml b/yaacc/src/main/res/values/setting_strings.xml
index cbf977f9..aeeddbeb 100644
--- a/yaacc/src/main/res/values/setting_strings.xml
+++ b/yaacc/src/main/res/values/setting_strings.xml
@@ -69,7 +69,7 @@
audio_chkbx
video_chkbx
image_chkbx
- local server configuration
+ UPnP server and networking
local server state
local server is started
local server is stopped
@@ -141,8 +141,8 @@
Change tabs on touch
Change tabs on swipe and touch
Swipe on browse
- settings_local_server_if_filter_key
- black listed interfaces for ip address retrieval
+ settings_upnp_if_filter_key
+ blacklisted interfaces for UPnP network interface discovery
settings_media_paths_pref_key
settings_local_server_shutdown_timer_chkbx
settings_saf_tree_uris_pref_key
diff --git a/yaacc/src/main/res/values/strings.xml b/yaacc/src/main/res/values/strings.xml
index bf464c57..34c05b7b 100644
--- a/yaacc/src/main/res/values/strings.xml
+++ b/yaacc/src/main/res/values/strings.xml
@@ -133,4 +133,6 @@
PAUSED
STOPPED
NO MEDIA
+ interfaces blacklisted for obtaining IP addresses
+ local server configuration
diff --git a/yaacc/src/main/res/xml/preference.xml b/yaacc/src/main/res/xml/preference.xml
index fd154c06..b1c16812 100644
--- a/yaacc/src/main/res/xml/preference.xml
+++ b/yaacc/src/main/res/xml/preference.xml
@@ -111,7 +111,7 @@
android:key="@string/image_viewer_settings_duration_key"
android:title="@string/image_viewer_settings_duration" />
-
+
+ android:key="@string/settings_upnp_if_filter_key"
+ android:title="@string/settings_upnp_if_filter_names"
+ android:defaultValue="lo,dummy,rmnet,ccmni,tun" />