No More Web Scraping!,Speed Boosted& Less Crashes.

This commit is contained in:
Shabinder 2020-11-07 03:56:10 +05:30
parent 923e28617a
commit 099a103e98
11 changed files with 366 additions and 156 deletions

View File

@ -25,6 +25,7 @@
<w>spotifydownloader</w> <w>spotifydownloader</w>
<w>spotifyler</w> <w>spotifyler</w>
<w>thru</w> <w>thru</w>
<w>weyfdnx</w>
<w>youtu</w> <w>youtu</w>
</words> </words>
</dictionary> </dictionary>

View File

@ -115,10 +115,13 @@ dependencies {
implementation 'com.squareup.moshi:moshi:1.11.0' implementation 'com.squareup.moshi:moshi:1.11.0'
implementation 'com.squareup.moshi:moshi-kotlin: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-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.mpatric:mp3agic:0.9.1'
implementation 'com.shreyaspatil:EasyUpiPayment:3.0.0' 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 "androidx.tonyodev.fetch2:xfetch2:3.1.5"
implementation 'com.github.javiersantos:AppUpdater:2.7' implementation 'com.github.javiersantos:AppUpdater:2.7'

View File

@ -69,9 +69,6 @@ class MainActivity : AppCompatActivity(){
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
sharedPref = this.getPreferences(Context.MODE_PRIVATE) sharedPref = this.getPreferences(Context.MODE_PRIVATE)
//starting Notification and Downloader Service!
SpotifyDownloadHelper.startService(this)
if(sharedViewModel.spotifyService.value == null){ if(sharedViewModel.spotifyService.value == null){
authenticateSpotify() authenticateSpotify()
}else{ }else{
@ -86,6 +83,9 @@ class MainActivity : AppCompatActivity(){
sharedViewModel.isConnected.value = isConnected sharedViewModel.isConnected.value = isConnected
Log.i("Connection Status", isConnected.toString()) Log.i("Connection Status", isConnected.toString())
//starting Notification and Downloader Service!
SpotifyDownloadHelper.startService(this)
handleIntentFromExternalActivity() handleIntentFromExternalActivity()
} }

View File

@ -17,18 +17,13 @@
package com.shabinder.spotiflyer.downloadHelper package com.shabinder.spotiflyer.downloadHelper
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build
import android.os.Environment import android.os.Environment
import android.os.Handler
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import android.view.animation.AlphaAnimation import android.view.animation.AlphaAnimation
import android.view.animation.Animation import android.view.animation.Animation
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.github.kiulian.downloader.YoutubeDownloader 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.DownloadObject
import com.shabinder.spotiflyer.models.Track import com.shabinder.spotiflyer.models.Track
import com.shabinder.spotiflyer.ui.spotify.SpotifyViewModel import com.shabinder.spotiflyer.ui.spotify.SpotifyViewModel
import com.shabinder.spotiflyer.utils.YoutubeMusicApi
import com.shabinder.spotiflyer.utils.getEmojiByUnicode import com.shabinder.spotiflyer.utils.getEmojiByUnicode
import com.shabinder.spotiflyer.utils.makeJsonBody
import com.shabinder.spotiflyer.worker.ForegroundService import com.shabinder.spotiflyer.worker.ForegroundService
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.io.File import java.io.File
object SpotifyDownloadHelper { object SpotifyDownloadHelper {
var webView:WebView? = null
var context : Context? = null var context : Context? = null
var statusBar:TextView? = null var statusBar:TextView? = null
var youtubeMusicApi:YoutubeMusicApi? = null
val defaultDir = Environment.DIRECTORY_MUSIC + File.separator + "SpotiFlyer" + File.separator val defaultDir = Environment.DIRECTORY_MUSIC + File.separator + "SpotiFlyer" + File.separator
var spotifyViewModel: SpotifyViewModel? = null var spotifyViewModel: SpotifyViewModel? = null
private var isBrowserLoading = false var total = 0
private var total = 0 var Processed = 0
private var Processed = 0 var notFound = 0
private var notFound = 0
private var listProcessed:Boolean = false
var youtubeList = mutableListOf<YoutubeRequest>()
/** /**
* Function To Download All Tracks Available in a List * Function To Download All Tracks Available in a List
@ -70,16 +67,9 @@ object SpotifyDownloadHelper {
if(it.downloaded == "Downloaded"){//Download Already Present!! if(it.downloaded == "Downloaded"){//Download Already Present!!
Processed++ Processed++
}else{ }else{
if(isBrowserLoading){//WebView Busy!! val artistsList = mutableListOf<String>()
if (listProcessed){//Previous List request progress check it.artists?.forEach { artist -> artistsList.add(artist!!.name!!) }
getYTLink(type,subFolder,ytDownloader,"${it.name} ${it.artists?.get(0)?.name ?:""}", it) searchYTMusic(type,subFolder,ytDownloader,"${it.name} - ${artistsList.joinToString(",")}", 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)
}
} }
updateStatusBar() updateStatusBar()
} }
@ -88,66 +78,32 @@ object SpotifyDownloadHelper {
} }
suspend fun searchYTMusic(type:String,
//TODO CleanUp here and there!!
@SuppressLint("SetJavaScriptEnabled")
suspend fun getYTLink(type:String,
subFolder:String?, subFolder:String?,
ytDownloader: YoutubeDownloader?, ytDownloader: YoutubeDownloader?,
searchQuery: String, searchQuery: String,
track: Track){ track: Track){
isBrowserLoading = true // Notify Web View Started Loading val jsonBody = makeJsonBody(searchQuery.trim())
val searchText = searchQuery.replace("\\s".toRegex(), "+") youtubeMusicApi?.getYoutubeMusicResponse(jsonBody)?.enqueue(
val url = "https://www.youtube.com/results?sp=EgIQAQ%253D%253D&q=$searchText" object : Callback<String>{
Log.i("DH YT LINK ",url) override fun onResponse(call: Call<String>, response: Response<String>) {
applyWebViewSettings(webView!!) spotifyViewModel?.uiScope?.launch {
withContext(Dispatchers.Main){ Log.i("YT API BODY",response.body().toString())
webView!!.loadUrl(url) Log.i("YT Search Query",searchQuery)
webView!!.webViewClient = object : WebViewClient() { getYTLink(type,subFolder,ytDownloader,response.body().toString(),track)
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)
}
}
}
}
} }
} }
private fun updateStatusBar() { override fun onFailure(call: Call<String>, t: Throwable) {
Log.i("YT API Fail",t.message.toString())
}
}
)
}
fun updateStatusBar() {
statusBar!!.visibility = View.VISIBLE statusBar!!.visibility = View.VISIBLE
statusBar?.text = "Total: $total ${getEmojiByUnicode(0x2705)}: $Processed ${getEmojiByUnicode(0x274C)}: $notFound" statusBar?.text = "Total: $total ${getEmojiByUnicode(0x2705)}: $Processed ${getEmojiByUnicode(0x274C)}: $notFound"
} }
@ -158,7 +114,6 @@ object SpotifyDownloadHelper {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
try { try {
val video = ytDownloader?.getVideo(id) val video = ytDownloader?.getVideo(id)
val detail = video?.details()
val format: Format? = try { val format: Format? = try {
video?.findAudioWithQuality(AudioQuality.high)?.get(0) as Format video?.findAudioWithQuality(AudioQuality.high)?.get(0) as Format
} catch (e: java.lang.IndexOutOfBoundsException) { } catch (e: java.lang.IndexOutOfBoundsException) {
@ -191,18 +146,21 @@ object SpotifyDownloadHelper {
) )
Log.i("DH", outputFile) Log.i("DH", outputFile)
startService(context!!, downloadObject) startService(context!!, downloadObject)
Processed++
spotifyViewModel?.uiScope?.launch(Dispatchers.Main) {
updateStatusBar()
}
} }
}catch (e: com.github.kiulian.downloader.YoutubeException){ }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 ) { fun startService(context:Context,obj:DownloadObject? = null ) {
val serviceIntent = Intent(context, ForegroundService::class.java) val serviceIntent = Intent(context, ForegroundService::class.java)
serviceIntent.putExtra("object",obj) obj?.let { serviceIntent.putExtra("object",it) }
ContextCompat.startForegroundService(context, serviceIntent) ContextCompat.startForegroundService(context, serviceIntent)
} }
@ -255,35 +213,4 @@ object SpotifyDownloadHelper {
anim.repeatCount = Animation.INFINITE anim.repeatCount = Animation.INFINITE
statusBar?.animation = anim 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
)

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<YoutubeTrack>()
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<JsonObject>("contents")
val resultBlocks = mutableListOf<JsonArray<JsonObject>>()
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<JsonObject>("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<JsonObject>("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<JsonObject>()
for(result in resultBlocks){
// Blindly gather available details
val availableDetails = mutableListOf<String>()
/*
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<JsonObject>("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()
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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

View File

@ -29,7 +29,6 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.webkit.WebView
import android.widget.Toast import android.widget.Toast
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
@ -50,6 +49,7 @@ import com.shabinder.spotiflyer.databinding.SpotifyFragmentBinding
import com.shabinder.spotiflyer.downloadHelper.SpotifyDownloadHelper import com.shabinder.spotiflyer.downloadHelper.SpotifyDownloadHelper
import com.shabinder.spotiflyer.models.Track import com.shabinder.spotiflyer.models.Track
import com.shabinder.spotiflyer.recyclerView.SpotifyTrackListAdapter import com.shabinder.spotiflyer.recyclerView.SpotifyTrackListAdapter
import com.shabinder.spotiflyer.utils.YoutubeMusicApi
import com.shabinder.spotiflyer.utils.bindImage import com.shabinder.spotiflyer.utils.bindImage
import com.shabinder.spotiflyer.utils.copyTo import com.shabinder.spotiflyer.utils.copyTo
import com.shabinder.spotiflyer.utils.rotateAnim import com.shabinder.spotiflyer.utils.rotateAnim
@ -70,7 +70,7 @@ class SpotifyFragment : Fragment() {
private lateinit var sharedViewModel: SharedViewModel private lateinit var sharedViewModel: SharedViewModel
private lateinit var adapterSpotify:SpotifyTrackListAdapter private lateinit var adapterSpotify:SpotifyTrackListAdapter
@Inject lateinit var ytDownloader:YoutubeDownloader @Inject lateinit var ytDownloader:YoutubeDownloader
private var webView: WebView? = null @Inject lateinit var youtubeMusicApi: YoutubeMusicApi
private var intentFilter:IntentFilter? = null private var intentFilter:IntentFilter? = null
private var updateUIReceiver: BroadcastReceiver? = null private var updateUIReceiver: BroadcastReceiver? = null
@ -231,14 +231,13 @@ class SpotifyFragment : Fragment() {
* Basic Initialization * Basic Initialization
**/ **/
private fun initializeAll() { private fun initializeAll() {
webView = binding.webViewSpotify
sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java) sharedViewModel = ViewModelProvider(this.requireActivity()).get(SharedViewModel::class.java)
spotifyViewModel = ViewModelProvider(this).get(SpotifyViewModel::class.java) spotifyViewModel = ViewModelProvider(this).get(SpotifyViewModel::class.java)
sharedViewModel.spotifyService.observe(viewLifecycleOwner, Observer { sharedViewModel.spotifyService.observe(viewLifecycleOwner, Observer {
spotifyViewModel.spotifyService = it spotifyViewModel.spotifyService = it
}) })
SpotifyDownloadHelper.webView = binding.webViewSpotify
SpotifyDownloadHelper.context = requireContext() SpotifyDownloadHelper.context = requireContext()
SpotifyDownloadHelper.youtubeMusicApi = youtubeMusicApi
SpotifyDownloadHelper.spotifyViewModel = spotifyViewModel SpotifyDownloadHelper.spotifyViewModel = spotifyViewModel
SpotifyDownloadHelper.statusBar = binding.StatusBarSpotify SpotifyDownloadHelper.statusBar = binding.StatusBarSpotify
binding.trackListSpotify.adapter = adapterSpotify binding.trackListSpotify.adapter = adapterSpotify

View File

@ -35,7 +35,9 @@ import okhttp3.Interceptor
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.converter.moshi.MoshiConverterFactory import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.converter.scalars.ScalarsConverterFactory
import javax.inject.Singleton import javax.inject.Singleton
@InstallIn(ApplicationComponent::class) @InstallIn(ApplicationComponent::class)
@ -97,4 +99,17 @@ object Provider {
return retrofit.create(SpotifyServiceTokenRequest::class.java) 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)
}
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String>
}
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
}

View File

@ -17,6 +17,7 @@
package com.shabinder.spotiflyer.worker package com.shabinder.spotiflyer.worker
import android.annotation.SuppressLint
import android.app.* import android.app.*
import android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED import android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
@ -144,18 +145,14 @@ class ForegroundService : Service(){
return channelId return channelId
} }
@SuppressLint("WakelockTimeout")
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
// Send a notification that service is started // Send a notification that service is started
Log.i(tag,"Service Started.") Log.i(tag,"Service Started.")
startForeground() startForeground()
//do heavy work on a background thread val obj:DownloadObject? = intent.getParcelableExtra("object") ?: intent.extras?.getParcelable("object")
//val list = intent.getSerializableExtra("list") as List<Any?>
// val list = intent.getParcelableArrayListExtra<DownloadObject>("list") ?: intent.extras?.getParcelableArrayList<DownloadObject>("list")
// Log.i(tag,"Intent List Size: ${list!!.size}")
val obj = intent.getParcelableExtra<DownloadObject>("object") ?: intent.extras?.getParcelable<DownloadObject>("object")
obj?.let { obj?.let {
total ++ total ++
// Log.i(tag,"Intent List Size: ${list!!.size}")
updateNotification() updateNotification()
serviceScope.launch { serviceScope.launch {
val request= Request(obj.url, obj.outputDir) val request= Request(obj.url, obj.outputDir)
@ -316,12 +313,6 @@ class ForegroundService : Service(){
messageList[messageList.indexOf(message)] = "" messageList[messageList.indexOf(message)] = ""
} }
} }
//Notify Download Completed
val intent = Intent()
.setAction("track_download_completed")
.putExtra("track",track)
this@ForegroundService.sendBroadcast(intent)
serviceScope.launch { serviceScope.launch {
try{ try{
@ -457,11 +448,17 @@ class ForegroundService : Service(){
newFile.renameTo(file) newFile.renameTo(file)
converted++ converted++
updateNotification() updateNotification()
//Notify Download Completed
val intent = Intent()
.setAction("track_download_completed")
.putExtra("track",track)
this@ForegroundService.sendBroadcast(intent)
//All tasks completed (REST IN PEACE) //All tasks completed (REST IN PEACE)
if(converted == total){ if(converted == total){
onDestroy() onDestroy()
} }
} }
/** /**

View File

@ -154,14 +154,5 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appbar_spotify" /> app:layout_constraintTop_toBottomOf="@id/appbar_spotify" />
<WebView
android:id="@+id/webView_spotify"
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_gravity="bottom"
android:visibility="gone"
app:layout_anchorGravity="bottom"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout> </layout>