remove old player

This commit is contained in:
jmir1 2022-01-22 02:11:10 +01:00
parent 523d343a1e
commit 28bfb6a3b5
13 changed files with 11 additions and 3332 deletions

View file

@ -297,17 +297,6 @@ dependencies {
// For detecting memory leaks; see https://square.github.io/leakcanary/ // For detecting memory leaks; see https://square.github.io/leakcanary/
// debugImplementation("com.squareup.leakcanary:leakcanary-android:2.7") // debugImplementation("com.squareup.leakcanary:leakcanary-android:2.7")
// Exoplayer
val exoplayerVersion = "2.16.0"
implementation("com.google.android.exoplayer:exoplayer:$exoplayerVersion")
implementation("com.google.android.exoplayer:exoplayer-core:$exoplayerVersion")
implementation("com.google.android.exoplayer:exoplayer-dash:$exoplayerVersion")
implementation("com.google.android.exoplayer:exoplayer-hls:$exoplayerVersion")
implementation("com.google.android.exoplayer:exoplayer-ui:$exoplayerVersion")
// Doubletap Player
implementation("com.github.vkay94:DoubleTapPlayerView:1.0.2")
// FFmpeg // FFmpeg
implementation("com.arthenica:ffmpeg-kit-https:4.5.LTS") implementation("com.arthenica:ffmpeg-kit-https:4.5.LTS")

View file

@ -93,24 +93,6 @@
<meta-data android:name="com.samsung.android.support.REMOTE_ACTION" <meta-data android:name="com.samsung.android.support.REMOTE_ACTION"
android:resource="@xml/s_pen_actions"/> android:resource="@xml/s_pen_actions"/>
</activity> </activity>
<activity
android:name=".ui.player.PlayerActivity"
android:theme="@style/Theme.AppCompat.Light.NoActionBar.FullScreen"
android:supportsPictureInPicture="true"
android:resizeableActivity="true"
android:excludeFromRecents="true"
android:taskAffinity=".ui.player.PlayerActivity"
android:configChanges="keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize"
android:screenOrientation="userLandscape"
android:exported="false">
<intent-filter>
<action android:name="com.samsung.android.support.REMOTE_ACTION" />
</intent-filter>
<meta-data android:name="com.samsung.android.support.REMOTE_ACTION"
android:resource="@xml/s_pen_actions"/>
</activity>
<activity <activity
android:name=".ui.player.NewPlayerActivity" android:name=".ui.player.NewPlayerActivity"
android:launchMode="singleTask" android:launchMode="singleTask"
@ -126,17 +108,6 @@
<meta-data android:name="com.samsung.android.support.REMOTE_ACTION" <meta-data android:name="com.samsung.android.support.REMOTE_ACTION"
android:resource="@xml/s_pen_actions"/> android:resource="@xml/s_pen_actions"/>
</activity> </activity>
<activity
android:name=".ui.player.MPVActivity"
android:launchMode="singleTask"
android:exported="false">
<intent-filter>
<action android:name="com.samsung.android.support.REMOTE_ACTION" />
</intent-filter>
<meta-data android:name="com.samsung.android.support.REMOTE_ACTION"
android:resource="@xml/s_pen_actions"/>
</activity>
<activity <activity
android:name=".ui.security.UnlockActivity" android:name=".ui.security.UnlockActivity"
android:theme="@style/Theme.Tachiyomi" android:theme="@style/Theme.Tachiyomi"

View file

@ -27,7 +27,7 @@ import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.anime.AnimeController import eu.kanade.tachiyomi.ui.anime.AnimeController
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.player.PlayerActivity import eu.kanade.tachiyomi.ui.player.NewPlayerActivity
import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
@ -243,7 +243,7 @@ class NotificationReceiver : BroadcastReceiver() {
val anime = db.getAnime(animeId).executeAsBlocking() val anime = db.getAnime(animeId).executeAsBlocking()
val episode = db.getEpisode(episodeId).executeAsBlocking() val episode = db.getEpisode(episodeId).executeAsBlocking()
if (anime != null && episode != null) { if (anime != null && episode != null) {
val intent = PlayerActivity.newIntent(context, anime, episode).apply { val intent = NewPlayerActivity.newIntent(context, anime, episode).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
} }
context.startActivity(intent) context.startActivity(intent)
@ -609,7 +609,7 @@ class NotificationReceiver : BroadcastReceiver() {
* @param episode episode that needs to be opened * @param episode episode that needs to be opened
*/ */
internal fun openEpisodePendingActivity(context: Context, anime: Anime, episode: Episode): PendingIntent { internal fun openEpisodePendingActivity(context: Context, anime: Anime, episode: Episode): PendingIntent {
val newIntent = PlayerActivity.newIntent(context, anime, episode) val newIntent = NewPlayerActivity.newIntent(context, anime, episode)
return PendingIntent.getActivity(context, AnimeController.REQUEST_INTERNAL, newIntent, PendingIntent.FLAG_UPDATE_CURRENT) return PendingIntent.getActivity(context, AnimeController.REQUEST_INTERNAL, newIntent, PendingIntent.FLAG_UPDATE_CURRENT)
} }

View file

@ -5,7 +5,6 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import com.google.android.exoplayer2.util.MimeTypes
import eu.kanade.tachiyomi.animesource.AnimeSource import eu.kanade.tachiyomi.animesource.AnimeSource
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
@ -99,9 +98,9 @@ class ExternalIntents(val anime: Anime, val source: AnimeSource) {
private fun getMime(uri: Uri): String { private fun getMime(uri: Uri): String {
return when (uri.path?.substringAfterLast(".")) { return when (uri.path?.substringAfterLast(".")) {
"mp4" -> MimeTypes.VIDEO_MP4 "mp4" -> "video/mp4"
"mkv" -> MimeTypes.VIDEO_MATROSKA "mkv" -> "video/x-matroska"
"m3u8" -> MimeTypes.APPLICATION_M3U8 "m3u8" -> "application/x-mpegURL"
else -> "video/any" else -> "video/any"
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -1,190 +0,0 @@
package eu.kanade.tachiyomi.ui.player
import android.os.Bundle
import eu.kanade.tachiyomi.animesource.AnimeSourceManager
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.data.cache.AnimeCoverCache
import eu.kanade.tachiyomi.data.database.AnimeDatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.download.AnimeDownloadManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.job.DelayedTrackingStore
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.util.episode.getEpisodeSort
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.system.logcat
import logcat.LogPriority
import rx.android.schedulers.AndroidSchedulers
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class MPVPresenter(
private val db: AnimeDatabaseHelper = Injekt.get(),
private val sourceManager: AnimeSourceManager = Injekt.get(),
private val downloadManager: AnimeDownloadManager = Injekt.get(),
private val coverCache: AnimeCoverCache = Injekt.get(),
private val preferences: PreferencesHelper = Injekt.get(),
private val delayedTrackingStore: DelayedTrackingStore = Injekt.get(),
) : BasePresenter<MPVActivity>() {
/**
* The anime loaded in the player. It can be null when instantiated for a short time.
*/
var anime: Anime? = null
private set
/**
* The episode id of the currently loaded episode. Used to restore from process kill.
*/
private var episodeId = -1L
private var currentEpisode: Episode? = null
private var currentVideoList: List<Video>? = null
/**
* Episode list for the active anime. It's retrieved lazily and should be accessed for the first
* time in a background thread to avoid blocking the UI.
*/
private val episodeList by lazy {
val anime = anime!!
val dbEpisodes = db.getEpisodes(anime).executeAsBlocking()
val selectedEpisode = dbEpisodes.find { it.id == episodeId }
?: error("Requested episode of id $episodeId not found in episode list")
val episodesForPlayer = when {
preferences.skipRead() || preferences.skipFiltered() -> {
val filteredEpisodes = dbEpisodes.filterNot {
when {
preferences.skipRead() && it.seen -> true
preferences.skipFiltered() -> {
anime.seenFilter == Anime.EPISODE_SHOW_SEEN && !it.seen ||
anime.seenFilter == Anime.EPISODE_SHOW_UNSEEN && it.seen ||
anime.downloadedFilter == Anime.EPISODE_SHOW_DOWNLOADED && !downloadManager.isEpisodeDownloaded(it, anime) ||
anime.downloadedFilter == Anime.EPISODE_SHOW_NOT_DOWNLOADED && downloadManager.isEpisodeDownloaded(it, anime) ||
anime.bookmarkedFilter == Anime.EPISODE_SHOW_BOOKMARKED && !it.bookmark ||
anime.bookmarkedFilter == Anime.EPISODE_SHOW_NOT_BOOKMARKED && it.bookmark
}
else -> false
}
}
if (filteredEpisodes.any { it.id == episodeId }) {
filteredEpisodes
} else {
filteredEpisodes + listOf(selectedEpisode)
}
}
else -> dbEpisodes
}
episodesForPlayer
.sortedWith(getEpisodeSort(anime, sortDescending = false))
}
private var hasTrackers: Boolean = false
private val checkTrackers: (Anime) -> Unit = { anime ->
val tracks = db.getTracks(anime).executeAsBlocking()
hasTrackers = tracks.size > 0
}
private val incognitoMode = preferences.incognitoMode().get()
/**
* Called when the presenter is created. It retrieves the saved active episode if the process
* was restored.
*/
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
if (savedState != null) {
episodeId = savedState.getLong(::episodeId.name, -1)
}
}
/**
* Called when the presenter is destroyed. It saves the current progress and cleans up
* references on the currently active episodes.
*/
override fun onDestroy() {
super.onDestroy()
/*val currentEpisode = currentEpisode
if (currentEpisode != null) {
saveEpisodeProgress(currentEpisode)
saveEpisodeHistory(currentEpisode)
}*/
}
/**
* Called when the presenter instance is being saved. It saves the currently active episode
* id and the last page seen.
*/
override fun onSave(state: Bundle) {
super.onSave(state)
val currentEpisode = currentEpisode
if (currentEpisode != null) {
// saveEpisodeProgress(currentEpisode)
// saveEpisodeHistory(currentEpisode)
state.putLong(::episodeId.name, currentEpisode.id!!)
}
}
/**
* Whether this presenter is initialized yet.
*/
fun needsInit(): Boolean {
return anime == null
}
/**
* Initializes this presenter with the given [animeId] and [initialEpisodeId]. This method will
* fetch the anime from the database and initialize the initial episode.
*/
fun init(animeId: Long, initialEpisodeId: Long) {
if (!needsInit()) return
db.getAnime(animeId).asRxObservable()
.first()
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { init(it, initialEpisodeId) }
.subscribeFirst(
{ _, _ ->
// Ignore onNext event
},
MPVActivity::setInitialEpisodeError
)
}
/**
* Initializes this presenter with the given [anime] and [initialEpisodeId]. This method will
* set the episode loader, view subscriptions and trigger an initial load.
*/
private fun init(anime: Anime, initialEpisodeId: Long) {
if (!needsInit()) return
this.anime = anime
if (episodeId == -1L) episodeId = initialEpisodeId
checkTrackers(anime)
val source = sourceManager.getOrStub(anime.source)
currentEpisode = episodeList.first { initialEpisodeId == it.id }
launchIO {
try {
val currentEpisode = currentEpisode ?: throw Exception("bruh")
EpisodeLoader.getLinks(currentEpisode, anime, source)
.subscribeFirst(
{ activity, it ->
currentVideoList = it
activity.setVideoList(it)
},
MPVActivity::setInitialEpisodeError
)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e) { e.message ?: "error getting links" }
}
}
}
}

View file

@ -35,6 +35,7 @@ import eu.kanade.tachiyomi.util.system.toast
import `is`.xyz.mpv.MPVLib import `is`.xyz.mpv.MPVLib
import `is`.xyz.mpv.PickerDialog import `is`.xyz.mpv.PickerDialog
import `is`.xyz.mpv.SpeedPickerDialog import `is`.xyz.mpv.SpeedPickerDialog
import `is`.xyz.mpv.StateRestoreCallback
import `is`.xyz.mpv.Utils import `is`.xyz.mpv.Utils
import logcat.LogPriority import logcat.LogPriority
import nucleus.factory.RequiresPresenter import nucleus.factory.RequiresPresenter

View file

@ -1,829 +0,0 @@
package eu.kanade.tachiyomi.ui.player
import android.app.PictureInPictureParams
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.Menu
import android.view.View
import android.view.WindowManager
import android.webkit.WebSettings
import android.widget.ImageButton
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.SeekBar
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import com.github.vkay94.dtpv.DoubleTapPlayerView
import com.github.vkay94.dtpv.youtube.YouTubeOverlay
import com.google.android.exoplayer2.DefaultLoadControl
import com.google.android.exoplayer2.DefaultRenderersFactory
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.PlaybackException
import com.google.android.exoplayer2.PlaybackParameters
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.analytics.AnalyticsCollector
import com.google.android.exoplayer2.database.StandaloneDatabaseProvider
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory
import com.google.android.exoplayer2.source.MediaSourceFactory
import com.google.android.exoplayer2.source.TrackGroupArray
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.trackselection.TrackSelectionArray
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter
import com.google.android.exoplayer2.upstream.DefaultDataSource
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
import com.google.android.exoplayer2.upstream.HttpDataSource.HttpDataSourceException
import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException
import com.google.android.exoplayer2.upstream.cache.CacheDataSource
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor
import com.google.android.exoplayer2.upstream.cache.SimpleCache
import com.google.android.exoplayer2.util.Clock
import com.google.android.exoplayer2.util.MimeTypes
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.animesource.AnimeSource
import eu.kanade.tachiyomi.animesource.AnimeSourceManager
import eu.kanade.tachiyomi.animesource.LocalAnimeSource
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.data.database.AnimeDatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.AnimeHistory
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.download.AnimeDownloadManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.job.DelayedTrackingStore
import eu.kanade.tachiyomi.data.track.job.DelayedTrackingUpdateJob
import eu.kanade.tachiyomi.databinding.WatcherActivityBinding
import eu.kanade.tachiyomi.ui.anime.episode.EpisodeItem
import eu.kanade.tachiyomi.ui.base.activity.BaseThemedActivity.Companion.applyAppTheme
import eu.kanade.tachiyomi.util.lang.awaitSingle
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.system.isOnline
import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.widget.listener.SimpleSeekBarListener
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import logcat.LogPriority
import rx.schedulers.Schedulers
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
import java.util.Date
class PlayerActivity : AppCompatActivity() {
private lateinit var binding: WatcherActivityBinding
private val preferences: PreferencesHelper = Injekt.get()
private val incognitoMode = preferences.incognitoMode().get()
private val db: AnimeDatabaseHelper = Injekt.get()
private val downloadManager: AnimeDownloadManager = Injekt.get()
private val delayedTrackingStore: DelayedTrackingStore = Injekt.get()
private lateinit var exoPlayer: ExoPlayer
private lateinit var dataSourceFactory: DataSource.Factory
private lateinit var dbProvider: StandaloneDatabaseProvider
private val cacheSize = 100L * 1024L * 1024L // 100 MB
private var simpleCache: SimpleCache? = null
private lateinit var cacheFactory: CacheDataSource.Factory
private lateinit var mediaSourceFactory: MediaSourceFactory
private lateinit var playerView: DoubleTapPlayerView
private lateinit var youTubeDoubleTap: YouTubeOverlay
private lateinit var skipBtn: TextView
private lateinit var nextBtn: ImageButton
private lateinit var prevBtn: ImageButton
private lateinit var backBtn: TextView
private lateinit var settingsBtn: ImageButton
private lateinit var fitScreenBtn: ImageButton
private lateinit var title: TextView
private lateinit var bufferingView: ProgressBar
private lateinit var episode: Episode
private lateinit var anime: Anime
private lateinit var source: AnimeSource
private lateinit var uri: String
private var videos = emptyList<Video>()
private var isBuffering = true
private var isLocal = false
private var currentQuality = 0
private var duration: Long = 0
private var playbackPosition = 0L
private var isFullscreen = false
private var isPlayerPlaying = true
private var mediaItem = MediaItem.Builder()
.setUri("bruh")
.setMimeType(MimeTypes.VIDEO_MP4)
.build()
private var isInPipMode: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
binding = WatcherActivityBinding.inflate(layoutInflater)
applyAppTheme(preferences)
super.onCreate(savedInstanceState)
setContentView(binding.root)
setVisibilities()
playerView = binding.playerView
playerView.resizeMode = preferences.getPlayerViewMode()
youTubeDoubleTap = binding.youtubeOverlay
youTubeDoubleTap.seekSeconds(preferences.skipLengthPreference())
youTubeDoubleTap
.performListener(object : YouTubeOverlay.PerformListener {
override fun onAnimationStart() {
// Do UI changes when circle scaling animation starts (e.g. hide controller views)
youTubeDoubleTap.visibility = View.VISIBLE
}
override fun onAnimationEnd() {
// Do UI changes when circle scaling animation starts (e.g. show controller views)
youTubeDoubleTap.visibility = View.GONE
}
})
backBtn = findViewById(R.id.exo_overlay_back)
title = findViewById(R.id.exo_overlay_title)
skipBtn = findViewById(R.id.watcher_controls_skip_btn)
nextBtn = findViewById(R.id.watcher_controls_next)
prevBtn = findViewById(R.id.watcher_controls_prev)
settingsBtn = findViewById(R.id.watcher_controls_settings)
fitScreenBtn = findViewById(R.id.watcher_controls_fit_screen)
bufferingView = findViewById(R.id.exo_buffering)
anime = intent.getSerializableExtra("anime") as Anime
episode = intent.getSerializableExtra("episode") as Episode
title.text = baseContext.getString(R.string.playertitle, anime.title, episode.name)
source = Injekt.get<AnimeSourceManager>().getOrStub(anime.source)
initDummyPlayer()
if (savedInstanceState != null) {
playbackPosition = savedInstanceState.getLong(STATE_RESUME_POSITION)
isFullscreen = savedInstanceState.getBoolean(STATE_PLAYER_FULLSCREEN)
isPlayerPlaying = savedInstanceState.getBoolean(STATE_PLAYER_PLAYING)
}
}
@Suppress("DEPRECATION")
private fun setVisibilities() {
// TODO: replace this atrocity
binding.root.systemUiVisibility =
View.SYSTEM_UI_FLAG_LOW_PROFILE or
View.SYSTEM_UI_FLAG_FULLSCREEN or
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
}
}
private fun initDummyPlayer() {
mediaSourceFactory = DefaultMediaSourceFactory(DefaultDataSource.Factory(this))
exoPlayer = newPlayer()
dbProvider = StandaloneDatabaseProvider(baseContext)
val cacheFolder = File(baseContext.filesDir, "media")
simpleCache = if (SimpleCache.isCacheFolderLocked(cacheFolder)) {
null
} else {
SimpleCache(
cacheFolder,
LeastRecentlyUsedCacheEvictor(cacheSize),
dbProvider
)
}
initPlayer()
}
private fun onGetLinksError(e: Throwable? = null) {
launchUI {
baseContext.toast(e?.message ?: "error getting links", Toast.LENGTH_LONG)
finish()
}
}
private fun onGetLinks() {
val context = this
launchUI {
isBuffering(false)
if (videos.isEmpty()) {
onGetLinksError(Exception("Couldn't find any video links."))
return@launchUI
}
dbProvider = StandaloneDatabaseProvider(baseContext)
isLocal = EpisodeLoader.isDownloaded(episode, anime) || source is LocalAnimeSource
if (isLocal) {
uri = videos.firstOrNull()?.videoUrl ?: return@launchUI onGetLinksError(Exception("URI is null."))
dataSourceFactory = DefaultDataSource.Factory(context)
} else {
uri = videos.firstOrNull()?.videoUrl ?: return@launchUI onGetLinksError(Exception("video URL is null."))
dataSourceFactory = newDataSourceFactory()
}
logcat(LogPriority.INFO) { "playing $uri" }
if (simpleCache != null) {
cacheFactory = CacheDataSource.Factory().apply {
setCache(simpleCache!!)
setUpstreamDataSourceFactory(dataSourceFactory)
}
mediaSourceFactory = DefaultMediaSourceFactory(cacheFactory)
} else {
mediaSourceFactory = DefaultMediaSourceFactory(dataSourceFactory)
}
mediaItem = MediaItem.Builder()
.setUri(uri)
.setMimeType(getMime(uri))
.build()
playbackPosition = episode.last_second_seen
changePlayer(playbackPosition, isLocal)
}
}
private fun newDataSourceFactory(): DefaultHttpDataSource.Factory {
val defaultUserAgentString = WebSettings.getDefaultUserAgent(baseContext)
return DefaultHttpDataSource.Factory().apply {
val currentHeaders = videos.getOrNull(currentQuality)?.headers
val headers = currentHeaders?.toMultimap()
?.mapValues { it.value.getOrNull(0) ?: "" }
?.toMutableMap()
?: (source as AnimeHttpSource).headers.toMultimap()
.mapValues { it.value.getOrNull(0) ?: "" }
.toMutableMap()
setDefaultRequestProperties(headers)
setUserAgent(headers["user-agent"] ?: defaultUserAgentString)
}
}
private fun getMime(uri: String): String {
return when (Uri.parse(uri).path?.substringAfterLast(".")) {
"mp4" -> MimeTypes.VIDEO_MP4
"mkv" -> MimeTypes.VIDEO_MATROSKA
"m3u8" -> MimeTypes.APPLICATION_M3U8
else -> MimeTypes.VIDEO_MP4
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
return super.onCreateOptionsMenu(menu)
}
private fun initPlayer() {
exoPlayer = newPlayer().apply {
playWhenReady = isPlayerPlaying
prepare()
}
exoPlayer.addListener(PlayerEventListener(playerView, baseContext))
backBtn.setOnClickListener {
onBackPressed()
}
fitScreenBtn.setOnClickListener {
onClickFitScreen()
}
settingsBtn.setOnClickListener {
optionsDialog()
}
skipBtn.setOnClickListener {
exoPlayer.seekTo(exoPlayer.currentPosition + 85000)
}
nextBtn.setOnClickListener {
nextEpisode()
}
prevBtn.setOnClickListener {
previousEpisode()
}
youTubeDoubleTap.player(exoPlayer)
playerView.player = exoPlayer
duration = exoPlayer.duration
awaitVideoList()
}
private fun isBuffering(param: Boolean) {
isBuffering = param
if (param) {
bufferingView.visibility = View.VISIBLE
} else {
bufferingView.visibility = View.GONE
}
}
private fun onClickFitScreen() {
playerView.resizeMode = when (playerView.resizeMode) {
AspectRatioFrameLayout.RESIZE_MODE_FILL -> AspectRatioFrameLayout.RESIZE_MODE_FIT
AspectRatioFrameLayout.RESIZE_MODE_FIT -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM
AspectRatioFrameLayout.RESIZE_MODE_ZOOM -> AspectRatioFrameLayout.RESIZE_MODE_FILL
else -> AspectRatioFrameLayout.RESIZE_MODE_FIT
}
preferences.setPlayerViewMode(playerView.resizeMode)
}
private inner class HideBarsMaterialAlertDialogBuilder(context: Context) : MaterialAlertDialogBuilder(context) {
override fun create(): AlertDialog {
return super.create().apply {
val window = this.window ?: return@apply
val alertWindowInsetsController = WindowInsetsControllerCompat(window, window.decorView)
alertWindowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
alertWindowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
}
}
private fun optionsDialog() {
val alert = HideBarsMaterialAlertDialogBuilder(this)
alert.setTitle(R.string.playback_options_title)
alert.setItems(R.array.playback_options) { dialog, which ->
when (which) {
0 -> {
val speedAlert = HideBarsMaterialAlertDialogBuilder(this)
val linear = LinearLayout(this)
val items = arrayOf(
"10%", "25%", "40%", "50%", "60%", "70%", "75%", "80%",
"85%", "90%", "95%", "100% (Normal)", "105%", "110%", "115%", "120%", "125%",
"130%", "140%", "150%", "175%", "200%", "250%", "300%"
)
val floatItems = arrayOf(
0.1F, 0.25F, 0.4F, 0.5F, 0.6F, 0.7F, 0.75F, 0.8F, 0.85F, 0.9F,
0.95F, 1F, 1.05F, 1.1F, 1.15F, 1.2F, 1.25F, 1.3F, 1.4F, 1.5F, 1.75F, 2F, 2.5F, 3F
)
var newSpeed = preferences.getPlayerSpeed()
speedAlert.setTitle(R.string.playback_speed_dialog_title)
linear.orientation = LinearLayout.VERTICAL
val text = TextView(this)
text.text = items[floatItems.indexOf(newSpeed)]
text.setPadding(30, 10, 10, 10)
val seek = SeekBar(this).apply {
max = items.lastIndex
progress = floatItems.indexOf(newSpeed)
}
seek.setOnSeekBarChangeListener(object : SimpleSeekBarListener() {
override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
text.text = items[value]
newSpeed = floatItems[value]
}
})
linear.addView(seek)
linear.addView(text)
speedAlert.setView(linear)
speedAlert.setPositiveButton(android.R.string.ok) { speedDialog, _ ->
exoPlayer.playbackParameters = PlaybackParameters(newSpeed)
preferences.setPlayerSpeed(newSpeed)
speedDialog.dismiss()
}
speedAlert.setNegativeButton(android.R.string.cancel) { speedDialog, _ ->
speedDialog.cancel()
}
speedAlert.setNeutralButton(R.string.playback_speed_dialog_reset) { _, _ ->
newSpeed = 1F
val newProgress = floatItems.indexOf(newSpeed)
text.text = items[newProgress]
seek.progress = newProgress
exoPlayer.playbackParameters = PlaybackParameters(newSpeed)
preferences.setPlayerSpeed(newSpeed)
}
speedAlert.show()
}
1 -> {
if (videos.isNotEmpty()) {
val qualityAlert = HideBarsMaterialAlertDialogBuilder(this)
qualityAlert.setTitle(R.string.playback_quality_dialog_title)
var requestedQuality = 0
val qualities = videos.map { it.quality }.toTypedArray()
qualityAlert.setSingleChoiceItems(qualities, currentQuality) { qualityDialog, selectedQuality ->
if (selectedQuality > qualities.lastIndex) {
qualityDialog.cancel()
} else {
requestedQuality = selectedQuality
}
}
qualityAlert.setPositiveButton(android.R.string.ok) { qualityDialog, _ ->
if (requestedQuality != currentQuality) changeQuality(requestedQuality)
qualityDialog.dismiss()
}
qualityAlert.setNegativeButton(android.R.string.cancel) { qualityDialog, _ ->
qualityDialog.cancel()
}
qualityAlert.show()
}
}
else -> {
dialog.cancel()
}
}
}
alert.show()
}
private fun releasePlayer() {
youTubeDoubleTap.player(exoPlayer)
isPlayerPlaying = exoPlayer.playWhenReady
playbackPosition = exoPlayer.currentPosition
exoPlayer.release()
}
private fun awaitVideoList() {
isBuffering(true)
launchIO {
try {
EpisodeLoader.getLinks(episode, anime, source)
.doOnNext {
videos = it
onGetLinks()
}.awaitSingle()
} catch (e: Exception) {
logcat(LogPriority.ERROR, e) { e.message ?: "error getting links" }
onGetLinksError(e)
}
}
}
private fun nextEpisode() {
if (exoPlayer.isPlaying) exoPlayer.pause()
saveEpisodeHistory(EpisodeItem(episode, anime))
setEpisodeProgress(episode, anime, exoPlayer.currentPosition, exoPlayer.duration)
val oldEpisode = episode
episode = getNextEpisode(episode, anime)
if (oldEpisode == episode) return
title.text = baseContext.getString(R.string.playertitle, anime.title, episode.name)
currentQuality = 0
awaitVideoList()
}
private fun previousEpisode() {
if (exoPlayer.isPlaying) exoPlayer.pause()
saveEpisodeHistory(EpisodeItem(episode, anime))
setEpisodeProgress(episode, anime, exoPlayer.currentPosition, exoPlayer.duration)
val oldEpisode = episode
episode = getPreviousEpisode(episode, anime)
if (oldEpisode == episode) return
title.text = baseContext.getString(R.string.playertitle, anime.title, episode.name)
currentQuality = 0
awaitVideoList()
}
private fun changeQuality(quality: Int) {
baseContext.toast(videos.getOrNull(quality)?.quality, Toast.LENGTH_SHORT)
uri = if (isLocal) {
videos.getOrNull(quality)?.videoUrl ?: return
} else {
videos.getOrNull(quality)?.videoUrl ?: return
}
currentQuality = quality
mediaItem = MediaItem.Builder()
.setUri(uri)
.setMimeType(getMime(uri))
.build()
changePlayer(exoPlayer.currentPosition, isLocal)
}
private fun changePlayer(resumeAt: Long, isLocal: Boolean) {
dataSourceFactory = if (isLocal) {
DefaultDataSource.Factory(this)
} else {
newDataSourceFactory()
}
if (simpleCache != null) {
cacheFactory = CacheDataSource.Factory().apply {
setCache(simpleCache!!)
setUpstreamDataSourceFactory(dataSourceFactory)
}
mediaSourceFactory = DefaultMediaSourceFactory(cacheFactory)
} else {
mediaSourceFactory = DefaultMediaSourceFactory(dataSourceFactory)
}
exoPlayer.release()
exoPlayer = newPlayer()
exoPlayer.setMediaSource(mediaSourceFactory.createMediaSource(mediaItem), resumeAt)
exoPlayer.prepare()
exoPlayer.playWhenReady = true
exoPlayer.addListener(PlayerEventListener(playerView, baseContext))
youTubeDoubleTap.player(exoPlayer)
playerView.player = exoPlayer
}
private fun newPlayer(): ExoPlayer {
return ExoPlayer.Builder(
this,
DefaultRenderersFactory(this),
mediaSourceFactory,
DefaultTrackSelector(this),
DefaultLoadControl(),
DefaultBandwidthMeter.Builder(this).build(),
AnalyticsCollector(Clock.DEFAULT)
).setSeekForwardIncrementMs(85000L)
.build().apply {
playbackParameters = PlaybackParameters(preferences.getPlayerSpeed())
}
}
class PlayerEventListener(private val playerView: DoubleTapPlayerView, val baseContext: Context) : Player.Listener {
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
playerView.keepScreenOn = !(
playbackState == Player.STATE_IDLE || playbackState == Player.STATE_ENDED ||
!playWhenReady
)
}
override fun onTracksChanged(trackGroups: TrackGroupArray, trackSelections: TrackSelectionArray) {}
override fun onLoadingChanged(isLoading: Boolean) {}
override fun onRepeatModeChanged(repeatMode: Int) {}
override fun onPlayerError(error: PlaybackException) {
val cause: Throwable? = error.cause
if (cause is HttpDataSourceException) {
// An HTTP error occurred.
// This is the request for which the error occurred.
val requestDataSpec = cause.dataSpec
for (header in requestDataSpec.httpRequestHeaders) {
var message = ""
message += header.key + " - " + header.value
}
// It's possible to find out more about the error both by casting and by
// querying the cause.
if (cause is InvalidResponseCodeException) {
// Cast to InvalidResponseCodeException and retrieve the response code,
// message and headers.
val errorMessage =
"Error " + cause.responseCode.toString() + ": " + cause.message
baseContext.toast(errorMessage, Toast.LENGTH_SHORT)
} else {
cause.cause
// Try calling httpError.getCause() to retrieve the underlying cause,
// although note that it may be null.
}
}
}
override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters) {}
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putLong(STATE_RESUME_POSITION, exoPlayer.currentPosition)
outState.putBoolean(STATE_PLAYER_FULLSCREEN, isFullscreen)
outState.putBoolean(STATE_PLAYER_PLAYING, isPlayerPlaying)
super.onSaveInstanceState(outState)
}
override fun onStart() {
playerView.onResume()
super.onStart()
}
override fun onStop() {
saveEpisodeHistory(EpisodeItem(episode, anime))
setEpisodeProgress(episode, anime, exoPlayer.currentPosition, exoPlayer.duration)
if (exoPlayer.isPlaying) exoPlayer.pause()
if (isInPipMode) finish()
playerView.onPause()
super.onStop()
}
override fun onDestroy() {
deletePendingEpisodes()
releasePlayer()
simpleCache?.release()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
finishAndRemoveTask()
}
super.onDestroy()
}
override fun onUserLeaveHint() {
if (exoPlayer.isPlaying) {
startPiP()
}
super.onUserLeaveHint()
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
val oldAnime = anime
anime = intent?.getSerializableExtra("anime") as? Anime ?: return
val oldEpisode = episode
episode = intent.getSerializableExtra("episode") as Episode
if (oldEpisode == episode) return
saveEpisodeHistory(EpisodeItem(oldEpisode, oldAnime))
setEpisodeProgress(oldEpisode, oldAnime, exoPlayer.currentPosition, exoPlayer.duration)
title.text = baseContext.getString(R.string.playertitle, anime.title, episode.name)
source = Injekt.get<AnimeSourceManager>().getOrStub(anime.source)
awaitVideoList()
}
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration?) {
isInPipMode = isInPictureInPictureMode
playerView.useController = !isInPictureInPictureMode
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
}
@Suppress("DEPRECATION")
private fun startPiP() {
if (!preferences.pipPlayerPreference()) return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
playerView.useController = false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
this.enterPictureInPictureMode(PictureInPictureParams.Builder().build())
} else {
this.enterPictureInPictureMode()
}
}
}
private fun saveEpisodeHistory(episode: EpisodeItem) {
if (!incognitoMode && !isBuffering) {
val history = AnimeHistory.create(episode.episode).apply { last_seen = Date().time }
db.updateAnimeHistoryLastSeen(history).asRxCompletable()
.onErrorComplete()
.subscribeOn(Schedulers.io())
.subscribe()
}
}
private fun setEpisodeProgress(episode: Episode, anime: Anime, seconds: Long, totalSeconds: Long) {
if (!incognitoMode && !isBuffering) {
if (totalSeconds > 0L) {
episode.last_second_seen = seconds
episode.total_seconds = totalSeconds
val progress = preferences.progressPreference()
if (!episode.seen) episode.seen = episode.last_second_seen >= episode.total_seconds * progress
val episodes = listOf(EpisodeItem(episode, anime))
launchIO {
db.updateEpisodesProgress(episodes).executeAsBlocking()
if (preferences.autoUpdateTrack() && episode.seen) {
updateTrackEpisodeSeen(episode)
}
if (episode.seen) {
deleteEpisodeIfNeeded(episode)
deleteEpisodeFromDownloadQueue(episode)
}
}
}
}
}
private fun deleteEpisodeFromDownloadQueue(episode: Episode) {
downloadManager.getEpisodeDownloadOrNull(episode)?.let { download ->
downloadManager.deletePendingDownload(download)
}
}
private fun enqueueDeleteSeenEpisodes(episode: Episode) {
if (!episode.seen) return
val anime = anime
launchIO {
downloadManager.enqueueDeleteEpisodes(listOf(episode), anime)
}
}
private fun deleteEpisodeIfNeeded(episode: Episode) {
// Determine which chapter should be deleted and enqueue
val sortFunction: (Episode, Episode) -> Int = when (anime.sorting) {
Anime.EPISODE_SORTING_SOURCE -> { c1, c2 -> c2.source_order.compareTo(c1.source_order) }
Anime.EPISODE_SORTING_NUMBER -> { c1, c2 -> c1.episode_number.compareTo(c2.episode_number) }
Anime.EPISODE_SORTING_UPLOAD_DATE -> { c1, c2 -> c1.date_upload.compareTo(c2.date_upload) }
else -> throw NotImplementedError("Unknown sorting method")
}
val episodes = db.getEpisodes(anime).executeAsBlocking()
.sortedWith { e1, e2 -> sortFunction(e1, e2) }
val currentEpisodePosition = episodes.indexOf(episode)
val removeAfterReadSlots = preferences.removeAfterReadSlots()
val episodeToDelete = episodes.getOrNull(currentEpisodePosition - removeAfterReadSlots)
// Check if deleting option is enabled and chapter exists
if (removeAfterReadSlots != -1 && episodeToDelete != null) {
enqueueDeleteSeenEpisodes(episodeToDelete)
}
}
/**
* Deletes all the pending episodes. This operation will run in a background thread and errors
* are ignored.
*/
private fun deletePendingEpisodes() {
launchIO {
downloadManager.deletePendingEpisodes()
}
}
private fun updateTrackEpisodeSeen(episode: Episode) {
val episodeSeen = episode.episode_number
val trackManager = Injekt.get<TrackManager>()
launchIO {
db.getTracks(anime).executeAsBlocking()
.mapNotNull { track ->
val service = trackManager.getService(track.sync_id)
if (service != null && service.isLogged && episodeSeen > track.last_episode_seen) {
track.last_episode_seen = episodeSeen
// We want these to execute even if the presenter is destroyed and leaks
// for a while. The view can still be garbage collected.
async {
runCatching {
if (baseContext.isOnline()) {
service.update(track, true)
db.insertTrack(track).executeAsBlocking()
} else {
delayedTrackingStore.addItem(track)
DelayedTrackingUpdateJob.setupTask(baseContext)
}
}
}
} else {
null
}
}
.awaitAll()
.mapNotNull { it.exceptionOrNull() }
.forEach { logcat(LogPriority.WARN, it) }
}
}
private fun getNextEpisode(episode: Episode, anime: Anime): Episode {
val sortFunction: (Episode, Episode) -> Int = when (anime.sorting) {
Anime.EPISODE_SORTING_SOURCE -> { c1, c2 -> c2.source_order.compareTo(c1.source_order) }
Anime.EPISODE_SORTING_NUMBER -> { c1, c2 -> c1.episode_number.compareTo(c2.episode_number) }
Anime.EPISODE_SORTING_UPLOAD_DATE -> { c1, c2 -> c1.date_upload.compareTo(c2.date_upload) }
else -> throw NotImplementedError("Unknown sorting method")
}
val episodes = db.getEpisodes(anime).executeAsBlocking()
.sortedWith { e1, e2 -> sortFunction(e1, e2) }
val currEpisodeIndex = episodes.indexOfFirst { episode.id == it.id }
val episodeNumber = episode.episode_number
return (currEpisodeIndex + 1 until episodes.size)
.map { episodes[it] }
.firstOrNull {
it.episode_number > episodeNumber &&
it.episode_number <= episodeNumber + 1
} ?: episode
}
private fun getPreviousEpisode(episode: Episode, anime: Anime): Episode {
val sortFunction: (Episode, Episode) -> Int = when (anime.sorting) {
Anime.EPISODE_SORTING_SOURCE -> { c1, c2 -> c2.source_order.compareTo(c1.source_order) }
Anime.EPISODE_SORTING_NUMBER -> { c1, c2 -> c1.episode_number.compareTo(c2.episode_number) }
Anime.EPISODE_SORTING_UPLOAD_DATE -> { c1, c2 -> c1.date_upload.compareTo(c2.date_upload) }
else -> throw NotImplementedError("Unknown sorting method")
}
val episodes = db.getEpisodes(anime).executeAsBlocking()
.sortedWith { e1, e2 -> sortFunction(e2, e1) }
val currEpisodeIndex = episodes.indexOfFirst { episode.id == it.id }
val episodeNumber = episode.episode_number
return (currEpisodeIndex + 1 until episodes.size)
.map { episodes[it] }
.firstOrNull {
it.episode_number < episodeNumber &&
it.episode_number >= episodeNumber - 1
} ?: episode
}
companion object {
fun newIntent(context: Context, anime: Anime, episode: Episode): Intent {
return Intent(context, PlayerActivity::class.java).apply {
putExtra("anime", anime)
putExtra("episode", episode)
putExtra("second", episode.last_second_seen)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
}
}
}
}
private const val STATE_RESUME_POSITION = "resumePosition"
private const val STATE_PLAYER_FULLSCREEN = "playerFullscreen"
private const val STATE_PLAYER_PLAYING = "playerOnPlay"

