Kotlin+协程+FLow+Channel+Compose 实现一个直播多个弹幕效果

Kotlin+协程+FLow+Channel+Compose 实现一个直播多个弹幕效果,原来如此简单

一、前言

相信做过直播的朋友,都对弹幕有一点了解。弹幕一般分两块:

  1. 第一块是直播服务器推送过来的弹幕,这些弹幕是其他人发送到直播服务器,直播服务器推送到客户端的,客户端需要展示。
  2. 第二块是本地发送弹幕,只要发送成功了,本地立马可以展示发送的这条弹幕,本地自己发送的弹幕是可以单独处理样式以来区分的。

本文我们来简单介绍下: 通过Kotlin+协程+FLow+Channel+Compose 实现一个直播多个弹幕效果的简单实现逻辑。 实现逻辑总共分为4个核心模块:

  1. 数据模型定义。
  2. 数据仓库管理
  3. 视图逻辑处理
  4. Compose实现UI渲染

下面我们来看怎么具体实现:

二、数据模型定义

如下代码:

  1. 数据模型定义主要包含:
  2. 弹幕ID
  3. 弹幕文字内容
  4. 弹幕颜色
  5. 弹幕字体大小
  6. 弹幕移动速度
  7. 弹幕初始在右侧X需要减去的偏移值
  8. 弹幕在Y坐标下显示的位置
  9. 用来区分本地弹幕和网络弹幕
kotlin 复制代码
data class DanmuItem(
    val id: Long = System.currentTimeMillis(),//弹幕ID
    val text: String,//弹幕文字内容
    val color: Color = randomColor(), //弹幕颜色随机
    val fontSize: TextUnit = (18 + Random.nextInt(16)).sp,//弹幕字体大小
    val speed: Float = 1f + Random.nextFloat() * 2f,
    val offsetX: Float = 1f, // 初始在右侧
    val offsetY: Float = Random.nextFloat() * 0.8f,
    val isLocal: Boolean = false
) {
    companion object {
        fun randomColor() = Color(
            red = Random.nextFloat(), green = Random.nextFloat(), blue = Random.nextFloat(), alpha = 1f
        )
    }
}

三、数据仓库管理

为了区分服务端弹幕和本地弹幕采用双Channel分别处理服务端和本地消息,合并流实现统一数据管道

kotlin 复制代码
class DanmuRepository {

    private val serverChannel = Channel<DanmuItem>(Channel.UNLIMITED)
    private val localChannel = Channel<DanmuItem>(Channel.UNLIMITED)

    suspend fun emitServerDanmu(item: DanmuItem) {
        serverChannel.send(item.copy(isLocal = false))
    }

    suspend fun emitLocalDanmu(item: DanmuItem) {
        localChannel.send(item.copy(isLocal = true))
    }


    fun getDanmuStream(): Flow<DanmuItem> {
        return merge(serverChannel.consumeAsFlow(), localChannel.consumeAsFlow())
    }
}

四、视图逻辑处理

这个在ViewModel层:

  1. 本Demo通过模拟服务端推送实现弹幕,本地消息通过用户输入触发。所有消息弹幕都具有随机位置、大小、颜色和速度特性,然后通过合并数据源通过Flow进行收集 ,通过StateFlow更新UI界面数据
  2. 采用定时60fps动画(协程里面: delay(16)) 引擎保证平滑移动
kotlin 复制代码
class DanmuViewModel : ViewModel() {
    private val repo = DanmuRepository()
    private val _danmus = MutableStateFlow<List<DanmuItem>>(emptyList())
    val danmus: StateFlow<List<DanmuItem>> = _danmus

    init {
        // 模拟服务端推送
        viewModelScope.launch {
            while (true) {
                delay((300 + Random.nextInt(100)).toLong())
                repo.emitServerDanmu(DanmuItem(text = randomDanmuText()))
            }
        }

        // 合并数据源
        viewModelScope.launch {
            repo.getDanmuStream().collect { newDanmu ->
                _danmus.update { it + newDanmu }
            }
        }

        // 弹幕动画
        viewModelScope.launch {
            while (true) {
                delay(16)
                _danmus.update { current ->
                    current.map {
                        it.copy(offsetX = it.offsetX - 0.002f * it.speed)
                    }.filter { it.offsetX > -0.5f }
                }
            }
        }
    }


    //发送本地弹幕
    fun sendLocalDanmu(text: String) {
        viewModelScope.launch {
            repo.emitLocalDanmu(DanmuItem(text = text))
        }
    }


    //模拟服务端弹幕集
    fun randomDanmuText(): String {
        val greetings = listOf("来了来了", "前排", "打卡", "签到", "第一!", "强到没朋友")
        val reactions = listOf("666", "太强了", "哈哈哈", "awsl", "好活", "泪目")
        val questions = listOf("有人吗?", "这是哪?", "主播多大了?", "几点开播?")
        val emotes = listOf("(≧∇≦)ノ", "(╯‵□′)╯", "╮(╯▽╰)╭", "(❤ ω ❤)", "( ̄▽ ̄*)ゞ")
        val memes = listOf("一键三连", "下次一定", "白嫖", "老板大气", "感谢飞机")

        return when ((1..5).random()) {
            1 -> greetings.random()
            2 -> reactions.random() + listOf("", "!!", "~").random()
            3 -> questions.random()
            4 -> emotes.random() + " " + reactions.random()
            else -> memes.random() + listOf("", "!", "!!!").random()
        }
    }
}

五、Compose实现UI渲染

  1. UI渲染,采用 Compose的Canvas的drawText方法来 绘制弹幕
  2. 弹幕左右平滑移动动画主要使用到drawText中Offset中的X值偏移,从绘制弹幕控件 宽度乘以动态偏移值比例(item.offsetX),这个值item.offsetX从1逐渐变到0,那么弹幕移动也就从右边逐渐移动到左边。
  3. 这里,如果我们想要实现视频加弹幕,或者直播画面加弹幕,只需要把直播或者视频显示的控件放到 BoxWithConstraints 里面就可以了。从它的名字上也可以看出,它内部实现的就是一个 Box控件 ,然后把其他控件包含在里面就可以了。其实我们不用它,自己来用 Box控件 来包起来也可以。
ini 复制代码
@Composable
fun DanmuScreen(viewModel: DanmuViewModel = DanmuViewModel()) {
    val danmus by viewModel.danmus.collectAsState()
    var inputText by remember { mutableStateOf("") }
    val textLayout = rememberTextMeasurer()

    BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
        Canvas(modifier = Modifier.fillMaxSize()) {
            danmus.forEach { item ->
                val text = textLayout.measure(
                    text = AnnotatedString(item.text), style = TextStyle(
                        color = item.color,
                        fontSize = item.fontSize,
                    )
                )
                drawText(text, topLeft = Offset(size.width * item.offsetX, size.height * item.offsetY))
            }
        }
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .align(Alignment.BottomCenter)
                .padding(16.dp)
        ) {
            OutlinedTextField(
                value = inputText, onValueChange = { inputText = it }, modifier = Modifier.fillMaxWidth()
            )
            Button(
                onClick = {
                    viewModel.sendLocalDanmu(inputText)
                    inputText = ""
                }) {
                Text("发送弹幕")
            }
        }
    }
}

六、总结

本文简单通过Kotlin+协程+FLow+Channel+Compose 实现一个直播多个弹幕效果:该实现包含四个关键设计

  1. 采用双Channel分别处理服务端和本地消息;
  2. 通过Koltin中的Flow来合并流实现统一数据管道;
  3. 通过 协程里面: delay(16) 来保证60fps动画引擎保证平滑移动3;
  4. 通过Compose的Canvas绘制来保证高性能渲染支持大量弹幕
  5. 其实很简单,没有什么复杂逻辑

感谢阅读:

欢迎用你发财的小手 关注,点赞、收藏

这里你会学到不一样的东西

相关推荐
jingling55512 小时前
Flutter | Dio网络请求实战
android·开发语言·前端·flutter
帅次12 小时前
讯飞与腾讯云:Android 实时语音识别服务对比选择
android·ios·微信小程序·小程序·android studio·android runtime
ST——Jess12 小时前
2026年度传统文化数字化与命理科技(Ethno-tech)行业趋势研究报告:专业级数智工作台的技术壁垒与评测标准
人工智能·科技·算法·架构
小马爱打代码12 小时前
TiDB 架构解析
架构
2601_9564141413 小时前
2026多账号防关联底层逻辑重构:主流指纹浏览器技术架构与高并发横测
重构·架构
jiayong2313 小时前
MySQL 排序规则冲突问题与 utf8mb4_general_ci 统一方案
android·mysql·ci/cd
ai产品老杨13 小时前
【架构实战】如何基于 Docker 与边缘计算构建企业级 AI 视频管理平台?打通 GB28181/RTSP 统一接入与异构算力调度,全量源码交付破解集成痛点
人工智能·docker·架构
意图共鸣13 小时前
意图共鸣科技《认知智能白皮书》——认知操作系统(COS):大模型之上的“认知中间件”如何调度边界
人工智能·科技·架构
段一凡-华北理工大学14 小时前
工业领域的Hadoop架构学习~系列文章02:HDFS架构深度剖析
大数据·人工智能·hadoop·学习·架构·高炉炼铁
随遇丿而安14 小时前
第6周:RecyclerView 真正难的不是“写个列表”,而是让列表在复用中保持正确
android