From 099a103e987e3d81b78b14e4456ae8c823695d6f Mon Sep 17 00:00:00 2001 From: Shabinder Date: Sat, 7 Nov 2020 03:56:10 +0530 Subject: [PATCH] No More Web Scraping!,Speed Boosted& Less Crashes. --- .idea/dictionaries/shabinder.xml | 1 + app/build.gradle | 5 +- .../com/shabinder/spotiflyer/MainActivity.kt | 6 +- .../downloadHelper/SpotifyDownloadHelper.kt | 153 ++++---------- .../downloadHelper/YoutubeProvider.kt | 190 ++++++++++++++++++ .../spotiflyer/models/YoutubeTrack.kt | 29 +++ .../spotiflyer/ui/spotify/SpotifyFragment.kt | 9 +- .../shabinder/spotiflyer/utils/Provider.kt | 15 ++ .../spotiflyer/utils/YoutubeMusicApi.kt | 58 ++++++ .../spotiflyer/worker/ForegroundService.kt | 47 ++--- app/src/main/res/layout/spotify_fragment.xml | 9 - 11 files changed, 366 insertions(+), 156 deletions(-) create mode 100644 app/src/main/java/com/shabinder/spotiflyer/downloadHelper/YoutubeProvider.kt create mode 100644 app/src/main/java/com/shabinder/spotiflyer/models/YoutubeTrack.kt create mode 100644 app/src/main/java/com/shabinder/spotiflyer/utils/YoutubeMusicApi.kt diff --git a/.idea/dictionaries/shabinder.xml b/.idea/dictionaries/shabinder.xml index 7218236f..726356e5 100755 --- a/.idea/dictionaries/shabinder.xml +++ b/.idea/dictionaries/shabinder.xml @@ -25,6 +25,7 @@ spotifydownloader spotifyler thru + weyfdnx youtu diff --git a/app/build.gradle b/app/build.gradle index d390587f..36a5fdfe 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -115,10 +115,13 @@ dependencies { implementation 'com.squareup.moshi:moshi:1.11.0' implementation 'com.squareup.moshi:moshi-kotlin:1.11.0' implementation "com.squareup.retrofit2:converter-moshi:2.9.0" + implementation "com.squareup.retrofit2:converter-scalars:2.9.0" + implementation 'com.squareup.retrofit2:converter-gson:2.9.0' + implementation 'com.beust:klaxon:5.4' implementation 'com.mpatric:mp3agic:0.9.1' implementation 'com.shreyaspatil:EasyUpiPayment:3.0.0' - implementation 'com.github.sealedtx:java-youtube-downloader:2.4.2' + implementation 'com.github.sealedtx:java-youtube-downloader:2.4.3' implementation "androidx.tonyodev.fetch2:xfetch2:3.1.5" implementation 'com.github.javiersantos:AppUpdater:2.7' diff --git a/app/src/main/java/com/shabinder/spotiflyer/MainActivity.kt b/app/src/main/java/com/shabinder/spotiflyer/MainActivity.kt index b4a1272e..4868a89b 100755 --- a/app/src/main/java/com/shabinder/spotiflyer/MainActivity.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/MainActivity.kt @@ -69,9 +69,6 @@ class MainActivity : AppCompatActivity(){ AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) sharedPref = this.getPreferences(Context.MODE_PRIVATE) - //starting Notification and Downloader Service! - SpotifyDownloadHelper.startService(this) - if(sharedViewModel.spotifyService.value == null){ authenticateSpotify() }else{ @@ -86,6 +83,9 @@ class MainActivity : AppCompatActivity(){ sharedViewModel.isConnected.value = isConnected Log.i("Connection Status", isConnected.toString()) + //starting Notification and Downloader Service! + SpotifyDownloadHelper.startService(this) + handleIntentFromExternalActivity() } diff --git a/app/src/main/java/com/shabinder/spotiflyer/downloadHelper/SpotifyDownloadHelper.kt b/app/src/main/java/com/shabinder/spotiflyer/downloadHelper/SpotifyDownloadHelper.kt index a68427d6..b783a9a5 100755 --- a/app/src/main/java/com/shabinder/spotiflyer/downloadHelper/SpotifyDownloadHelper.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/downloadHelper/SpotifyDownloadHelper.kt @@ -17,18 +17,13 @@ package com.shabinder.spotiflyer.downloadHelper -import android.annotation.SuppressLint import android.content.Context import android.content.Intent -import android.os.Build import android.os.Environment -import android.os.Handler import android.util.Log import android.view.View import android.view.animation.AlphaAnimation import android.view.animation.Animation -import android.webkit.WebView -import android.webkit.WebViewClient import android.widget.TextView import androidx.core.content.ContextCompat import com.github.kiulian.downloader.YoutubeDownloader @@ -37,25 +32,27 @@ import com.github.kiulian.downloader.model.quality.AudioQuality import com.shabinder.spotiflyer.models.DownloadObject import com.shabinder.spotiflyer.models.Track import com.shabinder.spotiflyer.ui.spotify.SpotifyViewModel +import com.shabinder.spotiflyer.utils.YoutubeMusicApi import com.shabinder.spotiflyer.utils.getEmojiByUnicode +import com.shabinder.spotiflyer.utils.makeJsonBody import com.shabinder.spotiflyer.worker.ForegroundService import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response import java.io.File object SpotifyDownloadHelper { - var webView:WebView? = null var context : Context? = null var statusBar:TextView? = null + var youtubeMusicApi:YoutubeMusicApi? = null val defaultDir = Environment.DIRECTORY_MUSIC + File.separator + "SpotiFlyer" + File.separator var spotifyViewModel: SpotifyViewModel? = null - private var isBrowserLoading = false - private var total = 0 - private var Processed = 0 - private var notFound = 0 - private var listProcessed:Boolean = false - var youtubeList = mutableListOf() + var total = 0 + var Processed = 0 + var notFound = 0 /** * Function To Download All Tracks Available in a List @@ -70,16 +67,9 @@ object SpotifyDownloadHelper { if(it.downloaded == "Downloaded"){//Download Already Present!! Processed++ }else{ - if(isBrowserLoading){//WebView Busy!! - if (listProcessed){//Previous List request progress check - getYTLink(type,subFolder,ytDownloader,"${it.name} ${it.artists?.get(0)?.name ?:""}", it) - listProcessed = false//Notifying A list Processing Started - }else{//Adding Requests to a Queue - youtubeList.add(YoutubeRequest(type,subFolder,ytDownloader,"${it.name} ${it.artists?.get(0)?.name ?:""}", it)) - } - }else{ - getYTLink(type,subFolder,ytDownloader,"${it.name} ${it.artists?.get(0)?.name ?:""}", it) - } + val artistsList = mutableListOf() + it.artists?.forEach { artist -> artistsList.add(artist!!.name!!) } + searchYTMusic(type,subFolder,ytDownloader,"${it.name} - ${artistsList.joinToString(",")}", it) } updateStatusBar() } @@ -88,66 +78,32 @@ object SpotifyDownloadHelper { } - - //TODO CleanUp here and there!! - @SuppressLint("SetJavaScriptEnabled") - suspend fun getYTLink(type:String, - subFolder:String?, - ytDownloader: YoutubeDownloader?, - searchQuery: String, - track: Track){ - isBrowserLoading = true // Notify Web View Started Loading - val searchText = searchQuery.replace("\\s".toRegex(), "+") - val url = "https://www.youtube.com/results?sp=EgIQAQ%253D%253D&q=$searchText" - Log.i("DH YT LINK ",url) - applyWebViewSettings(webView!!) - withContext(Dispatchers.Main){ - webView!!.loadUrl(url) - webView!!.webViewClient = object : WebViewClient() { - override fun onPageFinished(view: WebView?, url: String?) { - super.onPageFinished(view, url) - view?.evaluateJavascript( - "document.getElementsByClassName(\"yt-simple-endpoint style-scope ytd-video-renderer\")[0].href" - ) { value -> - Log.i("YT-id Link", value.toString().replace("\"", "")) - val id = value!!.substringAfterLast("=", "error").replace("\"", "") - Log.i("YT-ID", id) - if (id != "error") {//Link extracting error - Processed++ - downloadFile(subFolder, type, track, ytDownloader, id) - }else notFound++ - updateStatusBar() - if (youtubeList.isNotEmpty()) { - val request = youtubeList[0] - spotifyViewModel!!.uiScope.launch { - getYTLink( - request.type, - request.subFolder, - request.ytDownloader, - request.searchQuery, - request.track - ) - } - youtubeList.remove(request) - if (youtubeList.size == 0) {//list processing completed , webView is free again! - isBrowserLoading = false - listProcessed = true - } - } else {//YT List Empty....Maybe it was one Single Download - Handler().postDelayed({//Delay of 1.5 sec - if (youtubeList.isEmpty()) {//Lets Make It sure , There are No more Downloads In Queue..... - isBrowserLoading = false - listProcessed = true - } - }, 1500) - } + suspend fun searchYTMusic(type:String, + subFolder:String?, + ytDownloader: YoutubeDownloader?, + searchQuery: String, + track: Track){ + val jsonBody = makeJsonBody(searchQuery.trim()) + youtubeMusicApi?.getYoutubeMusicResponse(jsonBody)?.enqueue( + object : Callback{ + override fun onResponse(call: Call, response: Response) { + spotifyViewModel?.uiScope?.launch { + Log.i("YT API BODY",response.body().toString()) + Log.i("YT Search Query",searchQuery) + getYTLink(type,subFolder,ytDownloader,response.body().toString(),track) } } + + override fun onFailure(call: Call, t: Throwable) { + Log.i("YT API Fail",t.message.toString()) + } } - } + ) + } - private fun updateStatusBar() { + + fun updateStatusBar() { statusBar!!.visibility = View.VISIBLE statusBar?.text = "Total: $total ${getEmojiByUnicode(0x2705)}: $Processed ${getEmojiByUnicode(0x274C)}: $notFound" } @@ -158,7 +114,6 @@ object SpotifyDownloadHelper { withContext(Dispatchers.IO) { try { val video = ytDownloader?.getVideo(id) - val detail = video?.details() val format: Format? = try { video?.findAudioWithQuality(AudioQuality.high)?.get(0) as Format } catch (e: java.lang.IndexOutOfBoundsException) { @@ -191,18 +146,21 @@ object SpotifyDownloadHelper { ) Log.i("DH", outputFile) startService(context!!, downloadObject) + Processed++ + spotifyViewModel?.uiScope?.launch(Dispatchers.Main) { + updateStatusBar() + } } }catch (e: com.github.kiulian.downloader.YoutubeException){ - Log.i("DH", "Error- Maybe Network") + Log.i("DH", e.message) } } } } - fun startService(context:Context,obj:DownloadObject? = null ) { val serviceIntent = Intent(context, ForegroundService::class.java) - serviceIntent.putExtra("object",obj) + obj?.let { serviceIntent.putExtra("object",it) } ContextCompat.startForegroundService(context, serviceIntent) } @@ -255,35 +213,4 @@ object SpotifyDownloadHelper { anim.repeatCount = Animation.INFINITE statusBar?.animation = anim } - @SuppressLint("SetJavaScriptEnabled") - fun applyWebViewSettings(webView: WebView) { - val desktopUserAgent = - "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:82.0) Gecko/20100101 Firefox/82.0" - val mobileUserAgent = - "Mozilla/5.0 (Linux; U; Android 4.4; en-us; Nexus 4 Build/JOP24G) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30" - - //Choose Mobile/Desktop client. - webView.settings.userAgentString = desktopUserAgent - webView.settings.loadWithOverviewMode = true - webView.settings.builtInZoomControls = true - webView.settings.setSupportZoom(true) - webView.isScrollbarFadingEnabled = false - webView.scrollBarStyle = WebView.SCROLLBARS_OUTSIDE_OVERLAY - webView.settings.displayZoomControls = false - webView.settings.useWideViewPort = true - webView.settings.javaScriptEnabled = true - webView.settings.loadsImagesAutomatically = false - webView.settings.blockNetworkImage = true - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - webView.settings.safeBrowsingEnabled = true - } - } -} -data class YoutubeRequest( - val type:String, - val subFolder:String?, - val ytDownloader: YoutubeDownloader?, - val searchQuery: String, - val track: Track, - val index: Int? = null -) \ No newline at end of file +} \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/downloadHelper/YoutubeProvider.kt b/app/src/main/java/com/shabinder/spotiflyer/downloadHelper/YoutubeProvider.kt new file mode 100644 index 00000000..daa90974 --- /dev/null +++ b/app/src/main/java/com/shabinder/spotiflyer/downloadHelper/YoutubeProvider.kt @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2020 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 . + */ + +package com.shabinder.spotiflyer.downloadHelper + +import android.util.Log +import com.beust.klaxon.JsonArray +import com.beust.klaxon.JsonObject +import com.beust.klaxon.Parser +import com.github.kiulian.downloader.YoutubeDownloader +import com.shabinder.spotiflyer.downloadHelper.SpotifyDownloadHelper.downloadFile +import com.shabinder.spotiflyer.downloadHelper.SpotifyDownloadHelper.notFound +import com.shabinder.spotiflyer.models.Track +import com.shabinder.spotiflyer.models.YoutubeTrack + +/* +* Thanks and credits To https://github.com/spotDL/spotify-downloader +* */ +fun getYTLink(type:String, + subFolder:String?, + ytDownloader: YoutubeDownloader?, + response: String, + track: Track +){ + //TODO Download File + val youtubeTracks = mutableListOf() + val parser: Parser = Parser.default() + val stringBuilder: StringBuilder = StringBuilder(response) + val responseObj: JsonObject = parser.parse(stringBuilder) as JsonObject + val contentBlocks = responseObj.obj("contents")?.obj("sectionListRenderer")?.array("contents") + val resultBlocks = mutableListOf>() + if (contentBlocks != null) { + Log.i("Total Content Blocks:", contentBlocks.size.toString()) + for (cBlock in contentBlocks){ + /** + *Ignore user-suggestion + *The 'itemSectionRenderer' field is for user notices (stuff like - 'showing + *results for xyz, search for abc instead') we have no use for them, the for + *loop below if throw a keyError if we don't ignore them + */ + if(cBlock.containsKey("itemSectionRenderer")){ + continue + } + + for(contents in cBlock.obj("musicShelfRenderer")?.array("contents") ?: listOf()){ + /** + * apparently content Blocks without an 'overlay' field don't have linkBlocks + * I have no clue what they are and why there even exist + * + if(!contents.containsKey("overlay")){ + println(contents) + continue + TODO check and correct + }*/ + + val result = contents.obj("musicResponsiveListItemRenderer") + ?.array("flexColumns") + + //Add the linkBlock + val linkBlock = contents.obj("musicResponsiveListItemRenderer") + ?.obj("overlay") + ?.obj("musicItemThumbnailOverlayRenderer") + ?.obj("content") + ?.obj("musicPlayButtonRenderer") + ?.obj("playNavigationEndpoint") + + // detailsBlock is always a list, so we just append the linkBlock to it + // instead of carrying along all the other junk from "musicResponsiveListItemRenderer" + linkBlock?.let { result?.add(it) } + result?.let { resultBlocks.add(it) } + } + } + + /* We only need results that are Songs or Videos, so we filter out the rest, since + ! Songs and Videos are supplied with different details, extracting all details from + ! both is just carrying on redundant data, so we also have to selectively extract + ! relevant details. What you need to know to understand how we do that here: + ! + ! Songs details are ALWAYS in the following order: + ! 0 - Name + ! 1 - Type (Song) + ! 2 - Artist + ! 3 - Album + ! 4 - Duration (mm:ss) + ! + ! Video details are ALWAYS in the following order: + ! 0 - Name + ! 1 - Type (Video) + ! 2 - Channel + ! 3 - Viewers + ! 4 - Duration (hh:mm:ss) + ! + ! We blindly gather all the details we get our hands on, then + ! cherrypick the details we need based on their index numbers, + ! we do so only if their Type is 'Song' or 'Video + */ + + val simplifiedResults = mutableListOf() + + for(result in resultBlocks){ + + // Blindly gather available details + val availableDetails = mutableListOf() + + /* + Filter Out dummies here itself + ! 'musicResponsiveListItemFlexColumnRenderer' should have more that one + ! sub-block, if not its a dummy, why does the YTM response contain dummies? + ! I have no clue. We skip these. + + ! Remember that we appended the linkBlock to result, treating that like the + ! other constituents of a result block will lead to errors, hence the 'in + ! result[:-1] ,i.e., skip last element in array ' + */ + for(detail in result.subList(0,result.size-2)){ + if(detail.obj("musicResponsiveListItemFlexColumnRenderer")?.size!! < 2) continue + + // if not a dummy, collect All Variables + detail.obj("musicResponsiveListItemFlexColumnRenderer") + ?.obj("text") + ?.array("runs")?.get(0)?.get("text")?.let { + availableDetails.add( + it.toString() + ) + } + } + + /* + ! Filter Out non-Song/Video results and incomplete results here itself + ! From what we know about detail order, note that [1] - indicate result type + */ + if ( availableDetails.size > 1 && availableDetails[1] in listOf("Song","Video") ){ + + // skip if result is in hours instead of minutes (no song is that long) +// if(availableDetails[4].split(':').size != 2) continue TODO + + /* + ! grab position of result + ! This helps for those oddball cases where 2+ results are rated equally, + ! lower position --> better match + */ + val resultPosition = resultBlocks.indexOf(result) + + /* + ! grab Video ID + ! this is nested as [playlistEndpoint/watchEndpoint][videoId/playlistId/...] + ! so hardcoding the dict keys for data look up is an ardours process, since + ! the sub-block pattern is fixed even though the key isn't, we just + ! reference the dict keys by index + */ + + val videoId:String = result.last().obj("watchEndpoint")?.get("videoId") as String + val ytTrack = YoutubeTrack( + name = availableDetails[0], + type = availableDetails[1], + artist = availableDetails[2], + videoId = videoId + ) + youtubeTracks.add(ytTrack) + } + } + } + //Songs First, Videos Later + youtubeTracks.sortWith { o1: YoutubeTrack, o2: YoutubeTrack -> o1.type.toString().compareTo(o2.type.toString()) } + + if(youtubeTracks.firstOrNull()?.videoId.isNullOrBlank()) notFound++ + else downloadFile( + subFolder, + type, + track, + ytDownloader, + id = youtubeTracks[0].videoId.toString() + ) + Log.i("DHelper YT ID", youtubeTracks.firstOrNull()?.videoId ?: "Not Found") + SpotifyDownloadHelper.updateStatusBar() +} diff --git a/app/src/main/java/com/shabinder/spotiflyer/models/YoutubeTrack.kt b/app/src/main/java/com/shabinder/spotiflyer/models/YoutubeTrack.kt new file mode 100644 index 00000000..4ea7de9d --- /dev/null +++ b/app/src/main/java/com/shabinder/spotiflyer/models/YoutubeTrack.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2020 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 . + */ + +package com.shabinder.spotiflyer.models + +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize + +@Parcelize +data class YoutubeTrack( + var name: String? = null, + var type: String? = null, // Song / Video + var artist: String? = null, + var videoId: String? = null +):Parcelable \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/ui/spotify/SpotifyFragment.kt b/app/src/main/java/com/shabinder/spotiflyer/ui/spotify/SpotifyFragment.kt index 9e2835b3..b4a2d6e5 100755 --- a/app/src/main/java/com/shabinder/spotiflyer/ui/spotify/SpotifyFragment.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/ui/spotify/SpotifyFragment.kt @@ -29,7 +29,6 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.webkit.WebView import android.widget.Toast import androidx.core.net.toUri import androidx.databinding.DataBindingUtil @@ -50,6 +49,7 @@ import com.shabinder.spotiflyer.databinding.SpotifyFragmentBinding import com.shabinder.spotiflyer.downloadHelper.SpotifyDownloadHelper import com.shabinder.spotiflyer.models.Track import com.shabinder.spotiflyer.recyclerView.SpotifyTrackListAdapter +import com.shabinder.spotiflyer.utils.YoutubeMusicApi import com.shabinder.spotiflyer.utils.bindImage import com.shabinder.spotiflyer.utils.copyTo import com.shabinder.spotiflyer.utils.rotateAnim @@ -70,7 +70,7 @@ class SpotifyFragment : Fragment() { private lateinit var sharedViewModel: SharedViewModel private lateinit var adapterSpotify:SpotifyTrackListAdapter @Inject lateinit var ytDownloader:YoutubeDownloader - private var webView: WebView? = null + @Inject lateinit var youtubeMusicApi: YoutubeMusicApi private var intentFilter:IntentFilter? = null private var updateUIReceiver: BroadcastReceiver? = null @@ -88,7 +88,7 @@ class SpotifyFragment : Fragment() { val args = SpotifyFragmentArgs.fromBundle(requireArguments()) val spotifyLink = args.link - + val link = spotifyLink.substringAfterLast('/', "Error").substringBefore('?') val type = spotifyLink.substringBeforeLast('/', "Error").substringAfterLast('/') @@ -231,14 +231,13 @@ class SpotifyFragment : Fragment() { * Basic Initialization **/ private fun initializeAll() { - webView = binding.webViewSpotify sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java) spotifyViewModel = ViewModelProvider(this).get(SpotifyViewModel::class.java) sharedViewModel.spotifyService.observe(viewLifecycleOwner, Observer { spotifyViewModel.spotifyService = it }) - SpotifyDownloadHelper.webView = binding.webViewSpotify SpotifyDownloadHelper.context = requireContext() + SpotifyDownloadHelper.youtubeMusicApi = youtubeMusicApi SpotifyDownloadHelper.spotifyViewModel = spotifyViewModel SpotifyDownloadHelper.statusBar = binding.StatusBarSpotify binding.trackListSpotify.adapter = adapterSpotify diff --git a/app/src/main/java/com/shabinder/spotiflyer/utils/Provider.kt b/app/src/main/java/com/shabinder/spotiflyer/utils/Provider.kt index 57726455..3638edf0 100755 --- a/app/src/main/java/com/shabinder/spotiflyer/utils/Provider.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/utils/Provider.kt @@ -35,7 +35,9 @@ import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Request import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.moshi.MoshiConverterFactory +import retrofit2.converter.scalars.ScalarsConverterFactory import javax.inject.Singleton @InstallIn(ApplicationComponent::class) @@ -97,4 +99,17 @@ object Provider { return retrofit.create(SpotifyServiceTokenRequest::class.java) } + @Provides + @Singleton + fun getYoutubeMusicApi():YoutubeMusicApi{ + + val retrofit = Retrofit.Builder() + .baseUrl("https://music.youtube.com/youtubei/v1/") + .addConverterFactory(ScalarsConverterFactory.create()) + .addConverterFactory(GsonConverterFactory.create()) + .build() + + return retrofit.create(YoutubeMusicApi::class.java) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/utils/YoutubeMusicApi.kt b/app/src/main/java/com/shabinder/spotiflyer/utils/YoutubeMusicApi.kt new file mode 100644 index 00000000..ccd72c67 --- /dev/null +++ b/app/src/main/java/com/shabinder/spotiflyer/utils/YoutubeMusicApi.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 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 . + */ + +package com.shabinder.spotiflyer.utils + +import com.beust.klaxon.JsonObject +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.Headers +import retrofit2.http.POST + + +const val apiKey = "AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30" +/*val body = """{ + "context": { + "client": { + "clientName": "WEB_REMIX", + "clientVersion": "0.1" + } + }, + "query": "songSearchQuery" +}"""*/ +interface YoutubeMusicApi { + + @Headers("Content-Type: application/json", "Referer: https://music.youtube.com/search") + @POST("search?alt=json&key=$apiKey") + fun getYoutubeMusicResponse(@Body text: JsonObject): Call + +} + +fun makeJsonBody(query: String):JsonObject{ + val client = JsonObject() + client["clientName"] = "WEB_REMIX" + client["clientVersion"] = "0.1" + + val context = JsonObject() + context["client"] = client + + val mainObject = JsonObject() + mainObject["context"] = context + mainObject["query"] = query + + return mainObject +} \ No newline at end of file diff --git a/app/src/main/java/com/shabinder/spotiflyer/worker/ForegroundService.kt b/app/src/main/java/com/shabinder/spotiflyer/worker/ForegroundService.kt index a1922fa2..ba103032 100755 --- a/app/src/main/java/com/shabinder/spotiflyer/worker/ForegroundService.kt +++ b/app/src/main/java/com/shabinder/spotiflyer/worker/ForegroundService.kt @@ -17,6 +17,7 @@ package com.shabinder.spotiflyer.worker +import android.annotation.SuppressLint import android.app.* import android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED import android.content.BroadcastReceiver @@ -144,33 +145,29 @@ class ForegroundService : Service(){ return channelId } + @SuppressLint("WakelockTimeout") override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { // Send a notification that service is started Log.i(tag,"Service Started.") startForeground() - //do heavy work on a background thread - //val list = intent.getSerializableExtra("list") as List -// val list = intent.getParcelableArrayListExtra("list") ?: intent.extras?.getParcelableArrayList("list") -// Log.i(tag,"Intent List Size: ${list!!.size}") - val obj = intent.getParcelableExtra("object") ?: intent.extras?.getParcelable("object") + val obj:DownloadObject? = intent.getParcelableExtra("object") ?: intent.extras?.getParcelable("object") obj?.let { total ++ -// Log.i(tag,"Intent List Size: ${list!!.size}") updateNotification() serviceScope.launch { - val request= Request(obj.url, obj.outputDir) - request.priority = Priority.NORMAL - request.networkType = NetworkType.ALL + val request= Request(obj.url, obj.outputDir) + request.priority = Priority.NORMAL + request.networkType = NetworkType.ALL - fetch!!.enqueue(request, - { - obj.track?.let { it1 -> requestMap.put(it, it1) } - downloadList.remove(obj) - Log.i(tag, "Enqueuing Download") - }, - { - Log.i(tag, "Enqueuing Error:${it.throwable.toString()}")} - ) + fetch!!.enqueue(request, + { + obj.track?.let { it1 -> requestMap.put(it, it1) } + downloadList.remove(obj) + Log.i(tag, "Enqueuing Download") + }, + { + Log.i(tag, "Enqueuing Error:${it.throwable.toString()}")} + ) } } @@ -316,12 +313,6 @@ class ForegroundService : Service(){ messageList[messageList.indexOf(message)] = "" } } - //Notify Download Completed - val intent = Intent() - .setAction("track_download_completed") - .putExtra("track",track) - this@ForegroundService.sendBroadcast(intent) - serviceScope.launch { try{ @@ -457,11 +448,17 @@ class ForegroundService : Service(){ newFile.renameTo(file) converted++ updateNotification() + + //Notify Download Completed + val intent = Intent() + .setAction("track_download_completed") + .putExtra("track",track) + this@ForegroundService.sendBroadcast(intent) + //All tasks completed (REST IN PEACE) if(converted == total){ onDestroy() } - } /** diff --git a/app/src/main/res/layout/spotify_fragment.xml b/app/src/main/res/layout/spotify_fragment.xml index 2270feaf..6b363de5 100755 --- a/app/src/main/res/layout/spotify_fragment.xml +++ b/app/src/main/res/layout/spotify_fragment.xml @@ -154,14 +154,5 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/appbar_spotify" /> - - -