bruhus momenticus

This commit is contained in:
jmir1 2022-01-12 20:20:05 +01:00
parent 53a298b157
commit 5e1fb417c9
8 changed files with 882 additions and 78 deletions

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="eu.kanade.tachiyomi">
<!-- Internet -->
@ -113,6 +114,9 @@
<activity
android:name=".ui.player.NewPlayerActivity"
android:launchMode="singleTask"
android:configChanges="orientation|screenLayout|screenSize|smallestScreenSize|keyboardHidden|keyboard"
android:supportsPictureInPicture="true"
android:resizeableActivity="true"
android:exported="false">
<intent-filter>
<action android:name="com.samsung.android.support.REMOTE_ACTION" />

View file

@ -7,7 +7,6 @@ import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
@ -84,7 +83,7 @@ import eu.kanade.tachiyomi.ui.browse.migration.search.AnimeSearchController
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.player.EpisodeLoader
import eu.kanade.tachiyomi.ui.player.ExternalIntents
import eu.kanade.tachiyomi.ui.player.MPVActivity
import eu.kanade.tachiyomi.ui.player.NewPlayerActivity
import eu.kanade.tachiyomi.ui.recent.HistoryTabsController
import eu.kanade.tachiyomi.ui.recent.UpdatesTabsController
import eu.kanade.tachiyomi.ui.recent.animehistory.AnimeHistoryController
@ -1050,7 +1049,8 @@ class AnimeController :
private fun openEpisode(episode: Episode, hasAnimation: Boolean = false, playerChangeRequested: Boolean = false) {
val context = view?.context ?: return
val intent = Intent(Intent.ACTION_VIEW).setClass(context, MPVActivity::class.java).setData(Uri.parse("http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4")) // NewPlayerActivity.newIntent(context, presenter.anime, episode)
val anime = anime ?: return
val intent = NewPlayerActivity.newIntent(context, anime, episode) // Intent(Intent.ACTION_VIEW).setClass(context, MPVActivity::class.java).setData(Uri.parse("http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4")) // NewPlayerActivity.newIntent(context, presenter.anime, episode)
val useInternal = preferences.alwaysUseExternalPlayer() == playerChangeRequested
if (hasAnimation) {
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
@ -1058,14 +1058,13 @@ class AnimeController :
if (!useInternal) launchIO {
val video = try {
EpisodeLoader.getLink(episode, anime!!, source!!).awaitSingle()
EpisodeLoader.getLink(episode, anime, source!!).awaitSingle()
} catch (e: Exception) {
return@launchIO makeErrorToast(context, e)
}
if (video != null) {
currentExtEpisode = episode
val anime = anime ?: return@launchIO
val source = source ?: return@launchIO
val extIntent = ExternalIntents(anime, source).getExternalIntent(episode, video, context)
if (extIntent != null) try {

View file

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.player
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.app.PictureInPictureParams
import android.app.RemoteAction
import android.content.Context
@ -17,12 +18,18 @@ import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.ParcelFileDescriptor
import android.preference.PreferenceManager.getDefaultSharedPreferences
import android.util.DisplayMetrics
import android.util.Log
import android.util.Rational
import android.view.*
import android.view.Gravity
import android.view.InputDevice
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import android.view.WindowManager
import android.widget.Button
import android.widget.RelativeLayout
import android.widget.SeekBar
@ -31,9 +38,19 @@ import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.Episode
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.MpvActivityBinding
import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity
import eu.kanade.tachiyomi.ui.base.activity.BaseThemedActivity.Companion.applyAppTheme
import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.system.toast
import `is`.xyz.mpv.BackgroundPlaybackService
import `is`.xyz.mpv.DecimalPickerDialog
import `is`.xyz.mpv.FilePickerActivity
@ -48,7 +65,9 @@ import `is`.xyz.mpv.SpeedPickerDialog
import `is`.xyz.mpv.TouchGestures
import `is`.xyz.mpv.TouchGesturesObserver
import `is`.xyz.mpv.Utils
import `is`.xyz.mpv.databinding.PlayerBinding
import logcat.LogPriority
import nucleus.factory.RequiresPresenter
import uy.kohesive.injekt.injectLazy
import java.io.File
import java.lang.IllegalArgumentException
import kotlin.math.roundToInt
@ -56,9 +75,10 @@ import kotlin.math.roundToInt
typealias ActivityResultCallback = (Int, Intent?) -> Unit
typealias StateRestoreCallback = () -> Unit
class MPVActivity : AppCompatActivity(), MPVLib.EventObserver, TouchGesturesObserver {
private val fadeHandler = Handler()
private val stopServiceHandler = Handler()
@RequiresPresenter(MPVPresenter::class)
class MPVActivity : BaseRxActivity<MpvActivityBinding, MPVPresenter>(), MPVLib.EventObserver, TouchGesturesObserver {
private val fadeHandler = Handler(Looper.getMainLooper())
private val stopServiceHandler = Handler(Looper.getMainLooper())
/**
* DO NOT USE THIS
@ -73,13 +93,16 @@ class MPVActivity : AppCompatActivity(), MPVLib.EventObserver, TouchGesturesObse
private var audioManager: AudioManager? = null
private var audioFocusRestore: () -> Unit = {}
private lateinit var binding: PlayerBinding
private lateinit var toast: Toast
private lateinit var gestures: TouchGestures
// convenience alias
private val player get() = binding.player
private val preferences: PreferencesHelper by injectLazy()
private val windowInsetsController by lazy { WindowInsetsControllerCompat(window, binding.root) }
private val seekBarChangeListener = object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (!fromUser) {
@ -146,7 +169,7 @@ class MPVActivity : AppCompatActivity(), MPVLib.EventObserver, TouchGesturesObse
}
override fun run() {
binding.topControls.animate().alpha(0f).setDuration(CONTROLS_FADE_DURATION)
binding.topControls.animate().alpha(0f).duration = CONTROLS_FADE_DURATION
binding.controls.animate().alpha(0f).setDuration(CONTROLS_FADE_DURATION).setListener(listener)
}
}
@ -165,11 +188,9 @@ class MPVActivity : AppCompatActivity(), MPVLib.EventObserver, TouchGesturesObse
}
// Fade out gesture text
private val fadeRunnable3 = object : Runnable {
private val fadeRunnable3 = Runnable {
// okay this doesn't actually fade...
override fun run() {
binding.gestureTextView.visibility = View.GONE
}
binding.gestureTextView.visibility = View.GONE
}
private val stopServiceRunnable = Runnable {
@ -216,14 +237,17 @@ class MPVActivity : AppCompatActivity(), MPVLib.EventObserver, TouchGesturesObse
// Activity lifetime
override fun onCreate(icicle: Bundle?) {
super.onCreate(icicle)
@TargetApi(Build.VERSION_CODES.P)
@SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
applyAppTheme(preferences)
super.onCreate(savedInstanceState)
// Do these here and not in MainActivity because mpv can be launched from a file browser
Utils.copyAssets(this)
BackgroundPlaybackService.createNotificationChannel(this)
binding = PlayerBinding.inflate(layoutInflater)
binding = MpvActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
// Init controls to be hidden and view fullscreen
@ -259,13 +283,15 @@ class MPVActivity : AppCompatActivity(), MPVLib.EventObserver, TouchGesturesObse
if (filepath == null) {
Log.e(TAG, "No file given, exiting")
showToast(getString(R.string.error_no_file))
finishWithResult(RESULT_CANCELED)
return
// finishWithResult(RESULT_CANCELED)
// return
}
player.initialize(applicationContext.filesDir.path)
player.addObserver(this)
player.playFile(filepath)
if (filepath != null) {
player.playFile(filepath)
}
binding.playbackSeekbar.setOnSeekBarChangeListener(seekBarChangeListener)
@ -285,6 +311,20 @@ class MPVActivity : AppCompatActivity(), MPVLib.EventObserver, TouchGesturesObse
Log.w(TAG, "Audio focus not granted")
onloadCommands.add(arrayOf("set", "pause", "yes"))
}
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
if (presenter?.needsInit() == true) {
val anime = intent.extras!!.getLong("anime", -1)
val episode = intent.extras!!.getLong("episode", -1)
if (anime == -1L || episode == -1L) {
finish()
return
}
presenter.init(anime, episode)
}
}
private fun finishWithResult(code: Int, includeTimePos: Boolean = false) {
@ -335,10 +375,7 @@ class MPVActivity : AppCompatActivity(), MPVLib.EventObserver, TouchGesturesObse
// Happens when mpv is still running (not necessarily playing) and the user selects a new
// file to be played from another app
val filepath = intent?.let { parsePathFromIntent(it) }
if (filepath == null) {
return
}
val filepath = intent?.let { parsePathFromIntent(it) } ?: return
if (!activityIsForeground && didResumeBackgroundPlayback) {
MPVLib.command(arrayOf("loadfile", filepath, "append"))
@ -361,7 +398,7 @@ class MPVActivity : AppCompatActivity(), MPVLib.EventObserver, TouchGesturesObse
if (isFinishing) { // about to exit?
return false
}
if (player.paused ?: true) {
if (player.paused != false) {
return false
}
return when (backgroundPlayMode) {
@ -498,7 +535,7 @@ class MPVActivity : AppCompatActivity(), MPVLib.EventObserver, TouchGesturesObse
if (!shouldSavePosition) {
return
}
if (MPVLib.getPropertyBoolean("eof-reached") ?: true) {
if (MPVLib.getPropertyBoolean("eof-reached") != false) {
Log.d(TAG, "player indicates EOF, not saving watch-later config")
return
}
@ -576,7 +613,7 @@ class MPVActivity : AppCompatActivity(), MPVLib.EventObserver, TouchGesturesObse
}
// TODO: what does this do?
window.decorView.systemUiVisibility = if (useAudioUI) View.SYSTEM_UI_FLAG_LAYOUT_STABLE else 0
// window.decorView.systemUiVisibility = if (useAudioUI) View.SYSTEM_UI_FLAG_LAYOUT_STABLE else 0
}
// add a new callback to hide the controls once again
@ -595,8 +632,8 @@ class MPVActivity : AppCompatActivity(), MPVLib.EventObserver, TouchGesturesObse
binding.topControls.visibility = View.GONE
binding.statsTextView.visibility = View.GONE
val flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE
window.decorView.systemUiVisibility = flags
// val flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE
// window.decorView.systemUiVisibility = flags
}
private fun hideControlsDelayed() {
@ -904,8 +941,7 @@ class MPVActivity : AppCompatActivity(), MPVLib.EventObserver, TouchGesturesObse
// Intent/Uri parsing
private fun parsePathFromIntent(intent: Intent): String? {
val filepath: String?
filepath = when (intent.action) {
val filepath: String? = when (intent.action) {
Intent.ACTION_VIEW -> intent.data?.let { resolveUri(it) }
Intent.ACTION_SEND -> intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
val uri = Uri.parse(it.trim())
@ -913,7 +949,7 @@ class MPVActivity : AppCompatActivity(), MPVLib.EventObserver, TouchGesturesObse
}
else -> intent.getStringExtra("filepath")
}
return filepath
return filepath ?: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
}
private fun resolveUri(data: Uri): String? {
@ -1550,7 +1586,7 @@ class MPVActivity : AppCompatActivity(), MPVLib.EventObserver, TouchGesturesObse
return
}
val intent1 = NotificationButtonReceiver.createIntent(this, "PLAY_PAUSE")
val action1 = if (player.paused ?: true) {
val action1 = if (player.paused != false) {
RemoteAction(
Icon.createWithResource(this, R.drawable.ic_play_arrow_black_24dp),
"Play", "", intent1
@ -1739,8 +1775,31 @@ class MPVActivity : AppCompatActivity(), MPVLib.EventObserver, TouchGesturesObse
}
}
/**
* Called from the presenter if the initial load couldn't load the videos of the episode. In
* this case the activity is closed and a toast is shown to the user.
*/
fun setInitialEpisodeError(error: Throwable) {
logcat(LogPriority.ERROR, error)
finish()
toast(error.message)
}
fun setVideoList(videos: List<Video>) {
logcat(LogPriority.INFO) { "loaded!!" }
videos.first().videoUrl?.let { player.playFile(it) }
}
companion object {
private const val TAG = "mpv"
fun newIntent(context: Context, anime: Anime, episode: Episode): Intent {
return Intent(context, MPVActivity::class.java).apply {
putExtra("anime", anime.id)
putExtra("episode", episode.id)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
}
}
const val TAG = "mpv"
// how long should controls be displayed on screen (ms)
private const val CONTROLS_DISPLAY_TIMEOUT = 1500L

View file

@ -0,0 +1,190 @@
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

@ -2,8 +2,18 @@ package eu.kanade.tachiyomi.ui.player
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.View
import android.view.WindowManager
import android.widget.SeekBar
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.Episode
@ -14,12 +24,14 @@ import eu.kanade.tachiyomi.ui.base.activity.BaseThemedActivity.Companion.applyAp
import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.system.toast
import `is`.xyz.mpv.MPVLib
import `is`.xyz.mpv.Utils
import logcat.LogPriority
import nucleus.factory.RequiresPresenter
import uy.kohesive.injekt.injectLazy
import kotlin.math.abs
@RequiresPresenter(NewPlayerPresenter::class)
class NewPlayerActivity : BaseRxActivity<NewPlayerActivityBinding, NewPlayerPresenter>() {
class NewPlayerActivity : BaseRxActivity<NewPlayerActivityBinding, NewPlayerPresenter>(), MPVLib.EventObserver {
companion object {
fun newIntent(context: Context, anime: Anime, episode: Episode): Intent {
@ -35,8 +47,33 @@ class NewPlayerActivity : BaseRxActivity<NewPlayerActivityBinding, NewPlayerPres
private val player get() = binding.player
var currentVideoList: List<Video>? = null
private var userIsOperatingSeekbar = false
private var lockedUI = false
private val seekBarChangeListener = object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (!fromUser) {
return
}
player.timePos = progress
updatePlaybackPos(progress)
}
override fun onStartTrackingTouch(seekBar: SeekBar) {
userIsOperatingSeekbar = true
}
override fun onStopTrackingTouch(seekBar: SeekBar) {
userIsOperatingSeekbar = false
}
}
private val windowInsetsController by lazy { WindowInsetsControllerCompat(window, binding.root) }
private var currentVideoList: List<Video>? = null
@RequiresApi(Build.VERSION_CODES.P)
override fun onCreate(savedInstanceState: Bundle?) {
logcat { "bruh" }
applyAppTheme(preferences)
@ -45,6 +82,16 @@ class NewPlayerActivity : BaseRxActivity<NewPlayerActivityBinding, NewPlayerPres
binding = NewPlayerActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
player.initialize(applicationContext.filesDir.path)
player.addObserver(this)
binding.playbackSeekbar.setOnSeekBarChangeListener(seekBarChangeListener)
// player.playFile(currentVideoList!!.first().videoUrl!!.toString())
if (presenter?.needsInit() == true) {
val anime = intent.extras!!.getLong("anime", -1)
val episode = intent.extras!!.getLong("episode", -1)
@ -55,14 +102,74 @@ class NewPlayerActivity : BaseRxActivity<NewPlayerActivityBinding, NewPlayerPres
presenter.init(anime, episode)
}
binding.button.setOnClickListener {
Log.i("bruh", currentVideoList!!.first().uri!!.toString())
player.playFile(currentVideoList!!.first().uri!!.toString())
MPVLib.command(arrayOf("loadfile", currentVideoList!!.first().uri!!.toString()))
binding.nextBtn.setOnClickListener { refreshUi() }
}
fun updatePlaybackPos(position: Int) {
binding.playbackPositionTxt.text = prettyTime(position)
if (!userIsOperatingSeekbar) {
binding.playbackSeekbar.progress = position
}
binding.button2.setOnClickListener {
player.initialize(applicationContext.filesDir.path)
updateDecoderButton()
updateSpeedButton()
}
private fun updatePlaybackDuration(duration: Int) {
binding.playbackDurationTxt.text = Utils.prettyTime(duration)
if (!userIsOperatingSeekbar) {
binding.playbackSeekbar.max = duration
}
}
private fun updateDecoderButton() {
if (binding.cycleDecoderBtn.visibility != View.VISIBLE) {
return
}
binding.cycleDecoderBtn.text = if (player.hwdecActive) "HW" else "SW"
}
private fun updateSpeedButton() {
binding.cycleSpeedBtn.text = getString(R.string.ui_speed, player.playbackSpeed)
}
private fun refreshUi() {
// forces update of entire UI, used when resuming the activity
val paused = player.paused ?: return
updatePlaybackStatus(paused)
player.timePos?.let { updatePlaybackPos(it) }
player.duration?.let { updatePlaybackDuration(it) }
updatePlaylistButtons()
player.loadTracks()
}
private fun updatePlaylistButtons() {
val plCount = presenter.episodeList.size
val plPos = presenter.getCurrentEpisodeIndex()
if (plCount == 1) {
// use View.GONE so the buttons won't take up any space
binding.prevBtn.visibility = View.GONE
binding.nextBtn.visibility = View.GONE
return
}
binding.prevBtn.visibility = View.VISIBLE
binding.nextBtn.visibility = View.VISIBLE
val g = ContextCompat.getColor(this, R.color.tint_disabled)
val w = ContextCompat.getColor(this, R.color.tint_normal)
binding.prevBtn.imageTintList = ColorStateList.valueOf(if (plPos == 0) g else w)
binding.nextBtn.imageTintList = ColorStateList.valueOf(if (plPos == plCount - 1) g else w)
}
private fun updatePlaybackStatus(paused: Boolean) {
val r = if (paused) R.drawable.ic_play_arrow_black_24dp else R.drawable.ic_pause_black_24dp
binding.playBtn.setImageResource(r)
if (paused) {
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} else {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
}
@ -84,5 +191,45 @@ class NewPlayerActivity : BaseRxActivity<NewPlayerActivityBinding, NewPlayerPres
fun setVideoList(videos: List<Video>) {
logcat(LogPriority.INFO) { "loaded!!" }
currentVideoList = videos
currentVideoList?.first()?.videoUrl.let {
MPVLib.command(arrayOf("loadfile", it))
presenter.currentEpisode?.last_second_seen?.let { pos -> player.timePos = (pos / 1000L).toInt() }
}
refreshUi()
}
private fun prettyTime(d: Int, sign: Boolean = false): String {
if (sign) {
return (if (d >= 0) "+" else "-") + prettyTime(abs(d))
}
val hours = d / 3600
val minutes = d % 3600 / 60
val seconds = d % 60
if (hours == 0) {
return "%02d:%02d".format(minutes, seconds)
}
return "%d:%02d:%02d".format(hours, minutes, seconds)
}
// mpv events
private fun eventPropertyUi(property: String, value: Long) {
when (property) {
"time-pos" -> updatePlaybackPos(value.toInt())
"duration" -> updatePlaybackDuration(value.toInt())
}
}
override fun eventProperty(property: String) {}
override fun eventProperty(property: String, value: Boolean) {}
override fun eventProperty(property: String, value: Long) {
runOnUiThread { eventPropertyUi(property, value) }
}
override fun eventProperty(property: String, value: String) {}
override fun event(eventId: Int) {}
}

View file

@ -38,7 +38,7 @@ class NewPlayerPresenter(
*/
private var episodeId = -1L
private var currentEpisode: Episode? = null
var currentEpisode: Episode? = null
private var currentVideoList: List<Video>? = null
@ -46,7 +46,7 @@ class NewPlayerPresenter(
* 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 episodeList by lazy {
val anime = anime!!
val dbEpisodes = db.getEpisodes(anime).executeAsBlocking()
@ -83,6 +83,10 @@ class NewPlayerPresenter(
.sortedWith(getEpisodeSort(anime, sortDescending = false))
}
fun getCurrentEpisodeIndex(): Int {
return episodeList.indexOfFirst { currentEpisode?.id == it.id }
}
private var hasTrackers: Boolean = false
private val checkTrackers: (Anime) -> Unit = { anime ->
val tracks = db.getTracks(anime).executeAsBlocking()

View file

@ -0,0 +1,273 @@
<?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,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
@ -12,37 +12,165 @@
android:layout_height="match_parent" />
<LinearLayout
android:layout_width="wrap_content"
android:id="@+id/controls"
style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" >
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="play!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ButtonStyle,HardcodedText"
android:layout_margin="@dimen/layer_padding" />
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:background="#bf000000"
android:orientation="vertical">
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="5dp"
android:text="load!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ButtonStyle,HardcodedText"
android:layout_margin="@dimen/layer_padding" />
<LinearLayout
android:id="@+id/controls_title_group"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="visible">
<!-- 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: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>
</androidx.constraintlayout.widget.ConstraintLayout>
</RelativeLayout>