diff --git a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt index d1b7199f8..b89cfc9e4 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -192,7 +192,7 @@ class AlbumDetailFragment : override fun onShuffle() { playbackModel.shuffle(unlikelyToBeNull(detailModel.currentAlbum.value)) } - + override fun onOpenSortMenu() { findNavController().navigateSafe(AlbumDetailFragmentDirections.sort()) } diff --git a/app/src/main/java/org/oxycblt/auxio/list/sort/SortAdapter.kt b/app/src/main/java/org/oxycblt/auxio/list/sort/SortAdapter.kt new file mode 100644 index 000000000..16ac05fd2 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/list/sort/SortAdapter.kt @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2023 Auxio Project + * SortAdapter.kt is part of Auxio. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.auxio.list.sort + +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import org.oxycblt.auxio.databinding.ItemSortModeBinding +import org.oxycblt.auxio.list.Sort +import org.oxycblt.auxio.list.adapter.FlexibleListAdapter +import org.oxycblt.auxio.util.inflater + +class SortAdapter(var selectedMode: Sort.Mode) : + FlexibleListAdapter(SortModeViewHolder.DIFF_CALLBACK) { + var currentlySelected = selectedMode + private set + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = + SortModeViewHolder.from(parent) + + override fun onBindViewHolder(holder: SortModeViewHolder, position: Int) { + throw NotImplementedError() + } + + override fun onBindViewHolder(holder: SortModeViewHolder, position: Int, payload: List) { + val mode = getItem(position) + if (payload.isEmpty()) { + holder.bind(mode) + } + holder.setSelected(mode == currentlySelected) + } + + fun setSelected(mode: Sort.Mode) { + if (mode == currentlySelected) return + val oldMode = currentList.indexOf(currentlySelected) + val newMode = currentList.indexOf(mode) + currentlySelected = selectedMode + notifyItemChanged(oldMode, PAYLOAD_SELECTION_CHANGED) + notifyItemChanged(newMode, PAYLOAD_SELECTION_CHANGED) + } + + private companion object { + val PAYLOAD_SELECTION_CHANGED = Any() + } +} + +class SortModeViewHolder private constructor(private val binding: ItemSortModeBinding) : + RecyclerView.ViewHolder(binding.root) { + fun bind(mode: Sort.Mode) { + // TODO: Add names to sort.mode + binding.sortRadio.text = mode.toString() + } + + fun setSelected(selected: Boolean) { + binding.sortRadio.isChecked = selected + } + + companion object { + fun from(parent: View) = + SortModeViewHolder(ItemSortModeBinding.inflate(parent.context.inflater)) + + val DIFF_CALLBACK = + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Sort.Mode, newItem: Sort.Mode) = + oldItem == newItem + + override fun areContentsTheSame(oldItem: Sort.Mode, newItem: Sort.Mode) = + oldItem == newItem + } + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/list/sort/SortDialog.kt b/app/src/main/java/org/oxycblt/auxio/list/sort/SortDialog.kt index 39e14d199..f6391ce87 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/sort/SortDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/sort/SortDialog.kt @@ -1,6 +1,6 @@ /* * Copyright (c) 2023 Auxio Project - * SortDialog.kt is part of Auxio. + * MenuDialogFragment.kt is part of Auxio. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,74 +16,126 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.list.sort +package org.oxycblt.auxio.list.menu +import android.annotation.SuppressLint import android.os.Bundle import android.view.LayoutInflater -import androidx.core.view.updatePadding +import android.view.MenuInflater +import android.view.MenuItem +import androidx.appcompat.view.menu.MenuBuilder +import androidx.core.view.children +import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.RecyclerView -import org.oxycblt.auxio.R -import org.oxycblt.auxio.databinding.DialogSortBinding +import org.oxycblt.auxio.databinding.DialogMenuBinding import org.oxycblt.auxio.list.ClickableListListener -import org.oxycblt.auxio.list.Sort +import org.oxycblt.auxio.list.ListViewModel +import org.oxycblt.auxio.list.Menu import org.oxycblt.auxio.list.adapter.UpdateInstructions import org.oxycblt.auxio.ui.ViewBindingBottomSheetDialogFragment -import org.oxycblt.auxio.util.systemBarInsetsCompat +import org.oxycblt.auxio.util.collectImmediately +import org.oxycblt.auxio.util.logD -abstract class SortDialog : - ViewBindingBottomSheetDialogFragment(), ClickableListListener { - private val modeAdapter = SortModeAdapter(this) +/** + * A [ViewBindingBottomSheetDialogFragment] that displays basic music information and a series of + * options. + * + * @author Alexander Capehart (OxygenCobalt) + * + * TODO: Extend the amount of music info shown in the dialog + */ +abstract class MenuDialogFragment : + ViewBindingBottomSheetDialogFragment(), ClickableListListener { + protected abstract val menuModel: MenuViewModel + protected abstract val listModel: ListViewModel + private val menuAdapter = MenuItemAdapter(@Suppress("LeakingThis") this) - abstract fun getInitialSort(): Sort + abstract val parcel: Menu.Parcel - abstract fun applyChosenSort(sort: Sort) + /** + * Get the options to disable in the context of the currently shown [M]. + * + * @param menu The currently-shown menu [M]. + */ + abstract fun getDisabledItemIds(menu: M): Set - abstract fun getModeChoices(): List + /** + * Update the displayed information about the currently shown [M]. + * + * @param binding The [DialogMenuBinding] to bind information to. + * @param menu The currently-shown menu [M]. + */ + abstract fun updateMenu(binding: DialogMenuBinding, menu: M) - override fun onCreateBinding(inflater: LayoutInflater) = DialogSortBinding.inflate(inflater) + /** + * Forward the clicked [MenuItem] to it's corresponding handler in another module. + * + * @param item The [MenuItem] that was clicked. + * @param menu The currently-shown menu [M]. + */ + abstract fun onClick(item: MenuItem, menu: M) - override fun onBindingCreated(binding: DialogSortBinding, savedInstanceState: Bundle?) { + override fun onCreateBinding(inflater: LayoutInflater) = DialogMenuBinding.inflate(inflater) + + override fun onBindingCreated(binding: DialogMenuBinding, savedInstanceState: Bundle?) { super.onBindingCreated(binding, savedInstanceState) // --- UI SETUP --- - binding.root.setOnApplyWindowInsetsListener { v, insets -> - v.updatePadding(bottom = insets.systemBarInsetsCompat.bottom) - insets + binding.menuName.isSelected = true + binding.menuInfo.isSelected = true + binding.menuOptionRecycler.apply { + adapter = menuAdapter + itemAnimator = null } - binding.sortModeRecycler.adapter = modeAdapter + // --- VIEWMODEL SETUP --- + listModel.menu.consume() + menuModel.setMenu(parcel) + collectImmediately(menuModel.currentMenu, this::updateMenu) + } - binding.sortCancel.setOnClickListener { dismiss() } + override fun onDestroyBinding(binding: DialogMenuBinding) { + super.onDestroyBinding(binding) + binding.menuName.isSelected = false + binding.menuInfo.isSelected = false + binding.menuOptionRecycler.adapter = null + } - binding.sortSave.setOnClickListener { - val initial = getInitialSort() - // FIXME: This won't work for the playlist sort dialog. - val mode = modeAdapter.currentMode ?: initial.mode - val direction = - when (binding.sortDirectionGroup.checkedButtonId) { - R.id.sort_direction_asc -> Sort.Direction.ASCENDING - R.id.sort_direction_dsc -> Sort.Direction.DESCENDING - else -> initial.direction - } - applyChosenSort(Sort(mode, direction)) - dismiss() + private fun updateMenu(menu: Menu?) { + if (menu == null) { + logD("No menu to show, navigating away") + findNavController().navigateUp() + return } - // --- STATE SETUP --- - val initial = getInitialSort() + @Suppress("UNCHECKED_CAST") val casted = menu as? M + check(casted != null) { "Unexpected menu instance ${menu::class.simpleName}" } - modeAdapter.update(getModeChoices(), UpdateInstructions.Diff) - modeAdapter.setSelected(initial.mode) + // We need to inflate the menu on every menu update since it might have changed + // what options are available (ex. if an artist with no songs has had new songs added). + // Since we don't have (and don't want) a dummy view to inflate this menu, just + // depend on the AndroidX Toolbar internal API and hope for the best. + @SuppressLint("RestrictedApi") val builder = MenuBuilder(requireContext()) + MenuInflater(requireContext()).inflate(casted.res, builder) - val directionId = - when (initial.direction) { - Sort.Direction.ASCENDING -> R.id.sort_direction_asc - Sort.Direction.DESCENDING -> R.id.sort_direction_dsc + // Disable any menu options as specified by the impl + val disabledIds = getDisabledItemIds(casted) + val visible = + builder.children.mapTo(mutableListOf()) { + it.isEnabled = !disabledIds.contains(it.itemId) + it } - binding.sortDirectionGroup.check(directionId) + menuAdapter.update(visible, UpdateInstructions.Diff) + + // Delegate to impl how to show music + updateMenu(requireBinding(), casted) } - override fun onClick(item: Sort.Mode, viewHolder: RecyclerView.ViewHolder) { - modeAdapter.setSelected(item) + final override fun onClick(item: MenuItem, viewHolder: RecyclerView.ViewHolder) { + // All option selections close the dialog currently. + // TODO: This should change if the app is 100% migrated to menu dialogs + findNavController().navigateUp() + // Delegate to impl on how to handle items + @Suppress("UNCHECKED_CAST") onClick(item, menuModel.currentMenu.value as M) } -} +} \ No newline at end of file diff --git a/app/src/main/res/navigation/inner.xml b/app/src/main/res/navigation/inner.xml index a7d0f08bb..a980bf595 100644 --- a/app/src/main/res/navigation/inner.xml +++ b/app/src/main/res/navigation/inner.xml @@ -210,6 +210,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index c07b03dc7..c2e376431 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -303,4 +303,5 @@ Recorta todas las portadas de los álbumes a una relación de aspecto 1:1 Canción Vista + Reproducir la canción por tí mismo \ No newline at end of file diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 93cb194d7..1f222e0a7 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -271,4 +271,6 @@ Lataa musiikkikirjasto uudelleen, käytä välimuistissa olevia tunnisteita kun mahdollista Rajaa kaikki albumikannet 1:1-suhteeseen Tyhjennä tunnistevälimuisti ja lataa musiikkikirjasto kokonaan uudelleen (hitaampi mutta kattavampi) + Kappale + Näytä \ No newline at end of file diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index bb80b279f..e34f480f0 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -187,7 +187,7 @@ स्लैश (/) -%.1f dB संपादन %s - Plus (+) + पलॅस (+) ऐंपरसैंड (&) मोड एल्बम कवर @@ -298,4 +298,5 @@ गुलाबी बुद्धिमान छंटाई संख्याओं या \"the\" जैसे शब्दों से शुरू होने वाले नामों को सही ढंग से क्रमबद्ध करें (अंग्रेजी भाषा के संगीत के साथ सबसे अच्छा काम करता है) + इसी गीत को चलाएं \ No newline at end of file diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index f40489591..caa995e31 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -9,7 +9,7 @@ Pavadinimas Metai Trukmė - Dainų skaičius + Dainos skaičius Diskas Pridėta data Didėjantis @@ -48,7 +48,7 @@ Naudoti grynai juodą tamsią temą Paprastas, racionalus Android muzikos grotuvas. Muzika kraunama - Peržiūrėti ir valdyti muzikos grojimą + Peržiūrėk ir valdyk muzikos grojimą Žanrai Pakartoti Suteikti @@ -74,8 +74,8 @@ Stebėjimas muzikos biblioteka Stebima tavo muzikos biblioteko dėl pakeitimų… Maišyti - Išmaišyti viską - Būsena atkurta + Maišyti viską + Atkurta būsena Išsaugota būsena Atšaukti Šaltinio kodas @@ -203,7 +203,7 @@ Nepavyko atkurti būsenos ReplayGain išankstinis stiprintuvas Išsaugoti grojimo būseną - Tvarkyk, kur muzika turėtų būti įkeliama iš + Tvarkyti, kur muzika turėtų būti įkeliama iš Žanro vaizdas %s Įjungti maišymą arba išjungti Takelis %d @@ -257,10 +257,10 @@ Nustatyti iš naujo Biblioteka Elgesys - Pakeisti programos temą ir spalvas + Pakeisk programos temą ir spalvas Valdyk, kaip muzika ir vaizdai įkeliami Konfigūruok garso ir grojimo elgesį - Pritaikyti UI valdiklius ir elgseną + Pritaikyk UI valdiklius ir elgseną Muzika Vaizdai Grojimas @@ -275,10 +275,10 @@ Grojaraščio vaizdas %s Sukurti naują grojaraštį Naujas grojaraštis - Įtraukti į grojaraštį + Pridėti į grojaraštį Pridėta į grojaraštį Ištrinti - Ištrinti %s\? To negalima atšaukti. + Ištrinti %s\? To negalima atkurti. Pervadinti Pervadinti grojaraštį Ištrinti grojaraštį\? diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index 8f748482d..0c83a6bfa 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -291,4 +291,5 @@ ਸਾਰੇ ਐਲਬਮ ਕਵਰਾਂ ਨੂੰ 1:1 ਦੇ ਆਕਾਰ ਅਨੁਪਾਤ ਤੱਕ ਕਾਂਟ-ਛਾਂਟ ਕਰੋ ਗੀਤ ਵੇਖੋ + ਇਸੇ ਗੀਤ ਨੂੰ ਚਲਾਓ \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index b4e179c74..a472dc577 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -302,4 +302,7 @@ Edytowanie %s Przytnij okładki do formatu 1:1 Wymuś kwadratowe okładki + Piosenka + Odtwarzanie utworu samodzielnie + Widok \ No newline at end of file diff --git a/fastlane/metadata/android/hi/full_description.txt b/fastlane/metadata/android/hi/full_description.txt index b22a5b7c4..2c5fdb04a 100644 --- a/fastlane/metadata/android/hi/full_description.txt +++ b/fastlane/metadata/android/hi/full_description.txt @@ -1,9 +1,9 @@ -Auxio एक तेज़, विश्वसनीय UI/UX वाला एक स्थानीय संगीत प्लेयर है, जिसमें अन्य संगीत प्लेयर में मौजूद कई बेकार सुविधाएँ नहीं हैं। एक्सोप्लेयर से निर्मित, औक्सियो में पुराने एंड्रॉइड कार्यक्षमता का उपयोग करने वाले अन्य ऐप्स की तुलना में बेहतर पुस्तकालय समर्थन और सुनने की गुणवत्ता है। संक्षेप में, +Auxio एक तेज़, विश्वसनीय UI/UX वाला एक स्थानीय संगीत प्लेयर है, जिसमें अन्य संगीत प्लेयर में मौजूद कई बेकार सुविधाएँ नहीं हैं। आधुनिक मीडिया प्लेबैक लाइब्रेरीओं से निर्मित, औक्सियो में पुराने एंड्रॉइड कार्यक्षमता का उपयोग करने वाले अन्य ऐप्स की तुलना में बेहतर पुस्तकालय समर्थन और सुनने की गुणवत्ता है। संक्षेप में, यह संगीत बजाता है. विशेषताएं -- ExoPlayer-आधारित प्लेबैक +- मीडिया3 एक्सोप्लेयर आधारित प्लेबैक - नवीनतम मटीरियल डिज़ाइन दिशानिर्देशों से प्राप्त स्नैपी UI - ओपिनियनेटेड UX जो ओवर एज केस के उपयोग को प्राथमिकता देता है - अनुकूलन योग्य व्यवहार @@ -13,7 +13,7 @@ Auxio एक तेज़, विश्वसनीय UI/UX वाला एक - विश्वसनीय प्लेलिस्टिंग कार्यक्षमता - प्लेबैक अवस्था दृढ़ता - पूर्ण रीप्लेगैन समर्थन (MP3, FLAC, OGG, OPUS और MP4 फ़ाइलों पर) -- बाहरी तुल्यकारक समर्थन (उदा। वेवलेट) +- बाहरी तुल्यकारक समर्थन (उदा: वेवलेट) - एज-टू-एज - एंबेडेड कवर समर्थन - खोज कार्यक्षमता diff --git a/fastlane/metadata/android/pa/full_description.txt b/fastlane/metadata/android/pa/full_description.txt index d4b927d7b..c98396ffa 100644 --- a/fastlane/metadata/android/pa/full_description.txt +++ b/fastlane/metadata/android/pa/full_description.txt @@ -1,22 +1,23 @@ -Auxio ਇੱਕ ਤੇਜ਼, ਭਰੋਸੇਮੰਦ UI/UX ਵਾਲਾ ਇੱਕ ਸਥਾਨਕ ਸੰਗੀਤ ਪਲੇਅਰ ਹੈ ਜੋ ਦੂਜੇ ਸੰਗੀਤ ਪਲੇਅਰਾਂ ਵਿੱਚ ਮੌਜੂਦ ਬਹੁਤ ਸਾਰੀਆਂ ਬੇਕਾਰ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਤੋਂ ਬਿਨਾਂ ਹੈ। Exoplayer ਤੋਂ ਬਣਿਆ, Auxio ਕੋਲ ਪੁਰਾਣੀ ਐਂਡਰੌਇਡ ਕਾਰਜਕੁਸ਼ਲਤਾ ਦੀ ਵਰਤੋਂ ਕਰਨ ਵਾਲੀਆਂ ਹੋਰ ਐਪਾਂ ਦੇ ਮੁਕਾਬਲੇ ਵਧੀਆ ਲਾਇਬ੍ਰੇਰੀ ਸਹਾਇਤਾ ਅਤੇ ਸੁਣਨ ਦੀ ਗੁਣਵੱਤਾ ਹੈ। ਸੰਖੇਪ ਵਿੱਚ, ਇਹ ਸੰਗੀਤ ਚਲਾਉਂਦਾ ਹੈ. +Auxio ਇੱਕ ਤੇਜ਼, ਭਰੋਸੇਮੰਦ UI/UX ਵਾਲਾ ਇੱਕ ਸਥਾਨਕ ਸੰਗੀਤ ਪਲੇਅਰ ਹੈ ਜੋ ਦੂਜੇ ਸੰਗੀਤ ਪਲੇਅਰਾਂ ਵਿੱਚ ਮੌਜੂਦ ਬਹੁਤ ਸਾਰੀਆਂ ਬੇਕਾਰ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਤੋਂ ਬਿਨਾਂ ਹੈ। ਆਧੁਨਿਕ ਮੀਡੀਆ ਪਲੇਬੈਕ ਲਾਇਬ੍ਰੇਰੀਆਂ ਤੋਂ ਬਣਿਆ, Auxio ਕੋਲ ਪੁਰਾਣੀ ਐਂਡਰੌਇਡ ਕਾਰਜਕੁਸ਼ਲਤਾ ਦੀ ਵਰਤੋਂ ਕਰਨ ਵਾਲੀਆਂ ਹੋਰ ਐਪਾਂ ਦੇ ਮੁਕਾਬਲੇ ਵਧੀਆ ਲਾਇਬ੍ਰੇਰੀ ਸਹਾਇਤਾ ਅਤੇ ਸੁਣਨ ਦੀ ਗੁਣਵੱਤਾ ਹੈ। ਸੰਖੇਪ ਵਿੱਚ, ਇਹ ਸੰਗੀਤ ਚਲਾਉਂਦਾ ਹੈ. ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ -- ExoPlayer-ਅਧਾਰਿਤ ਪਲੇਬੈਕ -- ਨਵੀਨਤਮ ਸਮੱਗਰੀ ਡਿਜ਼ਾਈਨ ਦਿਸ਼ਾ-ਨਿਰਦੇਸ਼ਾਂ ਤੋਂ ਲਿਆ ਗਿਆ Snappy UI +- ਮੀਡੀਆ 3 ਐਕਸੋਪਲੇਅਰ ਅਧਾਰਿਤ ਪਲੇਬੈਕ +- ਨਵੀਨਤਮ ਸਮੱਗਰੀ ਡਿਜ਼ਾਈਨ ਦਿਸ਼ਾ-ਨਿਰਦੇਸ਼ਾਂ ਤੋਂ ਲਿਆ ਗਿਆ ਚੁਸਤ-ਦਰੁਸਤ UI - ਓਪੀਨੀਏਟਿਡ UX ਜੋ ਕਿ ਕਿਨਾਰੇ ਕੇਸਾਂ 'ਤੇ ਵਰਤੋਂ ਵਿੱਚ ਆਸਾਨੀ ਨੂੰ ਤਰਜੀਹ ਦਿੰਦਾ ਹੈ - ਅਨੁਕੂਲਿਤ ਵਿਵਹਾਰ - ਡਿਸਕ ਨੰਬਰਾਂ, ਮਲਟੀਪਲ ਕਲਾਕਾਰਾਂ, ਰੀਲੀਜ਼ ਕਿਸਮਾਂ, ਸਟੀਕ ਲਈ ਸਮਰਥਨ /ਮੂਲ ਤਾਰੀਖਾਂ, ਕ੍ਰਮਬੱਧ ਟੈਗਸ, ਅਤੇ ਹੋਰ - ਉੱਨਤ ਕਲਾਕਾਰ ਪ੍ਰਣਾਲੀ ਜੋ ਕਲਾਕਾਰਾਂ ਅਤੇ ਐਲਬਮ ਕਲਾਕਾਰਾਂ ਨੂੰ ਇਕਜੁੱਟ ਕਰਦੀ ਹੈ - SD ਕਾਰਡ-ਜਾਣੂ ਫੋਲਡਰ ਪ੍ਰਬੰਧਨ + - ਭਰੋਸੇਯੋਗ ਪਲੇਅਲਿਸਟਿੰਗ ਕਾਰਜਕੁਸ਼ਲਤਾ - ਭਰੋਸੇਯੋਗ ਪਲੇਅਬੈਕ ਸਥਿਤੀ ਸਥਿਰਤਾ -- ਪੂਰਾ ਰੀਪਲੇਗੇਨ ਸਮਰਥਨ (MP3, FLAC, OGG, OPUS, ਅਤੇ MP4 ਫਾਈਲਾਂ 'ਤੇ) +- ਪੂਰਾ ਰੀਪਲੇਅ-ਗੇਨ ਸਮਰਥਨ (MP3, FLAC, OGG, OPUS, ਅਤੇ MP4 ਫਾਈਲਾਂ 'ਤੇ) - ਬਾਹਰੀ ਈਕੋਲਾਈਜ਼ਰ ਦਾ ਸਮਰਥਨ (ਉਦਾਹਰਨ. ਵੇਵਲੇਟ) - ਕਿਨਾਰੇ-ਤੋਂ-ਕਿਨਾਰੇ -- ਏਮਬੈਡਡ ਕਵਰ ਸਪੋਰਟ +- ਏਮਬੈੱਡਡ ਕਵਰ ਸਪੋਰਟ - ਖੋਜ ਕਾਰਜਸ਼ੀਲਤਾ - ਹੈੱਡਸੈੱਟ ਆਟੋਪਲੇ - ਸਟਾਈਲਿਸ਼ ਵਿਜੇਟਸ ਜੋ ਆਪਣੇ ਆਪ ਉਹਨਾਂ ਦੇ ਆਕਾਰ ਦੇ ਅਨੁਕੂਲ ਬਣਦੇ ਹਨ -- ਪੂਰੀ ਤਰ੍ਹਾਂ ਨਿੱਜੀ ਅਤੇ ਔਫਲਾਈਨ -- ਕੋਈ ਗੋਲ ਐਲਬਮ ਕਵਰ ਨਹੀਂ (ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਉਹਨਾਂ ਨੂੰ ਨਹੀਂ ਚਾਹੁੰਦੇ ਹੋ। ਤੁਸੀਂ ਕਰ ਸੱਕਦੇ ਹੋ।) +- ਪੂਰੀ ਤਰ੍ਹਾਂ ਨਿੱਜੀ ਅਤੇ ਆਫਲਾਈਨ +- ਕੋਈ ਗੋਲ ਐਲਬਮ ਕਵਰ ਨਹੀਂ (ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਉਹਨਾਂ ਨੂੰ ਨਹੀਂ ਚਾਹੁੰਦੇ ਹੋ। ਤੁਸੀਂ ਕਰ ਸਕਦੇ ਹੋ।)