mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
torrent testing
This commit is contained in:
parent
2bed79b1f1
commit
d394f0e1d0
8 changed files with 510 additions and 54 deletions
|
@ -94,6 +94,7 @@ import com.lagradost.cloudstream3.ui.WatchType
|
|||
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO
|
||||
import com.lagradost.cloudstream3.ui.home.HomeViewModel
|
||||
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.LinkGenerator
|
||||
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.DataStoreHelper.migrateResumeWatching
|
||||
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.InAppUpdater.Companion.runAutoUpdate
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.changeStatusBarState
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.checkWrite
|
||||
|
@ -1112,16 +1116,17 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
newLocalBinding.root.viewTreeObserver.addOnGlobalFocusChangeListener { _, newFocus ->
|
||||
// println("refocus $oldFocus -> $newFocus")
|
||||
try {
|
||||
val r = Rect(0,0,0,0)
|
||||
val r = Rect(0, 0, 0, 0)
|
||||
newFocus.getDrawingRect(r)
|
||||
val x = r.centerX()
|
||||
val y = r.centerY()
|
||||
val dx = 0 //screenWidth / 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)
|
||||
// TvFocus.current =TvFocus.current.copy(y=y.toFloat())
|
||||
} catch (_ : Throwable) { }
|
||||
} catch (_: Throwable) {
|
||||
}
|
||||
TvFocus.updateFocusView(newFocus)
|
||||
/*var focus = newFocus
|
||||
|
||||
|
@ -1562,6 +1567,26 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
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",
|
||||
url = "",
|
||||
"",
|
||||
Qualities.Unknown.value,
|
||||
type = INFER_TYPE
|
||||
)
|
||||
),
|
||||
emptyList()
|
||||
)
|
||||
)
|
||||
)*/
|
||||
|
||||
// Used to check current focus for TV
|
||||
// main {
|
||||
// while (true) {
|
||||
|
|
|
@ -370,12 +370,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,
|
||||
* 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 */
|
||||
open fun mainCallback(event : PlayerEvent) {
|
||||
Log.i(TAG, "Handle event: $event")
|
||||
when(event) {
|
||||
is DownloadEvent -> {
|
||||
onDownload(event)
|
||||
}
|
||||
is ResizedEvent -> {
|
||||
playerDimensionsLoaded(event.width, event.height)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.lagradost.cloudstream3.ui.player
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Handler
|
||||
|
@ -8,6 +9,7 @@ import android.os.Looper
|
|||
import android.util.Log
|
||||
import android.util.Rational
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.net.toUri
|
||||
import androidx.media3.common.C.*
|
||||
import androidx.media3.common.Format
|
||||
import androidx.media3.common.MediaItem
|
||||
|
@ -49,6 +51,8 @@ import androidx.preference.PreferenceManager
|
|||
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
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.app
|
||||
import com.lagradost.cloudstream3.mvvm.debugAssert
|
||||
|
@ -63,6 +67,15 @@ import com.lagradost.cloudstream3.utils.ExtractorLinkPlayList
|
|||
import com.lagradost.cloudstream3.utils.ExtractorLinkType
|
||||
import com.lagradost.cloudstream3.utils.ExtractorUri
|
||||
import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage
|
||||
import com.lagradost.fetchbutton.aria2c.Aria2Settings
|
||||
import com.lagradost.fetchbutton.aria2c.Aria2Starter
|
||||
import com.lagradost.fetchbutton.aria2c.DownloadListener
|
||||
import com.lagradost.fetchbutton.aria2c.DownloadStatusTell
|
||||
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.lang.IllegalArgumentException
|
||||
import java.util.UUID
|
||||
|
@ -88,7 +101,9 @@ class CS3IPlayer : IPlayer {
|
|||
private var exoPlayer: ExoPlayer? = null
|
||||
set(value) {
|
||||
// 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
|
||||
}
|
||||
|
||||
|
@ -182,6 +197,273 @@ class CS3IPlayer : IPlayer {
|
|||
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,
|
||||
) {
|
||||
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 -> {
|
||||
if (metadata.downloadedLength >= metadata.totalLength && getPlayableFile(
|
||||
metadata,
|
||||
minimumBytes = 10 shl 20
|
||||
) != 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 > (10 shl 20)) {
|
||||
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 -> break
|
||||
}
|
||||
}
|
||||
|
||||
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 = 10 shl 20)
|
||||
?: throw Exception("Not downloaded enough")
|
||||
activity.runOnUiThread {
|
||||
//Log.i(TAG, "downloaded data: $metadata")
|
||||
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()
|
||||
|
||||
val uriReq = newUriRequest(
|
||||
id = requestId,
|
||||
uri = link.url,
|
||||
fileName = null,
|
||||
seed = false
|
||||
)
|
||||
|
||||
val metadata =
|
||||
DownloadListener.sessionIdToGid[requestId]?.let { gid -> DownloadListener.getInfo(gid) }
|
||||
|
||||
when (metadata?.status) {
|
||||
DownloadStatusTell.Removed, DownloadStatusTell.Error, null -> {
|
||||
Aria2Starter.download(uriReq)
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
try {
|
||||
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(
|
||||
context: Context,
|
||||
sameEpisode: Boolean,
|
||||
|
@ -470,6 +752,7 @@ class CS3IPlayer : IPlayer {
|
|||
currentTextRenderer = null
|
||||
|
||||
exoPlayer = null
|
||||
releaseAria2c()
|
||||
//simpleCache = null
|
||||
}
|
||||
|
||||
|
@ -871,8 +1154,20 @@ class CS3IPlayer : IPlayer {
|
|||
|
||||
CSPlayerEvent.SeekForward -> seekTime(seekActionTime, source)
|
||||
CSPlayerEvent.SeekBack -> seekTime(-seekActionTime, source)
|
||||
CSPlayerEvent.NextEpisode -> event(EpisodeSeekEvent(offset = 1, source = source))
|
||||
CSPlayerEvent.PrevEpisode -> event(EpisodeSeekEvent(offset = -1, source = source))
|
||||
CSPlayerEvent.NextEpisode -> event(
|
||||
EpisodeSeekEvent(
|
||||
offset = 1,
|
||||
source = source
|
||||
)
|
||||
)
|
||||
|
||||
CSPlayerEvent.PrevEpisode -> event(
|
||||
EpisodeSeekEvent(
|
||||
offset = -1,
|
||||
source = source
|
||||
)
|
||||
)
|
||||
|
||||
CSPlayerEvent.SkipCurrentChapter -> {
|
||||
//val dur = this@CS3IPlayer.getDuration() ?: return@apply
|
||||
getCurrentTimestamp()?.let { lastTimeStamp ->
|
||||
|
@ -1249,7 +1544,7 @@ class CS3IPlayer : IPlayer {
|
|||
}
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
private fun loadOnlinePlayer(context: Context, link: ExtractorLink) {
|
||||
private fun loadOnlinePlayerReal(context: Context, link: ExtractorLink) {
|
||||
Log.i(TAG, "loadOnlinePlayer $link")
|
||||
try {
|
||||
currentLink = link
|
||||
|
|
|
@ -7,6 +7,7 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.os.Bundle
|
||||
import android.text.format.Formatter
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
@ -92,6 +93,52 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
|
||||
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)
|
||||
}
|
||||
|
||||
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() {
|
||||
player.release()
|
||||
currentSelectedSubtitles = null
|
||||
|
@ -883,10 +930,6 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
}
|
||||
|
||||
|
||||
override fun playerError(exception: Throwable) {
|
||||
Log.i(TAG, "playerError = $currentSelectedLink")
|
||||
super.playerError(exception)
|
||||
}
|
||||
|
||||
private fun noLinksFound() {
|
||||
showToast(R.string.no_links_found_toast, Toast.LENGTH_SHORT)
|
||||
|
@ -945,7 +988,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
|
||||
var maxEpisodeSet: Int? = null
|
||||
var hasRequestedStamps: Boolean = false
|
||||
override fun playerPositionChanged(position: Long, duration : Long) {
|
||||
override fun playerPositionChanged(position: Long, duration: Long) {
|
||||
// Don't save livestream data
|
||||
if ((currentMeta as? ResultEpisode)?.tvType?.isLiveStream() == true) return
|
||||
|
||||
|
@ -1208,10 +1251,6 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun playerDimensionsLoaded(width: Int, height : Int) {
|
||||
setPlayerDimen(width to height)
|
||||
}
|
||||
|
||||
private fun unwrapBundle(savedInstanceState: Bundle?) {
|
||||
Log.i(TAG, "unwrapBundle = $savedInstanceState")
|
||||
savedInstanceState?.let { bundle ->
|
||||
|
@ -1358,6 +1397,11 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
activity?.popCurrentPage()
|
||||
}
|
||||
|
||||
binding?.playerLoadingGoBack2?.setOnClickListener {
|
||||
player.release()
|
||||
activity?.popCurrentPage()
|
||||
}
|
||||
|
||||
observe(viewModel.currentStamps) { stamps ->
|
||||
player.addTimeStamps(stamps)
|
||||
}
|
||||
|
|
|
@ -18,7 +18,10 @@ fun LoadType.toSet() : Set<ExtractorLinkType> {
|
|||
LoadType.InApp -> setOf(
|
||||
ExtractorLinkType.VIDEO,
|
||||
ExtractorLinkType.DASH,
|
||||
ExtractorLinkType.M3U8
|
||||
ExtractorLinkType.M3U8,
|
||||
// testing
|
||||
ExtractorLinkType.TORRENT,
|
||||
ExtractorLinkType.MAGNET,
|
||||
)
|
||||
LoadType.Browser -> setOf(
|
||||
ExtractorLinkType.VIDEO,
|
||||
|
|
|
@ -72,9 +72,20 @@ data class PositionEvent(
|
|||
val durationMs: Long,
|
||||
) : PlayerEvent() {
|
||||
/** 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 */
|
||||
data class ErrorEvent(
|
||||
val error: Throwable,
|
||||
|
|
|
@ -280,7 +280,7 @@ enum class ExtractorLinkType {
|
|||
private fun inferTypeFromUrl(url: String): ExtractorLinkType {
|
||||
val path = normalSafeApiCall { URL(url).path }
|
||||
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(".torrent") == true -> ExtractorLinkType.TORRENT
|
||||
url.startsWith("magnet:") -> ExtractorLinkType.MAGNET
|
||||
|
|
|
@ -29,6 +29,78 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:show_timeout="0" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/download_header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/black"
|
||||
android:backgroundTint="@android:color/black">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<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
|
||||
android:id="@+id/player_loading_overlay"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -38,7 +110,8 @@
|
|||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="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
|
||||
android:id="@+id/overlay_loading_skip_button"
|
||||
|
|
Loading…
Reference in a new issue