Migrating to youtube-api-dl

This commit is contained in:
shabinder 2021-04-16 22:43:45 +05:30
parent c9696fa4aa
commit e3c3a6cb6c
14 changed files with 186 additions and 447 deletions

View File

@ -25,6 +25,7 @@ allprojects {
google()
jcenter()
mavenCentral()
mavenLocal()
maven(url = "https://jitpack.io")
maven(url = "https://dl.bintray.com/ekito/koin")
maven(url = "https://kotlin.bintray.com/kotlinx/")

View File

@ -136,7 +136,8 @@ object Ktor {
}
object Extras {
const val youtubeDownloader = "com.github.sealedtx:java-youtube-downloader:2.5.1"
//const val youtubeDownloader = "com.github.sealedtx:java-youtube-downloader:2.5.1"
const val youtubeDownloader = "com.shabinder.downloader:youtube-api-dl:0.1-SNAPSHOT" //Local Maven
const val fuzzyWuzzy = "me.xdrop:fuzzywuzzy:1.3.1"
const val mp3agic = "com.mpatric:mp3agic:0.9.1"
const val kermit = "co.touchlab:kermit:${Versions.kermit}"

View File

@ -26,20 +26,17 @@ import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.material.ripple.RippleAlpha
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
@ -47,7 +44,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.arkivanov.decompose.extensions.compose.jetbrains.Children

View File

@ -27,7 +27,7 @@ kotlin {
dependencies {
api("dev.icerock.moko:parcelize:0.6.0")
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3-native-mt")
}
}
}

View File

@ -37,6 +37,7 @@ kotlin {
implementation(Ktor.clientLogging)
implementation(Ktor.clientJson)
implementation(Ktor.auth)
api(Extras.youtubeDownloader)
// koin
api(Koin.core)
api(Koin.test)
@ -50,7 +51,6 @@ kotlin {
implementation(Ktor.clientAndroid)
implementation(Extras.Android.fetch)
implementation(Extras.Android.razorpay)
api(Extras.youtubeDownloader)
api(Extras.mp3agic)
// api(files("$rootDir/libs/mobile-ffmpeg.aar"))
}
@ -60,7 +60,6 @@ kotlin {
implementation(compose.materialIconsExtended)
implementation(Ktor.clientApache)
implementation(Ktor.slf4j)
api(Extras.youtubeDownloader)
api(Extras.mp3agic)
}
}

View File

@ -1,188 +0,0 @@
/*
* Copyright (c) 2021 Shabinder Singh
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.shabinder.common.di
import co.touchlab.kermit.Kermit
import com.github.kiulian.downloader.YoutubeDownloader
import com.shabinder.common.di.utils.removeIllegalChars
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.spotify.Source
import io.ktor.client.HttpClient
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
actual class YoutubeProvider actual constructor(
private val httpClient: HttpClient,
private val logger: Kermit,
private val dir: Dir,
) {
val ytDownloader: YoutubeDownloader = YoutubeDownloader()
/*
* YT Album Art Schema
* HI-RES Url: https://i.ytimg.com/vi/$searchId/maxresdefault.jpg"
* Normal Url: https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
* */
private val sampleDomain1 = "music.youtube.com"
private val sampleDomain2 = "youtube.com"
private val sampleDomain3 = "youtu.be"
actual suspend fun query(fullLink: String): PlatformQueryResult? = withContext(Dispatchers.IO) {
val link = fullLink.removePrefix("https://").removePrefix("http://")
if (link.contains("playlist", true) || link.contains("list", true)) {
// Given Link is of a Playlist
logger.i { link }
val playlistId = link.substringAfter("?list=").substringAfter("&list=").substringBefore("&").substringBefore("?")
getYTPlaylist(
playlistId
)
} else { // Given Link is of a Video
var searchId = "error"
when {
link.contains(sampleDomain1, true) -> { // Youtube Music
searchId = link.substringAfterLast("/", "error").substringBefore("&").substringAfterLast("=")
}
link.contains(sampleDomain2, true) -> { // Standard Youtube Link
searchId = link.substringAfterLast("=", "error").substringBefore("&")
}
link.contains(sampleDomain3, true) -> { // Shortened Youtube Link
searchId = link.substringAfterLast("/", "error").substringBefore("&")
}
}
if (searchId != "error") {
getYTTrack(
searchId
)
} else {
logger.d { "Your Youtube Link is not of a Video!!" }
null
}
}
}
private suspend fun getYTPlaylist(
searchId: String
): PlatformQueryResult? = withContext(Dispatchers.IO) {
val result = PlatformQueryResult(
folderType = "",
subFolder = "",
title = "",
coverUrl = "",
trackList = listOf(),
Source.YouTube
)
result.apply {
try {
val playlist = ytDownloader.getPlaylist(searchId)
val playlistDetails = playlist.details()
val name = playlistDetails.title()
subFolder = removeIllegalChars(name)
val videos = playlist.videos()
coverUrl = "https://i.ytimg.com/vi/${
videos.firstOrNull()?.videoId()
}/hqdefault.jpg"
title = name
trackList = videos.map {
TrackDetails(
title = it.title(),
artists = listOf(it.author().toString()),
durationSec = it.lengthSeconds(),
albumArtPath = dir.imageCacheDir() + it.videoId() + ".jpeg",
source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/${it.videoId()}/hqdefault.jpg",
downloaded = if (dir.isPresent(
dir.finalOutputDir(
itemName = it.title(),
type = folderType,
subFolder = subFolder,
dir.defaultDir()
)
)
)
DownloadStatus.Downloaded
else {
DownloadStatus.NotDownloaded
},
outputFilePath = dir.finalOutputDir(it.title(), folderType, subFolder, dir.defaultDir()/*,".m4a"*/),
videoID = it.videoId()
)
}
} catch (e: Exception) {
e.printStackTrace()
logger.d { "An Error Occurred While Processing!" }
}
}
if (result.title.isNotBlank()) result else null
}
@Suppress("DefaultLocale")
private suspend fun getYTTrack(
searchId: String,
): PlatformQueryResult? = withContext(Dispatchers.IO) {
val result = PlatformQueryResult(
folderType = "",
subFolder = "",
title = "",
coverUrl = "",
trackList = listOf(),
Source.YouTube
).apply {
try {
logger.i { searchId }
val video = ytDownloader.getVideo(searchId)
coverUrl = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
val detail = video?.details()
val name = detail?.title()?.replace(detail.author()!!.toUpperCase(), "", true)
?: detail?.title() ?: ""
// logger.i{ detail.toString() }
trackList = listOf(
TrackDetails(
title = name,
artists = listOf(detail?.author().toString()),
durationSec = detail?.lengthSeconds() ?: 0,
albumArtPath = dir.imageCacheDir() + "$searchId.jpeg",
source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg",
downloaded = if (dir.isPresent(
dir.finalOutputDir(
itemName = name,
type = folderType,
subFolder = subFolder,
defaultDir = dir.defaultDir()
)
)
)
DownloadStatus.Downloaded
else {
DownloadStatus.NotDownloaded
},
outputFilePath = dir.finalOutputDir(name, folderType, subFolder, dir.defaultDir()/*,".m4a"*/),
videoID = searchId
)
)
title = name
} catch (e: Exception) {
e.printStackTrace()
logger.e { "An Error Occurred While Processing!,$searchId" }
}
}
if (result.title.isNotBlank()) result else null
}
}

