mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-22 09:04: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.androidContext
|
||||||
import org.koin.android.ext.koin.androidLogger
|
import org.koin.android.ext.koin.androidLogger
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.logger.Level
|
||||||
|
|
||||||
class App: Application(), KoinComponent {
|
class App: Application(), KoinComponent {
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
@ -32,7 +33,7 @@ class App: Application(), KoinComponent {
|
|||||||
val loggingEnabled = true
|
val loggingEnabled = true
|
||||||
|
|
||||||
initKoin(loggingEnabled) {
|
initKoin(loggingEnabled) {
|
||||||
androidLogger()
|
androidLogger(Level.NONE)
|
||||||
androidContext(this@App)
|
androidContext(this@App)
|
||||||
modules(appModule(loggingEnabled))
|
modules(appModule(loggingEnabled))
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import android.graphics.Bitmap
|
|||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.media.MediaScannerConnection
|
import android.media.MediaScannerConnection
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.compose.ui.graphics.asImageBitmap
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
import co.touchlab.kermit.Kermit
|
import co.touchlab.kermit.Kermit
|
||||||
import com.mpatric.mp3agic.Mp3File
|
import com.mpatric.mp3agic.Mp3File
|
||||||
@ -93,53 +94,65 @@ actual class Dir actual constructor(
|
|||||||
) {
|
) {
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
val songFile = File(trackDetails.outputFilePath)
|
val songFile = File(trackDetails.outputFilePath)
|
||||||
/*
|
try {
|
||||||
* Check , if Fetch was Used, File is saved Already, else write byteArray we Received
|
/*
|
||||||
* */
|
* Check , if Fetch was Used, File is saved Already, else write byteArray we Received
|
||||||
// if(!m4aFile.exists()) m4aFile.writeBytes(mp3ByteArray)
|
* */
|
||||||
|
if(!songFile.exists()) {
|
||||||
|
/*Make intermediate Dirs if they don't exist yet*/
|
||||||
|
songFile.parentFile.mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
when (trackDetails.outputFilePath.substringAfterLast('.')) {
|
if(mp3ByteArray.isNotEmpty()) songFile.writeBytes(mp3ByteArray)
|
||||||
".mp3" -> {
|
|
||||||
Mp3File(File(songFile.absolutePath))
|
when (trackDetails.outputFilePath.substringAfterLast('.')) {
|
||||||
.removeAllTags()
|
".mp3" -> {
|
||||||
.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 {
|
|
||||||
Mp3File(File(songFile.absolutePath))
|
Mp3File(File(songFile.absolutePath))
|
||||||
.removeAllTags()
|
.removeAllTags()
|
||||||
.setId3v1Tags(trackDetails)
|
.setId3v1Tags(trackDetails)
|
||||||
.setId3v2TagsAndSaveFile(trackDetails)
|
.setId3v2TagsAndSaveFile(trackDetails)
|
||||||
addToLibrary(songFile.absolutePath)
|
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.app.NotificationCompat
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import co.touchlab.kermit.Kermit
|
import co.touchlab.kermit.Kermit
|
||||||
import com.shabinder.common.di.Dir
|
import com.shabinder.common.di.*
|
||||||
import com.shabinder.common.di.FetchPlatformQueryResult
|
import com.shabinder.common.di.utils.ParallelExecutor
|
||||||
import com.shabinder.common.di.R
|
import com.shabinder.common.models.DownloadResult
|
||||||
import com.shabinder.common.di.getData
|
|
||||||
import com.shabinder.common.models.DownloadStatus
|
import com.shabinder.common.models.DownloadStatus
|
||||||
import com.shabinder.common.models.TrackDetails
|
import com.shabinder.common.models.TrackDetails
|
||||||
import com.shabinder.downloader.YoutubeDownloader
|
import com.shabinder.downloader.YoutubeDownloader
|
||||||
@ -59,6 +58,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -80,14 +80,14 @@ class ForegroundService : Service(), CoroutineScope {
|
|||||||
override val coroutineContext: CoroutineContext
|
override val coroutineContext: CoroutineContext
|
||||||
get() = serviceJob + Dispatchers.IO
|
get() = serviceJob + Dispatchers.IO
|
||||||
|
|
||||||
private val requestMap = hashMapOf<Request, TrackDetails>()
|
//private val requestMap = hashMapOf<Request, TrackDetails>()
|
||||||
private val allTracksStatus = hashMapOf<String, DownloadStatus>()
|
private val allTracksStatus = hashMapOf<String, DownloadStatus>()
|
||||||
private var wakeLock: PowerManager.WakeLock? = null
|
private var wakeLock: PowerManager.WakeLock? = null
|
||||||
private var isServiceStarted = false
|
private var isServiceStarted = false
|
||||||
private var messageList = mutableListOf("", "", "", "", "")
|
private var messageList = mutableListOf("", "", "", "", "")
|
||||||
private lateinit var cancelIntent: PendingIntent
|
private lateinit var cancelIntent: PendingIntent
|
||||||
private lateinit var downloadManager: DownloadManager
|
private lateinit var downloadManager: DownloadManager
|
||||||
|
private lateinit var downloadService: ParallelExecutor
|
||||||
private val fetcher: FetchPlatformQueryResult by inject()
|
private val fetcher: FetchPlatformQueryResult by inject()
|
||||||
private val logger: Kermit by inject()
|
private val logger: Kermit by inject()
|
||||||
private val fetch: Fetch by inject()
|
private val fetch: Fetch by inject()
|
||||||
@ -101,6 +101,7 @@ class ForegroundService : Service(), CoroutineScope {
|
|||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
serviceJob = SupervisorJob()
|
serviceJob = SupervisorJob()
|
||||||
|
downloadService = ParallelExecutor(Dispatchers.IO)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
createNotificationChannel(channelId, "Downloader Service")
|
createNotificationChannel(channelId, "Downloader Service")
|
||||||
}
|
}
|
||||||
@ -110,7 +111,7 @@ class ForegroundService : Service(), CoroutineScope {
|
|||||||
).apply { action = "kill" }
|
).apply { action = "kill" }
|
||||||
cancelIntent = PendingIntent.getService(this, 0, intent, FLAG_CANCEL_CURRENT)
|
cancelIntent = PendingIntent.getService(this, 0, intent, FLAG_CANCEL_CURRENT)
|
||||||
downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||||
fetch.removeAllListeners().addListener(fetchListener)
|
//fetch.removeAllListeners().addListener(fetchListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("WakelockTimeout")
|
@SuppressLint("WakelockTimeout")
|
||||||
@ -209,7 +210,72 @@ class ForegroundService : Service(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun enqueueDownload(url: String, track: TrackDetails) {
|
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
|
priority = Priority.NORMAL
|
||||||
networkType = NetworkType.ALL
|
networkType = NetworkType.ALL
|
||||||
}
|
}
|
||||||
@ -222,13 +288,13 @@ class ForegroundService : Service(), CoroutineScope {
|
|||||||
{ error ->
|
{ error ->
|
||||||
logger.d(tag) { "Enqueuing Error:${error.throwable}" }
|
logger.d(tag) { "Enqueuing Error:${error.throwable}" }
|
||||||
}
|
}
|
||||||
)
|
)*/
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch Listener/ Responsible for Fetch Behaviour
|
* Fetch Listener/ Responsible for Fetch Behaviour
|
||||||
**/
|
**/
|
||||||
private var fetchListener: FetchListener = object : FetchListener {
|
/*private var fetchListener: FetchListener = object : FetchListener {
|
||||||
override fun onQueued(
|
override fun onQueued(
|
||||||
download: Download,
|
download: Download,
|
||||||
waitingOnNetwork: Boolean
|
waitingOnNetwork: Boolean
|
||||||
@ -348,7 +414,7 @@ class ForegroundService : Service(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If fetch Fails , Android Download Manager To RESCUE!!
|
* If fetch Fails , Android Download Manager To RESCUE!!
|
||||||
@ -450,6 +516,7 @@ class ForegroundService : Service(), CoroutineScope {
|
|||||||
messageList = mutableListOf("Cleaning And Exiting", "", "", "", "")
|
messageList = mutableListOf("Cleaning And Exiting", "", "", "", "")
|
||||||
fetch.cancelAll()
|
fetch.cancelAll()
|
||||||
fetch.removeAll()
|
fetch.removeAll()
|
||||||
|
downloadService.close()
|
||||||
updateNotification()
|
updateNotification()
|
||||||
cleanFiles(File(dir.defaultDir()))
|
cleanFiles(File(dir.defaultDir()))
|
||||||
// TODO cleanFiles(File(dir.imageCacheDir()))
|
// TODO cleanFiles(File(dir.imageCacheDir()))
|
||||||
|
@ -58,7 +58,8 @@ suspend fun downloadFile(url: String): Flow<DownloadResult> {
|
|||||||
val data = ByteArray(response.contentLength()!!.toInt())
|
val data = ByteArray(response.contentLength()!!.toInt())
|
||||||
var offset = 0
|
var offset = 0
|
||||||
do {
|
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
|
offset += currentRead
|
||||||
val progress = (offset * 100f / data.size).roundToInt()
|
val progress = (offset * 100f / data.size).roundToInt()
|
||||||
emit(DownloadResult.Progress(progress))
|
emit(DownloadResult.Progress(progress))
|
||||||
|
@ -21,6 +21,7 @@ package com.shabinder.common.di.utils
|
|||||||
// implementation("org.jetbrains.kotlinx:atomicfu:0.14.4")
|
// implementation("org.jetbrains.kotlinx:atomicfu:0.14.4")
|
||||||
// Gist: https://gist.github.com/fluidsonic/ba32de21c156bbe8424c8d5fc20dcd8e
|
// Gist: https://gist.github.com/fluidsonic/ba32de21c156bbe8424c8d5fc20dcd8e
|
||||||
|
|
||||||
|
import com.shabinder.common.di.dispatcherIO
|
||||||
import io.ktor.utils.io.core.Closeable
|
import io.ktor.utils.io.core.Closeable
|
||||||
import kotlinx.atomicfu.atomic
|
import kotlinx.atomicfu.atomic
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
@ -36,7 +37,7 @@ import kotlinx.coroutines.withContext
|
|||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
class ParallelExecutor(
|
class ParallelExecutor(
|
||||||
parentContext: CoroutineContext,
|
parentContext: CoroutineContext = dispatcherIO,
|
||||||
) : Closeable {
|
) : Closeable {
|
||||||
|
|
||||||
private val concurrentOperationLimit = atomic(4)
|
private val concurrentOperationLimit = atomic(4)
|
||||||
|
Loading…
Reference in New Issue
Block a user