我需要在我的 flutter 应用程序中使用 argora sdk 添加屏幕共享功能。 屏幕共享仅支持本机。但是不知道如何调用本机代码来颤动。 我试图从本机端启动屏幕共享服务,但是当服务启动时,视频视图被冻结。我试过下面的代码。
以下代码适用于安卓端:
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
.setMethodCallHandler(
(call, result) -> {
if (call.method.equals("shareScreen")) {
handler = new Handler(Looper.getMainLooper());
appId = call.argument("appId");
token = call.argument("token");
channelId = call.argument("ChannelId");
Log.d("AppId:" + appId, "Token:"+token);
IRtcEngineEventHandler iRtcEngineEventHandler = new IRtcEngineEventHandler() {
@Override
public void onWarning(int warn) {
Log.w(TAG, String.format("onWarning code %d message %s", warn, RtcEngine.getErrorDescription(warn)));
}
/**Reports an error during SDK runtime.
* Error code: https://docs.agora.io/en/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_error_code.html*/
@Override
public void onError(int err) {
Log.e(TAG, String.format("onError code %d message %s", err, RtcEngine.getErrorDescription(err)));
videoCallJoinMsg = String.format("onError code %d message %s", err, RtcEngine.getErrorDescription(err));
result.error("VIDEOCALL_ERROR", videoCallJoinMsg, null);
}
/**Occurs when the local user joins a specified channel.
* The channel name assignment is based on channelName specified in the joinChannel method.
* If the uid is not specified when joinChannel is called, the server automatically assigns a uid.
* @param channel Channel name
* @param uid User ID
* @param elapsed Time elapsed (ms) from the user calling joinChannel until this callback is triggered*/
@Override
public void onJoinChannelSuccess(String channel, int uid, int elapsed) {
Log.i(TAG, String.format("onJoinChannelSuccess channel %s uid %d", channel, uid));
videoCallJoinMsg = String.format("onJoinChannelSuccess channel %s uid %d", channel, uid);
myUid = uid;
joined = true;
handler.post(() -> {
videoCallJoinMsg = "Joined Successfully";
joined = true;
result.success(videoCallJoinMsg);
});
}
@Override
public void onLocalVideoStateChanged(int localVideoState, int error) {
super.onLocalVideoStateChanged(localVideoState, error);
if (localVideoState == 1) {
Log.e(TAG, "launch successfully");
}
}
@Override
public void onRemoteVideoStateChanged(int uid, int state, int reason, int elapsed) {
super.onRemoteVideoStateChanged(uid, state, reason, elapsed);
Log.i(TAG, "onRemoteVideoStateChanged:uid->" + uid + ", state->" + state);
if (state == REMOTE_VIDEO_STATE_STARTING) {
/*Check if the context is correct/
Context context = getApplicationContext();
if (context == null) {
return;
}
handler.post(() ->
{
remoteUid = uid;
curRenderMode = RENDER_MODE_HIDDEN;
// setRemotePreview(context);
});
}
}
@Override
public void onRemoteVideoStats(RemoteVideoStats stats) {
super.onRemoteVideoStats(stats);
Log.d(TAG, "onRemoteVideoStats: width:" + stats.width + " x height:" + stats.height);
}
/**Occurs when a remote user (Communication)/host (Live Broadcast) joins the channel.
* @param uid ID of the user whose audio state changes.
* @param elapsed Time delay (ms) from the local user calling joinChannel/setClientRole
* until this callback is triggered.*/
@Override
public void onUserJoined(int uid, int elapsed) {
super.onUserJoined(uid, elapsed);
Log.i(TAG, "onUserJoined->" + uid);
videoCallJoinMsg = String.format("user %d joined!", uid);
}
@Override
public void onUserOffline(int uid, int reason) {
Log.i(TAG, String.format("user %d offline! reason:%d", uid, reason));
videoCallJoinMsg = String.format("user %d offline! reason:%d", uid, reason);
handler.post(() -> {
/**Clear render view
Note: The video will stay at its last frame, to completely remove it you will need to
remove the SurfaceView from its parent*/
ENGINE.setupRemoteVideo(new VideoCanvas(null, RENDER_MODE_HIDDEN, uid));
// fl_remote.removeAllViews();
});
}
};
try {
ENGINE = RtcEngine.create(getApplicationContext(), appId, iRtcEngineEventHandler);
Log.d("RtcEngine:", "started");
}
catch (Exception e) {
e.printStackTrace();
Log.d("RtcEngine Error:", e.getMessage());
result.error("UNAVAILABLE", videoCallJoinMsg, null);
onBackPressed();
}
bindVideoService();
}
else {
result.notImplemented();
}
}
);
}
private void bindVideoService() {
Intent intent = new Intent();
intent.setClass(getApplicationContext(), ExternalVideoInputService.class);
mServiceConnection = new VideoInputServiceConnection();
getApplicationContext().bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
private void unbindVideoService() {
if (mServiceConnection != null) {
getApplicationContext().unbindService(mServiceConnection);
mServiceConnection = null;
}
}
private class VideoInputServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mService = (IExternalVideoInputService) iBinder;
/*Start the screen recording service of the system/
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
MediaProjectionManager mpm = (MediaProjectionManager)
getSystemService(Context.MEDIA_PROJECTION_SERVICE);
Intent intent = mpm.createScreenCaptureIntent();
startActivityForResult(intent, PROJECTION_REQ_CODE);
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
mService = null;
}
}
颤振侧代码:
class _State extends State<JoinChannelVideo> {
late final RtcEngine _engine;
String channelId = ChannelId;
bool isJoined = false, switchCamera = true, switchRender = true;
List<int> remoteUid = [];
TextEditingController? _controller;
static const platform = const MethodChannel("com.agora/videoCallScreenShareMethodChannel");
videoCallScreenShare() async{
try {
Map data = {
"appId" : appId,
"token" : token,
"ChannelId" : channelId
};
await _engine.stopPreview();
final result = await platform.invokeMethod('shareScreen',data);
print("Result: $result");
} on PlatformException catch (e) {
}
}
@override
void initState() {
super.initState();
_controller = TextEditingController(text: channelId);
this._initEngine();
}
@override
void dispose() {
super.dispose();
_engine.destroy();
}
_initEngine() async {
_engine = await RtcEngine.createWithConfig(RtcEngineConfig(appId));
this._addListeners();
await _engine.enableVideo();
await _engine.startPreview();
await _engine.setChannelProfile(ChannelProfile.LiveBroadcasting);
await _engine.setClientRole(ClientRole.Broadcaster);
}
_addListeners() {
_engine.setEventHandler(RtcEngineEventHandler(
joinChannelSuccess: (channel, uid, elapsed) {
print('joinChannelSuccess ${channel} ${uid} ${elapsed}');
ScaffoldMessenger.of(context).showSnackBar(new SnackBar(content: Text('Channel $channel Joined Successfully! ${uid} \n ${elapsed}', style: TextStyle(color: Colors.white),)));
setState(() {
isJoined = true;
});
},
userJoined: (uid, elapsed) {
print('userJoined ${uid} ${elapsed}');
ScaffoldMessenger.of(context).showSnackBar(new SnackBar(content: Text('User Joined: ${uid} \n ${elapsed}', style: TextStyle(color: Colors.white),)));
setState(() {
remoteUid.add(uid);
});
},
userOffline: (uid, reason) {
print('userOffline ${uid} ${reason}');
ScaffoldMessenger.of(context).showSnackBar(new SnackBar(content: Text('User Offline: ${uid} \n ${reason}', style: TextStyle(color: Colors.white),)));
setState(() {
remoteUid.removeWhere((element) => element == uid);
});
},
remoteVideoStats: (stats){
print('RemoteVideo Stats ${stats.toJson()}\n');
ScaffoldMessenger.of(context).showSnackBar(new SnackBar(content: Text('Remote Video Stats: ${stats.toJson()}', style: TextStyle(color: Colors.white),)));
setState(() {
});
},
remoteVideoStateChanged: (uid, reason, stats, num){
print('RemoteVideo Stats $uid, $reason, $stats, $num\n');
ScaffoldMessenger.of(context).showSnackBar(new SnackBar(content: Text('Remote Video Stats changed!', style: TextStyle(color: Colors.white),)));
},
leaveChannel: (stats) {
print('leaveChannel ${stats.toJson()}');
setState(() {
isJoined = false;
remoteUid.clear();
});
},
));
}
_joinChannel() async {
if (defaultTargetPlatform == TargetPlatform.android) {
await [Permission.microphone, Permission.camera].request();
}
await _engine.joinChannel(token, channelId, null, uid);
}
_leaveChannel() async {
await _engine.leaveChannel();
}
_switchCamera() {
_engine.switchCamera().then((value) {
setState(() {
switchCamera = !switchCamera;
});
}).catchError((err) {
print('switchCamera $err');
});
}
_switchRender() {
setState(() {
switchRender = !switchRender;
remoteUid = List.of(remoteUid.reversed);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('VideoView'),
),
body: Stack(
children: [
Column(
children: [
TextField(
controller: _controller,
decoration: InputDecoration(hintText: 'Channel ID'),
onChanged: (text) {
setState(() {
channelId = text;
});
},
),
Row(
children: [
Expanded(
flex: 1,
child: ElevatedButton(
onPressed:
isJoined ? this._leaveChannel : this._joinChannel,
child: Text('${isJoined ? 'Leave' : 'Join'} channel'),
),
)
],
),
_renderVideo(),
],
),
Align(
alignment: Alignment.centerRight,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
onPressed: this._switchCamera,
child: Text('Camera ${switchCamera ? 'front' : 'rear'}'),
),
ElevatedButton(
onPressed: videoCallScreenShare,
child: Text('ScreenShare'),
),
],
),
)
],
),
);
}
_renderVideo() {
return Expanded(
child: Stack(
children: [
RtcLocalView.SurfaceView(),
Align(
alignment: Alignment.bottomLeft,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: List.of(remoteUid.map(
(e) => GestureDetector(
onTap: this._switchRender,
child: Container(
width: 120,
height: 150,
margin: EdgeInsets.only(bottom: 50),
color: Colors.black,
child: RtcRemoteView.SurfaceView(
uid: e,
),
),
),
)),
),
),
)
],
),
);
}
}
有人可以帮忙吗?