Mobile App Microphone Problem

I wanted to make video and non-video calls in my application. There is no problem in video calls, but the microphone does not physically open on both the video and non-video call pages. The green microphone emblem in the notification section on Android 12+ devices does not appear. I’m thinking of making this search system myself using socket_io and flutter_webrtc.What should I do?

CallScreen

import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:socket_io_client/socket_io_client.dart' as IO;
import 'package:permission_handler/permission_handler.dart';

class CallScreen extends StatefulWidget {
  final String callerId;
  final String callerName;
  final String receiverId;
  final String receiverName;
  final IO.Socket socket;
  final bool isCaller;

  const CallScreen({
    Key? key,
    required this.callerId,
    required this.callerName,
    required this.receiverId,
    required this.receiverName,
    required this.socket,
    required this.isCaller,
  }) : super(key: key);

  @override
  State<CallScreen> createState() => _CallScreenState();
}

class _CallScreenState extends State<CallScreen> {
  late IO.Socket socket;
  RTCPeerConnection? _peerConnection;
  MediaStream? _localStream;
  MediaStream? _remoteStream;
  final RTCVideoRenderer _localRenderer  = RTCVideoRenderer();
  final RTCVideoRenderer _remoteRenderer = RTCVideoRenderer();

  bool isMicOn     = true;
  bool isSpeakerOn = true;

  @override
  void initState() {
    super.initState();
    socket = widget.socket;
    _initCall();
  }

  Future<void> _initCall() async {
    await _checkMicPermissionBeforeCall();
    await _initRenderers();
    await _setupPeerConnection();
    debugPrint('✅ PeerConnection & local stream hazır');

    socket.emit('join', widget.callerId);
    socket.emit('join', widget.receiverId);
    _registerSocketHandlers();

    if (widget.isCaller) {
      debugPrint('🚀 Teklif (offer) oluşturuluyor…');
      await _startCall();
    }
  }

  Future<void> _checkMicPermissionBeforeCall() async {
    if (!await Permission.microphone.request().isGranted) {
      debugPrint('❌ Mikrofon izni aktif değil!');
      // Burada kullanıcıyı bilgilendiren bir dialog açabilirsiniz.
    } else {
      debugPrint('✅ Mikrofon izni aktif');
    }
  }

  Future<void> _initRenderers() async {
    await _localRenderer.initialize();
    await _remoteRenderer.initialize();
  }

  void _registerSocketHandlers() {
    socket.on('offer', _onOffer);
    socket.on('answer', _onAnswer);
    socket.on('accepted', _onAccepted);
    socket.on('ice-candidate', _onIceCandidate);
    socket.on('call-cancelled', _onCallCancelled);
  }

  void _unregisterSocketHandlers() {
    socket.off('offer', _onOffer);
    socket.off('answer', _onAnswer);
    socket.off('accepted', _onAccepted);
    socket.off('ice-candidate', _onIceCandidate);
    socket.off('call-cancelled', _onCallCancelled);
  }

  Future<void> _setupPeerConnection() async {
    // 1) Lokal audio stream
    _localStream = await navigator.mediaDevices.getUserMedia({
      'audio': true,
      'video': false,
    });
    _localRenderer.srcObject = _localStream;
    debugPrint('🎤 Lokal stream alındı');

    // 2) PeerConnection oluştur
    final config = {
      'iceServers': [
        {'urls': 'stun:stun.l.google.com:19302'},
        {
          'urls': 'turn:openrelay.metered.ca:80',
          'username': 'openrelayproject',
          'credential': 'openrelayproject'
        },
      ]
    };
    _peerConnection = await createPeerConnection(config);
    debugPrint('🔌 PeerConnection oluşturuldu');

    // 3) Track ekle
    for (var track in _localStream!.getTracks()) {
      await _peerConnection!.addTrack(track, _localStream!);
    }

    // 4) Remote track geldiğinde renderer’a ata
    _peerConnection!.onTrack = (RTCTrackEvent event) {
      if (event.streams.isNotEmpty) {
        setState(() {
          _remoteStream       = event.streams[0];
          _remoteRenderer.srcObject = _remoteStream;
        });
        debugPrint('📺 Remote stream geldi');
      }
    };

    // 5) ICE adaylarını signalling server’a gönder
    _peerConnection!.onIceCandidate = (RTCIceCandidate c) {
      debugPrint('❄️ ICE candidate: ${c.candidate}');
      socket.emit('ice-candidate', {
        'roomId': widget.isCaller ? widget.receiverId : widget.callerId,
        'candidate': {
          'candidate': c.candidate,
          'sdpMid': c.sdpMid,
          'sdpMLineIndex': c.sdpMLineIndex,
        },
      });
    };

    // Opsiyonel loglar
    _peerConnection!.onConnectionState      = (s) => debugPrint('📡 connection state: $s');
    _peerConnection!.onIceConnectionState   = (s) => debugPrint('❄️ ICE state: $s');
    _peerConnection!.onSignalingState       = (s) => debugPrint('📶 Signaling state: $s');
  }

