diff --git a/app/build.gradle b/app/build.gradle
index 174c85e06..87966773e 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -32,6 +32,10 @@ android {
kotlinOptions {
jvmTarget = "1.8"
}
+
+ compileOptions {
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
}
configurations {
@@ -76,6 +80,9 @@ dependencies {
// Lint
ktlint "com.pinterest:ktlint:0.37.2"
+ // ExoPlayer
+ implementation 'com.google.android.exoplayer:exoplayer-core:2.12.1'
+
// Memory Leak checking
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 203f183e5..3b33222b0 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,6 +3,8 @@
package="org.oxycblt.auxio">
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt
index da6cd0e6d..8ed3b495c 100644
--- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt
@@ -23,7 +23,9 @@ import org.oxycblt.auxio.theme.getTransparentAccent
import org.oxycblt.auxio.theme.toColor
class MainFragment : Fragment() {
- private val playbackModel: PlaybackViewModel by activityViewModels()
+ private val playbackModel: PlaybackViewModel by activityViewModels {
+ PlaybackViewModel.Factory(requireContext())
+ }
private val shownFragments = listOf(0, 1)
private val tabIcons = listOf(
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 af8d909f7..8dde340d8 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt
@@ -22,7 +22,9 @@ class AlbumDetailFragment : Fragment() {
private val args: AlbumDetailFragmentArgs by navArgs()
private val detailModel: DetailViewModel by activityViewModels()
- private val playbackModel: PlaybackViewModel by activityViewModels()
+ private val playbackModel: PlaybackViewModel by activityViewModels {
+ PlaybackViewModel.Factory(requireContext())
+ }
override fun onCreateView(
inflater: LayoutInflater,
diff --git a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt
index 7c133d152..613183667 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt
@@ -20,7 +20,9 @@ import org.oxycblt.auxio.theme.disable
class ArtistDetailFragment : Fragment() {
private val args: ArtistDetailFragmentArgs by navArgs()
private val detailModel: DetailViewModel by activityViewModels()
- private val playbackModel: PlaybackViewModel by activityViewModels()
+ private val playbackModel: PlaybackViewModel by activityViewModels {
+ PlaybackViewModel.Factory(requireContext())
+ }
override fun onCreateView(
inflater: LayoutInflater,
diff --git a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt
index 0d7418022..b521998a5 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt
@@ -21,7 +21,9 @@ class GenreDetailFragment : Fragment() {
private val args: GenreDetailFragmentArgs by navArgs()
private val detailModel: DetailViewModel by activityViewModels()
- private val playbackModel: PlaybackViewModel by activityViewModels()
+ private val playbackModel: PlaybackViewModel by activityViewModels {
+ PlaybackViewModel.Factory(requireContext())
+ }
override fun onCreateView(
inflater: LayoutInflater,
diff --git a/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt b/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt
index 91127661d..02a42ba77 100644
--- a/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt
@@ -35,7 +35,9 @@ import org.oxycblt.auxio.theme.resolveAttr
class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
private val libraryModel: LibraryViewModel by activityViewModels()
- private val playbackModel: PlaybackViewModel by activityViewModels()
+ private val playbackModel: PlaybackViewModel by activityViewModels {
+ PlaybackViewModel.Factory(requireContext())
+ }
override fun onCreateView(
inflater: LayoutInflater,
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt
index 72d2c3109..c157dafac 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt
@@ -22,7 +22,9 @@ import org.oxycblt.auxio.theme.toColor
// TODO: Add a swipe-to-next-track function using a ViewPager
class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
- private val playbackModel: PlaybackViewModel by activityViewModels()
+ private val playbackModel: PlaybackViewModel by activityViewModels {
+ PlaybackViewModel.Factory(requireActivity().application)
+ }
// TODO: Implement nav to artists/albums
override fun onCreateView(
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt
new file mode 100644
index 000000000..61fba8d3b
--- /dev/null
+++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt
@@ -0,0 +1,47 @@
+package org.oxycblt.auxio.playback
+
+import android.app.Service
+import android.content.Intent
+import android.os.Binder
+import android.os.Build
+import android.os.IBinder
+import com.google.android.exoplayer2.MediaItem
+import com.google.android.exoplayer2.Player
+import com.google.android.exoplayer2.SimpleExoPlayer
+import org.oxycblt.auxio.music.Song
+import org.oxycblt.auxio.music.toURI
+
+class PlaybackService : Service(), Player.EventListener {
+ private val player: SimpleExoPlayer by lazy {
+ val p = SimpleExoPlayer.Builder(applicationContext).build()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ p.experimentalSetOffloadSchedulingEnabled(true)
+ }
+ p.addListener(this)
+ p
+ }
+
+ private val mBinder = LocalBinder()
+
+ override fun onBind(intent: Intent): IBinder {
+ return mBinder
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+
+ player.release()
+ }
+
+ fun playSong(song: Song) {
+ val item = MediaItem.fromUri(song.id.toURI())
+
+ player.setMediaItem(item)
+ player.prepare()
+ player.play()
+ }
+
+ inner class LocalBinder : Binder() {
+ fun getService() = this@PlaybackService
+ }
+}
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt
index d398e1537..e25d0fb10 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt
@@ -1,10 +1,17 @@
package org.oxycblt.auxio.playback
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.IBinder
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.google.android.exoplayer2.Player
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel
@@ -15,12 +22,12 @@ import org.oxycblt.auxio.music.toDuration
import kotlin.random.Random
import kotlin.random.Random.Default.nextLong
-// TODO: Adding to Queue
+// TODO: User managed queue
// TODO: Add the playback service itself
// TODO: Add loop control [From playback]
// TODO: Implement persistence through Bundles [I want to keep my shuffles, okay?]
// A ViewModel that acts as an intermediary between PlaybackService and the Playback Fragments.
-class PlaybackViewModel : ViewModel() {
+class PlaybackViewModel(private val context: Context) : ViewModel(), Player.EventListener {
private val mCurrentSong = MutableLiveData()
val currentSong: LiveData get() = mCurrentSong
@@ -53,6 +60,25 @@ class PlaybackViewModel : ViewModel() {
private var mCanAnimate = false
val canAnimate: Boolean get() = mCanAnimate
+ private lateinit var playbackService: PlaybackService
+ private var playbackIntent: Intent
+
+ private val connection = object : ServiceConnection {
+ override fun onServiceConnected(name: ComponentName, binder: IBinder) {
+ playbackService = (binder as PlaybackService.LocalBinder).getService()
+ }
+
+ override fun onServiceDisconnected(name: ComponentName) {
+ Log.d(this::class.simpleName, "Service disconnected.")
+ }
+ }
+
+ init {
+ playbackIntent = Intent(context, PlaybackService::class.java).also {
+ context.bindService(it, connection, Context.BIND_AUTO_CREATE)
+ }
+ }
+
// Formatted variants of the duration
val formattedCurrentDuration = Transformations.map(mCurrentDuration) {
it.toDuration()
@@ -69,7 +95,6 @@ class PlaybackViewModel : ViewModel() {
// Update the current song while changing the queue mode.
fun update(song: Song, mode: PlaybackMode) {
-
// Auxio doesn't support playing songs while swapping the mode to GENRE, as its impossible
// to determine what genre a song has.
if (mode == PlaybackMode.IN_GENRE) {
@@ -312,6 +337,8 @@ class PlaybackViewModel : ViewModel() {
if (!mIsPlaying.value!!) {
mIsPlaying.value = true
}
+
+ playbackService.playSong(song)
}
// Generate a new shuffled queue.
@@ -383,4 +410,21 @@ class PlaybackViewModel : ViewModel() {
return final
}
+
+ override fun onCleared() {
+ super.onCleared()
+
+ context.unbindService(connection)
+ }
+
+ class Factory(private val context: Context) : ViewModelProvider.Factory {
+ @Suppress("unchecked_cast")
+ override fun create(modelClass: Class): T {
+ if (modelClass.isAssignableFrom(PlaybackViewModel::class.java)) {
+ return PlaybackViewModel(context) as T
+ }
+
+ throw IllegalArgumentException("Unknown ViewModel class.")
+ }
+ }
}
diff --git a/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt b/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt
index 78fb66cc9..9c8b85991 100644
--- a/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt
@@ -15,7 +15,9 @@ import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.theme.applyDivider
class SongsFragment : Fragment() {
- private val playbackModel: PlaybackViewModel by activityViewModels()
+ private val playbackModel: PlaybackViewModel by activityViewModels {
+ PlaybackViewModel.Factory(requireContext())
+ }
override fun onCreateView(
inflater: LayoutInflater,
diff --git a/app/src/main/res/drawable/ic_pause.xml b/app/src/main/res/drawable/ic_pause.xml
index f5f4c3387..60ef4d1d3 100644
--- a/app/src/main/res/drawable/ic_pause.xml
+++ b/app/src/main/res/drawable/ic_pause.xml
@@ -3,7 +3,8 @@
android:width="32dp"
android:height="32dp"
android:viewportWidth="24"
- android:viewportHeight="24">
+ android:viewportHeight="24"
+ android:tint="?android:attr/colorControlNormal">
diff --git a/app/src/main/res/drawable/ic_play.xml b/app/src/main/res/drawable/ic_play.xml
index 832dec261..3dfcbc471 100644
--- a/app/src/main/res/drawable/ic_play.xml
+++ b/app/src/main/res/drawable/ic_play.xml
@@ -3,7 +3,8 @@
android:width="32dp"
android:height="32dp"
android:viewportWidth="24"
- android:viewportHeight="24">
+ android:viewportHeight="24"
+ android:tint="?android:attr/colorControlNormal">
diff --git a/app/src/main/res/layout/fragment_artist_detail.xml b/app/src/main/res/layout/fragment_artist_detail.xml
index 1997bd23d..6fd7866d1 100644
--- a/app/src/main/res/layout/fragment_artist_detail.xml
+++ b/app/src/main/res/layout/fragment_artist_detail.xml
@@ -30,7 +30,6 @@
android:layout_height="?android:attr/actionBarSize"
android:background="?android:attr/windowBackground"
android:elevation="@dimen/elevation_normal"
- app:popupTheme="@style/AppThemeOverlay.Popup"
app:menu="@menu/menu_detail"
app:titleTextAppearance="@style/TextAppearance.Toolbar.Header"
app:navigationIcon="@drawable/ic_back"
diff --git a/app/src/main/res/menu/menu_detail.xml b/app/src/main/res/menu/menu_detail.xml
index 118d3eb14..80c4741a8 100644
--- a/app/src/main/res/menu/menu_detail.xml
+++ b/app/src/main/res/menu/menu_detail.xml
@@ -1,13 +1,14 @@
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 66e334a28..f3006f204 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -47,6 +47,7 @@
Skip to last song
Turn shuffle on
Turn shuffle off
+ The music playback service for Auxio
Unknown Genre