Android AppGlideModule,DataFetcher,ModelLoaderFactory,ModelLoader视频MediaMetadataRetriever解析Video封面失败后定制视频封面作为永久正式缓存,Kotlin(2)
plugins {
id 'org.jetbrains.kotlin.kapt'
}
implementation 'com.github.bumptech.glide:glide:4.16.0'
kapt 'com.github.bumptech.glide:compiler:4.16.0'
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
import android.content.Context
import android.os.Bundle
import android.provider.MediaStore
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recyclerView: RecyclerView = findViewById(R.id.recyclerview)
val layoutManager = GridLayoutManager(this, 5)
recyclerView.layoutManager = layoutManager
lifecycleScope.launch {
val list = readAllVideo(this@MainActivity)
var photoAdapter = VideoAdapter(this@MainActivity, list)
recyclerView.adapter = photoAdapter
}
}
private fun readAllVideo(context: Context): ArrayList<MyData> {
val videos = ArrayList<MyData>()
val cursor = context.contentResolver.query(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
null,
null,
null,
null
)
var index = 0
while (cursor!!.moveToNext()) {
//路径 uri
val path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA))
//名称
val name =
cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME))
//大小
val size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE))
videos.add(MyData(path, index++))
}
cursor.close()
return videos
}
}
class MyData(var path: String, val index: Int)
import android.content.Context
import android.util.Log
import com.bumptech.glide.Glide
import com.bumptech.glide.GlideBuilder
import com.bumptech.glide.Registry
import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.module.AppGlideModule
import java.io.InputStream
@GlideModule
class MyGlideModule : AppGlideModule() {
override fun applyOptions(context: Context, builder: GlideBuilder) {
super.applyOptions(context, builder)
builder.setLogLevel(Log.DEBUG)
}
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
super.registerComponents(context, glide, registry)
registry.append(
VideoCover::class.java,
InputStream::class.java,
VideoCoverLoaderFactory()
)
}
}
import android.content.Context
import android.graphics.drawable.Drawable
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
class VideoAdapter(private val context: Context, private val items: ArrayList<MyData>) :
RecyclerView.Adapter<MyViewHolder>() {
val TAG = "Glide/VideoAdapter"
private fun createView(parent: ViewGroup): View {
return LayoutInflater.from(parent.context).inflate(R.layout.item, parent, false)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(createView(parent))
}
override fun onBindViewHolder(holder: MyViewHolder, pos: Int) {
GlideApp.with(context)
.load(VideoCover(items[pos].path))
.override(150)
.addListener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>,
isFirstResource: Boolean
): Boolean {
//永远不会进入,因为VideoCoverFetcher的loadData里面总是onResourceReady
Log.d(TAG, "onLoadFailed")
return false
}
override fun onResourceReady(
resource: Drawable,
model: Any,
target: Target<Drawable>?,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
Log.d(TAG, "onResourceReady")
return false
}
})
.centerInside()
.into(holder.image)
holder.pos.text = "$pos"
holder.text.text = "-$pos-"
}
override fun getItemCount(): Int {
return items.size
}
}
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val image: ImageView = itemView.findViewById(R.id.image)
val pos: TextView = itemView.findViewById(R.id.pos)
val text: TextView = itemView.findViewById(R.id.text)
}
class VideoCover {
var path: String? = null
constructor(path: String) {
this.path = path
}
}
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.Bitmap.CompressFormat
import android.graphics.BitmapFactory
import android.media.MediaMetadataRetriever
import android.util.Log
import com.bumptech.glide.Priority
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.data.DataFetcher
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
class VideoCoverFetcher : DataFetcher<InputStream> {
val TAG = "Glide/VideoCoverFetcher"
private var model: VideoCover? = null
private val resId = android.R.drawable.stat_notify_error
constructor(model: VideoCover) {
this.model = model
}
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
Log.d(TAG, "loadData ${model?.path}")
var bmp: Bitmap? = getVideoThumbnail(model?.path)
if (bmp == null) {
Log.d(TAG, "loadData 使用默认的图")
//这张图将成为uri对应的正式的缓存图。
bmp = BitmapFactory.decodeResource(Resources.getSystem(), resId)
}
//无论如何都返回正确。
callback.onDataReady(ByteArrayInputStream(bitmapToByteArray(bmp!!)))
}
override fun cleanup() {
Log.d(TAG, "cleanup")
}
override fun cancel() {
Log.d(TAG, "cancel")
}
override fun getDataClass(): Class<InputStream> {
return InputStream::class.java
}
override fun getDataSource(): DataSource {
return DataSource.LOCAL
}
private fun bitmapToByteArray(bitmap: Bitmap): ByteArray {
val bos = ByteArrayOutputStream()
bitmap.compress(CompressFormat.PNG, 0, bos)
return bos.toByteArray()
}
private fun getVideoThumbnail(uriString: String?): Bitmap? {
val retriever = MediaMetadataRetriever()
try {
retriever.setDataSource(uriString)
return retriever.frameAtTime
} catch (e: Exception) {
Log.d(TAG, e.toString())
} finally {
retriever.release()
retriever.close()
}
return null
}
}
import android.util.Log
import com.bumptech.glide.load.model.ModelLoader
import com.bumptech.glide.load.model.ModelLoaderFactory
import com.bumptech.glide.load.model.MultiModelLoaderFactory
import java.io.InputStream
class VideoCoverLoaderFactory : ModelLoaderFactory<VideoCover, InputStream> {
val TAG = "Glide/VideoCoverLoaderFactory"
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<VideoCover, InputStream> {
return VideoCoverModuleLoader()
}
override fun teardown() {
Log.d(TAG, "teardown")
}
}
import android.util.Log
import com.bumptech.glide.load.Options
import com.bumptech.glide.load.model.ModelLoader
import com.bumptech.glide.load.model.ModelLoader.LoadData
import com.bumptech.glide.signature.ObjectKey
import java.io.InputStream
class VideoCoverModuleLoader : ModelLoader<VideoCover, InputStream> {
val TAG = "Glide/VideoCoverModuleLoader"
override fun buildLoadData(
model: VideoCover,
width: Int,
height: Int,
options: Options
): ModelLoader.LoadData<InputStream>? {
Log.d(TAG, "buildLoadData")
return LoadData(
VideoCoverSignature(model.path!!), //简单时候可以考虑ObjectKey(model.path!!)
VideoCoverFetcher(model)
)
}
override fun handles(model: VideoCover): Boolean {
return true
}
}
import com.bumptech.glide.load.Key
import java.security.MessageDigest
class VideoCoverSignature() : Key {
private var path: String? = null
constructor(path: String) : this() {
this.path = path
}
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
val ba: ByteArray = path?.toByteArray()!!
messageDigest.update(ba, 0, ba.size)
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" />
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="1dp">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="@drawable/ic_launcher_background" />
<TextView
android:id="@+id/pos"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="10dp" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="10dp" />
<View
android:layout_width="20dp"
android:layout_height="1px"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="10dp"
android:background="@color/cardview_dark_background" />
</LinearLayout>
Android自定义AppGlideModule,DataFetcher ,ModelLoaderFactory,ModelLoader,Kotlin(1)-CSDN博客假设实现一个简单的功能,对传入要加载的path路径增加一定的筛选、容错或“重定向”,需要自定义一个模型,基于这个模型,让Glide自动匹配模型展开加载。), //简单时候可以考虑ObjectKey(model.path!!= null!})!https://blog.csdn.net/zhangphil/article/details/133862536
Android读取设备所有Video视频,Kotlin-CSDN博客【Android设置头像,手机拍照或从本地相册选取图片作为头像】像微信、QQ、微博等社交类的APP,通常都有设置头像的功能,设置头像通常有两种方式:1,让用户通过选择本地相册之类的图片库中已有的图像,裁剪后作为头像。Android设置头像,手机拍照或从本地相册选取图片作为头像_android 头像拍照_zhangphil的博客-CSDN博客。Android图片添加文字水印并保存水印文字图片到指定文件_zhangphil的博客-CSDN博客。Android读取设备所有视频,Kotlin。https://blog.csdn.net/zhangphil/article/details/132173745