Skip to content
This repository was archived by the owner on Jul 18, 2026. It is now read-only.

Commit 8b660d9

Browse files
authored
Merge:增加画中画模式
设置增加画中画模式(实验性),你现在可以边干别的边看了!
2 parents 2892668 + 9024611 commit 8b660d9

11 files changed

Lines changed: 237 additions & 15 deletions

File tree

app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@
7474
</activity>
7575
<activity
7676
android:name=".ui.activity.MainActivity"
77-
android:configChanges="orientation|screenSize|keyboardHidden"
77+
android:supportsPictureInPicture="true"
78+
android:configChanges="orientation|smallestScreenSize|screenLayout|screenSize|keyboardHidden"
7879
android:exported="true"
7980
android:launchMode="singleTask"
8081
android:theme="@style/Theme.Hanime1.Main">

app/src/main/java/com/yenaly/han1meviewer/ui/activity/MainActivity.kt

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ package com.yenaly.han1meviewer.ui.activity
33
import android.Manifest
44
import android.annotation.SuppressLint
55
import android.app.KeyguardManager
6+
import android.content.BroadcastReceiver
67
import android.content.Context
78
import android.content.Intent
9+
import android.content.IntentFilter
810
import android.content.pm.PackageManager
11+
import android.content.res.Configuration
912
import android.graphics.Color
1013
import android.graphics.RenderEffect
1114
import android.graphics.Shader
@@ -59,6 +62,7 @@ import com.yenaly.han1meviewer.logic.state.WebsiteState
5962
import com.yenaly.han1meviewer.logout
6063
import com.yenaly.han1meviewer.ui.fragment.PermissionRequester
6164
import com.yenaly.han1meviewer.ui.fragment.ToolbarHost
65+
import com.yenaly.han1meviewer.ui.fragment.video.VideoFragment
6266
import com.yenaly.han1meviewer.ui.viewmodel.AppViewModel
6367
import com.yenaly.han1meviewer.ui.viewmodel.MainViewModel
6468
import com.yenaly.han1meviewer.util.logScreenViewEvent
@@ -90,6 +94,7 @@ class MainActivity : YenalyActivity<ActivityMainBinding>(), DrawerListener, Tool
9094

9195
companion object {
9296
private const val REQUEST_WRITE_EXTERNAL_STORAGE = 1234
97+
const val ACTION_TOGGLE_PLAY = "com.yenaly.han1meviewer.ACTION_TOGGLE_PLAY"
9398
}
9499

95100
val currentFragment get() = navHostFragment.childFragmentManager.primaryNavigationFragment
@@ -104,6 +109,18 @@ class MainActivity : YenalyActivity<ActivityMainBinding>(), DrawerListener, Tool
104109
}
105110
}
106111
private var hasAuthenticated = false
112+
private val pipActionReceiver = object : BroadcastReceiver() {
113+
override fun onReceive(context: Context?, intent: Intent?) {
114+
Log.i("pipmode", "✅ onReceive called with action: ${intent?.action}")
115+
when (intent?.action) {
116+
ACTION_TOGGLE_PLAY -> {
117+
Log.i("pipmode", "🎬 ACTION_TOGGLE_PLAY triggered")
118+
togglePlayPause()
119+
}
120+
}
121+
}
122+
}
123+
107124

108125
override fun getViewBinding(layoutInflater: LayoutInflater): ActivityMainBinding =
109126
ActivityMainBinding.inflate(layoutInflater)
@@ -251,8 +268,6 @@ class MainActivity : YenalyActivity<ActivityMainBinding>(), DrawerListener, Tool
251268

252269
}
253270

254-
255-
256271
private fun removeAuthGuard() {
257272
val root = findViewById<ViewGroup>(R.id.dl_main) // 或者 R.id.root
258273
val authGuard = findViewById<View>(R.id.auth_guard)
@@ -387,6 +402,7 @@ class MainActivity : YenalyActivity<ActivityMainBinding>(), DrawerListener, Tool
387402

388403
override fun onStart() {
389404
super.onStart()
405+
registerPipReceiver()
390406
binding.root.post {
391407
textFromClipboard?.let {
392408
videoUrlRegex.find(it)?.groupValues?.get(1)?.let { videoCode ->
@@ -395,6 +411,25 @@ class MainActivity : YenalyActivity<ActivityMainBinding>(), DrawerListener, Tool
395411
}
396412
}
397413
}
414+
private fun registerPipReceiver() {
415+
val filter = IntentFilter().apply {
416+
addAction(ACTION_TOGGLE_PLAY)
417+
}
418+
419+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
420+
registerReceiver(pipActionReceiver, filter, RECEIVER_NOT_EXPORTED)
421+
Log.i("pipmode", "✅ registerReceiver with RECEIVER_NOT_EXPORTED")
422+
} else {
423+
@SuppressLint("UnspecifiedRegisterReceiverFlag")
424+
registerReceiver(pipActionReceiver, filter)
425+
Log.i("pipmode", "✅ registerReceiver (legacy)")
426+
}
427+
}
428+
429+
override fun onStop() {
430+
super.onStop()
431+
unregisterReceiver(pipActionReceiver)
432+
}
398433

399434
override fun bindDataObservers() {
400435
lifecycleScope.launch {
@@ -745,4 +780,44 @@ class MainActivity : YenalyActivity<ActivityMainBinding>(), DrawerListener, Tool
745780
}
746781
}
747782
}
783+
784+
override fun onUserLeaveHint() {
785+
super.onUserLeaveHint()
786+
val currentFragment = supportFragmentManager
787+
.findFragmentById(R.id.fcv_main)
788+
?.childFragmentManager
789+
?.primaryNavigationFragment
790+
791+
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
792+
val allowPip = prefs.getBoolean("allow_pip_mode", true)
793+
794+
Log.i("pipmode","enter pip mode?\n$currentFragment\nallowpip:$allowPip\n")
795+
796+
if (currentFragment is VideoFragment && currentFragment.shouldEnterPip() && allowPip) {
797+
Log.i("pipmode","enter pip mode")
798+
currentFragment.enterPipMode()
799+
}
800+
}
801+
override fun onPictureInPictureModeChanged(
802+
isInPictureInPictureMode: Boolean,
803+
newConfig: Configuration
804+
) {
805+
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
806+
807+
val currentFragment = supportFragmentManager
808+
.findFragmentById(R.id.fcv_main)
809+
?.childFragmentManager
810+
?.primaryNavigationFragment
811+
812+
if (currentFragment is VideoFragment) {
813+
currentFragment.onPipModeChanged(isInPictureInPictureMode)
814+
}
815+
}
816+
fun togglePlayPause() {
817+
// val playing = exoPlayer?.isPlaying == true
818+
// exoPlayer?.playWhenReady = !playing
819+
Toast.makeText(this, "尚未实现", Toast.LENGTH_SHORT).show()
820+
Log.i("pipmode","toggleplay&pause")
821+
}
822+
748823
}

app/src/main/java/com/yenaly/han1meviewer/ui/fragment/settings/HomeSettingsFragment.kt

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
@file:Suppress("DEPRECATION")
22
package com.yenaly.han1meviewer.ui.fragment.settings
33

