mirror of
https://github.com/libre-tube/LibreTube.git
synced 2024-12-13 13:50:30 +05:30
feat: button to screenshot/capture current frame
This commit is contained in:
parent
f086b3ac4a
commit
52fb2a9861
@ -8,18 +8,23 @@ import android.content.Intent
|
|||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
import android.graphics.Bitmap
|
||||||
import android.media.session.PlaybackState
|
import android.media.session.PlaybackState
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.PixelCopy
|
||||||
|
import android.view.SurfaceView
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.ViewGroup.LayoutParams
|
import android.view.ViewGroup.LayoutParams
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.constraintlayout.motion.widget.MotionLayout
|
import androidx.constraintlayout.motion.widget.MotionLayout
|
||||||
import androidx.constraintlayout.motion.widget.TransitionAdapter
|
import androidx.constraintlayout.motion.widget.TransitionAdapter
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
@ -118,6 +123,7 @@ import java.util.concurrent.Executors
|
|||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
|
|
||||||
|
|
||||||
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||||
class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
||||||
private var _binding: FragmentPlayerBinding? = null
|
private var _binding: FragmentPlayerBinding? = null
|
||||||
@ -352,6 +358,21 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
|||||||
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
val playerData = requireArguments().parcelable<PlayerData>(IntentData.playerData)!!
|
val playerData = requireArguments().parcelable<PlayerData>(IntentData.playerData)!!
|
||||||
@ -526,9 +547,12 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
activity?.supportFragmentManager
|
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) }
|
bundle.getString(IntentData.url)?.let { handleLink(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.commentsToggle.setOnClickListener {
|
binding.commentsToggle.setOnClickListener {
|
||||||
if (!this::streams.isInitialized) return@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 {
|
binding.playerChannel.setOnClickListener {
|
||||||
if (!this::streams.isInitialized) return@setOnClickListener
|
if (!this::streams.isInitialized) return@setOnClickListener
|
||||||
|
|
||||||
@ -702,7 +748,8 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
|||||||
binding.player.updateMarginsByFullscreenMode()
|
binding.player.updateMarginsByFullscreenMode()
|
||||||
|
|
||||||
// set status bar icon color back to theme color after fullscreen dialog closed!
|
// 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
|
// close comment bottom sheet if opened for next video
|
||||||
activity?.supportFragmentManager?.fragments?.filterIsInstance<CommentsSheet>()
|
activity?.supportFragmentManager?.fragments?.filterIsInstance<CommentsSheet>()
|
||||||
?.firstOrNull()?.dismiss()
|
?.firstOrNull()?.dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
private fun initializePlayerView() {
|
private fun initializePlayerView() {
|
||||||
// initialize the player view actions
|
// 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.player.initPlayerOptions(viewModel, viewLifecycleOwner, trackSelector, this)
|
||||||
|
|
||||||
binding.descriptionLayout.setStreams(streams)
|
binding.descriptionLayout.setStreams(streams)
|
||||||
|
@ -78,6 +78,12 @@
|
|||||||
style="@style/PlayerActionsButton"
|
style="@style/PlayerActionsButton"
|
||||||
android:text="@string/pip"
|
android:text="@string/pip"
|
||||||
app:icon="@drawable/ic_open" />
|
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>
|
</LinearLayout>
|
||||||
|
|
||||||
</HorizontalScrollView>
|
</HorizontalScrollView>
|
||||||
@ -206,7 +212,8 @@
|
|||||||
app:layout_constraintEnd_toEndOf="@id/main_container"
|
app:layout_constraintEnd_toEndOf="@id/main_container"
|
||||||
app:layout_constraintStart_toStartOf="@id/main_container"
|
app:layout_constraintStart_toStartOf="@id/main_container"
|
||||||
app:layout_constraintTop_toTopOf="@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
|
<com.github.libretube.ui.views.DoubleTapOverlay
|
||||||
android:id="@+id/doubleTapOverlay"
|
android:id="@+id/doubleTapOverlay"
|
||||||
|
@ -78,6 +78,12 @@
|
|||||||
style="@style/PlayerActionsButton"
|
style="@style/PlayerActionsButton"
|
||||||
android:text="@string/pip"
|
android:text="@string/pip"
|
||||||
app:icon="@drawable/ic_open" />
|
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>
|
</LinearLayout>
|
||||||
|
|
||||||
</HorizontalScrollView>
|
</HorizontalScrollView>
|
||||||
@ -179,7 +185,8 @@
|
|||||||
app:layout_constraintBottom_toBottomOf="@id/main_container"
|
app:layout_constraintBottom_toBottomOf="@id/main_container"
|
||||||
app:layout_constraintStart_toStartOf="@id/main_container"
|
app:layout_constraintStart_toStartOf="@id/main_container"
|
||||||
app:layout_constraintTop_toTopOf="@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
|
<com.github.libretube.ui.views.DoubleTapOverlay
|
||||||
android:id="@+id/doubleTapOverlay"
|
android:id="@+id/doubleTapOverlay"
|
||||||
|
@ -463,6 +463,7 @@
|
|||||||
<string name="default_language">Default</string>
|
<string name="default_language">Default</string>
|
||||||
<string name="behavior_when_minimized">Behavior when minimized</string>
|
<string name="behavior_when_minimized">Behavior when minimized</string>
|
||||||
<string name="external_player">External player</string>
|
<string name="external_player">External player</string>
|
||||||
|
<string name="screenshot">Screenshot</string>
|
||||||
|
|
||||||
<!-- Backup & Restore Settings -->
|
<!-- Backup & Restore Settings -->
|
||||||
<string name="import_subscriptions_from">Import subscriptions from</string>
|
<string name="import_subscriptions_from">Import subscriptions from</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user