mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Compare commits
6 commits
master
...
Aria2cStre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bef34c33e9 | ||
|
|
fca8a55e05 |
||
|
|
49b905c089 | ||
|
|
afe82140fd | ||
|
|
8105231a6b | ||
|
|
d394f0e1d0 |
10 changed files with 657 additions and 61 deletions
|
|
@ -260,6 +260,8 @@ dependencies {
|
||||||
|
|
||||||
// color palette for images -> colors
|
// color palette for images -> colors
|
||||||
implementation("androidx.palette:palette-ktx:1.0.0")
|
implementation("androidx.palette:palette-ktx:1.0.0")
|
||||||
|
|
||||||
|
implementation("com.github.recloudstream:Aria2cStream:0.0.3")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register("androidSourcesJar", Jar::class) {
|
tasks.register("androidSourcesJar", Jar::class) {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
|
@ -42,6 +43,7 @@ class CustomReportSender : ReportSender {
|
||||||
// Sends all your crashes to google forms
|
// Sends all your crashes to google forms
|
||||||
override fun send(context: Context, errorContent: CrashReportData) {
|
override fun send(context: Context, errorContent: CrashReportData) {
|
||||||
println("Sending report")
|
println("Sending report")
|
||||||
|
//Log.i("Acra", "Sending report: ${errorContent.toMap().map { "${it.key}:${it.value}" }.joinToString()}")
|
||||||
val url =
|
val url =
|
||||||
"https://docs.google.com/forms/d/e/1FAIpQLSfO4r353BJ79TTY_-t5KWSIJT2xfqcQWY81xjAA1-1N0U2eSg/formResponse"
|
"https://docs.google.com/forms/d/e/1FAIpQLSfO4r353BJ79TTY_-t5KWSIJT2xfqcQWY81xjAA1-1N0U2eSg/formResponse"
|
||||||
val data = mapOf(
|
val data = mapOf(
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,7 @@ import com.lagradost.cloudstream3.ui.WatchType
|
||||||
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO
|
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO
|
||||||
import com.lagradost.cloudstream3.ui.home.HomeViewModel
|
import com.lagradost.cloudstream3.ui.home.HomeViewModel
|
||||||
import com.lagradost.cloudstream3.ui.player.BasicLink
|
import com.lagradost.cloudstream3.ui.player.BasicLink
|
||||||
|
import com.lagradost.cloudstream3.ui.player.ExtractorLinkGenerator
|
||||||
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
|
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
|
||||||
import com.lagradost.cloudstream3.ui.player.LinkGenerator
|
import com.lagradost.cloudstream3.ui.player.LinkGenerator
|
||||||
import com.lagradost.cloudstream3.ui.result.LinearListLayout
|
import com.lagradost.cloudstream3.ui.result.LinearListLayout
|
||||||
|
|
@ -130,8 +131,11 @@ import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching
|
||||||
import com.lagradost.cloudstream3.utils.Event
|
import com.lagradost.cloudstream3.utils.Event
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import com.lagradost.cloudstream3.utils.INFER_TYPE
|
||||||
import com.lagradost.cloudstream3.utils.IOnBackPressed
|
import com.lagradost.cloudstream3.utils.IOnBackPressed
|
||||||
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
|
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
|
||||||
|
import com.lagradost.cloudstream3.utils.Qualities
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.changeStatusBarState
|
import com.lagradost.cloudstream3.utils.UIHelper.changeStatusBarState
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.checkWrite
|
import com.lagradost.cloudstream3.utils.UIHelper.checkWrite
|
||||||
|
|
@ -1112,16 +1116,17 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
newLocalBinding.root.viewTreeObserver.addOnGlobalFocusChangeListener { _, newFocus ->
|
newLocalBinding.root.viewTreeObserver.addOnGlobalFocusChangeListener { _, newFocus ->
|
||||||
// println("refocus $oldFocus -> $newFocus")
|
// println("refocus $oldFocus -> $newFocus")
|
||||||
try {
|
try {
|
||||||
val r = Rect(0,0,0,0)
|
val r = Rect(0, 0, 0, 0)
|
||||||
newFocus.getDrawingRect(r)
|
newFocus.getDrawingRect(r)
|
||||||
val x = r.centerX()
|
val x = r.centerX()
|
||||||
val y = r.centerY()
|
val y = r.centerY()
|
||||||
val dx = 0 //screenWidth / 2
|
val dx = 0 //screenWidth / 2
|
||||||
val dy = screenHeight / 2
|
val dy = screenHeight / 2
|
||||||
val r2 = Rect(x-dx,y-dy,x+dx,y+dy)
|
val r2 = Rect(x - dx, y - dy, x + dx, y + dy)
|
||||||
newFocus.requestRectangleOnScreen(r2, false)
|
newFocus.requestRectangleOnScreen(r2, false)
|
||||||
// TvFocus.current =TvFocus.current.copy(y=y.toFloat())
|
// TvFocus.current =TvFocus.current.copy(y=y.toFloat())
|
||||||
} catch (_ : Throwable) { }
|
} catch (_: Throwable) {
|
||||||
|
}
|
||||||
TvFocus.updateFocusView(newFocus)
|
TvFocus.updateFocusView(newFocus)
|
||||||
/*var focus = newFocus
|
/*var focus = newFocus
|
||||||
|
|
||||||
|
|
@ -1562,6 +1567,25 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
setKey(HAS_DONE_SETUP_KEY, true)
|
setKey(HAS_DONE_SETUP_KEY, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//val defaultDirectory = "${filesDir.path}/torrent_tmp"
|
||||||
|
//File(defaultDirectory).deleteRecursively()
|
||||||
|
/*navigate(
|
||||||
|
R.id.global_to_navigation_player, GeneratorPlayer.newInstance(
|
||||||
|
ExtractorLinkGenerator(
|
||||||
|
listOf(
|
||||||
|
ExtractorLink(
|
||||||
|
source = "",
|
||||||
|
name = "hello world",
|
||||||
|
"",
|
||||||
|
Qualities.Unknown.value,
|
||||||
|
type = INFER_TYPE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)*/
|
||||||
|
|
||||||
// Used to check current focus for TV
|
// Used to check current focus for TV
|
||||||
// main {
|
// main {
|
||||||
// while (true) {
|
// while (true) {
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ abstract class AbstractPlayerFragment(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateIsPlaying(wasPlaying : CSPlayerLoading,
|
open fun updateIsPlaying(wasPlaying : CSPlayerLoading,
|
||||||
isPlaying : CSPlayerLoading) {
|
isPlaying : CSPlayerLoading) {
|
||||||
val isPlayingRightNow = CSPlayerLoading.IsPlaying == isPlaying
|
val isPlayingRightNow = CSPlayerLoading.IsPlaying == isPlaying
|
||||||
val isPausedRightNow = CSPlayerLoading.IsPaused == isPlaying
|
val isPausedRightNow = CSPlayerLoading.IsPaused == isPlaying
|
||||||
|
|
@ -265,7 +265,9 @@ abstract class AbstractPlayerFragment(
|
||||||
context?.getString(R.string.no_links_found_toast) + "\n" + message,
|
context?.getString(R.string.no_links_found_toast) + "\n" + message,
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG
|
||||||
)
|
)
|
||||||
activity?.popCurrentPage()
|
activity?.runOnUiThread {
|
||||||
|
activity?.popCurrentPage()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -370,12 +372,17 @@ abstract class AbstractPlayerFragment(
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
open fun onDownload(event : DownloadEvent) = Unit
|
||||||
|
|
||||||
/** This receives the events from the player, if you want to append functionality you do it here,
|
/** This receives the events from the player, if you want to append functionality you do it here,
|
||||||
* do note that this only receives events for UI changes,
|
* do note that this only receives events for UI changes,
|
||||||
* and returning early WONT stop it from changing in eg the player time or pause status */
|
* and returning early WONT stop it from changing in eg the player time or pause status */
|
||||||
open fun mainCallback(event : PlayerEvent) {
|
open fun mainCallback(event : PlayerEvent) {
|
||||||
Log.i(TAG, "Handle event: $event")
|
Log.i(TAG, "Handle event: $event")
|
||||||
when(event) {
|
when(event) {
|
||||||
|
is DownloadEvent -> {
|
||||||
|
onDownload(event)
|
||||||
|
}
|
||||||
is ResizedEvent -> {
|
is ResizedEvent -> {
|
||||||
playerDimensionsLoaded(event.width, event.height)
|
playerDimensionsLoaded(event.width, event.height)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.lagradost.cloudstream3.ui.player
|
package com.lagradost.cloudstream3.ui.player
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
|
|
@ -8,6 +9,7 @@ import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.Rational
|
import android.util.Rational
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.media3.common.C.*
|
import androidx.media3.common.C.*
|
||||||
import androidx.media3.common.Format
|
import androidx.media3.common.Format
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
|
|
@ -49,9 +51,12 @@ import androidx.preference.PreferenceManager
|
||||||
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||||
|
import com.lagradost.cloudstream3.CommonActivity
|
||||||
|
import com.lagradost.cloudstream3.TvType
|
||||||
import com.lagradost.cloudstream3.USER_AGENT
|
import com.lagradost.cloudstream3.USER_AGENT
|
||||||
import com.lagradost.cloudstream3.app
|
import com.lagradost.cloudstream3.app
|
||||||
import com.lagradost.cloudstream3.mvvm.debugAssert
|
import com.lagradost.cloudstream3.mvvm.debugAssert
|
||||||
|
import com.lagradost.cloudstream3.mvvm.launchSafe
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
|
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
|
||||||
|
|
@ -63,6 +68,20 @@ import com.lagradost.cloudstream3.utils.ExtractorLinkPlayList
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLinkType
|
import com.lagradost.cloudstream3.utils.ExtractorLinkType
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorUri
|
import com.lagradost.cloudstream3.utils.ExtractorUri
|
||||||
import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage
|
import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage
|
||||||
|
import com.lagradost.fetchbutton.aria2c.Aria2Args
|
||||||
|
import com.lagradost.fetchbutton.aria2c.Aria2Settings
|
||||||
|
import com.lagradost.fetchbutton.aria2c.Aria2Starter
|
||||||
|
import com.lagradost.fetchbutton.aria2c.BtPieceSelector
|
||||||
|
import com.lagradost.fetchbutton.aria2c.DownloadListener
|
||||||
|
import com.lagradost.fetchbutton.aria2c.DownloadStatusTell
|
||||||
|
import com.lagradost.fetchbutton.aria2c.FileAllocationType
|
||||||
|
import com.lagradost.fetchbutton.aria2c.FollowMetaLinkType
|
||||||
|
import com.lagradost.fetchbutton.aria2c.UriRequest
|
||||||
|
import com.lagradost.fetchbutton.aria2c.newUriRequest
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.lang.IllegalArgumentException
|
import java.lang.IllegalArgumentException
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
@ -88,7 +107,9 @@ class CS3IPlayer : IPlayer {
|
||||||
private var exoPlayer: ExoPlayer? = null
|
private var exoPlayer: ExoPlayer? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
// If the old value is not null then the player has not been properly released.
|
// If the old value is not null then the player has not been properly released.
|
||||||
debugAssert({ field != null && value != null }, { "Previous player instance should be released!" })
|
debugAssert(
|
||||||
|
{ field != null && value != null },
|
||||||
|
{ "Previous player instance should be released!" })
|
||||||
field = value
|
field = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,6 +125,9 @@ class CS3IPlayer : IPlayer {
|
||||||
private var lastMuteVolume: Float = 1.0f
|
private var lastMuteVolume: Float = 1.0f
|
||||||
|
|
||||||
private var currentLink: ExtractorLink? = null
|
private var currentLink: ExtractorLink? = null
|
||||||
|
|
||||||
|
private var currentAria2cRequestLink: ExtractorLink? = null
|
||||||
|
private var currentAria2cRequestId: Long? = null
|
||||||
private var currentDownloadedFile: ExtractorUri? = null
|
private var currentDownloadedFile: ExtractorUri? = null
|
||||||
private var hasUsedFirstRender = false
|
private var hasUsedFirstRender = false
|
||||||
|
|
||||||
|
|
@ -182,6 +206,333 @@ class CS3IPlayer : IPlayer {
|
||||||
subtitleHelper.initSubtitles(subView, subHolder, style)
|
subtitleHelper.initSubtitles(subView, subHolder, style)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getPlayableFile(
|
||||||
|
data: com.lagradost.fetchbutton.aria2c.Metadata,
|
||||||
|
minimumBytes: Long
|
||||||
|
): Uri? {
|
||||||
|
for (item in data.items) {
|
||||||
|
for (file in item.files) {
|
||||||
|
// only allow files with a length above minimumBytes
|
||||||
|
if (file.completedLength < minimumBytes) continue
|
||||||
|
// only allow video formats
|
||||||
|
if (videoFormats.none { suf ->
|
||||||
|
file.path.contains(
|
||||||
|
suf,
|
||||||
|
ignoreCase = true
|
||||||
|
)
|
||||||
|
}) continue
|
||||||
|
|
||||||
|
return file.path.toUri()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private val videoFormats = arrayOf(
|
||||||
|
".3g2",
|
||||||
|
".3gp",
|
||||||
|
".amv",
|
||||||
|
".asf",
|
||||||
|
".avi",
|
||||||
|
".drc",
|
||||||
|
".flv",
|
||||||
|
".f4v",
|
||||||
|
".f4p",
|
||||||
|
".f4a",
|
||||||
|
".f4b",
|
||||||
|
".gif",
|
||||||
|
".gifv",
|
||||||
|
".m4v",
|
||||||
|
".mkv",
|
||||||
|
".mng",
|
||||||
|
".mov",
|
||||||
|
".qt",
|
||||||
|
".mp4",
|
||||||
|
".m4p",
|
||||||
|
".mpg", ".mp2", ".mpeg", ".mpe", ".mpv",
|
||||||
|
".mpg", ".mpeg", ".m2v",
|
||||||
|
".MTS", ".M2TS", ".TS",
|
||||||
|
".mxf",
|
||||||
|
".nsv",
|
||||||
|
".ogv", ".ogg",
|
||||||
|
//".rm", // Made for RealPlayer
|
||||||
|
//".rmvb", // Made for RealPlayer
|
||||||
|
".svi",
|
||||||
|
".viv",
|
||||||
|
".vob",
|
||||||
|
".webm",
|
||||||
|
".wmv",
|
||||||
|
".yuv"
|
||||||
|
)
|
||||||
|
|
||||||
|
@Throws
|
||||||
|
private suspend fun awaitAria2c(
|
||||||
|
activity: Activity,
|
||||||
|
link: ExtractorLink,
|
||||||
|
requestId: Long,
|
||||||
|
) {
|
||||||
|
val minimumBytes: Long = 30 shl 20
|
||||||
|
var hasFileChecked = false
|
||||||
|
while (true) {
|
||||||
|
val gid = DownloadListener.sessionIdToGid[requestId]
|
||||||
|
|
||||||
|
// request has not yet been processed, wait for it to do
|
||||||
|
if (gid == null) {
|
||||||
|
delay(1000)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val metadata = DownloadListener.getInfo(gid)
|
||||||
|
event(
|
||||||
|
DownloadEvent(
|
||||||
|
downloadedBytes = metadata.downloadedLength,
|
||||||
|
downloadSpeed = metadata.downloadSpeed,
|
||||||
|
totalBytes = metadata.totalLength,
|
||||||
|
connections = metadata.items.sumOf { it.connections }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
when (metadata.status) {
|
||||||
|
// if completed/error/removed then we don't have to wait anymore
|
||||||
|
DownloadStatusTell.Complete,
|
||||||
|
DownloadStatusTell.Error,
|
||||||
|
DownloadStatusTell.Removed -> break
|
||||||
|
|
||||||
|
// if waiting to be added, wait more
|
||||||
|
DownloadStatusTell.Waiting -> {
|
||||||
|
delay(1000)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadStatusTell.Active -> {
|
||||||
|
//metadata.downloadedLength >= metadata.totalLength &&
|
||||||
|
if (getPlayableFile(
|
||||||
|
metadata,
|
||||||
|
minimumBytes = minimumBytes
|
||||||
|
) != null
|
||||||
|
) break
|
||||||
|
|
||||||
|
// as we don't want to waste the users time with torrents that is useless
|
||||||
|
// we do this to check that at a video file exists
|
||||||
|
if (!hasFileChecked && metadata.totalLength > minimumBytes) {
|
||||||
|
hasFileChecked = true
|
||||||
|
if (getPlayableFile(
|
||||||
|
metadata,
|
||||||
|
minimumBytes = -1
|
||||||
|
) == null
|
||||||
|
) {
|
||||||
|
throw Exception("Download file has no video")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println("downloaded ${metadata.downloadedLength}/${metadata.totalLength}")
|
||||||
|
delay(1000)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// if downloading then check if we have reached a stable file length
|
||||||
|
/*DownloadStatusTell.Active -> {
|
||||||
|
if (getPlayableFile(metadata, minimumBytes = 50 shl 20) != null) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
delay(1000)
|
||||||
|
continue
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// unpause any pending files
|
||||||
|
DownloadStatusTell.Paused -> {
|
||||||
|
Aria2Starter.unpause(gid)
|
||||||
|
delay(1000)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
null -> {
|
||||||
|
delay(1000)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val gid = DownloadListener.sessionIdToGid[requestId]
|
||||||
|
?: throw Exception("Unable to start download")
|
||||||
|
|
||||||
|
val metadata = DownloadListener.getInfo(gid)
|
||||||
|
|
||||||
|
when (metadata.status) {
|
||||||
|
DownloadStatusTell.Active, DownloadStatusTell.Complete -> {
|
||||||
|
val uri = getPlayableFile(metadata, minimumBytes = minimumBytes)
|
||||||
|
?: throw Exception("Not downloaded enough")
|
||||||
|
activity.runOnUiThread {
|
||||||
|
//Log.i(TAG, "downloaded data: $metadata")
|
||||||
|
exoPlayer?.release()
|
||||||
|
exoPlayer = null
|
||||||
|
loadOfflinePlayer(
|
||||||
|
activity,
|
||||||
|
ExtractorUri(
|
||||||
|
// we require at least 10MB to play the file
|
||||||
|
uri = uri,
|
||||||
|
name = link.name,
|
||||||
|
tvType = TvType.Torrent
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadStatusTell.Waiting -> {
|
||||||
|
throw Exception("Download was unable to be started")
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadStatusTell.Paused -> {
|
||||||
|
throw Exception("Download is paused")
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadStatusTell.Error -> {
|
||||||
|
throw Exception("Download error")
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadStatusTell.Removed -> {
|
||||||
|
throw Exception("Download removed")
|
||||||
|
}
|
||||||
|
|
||||||
|
null -> {
|
||||||
|
throw Exception("Unexpected download error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun releaseAria2c() = pauseAllAria2c()
|
||||||
|
private fun pauseAllAria2c() {
|
||||||
|
for ((_, gid) in DownloadListener.sessionIdToGid) {
|
||||||
|
Aria2Starter.pause(gid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Throws
|
||||||
|
private suspend fun playAria2c(activity: Activity, link: ExtractorLink) {
|
||||||
|
// ephemeral id based on url to make it unique
|
||||||
|
val requestId = link.url.hashCode().toLong()
|
||||||
|
currentAria2cRequestId = requestId
|
||||||
|
currentAria2cRequestLink = link
|
||||||
|
|
||||||
|
val uriReq = UriRequest(
|
||||||
|
id = requestId,
|
||||||
|
uris = listOf(link.url),
|
||||||
|
args = Aria2Args(
|
||||||
|
headers = link.headers,
|
||||||
|
referer = link.referer,
|
||||||
|
/** torrent specifics to make it possible to stream */
|
||||||
|
seedRatio = 0.0f,
|
||||||
|
seedTimeMin = 0.0f,
|
||||||
|
btPieceSelector = BtPieceSelector.Inorder,
|
||||||
|
followTorrent = FollowMetaLinkType.Mem,
|
||||||
|
fileAllocation = FileAllocationType.None,
|
||||||
|
btPrioritizePiece = "head=30M,tail=30M",
|
||||||
|
/** Best trackers to make it faster */
|
||||||
|
btTracker = listOf(
|
||||||
|
"udp://tracker.opentrackr.org:1337/announce",
|
||||||
|
"https://tracker2.ctix.cn/announce",
|
||||||
|
"https://tracker1.520.jp:443/announce",
|
||||||
|
"udp://opentracker.i2p.rocks:6969/announce",
|
||||||
|
"udp://open.tracker.cl:1337/announce",
|
||||||
|
"udp://open.demonii.com:1337/announce",
|
||||||
|
"http://tracker.openbittorrent.com:80/announce",
|
||||||
|
"udp://tracker.openbittorrent.com:6969/announce",
|
||||||
|
"udp://open.stealth.si:80/announce",
|
||||||
|
"udp://exodus.desync.com:6969/announce",
|
||||||
|
"udp://tracker-udp.gbitt.info:80/announce",
|
||||||
|
"udp://explodie.org:6969/announce",
|
||||||
|
"https://tracker.gbitt.info:443/announce",
|
||||||
|
"http://tracker.gbitt.info:80/announce",
|
||||||
|
"udp://uploads.gamecoast.net:6969/announce",
|
||||||
|
"udp://tracker1.bt.moack.co.kr:80/announce",
|
||||||
|
"udp://tracker.tiny-vps.com:6969/announce",
|
||||||
|
"udp://tracker.theoks.net:6969/announce",
|
||||||
|
"udp://tracker.dump.cl:6969/announce",
|
||||||
|
"udp://tracker.bittor.pw:1337/announce",
|
||||||
|
"https://tracker1.520.jp:443/announce",
|
||||||
|
"udp://opentracker.i2p.rocks:6969/announce",
|
||||||
|
"udp://open.tracker.cl:1337/announce",
|
||||||
|
"udp://open.demonii.com:1337/announce",
|
||||||
|
"http://tracker.openbittorrent.com:80/announce",
|
||||||
|
"udp://tracker.openbittorrent.com:6969/announce",
|
||||||
|
"udp://open.stealth.si:80/announce",
|
||||||
|
"udp://exodus.desync.com:6969/announce",
|
||||||
|
"udp://tracker-udp.gbitt.info:80/announce",
|
||||||
|
"udp://explodie.org:6969/announce",
|
||||||
|
"https://tracker.gbitt.info:443/announce",
|
||||||
|
"http://tracker.gbitt.info:80/announce",
|
||||||
|
"udp://uploads.gamecoast.net:6969/announce",
|
||||||
|
"udp://tracker1.bt.moack.co.kr:80/announce",
|
||||||
|
"udp://tracker.tiny-vps.com:6969/announce",
|
||||||
|
"udp://tracker.theoks.net:6969/announce",
|
||||||
|
"udp://tracker.dump.cl:6969/announce",
|
||||||
|
"udp://tracker.bittor.pw:1337/announce"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val metadata =
|
||||||
|
DownloadListener.sessionIdToGid[requestId]?.let { gid -> DownloadListener.getInfo(gid) }
|
||||||
|
|
||||||
|
when (metadata?.status) {
|
||||||
|
DownloadStatusTell.Removed, DownloadStatusTell.Error, null -> {
|
||||||
|
Aria2Starter.download(uriReq)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
saveData()
|
||||||
|
awaitAria2c(activity, link, requestId)
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
// if we detect any download error then we delete it as we don't want any useless background tasks
|
||||||
|
Aria2Starter.delete(DownloadListener.sessionIdToGid[requestId], requestId)
|
||||||
|
throw t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadAria2c(link: ExtractorLink) {
|
||||||
|
val act = CommonActivity.activity
|
||||||
|
if (act == null) {
|
||||||
|
event(ErrorEvent(IllegalArgumentException("No activity")))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
try {
|
||||||
|
val defaultDirectory = "${act.cacheDir.path}/torrent_tmp"
|
||||||
|
|
||||||
|
// start the client if not active, lazy init
|
||||||
|
if (Aria2Starter.client == null) {
|
||||||
|
Aria2Starter.start(
|
||||||
|
activity = act,
|
||||||
|
Aria2Settings(
|
||||||
|
UUID.randomUUID().toString(),
|
||||||
|
4337,
|
||||||
|
defaultDirectory,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
// remove all the cache
|
||||||
|
//File(defaultDirectory).deleteRecursively()
|
||||||
|
}
|
||||||
|
playAria2c(act, link)
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
event(ErrorEvent(t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** hijacks the torrent links and downloads them with aria2c instead to be played */
|
||||||
|
private fun loadOnlinePlayer(context: Context, link: ExtractorLink) {
|
||||||
|
when (link.type) {
|
||||||
|
ExtractorLinkType.TORRENT, ExtractorLinkType.MAGNET -> loadAria2c(link)
|
||||||
|
else -> {
|
||||||
|
loadOnlinePlayerReal(context, link)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun loadPlayer(
|
override fun loadPlayer(
|
||||||
context: Context,
|
context: Context,
|
||||||
sameEpisode: Boolean,
|
sameEpisode: Boolean,
|
||||||
|
|
@ -212,6 +563,7 @@ class CS3IPlayer : IPlayer {
|
||||||
if (link != null) {
|
if (link != null) {
|
||||||
loadOnlinePlayer(context, link)
|
loadOnlinePlayer(context, link)
|
||||||
} else if (data != null) {
|
} else if (data != null) {
|
||||||
|
currentAria2cRequestId = null
|
||||||
loadOfflinePlayer(context, data)
|
loadOfflinePlayer(context, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -470,6 +822,7 @@ class CS3IPlayer : IPlayer {
|
||||||
currentTextRenderer = null
|
currentTextRenderer = null
|
||||||
|
|
||||||
exoPlayer = null
|
exoPlayer = null
|
||||||
|
releaseAria2c()
|
||||||
//simpleCache = null
|
//simpleCache = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -871,8 +1224,20 @@ class CS3IPlayer : IPlayer {
|
||||||
|
|
||||||
CSPlayerEvent.SeekForward -> seekTime(seekActionTime, source)
|
CSPlayerEvent.SeekForward -> seekTime(seekActionTime, source)
|
||||||
CSPlayerEvent.SeekBack -> seekTime(-seekActionTime, source)
|
CSPlayerEvent.SeekBack -> seekTime(-seekActionTime, source)
|
||||||
CSPlayerEvent.NextEpisode -> event(EpisodeSeekEvent(offset = 1, source = source))
|
CSPlayerEvent.NextEpisode -> event(
|
||||||
CSPlayerEvent.PrevEpisode -> event(EpisodeSeekEvent(offset = -1, source = source))
|
EpisodeSeekEvent(
|
||||||
|
offset = 1,
|
||||||
|
source = source
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
CSPlayerEvent.PrevEpisode -> event(
|
||||||
|
EpisodeSeekEvent(
|
||||||
|
offset = -1,
|
||||||
|
source = source
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
CSPlayerEvent.SkipCurrentChapter -> {
|
CSPlayerEvent.SkipCurrentChapter -> {
|
||||||
//val dur = this@CS3IPlayer.getDuration() ?: return@apply
|
//val dur = this@CS3IPlayer.getDuration() ?: return@apply
|
||||||
getCurrentTimestamp()?.let { lastTimeStamp ->
|
getCurrentTimestamp()?.let { lastTimeStamp ->
|
||||||
|
|
@ -1019,10 +1384,66 @@ class CS3IPlayer : IPlayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPlayerError(error: PlaybackException) {
|
override fun onPlayerError(error: PlaybackException) {
|
||||||
// If the Network fails then ignore the exception if the duration is set.
|
val aria2cRequestId = currentAria2cRequestId
|
||||||
// This is to switch mirrors automatically if the stream has not been fetched, but
|
val loadedLink = currentAria2cRequestLink
|
||||||
// allow playing the buffer without internet as then the duration is fetched.
|
|
||||||
when {
|
when {
|
||||||
|
// if we are loading an torrent, then we will get these errors, in that case
|
||||||
|
// we just treat it as buffering
|
||||||
|
aria2cRequestId != null && loadedLink != null &&
|
||||||
|
(error.errorCode == PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE
|
||||||
|
|| error.errorCode == PlaybackException.ERROR_CODE_PARSING_MANIFEST_MALFORMED
|
||||||
|
|| error.errorCode == PlaybackException.ERROR_CODE_PARSING_CONTAINER_MALFORMED
|
||||||
|
|| error.errorCode == PlaybackException.ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED
|
||||||
|
|| error.errorCode == PlaybackException.ERROR_CODE_IO_UNSPECIFIED
|
||||||
|
) -> {
|
||||||
|
val position = exoPlayer?.currentPosition ?: 0L
|
||||||
|
val gid = DownloadListener.sessionIdToGid[aria2cRequestId]
|
||||||
|
Log.i(TAG, "Aria2 error $error error ${error.errorCode}")
|
||||||
|
if(gid == null) {
|
||||||
|
event(ErrorEvent(error))
|
||||||
|
super.onPlayerError(error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
event(
|
||||||
|
StatusEvent(
|
||||||
|
wasPlaying = CSPlayerLoading.IsPlaying,
|
||||||
|
isPlaying = CSPlayerLoading.IsBuffering
|
||||||
|
)
|
||||||
|
)
|
||||||
|
CoroutineScope(Dispatchers.IO).launchSafe {
|
||||||
|
for (i in 0..5) {
|
||||||
|
val metadata = DownloadListener.getInfo(gid)
|
||||||
|
event(
|
||||||
|
DownloadEvent(
|
||||||
|
downloadedBytes = metadata.downloadedLength,
|
||||||
|
downloadSpeed = metadata.downloadSpeed,
|
||||||
|
totalBytes = metadata.totalLength,
|
||||||
|
connections = metadata.items.sumOf { it.connections }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommonActivity.activity?.runOnUiThread {
|
||||||
|
// exoPlayer?.prepare()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// shitty solution to release it every time, however we will get timeout otherwise
|
||||||
|
CommonActivity.activity?.let { act ->
|
||||||
|
try {
|
||||||
|
playbackPosition = position
|
||||||
|
awaitAria2c(act, loadedLink, aria2cRequestId)
|
||||||
|
} catch (t : Throwable) {
|
||||||
|
event(ErrorEvent(t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the Network fails then ignore the exception if the duration is set.
|
||||||
|
// This is to switch mirrors automatically if the stream has not been fetched, but
|
||||||
|
// allow playing the buffer without internet as then the duration is fetched.
|
||||||
error.errorCode == PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED
|
error.errorCode == PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED
|
||||||
&& exoPlayer?.duration != TIME_UNSET -> {
|
&& exoPlayer?.duration != TIME_UNSET -> {
|
||||||
exoPlayer?.prepare()
|
exoPlayer?.prepare()
|
||||||
|
|
@ -1249,10 +1670,11 @@ class CS3IPlayer : IPlayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("UnsafeOptInUsageError")
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
private fun loadOnlinePlayer(context: Context, link: ExtractorLink) {
|
private fun loadOnlinePlayerReal(context: Context, link: ExtractorLink) {
|
||||||
Log.i(TAG, "loadOnlinePlayer $link")
|
Log.i(TAG, "loadOnlinePlayer $link")
|
||||||
try {
|
try {
|
||||||
currentLink = link
|
currentLink = link
|
||||||
|
currentAria2cRequestId = null
|
||||||
|
|
||||||
if (ignoreSSL) {
|
if (ignoreSSL) {
|
||||||
// Disables ssl check
|
// Disables ssl check
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.text.format.Formatter
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
|
@ -92,6 +93,60 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
|
|
||||||
private var binding: FragmentPlayerBinding? = null
|
private var binding: FragmentPlayerBinding? = null
|
||||||
|
|
||||||
|
override fun playerDimensionsLoaded(width: Int, height: Int) {
|
||||||
|
setPlayerDimen(width to height)
|
||||||
|
showDownloadProgress(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun playerError(exception: Throwable) {
|
||||||
|
Log.i(TAG, "playerError = $currentSelectedLink")
|
||||||
|
showDownloadProgress(null)
|
||||||
|
super.playerError(exception)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDownload(event: DownloadEvent) {
|
||||||
|
super.onDownload(event)
|
||||||
|
showDownloadProgress(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*override fun updateIsPlaying(wasPlaying: CSPlayerLoading, isPlaying: CSPlayerLoading) {
|
||||||
|
super.updateIsPlaying(wasPlaying, isPlaying)
|
||||||
|
if(isPlaying == CSPlayerLoading.IsPlaying)
|
||||||
|
activity?.runOnUiThread {
|
||||||
|
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
private fun showDownloadProgress(event: DownloadEvent?) {
|
||||||
|
activity?.runOnUiThread {
|
||||||
|
if(event == null) {
|
||||||
|
binding?.downloadHeader?.isVisible = false
|
||||||
|
return@runOnUiThread
|
||||||
|
}
|
||||||
|
binding?.downloadHeader?.isVisible = true
|
||||||
|
binding?.downloadedProgress?.apply {
|
||||||
|
val indeterminate = event.totalBytes <= 0 || event.downloadedBytes <= 0
|
||||||
|
isIndeterminate = indeterminate
|
||||||
|
if (!indeterminate) {
|
||||||
|
max = (event.totalBytes / 1000).toInt()
|
||||||
|
progress = (event.downloadedBytes / 1000).toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding?.downloadedProgressText.setText(
|
||||||
|
txt(
|
||||||
|
R.string.download_size_format,
|
||||||
|
Formatter.formatShortFileSize(context, event.downloadedBytes),
|
||||||
|
Formatter.formatShortFileSize(context, event.totalBytes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val downloadSpeed = Formatter.formatShortFileSize(context, event.downloadSpeed)
|
||||||
|
binding?.downloadedProgressSpeedText?.text = event.connections?.let { connections ->
|
||||||
|
"%s/s - %d Connections".format(downloadSpeed, connections)
|
||||||
|
} ?: downloadSpeed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun startLoading() {
|
private fun startLoading() {
|
||||||
player.release()
|
player.release()
|
||||||
currentSelectedSubtitles = null
|
currentSelectedSubtitles = null
|
||||||
|
|
@ -883,10 +938,6 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun playerError(exception: Throwable) {
|
|
||||||
Log.i(TAG, "playerError = $currentSelectedLink")
|
|
||||||
super.playerError(exception)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun noLinksFound() {
|
private fun noLinksFound() {
|
||||||
showToast(R.string.no_links_found_toast, Toast.LENGTH_SHORT)
|
showToast(R.string.no_links_found_toast, Toast.LENGTH_SHORT)
|
||||||
|
|
@ -945,7 +996,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
|
|
||||||
var maxEpisodeSet: Int? = null
|
var maxEpisodeSet: Int? = null
|
||||||
var hasRequestedStamps: Boolean = false
|
var hasRequestedStamps: Boolean = false
|
||||||
override fun playerPositionChanged(position: Long, duration : Long) {
|
override fun playerPositionChanged(position: Long, duration: Long) {
|
||||||
// Don't save livestream data
|
// Don't save livestream data
|
||||||
if ((currentMeta as? ResultEpisode)?.tvType?.isLiveStream() == true) return
|
if ((currentMeta as? ResultEpisode)?.tvType?.isLiveStream() == true) return
|
||||||
|
|
||||||
|
|
@ -1208,10 +1259,6 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun playerDimensionsLoaded(width: Int, height : Int) {
|
|
||||||
setPlayerDimen(width to height)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun unwrapBundle(savedInstanceState: Bundle?) {
|
private fun unwrapBundle(savedInstanceState: Bundle?) {
|
||||||
Log.i(TAG, "unwrapBundle = $savedInstanceState")
|
Log.i(TAG, "unwrapBundle = $savedInstanceState")
|
||||||
savedInstanceState?.let { bundle ->
|
savedInstanceState?.let { bundle ->
|
||||||
|
|
@ -1358,6 +1405,11 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
activity?.popCurrentPage()
|
activity?.popCurrentPage()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding?.playerLoadingGoBack2?.setOnClickListener {
|
||||||
|
player.release()
|
||||||
|
activity?.popCurrentPage()
|
||||||
|
}
|
||||||
|
|
||||||
observe(viewModel.currentStamps) { stamps ->
|
observe(viewModel.currentStamps) { stamps ->
|
||||||
player.addTimeStamps(stamps)
|
player.addTimeStamps(stamps)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,10 @@ fun LoadType.toSet() : Set<ExtractorLinkType> {
|
||||||
LoadType.InApp -> setOf(
|
LoadType.InApp -> setOf(
|
||||||
ExtractorLinkType.VIDEO,
|
ExtractorLinkType.VIDEO,
|
||||||
ExtractorLinkType.DASH,
|
ExtractorLinkType.DASH,
|
||||||
ExtractorLinkType.M3U8
|
ExtractorLinkType.M3U8,
|
||||||
|
// testing
|
||||||
|
ExtractorLinkType.TORRENT,
|
||||||
|
ExtractorLinkType.MAGNET,
|
||||||
)
|
)
|
||||||
LoadType.Browser -> setOf(
|
LoadType.Browser -> setOf(
|
||||||
ExtractorLinkType.VIDEO,
|
ExtractorLinkType.VIDEO,
|
||||||
|
|
|
||||||
|
|
@ -72,9 +72,20 @@ data class PositionEvent(
|
||||||
val durationMs: Long,
|
val durationMs: Long,
|
||||||
) : PlayerEvent() {
|
) : PlayerEvent() {
|
||||||
/** how many ms (+-) we have skipped */
|
/** how many ms (+-) we have skipped */
|
||||||
val seekMs : Long get() = toMs - fromMs
|
val seekMs: Long get() = toMs - fromMs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Used for torrent to pre-download a video before playing it */
|
||||||
|
data class DownloadEvent(
|
||||||
|
val downloadedBytes: Long,
|
||||||
|
val totalBytes: Long,
|
||||||
|
/** bytes / sec */
|
||||||
|
val downloadSpeed: Long,
|
||||||
|
val connections: Int?,
|
||||||
|
|
||||||
|
override val source: PlayerEventSource = PlayerEventSource.Player
|
||||||
|
) : PlayerEvent()
|
||||||
|
|
||||||
/** player error when rendering or misc, used to display toast or log */
|
/** player error when rendering or misc, used to display toast or log */
|
||||||
data class ErrorEvent(
|
data class ErrorEvent(
|
||||||
val error: Throwable,
|
val error: Throwable,
|
||||||
|
|
|
||||||
|
|
@ -278,9 +278,9 @@ enum class ExtractorLinkType {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun inferTypeFromUrl(url: String): ExtractorLinkType {
|
private fun inferTypeFromUrl(url: String): ExtractorLinkType {
|
||||||
val path = normalSafeApiCall { URL(url).path }
|
val path = try { URL(url).path } catch (_ : Throwable) { null }
|
||||||
return when {
|
return when {
|
||||||
path?.endsWith(".m3u8") == true -> ExtractorLinkType.M3U8
|
path?.endsWith(".m3u8") == true || path?.endsWith(".m3u") == true -> ExtractorLinkType.M3U8
|
||||||
path?.endsWith(".mpd") == true -> ExtractorLinkType.DASH
|
path?.endsWith(".mpd") == true -> ExtractorLinkType.DASH
|
||||||
path?.endsWith(".torrent") == true -> ExtractorLinkType.TORRENT
|
path?.endsWith(".torrent") == true -> ExtractorLinkType.TORRENT
|
||||||
url.startsWith("magnet:") -> ExtractorLinkType.MAGNET
|
url.startsWith("magnet:") -> ExtractorLinkType.MAGNET
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,78 @@
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:show_timeout="0" />
|
app:show_timeout="0" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:background="@android:color/black"
|
||||||
|
android:backgroundTint="@android:color/black"
|
||||||
|
android:id="@+id/download_header"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/downloaded_progress_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
tools:text="10MB / 20MB" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/downloaded_progress_speed_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
tools:text="10MB/s - 12 seeders" />
|
||||||
|
|
||||||
|
<androidx.core.widget.ContentLoadingProgressBar
|
||||||
|
android:id="@+id/downloaded_progress"
|
||||||
|
style="@android:style/Widget.Material.ProgressBar.Horizontal"
|
||||||
|
android:layout_width="200dp"
|
||||||
|
android:layout_height="15dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginBottom="-6.5dp"
|
||||||
|
android:indeterminate="false"
|
||||||
|
android:indeterminateTint="?attr/colorPrimary"
|
||||||
|
android:progressBackgroundTint="?attr/colorPrimary"
|
||||||
|
android:progressTint="?attr/colorPrimary"
|
||||||
|
tools:progress="20" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="5dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="30dp"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:contentDescription="@string/go_back_img_des"
|
||||||
|
android:src="@drawable/ic_baseline_arrow_back_24"
|
||||||
|
app:tint="@android:color/white" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/player_loading_go_back2"
|
||||||
|
android:layout_width="70dp"
|
||||||
|
android:layout_height="70dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="@drawable/video_tap_button_always_white"
|
||||||
|
android:clickable="true"
|
||||||
|
android:contentDescription="@string/go_back_img_des"
|
||||||
|
android:focusable="true" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/player_loading_overlay"
|
android:id="@+id/player_loading_overlay"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
@ -38,7 +110,8 @@
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:visibility="gone">
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/overlay_loading_skip_button"
|
android:id="@+id/overlay_loading_skip_button"
|
||||||
|
|
@ -93,39 +166,39 @@
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<!-- <FrameLayout
|
<!-- <FrameLayout
|
||||||
android:id="@+id/player_torrent_info"
|
android:id="@+id/player_torrent_info"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingStart="20dp"
|
android:paddingStart="20dp"
|
||||||
android:paddingEnd="20dp"
|
android:paddingEnd="20dp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/video_torrent_progress"
|
android:id="@+id/video_torrent_progress"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="15dp"
|
android:layout_marginTop="15dp"
|
||||||
android:gravity="start"
|
android:gravity="start"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:text="78% at 18kb/s" />
|
tools:text="78% at 18kb/s" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/video_torrent_seeders"
|
android:id="@+id/video_torrent_seeders"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="0dp"
|
android:layout_marginTop="0dp"
|
||||||
android:gravity="start"
|
android:gravity="start"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/player_video_title"
|
app:layout_constraintTop_toBottomOf="@+id/player_video_title"
|
||||||
tools:text="17 seeders" />
|
tools:text="17 seeders" />
|
||||||
</FrameLayout>-->
|
</FrameLayout>-->
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue