WatchCartoonOnlineProvider

This commit is contained in:
LagradOst 2021-08-14 19:31:27 +02:00
parent 2839d59a28
commit a0ad101605
18 changed files with 354 additions and 41 deletions

View File

@ -94,4 +94,7 @@ dependencies {
// subtitle color picker
implementation 'com.jaredrummler:colorpicker:1.1.0'
//run JS
implementation 'org.mozilla:rhino:1.7R4'
}

View File

@ -6,17 +6,18 @@ import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.lagradost.cloudstream3.animeproviders.DubbedAnimeProvider
import com.lagradost.cloudstream3.animeproviders.ShiroProvider
import com.lagradost.cloudstream3.animeproviders.TenshiProvider
import com.lagradost.cloudstream3.animeproviders.WatchCartoonOnlineProvider
import com.lagradost.cloudstream3.animeproviders.WcoProvider
import com.lagradost.cloudstream3.movieproviders.HDMProvider
import com.lagradost.cloudstream3.movieproviders.MeloMovieProvider
import com.lagradost.cloudstream3.movieproviders.TrailersToProvider
import com.lagradost.cloudstream3.movieproviders.VMoveeProvider
import com.lagradost.cloudstream3.utils.ExtractorLink
import java.util.*
const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; rv:68.0) Gecko/20100101 Firefox/68.0"
const val USER_AGENT =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
val baseHeader = mapOf("User-Agent" to USER_AGENT)
val mapper = JsonMapper.builder().addModule(KotlinModule())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()!!
@ -37,6 +38,7 @@ object APIHolder {
HDMProvider(),
//LookMovieProvider(), // RECAPTCHA (Please allow up to 5 seconds...)
VMoveeProvider(),
WatchCartoonOnlineProvider(),
)
fun getApiFromName(apiName: String?): MainAPI {
@ -86,6 +88,14 @@ abstract class MainAPI {
open val hasMainPage = false
open val hasQuickSearch = false
open val supportedTypes = setOf(
TvType.Movie,
TvType.TvSeries,
TvType.Cartoon,
TvType.Anime,
TvType.ONA
)
open fun getMainPage(): HomePageResponse? {
return null
}
@ -177,6 +187,7 @@ enum class DubStatus {
enum class TvType {
Movie,
TvSeries,
Cartoon,
Anime,
ONA,
}
@ -255,7 +266,7 @@ interface LoadResponse {
val year: Int?
val plot: String?
val rating: Int? // 0-100
val tags: ArrayList<String>?
val tags: List<String>?
val duration: String?
val trailerUrl: String?
}
@ -290,13 +301,13 @@ data class AnimeLoadResponse(
override val posterUrl: String?,
override val year: Int?,
val dubEpisodes: ArrayList<AnimeEpisode>?,
val subEpisodes: ArrayList<AnimeEpisode>?,
val dubEpisodes: List<AnimeEpisode>?,
val subEpisodes: List<AnimeEpisode>?,
val showStatus: ShowStatus?,
override val plot: String?,
override val tags: ArrayList<String>? = null,
val synonyms: ArrayList<String>? = null,
override val tags: List<String>? = null,
val synonyms: List<String>? = null,
val malId: Int? = null,
val anilistId: Int? = null,
@ -318,7 +329,7 @@ data class MovieLoadResponse(
val imdbId: String?,
override val rating: Int? = null,
override val tags: ArrayList<String>? = null,
override val tags: List<String>? = null,
override val duration: String? = null,
override val trailerUrl: String? = null,
) : LoadResponse
@ -339,7 +350,7 @@ data class TvSeriesLoadResponse(
override val url: String,
override val apiName: String,
override val type: TvType,
val episodes: ArrayList<TvSeriesEpisode>,
val episodes: List<TvSeriesEpisode>,
override val posterUrl: String?,
override val year: Int?,
@ -348,7 +359,7 @@ data class TvSeriesLoadResponse(
val showStatus: ShowStatus?,
val imdbId: String?,
override val rating: Int? = null,
override val tags: ArrayList<String>? = null,
override val tags: List<String>? = null,
override val duration: String? = null,
override val trailerUrl: String? = null,
) : LoadResponse

View File

@ -18,6 +18,12 @@ class DubbedAnimeProvider : MainAPI() {
override val hasQuickSearch: Boolean
get() = true
override val supportedTypes: Set<TvType>
get() = setOf(
TvType.Movie,
TvType.Anime,
)
data class QueryEpisodeResultRoot(
@JsonProperty("result")
val result: QueryEpisodeResult,

View File

@ -160,7 +160,7 @@ class ShiroProvider : MainAPI() {
val set: EnumSet<DubStatus> =
EnumSet.of(if (isDubbed) DubStatus.Dubbed else DubStatus.Subbed)
val episodeCount = data.episodeCount?.toIntOrNull()
val episodeCount = data.episodeCount.toIntOrNull()
return@map AnimeSearchResponse(
data.name.replace("Dubbed", ""), // i.english ?: i.canonicalTitle,

View File

@ -29,6 +29,9 @@ class TenshiProvider : MainAPI() {
override val hasQuickSearch: Boolean
get() = false
override val supportedTypes: Set<TvType>
get() = setOf(TvType.Anime, TvType.Movie, TvType.ONA)
private fun autoLoadToken(): Boolean {
if (token != null) return true
return loadToken()
@ -91,7 +94,11 @@ class TenshiProvider : MainAPI() {
private fun dateParser(dateString: String): String? {
val format = SimpleDateFormat("dd 'of' MMM',' yyyy")
val newFormat = SimpleDateFormat("dd-MM-yyyy")
return newFormat.format(format.parse(dateString.replace("th ", " ").replace("st ", " ").replace("nd ", " ").replace("rd ", " ")))
return newFormat.format(
format.parse(
dateString.replace("th ", " ").replace("st ", " ").replace("nd ", " ").replace("rd ", " ")
)
)
}
// data class TenshiSearchResponse(
@ -147,14 +154,14 @@ class TenshiProvider : MainAPI() {
override fun search(query: String): ArrayList<SearchResponse> {
val url = "$mainUrl/anime"
var response = khttp.get(url, params=mapOf("q" to query), cookies=mapOf("loop-view" to "thumb"))
var response = khttp.get(url, params = mapOf("q" to query), cookies = mapOf("loop-view" to "thumb"))
var document = Jsoup.parse(response.text)
val returnValue = parseSearchPage(document)
while (!document.select("""a.page-link[rel="next"]""").isEmpty()) {
val link = document.select("""a.page-link[rel="next"]""")
if (link != null && !link.isEmpty()) {
response = khttp.get(link[0].attr("href"), cookies=mapOf("loop-view" to "thumb"))
response = khttp.get(link[0].attr("href"), cookies = mapOf("loop-view" to "thumb"))
document = Jsoup.parse(response.text)
returnValue.addAll(parseSearchPage(document))
} else {
@ -166,7 +173,7 @@ class TenshiProvider : MainAPI() {
}
override fun load(url: String): LoadResponse {
val response = khttp.get(url, timeout = 120.0, cookies=mapOf("loop-view" to "thumb"))
val response = khttp.get(url, timeout = 120.0, cookies = mapOf("loop-view" to "thumb"))
val document = Jsoup.parse(response.text)
val englishTitle = document.selectFirst("span.value > span[title=\"English\"]")?.parent()?.text()?.trim()
@ -177,13 +184,14 @@ class TenshiProvider : MainAPI() {
val episodes = ArrayList<AnimeEpisode>(episodeNodes?.map {
AnimeEpisode(
it.attr("href"),
it.selectFirst(".episode-title")?.text()?.trim(),
it.selectFirst("img")?.attr("src"),
dateParser(it.selectFirst(".episode-date").text().trim()).toString(),
null,
it.attr("data-content").trim(),
) }
it.attr("href"),
it.selectFirst(".episode-title")?.text()?.trim(),
it.selectFirst("img")?.attr("src"),
dateParser(it.selectFirst(".episode-date").text().trim()).toString(),
null,
it.attr("data-content").trim(),
)
}
?: ArrayList<AnimeEpisode>())
val status = when (document.selectFirst("li.status > .value")?.text()?.trim()) {
"Ongoing" -> ShowStatus.Ongoing
@ -200,7 +208,8 @@ class TenshiProvider : MainAPI() {
val synopsis = document.selectFirst(".entry-description > .card-body")?.text()?.trim()
val genre = document.select("li.genre.meta-data > span.value").map { it?.text()?.trim().toString() }
val synonyms = document.select("li.synonym.meta-data > div.info-box > span.value").map { it?.text()?.trim().toString() }
val synonyms =
document.select("li.synonym.meta-data > div.info-box > span.value").map { it?.text()?.trim().toString() }
return AnimeLoadResponse(
englishTitle,
@ -231,7 +240,7 @@ class TenshiProvider : MainAPI() {
): Boolean {
val response = khttp.get(data)
val src = Jsoup.parse(response.text).selectFirst(".embed-responsive > iframe").attr("src")
val mp4moe = Jsoup.parse(khttp.get(src, headers=mapOf("Referer" to data)).text).selectFirst("video#player")
val mp4moe = Jsoup.parse(khttp.get(src, headers = mapOf("Referer" to data)).text).selectFirst("video#player")
val sources = mp4moe.select("source").map {
ExtractorLink(

View File

@ -0,0 +1,224 @@
package com.lagradost.cloudstream3.animeproviders
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.module.kotlin.readValue
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities
import org.jsoup.Jsoup
import org.mozilla.javascript.Context
import org.mozilla.javascript.Scriptable
import java.util.*
class WatchCartoonOnlineProvider : MainAPI() {
override val name: String
get() = "WatchCartoonOnline"
override val mainUrl: String
get() = "https://www.wcostream.com"
override val supportedTypes: Set<TvType>
get() = setOf(
TvType.Cartoon,
TvType.Anime,
)
override fun search(query: String): ArrayList<SearchResponse>? {
val url = "https://www.wcostream.com/search"
val response =
khttp.post(
url,
headers = mapOf("Referer" to url),
data = mapOf("catara" to query, "konuara" to "series")
)
val document = Jsoup.parse(response.text)
val items = document.select("div#blog > div.cerceve")
if (items.isNullOrEmpty()) return ArrayList()
val returnValue = ArrayList<SearchResponse>()
for (item in items) {
val header = item.selectFirst("> div.iccerceve")
val titleHeader = header.selectFirst("> div.aramadabaslik > a")
val title = titleHeader.text()
val href = fixUrl(titleHeader.attr("href"))
val poster = fixUrl(header.selectFirst("> a > img").attr("src"))
val genreText = item.selectFirst("div.cerceve-tur-ve-genre").ownText()
if (genreText.contains("cartoon")) {
returnValue.add(TvSeriesSearchResponse(title, href, this.name, TvType.Cartoon, poster, null, null))
} else {
val isDubbed = genreText.contains("dubbed")
val set: EnumSet<DubStatus> =
EnumSet.of(if (isDubbed) DubStatus.Dubbed else DubStatus.Subbed)
returnValue.add(
AnimeSearchResponse(
title,
href,
this.name,
TvType.Anime,
poster,
null,
null,
set,
null,
null
)
)
}
}
return returnValue
}
override fun load(url: String): LoadResponse? {
val response = khttp.get(url)
val document = Jsoup.parse(response.text)
val title = document.selectFirst("td.vsbaslik > h2").text()
val poster = fixUrl(document.selectFirst("div#cat-img-desc > div > img").attr("src"))
val plot = document.selectFirst("div.iltext").text()
val genres = document.select("div#cat-genre > div.wcobtn > a").map { it.text() }
val episodes = document.select("div#catlist-listview > ul > li > a").reversed().map {
val text = it.text()
val match = Regex("Season ([0-9]*) Episode ([0-9]*).*? (.*)").find(text)
val href = it.attr("href")
if (match != null) {
val last = match.groupValues[3]
return@map TvSeriesEpisode(
if (last.startsWith("English")) null else last,
match.groupValues[1].toIntOrNull(),
match.groupValues[2].toIntOrNull(),
href
)
}
val match2 = Regex("Episode ([0-9]*).*? (.*)").find(text)
if (match2 != null) {
val last = match2.groupValues[2]
return@map TvSeriesEpisode(
if (last.startsWith("English")) null else last,
null,
match2.groupValues[1].toIntOrNull(),
href
)
}
return@map TvSeriesEpisode(
text,
null,
null,
href
)
}
return TvSeriesLoadResponse(
title,
url,
this.name,
TvType.TvSeries,
episodes,
poster,
null,
plot,
null,
null,
tags = genres
)
}
data class LinkResponse(
// @JsonProperty("cdn")
// val cdn: String,
@JsonProperty("enc")
val enc: String,
@JsonProperty("hd")
val hd: String,
@JsonProperty("server")
val server: String,
)
override fun loadLinks(
data: String,
isCasting: Boolean,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
): Boolean {
val response = khttp.get(data)
/*val embedUrl = fixUrl(
Regex("itemprop=\"embedURL\" content=\"(.*?)\"").find(response.text)?.groupValues?.get(1) ?: return false
)*/
val text = response.text
val start = text.indexOf("itemprop=\"embedURL")
val foundJS = Regex("<script>(.*?)</script>").find(text, start)?.groupValues?.get(1)
?.replace("document.write", "var returnValue = ")
println("JS: $foundJS")
val rhino = Context.enter()
rhino.initStandardObjects()
rhino.optimizationLevel = -1
val scope: Scriptable = rhino.initStandardObjects()
val decodeBase64 = "atob = function(s) {\n" +
" var e={},i,b=0,c,x,l=0,a,r='',w=String.fromCharCode,L=s.length;\n" +
" var A=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n" +
" for(i=0;i<64;i++){e[A.charAt(i)]=i;}\n" +
" for(x=0;x<L;x++){\n" +
" c=e[s.charAt(x)];b=(b<<6)+c;l+=6;\n" +
" while(l>=8){((a=(b>>>(l-=8))&0xff)||(x<(L-2)))&&(r+=w(a));}\n" +
" }\n" +
" return r;\n" +
"};"
rhino.evaluateString(scope, decodeBase64 + foundJS, "JavaScript", 1, null)
val jsEval = scope.get("returnValue", scope) ?: return false
val src = fixUrl(Regex("src=\"(.*?)\"").find(jsEval as String)?.groupValues?.get(1) ?: return false)
val embedResponse = khttp.get(
(src),
headers = mapOf("Referer" to data)
)
val getVidLink = fixUrl(
Regex("get\\(\"(.*?)\"").find(embedResponse.text)?.groupValues?.get(1) ?: return false
)
val linkResponse = khttp.get(
getVidLink, headers = mapOf(
"sec-ch-ua" to "\"Chromium\";v=\"91\", \" Not;A Brand\";v=\"99\"",
"sec-ch-ua-mobile" to "?0",
"sec-fetch-dest" to "empty",
"sec-fetch-mode" to "cors",
"sec-fetch-site" to "same-origin",
"accept" to "*/*",
"x-requested-with" to "XMLHttpRequest",
"referer" to src.replace(" ", "%20"),
"user-agent" to "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
"cookie" to "countrytabs=0"
)
)
println("LINK:" + linkResponse.text)
val link = mapper.readValue<LinkResponse>(linkResponse.text)
val hdLink = "${link.server}/getvid?evid=${link.hd}"
val sdLink = "${link.server}/getvid?evid=${link.enc}"
if (link.hd.isNotBlank())
callback.invoke(
ExtractorLink(
this.name,
this.name + " HD",
hdLink,
"",
Qualities.HD.value
)
)
if (link.enc.isNotBlank())
callback.invoke(
ExtractorLink(
this.name,
this.name + " SD",
sdLink,
"",
Qualities.SD.value
)
)
return true
}
}

View File

@ -27,6 +27,13 @@ class WcoProvider : MainAPI() {
override val hasMainPage: Boolean
get() = true
override val supportedTypes: Set<TvType>
get() = setOf(
TvType.Movie,
TvType.Anime,
TvType.ONA
)
override fun getMainPage(): HomePageResponse? {
val urls = listOf(
Pair("$mainUrl/ajax/list/recently_updated?type=tv", "Recently Updated Anime"),

View File

@ -13,6 +13,11 @@ class HDMProvider : MainAPI() {
override val hasDownloadSupport: Boolean
get() = false
override val supportedTypes: Set<TvType>
get() = setOf(
TvType.Movie,
)
override fun search(query: String): ArrayList<SearchResponse> {
val url = "$mainUrl/search/$query"
val response = khttp.get(url)

View File

@ -20,6 +20,12 @@ class LookMovieProvider : MainAPI() {
override val mainUrl: String
get() = "https://lookmovie.io"
override val supportedTypes: Set<TvType>
get() = setOf(
TvType.Movie,
TvType.TvSeries,
)
data class LookMovieSearchResult(
@JsonProperty("backdrop") val backdrop: String?,
@JsonProperty("imdb_rating") val imdb_rating: String,

View File

@ -23,6 +23,12 @@ class TrailersToProvider : MainAPI() {
override val hasChromecastSupport: Boolean
get() = false
override val supportedTypes: Set<TvType>
get() = setOf(
TvType.Movie,
TvType.TvSeries,
)
override fun getMainPage(): HomePageResponse? {
val response = khttp.get(mainUrl)
val document = Jsoup.parse(response.text)

View File

@ -14,6 +14,11 @@ class VMoveeProvider : MainAPI() {
override val mainUrl: String
get() = "https://www.vmovee.watch"
override val supportedTypes: Set<TvType>
get() = setOf(
TvType.Movie,
)
override fun search(query: String): ArrayList<SearchResponse>? {
val url = "$mainUrl/?s=$query"
val response = khttp.get(url)

View File

@ -61,6 +61,7 @@ class HomeChildItemAdapter(
TvType.Movie -> "Movie"
TvType.ONA -> "ONA"
TvType.TvSeries -> "TV"
TvType.Cartoon -> "Cartoon"
}
// search_result_lang?.visibility = View.GONE

View File

@ -796,7 +796,7 @@ class PlayerFragment : Fragment() {
}
//endregion
private fun onSubStyleChanged(style : SaveCaptionStyle) {
private fun onSubStyleChanged(style: SaveCaptionStyle) {
context?.let { ctx ->
subStyle = style
subView?.setStyle(ctx.fromSaveToStyle(style))
@ -1131,7 +1131,7 @@ class PlayerFragment : Fragment() {
val speedsNumbers = listOf(0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f)
val speedIndex = speedsNumbers.indexOf(playbackSpeed)
context?.showDialog(speedsText,speedIndex,"Player Speed", false, {
context?.showDialog(speedsText, speedIndex, "Player Speed", false, {
activity?.hideSystemUI()
}) { index ->
playbackSpeed = speedsNumbers[index]
@ -1541,8 +1541,19 @@ class PlayerFragment : Fragment() {
/*FastAniApi.currentHeaders?.forEach {
dataSource.setRequestProperty(it.key, it.value)
}*/
if (currentUrl != null)
if (currentUrl != null) {
dataSource.setRequestProperty("Referer", currentUrl.referer)
// extra stuff
dataSource.setRequestProperty(
"sec-ch-ua",
"\"Chromium\";v=\"91\", \" Not;A Brand\";v=\"99\""
)
dataSource.setRequestProperty("sec-ch-ua-mobile", "?0")
// dataSource.setRequestProperty("Sec-Fetch-Site", "none") //same-site
dataSource.setRequestProperty("Sec-Fetch-User", "?1")
dataSource.setRequestProperty("Sec-Fetch-Mode", "navigate")
dataSource.setRequestProperty("Sec-Fetch-Dest", "document")
}
dataSource
} else {
DefaultDataSourceFactory(requireContext(), USER_AGENT).createDataSource()

View File

@ -238,14 +238,14 @@ class ResultFragment : Fragment() {
var startAction: Int? = null
private fun lateFixDownloadButton(show: Boolean) {
if (show) {
result_movie_parent.visibility = VISIBLE
result_episodes_text.visibility = GONE
result_episodes.visibility = GONE
} else {
if(!show || currentType?.isMovieType() == true) {
result_movie_parent.visibility = GONE
result_episodes_text.visibility = VISIBLE
result_episodes.visibility = VISIBLE
} else {
result_movie_parent.visibility = VISIBLE
result_episodes_text.visibility = GONE
result_episodes.visibility = GONE
}
}
@ -430,6 +430,7 @@ class ResultFragment : Fragment() {
TvType.Movie -> "Movies"
TvType.TvSeries -> "TVSeries/$titleName"
TvType.ONA -> "ONA"
TvType.Cartoon -> "Cartoons/$titleName"
else -> null
}

View File

@ -119,7 +119,6 @@ class ResultViewModel : ViewModel() {
rangeList.add("${i + 1}-${currentList.size}")
}
}
_rangeOptions.postValue(rangeList)
val cRange = range ?: if (selection != null) {
0
@ -133,14 +132,19 @@ class ResultViewModel : ViewModel() {
cRange
}
selectedRangeInt.postValue(realRange)
selectedRange.postValue(rangeList[realRange])
if (currentList.size > EPISODE_RANGE_OVERLOAD) {
currentList = currentList.subList(
realRange * EPISODE_RANGE_SIZE,
minOf(currentList.size, (realRange + 1) * EPISODE_RANGE_SIZE)
)
_rangeOptions.postValue(rangeList)
selectedRangeInt.postValue(realRange)
selectedRange.postValue(rangeList[realRange])
} else {
val allRange ="1-${currentList.size}"
_rangeOptions.postValue(listOf(allRange))
selectedRangeInt.postValue(0)
selectedRange.postValue(allRange)
}
_publicEpisodes.postValue(currentList)

View File

@ -50,7 +50,6 @@ class SearchAdapter(
is CardViewHolder -> {
holder.bind(cardList[position])
}
}
}
@ -94,6 +93,7 @@ class SearchAdapter(
TvType.Movie -> "Movie"
TvType.ONA -> "ONA"
TvType.TvSeries -> "TV"
TvType.Cartoon -> "Cartoon"
}
// search_result_lang?.visibility = View.GONE

View File

@ -47,7 +47,7 @@ object VideoDownloadManager {
private var currentDownloads = mutableListOf<Int>()
private const val USER_AGENT =
"Mozilla/5.0 (Windows NT 10.0; rv:68.0) Gecko/20100101 Firefox/68.0"
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
@DrawableRes
const val imgDone = R.drawable.rddone
@ -511,6 +511,18 @@ object VideoDownloadManager {
connection.setRequestProperty("Accept-Encoding", "identity")
connection.setRequestProperty("User-Agent", USER_AGENT)
if (link.referer.isNotEmpty()) connection.setRequestProperty("Referer", link.referer)
// extra stuff
connection.setRequestProperty(
"sec-ch-ua",
"\"Chromium\";v=\"91\", \" Not;A Brand\";v=\"99\""
)
connection.setRequestProperty("sec-ch-ua-mobile", "?0")
// dataSource.setRequestProperty("Sec-Fetch-Site", "none") //same-site
connection.setRequestProperty("Sec-Fetch-User", "?1")
connection.setRequestProperty("Sec-Fetch-Mode", "navigate")
connection.setRequestProperty("Sec-Fetch-Dest", "document")
if (resume)
connection.setRequestProperty("Range", "bytes=${fileLength}-")
val resumeLength = (if (resume) fileLength else 0)

View File

@ -11,6 +11,9 @@
android:screenOrientation="userLandscape"
app:surface_type="texture_view"
>
<!--
app:fastforward_increment="10000"
app:rewind_increment="10000"-->
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/player_view"
app:show_timeout="0"
@ -18,8 +21,7 @@
app:auto_show="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:fastforward_increment="10000"
app:rewind_increment="10000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"