Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
reports
.kiro
doxygen
.bundle
Expand Down
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,15 @@ tobexyz
alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/de.yaacc/)

<img src="./docs/screenshots/4.4.x/browse_servers.png" alt= “” width="30%" height="30%"> <img src="./docs/screenshots/4.4.x/browse_image_folder.png" alt= “” width="30%" height="30%"> <img src="./docs/screenshots/4.4.x/browse_receiver.png" alt= “” width="30%" height="30%">
<img src="./docs/screenshots/4.2.x/music_player.png" alt= “” width="30%" height="30%"> <img src="./docs/screenshots/4.4.x/playlist_fully_editable.png" alt= “” width="30%" height="30%"> <img src="./docs/screenshots/4.4.x/image_player_show_menu.png" alt= “” width="30%" height="30%">

![Downloads last month](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgithub.com%2Fkitswas%2Ffdroid-metrics-dashboard%2Fraw%2Frefs%2Fheads%2Fmain%2Fprocessed%2Fmonthly%2Fde.yaacc.json&query=%24.total_downloads&logo=fdroid&label=Downloads%20last%20month)


![Downloads (all time)](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgithub.com%2Fkitswas%2Ffdroid-metrics-dashboard%2Fraw%2Frefs%2Fheads%2Fmain%2Fprocessed%2Ftotal%2Fde.yaacc.json&query=%24.total_downloads&logo=fdroid&label=Downloads%20(all%20time))


<img src="./docs/screenshots/5.0.x/browse_servers.png" alt= “” width="30%" height="30%"> <img src="./docs/screenshots/5.0.x/browse_image_folder.png" alt= “” width="30%" height="30%"> <img src="./docs/screenshots/5.0.x/browse_receiver.png" alt= “” width="30%" height="30%">
<img src="./docs/screenshots/5.0.x/music_player.png" alt= “” width="30%" height="30%"> <img src="./docs/screenshots/5.0.x/playlist_fully_editable.png" alt= “” width="30%" height="30%"> <img src="./docs/screenshots/5.0.x/image_player_show_menu.png" alt= “” width="30%" height="30%">

## Description

Expand All @@ -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

Expand Down
37 changes: 20 additions & 17 deletions docs/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions fastlane/metadata/android/en-US/changelogs/50001.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Version 5.0.1
- fixed crash on startup if no network device is found
- fixed problems with device lookup
4 changes: 2 additions & 2 deletions yaacc/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:versionCode="50000"
android:versionName="5.0.0">
android:versionCode="50001"
android:versionName="5.0.1">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
Expand Down
48 changes: 26 additions & 22 deletions yaacc/src/main/java/de/yaacc/browser/ServerListFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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(() -> {
Expand All @@ -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");
});
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand All @@ -370,26 +374,26 @@ 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();
}
} 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
9 changes: 8 additions & 1 deletion yaacc/src/main/java/de/yaacc/upnp/registry/LocalItems.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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);
Expand Down
Loading
Loading