feat: prefer locally downloaded media vs server stream (#433)

resolves #404 and should address #285
This commit is contained in:
eddyizm 2026-02-11 21:31:46 -08:00 committed by GitHub
parent 3958cbcc1c
commit dbd32baa12
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 84 additions and 28 deletions

View file

@ -11,7 +11,7 @@ android {
targetSdk 35 targetSdk 35
versionCode 19 versionCode 19
versionName '4.10.1' versionName '4.10.2'
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
javaCompileOptions { javaCompileOptions {

View file

@ -7,6 +7,7 @@ import android.content.ServiceConnection;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -236,35 +237,57 @@ public class PlayerControllerFragment extends Fragment {
} }
private void setMediaInfo(MediaMetadata mediaMetadata) { private void setMediaInfo(MediaMetadata mediaMetadata) {
boolean isLocal = false;
if (mediaBrowserListenableFuture != null && mediaBrowserListenableFuture.isDone()) {
try {
MediaBrowser browser = mediaBrowserListenableFuture.get();
if (browser != null && browser.getCurrentMediaItem() != null) {
android.net.Uri currentUri = browser.getCurrentMediaItem().requestMetadata.mediaUri;
if (currentUri != null) {
String scheme = currentUri.getScheme();
isLocal = "content".equals(scheme) || "file".equals(scheme);
}
}
} catch (Exception e) {
Log.e("DEBUG_PLAYER", "Error getting browser for UI update", e);
}
}
if (mediaMetadata.extras != null) { if (mediaMetadata.extras != null) {
String extension = mediaMetadata.extras.getString("suffix", getString(R.string.player_unknown_format)); String extension = mediaMetadata.extras.getString("suffix", getString(R.string.player_unknown_format));
String bitrate = mediaMetadata.extras.getInt("bitrate", 0) != 0 ? mediaMetadata.extras.getInt("bitrate", 0) + "kbps" : "Original"; int rawBitrate = mediaMetadata.extras.getInt("bitrate", 0);
String samplingRate = mediaMetadata.extras.getInt("samplingRate", 0) != 0 ? new DecimalFormat("0.#").format(mediaMetadata.extras.getInt("samplingRate", 0) / 1000.0) + "kHz" : ""; String bitrate = rawBitrate != 0 ? rawBitrate + "kbps" : "Original";
String samplingRate = mediaMetadata.extras.getInt("samplingRate", 0) != 0 ?
new java.text.DecimalFormat("0.#").format(mediaMetadata.extras.getInt("samplingRate", 0) / 1000.0) + "kHz" : "";
String bitDepth = mediaMetadata.extras.getInt("bitDepth", 0) != 0 ? mediaMetadata.extras.getInt("bitDepth", 0) + "b" : ""; String bitDepth = mediaMetadata.extras.getInt("bitDepth", 0) != 0 ? mediaMetadata.extras.getInt("bitDepth", 0) + "b" : "";
playerMediaExtension.setText(extension); playerMediaExtension.setText(extension);
if (bitrate.equals("Original")) { if (bitrate.equals("Original") && !isLocal) {
playerMediaBitrate.setVisibility(View.GONE); playerMediaBitrate.setVisibility(View.GONE);
} else { } else {
List<String> mediaQualityItems = new ArrayList<>(); List<String> items = new ArrayList<>();
if (!bitrate.trim().isEmpty()) items.add(bitrate);
if (!bitDepth.trim().isEmpty()) items.add(bitDepth);
if (!samplingRate.trim().isEmpty()) items.add(samplingRate);
String mediaQuality = TextUtils.join("", items);
if (!bitrate.trim().isEmpty()) mediaQualityItems.add(bitrate);
if (!bitDepth.trim().isEmpty()) mediaQualityItems.add(bitDepth);
if (!samplingRate.trim().isEmpty()) mediaQualityItems.add(samplingRate);
String mediaQuality = TextUtils.join("", mediaQualityItems);
playerMediaBitrate.setVisibility(View.VISIBLE); playerMediaBitrate.setVisibility(View.VISIBLE);
playerMediaBitrate.setText(mediaQuality); playerMediaBitrate.setText(isLocal ? mediaQuality : mediaQuality);
} }
} }
if (!isLocal) {
boolean isTranscodingExtension = !MusicUtil.getTranscodingFormatPreference().equals("raw"); boolean isTranscodingExtension = !MusicUtil.getTranscodingFormatPreference().equals("raw");
boolean isTranscodingBitrate = !MusicUtil.getBitratePreference().equals("0"); boolean isTranscodingBitrate = !MusicUtil.getBitratePreference().equals("0");
if (isTranscodingExtension || isTranscodingBitrate) { if (isTranscodingExtension || isTranscodingBitrate) {
playerMediaExtension.setText(MusicUtil.getTranscodingFormatPreference() + " (" + getString(R.string.player_transcoding) + ")"); playerMediaExtension.setText(MusicUtil.getTranscodingFormatPreference() + " (" + getString(R.string.player_transcoding) + ")");
playerMediaBitrate.setText(!MusicUtil.getBitratePreference().equals("0") ? MusicUtil.getBitratePreference() + "kbps" : getString(R.string.player_transcoding_requested)); playerMediaBitrate.setText(!MusicUtil.getBitratePreference().equals("0") ?
MusicUtil.getBitratePreference() + "kbps" : getString(R.string.player_transcoding_requested));
}
} }
playerTrackInfo.setOnClickListener(view -> { playerTrackInfo.setOnClickListener(view -> {
@ -272,7 +295,6 @@ public class PlayerControllerFragment extends Fragment {
dialog.show(activity.getSupportFragmentManager(), null); dialog.show(activity.getSupportFragmentManager(), null);
}); });
} }
private void updateAssetLinkChips(MediaMetadata mediaMetadata) { private void updateAssetLinkChips(MediaMetadata mediaMetadata) {
if (assetLinkChipGroup == null) return; if (assetLinkChipGroup == null) return;
String mediaType = mediaMetadata.extras != null ? mediaMetadata.extras.getString("type", Constants.MEDIA_TYPE_MUSIC) : Constants.MEDIA_TYPE_MUSIC; String mediaType = mediaMetadata.extras != null ? mediaMetadata.extras.getString("type", Constants.MEDIA_TYPE_MUSIC) : Constants.MEDIA_TYPE_MUSIC;

View file

@ -288,13 +288,24 @@ public class MappingUtil {
} }
private static Uri getUri(Child media) { private static Uri getUri(Child media) {
// Check if it's in our local SQL Database
DownloadRepository repo = new DownloadRepository();
Download localDownload = repo.getDownload(media.getId());
if (localDownload != null && localDownload.getDownloadUri() != null && !localDownload.getDownloadUri().isEmpty()) {
Log.d(TAG, "Playing local file for: " + media.getTitle());
return Uri.parse(localDownload.getDownloadUri());
}
// Legacy check for external directory, i think this was broken/buggy
if (Preferences.getDownloadDirectoryUri() != null) { if (Preferences.getDownloadDirectoryUri() != null) {
Uri local = ExternalAudioReader.getUri(media); Uri local = ExternalAudioReader.getUri(media);
return local != null ? local : MusicUtil.getStreamUri(media.getId()); if (local != null) return local;
} }
return DownloadUtil.getDownloadTracker(App.getContext()).isDownloaded(media.getId())
? getDownloadUri(media.getId()) // Fallback to streaming
: MusicUtil.getStreamUri(media.getId()); Log.d(TAG, "No local file found. Streaming: " + media.getTitle());
return MusicUtil.getStreamUri(media.getId());
} }
private static Uri getUri(PodcastEpisode podcastEpisode) { private static Uri getUri(PodcastEpisode podcastEpisode) {

View file

@ -52,6 +52,10 @@ public class MusicUtil {
if (params.containsKey("c") && params.get("c") != null) if (params.containsKey("c") && params.get("c") != null)
uri.append("&c=").append(params.get("c")); uri.append("&c=").append(params.get("c"));
String selectedBitrate = getBitratePreference();
String selectedFormat = getTranscodingFormatPreference();
Log.i(TAG, "DEBUG: Requesting Format: " + selectedFormat + " at Bitrate: " + selectedBitrate);
if (!Preferences.isServerPrioritized()) if (!Preferences.isServerPrioritized())
uri.append("&maxBitRate=").append(getBitratePreference()); uri.append("&maxBitRate=").append(getBitratePreference());
if (!Preferences.isServerPrioritized()) if (!Preferences.isServerPrioritized())
@ -73,7 +77,17 @@ public class MusicUtil {
} }
public static Uri updateStreamUri(Uri uri) { public static Uri updateStreamUri(Uri uri) {
if (uri == null) return null;
String scheme = uri.getScheme();
// If it is local (content:// or file://), return it IMMEDIATELY.
// This prevents the code below from appending &maxBitRate to a local path.
if (scheme != null && (scheme.equals("content") || scheme.equals("file"))) {
return uri;
}
String s = uri.toString(); String s = uri.toString();
Matcher m1 = BITRATE_PATTERN.matcher(s); Matcher m1 = BITRATE_PATTERN.matcher(s);
s = m1.replaceAll(""); s = m1.replaceAll("");
Matcher m2 = FORMAT_PATTERN.matcher(s); Matcher m2 = FORMAT_PATTERN.matcher(s);
@ -157,7 +171,6 @@ public class MusicUtil {
return Uri.parse(uri.toString()); return Uri.parse(uri.toString());
} }
public static String getReadableDurationString(Long duration, boolean millis) { public static String getReadableDurationString(Long duration, boolean millis) {
long lenght = duration != null ? duration : 0; long lenght = duration != null ? duration : 0;
@ -303,13 +316,17 @@ public class MusicUtil {
if (network == null || networkCapabilities == null) return "raw"; if (network == null || networkCapabilities == null) return "raw";
String format;
if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
return Preferences.getAudioTranscodeFormatWifi(); format = Preferences.getAudioTranscodeFormatWifi();
Log.d(TAG, "DEBUG: Using WIFI Format: " + format);
} else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { } else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
return Preferences.getAudioTranscodeFormatMobile(); format = Preferences.getAudioTranscodeFormatMobile();
Log.d(TAG, "DEBUG: Using MOBILE Format: " + format);
} else { } else {
return Preferences.getAudioTranscodeFormatWifi(); format = Preferences.getAudioTranscodeFormatWifi();
} }
return format;
} }
public static String getBitratePreferenceForDownload() { public static String getBitratePreferenceForDownload() {

View file

@ -33,12 +33,18 @@ class TranscodingMediaSource(
init { init {
val extras = mediaItem.mediaMetadata.extras val extras = mediaItem.mediaMetadata.extras
if (extras != null && extras.containsKey("duration")) { val uri = mediaItem.localConfiguration?.uri
val isLocal = uri?.scheme == "content" || uri?.scheme == "file"
// Only apply the override if it's NOT a local file
if (!isLocal && extras != null && extras.containsKey("duration")) {
val seconds = extras.getInt("duration") val seconds = extras.getInt("duration")
if (seconds > 0) { if (seconds > 0) {
durationUs = Util.msToUs(seconds * 1000L) durationUs = Util.msToUs(seconds * 1000L)
} }
} }
currentSource = progressiveMediaSourceFactory.createMediaSource(mediaItem)
} }
override fun getMediaItem() = mediaItem override fun getMediaItem() = mediaItem