Skip to content

Unable to view video on TestFlight Build IOS #2285

Closed as not planned
Closed as not planned
@vishnu-launchpadapps

Description

@vishnu-launchpadapps

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

  1. Create token from backend
  2. Join video call
  3. Join channel
  4. Unable to view video

Expected results

  1. We should be able to view the video

Actual results

  1. 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]

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions