mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-29 09:39:03 +03:00
bruhus momenticus
This commit is contained in:
parent
53a298b157
commit
5e1fb417c9
8 changed files with 882 additions and 78 deletions
|
@ -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" />
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
190
app/src/main/java/eu/kanade/tachiyomi/ui/player/MPVPresenter.kt
Normal file
190
app/src/main/java/eu/kanade/tachiyomi/ui/player/MPVPresenter.kt
Normal 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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
273
app/src/main/res/layout/mpv_activity.xml
Normal file
273
app/src/main/res/layout/mpv_activity.xml
Normal 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>
|
|
@ -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>
|
Loading…
Reference in a new issue