一、技术选型与源码架构设计
原生手机版本的碰一碰发视频功能需兼顾跨平台兼容性与硬件交互深度,推荐采用 "双原生 + 中间件" 架构:
开发维度 |
Android 方案 |
iOS 方案 |
跨平台中间件 |
核心语言 |
Kotlin 1.9 |
Swift 5.8 |
Rust(传输协议层) |
硬件交互层 |
NFC SDK + BLE 5.3 |
CoreNFC + CoreBluetooth |
自定义 AIDL 接口 |
视频处理 |
MediaCodec |
AVFoundation |
FFmpeg(跨平台编解码) |
通信协议 |
自定义 BLE Gatt Profile |
CoreBluetooth 协议栈 |
Protobuf(数据序列化) |
源码核心模块划分:
touch-video/
├── core/ # 核心SDK(NFC/BLE/视频处理)
├── ui/ # 界面组件(定制化入口)
├── protocol/ # 通信协议定义
├── bridge/ # 跨平台交互层
└── demo/ # 示例工程
二、源码搭建全流程(以 Android 为例)
1. 环境配置
// build.gradle 核心依赖
dependencies {
implementation 'androidx.nfc:nfc:1.3.0'
implementation 'com.polidea.rxandroidble3:rxandroidble:1.17.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
implementation 'com.google.code.gson:gson:2.10.1'
// 视频处理库
implementation 'com.google.android.exoplayer:exoplayer:2.19.1'
}
权限配置(AndroidManifest.xml):
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <!– BLE扫描需要 –>
<uses-feature android:name="android.hardware.nfc" android:required="true" />
2. 核心模块源码实现
(1)NFC 触碰唤醒模块
class NfcHandler(context: Context) {
private val nfcAdapter = NfcAdapter.getDefaultAdapter(context)
private val pendingIntent = PendingIntent.getActivity(
context, 0,
Intent(context, VideoTransferActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
PendingIntent.FLAG_IMMUTABLE
)
fun enableForegroundDispatch(activity: Activity) {
val techList = arrayOf(arrayOf(NfcA::class.java.name), arrayOf(Ndef::class.java.name))
nfcAdapter.enableForegroundDispatch(activity, pendingIntent, null, techList)
}
fun handleNfcIntent(intent: Intent): String? {
if (NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action) {
intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)?.let { messages ->
val ndefMessage = messages[0] as NdefMessage
val record = ndefMessage.records[0]
return String(record.payload, Charsets.UTF_8).substring(3) // 去除语言编码前缀
}
}
return null
}
}
(2)BLE 传输协议定制
class BleTransferManager(context: Context) {
private val bleClient = RxBleClient.create(context)
private var connection: Disposable? = null
private val videoChunkQueue = ConcurrentLinkedQueue<ByteArray>()
// 定制分包协议:包头(4字节长度)+包体+校验位(2字节CRC)
fun sendVideoFile(filePath: String, deviceAddress: String) {
val file = File(filePath)
val fileSize = file.length()
val chunkSize = 512 // 适配MTU大小动态调整
bleClient.getBleDevice(deviceAddress)
.establishConnection(false)
.flatMap { connection ->
this.connection = connection
// 发送文件元信息(文件名+大小)
val metaData = "${file.name}|$fileSize".toByteArray()
connection.writeCharacteristic(UUID.fromString(CHARACTERISTIC_UUID), metaData)
.flatMap {
// 分块发送视频数据
Observable.range(0, (fileSize / chunkSize).toInt() + 1)
.map { i ->
val offset = i * chunkSize.toLong()
val length = minOf(chunkSize.toLong(), fileSize – offset).toInt()
val buffer = ByteArray(length)
FileInputStream(file).channel.read(ByteBuffer.wrap(buffer), offset)
// 构建协议包
val packet = buildDataPacket(buffer)
packet
}
.flatMap { packet ->
connection.writeCharacteristic(UUID.fromString(CHARACTERISTIC_UUID), packet)
}
}
}
.subscribe()
}
private fun buildDataPacket(data: ByteArray): ByteArray {
val length = data.size
val header = ByteBuffer.allocate(4).putInt(length).array()
val crc = Crc16.calculate(data)
val crcBytes = ByteBuffer.allocate(2).putShort(crc).array()
return header + data + crcBytes
}
}
(3)视频处理引擎定制
class VideoProcessor {
// 定制视频压缩参数(分辨率/码率可配置)
fun compressVideo(inputPath: String, outputPath: String, config: VideoConfig): Boolean {
val builder = MediaCodecBuilder()
.setInputPath(inputPath)
.setOutputPath(outputPath)
.setResolution(config.width, config.height)
.setBitRate(config.bitRate)
.setFrameRate(config.frameRate)
// 支持水印/滤镜等定制化处理
if (config.watermarkPath.isNotEmpty()) {
builder.addWatermark(config.watermarkPath, config.watermarkPosition)
}
return builder.process()
}
// 预加载优化:生成缩略图与关键帧索引
fun generateVideoPreview(inputPath: String, previewPath: String): PreviewInfo {
val extractor = MediaExtractor()
extractor.setDataSource(inputPath)
// 提取关键帧生成预览图
// …
return PreviewInfo(thumbnailPath, keyFrameTimestamps)
}
}
// 可定制配置类
data class VideoConfig(
var width: Int = 1280,
var height: Int = 720,
var bitRate: Int = 2_000_000,
var frameRate: Int = 30,
var watermarkPath: String = "",
var watermarkPosition: Position = Position.BOTTOM_RIGHT
)
三、定制化开发核心维度
1. 硬件交互层定制
- 多协议适配:可集成 UWB 模块实现精确定位触发(适用于会议签到场景)
- 唤醒方式定制:支持 "碰一碰 + 摇一摇" 双重触发(源码修改 NfcHandler 的事件监听逻辑)
- 设备白名单:通过 MAC 地址过滤实现指定设备间通信(修改 BleTransferManager 的连接验证)
2. 传输策略定制
// 动态传输策略示例(根据场景切换)
class DynamicTransferStrategy {
fun getStrategy(scene: String): TransferStrategy {
return when (scene) {
"high_speed" -> HighSpeedStrategy() // 优先速度(大分包+不校验)
"stable" -> StableStrategy() // 优先稳定(小分包+重传机制)
"low_power" -> PowerSavingStrategy() // 低功耗(降低发射功率)
else -> DefaultStrategy()
}
}
}
3. 界面与交互定制
提供模块化 UI 组件库,支持:
- 自定义传输进度条样式(环形 / 线性 / 3D 动画)
- 定制化传输完成页(支持品牌 Logo 植入)
- 交互流程调整(跳过确认 / 强制横屏传输等)
4. 行业场景定制案例
- 零售场景:碰一碰传输商品视频介绍,集成二维码生成模块
- 教育场景:支持多设备同时接收教学视频,添加权限验证
- 展会场景:触碰后自动播放企业宣传片,集成统计分析模块
四、源码搭建与部署流程
# 克隆基础源码仓库
git clone https://github.com/touch-video/touch-video-android.git
# 初始化子模块(包含BLE协议库)
git submodule init && git submodule update
# 配置签名信息
cp config.example.gradle config.gradle
// 在app/build.gradle中配置定制参数
android {
defaultConfig {
buildConfigField "String", "APP_NAME", "\\"${customAppName}\\""
buildConfigField "boolean", "ENABLE_MULTI_TRANSFER", "${enableMultiTransfer}"
}
}
- 使用 NFC 读卡器写入测试数据
- 蓝牙调试工具监控传输包(推荐 nRF Connect)
- 视频传输压力测试:连续传输 100 个 50MB 文件验证稳定性
# 生成定制化APK
./gradlew assembleCustomRelease
# 生成OTA更新包
./gradlew bundleCustomRelease
五、定制化开发注意事项
-
- 测试不同 NFC 芯片型号(NXP PN548/Dialog DA14580)
-
- 适配 BLE 4.2/5.0/5.3 不同版本特性
-
- 视频传输时关闭后台应用(通过 ActivityManager)
-
- 实现传输线程优先级动态调整
-
- 传输内容加密(AES-128 CBC 模式)
-
- 设备配对采用 ECC 非对称加密验证
-
- 采用插件化架构便于功能模块更新
-
- 建立传输协议版本兼容机制
六、结语
碰一碰发视频功能的定制化开发核心在于平衡技术可行性与业务需求,通过本文提供的源码框架与定制化方案,开发者可快速搭建适应不同场景的应用。建议从实际业务场景出发,优先实现核心传输功能,再逐步叠加定制化模块。未来可扩展 AR 预览、AI 内容识别等高级功能,进一步提升产品竞争力。
附录:定制化开发资源
(全文完)
评论前必须登录!
注册