  Future<void> _startCall() async {
    final offer = await _peerConnection!.createOffer();
    await _peerConnection!.setLocalDescription(offer);
    socket.emit('offer', {
      'roomId': widget.receiverId,
      'offer': {'sdp': offer.sdp, 'type': offer.type},
      'callerId': widget.callerId,
      'callerName': widget.callerName,
    });
    debugPrint('📤 Offer emit edildi');
  }

  Future<void> _onOffer(dynamic payload) async {
    if (widget.isCaller) return;
    final data = (payload is List && payload.isNotEmpty) ? payload[0] : payload;
    debugPrint('📥 Offer alındı: ${data['sdp']}');
    await _peerConnection!.setRemoteDescription(
      RTCSessionDescription(data['sdp'], data['type'])
    );
    final answer = await _peerConnection!.createAnswer();
    await _peerConnection!.setLocalDescription(answer);
    socket.emit('answer', {
      'roomId': widget.callerId,
      'answer': {'sdp': answer.sdp, 'type': answer.type},
    });
    socket.emit('accepted', widget.callerId);
    debugPrint('📤 Answer emit edildi');
  }

  Future<void> _onAnswer(dynamic payload) async {
    if (!widget.isCaller) return;
    final data = (payload is List && payload.isNotEmpty) ? payload[0] : payload;
    debugPrint('📥 Answer alındı: ${data['sdp']}');
    await _peerConnection!.setRemoteDescription(
      RTCSessionDescription(data['sdp'], data['type'])
    );
  }

  Future<void> _onAccepted(dynamic _) async {
    if (!widget.isCaller) return;
    debugPrint('📩 Karşı taraf kabul etti');
  }

  Future<void> _onIceCandidate(dynamic payload) async {
    final data = (payload is List && payload.isNotEmpty) ? payload[0] : payload;
    final cand = RTCIceCandidate(
      data['candidate'], data['sdpMid'], data['sdpMLineIndex']
    );
    await _peerConnection?.addCandidate(cand);
    debugPrint('❄️ ICE candidate eklendi');
  }

  Future<void> _onCallCancelled(dynamic _) async {
    // Çağrı iptal edildi, temizleyip başa dön
    _cleanupAndExit();
  }

  void _endCall() {
    socket.emit(
      'cancel-call',
      widget.isCaller ? widget.receiverId : widget.callerId,
    );
    _cleanupAndExit();
  }

  void _cleanupAndExit() {
    // 1) Stream ve PeerConnection’ı durdur / kapat
    _localStream?.getTracks().forEach((t) => t.stop());
    _peerConnection?.close();

    // 2) Navigation: en baştaki route’a dön
    if (mounted) {
      Navigator.of(context).popUntil((route) => route.isFirst);
    }
  }

