Skip to content

Unable to view video on TestFlight Build IOS #2285

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
1 of 5 tasks
vishnu-launchpadapps opened this issue Apr 23, 2025 · 1 comment
Closed
1 of 5 tasks

Unable to view video on TestFlight Build IOS #2285

vishnu-launchpadapps opened this issue Apr 23, 2025 · 1 comment

Comments

@vishnu-launchpadapps
Copy link

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]
@peilinok
Copy link
Contributor

@vishnu-launchpadapps

To properly investigate this issue, we need to collect sensitive information that shouldn't be discussed in a public forum.

Please submit a ticket through Agora Support for a detailed investigation. Once you have any findings or solutions, feel free to share them here to help other developers. Thank you for your understanding!

@peilinok peilinok closed this as not planned Won't fix, can't repro, duplicate, stale Apr 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants