diff --git a/app/src/main/java/org/oxycblt/auxio/music/storage/MediaStoreExtractor.kt b/app/src/main/java/org/oxycblt/auxio/music/storage/MediaStoreExtractor.kt index 3c0391c2f..6308b450d 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/storage/MediaStoreExtractor.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/storage/MediaStoreExtractor.kt @@ -196,7 +196,7 @@ private abstract class BaseMediaStoreExtractor( if (cache?.populate(rawSong) == true) { completeSongs.send(rawSong) } else { - query.populateFileInfo(rawSong) + query.populateTags(rawSong) incompleteSongs.send(rawSong) } yield() diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackModule.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackModule.kt index 6bacbbb81..dc05fc8c1 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackModule.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackModule.kt @@ -17,17 +17,19 @@ package org.oxycblt.auxio.playback -import android.content.Context +import dagger.Binds import dagger.Module -import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton import org.oxycblt.auxio.playback.state.PlaybackStateManager +import org.oxycblt.auxio.playback.state.PlaybackStateManagerImpl @Module @InstallIn(SingletonComponent::class) -class PlaybackModule { - @Provides fun stateManager() = PlaybackStateManager.get() - @Provides fun settings(@ApplicationContext context: Context) = PlaybackSettings.from(context) +interface PlaybackModule { + @Singleton + @Binds + fun stateManager(playbackManager: PlaybackStateManagerImpl): PlaybackStateManager + @Binds fun settings(playbackSettings: PlaybackSettingsImpl): PlaybackSettings } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSettings.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSettings.kt index 866db08bf..218c83b89 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSettings.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSettings.kt @@ -19,6 +19,8 @@ package org.oxycblt.auxio.playback import android.content.Context import androidx.core.content.edit +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.R import org.oxycblt.auxio.music.MusicMode @@ -65,17 +67,9 @@ interface PlaybackSettings : Settings { /** Called when [notificationAction] has changed. */ fun onNotificationActionChanged() {} } - - companion object { - /** - * Get a framework-backed implementation. - * @param context [Context] required. - */ - fun from(context: Context): PlaybackSettings = PlaybackSettingsImpl(context) - } } -class PlaybackSettingsImpl(context: Context) : +class PlaybackSettingsImpl @Inject constructor(@ApplicationContext context: Context) : Settings.Impl(context), PlaybackSettings { override val inListPlaybackMode: MusicMode get() = diff --git a/app/src/main/java/org/oxycblt/auxio/playback/replaygain/PreAmpCustomizeDialog.kt b/app/src/main/java/org/oxycblt/auxio/playback/replaygain/PreAmpCustomizeDialog.kt index fa589f015..81db4bad3 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/replaygain/PreAmpCustomizeDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/replaygain/PreAmpCustomizeDialog.kt @@ -21,6 +21,8 @@ import android.os.Bundle import android.view.LayoutInflater import android.widget.TextView import androidx.appcompat.app.AlertDialog +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject import kotlin.math.abs import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogPreAmpBinding @@ -31,7 +33,10 @@ import org.oxycblt.auxio.ui.ViewBindingDialogFragment * aa [ViewBindingDialogFragment] that allows user configuration of the current [ReplayGainPreAmp]. * @author Alexander Capehart (OxygenCobalt) */ +@AndroidEntryPoint class PreAmpCustomizeDialog : ViewBindingDialogFragment() { + @Inject lateinit var playbackSettings: PlaybackSettings + override fun onCreateBinding(inflater: LayoutInflater) = DialogPreAmpBinding.inflate(inflater) override fun onConfigDialog(builder: AlertDialog.Builder) { @@ -39,11 +44,11 @@ class PreAmpCustomizeDialog : ViewBindingDialogFragment() { .setTitle(R.string.set_pre_amp) .setPositiveButton(R.string.lbl_ok) { _, _ -> val binding = requireBinding() - PlaybackSettings.from(requireContext()).replayGainPreAmp = + playbackSettings.replayGainPreAmp = ReplayGainPreAmp(binding.withTagsSlider.value, binding.withoutTagsSlider.value) } .setNeutralButton(R.string.lbl_reset) { _, _ -> - PlaybackSettings.from(requireContext()).replayGainPreAmp = ReplayGainPreAmp(0f, 0f) + playbackSettings.replayGainPreAmp = ReplayGainPreAmp(0f, 0f) } .setNegativeButton(R.string.lbl_cancel, null) } @@ -53,7 +58,7 @@ class PreAmpCustomizeDialog : ViewBindingDialogFragment() { // First initialization, we need to supply the sliders with the values from // settings. After this, the sliders save their own state, so we do not need to // do any restore behavior. - val preAmp = PlaybackSettings.from(requireContext()).replayGainPreAmp + val preAmp = playbackSettings.replayGainPreAmp binding.withTagsSlider.value = preAmp.with binding.withoutTagsSlider.value = preAmp.without } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/replaygain/ReplayGainAudioProcessor.kt b/app/src/main/java/org/oxycblt/auxio/playback/replaygain/ReplayGainAudioProcessor.kt index 5dc4c1280..29f19fde4 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/replaygain/ReplayGainAudioProcessor.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/replaygain/ReplayGainAudioProcessor.kt @@ -17,7 +17,6 @@ package org.oxycblt.auxio.playback.replaygain -import android.content.Context import com.google.android.exoplayer2.C import com.google.android.exoplayer2.Format import com.google.android.exoplayer2.Player @@ -26,6 +25,7 @@ import com.google.android.exoplayer2.audio.AudioProcessor import com.google.android.exoplayer2.audio.BaseAudioProcessor import com.google.android.exoplayer2.util.MimeTypes import java.nio.ByteBuffer +import javax.inject.Inject import kotlin.math.pow import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.metadata.TextTags @@ -43,10 +43,12 @@ import org.oxycblt.auxio.util.logD * * @author Alexander Capehart (OxygenCobalt) */ -class ReplayGainAudioProcessor(context: Context) : - BaseAudioProcessor(), Player.Listener, PlaybackSettings.Listener { - private val playbackManager = PlaybackStateManager.get() - private val playbackSettings = PlaybackSettings.from(context) +class ReplayGainAudioProcessor +@Inject +constructor( + private val playbackManager: PlaybackStateManager, + private val playbackSettings: PlaybackSettings +) : BaseAudioProcessor(), Player.Listener, PlaybackSettings.Listener { private var lastFormat: Format? = null private var volume = 1f diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt index ea5690946..f00182b53 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt @@ -17,6 +17,7 @@ package org.oxycblt.auxio.playback.state +import javax.inject.Inject import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.Song @@ -40,8 +41,6 @@ import org.oxycblt.auxio.util.logW * Internal consumers should usually use [Listener], however the component that manages the player * itself should instead use [InternalPlayer]. * - * All access should be done with [get]. - * * @author Alexander Capehart (OxygenCobalt) */ interface PlaybackStateManager { @@ -270,32 +269,9 @@ interface PlaybackStateManager { val positionMs: Long, val repeatMode: RepeatMode, ) - - companion object { - @Volatile private var INSTANCE: PlaybackStateManager? = null - - /** - * Get a singleton instance. - * @return The (possibly newly-created) singleton instance. - */ - fun get(): PlaybackStateManager { - val currentInstance = INSTANCE - logD(currentInstance) - - if (currentInstance != null) { - return currentInstance - } - - synchronized(this) { - val newInstance = PlaybackStateManagerImpl() - INSTANCE = newInstance - return newInstance - } - } - } } -private class PlaybackStateManagerImpl : PlaybackStateManager { +class PlaybackStateManagerImpl @Inject constructor() : PlaybackStateManager { private val listeners = mutableListOf() @Volatile private var internalPlayer: InternalPlayer? = null @Volatile private var pendingAction: InternalPlayer.Action? = null diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt index 0f0b26e8e..02e4ff557 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt @@ -22,15 +22,19 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import androidx.core.content.ContextCompat +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject import org.oxycblt.auxio.playback.state.PlaybackStateManager /** * A [BroadcastReceiver] that forwards [Intent.ACTION_MEDIA_BUTTON] [Intent]s to [PlaybackService]. * @author Alexander Capehart (OxygenCobalt) */ +@AndroidEntryPoint class MediaButtonReceiver : BroadcastReceiver() { + @Inject lateinit var playbackManager: PlaybackStateManager + override fun onReceive(context: Context, intent: Intent) { - val playbackManager = PlaybackStateManager.get() if (playbackManager.queue.currentSong != null) { // We have a song, so we can assume that the service will start a foreground state. // At least, I hope. Again, *this is why we don't do this*. I cannot describe how diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt index 257f7f298..7ec25f7aa 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt @@ -27,6 +27,8 @@ import android.support.v4.media.MediaMetadataCompat import android.support.v4.media.session.MediaSessionCompat import android.support.v4.media.session.PlaybackStateCompat import androidx.media.session.MediaButtonReceiver +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.R import org.oxycblt.auxio.image.BitmapProvider @@ -44,11 +46,15 @@ import org.oxycblt.auxio.util.logD /** * A component that mirrors the current playback state into the [MediaSessionCompat] and * [NotificationComponent]. - * @param context [Context] required to initialize components. - * @param listener [Listener] to forward notification updates to. * @author Alexander Capehart (OxygenCobalt) */ -class MediaSessionComponent(private val context: Context, private val listener: Listener) : +class MediaSessionComponent +@Inject +constructor( + @ApplicationContext private val context: Context, + private val playbackManager: PlaybackStateManager, + private val playbackSettings: PlaybackSettings +) : MediaSessionCompat.Callback(), PlaybackStateManager.Listener, ImageSettings.Listener, @@ -59,12 +65,11 @@ class MediaSessionComponent(private val context: Context, private val listener: setQueueTitle(context.getString(R.string.lbl_queue)) } - private val playbackManager = PlaybackStateManager.get() - private val playbackSettings = PlaybackSettings.from(context) - private val notification = NotificationComponent(context, mediaSession.sessionToken) private val provider = BitmapProvider(context) + private var listener: Listener? = null + init { playbackManager.addListener(this) playbackSettings.registerListener(this) @@ -79,11 +84,20 @@ class MediaSessionComponent(private val context: Context, private val listener: MediaButtonReceiver.handleIntent(mediaSession, intent) } + /** + * Register a [Listener] for notification updates to this service. + * @param listener The [Listener] to register. + */ + fun registerListener(listener: Listener) { + this.listener = listener + } + /** * Release this instance, closing the [MediaSessionCompat] and preventing any further updates to * the [NotificationComponent]. */ fun release() { + listener = null provider.release() playbackSettings.unregisterListener(this) playbackManager.removeListener(this) @@ -135,7 +149,7 @@ class MediaSessionComponent(private val context: Context, private val listener: invalidateSessionState() notification.updatePlaying(playbackManager.playerState.isPlaying) if (!provider.isBusy) { - listener.onPostNotification(notification) + listener?.onPostNotification(notification) } } @@ -316,7 +330,7 @@ class MediaSessionComponent(private val context: Context, private val listener: val metadata = builder.build() mediaSession.setMetadata(metadata) notification.updateMetadata(metadata) - listener.onPostNotification(notification) + listener?.onPostNotification(notification) } }) } @@ -403,7 +417,7 @@ class MediaSessionComponent(private val context: Context, private val listener: } if (!provider.isBusy) { - listener.onPostNotification(notification) + listener?.onPostNotification(notification) } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt index 7ad6ebc08..517c57984 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt @@ -86,11 +86,11 @@ class PlaybackService : MusicRepository.Listener { // Player components private lateinit var player: ExoPlayer - private lateinit var replayGainProcessor: ReplayGainAudioProcessor + @Inject lateinit var replayGainProcessor: ReplayGainAudioProcessor // System backend components - private lateinit var mediaSessionComponent: MediaSessionComponent - private lateinit var widgetComponent: WidgetComponent + @Inject lateinit var mediaSessionComponent: MediaSessionComponent + @Inject lateinit var widgetComponent: WidgetComponent private val systemReceiver = PlaybackReceiver() // Shared components @@ -115,8 +115,6 @@ class PlaybackService : override fun onCreate() { super.onCreate() - // Initialize the player component. - replayGainProcessor = ReplayGainAudioProcessor(this) // Enable constant bitrate seeking so that certain MP3s/AACs are seekable val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true) // Since Auxio is a music player, only specify an audio renderer to save @@ -155,8 +153,7 @@ class PlaybackService : // condition to cause us to load music before we were fully initialize. playbackManager.registerInternalPlayer(this) musicRepository.addListener(this) - widgetComponent = WidgetComponent(this) - mediaSessionComponent = MediaSessionComponent(this, this) + mediaSessionComponent.registerListener(this) registerReceiver( systemReceiver, IntentFilter().apply { diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt index 05bde11d4..65955fec4 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt @@ -22,6 +22,8 @@ import android.graphics.Bitmap import android.os.Build import coil.request.ImageRequest import coil.transform.RoundedCornersTransformation +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject import org.oxycblt.auxio.R import org.oxycblt.auxio.image.BitmapProvider import org.oxycblt.auxio.image.ImageSettings @@ -39,14 +41,16 @@ import org.oxycblt.auxio.util.logD /** * A component that manages the "Now Playing" state. This is kept separate from the [WidgetProvider] * itself to prevent possible memory leaks and enable extension to more widgets in the future. - * @param context [Context] required to manage AppWidgetProviders. * @author Alexander Capehart (OxygenCobalt) */ -class WidgetComponent(private val context: Context) : - PlaybackStateManager.Listener, UISettings.Listener, ImageSettings.Listener { - private val playbackManager = PlaybackStateManager.get() - private val uiSettings = UISettings.from(context) - private val imageSettings = ImageSettings.from(context) +class WidgetComponent +@Inject +constructor( + @ApplicationContext private val context: Context, + private val imageSettings: ImageSettings, + private val playbackManager: PlaybackStateManager, + private val uiSettings: UISettings +) : PlaybackStateManager.Listener, UISettings.Listener, ImageSettings.Listener { private val widgetProvider = WidgetProvider() private val provider = BitmapProvider(context) @@ -109,9 +113,10 @@ class WidgetComponent(private val context: Context) : /** Release this instance, preventing any further events from updating the widget instances. */ fun release() { provider.release() + imageSettings.unregisterListener(this) + playbackManager.removeListener(this) uiSettings.unregisterListener(this) widgetProvider.reset(context) - playbackManager.removeListener(this) } // --- CALLBACKS --- diff --git a/app/src/test/java/org/oxycblt/auxio/music/parsing/ParsingUtilTest.kt b/app/src/test/java/org/oxycblt/auxio/music/metadata/TagUtilTest.kt similarity index 92% rename from app/src/test/java/org/oxycblt/auxio/music/parsing/ParsingUtilTest.kt rename to app/src/test/java/org/oxycblt/auxio/music/metadata/TagUtilTest.kt index aa61d7ea0..96d34b98d 100644 --- a/app/src/test/java/org/oxycblt/auxio/music/parsing/ParsingUtilTest.kt +++ b/app/src/test/java/org/oxycblt/auxio/music/metadata/TagUtilTest.kt @@ -15,19 +15,13 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.parsing +package org.oxycblt.auxio.music.metadata import org.junit.Assert.assertEquals import org.junit.Test import org.oxycblt.auxio.music.FakeMusicSettings -import org.oxycblt.auxio.music.metadata.correctWhitespace -import org.oxycblt.auxio.music.metadata.parseId3GenreNames -import org.oxycblt.auxio.music.metadata.parseId3v2PositionField -import org.oxycblt.auxio.music.metadata.parseMultiValue -import org.oxycblt.auxio.music.metadata.parseVorbisPositionField -import org.oxycblt.auxio.music.metadata.splitEscaped -class ParsingUtilTest { +class TagUtilTest { @Test fun parseMultiValue_single() { assertEquals(