View file

@ -25,7 +25,7 @@ import eu.kanade.tachiyomi.ui.base.controller.RootController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.browse.animesource.browse.ProgressItem import eu.kanade.tachiyomi.ui.browse.animesource.browse.ProgressItem
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.player.PlayerActivity import eu.kanade.tachiyomi.ui.player.NewPlayerActivity
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.onAnimationsFinished import eu.kanade.tachiyomi.util.view.onAnimationsFinished
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
@ -159,7 +159,7 @@ class AnimeHistoryController :
val nextEpisode = presenter.getNextEpisode(chapter, anime) val nextEpisode = presenter.getNextEpisode(chapter, anime)
if (nextEpisode != null) { if (nextEpisode != null) {
val newIntent = PlayerActivity.newIntent(activity, anime, nextEpisode) val newIntent = NewPlayerActivity.newIntent(activity, anime, nextEpisode)
startActivity(newIntent) startActivity(newIntent)
} else { } else {
activity.toast(R.string.no_next_episode) activity.toast(R.string.no_next_episode)

View file

@ -23,7 +23,7 @@ import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.RootController import eu.kanade.tachiyomi.ui.base.controller.RootController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.player.PlayerActivity import eu.kanade.tachiyomi.ui.player.NewPlayerActivity
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
@ -193,7 +193,7 @@ class AnimeUpdatesController :
*/ */
private fun openEpisode(item: AnimeUpdatesItem) { private fun openEpisode(item: AnimeUpdatesItem) {
val activity = activity ?: return val activity = activity ?: return
val intent = PlayerActivity.newIntent(activity, item.anime, item.episode) val intent = NewPlayerActivity.newIntent(activity, item.anime, item.episode)
startActivity(intent) startActivity(intent)
} }

View file

@ -1,273 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<is.xyz.mpv.MPVView
android:id="@+id/player"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<LinearLayout
android:id="@+id/controls"
style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginLeft="24dp"
android:layout_marginTop="60dp"
android:layout_marginRight="24dp"
android:layout_marginBottom="60dp"
android:background="#bf000000"
android:orientation="vertical">
<LinearLayout
android:id="@+id/controls_title_group"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone">
<!-- These two are only used for audio -->
<TextView
android:id="@+id/titleTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="-"
android:textColor="@color/tint_normal"
android:textSize="24sp"
android:visibility="gone" />
<TextView
android:id="@+id/minorTitleTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="-"
android:textColor="@color/tint_normal"
android:textSize="14sp"
android:visibility="gone" />
<!-- This one for video title display -->
<TextView
android:id="@+id/fullTitleTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="middle"
android:gravity="center"
android:singleLine="true"
android:text="-"
android:textColor="@color/tint_normal"
android:textSize="16sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/controls_seekbar_group"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:weightSum="100">
<ImageButton
android:id="@+id/prevBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#00000000"
android:onClick="playlistPrev"
android:src="@drawable/ic_skip_previous_black_24dp"
app:tint="@color/tint_normal" />
<TextView
android:id="@+id/playbackPositionTxt"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="10"
android:gravity="center"
android:textColor="@android:color/white" />
<SeekBar
android:id="@+id/playbackSeekbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="80"
android:progressBackgroundTint="@color/tint_seekbar_bg" />
<TextView
android:id="@+id/playbackDurationTxt"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="10"
android:gravity="center"
android:textColor="@android:color/white" />
<ImageButton
android:id="@+id/nextBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#00000000"
android:onClick="playlistNext"
android:src="@drawable/ic_skip_next_black_24dp"
app:tint="@color/tint_normal" />
</LinearLayout>
<LinearLayout
android:id="@+id/controls_button_group"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="horizontal">
<ImageButton
android:id="@+id/playBtn"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="playPause"
android:text="play"
android:textColor="@android:color/white"
app:tint="@color/tint_normal" />
<ImageButton
android:id="@+id/cycleAudioBtn"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="cycleAudio"
android:src="@drawable/ic_audiotrack_black_24dp"
android:text="..."
android:textColor="@android:color/white"
app:tint="@color/tint_normal" />
<ImageButton
android:id="@+id/cycleSubsBtn"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="cycleSub"
android:src="@drawable/ic_subtitles_black_24dp"
android:text="..."
android:textColor="@android:color/white"
app:tint="@color/tint_normal" />
<Button
android:id="@+id/cycleDecoderBtn"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="switchDecoder"
android:text=".."
android:textColor="@android:color/white" />
<Button
android:id="@+id/cycleSpeedBtn"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="cycleSpeed"
android:text=".."
android:textColor="@android:color/white" />
</LinearLayout>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="stats"
android:id="@+id/statsTextView"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true"
android:textColor="#ffffff"
android:shadowColor="#000000"
android:shadowDx="1"
android:shadowDy="1"
android:shadowRadius="1"
android:visibility="gone" />
<TextView
android:id="@+id/gestureTextView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:shadowColor="#000000"
android:shadowDx="0"
android:shadowDy="0"
android:shadowRadius="4"
android:text="[gesture]"
android:textAlignment="center"
android:textColor="#ffffff"
android:textSize="36sp"
android:textStyle="bold"
android:visibility="gone" />
<LinearLayout
android:id="@+id/top_controls"
style="?android:attr/buttonBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:alpha="0.5"
android:background="#bf000000"
android:orientation="horizontal">
<ImageButton
android:id="@+id/topLockBtn"
style="?android:attr/buttonBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="40dp"
android:minHeight="40dp"
android:onClick="lockUI"
android:padding="4dp"
android:src="@drawable/ic_lock_24dp"
app:tint="@color/tint_normal" />
<ImageButton
android:id="@+id/topPiPBtn"
style="?android:attr/buttonBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="40dp"
android:minHeight="40dp"
android:onClick="goIntoPiP"
android:padding="4dp"
android:src="@drawable/ic_picture_in_picture_24dp"
app:tint="@color/tint_normal" />
<ImageButton
android:id="@+id/topMenuBtn"
style="?android:attr/buttonBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="40dp"
android:minHeight="40dp"
android:onClick="openTopMenu"
android:padding="4dp"
android:src="@drawable/ic_settings_black_24dp"
app:tint="@color/tint_normal" />
</LinearLayout>
<!-- mismatching width/height so that the button appears exactly square-->
<ImageButton
android:id="@+id/unlockBtn"
style="@style/Widget.AppCompat.ImageButton"
android:layout_width="48dp"
android:layout_height="52dp"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_margin="24dp"
android:backgroundTint="@color/background_material_dark"
android:onClick="unlockUI"
android:src="@drawable/ic_lock_open_24dp"
app:tint="@android:color/white"
android:visibility="gone" />
</RelativeLayout>

View file

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:background="#000000">
<com.github.vkay94.dtpv.DoubleTapPlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:controller_layout_id="@layout/watcher_controls_view"
app:dtpv_controller="@id/youtube_overlay"
app:show_buffering="when_playing"
tools:alpha="0.1"/>
<com.github.vkay94.dtpv.youtube.YouTubeOverlay
android:id="@+id/youtube_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible"
app:yt_playerView="@+id/player_view" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -1,136 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/RelativeLayout01"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layoutDirection="ltr"
android:layout_alignParentTop="true"
android:background="#70000000"
android:orientation="horizontal">
<TextView
android:id="@+id/exo_overlay_back"
android:layout_width="wrap_content"
android:layout_height="@dimen/exo_media_button_height"
android:layout_gravity="start"
android:gravity="center"
android:paddingHorizontal="20dp"
android:text="@string/player_overlay_back"
android:textColor="@color/md_white_1000"
android:textStyle="bold"
style="@style/ExoMediaButton"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingTop="4dp"
android:orientation="horizontal">
<TextView
android:id="@+id/exo_overlay_title"
android:textColor="@color/md_white_1000"
android:textStyle="bold"
android:gravity="center"
android:layout_height="@dimen/exo_media_button_height"
android:layout_width="wrap_content"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layoutDirection="ltr"
android:layout_alignParentBottom="true"
android:background="#70000000"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingTop="4dp"
android:orientation="horizontal">
<ImageButton android:id="@+id/watcher_controls_fit_screen"
style="@style/ExoStyledControls.Button.Bottom.FullScreen"
android:contentDescription="@string/action_screen_fit" />
<ImageButton android:id="@+id/watcher_controls_prev"
style="@style/ExoMediaButton.Previous"
android:contentDescription="@string/action_previous_episode" />
<ImageButton android:id="@id/exo_play"
style="@style/ExoMediaButton.Play"
android:contentDescription="@string/action_play" />
<ImageButton android:id="@id/exo_pause"
style="@style/ExoMediaButton.Pause"
android:contentDescription="@string/action_pause" />
<ImageButton android:id="@+id/watcher_controls_next"
style="@style/ExoMediaButton.Next"
android:contentDescription="@string/action_next_episode" />
<TextView
android:id="@+id/watcher_controls_skip_btn"
android:text="@string/watcher_controls_skip_text"
android:textColor="@color/md_white_1000"
android:textStyle="bold"
android:gravity="center"
style="@style/ExoMediaButton" />
<ImageButton android:id="@+id/watcher_controls_settings"
style="@style/ExoStyledControls.Button.Bottom.Settings"
android:contentDescription="@string/action_next_episode" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView android:id="@id/exo_position"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textStyle="bold"
android:paddingStart="15dp"
android:paddingEnd="4dp"
android:includeFontPadding="false"
android:textColor="@color/md_white_1000"/>
<com.google.android.exoplayer2.ui.DefaultTimeBar
android:id="@id/exo_progress"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="26dp"/>
<TextView android:id="@id/exo_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textStyle="bold"
android:paddingStart="4dp"
android:paddingEnd="15dp"
android:includeFontPadding="false"
android:textColor="@color/md_white_1000"/>
</LinearLayout>
</LinearLayout></RelativeLayout>