Closed danilo1998271 closed 4 weeks ago
You should know what the screen share uid is, in your case, it's 1001.
Hi @littleGnAl , what about when we use one RtcEngine instance only to do this?
Below is the code where we can share screen track without joining as different user (1001).
await _engine.startScreenCapture(
const ScreenCaptureParameters2(captureAudio: true, captureVideo: true));
await _engine.startPreview(sourceType: VideoSourceType.videoSourceScreen);
await _engine.updateChannelMediaOptions(
const ChannelMediaOptions(
publishScreenTrack: true,
publishSecondaryScreenTrack: true,
publishCameraTrack: false,
publishMicrophoneTrack: false,
publishScreenCaptureAudio: true,
publishScreenCaptureVideo: true,
clientRoleType: ClientRoleType.clientRoleBroadcaster,
),
);
This shares the screen track and another remote user is able to view the screenshare track successfully when already joined inside that channel. But when that remote user leaves and rejoins the channel, this screenshare track does not load again. How can I solve this for remote user who joined after screenshare happened?
When using the below video view, screenshare track does not load for remote users when joining after screen is already shared by another user.
AgoraVideoView(
controller: VideoViewController.remote(
rtcEngine: _engine,
canvas: VideoCanvas(
uid: e,
sourceType: VideoSourceType.videoSourceScreen,
),
connection: RtcConnection(
channelId:rtcChannelName)
)),
The screen-sharing stream should not be published without calling joinChannel
, if it is published by only calling the updateChannelMediaOptions
, it should be a bug.
The screen-sharing stream should not be published without calling
joinChannel
, if it is published by only calling theupdateChannelMediaOptions
, it should be a bug.
Did you mean that we cannot join a channel first and then also, share screen later with same user without joining as another user?
So far, I have always seen Agora's flutter screen sharing examples always have an extra dedicated user joined for sharing screen only. Is that the only to share screen? Join an additional "screen share only" user always?
Without additional information, we are unfortunately not sure how to resolve this issue. We are therefore reluctantly going to close this bug for now. If you find this problem please file a new issue with the same description, what happens, logs and the output. All system setups can be slightly different so it's always better to open new issues and reference the related ones. Thanks for your contribution.
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please raise a new issue.
import 'package:agora_rtc_engine/agora_rtc_engine.dart'; import 'package:agora_rtc_engine_example/components/rgba_image.dart'; import 'package:agora_rtc_engine_example/components/basic_video_configuration_widget.dart'; import 'package:agora_rtc_engine_example/config/agora.config.dart' as config; import 'package:agora_rtc_engine_example/components/example_actions_widget.dart'; import 'package:agora_rtc_engine_example/components/log_sink.dart'; import 'package:agora_rtc_engine_example/components/remote_video_views_widget.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'dart:ui' as ui;
/// ScreenSharing Example class ScreenSharing extends StatefulWidget { /// Construct the [ScreenSharing] const ScreenSharing({Key? key}) : super(key: key);
@override State createState() => _State();
}
class _State extends State with KeepRemoteVideoViewsMixin {
late final RtcEngineEx _engine;
bool _isReadyPreview = false;
String channelId = config.channelId;
bool isJoined = false;
late TextEditingController _controller;
late final TextEditingController _localUidController;
late final TextEditingController _screenShareUidController;
bool _isScreenShared = false; late final RtcEngineEventHandler _rtcEngineEventHandler;
@override void initState() { super.initState(); _controller = TextEditingController(text: channelId); _localUidController = TextEditingController(text: '1000'); _screenShareUidController = TextEditingController(text: '1001'); _initEngine(); }
@override void dispose() { super.dispose(); _engine.unregisterEventHandler(_rtcEngineEventHandler); _engine.release(); }
_initEngine() async { _rtcEngineEventHandler = RtcEngineEventHandler( onError: (ErrorCodeType err, String msg) { logSink.log('[onError] err: $err, msg: $msg'); }, onJoinChannelSuccess: (RtcConnection connection, int elapsed) { logSink.log( '[onJoinChannelSuccess] connection: ${connection.toJson()} elapsed: $elapsed'); setState(() { isJoined = true; }); }, onLeaveChannel: (RtcConnection connection, RtcStats stats) { logSink.log( '[onLeaveChannel] connection: ${connection.toJson()} stats: ${stats.toJson()}'); setState(() { isJoined = false; }); }, onLocalVideoStateChanged: (VideoSourceType source, LocalVideoStreamState state, LocalVideoStreamReason error) { logSink.log( '[onLocalVideoStateChanged] source: $source, state: $state, error: $error'); if (!(source == VideoSourceType.videoSourceScreen || source == VideoSourceType.videoSourceScreenPrimary)) { return; }
}
void _joinChannel() async { final localUid = int.tryParse(_localUidController.text); if (localUid != null) { await _engine.joinChannelEx( token: '', connection: RtcConnection(channelId: _controller.text, localUid: localUid), options: const ChannelMediaOptions( autoSubscribeVideo: true, autoSubscribeAudio: true, publishCameraTrack: true, publishMicrophoneTrack: true, clientRoleType: ClientRoleType.clientRoleBroadcaster, )); }
}
Future _updateScreenShareChannelMediaOptions() async {
final shareShareUid = int.tryParse(_screenShareUidController.text);
if (shareShareUid == null) return;
await _engine.updateChannelMediaOptionsEx(
options: const ChannelMediaOptions(
publishScreenTrack: true,
publishSecondaryScreenTrack: true,
publishCameraTrack: false,
publishMicrophoneTrack: false,
publishScreenCaptureAudio: true,
publishScreenCaptureVideo: true,
clientRoleType: ClientRoleType.clientRoleBroadcaster,
),
connection:
RtcConnection(channelId: _controller.text, localUid: shareShareUid),
);
}
_leaveChannel() async { await _engine.stopScreenCapture(); await _engine.leaveChannel(); }
@override Widget build(BuildContext context) { return ExampleActionsWidget( displayContentBuilder: (context, isLayoutHorizontal) { if (!_isReadyPreview) return Container(); final children =[
Expanded(
flex: 1,
child: AspectRatio(
aspectRatio: 1,
child: AgoraVideoView(
controller: VideoViewController(
rtcEngine: _engine,
canvas: const VideoCanvas(
uid: 0,
),
)),
),
),
Expanded(
flex: 1,
child: AspectRatio(
aspectRatio: 1,
child: _isScreenShared
? AgoraVideoView(
controller: VideoViewController(
rtcEngine: _engine,
canvas: const VideoCanvas(
uid: 0,
sourceType: VideoSourceType.videoSourceScreen,
),
))
: Container(
color: Colors.grey[200],
child: const Center(
child: Text('Screen Sharing View'),
),
),
),
),
];
Widget localVideoView;
if (isLayoutHorizontal) {
localVideoView = Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: children,
);
} else {
localVideoView = Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: children,
);
}
return Stack(
children: [
localVideoView,
Align(
alignment: Alignment.topLeft,
child: RemoteVideoViewsWidget(
key: keepRemoteVideoViewsKey,
rtcEngine: _engine,
channelId: _controller.text,
connectionUid: int.tryParse(_localUidController.text),
),
)
],
);
},
actionsBuilder: (context, isLayoutHorizontal) {
if (!_isReadyPreview) return Container();
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: _controller,
decoration: const InputDecoration(hintText: 'Channel ID'),
),
TextField(
controller: _localUidController,
decoration: const InputDecoration(hintText: 'Local Uid'),
),
TextField(
controller: _screenShareUidController,
decoration: const InputDecoration(hintText: 'Screen Sharing Uid'),
),
const SizedBox(
height: 20,
),
BasicVideoConfigurationWidget(
rtcEngine: _engine,
title: 'Video Encoder Configuration',
setConfigButtonText: const Text(
'setVideoEncoderConfiguration',
style: TextStyle(fontSize: 10),
),
onConfigChanged: (width, height, frameRate, bitrate) {
_engine.setVideoEncoderConfiguration(VideoEncoderConfiguration(
dimensions: VideoDimensions(width: width, height: height),
frameRate: frameRate,
bitrate: bitrate,
));
},
),
const SizedBox(
height: 20,
),
Row(
children: [
Expanded(
flex: 1,
child: ElevatedButton(
onPressed: isJoined ? _leaveChannel : _joinChannel,
child: Text('${isJoined ? 'Leave' : 'Join'} channel'),
),
)
],
),
if (kIsWeb)
ScreenShareWeb(
rtcEngine: _engine,
isScreenShared: _isScreenShared,
onStartScreenShared: () {
if (isJoined) {
_updateScreenShareChannelMediaOptions();
}
},
onStopScreenShare: () {}),
if (!kIsWeb &&
(defaultTargetPlatform == TargetPlatform.android ||
defaultTargetPlatform == TargetPlatform.iOS))
ScreenShareMobile(
rtcEngine: _engine,
isScreenShared: _isScreenShared,
onStartScreenShared: () {
if (isJoined) {
_updateScreenShareChannelMediaOptions();
}
},
onStopScreenShare: () {}),
if (!kIsWeb &&
(defaultTargetPlatform == TargetPlatform.windows ||
defaultTargetPlatform == TargetPlatform.macOS))
ScreenShareDesktop(
rtcEngine: _engine,
isScreenShared: _isScreenShared,
onStartScreenShared: () {
if (isJoined) {
_updateScreenShareChannelMediaOptions();
}
},
onStopScreenShare: () {}),
],
);
},
);
}
}
class ScreenShareWeb extends StatefulWidget { const ScreenShareWeb( {Key? key, required this.rtcEngine, required this.isScreenShared, required this.onStartScreenShared, required this.onStopScreenShare}) : super(key: key);
final RtcEngine rtcEngine; final bool isScreenShared; final VoidCallback onStartScreenShared; final VoidCallback onStopScreenShare;
@override State createState() => _ScreenShareWebState();
}
class _ScreenShareWebState extends State
implements ScreenShareInterface {
@override
bool get isScreenShared => widget.isScreenShared;
@override void onStartScreenShared() { widget.onStartScreenShared(); }
@override void onStopScreenShare() { widget.onStopScreenShare(); }
@override RtcEngine get rtcEngine => widget.rtcEngine;
@override Widget build(BuildContext context) { return Row( children: [ Expanded( flex: 1, child: ElevatedButton( onPressed: !isScreenShared ? startScreenShare : stopScreenShare, child: Text('${isScreenShared ? 'Stop' : 'Start'} screen share'), ), ) ], ); }
@override void startScreenShare() async { if (isScreenShared) return;
}
@override void stopScreenShare() async { if (!isScreenShared) return;
} }
class ScreenShareMobile extends StatefulWidget { const ScreenShareMobile( {Key? key, required this.rtcEngine, required this.isScreenShared, required this.onStartScreenShared, required this.onStopScreenShare}) : super(key: key);
final RtcEngine rtcEngine; final bool isScreenShared; final VoidCallback onStartScreenShared; final VoidCallback onStopScreenShare;
@override State createState() => _ScreenShareMobileState();
}
class _ScreenShareMobileState extends State
implements ScreenShareInterface {
final MethodChannel _iosScreenShareChannel =
const MethodChannel('example_screensharing_ios');
@override bool get isScreenShared => widget.isScreenShared;
@override void onStartScreenShared() { widget.onStartScreenShared(); }
@override void onStopScreenShare() { widget.onStopScreenShare(); }
@override RtcEngine get rtcEngine => widget.rtcEngine;
@override Widget build(BuildContext context) { return Row( children: [ Expanded( flex: 1, child: ElevatedButton( onPressed: !isScreenShared ? startScreenShare : stopScreenShare, child: Text('${isScreenShared ? 'Stop' : 'Start'} screen share'), ), ) ], ); }
@override void startScreenShare() async { if (isScreenShared) return;
}
@override void stopScreenShare() async { if (!isScreenShared) return;
}
Future _showRPSystemBroadcastPickerViewIfNeed() async {
if (defaultTargetPlatform != TargetPlatform.iOS) {
return;
}
} }
class ScreenShareDesktop extends StatefulWidget { const ScreenShareDesktop( {Key? key, required this.rtcEngine, required this.isScreenShared, required this.onStartScreenShared, required this.onStopScreenShare}) : super(key: key);
final RtcEngine rtcEngine; final bool isScreenShared; final VoidCallback onStartScreenShared; final VoidCallback onStopScreenShare;
@override State createState() => _ScreenShareDesktopState();
}
class _ScreenShareDesktopState extends State
implements ScreenShareInterface {
List _screenCaptureSourceInfos = [];
late ScreenCaptureSourceInfo _selectedScreenCaptureSourceInfo;
@override bool get isScreenShared => widget.isScreenShared;
@override void onStartScreenShared() { widget.onStartScreenShared(); }
@override void onStopScreenShare() { widget.onStopScreenShare(); }
@override RtcEngine get rtcEngine => widget.rtcEngine;
Future _initScreenCaptureSourceInfos() async {
SIZE thumbSize = const SIZE(width: 50, height: 50);
SIZE iconSize = const SIZE(width: 50, height: 50);
_screenCaptureSourceInfos = await rtcEngine.getScreenCaptureSources(
thumbSize: thumbSize, iconSize: iconSize, includeScreen: true);
_selectedScreenCaptureSourceInfo = _screenCaptureSourceInfos[0];
setState(() {});
}
Widget _createDropdownButton() { if (_screenCaptureSourceInfos.isEmpty) return Container(); ui.PixelFormat format = ui.PixelFormat.rgba8888; if (defaultTargetPlatform == TargetPlatform.windows) { // The native sdk return the bgra format on Windows. format = ui.PixelFormat.bgra8888; } return DropdownButton(
items: _screenCaptureSourceInfos.map((info) {
Widget image;
if (info.iconImage!.width! != 0 && info.iconImage!.height! != 0) {
image = RgbaImage(
bytes: info.iconImage!.buffer!,
width: info.iconImage!.width!,
height: info.iconImage!.height!,
format: format,
);
} else if (info.thumbImage!.width! != 0 &&
info.thumbImage!.height! != 0) {
image = RgbaImage(
bytes: info.thumbImage!.buffer!,
width: info.thumbImage!.width!,
height: info.thumbImage!.height!,
format: format,
);
} else {
image = const SizedBox(
width: 50,
height: 50,
);
}
}
@override void initState() { super.initState();
}
@override Widget build(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ _createDropdownButton(), if (_screenCaptureSourceInfos.isNotEmpty) Row( children: [ Expanded( flex: 1, child: ElevatedButton( onPressed: !isScreenShared ? startScreenShare : stopScreenShare, child: Text('${isScreenShared ? 'Stop' : 'Start'} screen share'), ), ) ], ), ], ); }
@override void startScreenShare() async { if (isScreenShared) return;
}
@override void stopScreenShare() async { if (!isScreenShared) return;
} }
abstract class ScreenShareInterface { void onStartScreenShared();
void onStopScreenShare();
bool get isScreenShared;
RtcEngine get rtcEngine;
void startScreenShare();
void stopScreenShare(); }