  @override
  void dispose() {
    _unregisterSocketHandlers();
    _localStream?.dispose();
    _remoteStream?.dispose();
    _peerConnection?.dispose();
    _localRenderer.dispose();
    _remoteRenderer.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final otherName = widget.isCaller ? widget.receiverName : widget.callerName;
    return Scaffold(
      backgroundColor: Colors.black87,
      body: SafeArea(
        child: Column(
          children: [
            SizedBox(height: 10),
            Expanded(child: RTCVideoView(_remoteRenderer)),
            SizedBox(height: 10),
            SizedBox(
              height: 120,
              child: RTCVideoView(_localRenderer, mirror: true),
            ),
            SizedBox(height: 20),
            Text(
              '$otherName ile görüşülüyor...',
              style: TextStyle(color: Colors.white, fontSize: 20),
            ),
            Spacer(),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                // Mikrofon toggle
                IconButton(
                  icon: Icon(
                    isMicOn ? Icons.mic : Icons.mic_off,
                    color: Colors.white,
                  ),
                  onPressed: () {
                    setState(() => isMicOn = !isMicOn);
                    final audioTrack = _localStream?.getAudioTracks().first;
                    if (audioTrack != null) {
                      audioTrack.enabled = isMicOn;
                      debugPrint(
                        '🎙 Mikrofon şimdi ${isMicOn ? "açık" : "kapalı"}'
                      );
                    }
                  },
                ),
                // Çağrıyı bitir
                IconButton(
                  icon: Icon(Icons.call_end, color: Colors.red),
                  onPressed: _endCall,
                ),
                // Hoparlör toggle
                IconButton(
                  icon: Icon(
                    isSpeakerOn ? Icons.volume_up : Icons.hearing,
                    color: Colors.white,
                  ),
                  onPressed: () {
                    setState(() => isSpeakerOn = !isSpeakerOn);
                    Helper.setSpeakerphoneOn(isSpeakerOn);
                  },
                ),
              ],
            ),
            SizedBox(height: 40),
          ],
        ),
      ),
    );
  }
} ElevatedButton.icon(
                              icon: const Icon(Icons.call),
                              label: const Text('Ara'),
                              onPressed: () async {
                                final prefs = await SharedPreferences.getInstance();
                                final vid    = prefs.getString('user_id') ?? '';
                                final vname  = prefs.getString('user_name') ?? '';
                                if (vid.isEmpty || vname.isEmpty) {
                                  ScaffoldMessenger.of(context).showSnackBar(
                                    const SnackBar(
                                      content: Text(
                                          'Kullanıcı bilgileri eksik. Giriş yapın.'),
                                    ),
                                  );
                                  return;
                                }
                                final socket = IO.io(
                                  'http://192-----',
                                  IO.OptionBuilder()
                                      .setTransports(['websocket'])
                                      .disableAutoConnect()
                                      .build(),
                                );
                                socket.connect();
                                Navigator.push(
                                  context,
                                  MaterialPageRoute(
                                    builder: (_) => CallScreen(
                                      callerId:    vid,
                                      callerName:  vname,
                                      receiverId:  _selectedGuide!['_id'] as String,
                                      receiverName: _selectedGuide!['name'] as String,
                                      socket:      socket,
                                      isCaller:    true,
                                    ),
                                  ),
                                );
                              },

SignalingService

import 'package:socket_io_client/socket_io_client.dart' as IO;
import 'package:flutter_application_1/services/api.dart';

class SignalingService {
  late IO.Socket _socket;

  void connect() {
    _socket = IO.io(
      Api.socketUrl,
      IO.OptionBuilder()
          .setTransports(['websocket'])
          .disableAutoConnect()
          .build(),
    );

    _socket.connect();

    _socket.onConnect((_) {
      print('✅ WebSocket bağlandı: ${_socket.id}');
    });

    _socket.onDisconnect((_) {
      print('🔌 WebSocket bağlantısı kesildi');
    });
  }

  void joinRoom(String roomId) {
    _socket.emit('join', roomId);
  }

  void sendOffer(String roomId, dynamic offer) {
    _socket.emit('offer', {'roomId': roomId, 'offer': offer});
  }

  void sendAnswer(String roomId, dynamic answer) {
    _socket.emit('answer', {'roomId': roomId, 'answer': answer});
  }

  void sendCandidate(String roomId, dynamic candidate) {
    _socket.emit('ice-candidate', {'roomId': roomId, 'candidate': candidate});
  }

  void onOffer(Function(dynamic offer) callback) {
    _socket.on('offer', callback);
  }

  void onAnswer(Function(dynamic answer) callback) {
    _socket.on('answer', callback);
  }

  void onCandidate(Function(dynamic candidate) callback) {
    _socket.on('ice-candidate', callback);
  }

  void disconnect() {
    _socket.disconnect();
  }
}

IncomingCallScreen

import 'package:flutter/material.dart';
import 'package:flutter_application_1/call_screen.dart';
// import 'package:flutter_application_1/utils/audio_service.dart';  // <<< SES KALDIRILDI
import 'package:socket_io_client/socket_io_client.dart' as IO;

class IncomingCallScreen extends StatefulWidget {
  final String callerId;
  final String callerName;
  final String receiverId;
  final String receiverName;
  final IO.Socket socket;

  const IncomingCallScreen({
    Key? key,
    required this.callerId,
    required this.callerName,
    required this.receiverId,
    required this.receiverName,
    required this.socket,
  }) : super(key: key);

  @override
  State<IncomingCallScreen> createState() => _IncomingCallScreenState();
}

class _IncomingCallScreenState extends State<IncomingCallScreen> {
  @override
  void initState() {
    super.initState();
    // AudioService.playIncoming();  // <<< SES KALDIRILDI
  }

  void _acceptCall() {
    // AudioService.stopIncoming();  // <<< SES KALDIRILDI
    widget.socket.emit('accepted', widget.callerId);

    if (mounted) {
      Navigator.of(context).pushReplacement(
        MaterialPageRoute(
          builder: (_) => CallScreen(
            callerId: widget.callerId,
            callerName: widget.callerName,
            receiverId: widget.receiverId,
            receiverName: widget.receiverName,
            socket: widget.socket,
            isCaller: false,
          ),
        ),
      );
    }
  }

  void _rejectCall() {
    // AudioService.stopIncoming();  // <<< SES KALDIRILDI
    widget.socket.emit('cancel-call', widget.callerId);
    if (mounted) Navigator.of(context).pop();
  }

  @override
  void dispose() {
    // AudioService.stopIncoming();  // <<< SES KALDIRILDI
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black87,
      body: SafeArea(
        child: Column(
          children: [
            const Spacer(),
            const Icon(Icons.call, size: 100, color: Colors.greenAccent),
            const SizedBox(height: 20),
            Text(
              '${widget.callerName} sizi arıyor...',
              style: const TextStyle(color: Colors.white, fontSize: 24),
            ),
            const Spacer(),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton.icon(
                  style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
                  icon: const Icon(Icons.call_end),
                  label: const Text('Reddet'),
                  onPressed: _rejectCall,
                ),
                ElevatedButton.icon(
                  style:
                      ElevatedButton.styleFrom(backgroundColor: Colors.green),
                  icon: const Icon(Icons.call),
                  label: const Text('Kabul Et'),
                  onPressed: _acceptCall,
                ),
              ],
            ),
            const SizedBox(height: 40),
          ],
        ),
      ),
    );
  }
} 

