Kuikly 基础之封装自定义音频播放模块

第八篇:Kuikly基础之封装自定义模块(音频)

之前为了让小青蛙能"呱"一声叫出来,我们走了条"野路子":用一个看不见的Video组件来播放音频。虽然能用,但总感觉有点"名不正言不顺",不够优雅,也埋下了隐患。

于是,我们决定对这个"野路子"进行改造,采用Kuikly的自定义模块机制,把它从一个"组件技巧"升级为一项独立的"模块能力"。整个过程不复杂,我们分三步走。

第一步:定义共享模块------AudioModule (shared)

首先,在所有平台都能访问的shared端,我们定义一个AudioModule。它负责定义接口,比如"播放"或"停止",但它自己不关心具体怎么实现。

kotlin 复制代码
// file: AudioModule.kt

package com.lifeiyu.singlefrog.modules
import com.tencent.kuikly.core.module.Module
import com.tencent.kuikly.core.nvi.serialization.json.JSONObject

/**
 * 音频模块的共享部分,定义了跨平台使用的接口。
 */
class AudioModule : Module() {
    // 模块的唯一标识,平台端靠它来识别
    override fun moduleName(): String { return MODULE_NAME }

    // 定义"播放"接口,通过 callNativeMethod 把任务派发给平台端
    fun playAsset(assetPath: String) {
        val p = JSONObject(); p.put("assetPath", assetPath)
        callNativeMethod("playAsset", p, null)
    }

    // 定义"停止"接口
    fun stop() { callNativeMethod("stop", null, null) }

    companion object {
        // 模块名,两端要保持一致,像一个约定好的暗号
        const val MODULE_NAME = "HRAudioModule"
    }
}

定义好后,得让每个页面都能用上它。所以我们在BasePager里把它注册成一个外部模块。

kotlin 复制代码
// file: BasePager.kt

// ...
import com.lifeiyu.singlefrog.modules.AudioModule

override fun createExternalModules(): Map<String, Module>? {
    val m = hashMapOf<String, Module>()
    m[BridgeModule.MODULE_NAME] = BridgeModule()
    // 把我们的音频模块也加进去
    m[AudioModule.MODULE_NAME] = AudioModule()
    return m
}

第二步:提供安卓端实现------KRAudioModule (Android)

shared端定义了接口,我们还需要在平台端提供具体的实现。在安卓这边,我们创建了KRAudioModule

它继承KuiklyRenderBaseModule,通过重写call方法来响应来自shared端的调用。

kotlin 复制代码
// file: KRAudioModule.kt

package com.lifeiyu.singlefrog.module
import android.media.MediaPlayer
import com.tencent.kuikly.core.render.android.export.KuiklyRenderBaseModule
// ...

/**
 * 音频模块在安卓平台的具体实现。
 */
class KRAudioModule : KuiklyRenderBaseModule() {
    private var player: MediaPlayer? = null

    // 在这里接收并处理来自shared端的调用
    override fun call(method: String, params: String?, cb: KuiklyRenderCallback?): Any? = when(method){
        "playAsset" -> { // 实现"播放"
            val path = JSONObject(params ?: "{}").optString("assetPath")
            val context = context ?: return null
            // 下面就是安卓原生播放音频的逻辑了
            val afd = context.assets.openFd(path)
            val mp = player ?: MediaPlayer().also { player = it }
            mp.reset()
            mp.setDataSource(afd.fileDescriptor, afd.startOffset, afd.length)
            afd.close()
            mp.isLooping = false
            mp.prepare()
            mp.start()
            null
        }
        "stop" -> { // 实现"停止"
            player?.let { if (it.isPlaying) it.stop(); it.reset() }
            null
        }
        else -> null
    }

    companion object {
        // 同样的模块名,确保能和shared端对上
        const val MODULE_NAME = "HRAudioModule"
    }
}

同样,这个实现也得在安卓项目里注册一下,这样系统才知道有这么个模块可用。

kotlin 复制代码
// file: KuiklyRenderActivity.kt

// ...
import com.lifeiyu.singlefrog.module.KRAudioModule

override fun registerExternalModule(kuiklyRenderExport: IKuiklyRenderExport) {
    // ...
    with(kuiklyRenderExport) {
        // ...
        // 在这里注册,告诉系统 HRAudioModule 这个名称由 KRAudioModule 来实现
        moduleExport(KRAudioModule.MODULE_NAME) { KRAudioModule() }
    }
}

第三步:在页面中使用模块

准备工作都做好了,现在回到我们的FrogMainPage,看看如何使用这个新模块。过程非常直接。

kotlin 复制代码
// file: FrogMainPage.kt

internal class FrogMainPage : BasePager() {
    // 声明一个变量来持有音频模块的实例
    private lateinit var audioModule: AudioModule

    override fun created() {
        super.created()
        // 页面创建时,通过 acquireModule 获取模块实例
        audioModule = acquireModule<AudioModule>(AudioModule.MODULE_NAME)
    }

    override fun body(): ViewBuilder {
        // ...
        event {
            click {
                // ...
                // 点击时,直接调用模块的接口
                audioModule.playAsset("audio/frog_sound.mp3")
            }
        }
        // ...
    }
}

现在,页面逻辑变得清爽多了。没有了隐藏的Video和复杂的playControl,只剩下一句清晰的方法调用。

小结:从"野路子"到"正规军"

这次重构,我们把音频播放从一个依赖UI组件的"野路子",转变成了一个独立的、平台无关的"模块能力"。这不仅仅是代码写法的改变,更是一种架构思维的进步。

  • 职责更清晰shared端定义能力,platform端负责实现,页面只管使用。
  • 代码更干净:页面不再关心音频是怎么播放的,耦合度大大降低。
  • 扩展性更强:未来想增加预加载、管理多个音效,都只需要在模块内部做文章,页面代码基本不用动。

现在,我们项目里那声清脆的"呱",背后有了一个更稳固的实现方式。感觉好多了!

相关推荐
小妖66637 分钟前
设置了 box-sizing: border-box; 不管用,下边框还是被挤压没了
前端·css·html
雪隐1 小时前
个人电脑玩AI-16让5060 Ti给你打工——5060Ti 16G 跑 MiniMax-Music-3:从下载到 60s 出歌的全流程
前端·人工智能·后端
CAD老兵2 小时前
让 AI Coding Agent 直接访问 CAD 文档:GitMCP 实战指南
前端·人工智能·github
程序员鱼皮3 小时前
DeepSeek Harness 最新邪修曝光!被吹成核弹的极简模式,真的夯吗?
前端·后端·ai编程
无己心3 小时前
面向多平台 AI 搜索引擎的适配层架构设计
前端·人工智能·搜索引擎·ai·1024程序员节
袅沫3 小时前
Nginx中部署多个前端项目——区分路径
服务器·前端
Asize3 小时前
为什么写代码前要先规划组件树?Next.js + Redis 笔记系统实战
前端·javascript·next.js
用户921080262863 小时前
6. 数据大屏 WebSocket 稳定性优化:心跳检测、断点续传和消息去重
前端
柚yuzumi3 小时前
CSS 定位布局:让元素各就各位
前端·css
光影少年3 小时前
react navite图片加载优化、大图卡顿、缓存策略
前端·react native·react.js