4+
import android.app.AppOpsManager
45
import android.app.KeyguardManager
56
import android.content.Context
67
import android.content.Intent
@@ -68,6 +69,8 @@ class HomeSettingsFragment : YenalySettingsFragment(R.xml.settings_home) {
6869
companion object {
6970
const val VIDEO_LANGUAGE = "video_language"
7071
const val DEFAULT_VIDEO_QUALITY = "default_video_quality"
72+
73+
const val ALLOW_PIP_MDOE = "allow_pip_mode"
7174
const val PLAYER_SETTINGS = "player_settings"
7275
const val H_KEYFRAME_SETTINGS = "h_keyframe_settings"
7376
const val UPDATE = "update"
@@ -92,6 +95,8 @@ class HomeSettingsFragment : YenalySettingsFragment(R.xml.settings_home) {
9295
by safePreference<MaterialDialogPreference>(DEFAULT_VIDEO_QUALITY)
9396
private val playerSettings
9497
by safePreference<Preference>(PLAYER_SETTINGS)
98+
private val allowPipMode
99+
by safePreference<Preference>(ALLOW_PIP_MDOE)
95100
private val hKeyframeSettings
96101
by safePreference<Preference>(H_KEYFRAME_SETTINGS)
97102
private val downloadSettings
@@ -131,6 +136,7 @@ class HomeSettingsFragment : YenalySettingsFragment(R.xml.settings_home) {
131136

132137
}
133138

139+
@RequiresApi(Build.VERSION_CODES.Q)
134140
override fun onPreferencesCreated(savedInstanceState: Bundle?) {
135141
val lockSwitch = findPreference<SwitchPreferenceCompat>("use_lock_screen")
136142
lockSwitch?.setOnPreferenceChangeListener { _, newValue ->
@@ -198,11 +204,27 @@ class HomeSettingsFragment : YenalySettingsFragment(R.xml.settings_home) {
198204

199205
setOnPreferenceChangeListener { _, newValue ->
200206
if (newValue != Preferences.videoLanguage) {
201-
Toast.makeText(application, "修改成功$newValue", Toast.LENGTH_SHORT).show()
207+
Toast.makeText(application, "Success$newValue", Toast.LENGTH_SHORT).show()
202208
}
203209
return@setOnPreferenceChangeListener true
204210
}
205211
}
212+
allowPipMode.apply {
213+
setOnPreferenceChangeListener{ preference: Preference, newValue ->
214+
val enabled = newValue as Boolean
215+
if (enabled && !isPipPermissionGranted(requireContext())) {
216+
Toast.makeText(requireContext(),
217+
getString(R.string.request_pip_alert), Toast.LENGTH_SHORT).show()
218+
openPipPermissionSettings(requireContext())
219+
Handler(Looper.getMainLooper()).post {
220+
(preference as SwitchPreferenceCompat).isChecked = false
221+
}
222+
false
223+
} else {
224+
true
225+
}
226+
}
227+
}
206228
playerSettings.setOnPreferenceClickListener {
207229
SettingsRouter.with(this).navigateWithinSettings(R.id.playerSettingsFragment)
208230
return@setOnPreferenceClickListener true
@@ -441,4 +463,30 @@ class HomeSettingsFragment : YenalySettingsFragment(R.xml.settings_home) {
441463
val km = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
442464
return km.isDeviceSecure
443465
}
466+
467+
468+
@RequiresApi(Build.VERSION_CODES.Q)
469+
fun isPipPermissionGranted(context: Context): Boolean {
470+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return false
471+
val appOps = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
472+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
473+
val mode = appOps.unsafeCheckOpNoThrow(
474+
AppOpsManager.OPSTR_PICTURE_IN_PICTURE,
475+
android.os.Process.myUid(),
476+
context.packageName
477+
)
478+
mode == AppOpsManager.MODE_ALLOWED
479+
} else {
480+
true
481+
}
482+
}
483+
484+
private fun openPipPermissionSettings(context: Context) {
485+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
486+
val intent = Intent("android.settings.PICTURE_IN_PICTURE_SETTINGS",
487+
"package:${context.packageName}".toUri())
488+
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
489+
context.startActivity(intent)
490+
}
491+
}
444492
}

app/src/main/java/com/yenaly/han1meviewer/ui/fragment/video/VideoFragment.kt

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
package com.yenaly.han1meviewer.ui.fragment.video
22

3+
import android.app.PendingIntent
4+
import android.app.PictureInPictureParams
5+
import android.app.RemoteAction
6+
import android.content.Intent
37
import android.content.pm.ActivityInfo
8+
import android.graphics.drawable.Icon
9+
import android.os.Build
410
import android.os.Bundle
511
import android.util.Log
12+
import android.util.Rational
613
import android.view.Gravity
714
import android.view.LayoutInflater
815
import android.view.ViewGroup
916
import androidx.activity.OnBackPressedCallback
1017
import androidx.core.os.bundleOf
1118
import androidx.core.view.ViewCompat
1219
import androidx.core.view.WindowInsetsCompat
20+
import androidx.core.view.isVisible
1321
import androidx.core.view.updateLayoutParams
1422
import androidx.fragment.app.activityViewModels
1523
import androidx.fragment.app.viewModels
@@ -40,6 +48,7 @@ import com.yenaly.han1meviewer.logic.entity.WatchHistoryEntity
4048
import com.yenaly.han1meviewer.logic.exception.ParseException
4149
import com.yenaly.han1meviewer.logic.state.VideoLoadingState
4250
import com.yenaly.han1meviewer.ui.activity.MainActivity
51+
import com.yenaly.han1meviewer.ui.view.video.ExoMediaKernel
4352
import com.yenaly.han1meviewer.ui.view.video.HMediaKernel
4453
import com.yenaly.han1meviewer.ui.view.video.HanimeDataSource
4554
import com.yenaly.han1meviewer.ui.viewmodel.CommentViewModel
@@ -296,4 +305,40 @@ class VideoFragment : YenalyFragment<FragmentVideoBinding>(), OrientationManager
296305
number = count
297306
}
298307
}
308+
fun enterPipMode() {
309+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
310+
val aspectRatio = Rational(16, 9)
311+
val intent = PendingIntent.getBroadcast(
312+
requireContext(),
313+
0,
314+
Intent(MainActivity.ACTION_TOGGLE_PLAY).setPackage(requireContext().packageName),
315+
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
316+
)
317+
val icon = Icon.createWithResource(requireContext(), R.drawable.ic_baseline_play_circle_outline_24)
318+
val action = RemoteAction(
319+
icon,
320+
getString(R.string.play_pause),
321+
getString(R.string.play_pause),
322+
intent
323+
)
324+
val params = PictureInPictureParams.Builder()
325+
.setAspectRatio(aspectRatio)
326+
.setActions(listOf(action))
327+
.build()
328+
requireActivity().enterPictureInPictureMode(params)
329+
}
330+
}
331+
332+
fun shouldEnterPip(): Boolean {
333+
val isPlaying = (Jzvd.CURRENT_JZVD?.mediaInterface as? ExoMediaKernel)?.isPlaying ?: false
334+
Log.i("pipmode","enter pip mode?isPlaying:$isPlaying\n")
335+
return !requireActivity().isInPictureInPictureMode
336+
}
337+
fun onPipModeChanged(isInPip: Boolean) {
338+
binding.videoTl.isVisible = !isInPip
339+
binding.videoVp.isUserInputEnabled = !isInPip
340+
binding.videoVp.isVisible = !isInPip
341+
binding.videoPlayer.setControlsVisible(!isInPip)
342+
binding.videoPlayer.centerSurfaceInPip(isInPip, binding.videoPlayer.screen == Jzvd.SCREEN_FULLSCREEN)
343+
}
299344
}

