feat: button to screenshot/capture current frame

This commit is contained in:
Bnyro 2024-07-29 16:13:30 +02:00
parent f086b3ac4a
commit 52fb2a9861
4 changed files with 73 additions and 7 deletions

View File

@ -8,18 +8,23 @@ import android.content.Intent
import android.content.IntentFilter
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.graphics.Bitmap
import android.media.session.PlaybackState
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.PowerManager
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.PixelCopy
import android.view.SurfaceView
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.constraintlayout.motion.widget.TransitionAdapter
import androidx.core.content.ContextCompat
@ -118,6 +123,7 @@ import java.util.concurrent.Executors
import kotlin.math.abs
import kotlin.math.ceil
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
class PlayerFragment : Fragment(), OnlinePlayerOptions {
private var _binding: FragmentPlayerBinding? = null
@ -352,6 +358,21 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
)
private var screenshotBitmap: Bitmap? = null
private val openScreenshotFile =
registerForActivityResult(ActivityResultContracts.CreateDocument("image/png")) { uri ->
if (uri == null) {
screenshotBitmap = null
return@registerForActivityResult
}
context?.contentResolver?.openOutputStream(uri)?.use { outputStream ->
screenshotBitmap?.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
}
screenshotBitmap = null
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val playerData = requireArguments().parcelable<PlayerData>(IntentData.playerData)!!
@ -526,9 +547,12 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
}
activity?.supportFragmentManager
?.setFragmentResultListener(CommentsSheet.HANDLE_LINK_REQUEST_KEY, viewLifecycleOwner) { _, bundle ->
?.setFragmentResultListener(
CommentsSheet.HANDLE_LINK_REQUEST_KEY,
viewLifecycleOwner
) { _, bundle ->
bundle.getString(IntentData.url)?.let { handleLink(it) }
}
}
binding.commentsToggle.setOnClickListener {
if (!this::streams.isInitialized) return@setOnClickListener
@ -628,6 +652,28 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
binding.relPlayerScreenshot.setOnClickListener {
if (!this::exoPlayer.isInitialized || !this::streams.isInitialized) return@setOnClickListener
val surfaceView =
binding.player.videoSurfaceView as? SurfaceView ?: return@setOnClickListener
val bmp = Bitmap.createBitmap(
surfaceView.width,
surfaceView.height,
Bitmap.Config.ARGB_8888
)
PixelCopy.request(surfaceView, bmp, { _ ->
screenshotBitmap = bmp
val currentPosition = exoPlayer.currentPosition.toFloat() / 1000
openScreenshotFile.launch("${streams.title}-${currentPosition}.png")
}, Handler(Looper.getMainLooper()))
}
} else {
binding.relPlayerScreenshot.isGone = true
}
binding.playerChannel.setOnClickListener {
if (!this::streams.isInitialized) return@setOnClickListener
@ -702,7 +748,8 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
binding.player.updateMarginsByFullscreenMode()
// set status bar icon color back to theme color after fullscreen dialog closed!
windowInsetsControllerCompat.isAppearanceLightStatusBars = !ThemeHelper.isDarkMode(requireContext())
windowInsetsControllerCompat.isAppearanceLightStatusBars =
!ThemeHelper.isDarkMode(requireContext())
}
/**
@ -1031,13 +1078,17 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
// close comment bottom sheet if opened for next video
activity?.supportFragmentManager?.fragments?.filterIsInstance<CommentsSheet>()
?.firstOrNull()?.dismiss()
?.firstOrNull()?.dismiss()
}
@SuppressLint("SetTextI18n")
private fun initializePlayerView() {
// initialize the player view actions
binding.player.initialize(doubleTapOverlayBinding, playerGestureControlsViewBinding, chaptersViewModel)
binding.player.initialize(
doubleTapOverlayBinding,
playerGestureControlsViewBinding,
chaptersViewModel
)
binding.player.initPlayerOptions(viewModel, viewLifecycleOwner, trackSelector, this)
binding.descriptionLayout.setStreams(streams)

View File

@ -78,6 +78,12 @@
style="@style/PlayerActionsButton"
android:text="@string/pip"
app:icon="@drawable/ic_open" />
<com.google.android.material.button.MaterialButton
android:id="@+id/relPlayer_screenshot"
style="@style/PlayerActionsButton"
android:text="@string/screenshot"
app:icon="@drawable/ic_copy" />
</LinearLayout>
</HorizontalScrollView>
@ -206,7 +212,8 @@
app:layout_constraintEnd_toEndOf="@id/main_container"
app:layout_constraintStart_toStartOf="@id/main_container"
app:layout_constraintTop_toTopOf="@id/main_container"
app:show_buffering="when_playing">
app:show_buffering="when_playing"
app:surface_type="surface_view">
<com.github.libretube.ui.views.DoubleTapOverlay
android:id="@+id/doubleTapOverlay"

View File

@ -78,6 +78,12 @@
style="@style/PlayerActionsButton"
android:text="@string/pip"
app:icon="@drawable/ic_open" />
<com.google.android.material.button.MaterialButton
android:id="@+id/relPlayer_screenshot"
style="@style/PlayerActionsButton"
android:text="@string/screenshot"
app:icon="@drawable/ic_copy" />
</LinearLayout>
</HorizontalScrollView>
@ -179,7 +185,8 @@
app:layout_constraintBottom_toBottomOf="@id/main_container"
app:layout_constraintStart_toStartOf="@id/main_container"
app:layout_constraintTop_toTopOf="@id/main_container"
app:show_buffering="when_playing">
app:show_buffering="when_playing"
app:surface_type="surface_view">
<com.github.libretube.ui.views.DoubleTapOverlay
android:id="@+id/doubleTapOverlay"

View File

@ -463,6 +463,7 @@
<string name="default_language">Default</string>
<string name="behavior_when_minimized">Behavior when minimized</string>
<string name="external_player">External player</string>
<string name="screenshot">Screenshot</string>
<!-- Backup & Restore Settings -->
<string name="import_subscriptions_from">Import subscriptions from</string>