diff --git a/.gitignore b/.gitignore index 9341bbee..521ffffe 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ build # Configuration local.properties -# IntelliJ +# Other *.iml .idea +.vs diff --git a/app/src/main/java/com/dkanada/gramophone/adapter/DirectPlayCodecAdapter.java b/app/src/main/java/com/dkanada/gramophone/adapter/DirectPlayCodecAdapter.java new file mode 100644 index 00000000..a6e0c6dd --- /dev/null +++ b/app/src/main/java/com/dkanada/gramophone/adapter/DirectPlayCodecAdapter.java @@ -0,0 +1,65 @@ +package com.dkanada.gramophone.adapter; + +import android.annotation.SuppressLint; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.dkanada.gramophone.R; +import com.dkanada.gramophone.model.DirectPlayCodec; + +import java.util.List; + +public class DirectPlayCodecAdapter extends RecyclerView.Adapter { + private List directPlayCodecs; + + public DirectPlayCodecAdapter(List directPlayCodecs) { + this.directPlayCodecs = directPlayCodecs; + } + + @Override + @NonNull + public DirectPlayCodecAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.preference_dialog_direct_play_codecs_listitem, parent, false); + return new ViewHolder(view); + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public void onBindViewHolder(@NonNull DirectPlayCodecAdapter.ViewHolder holder, int position) { + DirectPlayCodec directPlayCodec = directPlayCodecs.get(position); + + holder.checkBox.setChecked(directPlayCodec.selected); + holder.title.setText(directPlayCodec.title); + + holder.itemView.setOnClickListener(v -> { + directPlayCodec.selected = !directPlayCodec.selected; + holder.checkBox.setChecked(directPlayCodec.selected); + }); + } + + @Override + public int getItemCount() { + return directPlayCodecs.size(); + } + + public List getDirectPlayCodecs() { + return directPlayCodecs; + } + + static class ViewHolder extends RecyclerView.ViewHolder { + public CheckBox checkBox; + public TextView title; + + public ViewHolder(View view) { + super(view); + checkBox = view.findViewById(R.id.checkbox); + title = view.findViewById(R.id.title); + } + } +} diff --git a/app/src/main/java/com/dkanada/gramophone/model/DirectPlayCodec.java b/app/src/main/java/com/dkanada/gramophone/model/DirectPlayCodec.java new file mode 100644 index 00000000..f19f21ac --- /dev/null +++ b/app/src/main/java/com/dkanada/gramophone/model/DirectPlayCodec.java @@ -0,0 +1,31 @@ +package com.dkanada.gramophone.model; + +public class DirectPlayCodec { + public String codecName; + public String title; + public String value; + public boolean selected; + + public DirectPlayCodec(String codecName, String title, String value, boolean selected) { + this.codecName = codecName; + this.title = title; + this.value = value; + this.selected = selected; + } + + public enum Codec { + FLAC("FLAC","flac|flac"), + MP3("MP3", "mp3|mp3"), + AAC("AAC", "m4a|aac"), + OGG("OGG", "ogg|vorbis"), + MKA("MKA", "mka|opus"); + + public final String title; + public final String value; + + Codec(String title, String value) { + this.value = value; + this.title = title; + } + } +} diff --git a/app/src/main/java/com/dkanada/gramophone/preferences/DirectPlayPreference.java b/app/src/main/java/com/dkanada/gramophone/preferences/DirectPlayPreference.java new file mode 100644 index 00000000..5071f3d5 --- /dev/null +++ b/app/src/main/java/com/dkanada/gramophone/preferences/DirectPlayPreference.java @@ -0,0 +1,24 @@ +package com.dkanada.gramophone.preferences; + +import android.content.Context; +import android.util.AttributeSet; + +import com.kabouzeid.appthemehelper.common.prefs.supportv7.ATEDialogPreference; + +public class DirectPlayPreference extends ATEDialogPreference { + public DirectPlayPreference(Context context) { + super(context); + } + + public DirectPlayPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public DirectPlayPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public DirectPlayPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } +} diff --git a/app/src/main/java/com/dkanada/gramophone/preferences/DirectPlayPreferenceDialog.java b/app/src/main/java/com/dkanada/gramophone/preferences/DirectPlayPreferenceDialog.java new file mode 100644 index 00000000..1ea1e92e --- /dev/null +++ b/app/src/main/java/com/dkanada/gramophone/preferences/DirectPlayPreferenceDialog.java @@ -0,0 +1,48 @@ +package com.dkanada.gramophone.preferences; + +import android.app.Dialog; +import android.os.Bundle; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.fragment.app.DialogFragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.afollestad.materialdialogs.MaterialDialog; +import com.dkanada.gramophone.R; +import com.dkanada.gramophone.adapter.DirectPlayCodecAdapter; +import com.dkanada.gramophone.util.PreferenceUtil; + +public class DirectPlayPreferenceDialog extends DialogFragment { + public static DirectPlayPreferenceDialog newInstance() { + return new DirectPlayPreferenceDialog(); + } + + private DirectPlayCodecAdapter adapter; + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + View view = getActivity().getLayoutInflater().inflate(R.layout.preference_dialog_direct_play_codecs, null); + + adapter = new DirectPlayCodecAdapter(PreferenceUtil.getInstance(getContext()).getDirectPlayCodecs()); + + RecyclerView recyclerView = view.findViewById(R.id.recycler_view); + recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); + recyclerView.setAdapter(adapter); + + return new MaterialDialog.Builder(getContext()) + .title(R.string.direct_play_codecs) + .customView(view, false) + .positiveText(android.R.string.ok) + .negativeText(android.R.string.cancel) + .autoDismiss(false) + .onNegative((dialog, action) -> dismiss()) + .onPositive((dialog, action) -> { + PreferenceUtil.getInstance(getContext()).setDirectPlayCodecs(adapter.getDirectPlayCodecs()); + dismiss(); + }) + .build(); + } +} diff --git a/app/src/main/java/com/dkanada/gramophone/ui/activities/SettingsActivity.java b/app/src/main/java/com/dkanada/gramophone/ui/activities/SettingsActivity.java index f9c00b6f..7c5741fc 100644 --- a/app/src/main/java/com/dkanada/gramophone/ui/activities/SettingsActivity.java +++ b/app/src/main/java/com/dkanada/gramophone/ui/activities/SettingsActivity.java @@ -16,6 +16,8 @@ import android.view.MenuItem; import android.view.View; import com.afollestad.materialdialogs.color.ColorChooserDialog; +import com.dkanada.gramophone.preferences.DirectPlayPreference; +import com.dkanada.gramophone.preferences.DirectPlayPreferenceDialog; import com.kabouzeid.appthemehelper.ThemeStore; import com.kabouzeid.appthemehelper.common.prefs.supportv7.ATEColorPreference; import com.kabouzeid.appthemehelper.common.prefs.supportv7.ATEPreferenceFragmentCompat; @@ -122,6 +124,7 @@ public class SettingsActivity extends AbsBaseActivity implements ColorChooserDia addPreferencesFromResource(R.xml.pref_lockscreen); addPreferencesFromResource(R.xml.pref_audio); addPreferencesFromResource(R.xml.pref_images); + addPreferencesFromResource(R.xml.pref_direct_play); } @Nullable @@ -131,6 +134,8 @@ public class SettingsActivity extends AbsBaseActivity implements ColorChooserDia return NowPlayingScreenPreferenceDialog.newInstance(); } else if (preference instanceof LibraryPreference) { return LibraryPreferenceDialog.newInstance(); + } else if (preference instanceof DirectPlayPreference) { + return DirectPlayPreferenceDialog.newInstance(); } return super.onCreatePreferenceDialog(preference); diff --git a/app/src/main/java/com/dkanada/gramophone/util/MusicUtil.java b/app/src/main/java/com/dkanada/gramophone/util/MusicUtil.java index 0c62c8b0..593886c9 100644 --- a/app/src/main/java/com/dkanada/gramophone/util/MusicUtil.java +++ b/app/src/main/java/com/dkanada/gramophone/util/MusicUtil.java @@ -14,6 +14,7 @@ import com.dkanada.gramophone.App; import com.dkanada.gramophone.R; import com.dkanada.gramophone.model.Album; import com.dkanada.gramophone.model.Artist; +import com.dkanada.gramophone.model.DirectPlayCodec; import com.dkanada.gramophone.model.Genre; import com.dkanada.gramophone.model.Song; @@ -26,25 +27,44 @@ import java.util.Locale; public class MusicUtil { public static Uri getSongFileUri(Song song) { - StringBuilder builder = new StringBuilder(); + PreferenceUtil preferenceUtil = PreferenceUtil.getInstance(App.getInstance()); + + StringBuilder builder = new StringBuilder(256); ApiClient apiClient = App.getApiClient(); builder.append(apiClient.getApiUrl()); builder.append("/Audio/"); builder.append(song.id); builder.append("/universal"); - builder.append("?UserId=" + apiClient.getCurrentUserId()); - builder.append("&DeviceId=" + apiClient.getDeviceId()); + builder.append("?UserId=").append(apiClient.getCurrentUserId()); + builder.append("&DeviceId=").append(apiClient.getDeviceId()); // web client maximum is 12444445 and 320kbps is 320000 - builder.append("&MaxStreamingBitrate=" + PreferenceUtil.getInstance(App.getInstance()).getMaximumBitrate()); - builder.append("&Container=flac"); + builder.append("&MaxStreamingBitrate=").append(preferenceUtil.getMaximumBitrate()); + + boolean containerAdded = false; + for (DirectPlayCodec directPlayCodec : preferenceUtil.getDirectPlayCodecs()) { + if (directPlayCodec.selected) { + if (!containerAdded) { + builder.append("&Container="); + containerAdded = true; + } + + builder.append(directPlayCodec.value).append(','); + } + } + + if (containerAdded) { + // remove last comma + builder.deleteCharAt(builder.length() - 1); + } + builder.append("&TranscodingContainer=ts"); builder.append("&TranscodingProtocol=hls"); // preferred codec when transcoding - builder.append("&AudioCodec=" + PreferenceUtil.getInstance(App.getInstance()).getTranscodeCodec()); - builder.append("&api_key=" + apiClient.getAccessToken()); + builder.append("&AudioCodec=").append(preferenceUtil.getTranscodeCodec()); + builder.append("&api_key=").append(apiClient.getAccessToken()); Log.i(MusicUtil.class.getName(), "playing audio: " + builder); return Uri.parse(builder.toString()); diff --git a/app/src/main/java/com/dkanada/gramophone/util/PreferenceUtil.java b/app/src/main/java/com/dkanada/gramophone/util/PreferenceUtil.java index 9ba7d58b..3255576c 100644 --- a/app/src/main/java/com/dkanada/gramophone/util/PreferenceUtil.java +++ b/app/src/main/java/com/dkanada/gramophone/util/PreferenceUtil.java @@ -4,6 +4,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.os.Build; import android.preference.PreferenceManager; + import androidx.annotation.StyleRes; import com.google.gson.Gson; @@ -14,14 +15,18 @@ import com.dkanada.gramophone.R; import com.dkanada.gramophone.helper.sort.SortMethod; import com.dkanada.gramophone.helper.sort.SortOrder; import com.dkanada.gramophone.model.CategoryInfo; +import com.dkanada.gramophone.model.DirectPlayCodec; import com.dkanada.gramophone.ui.fragments.player.NowPlayingScreen; import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; public final class PreferenceUtil { public static final String CATEGORIES = "library_categories"; + public static final String DIRECT_PLAY_CODECS = "direct_play_codecs"; public static final String MAXIMUM_LIST_SIZE = "maximum_list_size"; public static final String REMEMBER_LAST_TAB = "remember_last_tab"; public static final String LAST_TAB = "last_tab"; @@ -417,4 +422,37 @@ public final class PreferenceUtil { defaultCategories.add(new CategoryInfo(CategoryInfo.Category.PLAYLISTS, true)); return defaultCategories; } + + public List getDirectPlayCodecs() { + DirectPlayCodec.Codec[] codecs = DirectPlayCodec.Codec.values(); + + Set selectedCodecNames = new HashSet<>(); + for (DirectPlayCodec.Codec codec : codecs) { + selectedCodecNames.add(codec.name()); + } + + selectedCodecNames = mPreferences.getStringSet(DIRECT_PLAY_CODECS, selectedCodecNames); + + ArrayList directPlayCodecs = new ArrayList<>(); + for (DirectPlayCodec.Codec codec : codecs) { + String name = codec.name(); + boolean selected = selectedCodecNames.contains(name); + directPlayCodecs.add(new DirectPlayCodec(name, codec.title, codec.value, selected)); + } + + return directPlayCodecs; + } + + public void setDirectPlayCodecs(List directPlayCodecs) { + Set codecNames = new HashSet<>(); + for (DirectPlayCodec directPlayCodec : directPlayCodecs) { + if (directPlayCodec.selected) { + codecNames.add(directPlayCodec.codecName); + } + } + + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putStringSet(DIRECT_PLAY_CODECS, codecNames); + editor.apply(); + } } diff --git a/app/src/main/res/layout/preference_dialog_direct_play_codecs.xml b/app/src/main/res/layout/preference_dialog_direct_play_codecs.xml new file mode 100644 index 00000000..e3f54417 --- /dev/null +++ b/app/src/main/res/layout/preference_dialog_direct_play_codecs.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/app/src/main/res/layout/preference_dialog_direct_play_codecs_listitem.xml b/app/src/main/res/layout/preference_dialog_direct_play_codecs_listitem.xml new file mode 100644 index 00000000..436681e2 --- /dev/null +++ b/app/src/main/res/layout/preference_dialog_direct_play_codecs_listitem.xml @@ -0,0 +1,42 @@ + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 89411c36..45d05fa8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -93,11 +93,13 @@ Colors Now Playing Audio + Direct Play Images Library Lockscreen Notification + Direct Play Codecs Categories Primary Color Accent Color @@ -135,6 +137,7 @@ Colors the app shortcuts in the primary color. Go to the last opened tab on launch. Configure visibility and order of display categories. + Disable direct play codecs to force transcoding. Delete Remove diff --git a/app/src/main/res/xml/pref_direct_play.xml b/app/src/main/res/xml/pref_direct_play.xml new file mode 100644 index 00000000..53d1ad56 --- /dev/null +++ b/app/src/main/res/xml/pref_direct_play.xml @@ -0,0 +1,15 @@ + + + + + + + + + +