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.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
|
||||||
|
@ -1121,7 +1125,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
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,26 @@ 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",
|
||||||
|
url = "",
|
||||||
|
"",
|
||||||
|
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) {
|
||||||
|
|
|
@ -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,
|
/** 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,6 +51,8 @@ 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
|
||||||
|
@ -63,6 +67,15 @@ 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.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.io.File
|
||||||
import java.lang.IllegalArgumentException
|
import java.lang.IllegalArgumentException
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
@ -88,7 +101,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,6 +197,273 @@ 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,
|
||||||
|
) {
|
||||||
|
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(
|
override fun loadPlayer(
|
||||||
context: Context,
|
context: Context,
|
||||||
sameEpisode: Boolean,
|
sameEpisode: Boolean,
|
||||||
|
@ -470,6 +752,7 @@ class CS3IPlayer : IPlayer {
|
||||||
currentTextRenderer = null
|
currentTextRenderer = null
|
||||||
|
|
||||||
exoPlayer = null
|
exoPlayer = null
|
||||||
|
releaseAria2c()
|
||||||
//simpleCache = null
|
//simpleCache = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -871,8 +1154,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 ->
|
||||||
|
@ -1249,7 +1544,7 @@ 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
|
||||||
|
|
|
@ -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,52 @@ 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 +930,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)
|
||||||
|
@ -1208,10 +1251,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 +1397,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,
|
||||||
|
|
|
@ -75,6 +75,17 @@ data class PositionEvent(
|
||||||
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,
|
||||||
|
|
|
@ -280,7 +280,7 @@ enum class ExtractorLinkType {
|
||||||
private fun inferTypeFromUrl(url: String): ExtractorLinkType {
|
private fun inferTypeFromUrl(url: String): ExtractorLinkType {
|
||||||
val path = normalSafeApiCall { URL(url).path }
|
val path = normalSafeApiCall { URL(url).path }
|
||||||
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: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
|
<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"
|
||||||
|
|
Loading…
Reference in a new issue