大牛直播 Android SDK 接入 Flutter Demo
实现裁图自定义渲染
gavin.chen 4e23716765 解决内存泄漏问题 | 1 rok temu | |
---|---|---|
.vscode | 1 rok temu | |
android | 1 rok temu | |
assets | 1 rok temu | |
ios | 1 rok temu | |
lib | 1 rok temu | |
test | 1 rok temu | |
web | 1 rok temu | |
.gitignore | 1 rok temu | |
.metadata | 1 rok temu | |
README.md | 1 rok temu | |
package.bat | 1 rok temu | |
pubspec.yaml | 1 rok temu |
flyinsono-min
确保 SmartPlayerJniV2.java 放到 com.daniulive.smartplayer 包名下(可在其他 包名下调用);
Smartavengine.jar
加入到工程;SmartPlayerV2\app\src\main\jniLibs\armeabi-v7a、SmartPlayerV2\app\src\main\jniLibs\arm64-v8a、SmartPlayerV2\app\src\main\jniLibs\x86
和SmartPlayerV2\app\src\main\jniLibs\x86_64
下 libSmartPlayer.so
到工程将Smartavengine.jar
添加到工程中,具体操作如下:
libs
的文件夹,将其放在您的Android原生项目的android\app
目录下;Smartavengine.jar
文件复制到刚创建的libs
文件夹;在android\app\build.gradle
文件中添加以下代码:
android {
//...
splits {
// Configures screen ABI split settings
abi {
// Enable ABI APK splits
enable true
// Resets the list of ABIs that Gradle should create APKs for to none
reset()
// Specifies a list of ABIs that Gradle should create APKs for
include 'armeabi-v7a', 'arm64-v8a' //select ABIs to build APKs for
// Specify that we do not want to also generate a universal APK that includes all ABIs
universalApk true
}
}
}
//...
dependencies {
//...
implementation files('libs/Smartavengine.jar')
}
在 android\app\src\main\AndroidManifest.xml
内增加
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
android\app\src\main\kotlin\com\vinno\flyinsono\RTMPPlugin.kt
下写入
这里面主要是将 Flutter 自定义渲染在 Player 初始化时传入 Player
package com.vinno.flyinsono
import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.EventChannel.EventSink;
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import com.videoengine.NTExternalRender;
import com.videoengine.NTRenderer;
import com.videoengine.NTSEIDataCallback;
import com.videoengine.NTUserDataCallback;
import java.nio.ByteBuffer;
import android.os.Handler
import android.os.Looper
import com.daniulive.smartplayer.SmartPlayer
import android.content.Context;
class RTMPPlugin : FlutterPlugin, MethodCallHandler {
private lateinit var methodChannel: MethodChannel
private lateinit var eventChannel: EventChannel
private lateinit var smartPlayer: SmartPlayer
private lateinit var eventSink: EventSink
private lateinit var flutterExternalRender: FlutterExternalRender
// private lateinit var currContext: Context
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
val currContext = flutterPluginBinding.applicationContext
/// Flutter 渲染器
flutterExternalRender = FlutterExternalRender()
smartPlayer = SmartPlayer(currContext, flutterExternalRender)
methodChannel = MethodChannel(flutterPluginBinding.binaryMessenger, CHANNEL_NAME)
methodChannel.setMethodCallHandler(this)
// 使用 EventChannel 添加回调函数,用于接收smartPlayer每一帧的图像数据
eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, EVENT_CHANNEL_NAME)
eventChannel.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, eventSink: EventSink) {
this@RTMPPlugin.eventSink = eventSink
println("⭐⭐⭐⭐ 设置事件监听 ⭐⭐⭐⭐")
}
override fun onCancel(arguments: Any?) {
// smartPlayer.setExternalRender(null)
}
})
}
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
when (call.method) {
"start" -> {
startRTMP()
result.success(null)
}
"stop" -> {
stopRTMP()
result.success(null)
}
else -> result.notImplemented()
}
}
private fun startRTMP() {
smartPlayer.onClickStart()
println("Simulating RTMPSDK.initialize()")
println("Simulating RTMPSDK.startStreaming()")
}
private fun stopRTMP() {
smartPlayer.onClickStop()
println("Simulating RTMPSDK.stopStreaming()")
println("Simulating RTMPSDK.release()")
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
methodChannel.setMethodCallHandler(null)
}
companion object {
private const val CHANNEL_NAME = "com.vinno.flyinsono.rtmp"
private const val EVENT_CHANNEL_NAME = "com.vinno.flyinsono.rtmp.event"
}
inner class FlutterExternalRender : NTExternalRender {
private var width_ = 0
private var height_ = 0
private var row_bytes_ = 0
private var rgba_buffer_: ByteBuffer? = null
override fun getNTFrameFormat(): Int {
return 1
}
override fun onNTFrameSizeChanged(width: Int, height: Int) {
width_ = width
height_ = height
row_bytes_ = width_ * 4
rgba_buffer_ = ByteBuffer.allocateDirect(row_bytes_ * height_)
println("onNTFrameSizeChanged" + width_ + " " + height_ + " " + row_bytes_ + "⭐")
}
override fun getNTPlaneByteBuffer(index: Int): ByteBuffer? {
if (index == 0) {
return rgba_buffer_;
} else {
return null
}
}
override fun getNTPlanePerRowBytes(index: Int): Int {
if (index == 0) {
return row_bytes_
} else {
return 0
}
}
override fun onNTRenderFrame(width: Int, height: Int, timestamp: Long) {
if (rgba_buffer_ == null) return
rgba_buffer_?.rewind()
val rgba = ByteArray(row_bytes_*height_)
rgba_buffer_?.get(rgba)
// 使用 Handler 在主线程中执行 eventSink.success 方法
Handler(Looper.getMainLooper()).post {
this@RTMPPlugin.eventSink.success(rgba)
}
}
}
}
android\app\src\main\kotlin\com\vinno\flyinsono\MainActivity.kt
内新增
...
import com.vinno.flyinsono.RTMPPlugin;
...
class MainActivity: FlutterActivity() {
...
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
...
flutterEngine.plugins.add(RTMPPlugin()) // 注册 RTMPPlugin
}
}
lib\views\temp\rtmp_android_method\rtmp_android.dart
内写入
import 'dart:async';
import 'package:flutter/services.dart';
class RTMPAndroidMethodChannel {
static const MethodChannel _methodChannel =
MethodChannel("com.vinno.flyinsono.rtmp");
static const EventChannel _eventChannel =
EventChannel('com.vinno.flyinsono.rtmp.event');
static Stream<List<int>>? _imageStream;
StreamController _streamController = StreamController();
static final RTMPAndroidMethodChannel _singleton =
RTMPAndroidMethodChannel._internal();
factory RTMPAndroidMethodChannel() {
return _singleton;
}
RTMPAndroidMethodChannel._internal() {
_init();
}
Future _onMethodCall(MethodCall call) async {
switch (call.method) {
case "onFrameData":
Uint8List frameData = call.arguments;
_streamController.add(frameData);
break;
default:
throw UnimplementedError("Method ${call.method} not implemented");
}
}
_init() async {
print("初始化移动端订阅");
_methodChannel.setMethodCallHandler(_onMethodCall);
_imageStream =
_eventChannel.receiveBroadcastStream().map((data) => data.cast<int>());
}
// 添加 start 和 stop 方法
Future start() async {
await _methodChannel.invokeMethod('start');
}
Future stop() async {
await _methodChannel.invokeMethod('stop');
}
static Stream<List<int>>? get imageStream => _imageStream;
}