app/src/main/java/com/yenaly/han1meviewer/ui/view/video/HJzvdStd.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,26 @@ class HJzvdStd @JvmOverloads constructor(
334334
fun setUp(jzDataSource: JZDataSource?, screen: Int, kernel: HMediaKernel.Type) {
335335
setUp(jzDataSource, screen, kernel.clazz)
336336
}
337+
fun setControlsVisible(visible: Boolean) {
338+
findViewById<View>(R.id.tv_speed)?.isVisible = visible
339+
findViewById<View>(R.id.tv_keyframe)?.isVisible = visible
340+
findViewById<View>(R.id.tv_timer)?.isVisible = visible
341+
findViewById<View>(R.id.go_home)?.isVisible = visible
342+
findViewById<View>(R.id.layout_top)?.isVisible = visible
343+
findViewById<View>(R.id.layout_bottom)?.isVisible = visible
344+
}
345+
fun centerSurfaceInPip(isInPip: Boolean, isFullscreen: Boolean) {
346+
val container = findViewById<View>(R.id.surface_container)
347+
container?.let {
348+
if (isInPip && !isFullscreen) {
349+
// it.translationY = -210f // 调节竖屏模式下因系统UI导致的偏移
350+
it.translationY = 0f
351+
} else {
352+
it.translationY = 0f
353+
}
354+
}
355+
}
356+
337357

338358
override fun setUp(jzDataSource: JZDataSource?, screen: Int, clazz: Class<*>) {
339359
super.setUp(jzDataSource, screen, clazz)

0 commit comments

Comments
 (0)