MainPageGuide

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:geolocator/geolocator.dart';
import 'package:socket_io_client/socket_io_client.dart' as IO;

import 'package:flutter_application_1/incoming_call_screen.dart';
import 'package:flutter_application_1/incoming_video_call.dart';
import 'package:flutter_application_1/guide/statistic_page.dart';
import 'package:flutter_application_1/guide/meetings.dart';
import 'package:flutter_application_1/guide/notifications_page.dart';
import 'package:flutter_application_1/guide/profile_page.dart';
import 'package:flutter_application_1/services/guide_service.dart';

class MainPageGuide extends StatefulWidget {
  const MainPageGuide({Key? key}) : super(key: key);

  @override
  State<MainPageGuide> createState() => _MainPageGuideState();
}

class _MainPageGuideState extends State<MainPageGuide>
    with WidgetsBindingObserver {
  int _currentIndex = 0;
  String? _email, _name, _id;
  Position? _currentPosition;
  Timer? _locationUpdateTimer;
  IO.Socket? _socket;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _loadSharedData();
    _startLocationUpdates();
  }

  Future<void> _loadSharedData() async {
    final prefs = await SharedPreferences.getInstance();
    _email = prefs.getString('user_email');
    _name  = prefs.getString('user_name');
    _id    = prefs.getString('user_id');

    if (_id != null)   _initSocket(_id!);
    if (_email != null) await _fetchLocation();
  }

  void _initSocket(String guideId) {
    _socket = IO.io('http://192----', <String, dynamic>{
      'transports': ['websocket'],
      'autoConnect': true,
    });

    _socket!.onConnect((_) {
      print('📡 Bağlandı (guide $guideId)');
      _socket!.emit('join', guideId);
    });

    // SESLİ çağrı
    _socket!.on('incoming-call', (data) {
      final callerId   = data['callerId']   as String?;
      final callerName = data['callerName'] as String?;
      if (callerId != null && callerName != null) {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (_) => IncomingCallScreen(
              callerId: callerId,
              callerName: callerName,
              receiverId: guideId,
              receiverName: _name ?? '',
              socket: _socket!,
            ),
          ),
        );
      }
    });

    // GÖRÜNTÜLÜ çağrı
    _socket!.on('incoming-video-call', (data) {
      final callerId   = data['callerId']   as String?;
      final callerName = data['callerName'] as String?;
      if (callerId != null && callerName != null) {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (_) => IncomingVideoCallScreen(
              callerId: callerId,
              callerName: callerName,
              receiverId: guideId,
              receiverName: _name ?? '',
              socket: _socket!,
            ),
          ),
        );
      }
    });

    _socket!.onDisconnect((_) => print('🔌 Bağlantı koptu'));
  }

  void _startLocationUpdates() {
    _locationUpdateTimer =
        Timer.periodic(const Duration(seconds: 30), (_) => _fetchLocation());
  }

  Future<void> _fetchLocation() async {
    if (_email == null) return;
    try {
      final pos = await Geolocator.getCurrentPosition();
      setState(() => _currentPosition = pos);
      await updateGuideLocation(
        email:     _email!,
        latitude:  pos.latitude,
        longitude: pos.longitude,
        isOnline:  true,
      );
    } catch (e) {
      print('Konum alınamadı: $e');
    }
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (_email == null) return;
    final offline = state == AppLifecycleState.paused ||
                    state == AppLifecycleState.detached;
    final online  = state == AppLifecycleState.resumed;
    if (offline) {
      updateGuideLocation(
        email:     _email!,
        latitude:  0,
        longitude: 0,
        isOnline:  false,
      );
    } else if (online && _currentPosition != null) {
      updateGuideLocation(
        email:     _email!,
        latitude:  _currentPosition!.latitude,
        longitude: _currentPosition!.longitude,
        isOnline:  true,
      );
    }
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    _locationUpdateTimer?.cancel();
    _socket?.disconnect();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final pages = [
      const StatisticsPage(),
      MeetingsPage(guideId: _id ?? '', guideName: _name ?? ''),
      const NotificationsPage(),
      const ProfilePage(),
    ];

    return Scaffold(
      body: pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        currentIndex:      _currentIndex,
        selectedItemColor: Colors.blue,
        unselectedItemColor: Colors.grey,
        onTap: (i) => setState(() => _currentIndex = i),
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.bar_chart),
            label: 'İstatistiklerim',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.chat),
            label: 'Görüşmelerim',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.notifications),
            label: 'Bildirimler',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: 'Profilim',
          ),
        ],
      ),
    );
  }
}

android manifest

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.flutter_application_1">

    <uses-feature
        android:glEsVersion="0x00020000"
        android:required="true"/>

    <!-- GEREKLİ İZİNLER -->
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
    <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

    <application
        android:label="flutter_application_1"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher"
        android:requestLegacyExternalStorage="true"
        android:usesCleartextTraffic="true">
        <service
            android:name="io.flutter.plugins.webrtc.WebRTCForegroundService"
            android:exported="false"
            android:foregroundServiceType="microphone|camera"/>

        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTop"
            android:taskAffinity=""
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:enableOnBackInvokedCallback="true"
            android:windowSoftInputMode="adjustResize">

            <meta-data
                android:name="io.flutter.embedding.android.NormalTheme"
                android:resource="@style/NormalTheme"/>

            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>

        </activity>

        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />

        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="" />
        
        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />
        <meta-data
            android:name="io.flutter.embedding.android.EnableImpeller"
            android:value="false"/>    

    </application>

    <queries>
        <intent>
            <action android:name="android.intent.action.PROCESS_TEXT"/>
            <data android:mimeType="text/plain"/>
        </intent>
    </queries>
</manifest>