mirror of
				https://github.com/recloudstream/cloudstream.git
				synced 2024-08-15 01:53:11 +00:00 
			
		
		
		
	code fix
This commit is contained in:
		
							parent
							
								
									613c26ec63
								
							
						
					
					
						commit
						3b6b3dedc0
					
				
					 12 changed files with 550 additions and 21 deletions
				
			
		|  | @ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.json.JsonMapper | |||
| import com.fasterxml.jackson.module.kotlin.KotlinModule | ||||
| import com.lagradost.cloudstream3.animeproviders.* | ||||
| import com.lagradost.cloudstream3.movieproviders.* | ||||
| import com.lagradost.cloudstream3.torrentproviders.* | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import java.util.* | ||||
| 
 | ||||
|  | @ -41,6 +42,7 @@ object APIHolder { | |||
|         WatchCartoonOnlineProvider(), | ||||
|         AllMoviesForYouProvider(), | ||||
|         AsiaFlixProvider(), | ||||
|         //NyaaProvider(), | ||||
|     ) | ||||
| 
 | ||||
|     fun getApiFromName(apiName: String?): MainAPI { | ||||
|  | @ -116,7 +118,7 @@ abstract class MainAPI { | |||
|         TvType.TvSeries, | ||||
|         TvType.Cartoon, | ||||
|         TvType.Anime, | ||||
|         TvType.ONA | ||||
|         TvType.ONA, | ||||
|     ) | ||||
| 
 | ||||
|     open val vpnStatus = VPNStatus.None | ||||
|  | @ -222,11 +224,12 @@ enum class TvType { | |||
|     Cartoon, | ||||
|     Anime, | ||||
|     ONA, | ||||
|     Torrent, | ||||
| } | ||||
| 
 | ||||
| // IN CASE OF FUTURE ANIME MOVIE OR SMTH | ||||
| fun TvType.isMovieType(): Boolean { | ||||
|     return this == TvType.Movie || this == TvType.AnimeMovie | ||||
|     return this == TvType.Movie || this == TvType.AnimeMovie || this == TvType.Torrent | ||||
| } | ||||
| 
 | ||||
| data class SubtitleFile(val lang: String, val url: String) | ||||
|  | @ -265,6 +268,16 @@ data class AnimeSearchResponse( | |||
|     override val id: Int? = null, | ||||
| ) : SearchResponse | ||||
| 
 | ||||
| data class TorrentSearchResponse( | ||||
|     override val name: String, | ||||
|     override val url: String, | ||||
|     override val apiName: String, | ||||
|     override val type: TvType, | ||||
| 
 | ||||
|     override val posterUrl: String?, | ||||
|     override val id: Int? = null, | ||||
| ) : SearchResponse | ||||
| 
 | ||||
| data class MovieSearchResponse( | ||||
|     override val name: String, | ||||
|     override val url: String, | ||||
|  | @ -321,6 +334,22 @@ data class AnimeEpisode( | |||
|     val descript: String? = null, | ||||
| ) | ||||
| 
 | ||||
| data class TorrentLoadResponse( | ||||
|     override val name: String, | ||||
|     override val url: String, | ||||
|     override val apiName: String, | ||||
|     val magnet: String?, | ||||
|     val torrent: String?, | ||||
|     override val plot: String?, | ||||
|     override val type: TvType = TvType.Torrent, | ||||
|     override val posterUrl: String? = null, | ||||
|     override val year: Int? = null, | ||||
|     override val rating: Int? = null, | ||||
|     override val tags: List<String>? = null, | ||||
|     override val duration: String? = null, | ||||
|     override val trailerUrl: String? = null | ||||
| ) : LoadResponse | ||||
| 
 | ||||
| data class AnimeLoadResponse( | ||||
|     val engName: String?, | ||||
|     val japName: String?, | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ import android.content.pm.PackageManager | |||
| import android.content.res.ColorStateList | ||||
| import android.os.Build | ||||
| import android.os.Bundle | ||||
| import android.os.Environment | ||||
| import android.view.* | ||||
| import android.widget.TextView | ||||
| import android.widget.Toast | ||||
|  | @ -16,6 +17,11 @@ import androidx.appcompat.app.AppCompatActivity | |||
| import androidx.navigation.NavOptions | ||||
| import androidx.navigation.findNavController | ||||
| import androidx.navigation.fragment.NavHostFragment | ||||
| import com.github.se_bastiaan.torrentstream.StreamStatus | ||||
| import com.github.se_bastiaan.torrentstream.Torrent | ||||
| import com.github.se_bastiaan.torrentstream.TorrentOptions | ||||
| import com.github.se_bastiaan.torrentstream.TorrentStream | ||||
| import com.github.se_bastiaan.torrentstream.listeners.TorrentListener | ||||
| import com.google.android.gms.cast.framework.CastButtonFactory | ||||
| import com.jaredrummler.android.colorpicker.ColorPickerDialogListener | ||||
| import com.lagradost.cloudstream3.APIHolder.apis | ||||
|  | @ -351,5 +357,4 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { | |||
|             runAutoUpdate() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,67 @@ | |||
| package com.lagradost.cloudstream3.torrentproviders | ||||
| 
 | ||||
| import com.lagradost.cloudstream3.* | ||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | ||||
| import com.lagradost.cloudstream3.utils.Qualities | ||||
| import org.jsoup.Jsoup | ||||
| 
 | ||||
| class NyaaProvider : MainAPI() { | ||||
|     override val name: String | ||||
|         get() = "Nyaa" | ||||
|     override val hasChromecastSupport: Boolean | ||||
|         get() = false | ||||
|    // override val hasDownloadSupport: Boolean | ||||
|     //    get() = false | ||||
|     override val mainUrl: String | ||||
|         get() = "https://nyaa.si" | ||||
|     override val supportedTypes: Set<TvType> | ||||
|         get() = setOf(TvType.Torrent) | ||||
|     override val vpnStatus: VPNStatus | ||||
|         get() = VPNStatus.Torrent | ||||
|     override val instantLinkLoading: Boolean | ||||
|         get() = true | ||||
| 
 | ||||
|     override fun search(query: String): List<SearchResponse> { | ||||
|         val url = "$mainUrl/?f=0&c=0_0&q=$query&s=seeders&o=desc" | ||||
|         val response = khttp.get(url) | ||||
|         val document = Jsoup.parse(response.text) | ||||
| 
 | ||||
|         val returnValues = ArrayList<SearchResponse>() | ||||
|                 val elements = document.select("table > tbody > tr") | ||||
|         for (element in elements) { | ||||
|             val tds = element.select("> td") | ||||
|             if(tds.size < 2) continue | ||||
|             val type = tds[0].select("> a").attr("title") | ||||
|             val titleHeader = tds[1].select("> a").last() | ||||
|             val href = titleHeader.attr("href") | ||||
|             val title = titleHeader.text() | ||||
|             if (title.contains("[Batch]") || !type.contains("Anime")) continue | ||||
|             returnValues.add(TorrentSearchResponse(title, fixUrl(href), this.name, TvType.Torrent, null)) | ||||
|         } | ||||
| 
 | ||||
|         return returnValues | ||||
|     } | ||||
| 
 | ||||
|     override fun load(url: String): LoadResponse { | ||||
|         val response = khttp.get(url) | ||||
|         val document = Jsoup.parse(response.text) | ||||
|         val title = document.selectFirst("h3.panel-title").text() | ||||
|         val description = document.selectFirst("div#torrent-description").text() | ||||
|         val downloadLinks = document.select("div.panel-footer > a") | ||||
|         val magnet = downloadLinks[1].attr("href") | ||||
|         val torrent = downloadLinks[0].attr("href") | ||||
| 
 | ||||
|         return TorrentLoadResponse(title, url, this.name, magnet, fixUrl(torrent) , description) | ||||
|     } | ||||
| 
 | ||||
|     override fun loadLinks( | ||||
|         data: String, | ||||
|         isCasting: Boolean, | ||||
|         subtitleCallback: (SubtitleFile) -> Unit, | ||||
|         callback: (ExtractorLink) -> Unit | ||||
|     ): Boolean { | ||||
|         if (isCasting) return false | ||||
|         callback.invoke(ExtractorLink(this.name, this.name, data, "", Qualities.Unknown.value)) | ||||
|         return true | ||||
|     } | ||||
| } | ||||
|  | @ -29,6 +29,7 @@ import android.widget.* | |||
| import android.widget.Toast.LENGTH_SHORT | ||||
| import androidx.appcompat.app.AlertDialog | ||||
| import androidx.core.content.ContextCompat | ||||
| import androidx.core.net.toUri | ||||
| import androidx.fragment.app.Fragment | ||||
| import androidx.lifecycle.ViewModelProvider | ||||
| import androidx.preference.PreferenceManager | ||||
|  | @ -39,6 +40,11 @@ import com.fasterxml.jackson.databind.DeserializationFeature | |||
| import com.fasterxml.jackson.databind.json.JsonMapper | ||||
| import com.fasterxml.jackson.module.kotlin.KotlinModule | ||||
| import com.fasterxml.jackson.module.kotlin.readValue | ||||
| import com.github.se_bastiaan.torrentstream.StreamStatus | ||||
| import com.github.se_bastiaan.torrentstream.Torrent | ||||
| import com.github.se_bastiaan.torrentstream.TorrentOptions | ||||
| import com.github.se_bastiaan.torrentstream.TorrentStream | ||||
| import com.github.se_bastiaan.torrentstream.listeners.TorrentListener | ||||
| import com.google.android.exoplayer2.* | ||||
| import com.google.android.exoplayer2.C.TIME_UNSET | ||||
| import com.google.android.exoplayer2.database.ExoDatabaseProvider | ||||
|  | @ -163,6 +169,73 @@ data class UriData( | |||
| 
 | ||||
| // YE, I KNOW, THIS COULD BE HANDLED A LOT BETTER | ||||
| class PlayerFragment : Fragment() { | ||||
| 
 | ||||
|     // ============ TORRENT ============ | ||||
|     private var torrentStream: TorrentStream? = null | ||||
|     private var lastTorrentUrl = "" | ||||
|     private val isTorrent: Boolean get() = torrentStream != null | ||||
|     private fun initTorrentStream(torrentUrl: String) { | ||||
|         if (lastTorrentUrl == torrentUrl) return | ||||
|         lastTorrentUrl = torrentUrl | ||||
|         torrentStream?.stopStream() | ||||
|         torrentStream = null | ||||
| 
 | ||||
|         activity?.let { act -> | ||||
|             val normalPath = | ||||
|                 act.cacheDir.absolutePath // "${Environment.getExternalStorageDirectory()}${File.separatorChar}$relativePath" | ||||
|             val torrentOptions: TorrentOptions = TorrentOptions.Builder() | ||||
|                 .saveLocation(normalPath) | ||||
|                 .removeFilesAfterStop(true) | ||||
|                 .build() | ||||
| 
 | ||||
|             torrentStream = TorrentStream.init(torrentOptions) | ||||
|             torrentStream?.startStream(torrentUrl) | ||||
|             torrentStream?.addListener(object : TorrentListener { | ||||
|                 override fun onStreamPrepared(torrent: Torrent?) { | ||||
|                     showToast(activity, "Stream Prepared", LENGTH_SHORT) | ||||
|                 } | ||||
| 
 | ||||
|                 override fun onStreamStarted(torrent: Torrent?) { | ||||
|                     showToast(activity, "Stream Started", LENGTH_SHORT) | ||||
|                 } | ||||
| 
 | ||||
|                 override fun onStreamError(torrent: Torrent?, e: java.lang.Exception?) { | ||||
|                     e?.printStackTrace() | ||||
|                     showToast(activity, e?.localizedMessage ?: "Error loading", LENGTH_SHORT) | ||||
|                 } | ||||
| 
 | ||||
|                 override fun onStreamReady(torrent: Torrent?) { | ||||
|                     initPlayer(null, null, torrent?.videoFile?.toUri()) | ||||
|                 } | ||||
| 
 | ||||
|                 @SuppressLint("SetTextI18n") | ||||
|                 override fun onStreamProgress(torrent: Torrent?, status: StreamStatus?) { | ||||
|                     try { | ||||
|                         println("Seeders ${status?.seeds}") | ||||
|                         println("Download Speed ${status?.downloadSpeed}") | ||||
|                         println("Progress ${status?.progress}%") | ||||
|                         if (isShowing) | ||||
|                             player_torrent_info?.visibility = VISIBLE | ||||
|                         video_torrent_progress?.text = | ||||
|                             "${"%.1f".format(status?.progress ?: 0f)}% at ${status?.downloadSpeed?.div(1000) ?: 0} kb/s" | ||||
|                         video_torrent_seeders?.text = "${status?.seeds ?: 0} Seeders" | ||||
|                         //streamSeeds.formatText(R.string.streamSeeds, status?.seeds) | ||||
|                         //streamSpeed.formatText(R.string.streamDownloadSpeed, status?.downloadSpeed?.div(1024)) | ||||
|                         //streamProgressTxt.formatText(R.string.streamProgress, status?.progress, "%") | ||||
|                     } catch (e: IllegalStateException) { | ||||
|                         e.printStackTrace() | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 override fun onStreamStopped() { | ||||
|                     println("stream stopped") | ||||
|                 } | ||||
|             }) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // ================================= | ||||
| 
 | ||||
|     private lateinit var subStyle: SaveCaptionStyle | ||||
|     private var subView: SubtitleView? = null | ||||
| 
 | ||||
|  | @ -312,7 +385,8 @@ class PlayerFragment : Fragment() { | |||
|             shadow_overlay?.startAnimation(fadeAnimation) | ||||
|         } | ||||
|         video_holder?.startAnimation(fadeAnimation) | ||||
| 
 | ||||
|         player_torrent_info?.visibility = if (isTorrent && isShowing) VISIBLE else GONE | ||||
|         //  player_torrent_info?.startAnimation(fadeAnimation) | ||||
|         //video_lock_holder?.startAnimation(fadeAnimation) | ||||
|     } | ||||
| 
 | ||||
|  | @ -1199,7 +1273,7 @@ class PlayerFragment : Fragment() { | |||
|             activity?.popCurrentPage() | ||||
|         } | ||||
|         video_go_back_holder.setOnClickListener { | ||||
|             println("video_go_back_pressed") | ||||
|             //println("video_go_back_pressed") | ||||
|             // activity?.popCurrentPage(isInPlayer = true, isInExpandedView = false, isInResults = false) | ||||
|             activity?.popCurrentPage() | ||||
|         } | ||||
|  | @ -1460,6 +1534,7 @@ class PlayerFragment : Fragment() { | |||
| 
 | ||||
|     override fun onResume() { | ||||
|         super.onResume() | ||||
|         torrentStream?.currentTorrent?.resume() | ||||
|         onAudioFocusEvent += ::handlePauseEvent | ||||
| 
 | ||||
|         activity?.hideSystemUI() | ||||
|  | @ -1482,6 +1557,9 @@ class PlayerFragment : Fragment() { | |||
|         savePos() | ||||
|         SubtitlesFragment.applyStyleEvent -= ::onSubStyleChanged | ||||
| 
 | ||||
|         torrentStream?.stopStream() | ||||
|         torrentStream = null | ||||
| 
 | ||||
|         super.onDestroy() | ||||
|         canEnterPipMode = false | ||||
| 
 | ||||
|  | @ -1497,6 +1575,7 @@ class PlayerFragment : Fragment() { | |||
|     override fun onPause() { | ||||
|         savePos() | ||||
|         super.onPause() | ||||
|         torrentStream?.currentTorrent?.pause() | ||||
|         if (Util.SDK_INT <= 23) { | ||||
|             if (player_view != null) player_view.onPause() | ||||
|             releasePlayer() | ||||
|  | @ -1567,8 +1646,18 @@ class PlayerFragment : Fragment() { | |||
|     var preferredSubtitles: String = "" | ||||
| 
 | ||||
|     @SuppressLint("SetTextI18n") | ||||
|     fun initPlayer(currentUrl: ExtractorLink?, uri: String? = null) { | ||||
|         if (currentUrl == null && uri == null) return | ||||
|     fun initPlayer(currentUrl: ExtractorLink?, uri: String? = null, trueUri: Uri? = null) { | ||||
|         if (currentUrl == null && uri == null && trueUri == null) return | ||||
|         if (currentUrl?.url?.endsWith(".torrent") == true || currentUrl?.url?.startsWith("magnet") == true) { | ||||
|             initTorrentStream(currentUrl.url)//) | ||||
|             return | ||||
|         } | ||||
|         // player_torrent_info?.visibility = if(isTorrent) VISIBLE else GONE | ||||
|         // | ||||
|         isShowing = false | ||||
|         player_torrent_info?.visibility = GONE | ||||
|         //player_torrent_info?.alpha = 0f | ||||
|         println("LOADED: ${uri} or ${currentUrl}") | ||||
|         isCurrentlyPlaying = true | ||||
|         hasUsedFirstRender = false | ||||
| 
 | ||||
|  | @ -1605,14 +1694,14 @@ class PlayerFragment : Fragment() { | |||
| 
 | ||||
|             if (currentUrl != null) { | ||||
|                 mediaItemBuilder.setUri(currentUrl.url) | ||||
|             } else if (uri != null) { | ||||
|                 val uriPrimary = Uri.parse(uri) | ||||
|             } else if (trueUri != null || uri != null) { | ||||
|                 val uriPrimary = trueUri ?: Uri.parse(uri) | ||||
|                 if (uriPrimary.scheme == "content") { | ||||
|                     mediaItemBuilder.setUri(uriPrimary) | ||||
|                     //      video_title?.text = uriPrimary.toString() | ||||
|                 } else { | ||||
|                     //mediaItemBuilder.setUri(Uri.parse(currentUrl.url)) | ||||
|                     val realUri = getVideoContentUri(requireContext(), uri) | ||||
|                     val realUri = trueUri ?: getVideoContentUri(requireContext(), uri ?: uriPrimary.path ?: "") | ||||
|                     //    video_title?.text = uri.toString() | ||||
|                     mediaItemBuilder.setUri(realUri) | ||||
|                 } | ||||
|  | @ -1728,7 +1817,10 @@ class PlayerFragment : Fragment() { | |||
|             var epSeason: Int? = null | ||||
|             var isEpisodeBased = true | ||||
| 
 | ||||
|             if (isDownloadedFile) { | ||||
|             if (isTorrent) { | ||||
|                 hName = "Torrent Stream" | ||||
|                 isEpisodeBased = false | ||||
|             } else if (isDownloadedFile) { | ||||
|                 hName = uriData.name | ||||
|                 epEpisode = uriData.episode | ||||
|                 epSeason = uriData.season | ||||
|  | @ -1786,7 +1878,9 @@ class PlayerFragment : Fragment() { | |||
| 
 | ||||
|                     video_title_rez?.text = | ||||
|                         if (height == null || width == null) currentUrl?.name | ||||
|                             ?: "" else if (isDownloadedFile) "${width}x${height}" else "${currentUrl?.name} - ${width}x${height}" | ||||
|                             ?: "" else | ||||
|                             if (isTorrent) "${width}x${height}" else | ||||
|                                 if (isDownloadedFile || currentUrl?.name == null) "${width}x${height}" else "${currentUrl.name} - ${width}x${height}" | ||||
| 
 | ||||
|                     if (!hasUsedFirstRender) { // DON'T WANT TO SET MULTIPLE MESSAGES | ||||
|                         changeSkip() | ||||
|  |  | |||
|  | @ -441,6 +441,7 @@ class ResultFragment : Fragment() { | |||
|                     TvType.TvSeries -> "TVSeries/$titleName" | ||||
|                     TvType.ONA -> "ONA" | ||||
|                     TvType.Cartoon -> "Cartoons/$titleName" | ||||
|                     TvType.Torrent -> "Torrent" | ||||
|                     else -> null | ||||
|                 } | ||||
| 
 | ||||
|  | @ -457,7 +458,7 @@ class ResultFragment : Fragment() { | |||
|                             url ?: return@let, | ||||
|                             currentType ?: return@let, | ||||
|                             currentHeaderName ?: return@let, | ||||
|                             currentPoster ?: return@let, | ||||
|                             currentPoster, | ||||
|                             currentId ?: return@let, | ||||
|                             System.currentTimeMillis(), | ||||
|                         ) | ||||
|  | @ -863,7 +864,7 @@ class ResultFragment : Fragment() { | |||
|                             i.data = Uri.parse(d.url) | ||||
|                             try { | ||||
|                                 startActivity(i) | ||||
|                             } catch (e : Exception) { | ||||
|                             } catch (e: Exception) { | ||||
|                                 e.printStackTrace() | ||||
|                             } | ||||
|                         } | ||||
|  | @ -914,10 +915,13 @@ class ResultFragment : Fragment() { | |||
|                             result_metadata.visibility = GONE | ||||
|                         } | ||||
| 
 | ||||
|                         if (d.posterUrl != null) { | ||||
|                             result_poster?.setImage(d.posterUrl) | ||||
|                         } | ||||
|                         result_poster?.setImage(d.posterUrl) | ||||
|                         result_poster_holder?.visibility = if (d.posterUrl.isNullOrBlank()) GONE else VISIBLE | ||||
| 
 | ||||
|                         result_play_movie?.text = | ||||
|                             if (d.type == TvType.Torrent) getString(R.string.play_torrent_button) else getString(R.string.play_movie_button) | ||||
|                         result_plot_header?.text = | ||||
|                             if (d.type == TvType.Torrent) getString(R.string.torrent_plot) else getString(R.string.result_plot) | ||||
|                         if (d.plot != null) { | ||||
|                             var syno = d.plot!! | ||||
|                             if (syno.length > MAX_SYNO_LENGH) { | ||||
|  | @ -930,7 +934,8 @@ class ResultFragment : Fragment() { | |||
|                             } | ||||
|                             result_descript.text = syno | ||||
|                         } else { | ||||
|                             result_descript.text = "No Plot found" | ||||
|                             result_descript.text = | ||||
|                                 if (d.type == TvType.Torrent) getString(R.string.torrent_no_plot) else getString(R.string.normal_no_plot) | ||||
|                         } | ||||
| 
 | ||||
|                         result_tag.removeAllViews() | ||||
|  | @ -1065,7 +1070,7 @@ class ResultFragment : Fragment() { | |||
|                 i.data = Uri.parse(tempUrl) | ||||
|                 try { | ||||
|                     startActivity(i) | ||||
|                 } catch (e : Exception) { | ||||
|                 } catch (e: Exception) { | ||||
|                     e.printStackTrace() | ||||
|                 } | ||||
|             } | ||||
|  |  | |||
|  | @ -324,6 +324,25 @@ class ResultViewModel : ViewModel() { | |||
|                             ), -1 | ||||
|                         ) | ||||
|                     } | ||||
|                     is TorrentLoadResponse -> { | ||||
|                         updateEpisodes( | ||||
|                             context, mainId, arrayListOf( | ||||
|                                 context.buildResultEpisode( | ||||
|                                     d.name, | ||||
|                                     null, | ||||
|                                     0, | ||||
|                                     null, | ||||
|                                     d.torrent ?: d.magnet ?: "", | ||||
|                                     d.apiName, | ||||
|                                     (mainId), // HAS SAME ID | ||||
|                                     0, | ||||
|                                     null, | ||||
|                                     null, | ||||
|                                 ) | ||||
|                             ), -1 | ||||
|                         ) | ||||
|                     } | ||||
| 
 | ||||
|                 } | ||||
|             } | ||||
|             else -> { | ||||
|  |  | |||
|  | @ -130,7 +130,8 @@ class SearchFragment : Fragment() { | |||
|                     Pair("Movies", listOf(TvType.Movie)), | ||||
|                     Pair("TvSeries", listOf(TvType.TvSeries)), | ||||
|                     Pair("Cartoons", listOf(TvType.Cartoon)), | ||||
|                     Pair("Anime", listOf(TvType.Anime, TvType.ONA, TvType.AnimeMovie)) | ||||
|                     Pair("Anime", listOf(TvType.Anime, TvType.ONA, TvType.AnimeMovie)), | ||||
|                     Pair("Torrent", listOf(TvType.Torrent)), | ||||
|                 ) | ||||
| 
 | ||||
|                 val arrayAdapter2 = ArrayAdapter<String>(view.context, R.layout.sort_bottom_single_choice) | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| package com.lagradost.cloudstream3.utils | ||||
| 
 | ||||
| import android.annotation.SuppressLint | ||||
| import android.app.* | ||||
| import android.content.* | ||||
| import android.graphics.Bitmap | ||||
|  | @ -14,6 +15,11 @@ import androidx.core.app.NotificationCompat | |||
| import androidx.core.app.NotificationManagerCompat | ||||
| import androidx.core.net.toUri | ||||
| import com.bumptech.glide.Glide | ||||
| import com.github.se_bastiaan.torrentstream.StreamStatus | ||||
| import com.github.se_bastiaan.torrentstream.Torrent | ||||
| import com.github.se_bastiaan.torrentstream.TorrentOptions | ||||
| import com.github.se_bastiaan.torrentstream.TorrentStream | ||||
| import com.github.se_bastiaan.torrentstream.listeners.TorrentListener | ||||
| import com.lagradost.cloudstream3.MainActivity | ||||
| import com.lagradost.cloudstream3.MainActivity.Companion.showToast | ||||
| import com.lagradost.cloudstream3.R | ||||
|  | @ -488,6 +494,257 @@ object VideoDownloadManager { | |||
|         val bytesTotal: Long, | ||||
|     ) | ||||
| 
 | ||||
|     fun getSizeAndProgressFromTorrent(torrent: Torrent?, progress: Float?): Pair<Long, Long>? { | ||||
|         try { | ||||
|             if (torrent == null || progress == null) return null | ||||
|             val length = torrent.videoFile?.length() ?: 0 | ||||
|             if (length > 0) { | ||||
|                 // val bytesTotal = (length * 100 / progress).toLong() | ||||
|                 // if (bytesTotal > 0 && length > 0) { | ||||
|                 return Pair((length * progress / 100).toLong(), length) | ||||
|                 //} | ||||
|             } | ||||
|             return null | ||||
|         } catch (e: Exception) { | ||||
|             return null | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun downloadTorrent( | ||||
|         context: Context, | ||||
|         link: String, | ||||
|         name: String, | ||||
|         folder: String?, | ||||
|         extension: String, | ||||
|         //tryResume: Boolean = false, | ||||
|         parentId: Int?, | ||||
|         createNotificationCallback: (CreateNotificationMetadata) -> Unit | ||||
|     ): Int { | ||||
|         val relativePath = (Environment.DIRECTORY_DOWNLOADS + '/' + folder + '/').replace('/', File.separatorChar) | ||||
|         val displayName = "$name.$extension" | ||||
|         val fileStream: OutputStream | ||||
|         val fileLength: Long | ||||
|         val resume = false | ||||
|         val normalPath = "${Environment.getExternalStorageDirectory()}${File.separatorChar}$relativePath$displayName" | ||||
| 
 | ||||
|         if (isScopedStorage()) { | ||||
|             val cr = context.contentResolver ?: return ERROR_CONTENT_RESOLVER_NOT_FOUND | ||||
| 
 | ||||
|             val currentExistingFile = | ||||
|                 cr.getExistingDownloadUriOrNullQ(relativePath, displayName) // CURRENT FILE WITH THE SAME PATH | ||||
| 
 | ||||
|             fileLength = | ||||
|                 if (currentExistingFile == null || !resume) 0 else (cr.getFileLength(currentExistingFile) | ||||
|                     ?: 0)// IF NOT RESUME THEN 0, OTHERWISE THE CURRENT FILE SIZE | ||||
| 
 | ||||
|             if (!resume && currentExistingFile != null) { // DELETE FILE IF FILE EXITS AND NOT RESUME | ||||
|                 val rowsDeleted = context.contentResolver.delete(currentExistingFile, null, null) | ||||
|                 if (rowsDeleted < 1) { | ||||
|                     println("ERROR DELETING FILE!!!") | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             var appendFile = false | ||||
|             val newFileUri = if (resume && currentExistingFile != null) { | ||||
|                 appendFile = true | ||||
|                 currentExistingFile | ||||
|             } else { | ||||
|                 val contentUri = | ||||
|                     MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) // USE INSTEAD OF MediaStore.Downloads.EXTERNAL_CONTENT_URI | ||||
|                 //val currentMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) | ||||
|                 val currentMimeType = when (extension) { | ||||
|                     "vtt" -> "text/vtt" | ||||
|                     "mp4" -> "video/mp4" | ||||
|                     "srt" -> "text/plain" | ||||
|                     else -> null | ||||
|                 } | ||||
|                 val newFile = ContentValues().apply { | ||||
|                     put(MediaStore.MediaColumns.DISPLAY_NAME, displayName) | ||||
|                     put(MediaStore.MediaColumns.TITLE, name) | ||||
|                     if (currentMimeType != null) | ||||
|                         put(MediaStore.MediaColumns.MIME_TYPE, currentMimeType) | ||||
|                     put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath) | ||||
|                 } | ||||
| 
 | ||||
|                 cr.insert( | ||||
|                     contentUri, | ||||
|                     newFile | ||||
|                 ) ?: return ERROR_MEDIA_STORE_URI_CANT_BE_CREATED | ||||
|             } | ||||
| 
 | ||||
|             fileStream = cr.openOutputStream(newFileUri, "w" + (if (appendFile) "a" else "")) | ||||
|                 ?: return ERROR_CONTENT_RESOLVER_CANT_OPEN_STREAM | ||||
|         } else { | ||||
|             // NORMAL NON SCOPED STORAGE FILE CREATION | ||||
|             val rFile = File(normalPath) | ||||
|             if (!rFile.exists()) { | ||||
|                 fileLength = 0 | ||||
|                 rFile.parentFile?.mkdirs() | ||||
|                 if (!rFile.createNewFile()) return ERROR_CREATE_FILE | ||||
|             } else { | ||||
|                 if (resume) { | ||||
|                     fileLength = rFile.length() | ||||
|                 } else { | ||||
|                     fileLength = 0 | ||||
|                     rFile.parentFile?.mkdirs() | ||||
|                     if (!rFile.delete()) return ERROR_DELETING_FILE | ||||
|                     if (!rFile.createNewFile()) return ERROR_CREATE_FILE | ||||
|                 } | ||||
|             } | ||||
|             fileStream = FileOutputStream(rFile, false) | ||||
|         } | ||||
| 
 | ||||
|         val torrentOptions: TorrentOptions = TorrentOptions.Builder() | ||||
|             .saveLocation(context.cacheDir.absolutePath) | ||||
|             .removeFilesAfterStop(true) | ||||
|             .build() | ||||
| 
 | ||||
|         val torrentStream = TorrentStream.init(torrentOptions) | ||||
|         torrentStream.startStream(link) | ||||
| 
 | ||||
|         var progress = 0f | ||||
|         var isDone = false | ||||
|         var isFailed = false | ||||
| 
 | ||||
|         torrentStream.addListener(object : TorrentListener { | ||||
|             override fun onStreamPrepared(torrent: Torrent?) { | ||||
| 
 | ||||
|                 //showToast(context, "Stream Prepared", Toast.LENGTH_SHORT) | ||||
|             } | ||||
| 
 | ||||
|             override fun onStreamStarted(torrent: Torrent?) { | ||||
|                 // showToast(context, "Stream Started", Toast.LENGTH_SHORT) | ||||
|             } | ||||
| 
 | ||||
|             override fun onStreamError(torrent: Torrent?, e: java.lang.Exception?) { | ||||
|                 isFailed = true | ||||
|                 e?.printStackTrace() | ||||
|                 // showToast(context, e?.localizedMessage ?: "Error loading", Toast.LENGTH_SHORT) | ||||
|             } | ||||
| 
 | ||||
|             override fun onStreamReady(torrent: Torrent?) { | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
|             @SuppressLint("SetTextI18n") | ||||
|             override fun onStreamProgress(torrent: Torrent?, status: StreamStatus?) { | ||||
|                 try { | ||||
|                     println("Seeders ${status?.seeds}") | ||||
|                     println("Download Speed ${status?.downloadSpeed}") | ||||
|                     println("Progress ${status?.progress}%") | ||||
| 
 | ||||
|                     val lengthSize = getSizeAndProgressFromTorrent(torrent, status?.progress) | ||||
|                     if (lengthSize != null) { | ||||
|                         progress = status?.progress!! | ||||
|                         val type = if (progress >= 100f) DownloadType.IsDone else DownloadType.IsDownloading | ||||
|                         parentId?.let { id -> | ||||
|                             try { | ||||
|                                 downloadStatus[id] = type | ||||
|                                 downloadStatusEvent.invoke(Pair(id, type)) | ||||
|                                 downloadProgressEvent.invoke(Triple(id, lengthSize.first, lengthSize.second)) | ||||
|                             } catch (e: Exception) { | ||||
|                                 // IDK MIGHT ERROR | ||||
|                             } | ||||
|                         } | ||||
|                         createNotificationCallback.invoke( | ||||
|                             CreateNotificationMetadata( | ||||
|                                 type, | ||||
|                                 lengthSize.first, lengthSize.second | ||||
|                             ) | ||||
|                         ) | ||||
|                     } | ||||
|                     val pro = status?.progress | ||||
|                     if (pro != null && pro >= 100) { | ||||
|                         isDone = true | ||||
|                     } | ||||
| 
 | ||||
|                 } catch (e: IllegalStateException) { | ||||
|                     e.printStackTrace() | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             override fun onStreamStopped() { | ||||
|                 if (progress > 98) { | ||||
|                     isDone = true | ||||
|                 } else { | ||||
|                     isFailed = true | ||||
|                 } | ||||
|                 println("stream stopped") | ||||
|             } | ||||
|         }) | ||||
| 
 | ||||
|         fun updateNot(type: DownloadType) { | ||||
|             val lengthSize = getSizeAndProgressFromTorrent(torrentStream.currentTorrent, progress) ?: return | ||||
| 
 | ||||
|             createNotificationCallback.invoke( | ||||
|                 CreateNotificationMetadata( | ||||
|                     type, | ||||
|                     lengthSize.first, lengthSize.second | ||||
|                 ) | ||||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         val downloadEventListener = { event: Pair<Int, DownloadActionType> -> | ||||
|             if (event.first == parentId) { | ||||
|                 when (event.second) { | ||||
|                     DownloadActionType.Pause -> { | ||||
|                         torrentStream?.currentTorrent?.pause() | ||||
|                         updateNot(DownloadType.IsPaused) | ||||
|                     } | ||||
|                     DownloadActionType.Stop -> { | ||||
|                         isFailed = true | ||||
|                         torrentStream.stopStream() | ||||
|                         torrentStream?.currentTorrent?.videoFile?.delete() | ||||
|                         updateNot(DownloadType.IsStopped) | ||||
|                         context.removeKey(KEY_RESUME_PACKAGES, event.first.toString()) | ||||
|                         saveQueue(context) | ||||
|                     } | ||||
|                     DownloadActionType.Resume -> { | ||||
|                         torrentStream?.currentTorrent?.resume() | ||||
|                         updateNot(DownloadType.IsDownloading) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (parentId != null) | ||||
|             downloadEvent += downloadEventListener | ||||
| 
 | ||||
|         var lastProgress = progress | ||||
|         var lastUpdateTime = System.currentTimeMillis() | ||||
|         while (!isDone && !isFailed) { | ||||
|             sleep(100) | ||||
|             if (lastProgress != progress) { | ||||
|                 lastUpdateTime = System.currentTimeMillis() | ||||
|                 lastProgress = progress | ||||
|             } | ||||
|             if (progress >= 98f && System.currentTimeMillis() - lastUpdateTime > 10000L) { // after 10 sec set as done | ||||
|                 isDone = true | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (parentId != null) | ||||
|             downloadEvent -= downloadEventListener | ||||
| 
 | ||||
|         // RETURN MESSAGE | ||||
|         return when { | ||||
|             isFailed -> { | ||||
|                 parentId?.let { id -> downloadProgressEvent.invoke(Triple(id, 0, 0)) } | ||||
|                 SUCCESS_STOPPED | ||||
|             } | ||||
|             isDone -> { | ||||
|                 torrentStream?.currentTorrent?.videoStream?.copyTo(fileStream) | ||||
|                 torrentStream?.currentTorrent?.videoFile?.delete() | ||||
| 
 | ||||
|                 SUCCESS_DOWNLOAD_DONE | ||||
|             } | ||||
|             else -> { | ||||
|                 SUCCESS_DOWNLOAD_DONE | ||||
|                 //idk | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun downloadThing( | ||||
|         context: Context, | ||||
|         link: IDownloadableMinimum, | ||||
|  | @ -498,6 +755,10 @@ object VideoDownloadManager { | |||
|         parentId: Int?, | ||||
|         createNotificationCallback: (CreateNotificationMetadata) -> Unit | ||||
|     ): Int { | ||||
|         if (link.url.startsWith("magnet") || link.url.endsWith(".torrent")) { | ||||
|             return downloadTorrent(context, link.url, name, folder, extension, parentId, createNotificationCallback) | ||||
|         } | ||||
| 
 | ||||
|         val relativePath = (Environment.DIRECTORY_DOWNLOADS + '/' + folder + '/').replace('/', File.separatorChar) | ||||
|         val displayName = "$name.$extension" | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ | |||
|         xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|         xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="match_parent" | ||||
|         android:layout_height="match_parent" xmlns:tools="http://schemas.android.com/tools" | ||||
|         android:orientation="horizontal" | ||||
|         android:keepScreenOn="true" | ||||
|         app:backgroundTint="@android:color/black" | ||||
|  | @ -60,6 +60,8 @@ | |||
|                 android:layout_height="45dp"> | ||||
|         </com.google.android.material.button.MaterialButton> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|         <ProgressBar | ||||
|                 android:layout_width="50dp" | ||||
|                 android:layout_height="50dp" | ||||
|  | @ -94,4 +96,41 @@ | |||
|             </ImageView> | ||||
|         </FrameLayout> | ||||
|     </FrameLayout> | ||||
|     <FrameLayout | ||||
|             android:visibility="gone" | ||||
|             android:paddingStart="20dp" | ||||
|             android:paddingEnd="20dp" | ||||
|             app:layout_constraintBottom_toBottomOf="parent" | ||||
|             app:layout_constraintEnd_toEndOf="parent" | ||||
|             app:layout_constraintStart_toStartOf="parent" | ||||
|             app:layout_constraintTop_toTopOf="parent" | ||||
|             android:id="@+id/player_torrent_info" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content"> | ||||
| 
 | ||||
|         <TextView | ||||
|                 android:layout_width="match_parent" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 app:layout_constraintTop_toTopOf="parent" | ||||
|                 app:layout_constraintLeft_toLeftOf="parent" | ||||
|                 android:gravity="start" | ||||
|                 android:layout_marginTop="15dp" | ||||
|                 android:textStyle="bold" | ||||
|                 android:textColor="@color/white" | ||||
|                 android:id="@+id/video_torrent_progress" | ||||
|                 tools:text="78% at 18kb/s" | ||||
|         > | ||||
|         </TextView> | ||||
|         <TextView | ||||
|                 android:layout_width="match_parent" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 app:layout_constraintLeft_toLeftOf="parent" | ||||
|                 android:gravity="start" | ||||
|                 android:layout_marginTop="0dp" | ||||
|                 android:textColor="@color/white" | ||||
|                 android:id="@+id/video_torrent_seeders" | ||||
|                 tools:text="17 seeders" | ||||
|                 app:layout_constraintTop_toBottomOf="@+id/video_title"> | ||||
|         </TextView> | ||||
|     </FrameLayout> | ||||
| </androidx.constraintlayout.widget.ConstraintLayout> | ||||
|  | @ -154,6 +154,7 @@ | |||
|                         android:layout_height="wrap_content"> | ||||
| 
 | ||||
|                     <androidx.cardview.widget.CardView | ||||
|                             android:id="@+id/result_poster_holder" | ||||
|                             app:cardCornerRadius="@dimen/roundedImageRadius" | ||||
|                             android:layout_width="100dp" | ||||
|                             android:layout_height="140dp"> | ||||
|  | @ -275,6 +276,7 @@ | |||
|                 <TextView | ||||
|                         android:layout_width="wrap_content" | ||||
|                         android:layout_height="wrap_content" | ||||
|                         android:id="@+id/result_plot_header" | ||||
|                         android:text="@string/result_plot" | ||||
|                         android:textSize="17sp" | ||||
|                         android:layout_marginTop="10dp" | ||||
|  |  | |||
|  | @ -34,6 +34,7 @@ | |||
|     <string name="type_none">None</string> | ||||
| 
 | ||||
|     <string name="play_movie_button">Play Movie</string> | ||||
|     <string name="play_torrent_button">Stream Torrent</string> | ||||
|     <string name="pick_source">Sources</string> | ||||
|     <string name="pick_subtitle">Subtitles</string> | ||||
|     <string name="reload_error">Retry connection…</string> | ||||
|  | @ -99,4 +100,7 @@ | |||
| 
 | ||||
|     <string name="vpn_might_be_needed">A VPN might be needed for this provider to work correctly</string> | ||||
|     <string name="vpn_torrent">This providers is a torrent, a VPN is recommended</string> | ||||
|     <string name="torrent_plot">Description</string> | ||||
|     <string name="normal_no_plot">No Plot Found</string> | ||||
|     <string name="torrent_no_plot">No Description Found</string> | ||||
| </resources> | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue