Android Kotlin中Firebase Storage视频仅显URL无法播放求助
时间胶囊App视频播放问题解决方案
问题描述
开发一款时间胶囊类App,支持存储文本、图片和视频,内容仅在指定时间展示。目前文本和图片功能正常,但视频消息仅显示URL而非实际播放。已尝试使用VideoView等方法,问题仍未解决,需要实现Android Kotlin中流式播放Firebase Storage视频的功能。
现有Adapter代码
import android.net.Uri 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.example.timescape.Messages import com.example.timescape.R import com.google.android.exoplayer2.ExoPlayer import com.google.android.exoplayer2.MediaItem import com.google.android.exoplayer2.ui.PlayerView import com.squareup.picasso.Picasso import java.text.SimpleDateFormat import java.util.* class MessageAdapter(private val messages: List<Messages>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { private val TYPE_TEXT = 0 private val TYPE_IMAGE = 1 private val TYPE_VIDEO = 2 override fun getItemViewType(position: Int): Int { val message = messages[position] return when { message.mediaUrls.isNotEmpty() && message.mediaUrls[0].endsWith(".mp4") -> TYPE_VIDEO message.mediaUrls.isNotEmpty() -> TYPE_IMAGE else -> TYPE_TEXT } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return when (viewType) { TYPE_IMAGE -> { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_imgmsg, parent, false) ImageViewHolder(view) } TYPE_VIDEO -> { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_vidmsg, parent, false) VideoViewHolder(view) } else -> { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_textmsg, parent, false) TextViewHolder(view) } } } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val message = messages[position] val displayDateTime = Date(message.openDateTime) val currentDateTime = Date() if (currentDateTime.after(displayDateTime)) { when (holder) { is ImageViewHolder -> { Picasso.get().load(message.mediaUrls[0]).into(holder.imageView) holder.dateTimeView.text = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(Date(message.storedDateTime)) } is VideoViewHolder -> { holder.bind(message.mediaUrls[0]) holder.dateTimeView.text = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(Date(message.storedDateTime)) } is TextViewHolder -> { holder.textView.text = message.content holder.dateTimeView.text = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(Date(message.storedDateTime)) } } } else { val futureMessageText = "This message will be displayed on ${SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(displayDateTime)}" when (holder) { is ImageViewHolder -> { holder.imageView.setImageResource(R.drawable.baseline_image_24) holder.dateTimeView.text = futureMessageText } is VideoViewHolder -> { holder.itemView.setBackgroundResource(R.drawable.baseline_videocam_24) holder.dateTimeView.text = futureMessageText } is TextViewHolder -> { holder.textView.text = futureMessageText } } } } override fun getItemCount() = messages.size class ImageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val imageView: ImageView = itemView.findViewById(R.id.message_image) val dateTimeView: TextView = itemView.findViewById(R.id.message_date_time) } class VideoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { private val playerView: PlayerView = itemView.findViewById(R.id.player_view) val dateTimeView: TextView = itemView.findViewById(R.id.message_date_time) private var exoPlayer: ExoPlayer? = null fun bind(videoUrl: String) { if (exoPlayer == null) { exoPlayer = ExoPlayer.Builder(itemView.context).build() playerView.player = exoPlayer } val mediaItem = MediaItem.fromUri(Uri.parse(videoUrl)) exoPlayer?.setMediaItem(mediaItem) exoPlayer?.prepare() exoPlayer?.playWhenReady = true } fun releasePlayer() { exoPlayer?.release() exoPlayer = null } } class TextViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val textView: TextView = itemView.findViewById(R.id.message_text) val dateTimeView: TextView = itemView.findViewById(R.id.message_date_time) } override fun onViewRecycled(holder: RecyclerView.ViewHolder) { if (holder is VideoViewHolder) { holder.releasePlayer() } super.onViewRecycled(holder) } }
数据类代码
package com.example.timescape data class Messages( val id: String="", val content: String="", val openDateTime: Long=0L, val mediaUrls: List<String> = listOf(), var isRead: Boolean = false, var type: String = "text", val storedDateTime: Long = 0L )
解决方案
1. 确认Firebase Storage视频URL有效性
Firebase Storage的存储路径(如gs://bucket-name/path/video.mp4)无法直接用于播放,必须获取临时HTTPS下载URL:
val storageRef = FirebaseStorage.getInstance().getReferenceFromUrl("gs://your-bucket-name/video-path.mp4") storageRef.downloadUrl.addOnSuccessListener { uri -> val validDownloadUrl = uri.toString() // 将此URL存入Messages的mediaUrls字段 }
检查当前mediaUrls中的值是否为有效的HTTPS URL,而非存储路径或相对路径。
2. 修复VideoViewHolder绑定逻辑
优化播放器复用逻辑,避免状态残留:
fun bind(videoUrl: String) { if (exoPlayer == null) { exoPlayer = ExoPlayer.Builder(itemView.context).build() playerView.player = exoPlayer } else { exoPlayer?.stop() exoPlayer?.clearMediaItems() } val mediaItem = MediaItem.fromUri(Uri.parse(videoUrl)) exoPlayer?.addMediaItem(mediaItem) exoPlayer?.prepare() exoPlayer?.playWhenReady = true }
3. 检查视频项布局文件
确保item_vidmsg.xml中PlayerView布局正确,无遮挡且宽高合理:
<?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="8dp"> <com.google.android.exoplayer2.ui.PlayerView android:id="@+id/player_view" android:layout_width="match_parent" android:layout_height="200dp" /> <TextView android:id="@+id/message_date_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:textSize="12sp" /> </LinearLayout>
确认布局中没有额外显示URL的TextView组件。
4. 添加必要权限
在AndroidManifest.xml中添加网络权限:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- 可选,用于检测网络状态 -->
5. 优化未到展示时间的处理逻辑
在onBindViewHolder的else分支中,确保释放播放器并清除状态:
is VideoViewHolder -> { holder.releasePlayer() holder.playerView.setDefaultArtwork(null) holder.itemView.setBackgroundResource(R.drawable.baseline_videocam_24) holder.dateTimeView.text = futureMessageText }
内容的提问来源于stack exchange,提问作者Muskan Bandri
相关产品推荐
相关产品推荐

