Description
Version of the agora_rtc_engine
6.3.2
Unable to video on TestFlight build IOS
Its working perfectly fine locally and on android but IOS when pushed to Testflight unable to view the video
Platforms affected
- Android
- iOS
- macOS
- Windows
- Web
Steps to reproduce
- Create token from backend
- Join video call
- Join channel
- Unable to view video
Expected results
- We should be able to view the video
Actual results
- Currently could not see the current user or remote user video
Code sample
Code sample
[Paste your code here]
Screenshots or Video
Screenshots / Video demonstration
`import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:ollie/bloc/agoraBloc/agora_bloc.dart';
import 'package:ollie/bloc/bookAppointmentBloc/bookAppointment_bloc.dart';
import 'package:ollie/constants/agora_constants.dart';
import 'package:ollie/constants/app_styles.dart';
import 'package:ollie/repository/user_repository.dart';
import 'package:ollie/routes/route_names.dart';
import 'package:ollie/services/agora_service.dart';
import 'package:ollie/services/agora_user.dart';
import 'package:ollie/utils/app_helpers.dart';
import 'package:ollie/utils/design_system_size_config.dart';
import 'package:ollie/views/booking/join_telehealth_video/message_stream_service.dart';
import 'package:ollie/views/booking/join_telehealth_video/trascript_view_state.dart';
import 'package:ollie/views/inbox/direct_message/chat_screen_view_state.dart';
import '/flutter_flow/flutter_flow_theme.dart';
import '/flutter_flow/flutter_flow_util.dart';
import '../../../proto/SttMessage.pb.dart' as proto;
import 'join_telehealth_video_model.dart';
export 'join_telehealth_video_model.dart';
class JoinTelehealthVideoWidget extends StatefulWidget {
final String channelName;
final String rtcToken;
final bool isMicEnabled;
final bool isVideoEnabled;
const JoinTelehealthVideoWidget({
super.key,
required this.channelName,
required this.rtcToken,
this.isMicEnabled = false,
this.isVideoEnabled = false,
});
@OverRide
State createState() =>
_JoinTelehealthVideoWidgetState();
}
class _JoinTelehealthVideoWidgetState extends State {
late final AgoraService _agoraService;
late JoinTelehealthVideoModel _model;
late final RtcEngine _agoraEngine;
final Set _users = {};
late double _viewAspectRatio;
// StreamController for broadcasting messages
int? _currentUid;
bool _isMicEnabled = false;
bool _isVideoEnabled = false;
@OverRide
void initState() {
super.initState();
_model = createModel(context, () => JoinTelehealthVideoModel());
// _agoraService = AgoraService.instance;
// _model.isMicMuted = widget.isMuted;
// _model.isVideoEnabled = widget.isVideoEnabled;
// // Agora Event Handlers
// _agoraService.onRemoteUserJoined = (int remoteUid) {
// setState(() => _model.remoteUid = remoteUid);
// };
// _agoraService.onRemoteUserVideoPaused = (bool isMuted) {
// log("Remote user video muted: $isMuted");
// setState(() => _model.isRemoteVideoEnabled = !isMuted);
// };
// _agoraService.onRemoteUserOffline = (int remoteUid) {
// setState(() {
// _model.remoteUid = null;
// _model.isRemoteVideoEnabled = false;
// });
// };
// _initializeAndJoinChannel();
_initialize();
createDataStream();
}
Future createDataStream() async {
await _agoraEngine.createDataStream(
DataStreamConfig(syncWithAudio: true, ordered: true),
);
}
Future _initialize() async {
_viewAspectRatio = kIsWeb
? 3 / 2
: (Platform.isAndroid || Platform.isIOS)
? 2 / 3
: 3 / 2;
_isMicEnabled = widget.isMicEnabled;
_isVideoEnabled = widget.isVideoEnabled;
await _initAgoraRtcEngine();
_addAgoraEventHandlers();
final userId = UserRepository().userData.id;
await _agoraEngine.joinChannel(
token: widget.rtcToken,
channelId: widget.channelName,
uid: userId,
options: const ChannelMediaOptions(
autoSubscribeVideo:
true, // Automatically subscribe to all video streams
autoSubscribeAudio:
true, // Automatically subscribe to all audio streams
publishCameraTrack: true, // Publish camera-captured video
publishMicrophoneTrack: true,
),
);
}
Future _initAgoraRtcEngine() async {
_agoraEngine = createAgoraRtcEngine();
await _agoraEngine.initialize(
RtcEngineContext(
appId: AgoraConstants.appId,
channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
audioScenario: AudioScenarioType
.audioScenarioGameStreaming, // Improves iOS audio performance
),
);
await _agoraEngine.enableAudio();
await _agoraEngine.enableVideo();
await _agoraEngine.startPreview();
await _agoraEngine.setClientRole(
role: ClientRoleType.clientRoleBroadcaster);
await _agoraEngine.muteLocalAudioStream(!_isMicEnabled);
await _agoraEngine.muteLocalVideoStream(!_isVideoEnabled);
}
void _addAgoraEventHandlers() {
_agoraEngine.registerEventHandler(
RtcEngineEventHandler(
onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
log('✅ Joined channel: ${connection.channelId}');
print('✅ Joined channel: ${connection.channelId}');
// setState(() {
// _currentUid = connection.localUid;
// });
print('✅ Joined channel: current user Id ${connection.localUid}');
},
onStreamMessage:
(connection, remoteUid, streamId, data, length, sentTs) {
messageStreamService.onStreamMessage(
connection, remoteUid, streamId, data, length, sentTs);
// var message = proto.Text.fromBuffer(data);
// log("message: ${message.words}");
// _messageStreamController.add(TrascriptViewState(
// message: message.words
// .where((item) => item.isFinal == true && item.text.isNotEmpty)
// .first
// .text,
// userId: message.uid.toString()));
},
onUserMuteVideo: (connection, remoteUid, muted) {
log('📹 Remote user video muted: $remoteUid - $muted');
setState(() {
_model.isRemoteVideoEnabled = !muted;
});
},
onUserMuteAudio: (connection, remoteUid, muted) {
log('🔇 Remote user audio muted: $remoteUid - $muted');
},
onUserJoined: (RtcConnection connection, int uid, int elapsed) {
log('👤 Remote user joined: $uid');
if (uid >= 20000000) {
return;
}
setState(() {
_users.add(AgoraUser(
uid: uid,
view: AgoraVideoView(
controller: VideoViewController.remote(
rtcEngine: _agoraEngine,
canvas: VideoCanvas(uid: uid),
connection: RtcConnection(channelId: widget.channelName),
),
)));
});
},
onUserOffline:
(RtcConnection connection, int uid, UserOfflineReasonType reason) {
setState(() {
_users.removeWhere((user) => user.uid == uid);
});
},
),
);
}
Future _onCallEnd(BuildContext context) async {
await _agoraEngine.leaveChannel();
if (context.mounted) Navigator.of(context).pop();
}
Future _onToggleAudio() async {
_isMicEnabled = !_isMicEnabled;
await _agoraEngine.muteLocalAudioStream(!_isMicEnabled);
setState(() {});
}
Future _onToggleCamera() async {
_isVideoEnabled = !_isVideoEnabled;
await _agoraEngine.muteLocalVideoStream(!_isVideoEnabled);
setState(() {});
}
Future _onSwitchCamera() async {
await _agoraEngine.switchCamera();
}
/// Initialize and Join Channel
Future _initializeAndJoinChannel() async {
try {
await _agoraService.initialize(
uid: 0,
isMicEnabled: _model.isMicMuted,
isVideoEnabled: _model.isVideoEnabled);
await _agoraService.setAsHost();
await _agoraService.startPreview();
if (mounted) {
setState(() => _model.isAgoraInitialized = true);
final userID = UserRepository().userData.id;
await _agoraService.joinChannel(
widget.rtcToken, widget.channelName, userID);
}
} catch (e) {
log('Error initializing Agora: $e');
}
}
/// Toggle Microphone
void _toggleMic() async {
if (_model.isAgoraInitialized) {
setState(() => _model.isMicMuted = !_model.isMicMuted);
await _agoraService.muteAudio(_model.isMicMuted);
}
}
/// Toggle Video
void _toggleVideo() async {
if (_model.isAgoraInitialized) {
setState(() => _model.isVideoEnabled = !_model.isVideoEnabled);
await _agoraService.muteVideo(!_model.isVideoEnabled);
}
}
Future _disposeAgora() async {
await _agoraEngine.leaveChannel();
await _agoraEngine.release();
}
@OverRide
void dispose() {
_users.clear();
_disposeAgora();
super.dispose();
}
@OverRide
Widget build(BuildContext context) {
final doctorId = context
.watch()
.bookingDetailsViewState
?.doctorId ??
0;
final doctorName = context
.watch()
.bookingDetailsViewState
?.doctorName ??
'';
return Scaffold(
backgroundColor: FlutterFlowTheme.of(context).primaryBackground,
body: Stack(
children: [
/// **Remote Video View**
Positioned.fill(
child: _users.isNotEmpty
? _model.isRemoteVideoEnabled
? _users.first.view ?? Container()
: _buildRemoteUserPlaceHolder(
doctorName,
)
: _buildWaitingForUser(),
),
/// **Bottom Action Buttons**
_buildBottomControls(),
/// **Local Video View (Top Right)**
_buildLocalVideoView(),
],
),
);
}
/// Waiting Placeholder
Widget _buildWaitingForUser() {
return Center(
child: Text(
'Waiting for remote user...',
style: AppStyles.primaryBold(24),
),
);
}
/// Bottom Control Buttons
Widget _buildBottomControls() {
return Positioned(
bottom: 20,
left: 0,
right: 0,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
/// End Call Button
_buildControlButton(Icons.call_end, Colors.red, () {
_onCallEnd(context);
Navigator.pop(context);
}),
/// **Toggle Mic Button**
_buildControlButton(
_isMicEnabled ? Icons.mic : Icons.mic_off,
Colors.black,
_onToggleAudio,
),
/// **Toggle Video Button**
_buildControlButton(
_isVideoEnabled ? Icons.videocam : Icons.videocam_off,
Colors.black,
_onToggleCamera,
),
/// **More Options (Transcript, In-call Chat)**
_buildMoreOptions(),
],
),
),
);
}
/// Build Control Button
Widget _buildControlButton(IconData icon, Color color, VoidCallback onTap) {
return CircleAvatar(
radius: 28,
backgroundColor: FlutterFlowTheme.of(context).secondaryBackground,
child: IconButton(icon: Icon(icon, color: color), onPressed: onTap),
);
}
/// More Options (Transcript, Chat)
Widget _buildMoreOptions() {
return CircleAvatar(
radius: 28,
backgroundColor: FlutterFlowTheme.of(context).secondaryBackground,
child: PopupMenuButton(
icon: const Icon(Icons.more_vert, color: Colors.black),
onSelected: (value) => _handleMoreOptions(value),
itemBuilder: (context) => [
_buildPopupMenuItem('Transcript', Icons.notes),
_buildPopupMenuItem('In-call chat', Icons.chat),
],
),
);
}
/// Popup Menu Item
PopupMenuItem _buildPopupMenuItem(String text, IconData icon) {
return PopupMenuItem(
value: text,
child: Row(children: [
Icon(icon, color: Colors.black),
const SizedBox(width: 8),
Text(text)
]),
);
}
/// Handle More Options
void _handleMoreOptions(String value) {
if (value == 'Transcript') {
context.pushNamed(RouteNames.transcriptionView,
extra: widget.channelName);
} else if (value == 'In-call chat') {
context.read().add(InitializeAgoraChatServerEvent());
context.pushNamed(
RouteNames.inCallChat,
extra: ChatScreenViewState(
chatId: '',
fullName: context
.read()
.bookingDetailsViewState
?.doctorName ??
'',
channelName: widget.channelName,
profilePic: '',
userId: context
.read()
.bookingDetailsViewState
?.doctorId ??
0,
),
);
}
}
/// Local Video View (Top Right)
Widget _buildLocalVideoView() {
return Align(
alignment: Alignment.topRight,
child: Container(
width: DesignSystemSizeConfig.width(120),
margin: const EdgeInsets.only(top: 60, right: 20),
height: DesignSystemSizeConfig.height(150),
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(8.0),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: _isVideoEnabled
? AgoraVideoView(
controller: VideoViewController(
rtcEngine: _agoraEngine,
canvas: VideoCanvas(uid: _currentUid ?? 0),
),
onAgoraVideoViewCreated: (viewId) {
log('Local video view created: $viewId');
_agoraEngine.startPreview();
},
)
: _buildProfilePlaceholder(),
),
),
);
}
/// Profile Placeholder
Widget _buildProfilePlaceholder() {
return Center(
child: CircleAvatar(
backgroundColor: Colors.white,
radius: 30,
child: Text(getInitials(UserRepository().userData.firstName ?? '',
UserRepository().userData.lastName ?? '')),
),
);
}
/// Remote Profile Placeholder
Widget _buildRemoteUserPlaceHolder(String name) {
return Center(
child: CircleAvatar(
backgroundColor: Colors.white,
radius: 30,
child: Text(getInitialsFromFullName(name)),
),
);
}
}
`
Logs
Logs
[Paste your logs here]
Flutter Doctor output
Doctor output
[Paste your output here]