View File

@ -38,14 +38,14 @@ import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.net.toUri
import co.touchlab.kermit.Kermit
import com.github.kiulian.downloader.YoutubeDownloader
import com.github.kiulian.downloader.model.formats.Format
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.models.DownloadStatus
import com.shabinder.common.models.TrackDetails
import com.shabinder.downloader.YoutubeDownloader
import com.shabinder.downloader.models.formats.Format
import com.tonyodev.fetch2.Download
import com.tonyodev.fetch2.Error
import com.tonyodev.fetch2.Fetch
@ -198,7 +198,7 @@ class ForegroundService : Service(), CoroutineScope {
val url = fetcher.youtubeMp3.getMp3DownloadLink(videoID)
if (url == null) {
val audioData: Format = ytDownloader.getVideo(videoID).getData() ?: throw Exception("Java YT Dependency Error")
val ytUrl: String = audioData.url()
val ytUrl = audioData.url!! //We Will catch NPE
enqueueDownload(ytUrl, track)
} else enqueueDownload(url, track)
} catch (e: Exception) {

View File

@ -17,13 +17,182 @@
package com.shabinder.common.di
import co.touchlab.kermit.Kermit
import com.shabinder.common.di.utils.removeIllegalChars
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.spotify.Source
import com.shabinder.downloader.YoutubeDownloader
import com.shabinder.downloader.models.YoutubeVideo
import com.shabinder.downloader.models.formats.Format
import com.shabinder.downloader.models.quality.AudioQuality
import io.ktor.client.HttpClient
expect class YoutubeProvider(
httpClient: HttpClient,
logger: Kermit,
dir: Dir
class YoutubeProvider(
private val httpClient: HttpClient,
private val logger: Kermit,
private val dir: Dir,
) {
suspend fun query(fullLink: String): PlatformQueryResult?
val ytDownloader: YoutubeDownloader = YoutubeDownloader()
/*
* YT Album Art Schema
* HI-RES Url: https://i.ytimg.com/vi/$searchId/maxresdefault.jpg"
* Normal Url: https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
* */
private val sampleDomain1 = "music.youtube.com"
private val sampleDomain2 = "youtube.com"
private val sampleDomain3 = "youtu.be"
suspend fun query(fullLink: String): PlatformQueryResult? {
val link = fullLink.removePrefix("https://").removePrefix("http://")
if (link.contains("playlist", true) || link.contains("list", true)) {
// Given Link is of a Playlist
logger.i { link }
val playlistId = link.substringAfter("?list=").substringAfter("&list=").substringBefore("&").substringBefore("?")
return getYTPlaylist(
playlistId
)
} else { // Given Link is of a Video
var searchId = "error"
when {
link.contains(sampleDomain1, true) -> { // Youtube Music
searchId = link.substringAfterLast("/", "error").substringBefore("&").substringAfterLast("=")
}
link.contains(sampleDomain2, true) -> { // Standard Youtube Link
searchId = link.substringAfterLast("=", "error").substringBefore("&")
}
link.contains(sampleDomain3, true) -> { // Shortened Youtube Link
searchId = link.substringAfterLast("/", "error").substringBefore("&")
}
}
return if (searchId != "error") {
getYTTrack(
searchId
)
} else {
logger.d { "Your Youtube Link is not of a Video!!" }
null
}
}
}
private suspend fun getYTPlaylist(
searchId: String
): PlatformQueryResult? {
val result = PlatformQueryResult(
folderType = "",
subFolder = "",
title = "",
coverUrl = "",
trackList = listOf(),
Source.YouTube
)
result.apply {
try {
val playlist = ytDownloader.getPlaylist(searchId)
val playlistDetails = playlist.details
val name = playlistDetails.title
subFolder = removeIllegalChars(name)
val videos = playlist.videos
coverUrl = "https://i.ytimg.com/vi/${
videos.firstOrNull()?.videoId
}/hqdefault.jpg"
title = name
trackList = videos.map {
TrackDetails(
title = it.title ?: "N/A",
artists = listOf(it.author ?: "N/A"),
durationSec = it.lengthSeconds,
albumArtPath = dir.imageCacheDir() + it.videoId + ".jpeg",
source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/${it.videoId}/hqdefault.jpg",
downloaded = if (dir.isPresent(
dir.finalOutputDir(
itemName = it.title ?: "N/A",
type = folderType,
subFolder = subFolder,
dir.defaultDir()
)
)
)
DownloadStatus.Downloaded
else {
DownloadStatus.NotDownloaded
},
outputFilePath = dir.finalOutputDir(it.title ?: "N/A", folderType, subFolder, dir.defaultDir()/*,".m4a"*/),
videoID = it.videoId
)
}
} catch (e: Exception) {
e.printStackTrace()
logger.d { "An Error Occurred While Processing!" }
}
}
return if (result.title.isNotBlank()) result
else null
}
@Suppress("DefaultLocale")
private suspend fun getYTTrack(
searchId: String,
): PlatformQueryResult? {
val result = PlatformQueryResult(
folderType = "",
subFolder = "",
title = "",
coverUrl = "",
trackList = listOf(),
Source.YouTube
).apply {
try {
logger.i { searchId }
val video = ytDownloader.getVideo(searchId)
coverUrl = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
val detail = video.videoDetails
val name = detail.title?.replace(detail.author?.toUpperCase() ?: "", "", true)
?: detail.title ?: ""
// logger.i{ detail.toString() }
trackList = listOf(
TrackDetails(
title = name,
artists = listOf(detail.author ?: "N/A"),
durationSec = detail.lengthSeconds,
albumArtPath = dir.imageCacheDir() + "$searchId.jpeg",
source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg",
downloaded = if (dir.isPresent(
dir.finalOutputDir(
itemName = name,
type = folderType,
subFolder = subFolder,
defaultDir = dir.defaultDir()
)
)
)
DownloadStatus.Downloaded
else {
DownloadStatus.NotDownloaded
},
outputFilePath = dir.finalOutputDir(name, folderType, subFolder, dir.defaultDir()/*,".m4a"*/),
videoID = searchId
)
)
title = name
} catch (e: Exception) {
e.printStackTrace()
logger.e { "An Error Occurred While Processing!,$searchId" }
}
}
return if (result.title.isNotBlank()) result
else null
}
}
fun YoutubeVideo.getData(): Format? {
return getAudioWithQuality(AudioQuality.high).getOrNull(0)
?: getAudioWithQuality(AudioQuality.medium).getOrNull(0)
?: getAudioWithQuality(AudioQuality.low).getOrNull(0)
}

View File

@ -16,15 +16,12 @@
package com.shabinder.common.di
import com.github.kiulian.downloader.YoutubeDownloader
import com.github.kiulian.downloader.model.YoutubeVideo
import com.github.kiulian.downloader.model.formats.Format
import com.github.kiulian.downloader.model.quality.AudioQuality
import com.shabinder.common.di.utils.ParallelExecutor
import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.DownloadResult
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.TrackDetails
import com.shabinder.downloader.YoutubeDownloader
import io.ktor.client.request.head
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
@ -115,7 +112,7 @@ suspend fun downloadTrack(
val audioData = ytDownloader.getVideo(videoID).getData()
audioData?.let { format ->
val url: String = format.url()
val url = format.url ?: return
downloadFile(url).collect {
when (it) {
is DownloadResult.Error -> {
@ -147,18 +144,3 @@ suspend fun downloadTrack(
e.printStackTrace()
}
}
fun YoutubeVideo.getData(): Format? {
return try {
findAudioWithQuality(AudioQuality.medium)?.get(0) as Format
} catch (e: java.lang.IndexOutOfBoundsException) {
try {
findAudioWithQuality(AudioQuality.high)?.get(0) as Format
} catch (e: java.lang.IndexOutOfBoundsException) {
try {
findAudioWithQuality(AudioQuality.low)?.get(0) as Format
} catch (e: java.lang.IndexOutOfBoundsException) {
null
}
}
}
}

View File

@ -1,189 +0,0 @@
/*
* Copyright (c) 2021 Shabinder Singh
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.shabinder.common.di
import co.touchlab.kermit.Kermit
import com.github.kiulian.downloader.YoutubeDownloader
import com.shabinder.common.di.utils.removeIllegalChars
import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.spotify.Source
import io.ktor.client.HttpClient
actual class YoutubeProvider actual constructor(
private val httpClient: HttpClient,
private val logger: Kermit,
private val dir: Dir,
) {
private val ytDownloader: YoutubeDownloader = YoutubeDownloader()
/*
* YT Album Art Schema
* HI-RES Url: https://i.ytimg.com/vi/$searchId/maxresdefault.jpg"
* Normal Url: https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
* */
private val sampleDomain1 = "music.youtube.com"
private val sampleDomain2 = "youtube.com"
private val sampleDomain3 = "youtu.be"
actual suspend fun query(fullLink: String): PlatformQueryResult? {
val link = fullLink.removePrefix("https://").removePrefix("http://")
if (link.contains("playlist", true) || link.contains("list", true)) {
// Given Link is of a Playlist
logger.i { link }
val playlistId = link.substringAfter("?list=").substringAfter("&list=").substringBefore("&").substringBefore("?")
return getYTPlaylist(
playlistId
)
} else { // Given Link is of a Video
var searchId = "error"
when {
link.contains(sampleDomain1, true) -> { // Youtube Music
searchId = link.substringAfterLast("/", "error").substringBefore("&").substringAfterLast("=")
}
link.contains(sampleDomain2, true) -> { // Standard Youtube Link
searchId = link.substringAfterLast("=", "error").substringBefore("&")
}
link.contains(sampleDomain3, true) -> { // Shortened Youtube Link
searchId = link.substringAfterLast("/", "error").substringBefore("&")
}
}
return if (searchId != "error") {
getYTTrack(
searchId
)
} else {
logger.d { "Your Youtube Link is not of a Video!!" }
null
}
}
}
private suspend fun getYTPlaylist(
searchId: String
): PlatformQueryResult? {
val result = PlatformQueryResult(
folderType = "",
subFolder = "",
title = "",
coverUrl = "",
trackList = listOf(),
Source.YouTube
)
result.apply {
try {
val playlist = ytDownloader.getPlaylist(searchId)
val playlistDetails = playlist.details()
val name = playlistDetails.title()
subFolder = removeIllegalChars(name)
val videos = playlist.videos()
coverUrl = "https://i.ytimg.com/vi/${
videos.firstOrNull()?.videoId()
}/hqdefault.jpg"
title = name
trackList = videos.map {
TrackDetails(
title = it.title(),
artists = listOf(it.author().toString()),
durationSec = it.lengthSeconds(),
albumArtPath = dir.imageCacheDir() + it.videoId() + ".jpeg",
source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/${it.videoId()}/hqdefault.jpg",
downloaded = if (dir.isPresent(
dir.finalOutputDir(
itemName = it.title(),
type = folderType,
subFolder = subFolder,
dir.defaultDir()
)
)
)
DownloadStatus.Downloaded
else {
DownloadStatus.NotDownloaded
},
outputFilePath = dir.finalOutputDir(it.title(), folderType, subFolder, dir.defaultDir()/*,".m4a"*/),
videoID = it.videoId()
)
}
} catch (e: Exception) {
e.printStackTrace()
logger.d { "An Error Occurred While Processing!" }
}
}
return if (result.title.isNotBlank()) result
else null
}
@Suppress("DefaultLocale")
private suspend fun getYTTrack(
searchId: String,
): PlatformQueryResult? {
val result = PlatformQueryResult(
folderType = "",
subFolder = "",
title = "",
coverUrl = "",
trackList = listOf(),
Source.YouTube
).apply {
try {
logger.i { searchId }
val video = ytDownloader.getVideo(searchId)
coverUrl = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg"
val detail = video?.details()
val name = detail?.title()?.replace(detail.author()!!.toUpperCase(), "", true)
?: detail?.title() ?: ""
// logger.i{ detail.toString() }
trackList = listOf(
TrackDetails(
title = name,
artists = listOf(detail?.author().toString()),
durationSec = detail?.lengthSeconds() ?: 0,
albumArtPath = dir.imageCacheDir() + "$searchId.jpeg",
source = Source.YouTube,
albumArtURL = "https://i.ytimg.com/vi/$searchId/hqdefault.jpg",
downloaded = if (dir.isPresent(
dir.finalOutputDir(
itemName = name,
type = folderType,
subFolder = subFolder,
defaultDir = dir.defaultDir()
)
)
)
DownloadStatus.Downloaded
else {
DownloadStatus.NotDownloaded
},
outputFilePath = dir.finalOutputDir(name, folderType, subFolder, dir.defaultDir()/*,".m4a"*/),
videoID = searchId
)
)
title = name
} catch (e: Exception) {
e.printStackTrace()
logger.e { "An Error Occurred While Processing!,$searchId" }
}
}
return if (result.title.isNotBlank()) result
else null
}
}

View File

@ -1,31 +0,0 @@
/*
* * Copyright (c) 2021 Shabinder Singh
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.shabinder.common.di
import co.touchlab.kermit.Kermit
import com.shabinder.common.models.PlatformQueryResult
import io.ktor.client.HttpClient
actual class YoutubeProvider actual constructor(
httpClient: HttpClient,
logger: Kermit,
dir: Dir
) {
actual suspend fun query(fullLink: String): PlatformQueryResult? {
return null // TODO
}
}

View File

@ -14,7 +14,6 @@
* * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import co.touchlab.kermit.Kermit
import com.arkivanov.decompose.DefaultComponentContext
import com.arkivanov.decompose.lifecycle.LifecycleRegistry
import com.arkivanov.decompose.lifecycle.destroy

View File

@ -32,7 +32,7 @@ class RootR(props: Props<SpotiFlyerRoot>) : RenderableRootComponent<SpotiFlyerRo
initialState = State(routerState = props.model.routerState.value)
) {
private val component: Child
get() = model.routerState.value.activeChild.component
get() = model.routerState.value.activeChild.instance
private val callBacks get() = model.callBacks