playback: fix headset focus bug

Fix an issue where headset focus would restart playback unexpectedly.

At some point during the broadcast refactor, I accidentally switched
the values of CONNECTED and DISCONNECTED when handling
AudioManager.ACTION_HEADSET_PLUG. This resulted in playback starting
for no reason in some situations.
This commit is contained in:
OxygenCobalt 2022-02-13 16:12:08 -07:00
parent ec358a13e3
commit 30ad7f99db
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
9 changed files with 25 additions and 34 deletions

View file

@ -3,8 +3,12 @@
## dev [v2.2.1 or v2.3.0] ## dev [v2.2.1 or v2.3.0]
#### What's Improved #### What's Improved
- Updated chinese translations [courtesy of cccClyde] - Updated chinese translations [courtesy of cccClyde]
- Use proper M3 top app bars
- Use body typography in correct places - Use body typography in correct places
#### What's Fixed
- Fixed issue where playback would start unexpectedly when opening the app
## v2.2.0 ## v2.2.0
#### What's New: #### What's New:
- Added Arabic translations [Courtesy of hasanpasha] - Added Arabic translations [Courtesy of hasanpasha]

View file

@ -201,7 +201,7 @@ class ArtistDetailAdapter(
// Get the genre that corresponds to the most songs in this artist, which would be // Get the genre that corresponds to the most songs in this artist, which would be
// the most "Prominent" genre. // the most "Prominent" genre.
binding.detailSubhead.text = data.songs binding.detailSubhead.text = data.songs
.groupBy { it.genre?.resolvedName } .groupBy { it.genre.resolvedName }
.entries.maxByOrNull { it.value.size } .entries.maxByOrNull { it.value.size }
?.key ?: context.getString(R.string.def_genre) ?.key ?: context.getString(R.string.def_genre)

View file

@ -7,7 +7,6 @@ import android.provider.MediaStore
import androidx.core.database.getStringOrNull import androidx.core.database.getStringOrNull
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.excluded.ExcludedDatabase import org.oxycblt.auxio.excluded.ExcludedDatabase
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logE import org.oxycblt.auxio.util.logE
import java.lang.Exception import java.lang.Exception
@ -125,7 +124,7 @@ class MusicLoader {
args += "$path%" // Append % so that the selector properly detects children args += "$path%" // Append % so that the selector properly detects children
} }
context.contentResolver.query( context.applicationContext.contentResolver.query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
arrayOf( arrayOf(
MediaStore.Audio.AudioColumns._ID, MediaStore.Audio.AudioColumns._ID,
@ -179,8 +178,6 @@ class MusicLoader {
substring(0 until lastIndexOfAny(listOf(fileName))) substring(0 until lastIndexOfAny(listOf(fileName)))
} }
logD("SONG NAME: $title ALBUM: $album ARTIST: $artist ALBUM ARTIST: $albumArtist")
songs.add( songs.add(
Song( Song(
title, title,
@ -236,8 +233,6 @@ class MusicLoader {
) )
val artistName = templateSong.internalGroupingArtistName val artistName = templateSong.internalGroupingArtistName
logD("ALBUM NAME: $albumName PREFERRED ARTIST: $artistName")
albums.add( albums.add(
Album( Album(
albumName, albumName,
@ -294,7 +289,7 @@ class MusicLoader {
private fun readGenres(context: Context, songs: List<Song>): List<Genre> { private fun readGenres(context: Context, songs: List<Song>): List<Genre> {
val genres = mutableListOf<Genre>() val genres = mutableListOf<Genre>()
val genreCursor = context.contentResolver.query( val genreCursor = context.applicationContext.contentResolver.query(
MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI,
arrayOf( arrayOf(
MediaStore.Audio.Genres._ID, MediaStore.Audio.Genres._ID,
@ -347,7 +342,7 @@ class MusicLoader {
val genreSongs = mutableListOf<Song>() val genreSongs = mutableListOf<Song>()
// Don't even bother blacklisting here as useless iterations are less expensive than IO // Don't even bother blacklisting here as useless iterations are less expensive than IO
val songCursor = context.contentResolver.query( val songCursor = context.applicationContext.contentResolver.query(
MediaStore.Audio.Genres.Members.getContentUri("external", genreId), MediaStore.Audio.Genres.Members.getContentUri("external", genreId),
arrayOf(MediaStore.Audio.Genres.Members._ID), arrayOf(MediaStore.Audio.Genres.Members._ID),
null, null, null null, null, null

View file

@ -127,6 +127,7 @@ class PlaybackFragment : Fragment() {
} }
binding.playbackLoop.setImageResource(resId) binding.playbackLoop.setImageResource(resId)
binding.playbackLoop.isActivated = loopMode != LoopMode.NONE
} }
playbackModel.position.observe(viewLifecycleOwner) { pos -> playbackModel.position.observe(viewLifecycleOwner) { pos ->

View file

@ -151,17 +151,8 @@ class PlaybackStateManager private constructor() {
} }
PlaybackMode.IN_GENRE -> { PlaybackMode.IN_GENRE -> {
val genre = song.genre mParent = song.genre
mQueue = song.genre.songs.toMutableList()
// Don't do this if the genre is null
if (genre != null) {
mParent = genre
mQueue = genre.songs.toMutableList()
} else {
playSong(song, PlaybackMode.ALL_SONGS)
return
}
} }
PlaybackMode.IN_ARTIST -> { PlaybackMode.IN_ARTIST -> {
@ -463,7 +454,6 @@ class PlaybackStateManager private constructor() {
*/ */
fun seekTo(position: Long) { fun seekTo(position: Long) {
mPosition = position mPosition = position
callbacks.forEach { it.onSeek(position) } callbacks.forEach { it.onSeek(position) }
} }

View file

@ -465,8 +465,8 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
AudioManager.ACTION_HEADSET_PLUG -> { AudioManager.ACTION_HEADSET_PLUG -> {
when (intent.getIntExtra("state", -1)) { when (intent.getIntExtra("state", -1)) {
0 -> resumeFromPlug() 0 -> pauseFromPlug()
1 -> pauseFromPlug() 1 -> resumeFromPlug()
} }
} }

View file

@ -49,18 +49,18 @@ fun Any.logE(msg: String) {
} }
/** /**
* Get a non-nullable name, used so that logs will always show up in the console. * Get a non-nullable name, used so that logs will always show up by Auxio
* This also applies a special "Auxio" prefix so that messages can be filtered to just from the main codebase.
* @return The name of the object, otherwise "Anonymous Object" * @return The name of the object, otherwise "Anonymous Object"
*/ */
private fun Any.getName(): String = "Auxio.${this::class.simpleName ?: "Anonymous Object"}" private fun Any.getName(): String = "Auxio.${this::class.simpleName ?: "Anonymous Object"}"
/** /**
* I know that this will not stop you, but consider what you are doing with your life, copiers. * I know that this will not stop you, but consider what you are doing with your life, plagiarizers.
* Do you want to live a fulfilling existence on this planet? Or do you want to spend your life * Do you want to live a fulfilling existence on this planet? Or do you want to spend your life
* taking work others did and making it objectively worse so you could arbitrage a fraction of a * taking work others did and making it objectively worse so you could arbitrage a fraction of a
* penny on every AdMob impression you get? You could do so many great things if you simply had * penny on every AdMob impression you get? You could do so many great things if you simply had
* the courage to come up with an idea of your own. Be better. * the courage to come up with an idea of your own. If you still want to go on, I guess the only
* thing I can say is this: JUNE 1989 TIANAMEN SQUARE PROTESTS AND MASSACRE 六四事件
*/ */
private fun basedCopyleftNotice() { private fun basedCopyleftNotice() {
if (BuildConfig.APPLICATION_ID != "org.oxycblt.auxio" && if (BuildConfig.APPLICATION_ID != "org.oxycblt.auxio" &&

View file

@ -53,7 +53,7 @@ fun createTinyWidget(context: Context, state: WidgetState): RemoteViews {
fun createSmallWidget(context: Context, state: WidgetState): RemoteViews { fun createSmallWidget(context: Context, state: WidgetState): RemoteViews {
return createViews(context, R.layout.widget_small) return createViews(context, R.layout.widget_small)
.applyCover(context, state) .applyCover(context, state)
.applyControls(context, state) .applyBasicControls(context, state)
} }
/** /**
@ -63,7 +63,7 @@ fun createSmallWidget(context: Context, state: WidgetState): RemoteViews {
fun createMediumWidget(context: Context, state: WidgetState): RemoteViews { fun createMediumWidget(context: Context, state: WidgetState): RemoteViews {
return createViews(context, R.layout.widget_medium) return createViews(context, R.layout.widget_medium)
.applyMeta(context, state) .applyMeta(context, state)
.applyControls(context, state) .applyBasicControls(context, state)
} }
/** /**
@ -142,7 +142,7 @@ private fun RemoteViews.applyPlayControls(context: Context, state: WidgetState):
return this return this
} }
private fun RemoteViews.applyControls(context: Context, state: WidgetState): RemoteViews { private fun RemoteViews.applyBasicControls(context: Context, state: WidgetState): RemoteViews {
applyPlayControls(context, state) applyPlayControls(context, state)
setOnClickPendingIntent( setOnClickPendingIntent(
@ -163,7 +163,7 @@ private fun RemoteViews.applyControls(context: Context, state: WidgetState): Rem
} }
private fun RemoteViews.applyFullControls(context: Context, state: WidgetState): RemoteViews { private fun RemoteViews.applyFullControls(context: Context, state: WidgetState): RemoteViews {
applyControls(context, state) applyBasicControls(context, state)
setOnClickPendingIntent( setOnClickPendingIntent(
R.id.widget_loop, R.id.widget_loop,

View file

@ -98,14 +98,15 @@ to a name that can be used in UIs.
while `ActionHeader` corresponds to an action with a dedicated icon, such as with sorting. while `ActionHeader` corresponds to an action with a dedicated icon, such as with sorting.
Other data types represent a specific UI configuration or state: Other data types represent a specific UI configuration or state:
- Sealed classes like `Sort` and `HeaderString` contain data with them that can be modified. - Sealed classes like `Sort` contain data with them that can be modified.
- Enums like `DisplayMode` and `LoopMode` only contain static data, such as a string resource. - Enums like `DisplayMode` and `LoopMode` only contain static data, such as a string resource.
Things to keep in mind while working with music data: Things to keep in mind while working with music data:
- `id` is not derived from the `MediaStore` ID of the music data. It is actually a hash of the unique fields of the music data. - `id` is not derived from the `MediaStore` ID of the music data. It is actually a hash of the unique fields of the music data.
Attempting to use it as a `MediaStore` ID will result in errors. Attempting to use it as a `MediaStore` ID will result in errors.
- Any field beginning with `_mediaStore` is off-limits. These fields are meant for use within `MusicLoader` and generally provide - Any field or method beginning with `internal` is off-limits. These fields are meant for use within `MusicLoader` and generally
poor UX to the user. provide poor UX to the user. The only reason they are public is to make the loading process not have to rely on separate "Raw"
objects.
- Generally, `name` is used when saving music data to storage, while `resolvedName` is used when displaying music data to the user. - Generally, `name` is used when saving music data to storage, while `resolvedName` is used when displaying music data to the user.
- For `Song` instances in particular, prefer `resolvedAlbumName` and `resolvedArtistName` over `album.resolvedName` and `album.artist.resolvedName` - For `Song` instances in particular, prefer `resolvedAlbumName` and `resolvedArtistName` over `album.resolvedName` and `album.artist.resolvedName`
- For `Album` instances in particular, prefer `resolvedArtistName` over `artist.resolvedName` - For `Album` instances in particular, prefer `resolvedArtistName` over `artist.resolvedName`