feat: new video player
Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
bf2d1520a6
commit
f0d27b9a80
|
|
@ -118,6 +118,11 @@
|
|||
android:configChanges="orientation|screenSize"
|
||||
android:theme="@style/Theme.Revolt" />
|
||||
|
||||
<activity
|
||||
android:name=".activities.media.VideoViewActivity2"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:theme="@style/Theme.Revolt" />
|
||||
|
||||
<!-- Backport photo picker via Google Play Services -->
|
||||
<service
|
||||
android:name="com.google.android.gms.metadata.ModuleDependencies"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,220 @@
|
|||
package chat.revolt.activities.media
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import chat.revolt.R
|
||||
import chat.revolt.api.REVOLT_FILES
|
||||
import chat.revolt.api.RevoltHttp
|
||||
import chat.revolt.api.schemas.AutumnResource
|
||||
import chat.revolt.databinding.ActivityVideoplayerBinding
|
||||
import chat.revolt.provider.getAttachmentContentUri
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.statement.readBytes
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class VideoViewActivity2 : FragmentActivity() {
|
||||
private lateinit var binding: ActivityVideoplayerBinding
|
||||
|
||||
private var player: ExoPlayer? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val autumnResource = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
intent.getParcelableExtra("autumnResource", AutumnResource::class.java)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
intent.getParcelableExtra("autumnResource")
|
||||
}
|
||||
|
||||
if (autumnResource?.id == null) {
|
||||
Log.e("VideoViewActivity", "No AutumnResource provided")
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
val resourceUrl =
|
||||
"$REVOLT_FILES/attachments/${autumnResource.id}/${autumnResource.filename}"
|
||||
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
|
||||
binding = ActivityVideoplayerBinding.inflate(layoutInflater)
|
||||
|
||||
binding.tbTop.title = autumnResource.filename
|
||||
binding.tbTop.setNavigationOnClickListener { finish() }
|
||||
binding.tbTop.setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.mi_save -> {
|
||||
downloadFile(autumnResource, resourceUrl)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.mi_share_file -> {
|
||||
shareFile(autumnResource, resourceUrl)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.mi_share_link -> {
|
||||
shareUrl(resourceUrl)
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
player = ExoPlayer.Builder(this).build().apply {
|
||||
setMediaItem(MediaItem.fromUri(resourceUrl))
|
||||
prepare()
|
||||
play()
|
||||
}
|
||||
|
||||
binding.xpPlayer.player = player
|
||||
binding.xpPlayer.setFullscreenButtonClickListener {
|
||||
when (binding.alTop.visibility) {
|
||||
android.view.View.VISIBLE -> {
|
||||
binding.alTop.visibility = android.view.View.GONE
|
||||
WindowInsetsControllerCompat(window, binding.root).let { controller ->
|
||||
controller.hide(WindowInsetsCompat.Type.systemBars())
|
||||
controller.systemBarsBehavior =
|
||||
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
}
|
||||
binding.xpPlayer.background = ColorDrawable(Color.BLACK)
|
||||
}
|
||||
|
||||
else -> {
|
||||
binding.alTop.visibility = android.view.View.VISIBLE
|
||||
WindowInsetsControllerCompat(window, binding.root).let { controller ->
|
||||
controller.show(WindowInsetsCompat.Type.systemBars())
|
||||
controller.systemBarsBehavior =
|
||||
WindowInsetsControllerCompat.BEHAVIOR_DEFAULT
|
||||
}
|
||||
binding.xpPlayer.background = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
setContentView(binding.root)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
player?.release()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
player?.pause()
|
||||
}
|
||||
|
||||
private fun shareUrl(resourceUrl: String) {
|
||||
val intent =
|
||||
Intent(Intent.ACTION_SEND)
|
||||
intent.type = "text/plain"
|
||||
intent.putExtra(
|
||||
Intent.EXTRA_TEXT,
|
||||
resourceUrl
|
||||
)
|
||||
|
||||
val shareIntent = Intent.createChooser(intent, null)
|
||||
startActivity(shareIntent)
|
||||
}
|
||||
|
||||
private fun shareFile(resource: AutumnResource, resourceUrl: String) {
|
||||
lifecycleScope.launch {
|
||||
val contentUri = getAttachmentContentUri(
|
||||
this@VideoViewActivity2,
|
||||
resourceUrl,
|
||||
resource.id!!,
|
||||
resource.filename ?: "video"
|
||||
)
|
||||
|
||||
val intent =
|
||||
Intent(Intent.ACTION_SEND)
|
||||
intent.type = resource.contentType ?: "video/*"
|
||||
intent.putExtra(
|
||||
Intent.EXTRA_TITLE,
|
||||
resource.filename
|
||||
)
|
||||
intent.putExtra(
|
||||
Intent.EXTRA_SUBJECT,
|
||||
resource.filename
|
||||
)
|
||||
intent.putExtra(
|
||||
Intent.EXTRA_STREAM,
|
||||
contentUri
|
||||
)
|
||||
|
||||
val shareIntent = Intent.createChooser(intent, null)
|
||||
startActivity(shareIntent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun downloadFile(resource: AutumnResource, resourceUrl: String) {
|
||||
lifecycleScope.launch {
|
||||
this@VideoViewActivity2.applicationContext.let {
|
||||
it.contentResolver.insert(
|
||||
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
|
||||
ContentValues().apply {
|
||||
put(MediaStore.Video.Media.DISPLAY_NAME, resource.filename)
|
||||
put(MediaStore.Video.Media.MIME_TYPE, resource.contentType)
|
||||
put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/Revolt")
|
||||
put(MediaStore.Video.Media.IS_PENDING, 1)
|
||||
}
|
||||
)
|
||||
}?.let { uri ->
|
||||
this@VideoViewActivity2.contentResolver.openOutputStream(uri).use { stream ->
|
||||
val video = RevoltHttp.get(resourceUrl).readBytes()
|
||||
stream?.write(video)
|
||||
|
||||
this@VideoViewActivity2.applicationContext.let {
|
||||
it.contentResolver.update(
|
||||
uri,
|
||||
ContentValues().apply {
|
||||
put(MediaStore.Video.Media.IS_PENDING, 0)
|
||||
},
|
||||
null,
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
Snackbar.make(
|
||||
binding.xpPlayer,
|
||||
R.string.media_viewer_saved,
|
||||
Snackbar.LENGTH_SHORT
|
||||
).setAction(
|
||||
R.string.media_viewer_open
|
||||
) {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.setDataAndType(uri, resource.contentType)
|
||||
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
startActivity(intent)
|
||||
}
|
||||
.setActionTextColor(
|
||||
MaterialColors.getColor(
|
||||
binding.xpPlayer,
|
||||
com.google.android.material.R.attr.colorPrimary
|
||||
)
|
||||
)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -30,6 +30,19 @@ sealed class MediaConversationsVariates {
|
|||
data class Restricted(val predicate: () -> Boolean) : MediaConversationsVariates()
|
||||
}
|
||||
|
||||
@FeatureFlag("VideoViewActivity2")
|
||||
sealed class ViewViewActivity2Variates {
|
||||
@Treatment(
|
||||
"Enable the new XML-based video player activity for all users"
|
||||
)
|
||||
object Enabled : ViewViewActivity2Variates()
|
||||
|
||||
@Treatment(
|
||||
"Enable the new XML-based video player activity for users that meet certain or all criteria (implementation-specific)"
|
||||
)
|
||||
data class Restricted(val predicate: () -> Boolean) : ViewViewActivity2Variates()
|
||||
}
|
||||
|
||||
object FeatureFlags {
|
||||
@FeatureFlag("LabsAccessControl")
|
||||
var labsAccessControl by mutableStateOf<LabsAccessControlVariates>(
|
||||
|
|
@ -55,4 +68,17 @@ object FeatureFlags {
|
|||
is MediaConversationsVariates.Enabled -> true
|
||||
is MediaConversationsVariates.Restricted -> (mediaConversations as MediaConversationsVariates.Restricted).predicate()
|
||||
}
|
||||
|
||||
@FeatureFlag("VideoViewActivity2")
|
||||
var videoViewActivity2 by mutableStateOf<ViewViewActivity2Variates>(
|
||||
ViewViewActivity2Variates.Restricted {
|
||||
RevoltAPI.selfId == SpecialUsers.JENNIFER
|
||||
}
|
||||
)
|
||||
|
||||
val videoViewActivity2Granted: Boolean
|
||||
get() = when (videoViewActivity2) {
|
||||
is ViewViewActivity2Variates.Enabled -> true
|
||||
is ViewViewActivity2Variates.Restricted -> (videoViewActivity2 as ViewViewActivity2Variates.Restricted).predicate()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ import androidx.compose.ui.unit.sp
|
|||
import chat.revolt.R
|
||||
import chat.revolt.activities.media.ImageViewActivity
|
||||
import chat.revolt.activities.media.VideoViewActivity
|
||||
import chat.revolt.activities.media.VideoViewActivity2
|
||||
import chat.revolt.api.REVOLT_FILES
|
||||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.api.internals.BrushCompat
|
||||
|
|
@ -66,6 +67,7 @@ import chat.revolt.api.routes.channel.unreact
|
|||
import chat.revolt.api.routes.microservices.january.asJanuaryProxyUrl
|
||||
import chat.revolt.api.schemas.AutumnResource
|
||||
import chat.revolt.api.schemas.User
|
||||
import chat.revolt.api.settings.FeatureFlags
|
||||
import chat.revolt.api.settings.GlobalState
|
||||
import chat.revolt.api.settings.MessageReplyStyle
|
||||
import chat.revolt.callbacks.Action
|
||||
|
|
@ -387,7 +389,10 @@ fun Message(
|
|||
attachmentView.launch(
|
||||
Intent(
|
||||
context,
|
||||
VideoViewActivity::class.java
|
||||
when (FeatureFlags.videoViewActivity2Granted) {
|
||||
true -> VideoViewActivity2::class.java
|
||||
else -> VideoViewActivity::class.java
|
||||
}
|
||||
).apply {
|
||||
putExtra("autumnResource", attachment)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z" />
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<font-family xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<font
|
||||
app:font="@font/inter_display_thin"
|
||||
app:fontStyle="normal"
|
||||
app:fontWeight="100" />
|
||||
<font
|
||||
app:font="@font/inter_display_extralight"
|
||||
app:fontStyle="normal"
|
||||
app:fontWeight="200" />
|
||||
<font
|
||||
app:font="@font/inter_display_light"
|
||||
app:fontStyle="normal"
|
||||
app:fontWeight="300" />
|
||||
<font
|
||||
app:font="@font/inter_display_regular"
|
||||
app:fontStyle="normal"
|
||||
app:fontWeight="400" />
|
||||
<font
|
||||
app:font="@font/inter_display_medium"
|
||||
app:fontStyle="normal"
|
||||
app:fontWeight="500" />
|
||||
<font
|
||||
app:font="@font/inter_display_semibold"
|
||||
app:fontStyle="normal"
|
||||
app:fontWeight="600" />
|
||||
<font
|
||||
app:font="@font/inter_display_bold"
|
||||
app:fontStyle="normal"
|
||||
app:fontWeight="700" />
|
||||
<font
|
||||
app:font="@font/inter_display_extrabold"
|
||||
app:fontStyle="normal"
|
||||
app:fontWeight="800" />
|
||||
<font
|
||||
app:font="@font/inter_display_black"
|
||||
app:fontStyle="normal"
|
||||
app:fontWeight="900" />
|
||||
<font
|
||||
app:font="@font/inter_display_thin_italic"
|
||||
app:fontStyle="italic"
|
||||
app:fontWeight="100" />
|
||||
<font
|
||||
app:font="@font/inter_display_extralight_italic"
|
||||
app:fontStyle="italic"
|
||||
app:fontWeight="200" />
|
||||
<font
|
||||
app:font="@font/inter_display_light_italic"
|
||||
app:fontStyle="italic"
|
||||
app:fontWeight="300" />
|
||||
<font
|
||||
app:font="@font/inter_display_italic"
|
||||
app:fontStyle="italic"
|
||||
app:fontWeight="400" />
|
||||
<font
|
||||
app:font="@font/inter_display_medium_italic"
|
||||
app:fontStyle="italic"
|
||||
app:fontWeight="500" />
|
||||
<font
|
||||
app:font="@font/inter_display_semibold_italic"
|
||||
app:fontStyle="italic"
|
||||
app:fontWeight="600" />
|
||||
<font
|
||||
app:font="@font/inter_display_bold_italic"
|
||||
app:fontStyle="italic"
|
||||
app:fontWeight="700" />
|
||||
<font
|
||||
app:font="@font/inter_display_extrabold_italic"
|
||||
app:fontStyle="italic"
|
||||
app:fontWeight="800" />
|
||||
<font
|
||||
app:font="@font/inter_display_black_italic"
|
||||
app:fontStyle="italic"
|
||||
app:fontWeight="900" />
|
||||
</font-family>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/al_top"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/tb_top"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
app:title="@string/unknown"
|
||||
app:navigationIcon="@drawable/ic_arrow_left_24dp"
|
||||
app:menu="@menu/menu_videoplayer"
|
||||
app:navigationIconTint="?attr/colorOnSurface"
|
||||
style="@style/Widget.Revolt.Toolbar" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.media3.ui.PlayerView
|
||||
android:id="@+id/xp_player"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
app:show_buffering="always" />
|
||||
</LinearLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/mi_share"
|
||||
android:icon="@drawable/ic_share_24dp"
|
||||
android:iconTint="?attr/colorControlNormal"
|
||||
android:iconTintMode="multiply"
|
||||
android:title="@string/share"
|
||||
app:showAsAction="ifRoom"
|
||||
tools:targetApi="o">
|
||||
<menu>
|
||||
<item
|
||||
android:id="@+id/mi_share_link"
|
||||
android:title="@string/media_viewer_share_url" />
|
||||
<item
|
||||
android:id="@+id/mi_share_file"
|
||||
android:title="@string/media_viewer_share_video" />
|
||||
</menu>
|
||||
</item>
|
||||
<item
|
||||
android:id="@+id/mi_save"
|
||||
android:icon="@drawable/ic_download_24dp"
|
||||
android:iconTint="?attr/colorControlNormal"
|
||||
android:title="@string/media_viewer_save"
|
||||
app:showAsAction="ifRoom"
|
||||
tools:targetApi="o" />
|
||||
</menu>
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<style name="Theme.Revolt" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
<item name="textAppearanceTitleLarge">@style/TextAppearance.Revolt.TitleLarge</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Revolt.Starting" parent="Theme.SplashScreen" tools:targetApi="tiramisu">
|
||||
|
|
@ -12,4 +13,13 @@
|
|||
<item name="android:windowSplashScreenBehavior">icon_preferred</item>
|
||||
<item name="postSplashScreenTheme">@style/Theme.Revolt</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.Revolt.TitleLarge" parent="TextAppearance.Material3.TitleLarge">
|
||||
<item name="fontFamily">@font/inter_display_semibold</item>
|
||||
<item name="android:fontFamily">@font/inter_display_semibold</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Revolt.Toolbar" parent="Widget.Material3.Toolbar">
|
||||
<item name="titleTextAppearance">@style/TextAppearance.Revolt.TitleLarge</item>
|
||||
</style>
|
||||
</resources>
|
||||
Loading…
Reference in New Issue