diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SearchFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SearchFragment.kt
index fca7e7cd..323f8d05 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SearchFragment.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SearchFragment.kt
@@ -41,6 +41,7 @@ import org.moire.ultrasonic.domain.SearchResult
 import org.moire.ultrasonic.domain.Track
 import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle
 import org.moire.ultrasonic.model.SearchListModel
+import org.moire.ultrasonic.service.DownloadService
 import org.moire.ultrasonic.service.MediaPlayerController
 import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker
 import org.moire.ultrasonic.subsonic.ShareHandler
@@ -203,7 +204,7 @@ class SearchFragment : MultiListFragment<Identifiable>(), KoinComponent {
     private fun downloadBackground(save: Boolean, songs: List<Track?>) {
         val onValid = Runnable {
             networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
-            mediaPlayerController.downloadBackground(songs, save)
+            DownloadService.download(songs.filterNotNull(), save)
         }
         onValid.run()
     }
@@ -437,7 +438,7 @@ class SearchFragment : MultiListFragment<Identifiable>(), KoinComponent {
                         songs.size
                     )
                 )
-                mediaPlayerController.unpin(songs)
+                DownloadService.unpin(songs)
             }
             R.id.song_menu_share -> {
                 songs.add(item)
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt
index 4d268679..b67ec05b 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt
@@ -40,6 +40,7 @@ import org.moire.ultrasonic.domain.MusicDirectory
 import org.moire.ultrasonic.domain.Track
 import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle
 import org.moire.ultrasonic.model.TrackCollectionModel
+import org.moire.ultrasonic.service.DownloadService
 import org.moire.ultrasonic.service.MediaPlayerController
 import org.moire.ultrasonic.service.RxBus
 import org.moire.ultrasonic.service.plusAssign
@@ -429,7 +430,7 @@ open class TrackCollectionFragment(
     ) {
         val onValid = Runnable {
             networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
-            mediaPlayerController.downloadBackground(songs, save)
+            DownloadService.download(songs.filterNotNull(), save)
 
             if (save) {
                 Util.toast(
@@ -458,7 +459,7 @@ open class TrackCollectionFragment(
             )
         )
 
-        mediaPlayerController.delete(songs)
+        DownloadService.delete(songs)
     }
 
     internal fun unpin(songs: List<Track> = getSelectedSongs()) {
@@ -468,7 +469,7 @@ open class TrackCollectionFragment(
                 R.plurals.select_album_n_songs_unpinned, songs.size, songs.size
             )
         )
-        mediaPlayerController.unpin(songs)
+        DownloadService.unpin(songs)
     }
 
     override val defaultObserver: (List<MusicDirectory.Child>) -> Unit = {
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ArtworkBitmapLoader.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ArtworkBitmapLoader.kt
index ee060a2a..b7b6c15a 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ArtworkBitmapLoader.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ArtworkBitmapLoader.kt
@@ -19,11 +19,12 @@ import java.io.IOException
 import java.util.concurrent.Executors
 import org.koin.core.component.KoinComponent
 import org.koin.core.component.inject
+import org.moire.ultrasonic.subsonic.ImageLoaderProvider
 
 @SuppressLint("UnsafeOptInUsageError")
 class ArtworkBitmapLoader : BitmapLoader, KoinComponent {
 
-    private val imageLoader: ImageLoader by inject()
+    private val imageLoaderProvider: ImageLoaderProvider by inject()
 
     private val executorService: ListeningExecutorService by lazy {
         MoreExecutors.listeningDecorator(
@@ -55,6 +56,6 @@ class ArtworkBitmapLoader : BitmapLoader, KoinComponent {
         val parts = uri.path?.trim('/')?.split('|')
 
         require(parts!!.count() == 2) { "Invalid bitmap Uri" }
-        return imageLoader.getImage(parts[0], parts[1], false, 0)
+        return imageLoaderProvider.getImageLoader().getImage(parts[0], parts[1], false, 0)
     }
 }
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt
index f425822f..c814eeec 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt
@@ -18,6 +18,9 @@ import java.util.concurrent.ConcurrentHashMap
 import java.util.concurrent.CountDownLatch
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 import org.moire.ultrasonic.R
 import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
 import org.moire.ultrasonic.api.subsonic.throwOnFailure
@@ -36,7 +39,7 @@ class ImageLoader(
     context: Context,
     apiClient: SubsonicAPIClient,
     private val config: ImageLoaderConfig,
-) : CoroutineScope by CoroutineScope(Dispatchers.IO) {
+) : CoroutineScope by CoroutineScope(Dispatchers.Main + SupervisorJob()) {
     private val cacheInProgress: ConcurrentHashMap<String, CountDownLatch> = ConcurrentHashMap()
 
     // Shortcut
@@ -126,10 +129,13 @@ class ImageLoader(
         large: Boolean,
         size: Int,
         defaultResourceId: Int = R.drawable.unknown_album
-    ) {
+    ) = launch {
         val id = entry?.coverArt
-        // TODO getAlbumArtKey() accesses the disk from the UI thread..
-        val key = FileUtil.getAlbumArtKey(entry, large)
+        val key: String?
+
+        withContext(Dispatchers.IO) {
+            key = FileUtil.getAlbumArtKey(entry, large)
+        }
 
         loadImage(view, id, key, large, size, defaultResourceId)
     }
@@ -194,48 +200,49 @@ class ImageLoader(
         cacheCoverArt(track.coverArt!!, FileUtil.getAlbumArtFile(track))
     }
 
-    fun cacheCoverArt(id: String, file: String) {
-        if (id.isBlank()) return
-        // Return if have a cache hit
-        if (File(file).exists()) return
+    fun cacheCoverArt(id: String, file: String) = launch {
+        if (id.isBlank()) return@launch
 
-        // If another thread has started caching, wait until it finishes
-        val latch = cacheInProgress.putIfAbsent(file, CountDownLatch(1))
-        if (latch != null) {
-            latch.await()
-            return
-        }
+        withContext(Dispatchers.IO) {
+            // Return if have a cache hit
+            if (File(file).exists()) return@withContext
+
+            // If another coroutine has started caching, abort
+            if (cacheInProgress[file] != null) return@withContext
 
-        try {
             // Always download the large size..
             val size = config.largeSize
+
             File(file).createNewFile()
 
             // Query the API
             Timber.d("Loading cover art for: %s", id)
-            val response = API.getCoverArt(id, size.toLong()).execute().toStreamResponse()
-            response.throwOnFailure()
-
-            // Check for failure
-            if (response.stream == null) return
-
-            // Write Response stream to file
-            var inputStream: InputStream? = null
             try {
-                inputStream = response.stream
-                val bytes = inputStream!!.readBytes()
-                var outputStream: OutputStream? = null
+                val response = API.getCoverArt(id, size.toLong()).execute().toStreamResponse()
+
+                response.throwOnFailure()
+
+                // Check for failure
+                if (response.stream == null) return@withContext
+
+                // Write Response stream to file
+                var inputStream: InputStream? = null
                 try {
-                    outputStream = FileOutputStream(file)
-                    outputStream.write(bytes)
+                    inputStream = response.stream
+                    val bytes = inputStream!!.readBytes()
+                    var outputStream: OutputStream? = null
+                    try {
+                        outputStream = FileOutputStream(file)
+                        outputStream.write(bytes)
+                    } finally {
+                        outputStream.safeClose()
+                    }
                 } finally {
-                    outputStream.safeClose()
+                    inputStream.safeClose()
                 }
             } finally {
-                inputStream.safeClose()
+                cacheInProgress.remove(file)?.countDown()
             }
-        } finally {
-            cacheInProgress.remove(file)?.countDown()
         }
     }
 
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/AutoMediaBrowserCallback.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/AutoMediaBrowserCallback.kt
index 1f86d630..fcf0b9f8 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/AutoMediaBrowserCallback.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/AutoMediaBrowserCallback.kt
@@ -445,6 +445,9 @@ class AutoMediaBrowserCallback(var player: Player, val libraryService: MediaLibr
     private fun playFromSearch(
         query: String,
     ): ListenableFuture<List<MediaItem>> {
+
+        Timber.w("App state: %s", UApp.instance != null)
+
         Timber.i("AutoMediaBrowserService onSearch query: %s", query)
         val mediaItems: MutableList<MediaItem> = ArrayList()
 
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadService.kt
index a7e670f0..946ce471 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadService.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadService.kt
@@ -27,6 +27,7 @@ import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.SupervisorJob
 import kotlinx.coroutines.cancel
+import kotlinx.coroutines.launch
 import org.koin.core.component.KoinComponent
 import org.koin.core.component.inject
 import org.moire.ultrasonic.R
@@ -34,7 +35,6 @@ import org.moire.ultrasonic.app.UApp
 import org.moire.ultrasonic.domain.Track
 import org.moire.ultrasonic.service.DownloadState.Companion.isFinalState
 import org.moire.ultrasonic.util.CacheCleaner
-import org.moire.ultrasonic.util.FileUtil
 import org.moire.ultrasonic.util.FileUtil.getCompleteFile
 import org.moire.ultrasonic.util.FileUtil.getPartialFile
 import org.moire.ultrasonic.util.FileUtil.getPinnedFile
@@ -77,8 +77,7 @@ class DownloadService : Service(), KoinComponent {
 
         // Create Coroutine lifecycle scope. We use a SupervisorJob(), otherwise the failure of one
         // would mean the failure of all jobs!
-        val supervisor = SupervisorJob()
-        scope = CoroutineScope(Dispatchers.IO + supervisor)
+        scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
 
         val notificationManagerCompat = NotificationManagerCompat.from(this)
 
@@ -147,7 +146,7 @@ class DownloadService : Service(), KoinComponent {
 
             val downloadTask = DownloadTask(track, scope!!, ::downloadStateChangedCallback)
             activeDownloads[track.id] = downloadTask
-            FileUtil.createDirectoryForParent(track.pinnedFile)
+
             downloadTask.start()
             listChanged = true
         }
@@ -200,7 +199,7 @@ class DownloadService : Service(), KoinComponent {
 
     private fun updateLiveData() {
         val temp: MutableList<Track> = ArrayList()
-        temp.addAll(activeDownloads.values.map { it.track.track })
+        temp.addAll(activeDownloads.values.map { it.downloadTrack.track })
         temp.addAll(downloadQueue.map { x -> x.track })
         observableDownloads.postValue(temp.distinct().sorted())
     }
@@ -257,7 +256,7 @@ class DownloadService : Service(), KoinComponent {
         return notificationBuilder.build()
     }
 
-    @Suppress("MagicNumber", "NestedBlockDepth")
+    @Suppress("MagicNumber", "NestedBlockDepth", "TooManyFunctions")
     companion object {
 
         private var startFuture: SettableFuture<DownloadService>? = null
@@ -278,57 +277,60 @@ class DownloadService : Service(), KoinComponent {
             save: Boolean,
             isHighPriority: Boolean = false
         ) {
-            // First handle and filter out those tracks that are already completed
-            var filteredTracks: List<Track>
-            if (save) {
-                tracks.filter { Storage.isPathExists(it.getCompleteFile()) }.forEach { track ->
-                    Storage.getFromPath(track.getCompleteFile())?.let {
-                        Storage.renameOrDeleteIfAlreadyExists(it, track.getPinnedFile())
-                        postState(track, DownloadState.PINNED)
+            CoroutineScope(Dispatchers.IO).launch {
+
+                // First handle and filter out those tracks that are already completed
+                var filteredTracks: List<Track>
+                if (save) {
+                    tracks.filter { Storage.isPathExists(it.getCompleteFile()) }.forEach { track ->
+                        Storage.getFromPath(track.getCompleteFile())?.let {
+                            Storage.renameOrDeleteIfAlreadyExists(it, track.getPinnedFile())
+                            postState(track, DownloadState.PINNED)
+                        }
                     }
-                }
-                filteredTracks = tracks.filter { !Storage.isPathExists(it.getPinnedFile()) }
-            } else {
-                tracks.filter { Storage.isPathExists(it.getPinnedFile()) }.forEach { track ->
-                    Storage.getFromPath(track.getPinnedFile())?.let {
-                        Storage.renameOrDeleteIfAlreadyExists(it, track.getCompleteFile())
-                        postState(track, DownloadState.DONE)
+                    filteredTracks = tracks.filter { !Storage.isPathExists(it.getPinnedFile()) }
+                } else {
+                    tracks.filter { Storage.isPathExists(it.getPinnedFile()) }.forEach { track ->
+                        Storage.getFromPath(track.getPinnedFile())?.let {
+                            Storage.renameOrDeleteIfAlreadyExists(it, track.getCompleteFile())
+                            postState(track, DownloadState.DONE)
+                        }
                     }
-                }
-                filteredTracks = tracks.filter { !Storage.isPathExists(it.getCompleteFile()) }
-            }
-
-            // Update Pinned flag of items in progress
-            downloadQueue.filter { item -> tracks.any { it.id == item.id } }
-                .forEach { it.pinned = save }
-            tracks.forEach {
-                activeDownloads[it.id]?.track?.pinned = save
-            }
-            tracks.forEach {
-                failedList[it.id]?.pinned = save
-            }
-
-            filteredTracks = filteredTracks.filter {
-                !downloadQueue.any { i -> i.id == it.id } && !activeDownloads.containsKey(it.id)
-            }
-
-            // The remainder tracks should be added to the download queue
-            // By using the counter we ensure that the songs are added in the correct order
-            var priority = 0
-            val tracksToDownload =
-                filteredTracks.map {
-                    DownloadableTrack(
-                        it,
-                        save,
-                        0,
-                        if (isHighPriority) priority++ else backgroundPriorityCounter++
-                    )
+                    filteredTracks = tracks.filter { !Storage.isPathExists(it.getCompleteFile()) }
                 }
 
-            if (tracksToDownload.isNotEmpty()) {
-                downloadQueue.addAll(tracksToDownload)
-                tracksToDownload.forEach { postState(it.track, DownloadState.QUEUED) }
-                processNextTracksOnService()
+                // Update Pinned flag of items in progress
+                downloadQueue.filter { item -> tracks.any { it.id == item.id } }
+                    .forEach { it.pinned = save }
+                tracks.forEach {
+                    activeDownloads[it.id]?.downloadTrack?.pinned = save
+                }
+                tracks.forEach {
+                    failedList[it.id]?.pinned = save
+                }
+
+                filteredTracks = filteredTracks.filter {
+                    !downloadQueue.any { i -> i.id == it.id } && !activeDownloads.containsKey(it.id)
+                }
+
+                // The remainder tracks should be added to the download queue
+                // By using the counter we ensure that the songs are added in the correct order
+                var priority = 0
+                val tracksToDownload =
+                    filteredTracks.map {
+                        DownloadableTrack(
+                            it,
+                            save,
+                            0,
+                            if (isHighPriority) priority++ else backgroundPriorityCounter++
+                        )
+                    }
+
+                if (tracksToDownload.isNotEmpty()) {
+                    downloadQueue.addAll(tracksToDownload)
+                    tracksToDownload.forEach { postState(it.track, DownloadState.QUEUED) }
+                    processNextTracksOnService()
+                }
             }
         }
 
@@ -340,23 +342,34 @@ class DownloadService : Service(), KoinComponent {
         }
 
         fun delete(track: Track) {
+            CoroutineScope(Dispatchers.IO).launch {
+                downloadQueue.get(track.id)?.let { downloadQueue.remove(it) }
+                failedList[track.id]?.let { downloadQueue.remove(it) }
+                cancelDownload(track)
 
-            downloadQueue.get(track.id)?.let { downloadQueue.remove(it) }
-            failedList[track.id]?.let { downloadQueue.remove(it) }
-            cancelDownload(track)
+                Storage.delete(track.getPartialFile())
+                Storage.delete(track.getCompleteFile())
+                Storage.delete(track.getPinnedFile())
+                postState(track, DownloadState.IDLE)
+                CacheCleaner().cleanDatabaseSelective(track)
+                Util.scanMedia(track.getPinnedFile())
+            }
+        }
 
-            Storage.delete(track.getPartialFile())
-            Storage.delete(track.getCompleteFile())
-            Storage.delete(track.getPinnedFile())
-            postState(track, DownloadState.IDLE)
-            CacheCleaner().cleanDatabaseSelective(track)
-            Util.scanMedia(track.getPinnedFile())
+        @Synchronized
+        fun unpin(tracks: List<Track>) {
+            tracks.forEach(::unpin)
+        }
+
+        @Synchronized
+        fun delete(tracks: List<Track>) {
+            tracks.forEach(::delete)
         }
 
         fun unpin(track: Track) {
             // Update Pinned flag of items in progress
             downloadQueue.get(track.id)?.pinned = false
-            activeDownloads[track.id]?.track?.pinned = false
+            activeDownloads[track.id]?.downloadTrack?.pinned = false
             failedList[track.id]?.pinned = false
 
             val pinnedFile = track.getPinnedFile()
@@ -376,7 +389,7 @@ class DownloadService : Service(), KoinComponent {
             if (activeDownloads.contains(track.id)) return DownloadState.QUEUED
             if (downloadQueue.contains(track.id)) return DownloadState.QUEUED
 
-            val downloadableTrack = activeDownloads[track.id]?.track
+            val downloadableTrack = activeDownloads[track.id]?.downloadTrack
             if (downloadableTrack != null) {
                 if (downloadableTrack.tryCount > 0) return DownloadState.RETRYING
                 return DownloadState.DOWNLOADING
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadTask.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadTask.kt
index 4c1b0646..4cd4c7df 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadTask.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadTask.kt
@@ -36,7 +36,7 @@ private const val MAX_RETRIES = 5
 private const val REFRESH_INTERVAL = 50
 
 class DownloadTask(
-    private val item: DownloadableTrack,
+    val downloadTrack: DownloadableTrack,
     private val scope: CoroutineScope,
     private val stateChangedCallback: (DownloadableTrack, DownloadState, progress: Int?) -> Unit
 ) : KoinComponent {
@@ -49,38 +49,35 @@ class DownloadTask(
     private var outputStream: OutputStream? = null
     private var lastPostTime: Long = 0
 
-    val track: DownloadableTrack
-        get() = item
-
     private fun checkIfExists(): Boolean {
-        if (Storage.isPathExists(item.pinnedFile)) {
-            Timber.i("%s already exists. Skipping.", item.pinnedFile)
-            stateChangedCallback(item, DownloadState.PINNED, null)
+        if (Storage.isPathExists(downloadTrack.pinnedFile)) {
+            Timber.i("%s already exists. Skipping.", downloadTrack.pinnedFile)
+            stateChangedCallback(downloadTrack, DownloadState.PINNED, null)
             return true
         }
 
-        if (Storage.isPathExists(item.completeFile)) {
+        if (Storage.isPathExists(downloadTrack.completeFile)) {
             var newStatus: DownloadState = DownloadState.DONE
-            if (item.pinned) {
+            if (downloadTrack.pinned) {
                 Storage.rename(
-                    item.completeFile,
-                    item.pinnedFile
+                    downloadTrack.completeFile,
+                    downloadTrack.pinnedFile
                 )
                 newStatus = DownloadState.PINNED
             } else {
                 Timber.i(
                     "%s already exists. Skipping.",
-                    item.completeFile
+                    downloadTrack.completeFile
                 )
             }
 
             // Hidden feature: If track is toggled between pinned/saved, refresh the metadata..
             try {
-                item.track.cacheMetadataAndArtwork()
+                downloadTrack.track.cacheMetadataAndArtwork()
             } catch (ignore: Exception) {
                 Timber.w(ignore)
             }
-            stateChangedCallback(item, newStatus, null)
+            stateChangedCallback(downloadTrack, newStatus, null)
             return true
         }
 
@@ -88,15 +85,15 @@ class DownloadTask(
     }
 
     fun download() {
-        stateChangedCallback(item, DownloadState.DOWNLOADING, null)
+        stateChangedCallback(downloadTrack, DownloadState.DOWNLOADING, null)
 
-        val fileLength = Storage.getFromPath(item.partialFile)?.length ?: 0
+        val fileLength = Storage.getFromPath(downloadTrack.partialFile)?.length ?: 0
 
         // Attempt partial HTTP GET, appending to the file if it exists.
         val (inStream, isPartial) = musicService.getDownloadInputStream(
-            item.track, fileLength,
+            downloadTrack.track, fileLength,
             Settings.maxBitRate,
-            item.pinned
+            downloadTrack.pinned
         )
 
         inputStream = inStream
@@ -105,7 +102,7 @@ class DownloadTask(
             Timber.i("Executed partial HTTP GET, skipping %d bytes", fileLength)
         }
 
-        outputStream = Storage.getOrCreateFileFromPath(item.partialFile)
+        outputStream = Storage.getOrCreateFileFromPath(downloadTrack.partialFile)
             .getFileOutputStream(isPartial)
 
         val len = inputStream!!.copyWithProgress(outputStream!!) { totalBytesCopied ->
@@ -113,7 +110,7 @@ class DownloadTask(
             publishProgressUpdate(fileLength + totalBytesCopied)
         }
 
-        Timber.i("Downloaded %d bytes to %s", len, item.partialFile)
+        Timber.i("Downloaded %d bytes to %s", len, downloadTrack.partialFile)
 
         inputStream?.close()
         outputStream?.flush()
@@ -131,7 +128,7 @@ class DownloadTask(
             lastPostTime = SystemClock.elapsedRealtime()
 
             // If the file size is unknown we can only provide null as the progress
-            val size = item.track.size ?: 0
+            val size = downloadTrack.track.size ?: 0
             val progress = if (size <= 0) {
                 null
             } else {
@@ -139,7 +136,7 @@ class DownloadTask(
             }
 
             stateChangedCallback(
-                item,
+                downloadTrack,
                 DownloadState.DOWNLOADING,
                 progress
             )
@@ -148,39 +145,39 @@ class DownloadTask(
 
     private fun afterDownload() {
         try {
-            item.track.cacheMetadataAndArtwork()
+            downloadTrack.track.cacheMetadataAndArtwork()
         } catch (ignore: Exception) {
             Timber.w(ignore)
         }
 
-        if (item.pinned) {
+        if (downloadTrack.pinned) {
             Storage.rename(
-                item.partialFile,
-                item.pinnedFile
+                downloadTrack.partialFile,
+                downloadTrack.pinnedFile
             )
-            Timber.i("Renamed file to ${item.pinnedFile}")
-            stateChangedCallback(item, DownloadState.PINNED, null)
-            Util.scanMedia(item.pinnedFile)
+            Timber.i("Renamed file to ${downloadTrack.pinnedFile}")
+            stateChangedCallback(downloadTrack, DownloadState.PINNED, null)
+            Util.scanMedia(downloadTrack.pinnedFile)
         } else {
             Storage.rename(
-                item.partialFile,
-                item.completeFile
+                downloadTrack.partialFile,
+                downloadTrack.completeFile
             )
-            Timber.i("Renamed file to ${item.completeFile}")
-            stateChangedCallback(item, DownloadState.DONE, null)
+            Timber.i("Renamed file to ${downloadTrack.completeFile}")
+            stateChangedCallback(downloadTrack, DownloadState.DONE, null)
         }
     }
 
     private fun onCompletion(e: Throwable?) {
         if (e is CancellationException) {
-            Timber.w(e, "CompletionHandler ${item.pinnedFile}")
-            stateChangedCallback(item, DownloadState.CANCELLED, null)
+            Timber.w(e, "CompletionHandler ${downloadTrack.pinnedFile}")
+            stateChangedCallback(downloadTrack, DownloadState.CANCELLED, null)
         } else if (e != null) {
-            Timber.w(e, "CompletionHandler ${item.pinnedFile}")
-            if (item.tryCount < MAX_RETRIES) {
-                stateChangedCallback(item, DownloadState.RETRYING, null)
+            Timber.w(e, "CompletionHandler ${downloadTrack.pinnedFile}")
+            if (downloadTrack.tryCount < MAX_RETRIES) {
+                stateChangedCallback(downloadTrack, DownloadState.RETRYING, null)
             } else {
-                stateChangedCallback(item, DownloadState.FAILED, null)
+                stateChangedCallback(downloadTrack, DownloadState.FAILED, null)
             }
         }
         inputStream.safeClose()
@@ -189,15 +186,16 @@ class DownloadTask(
 
     private fun exceptionHandler(): CoroutineExceptionHandler {
         return CoroutineExceptionHandler { _, exception ->
-            Timber.w(exception, "Exception in DownloadTask ${item.pinnedFile}")
-            Storage.delete(item.completeFile)
-            Storage.delete(item.pinnedFile)
+            Timber.w(exception, "Exception in DownloadTask ${downloadTrack.pinnedFile}")
+            Storage.delete(downloadTrack.completeFile)
+            Storage.delete(downloadTrack.pinnedFile)
         }
     }
 
     fun start() {
-        Timber.i("Launching new Job ${item.pinnedFile}")
+        Timber.i("Launching new Job ${downloadTrack.pinnedFile}")
         job = scope.launch(exceptionHandler()) {
+            FileUtil.createDirectoryForParent(downloadTrack.pinnedFile)
             if (!checkIfExists() && isActive) {
                 download()
                 afterDownload()
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt
index fc92753e..b7f81c09 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt
@@ -418,13 +418,6 @@ class MediaPlayerController(
         }
     }
 
-    @Synchronized
-    fun downloadBackground(songs: List<Track?>?, save: Boolean) {
-        if (songs == null) return
-        val filteredSongs = songs.filterNotNull()
-        DownloadService.download(filteredSongs, save)
-    }
-
     @set:Synchronized
     var isShufflePlayEnabled: Boolean
         get() = controller?.shuffleModeEnabled == true
@@ -500,22 +493,6 @@ class MediaPlayerController(
         )
     }
 
-    @Synchronized
-    // TODO: Make it require not null
-    fun delete(tracks: List<Track?>) {
-        for (track in tracks.filterNotNull()) {
-            DownloadService.delete(track)
-        }
-    }
-
-    @Synchronized
-    // TODO: Make it require not null
-    fun unpin(tracks: List<Track?>) {
-        for (track in tracks.filterNotNull()) {
-            DownloadService.unpin(track)
-        }
-    }
-
     @Synchronized
     fun previous() {
         controller?.seekToPrevious()
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt
index 9ef03db7..286ce8f2 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt
@@ -20,6 +20,7 @@ import org.moire.ultrasonic.R
 import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
 import org.moire.ultrasonic.domain.MusicDirectory
 import org.moire.ultrasonic.domain.Track
+import org.moire.ultrasonic.service.DownloadService
 import org.moire.ultrasonic.service.MediaPlayerController
 import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
 import org.moire.ultrasonic.util.CommunicationError
@@ -266,7 +267,7 @@ class DownloadHandler(
         networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
         if (!background) {
             if (unpin) {
-                mediaPlayerController.unpin(songs)
+                DownloadService.unpin(songs)
             } else {
                 val insertionMode = when {
                     append -> MediaPlayerController.InsertionMode.APPEND
@@ -293,9 +294,9 @@ class DownloadHandler(
             }
         } else {
             if (unpin) {
-                mediaPlayerController.unpin(songs)
+                DownloadService.unpin(songs)
             } else {
-                mediaPlayerController.downloadBackground(songs, save)
+                DownloadService.download(songs, save)
             }
         }
     }
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt
index f9ade15f..95a887a9 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt
@@ -33,7 +33,7 @@ ImageLoaderProvider(val context: Context) :
     }
 
     init {
-        Timber.e("Prepping Loader")
+        Timber.d("Prepping Loader")
         // Populate the ImageLoader async & early
         launch {
             getImageLoader()