mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-22 17:14:32 +01:00
android DM Ktor
This commit is contained in:
parent
148d56956b
commit
149c9aceb3
@ -23,6 +23,7 @@ import com.shabinder.spotiflyer.di.appModule
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.android.ext.koin.androidLogger
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.logger.Level
|
||||
|
||||
class App: Application(), KoinComponent {
|
||||
override fun onCreate() {
|
||||
@ -32,7 +33,7 @@ class App: Application(), KoinComponent {
|
||||
val loggingEnabled = true
|
||||
|
||||
initKoin(loggingEnabled) {
|
||||
androidLogger()
|
||||
androidLogger(Level.NONE)
|
||||
androidContext(this@App)
|
||||
modules(appModule(loggingEnabled))
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.media.MediaScannerConnection
|
||||
import android.os.Environment
|
||||
import android.widget.Toast
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import co.touchlab.kermit.Kermit
|
||||
import com.mpatric.mp3agic.Mp3File
|
||||
@ -93,53 +94,65 @@ actual class Dir actual constructor(
|
||||
) {
|
||||
withContext(Dispatchers.IO){
|
||||
val songFile = File(trackDetails.outputFilePath)
|
||||
/*
|
||||
* Check , if Fetch was Used, File is saved Already, else write byteArray we Received
|
||||
* */
|
||||
// if(!m4aFile.exists()) m4aFile.writeBytes(mp3ByteArray)
|
||||
try {
|
||||
/*
|
||||
* Check , if Fetch was Used, File is saved Already, else write byteArray we Received
|
||||
* */
|
||||
if(!songFile.exists()) {
|
||||
/*Make intermediate Dirs if they don't exist yet*/
|
||||
songFile.parentFile.mkdirs()
|
||||
}
|
||||
|
||||
when (trackDetails.outputFilePath.substringAfterLast('.')) {
|
||||
".mp3" -> {
|
||||
Mp3File(File(songFile.absolutePath))
|
||||
.removeAllTags()
|
||||
.setId3v1Tags(trackDetails)
|
||||
.setId3v2TagsAndSaveFile(trackDetails)
|
||||
addToLibrary(songFile.absolutePath)
|
||||
}
|
||||
".m4a" -> {
|
||||
/*FFmpeg.executeAsync(
|
||||
"-i ${m4aFile.absolutePath} -y -b:a 160k -acodec libmp3lame -vn ${m4aFile.absolutePath.substringBeforeLast('.') + ".mp3"}"
|
||||
){ _, returnCode ->
|
||||
when (returnCode) {
|
||||
Config.RETURN_CODE_SUCCESS -> {
|
||||
//FFMPEG task Completed
|
||||
logger.d{ "Async command execution completed successfully." }
|
||||
scope.launch {
|
||||
Mp3File(File(m4aFile.absolutePath.substringBeforeLast('.') + ".mp3"))
|
||||
.removeAllTags()
|
||||
.setId3v1Tags(trackDetails)
|
||||
.setId3v2TagsAndSaveFile(trackDetails)
|
||||
addToLibrary(m4aFile.absolutePath.substringBeforeLast('.') + ".mp3")
|
||||
}
|
||||
}
|
||||
Config.RETURN_CODE_CANCEL -> {
|
||||
logger.d{"Async command execution cancelled by user."}
|
||||
}
|
||||
else -> {
|
||||
logger.d { "Async command execution failed with rc=$returnCode" }
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
else -> {
|
||||
try {
|
||||
if(mp3ByteArray.isNotEmpty()) songFile.writeBytes(mp3ByteArray)
|
||||
|
||||
when (trackDetails.outputFilePath.substringAfterLast('.')) {
|
||||
".mp3" -> {
|
||||
Mp3File(File(songFile.absolutePath))
|
||||
.removeAllTags()
|
||||
.setId3v1Tags(trackDetails)
|
||||
.setId3v2TagsAndSaveFile(trackDetails)
|
||||
addToLibrary(songFile.absolutePath)
|
||||
} catch (e: Exception) { e.printStackTrace() }
|
||||
}
|
||||
".m4a" -> {
|
||||
/*FFmpeg.executeAsync(
|
||||
"-i ${m4aFile.absolutePath} -y -b:a 160k -acodec libmp3lame -vn ${m4aFile.absolutePath.substringBeforeLast('.') + ".mp3"}"
|
||||
){ _, returnCode ->
|
||||
when (returnCode) {
|
||||
Config.RETURN_CODE_SUCCESS -> {
|
||||
//FFMPEG task Completed
|
||||
logger.d{ "Async command execution completed successfully." }
|
||||
scope.launch {
|
||||
Mp3File(File(m4aFile.absolutePath.substringBeforeLast('.') + ".mp3"))
|
||||
.removeAllTags()
|
||||
.setId3v1Tags(trackDetails)
|
||||
.setId3v2TagsAndSaveFile(trackDetails)
|
||||
addToLibrary(m4aFile.absolutePath.substringBeforeLast('.') + ".mp3")
|
||||
}
|
||||
}
|
||||
Config.RETURN_CODE_CANCEL -> {
|
||||
logger.d{"Async command execution cancelled by user."}
|
||||
}
|
||||
else -> {
|
||||
logger.d { "Async command execution failed with rc=$returnCode" }
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
else -> {
|
||||
try {
|
||||
Mp3File(File(songFile.absolutePath))
|
||||
.removeAllTags()
|
||||
.setId3v1Tags(trackDetails)
|
||||
.setId3v2TagsAndSaveFile(trackDetails)
|
||||
addToLibrary(songFile.absolutePath)
|
||||
} catch (e: Exception) { e.printStackTrace() }
|
||||
}
|
||||
}
|
||||
}catch (e:Exception){
|
||||
withContext(Dispatchers.Main){
|
||||
Toast.makeText(appContext,"Could Not Create File:\n${songFile.absolutePath}",Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
logger.e { "${songFile.absolutePath} could not be created" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,10 +38,9 @@ import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.net.toUri
|
||||
import co.touchlab.kermit.Kermit
|
||||
import com.shabinder.common.di.Dir
|
||||
import com.shabinder.common.di.FetchPlatformQueryResult
|
||||
import com.shabinder.common.di.R
|
||||
import com.shabinder.common.di.getData
|
||||
import com.shabinder.common.di.*
|
||||
import com.shabinder.common.di.utils.ParallelExecutor
|
||||
import com.shabinder.common.models.DownloadResult
|
||||
import com.shabinder.common.models.DownloadStatus
|
||||
import com.shabinder.common.models.TrackDetails
|
||||
import com.shabinder.downloader.YoutubeDownloader
|
||||
@ -59,6 +58,7 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
import java.io.File
|
||||
@ -80,14 +80,14 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = serviceJob + Dispatchers.IO
|
||||
|
||||
private val requestMap = hashMapOf<Request, TrackDetails>()
|
||||
//private val requestMap = hashMapOf<Request, TrackDetails>()
|
||||
private val allTracksStatus = hashMapOf<String, DownloadStatus>()
|
||||
private var wakeLock: PowerManager.WakeLock? = null
|
||||
private var isServiceStarted = false
|
||||
private var messageList = mutableListOf("", "", "", "", "")
|
||||
private lateinit var cancelIntent: PendingIntent
|
||||
private lateinit var downloadManager: DownloadManager
|
||||
|
||||
private lateinit var downloadService: ParallelExecutor
|
||||
private val fetcher: FetchPlatformQueryResult by inject()
|
||||
private val logger: Kermit by inject()
|
||||
private val fetch: Fetch by inject()
|
||||
@ -101,6 +101,7 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
serviceJob = SupervisorJob()
|
||||
downloadService = ParallelExecutor(Dispatchers.IO)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
createNotificationChannel(channelId, "Downloader Service")
|
||||
}
|
||||
@ -110,7 +111,7 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
).apply { action = "kill" }
|
||||
cancelIntent = PendingIntent.getService(this, 0, intent, FLAG_CANCEL_CURRENT)
|
||||
downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
fetch.removeAllListeners().addListener(fetchListener)
|
||||
//fetch.removeAllListeners().addListener(fetchListener)
|
||||
}
|
||||
|
||||
@SuppressLint("WakelockTimeout")
|
||||
@ -209,7 +210,72 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
}
|
||||
|
||||
private fun enqueueDownload(url: String, track: TrackDetails) {
|
||||
val request = Request(url, track.outputFilePath).apply {
|
||||
// Initiating Download
|
||||
addToNotification("Downloading ${track.title}")
|
||||
logger.d(tag) { "${track.title} Download Started" }
|
||||
allTracksStatus[track.title] = DownloadStatus.Downloading()
|
||||
sendTrackBroadcast(Status.DOWNLOADING.name, track)
|
||||
|
||||
// Enqueueing Download
|
||||
launch {
|
||||
downloadService.execute {
|
||||
downloadFile(url).collect {
|
||||
when (it) {
|
||||
is DownloadResult.Error -> {
|
||||
launch {
|
||||
logger.d(tag) { it.message }
|
||||
logger.d(tag) { "${track.title} Requesting Download thru Android DM" }
|
||||
downloadUsingDM(url, track.outputFilePath, track)
|
||||
removeFromNotification("Downloading ${track.title}")
|
||||
downloaded++
|
||||
}
|
||||
updateNotification()
|
||||
sendTrackBroadcast(Status.FAILED.name,track)
|
||||
}
|
||||
|
||||
is DownloadResult.Progress -> {
|
||||
allTracksStatus[track.title] = DownloadStatus.Downloading(it.progress)
|
||||
logger.d(tag) { "${track.title} Progress: ${it.progress} %" }
|
||||
|
||||
val intent = Intent().apply {
|
||||
action = "Progress"
|
||||
putExtra("progress", it.progress)
|
||||
putExtra("track", track)
|
||||
}
|
||||
sendBroadcast(intent)
|
||||
}
|
||||
|
||||
is DownloadResult.Success -> { // Todo clear map
|
||||
try {
|
||||
// Save File and Embed Metadata
|
||||
val job = launch(Dispatchers.Default) { dir.saveFileWithMetadata(it.byteArray, track) }
|
||||
allTracksStatus[track.title] = DownloadStatus.Converting
|
||||
sendTrackBroadcast("Converting", track)
|
||||
addToNotification("Processing ${track.title}")
|
||||
job.invokeOnCompletion { _ ->
|
||||
converted++
|
||||
allTracksStatus[track.title] = DownloadStatus.Downloaded
|
||||
sendTrackBroadcast(Status.COMPLETED.name, track)
|
||||
removeFromNotification("Processing ${track.title}")
|
||||
}
|
||||
logger.d(tag) { "${track.title} Download Completed" }
|
||||
} catch (
|
||||
e: KotlinNullPointerException
|
||||
) {
|
||||
// Try downloading using android DM
|
||||
logger.d(tag) { "${track.title} Download Failed! Error:Fetch!!!!" }
|
||||
logger.d(tag) { "${track.title} Requesting Download thru Android DM" }
|
||||
downloadUsingDM(url, track.outputFilePath, track)
|
||||
}
|
||||
downloaded++
|
||||
removeFromNotification("Downloading ${track.title}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* val request = Request(url, track.outputFilePath).apply {
|
||||
priority = Priority.NORMAL
|
||||
networkType = NetworkType.ALL
|
||||
}
|
||||
@ -222,13 +288,13 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
{ error ->
|
||||
logger.d(tag) { "Enqueuing Error:${error.throwable}" }
|
||||
}
|
||||
)
|
||||
)*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch Listener/ Responsible for Fetch Behaviour
|
||||
**/
|
||||
private var fetchListener: FetchListener = object : FetchListener {
|
||||
/*private var fetchListener: FetchListener = object : FetchListener {
|
||||
override fun onQueued(
|
||||
download: Download,
|
||||
waitingOnNetwork: Boolean
|
||||
@ -348,7 +414,7 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
/**
|
||||
* If fetch Fails , Android Download Manager To RESCUE!!
|
||||
@ -450,6 +516,7 @@ class ForegroundService : Service(), CoroutineScope {
|
||||
messageList = mutableListOf("Cleaning And Exiting", "", "", "", "")
|
||||
fetch.cancelAll()
|
||||
fetch.removeAll()
|
||||
downloadService.close()
|
||||
updateNotification()
|
||||
cleanFiles(File(dir.defaultDir()))
|
||||
// TODO cleanFiles(File(dir.imageCacheDir()))
|
||||
|
@ -58,7 +58,8 @@ suspend fun downloadFile(url: String): Flow<DownloadResult> {
|
||||
val data = ByteArray(response.contentLength()!!.toInt())
|
||||
var offset = 0
|
||||
do {
|
||||
val currentRead = response.content.readAvailable(data, offset, data.size)
|
||||
// Set Length optimally, after how many kb you want a progress update, now it 0.25mb
|
||||
val currentRead = response.content.readAvailable(data, offset, 250000)
|
||||
offset += currentRead
|
||||
val progress = (offset * 100f / data.size).roundToInt()
|
||||
emit(DownloadResult.Progress(progress))
|
||||
|
@ -21,6 +21,7 @@ package com.shabinder.common.di.utils
|
||||
// implementation("org.jetbrains.kotlinx:atomicfu:0.14.4")
|
||||
// Gist: https://gist.github.com/fluidsonic/ba32de21c156bbe8424c8d5fc20dcd8e
|
||||
|
||||
import com.shabinder.common.di.dispatcherIO
|
||||
import io.ktor.utils.io.core.Closeable
|
||||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.coroutines.CancellationException
|
||||
@ -36,7 +37,7 @@ import kotlinx.coroutines.withContext
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class ParallelExecutor(
|
||||
parentContext: CoroutineContext,
|
||||
parentContext: CoroutineContext = dispatcherIO,
|
||||
) : Closeable {
|
||||
|
||||
private val concurrentOperationLimit = atomic(4)
|
||||
|
Loading…
Reference in New Issue
Block a user