diff --git a/src/pt/vizer/AndroidManifest.xml b/src/pt/vizer/AndroidManifest.xml
new file mode 100644
index 000000000..16564b0c3
--- /dev/null
+++ b/src/pt/vizer/AndroidManifest.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pt/vizer/build.gradle b/src/pt/vizer/build.gradle
new file mode 100644
index 000000000..658831f55
--- /dev/null
+++ b/src/pt/vizer/build.gradle
@@ -0,0 +1,20 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlinx-serialization'
+
+ext {
+ extName = 'Vizer.tv'
+ pkgNameSuffix = 'pt.vizer'
+ extClass = '.Vizer'
+ extVersionCode = 1
+ libVersion = '13'
+ containsNsfw = true
+}
+
+dependencies {
+ implementation(project(':lib-fembed-extractor'))
+ implementation(project(':lib-streamtape-extractor'))
+ implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
+}
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/pt/vizer/res/mipmap-hdpi/ic_launcher.png b/src/pt/vizer/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..ae34c91d8
Binary files /dev/null and b/src/pt/vizer/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/pt/vizer/res/mipmap-mdpi/ic_launcher.png b/src/pt/vizer/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..b78a281b4
Binary files /dev/null and b/src/pt/vizer/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/pt/vizer/res/mipmap-xhdpi/ic_launcher.png b/src/pt/vizer/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..e07ab4a7a
Binary files /dev/null and b/src/pt/vizer/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/pt/vizer/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/vizer/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..a5654df64
Binary files /dev/null and b/src/pt/vizer/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/pt/vizer/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/vizer/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..b904558c7
Binary files /dev/null and b/src/pt/vizer/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/pt/vizer/src/eu/kanade/tachiyomi/animeextension/pt/vizer/Vizer.kt b/src/pt/vizer/src/eu/kanade/tachiyomi/animeextension/pt/vizer/Vizer.kt
new file mode 100644
index 000000000..c1d72fbde
--- /dev/null
+++ b/src/pt/vizer/src/eu/kanade/tachiyomi/animeextension/pt/vizer/Vizer.kt
@@ -0,0 +1,311 @@
+package eu.kanade.tachiyomi.animeextension.pt.vizer
+
+import android.app.Application
+import android.content.SharedPreferences
+import androidx.preference.ListPreference
+import androidx.preference.PreferenceScreen
+import eu.kanade.tachiyomi.animeextension.pt.vizer.dto.EpisodeListDto
+import eu.kanade.tachiyomi.animeextension.pt.vizer.dto.PlayersDto
+import eu.kanade.tachiyomi.animeextension.pt.vizer.dto.SearchItemDto
+import eu.kanade.tachiyomi.animeextension.pt.vizer.dto.SearchResultDto
+import eu.kanade.tachiyomi.animeextension.pt.vizer.dto.VideoDto
+import eu.kanade.tachiyomi.animeextension.pt.vizer.dto.VideoLanguagesDto
+import eu.kanade.tachiyomi.animeextension.pt.vizer.extractors.MixDropExtractor
+import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
+import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
+import eu.kanade.tachiyomi.animesource.model.AnimesPage
+import eu.kanade.tachiyomi.animesource.model.SAnime
+import eu.kanade.tachiyomi.animesource.model.SEpisode
+import eu.kanade.tachiyomi.animesource.model.Video
+import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
+import eu.kanade.tachiyomi.lib.fembedextractor.FembedExtractor
+import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.POST
+import eu.kanade.tachiyomi.network.asObservableSuccess
+import eu.kanade.tachiyomi.util.asJsoup
+import kotlinx.serialization.decodeFromString
+import kotlinx.serialization.json.Json
+import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
+import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.RequestBody.Companion.toRequestBody
+import okhttp3.Response
+import org.jsoup.nodes.Element
+import rx.Observable
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+
+class Vizer : ConfigurableAnimeSource, AnimeHttpSource() {
+
+ override val name = "Vizer.tv"
+
+ override val baseUrl = "https://vizer.tv"
+ private val API_URL = "$baseUrl/includes/ajax"
+
+ override val lang = "pt-BR"
+
+ override val supportsLatest = true
+
+ override val client: OkHttpClient = network.cloudflareClient
+
+ private val json = Json {
+ ignoreUnknownKeys = true
+ }
+
+ private val preferences: SharedPreferences by lazy {
+ Injekt.get().getSharedPreferences("source_$id", 0x0000)
+ }
+
+ // ============================== Popular ===============================
+
+ override fun popularAnimeRequest(page: Int): Request {
+ val initialUrl = "$API_URL/ajaxPagination.php?categoryFilterOrderBy=vzViews&page=$page&categoryFilterOrderWay=desc&categoryFilterYearMin=1950&categoryFilterYearMax=2022"
+ val pageType = preferences.getString(PREF_POPULAR_PAGE_KEY, "movie")!!
+ val finalUrl = if ("movie" in pageType) {
+ initialUrl + "&saga=0&categoriesListMovies=all"
+ } else {
+ (initialUrl + "&categoriesListSeries=all").let {
+ if ("anime" in pageType) it + "&anime=1"
+ else it + "&anime=0"
+ }
+ }
+ return GET(finalUrl)
+ }
+
+ override fun popularAnimeParse(response: Response): AnimesPage {
+ val result = response.parseAs()
+ val animes = result.list.map(::animeFromObject).toList()
+ val hasNext = result.quantity == 35
+ return AnimesPage(animes, hasNext)
+ }
+
+ private fun animeFromObject(item: SearchItemDto): SAnime =
+ SAnime.create().apply {
+ var slug = if (item.status.isBlank()) "filme" else "serie"
+ url = "/$slug/online/${item.url}"
+ slug = if (slug == "filme") "movies" else "series"
+ title = item.title
+ status = when (item.status) {
+ "Retornando" -> SAnime.ONGOING
+ else -> SAnime.COMPLETED
+ }
+ thumbnail_url = "$baseUrl/content/$slug/posterPt/342/${item.id}.webp"
+ }
+
+ // ============================== Episodes ==============================
+
+ private fun getSeasonEps(seasonElement: Element): List {
+ val id = seasonElement.attr("data-season-id")
+ val sname = seasonElement.text()
+ val response = client.newCall(apiRequest("getEpisodes=$id")).execute()
+ val episodes = response.parseAs().episodes.mapNotNull {
+ if (it.released)
+ SEpisode.create().apply {
+ name = "Temp $sname: Ep ${it.name} - ${it.title}"
+ episode_number = it.name.toFloatOrNull() ?: 0F
+ url = it.id
+ }
+ else null
+ }
+ return episodes
+ }
+
+ override fun episodeListParse(response: Response): List {
+ val doc = response.asJsoup()
+ val seasons = doc.select("div#seasonsList div.item[data-season-id]")
+ return if (seasons.size > 0) {
+ seasons.flatMap(::getSeasonEps).reversed()
+ } else {
+ listOf(
+ SEpisode.create().apply {
+ name = "Filme"
+ episode_number = 1F
+ url = response.request.url.toString()
+ }
+ )
+ }
+ }
+
+ // ============================ Video Links =============================
+
+ override fun videoListRequest(episode: SEpisode): Request {
+ val url = episode.url
+ return if (url.startsWith("https")) {
+ // Its an real url, maybe from a movie
+ GET(url, headers)
+ } else {
+ // Fake url, its an ID that will be used to get episode languages
+ // (sub/dub) and then return the video link
+ apiRequest("getEpisodeLanguages=$url")
+ }
+ }
+
+ override fun videoListParse(response: Response): List