mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-24 18:04:33 +01:00
No More Web Scraping!,Speed Boosted& Less Crashes.
This commit is contained in:
parent
923e28617a
commit
099a103e98
@ -25,6 +25,7 @@
|
||||
<w>spotifydownloader</w>
|
||||
<w>spotifyler</w>
|
||||
<w>thru</w>
|
||||
<w>weyfdnx</w>
|
||||
<w>youtu</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
|
@ -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'
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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<YoutubeRequest>()
|
||||
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<String>()
|
||||
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<String>{
|
||||
override fun onResponse(call: Call<String>, response: Response<String>) {
|
||||
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<String>, 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
|
||||
)
|
||||
}
|
@ -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()
|
||||
}
|
@ -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
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
@ -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<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")
|
||||
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()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -154,14 +154,5 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
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>
|
||||
</layout>
|
||||
|
Loading…
Reference in New Issue
Block a user