Обмен WebRTC ICE и SDP прошел успешно, но соединение PeerConnection зависло в новом состоянииAndroid

Форум для тех, кто программирует под Android
Ответить Пред. темаСлед. тема
Anonymous
 Обмен WebRTC ICE и SDP прошел успешно, но соединение PeerConnection зависло в новом состоянии

Сообщение Anonymous »

Я создаю приложение для голосовых/видеозвонков на Android с использованием WebRTC, где кандидаты SDP и ICE обмениваются через сервер WebSocket. Поток кандидатов SDP и ICE между двумя пользователями (A и B) выглядит правильно в журналах WebSocket, но соединение, похоже, не устанавливается полностью.Журнал Websocket

Код: Выделить всё

User = A Send User B =>> offer of SDP
User = A Send User B =>> offer of ICE
User = A Send User B =>> offer of ICE
User = A Send User B =>> offer of ICE
User = A Send User B =>> offer of ICE
User = A Send User B =>> offer of ICE
User = A Send User B =>> offer of ICE
User = A Send User B =>> offer of ICE
User = A Send User B =>> offer of ICE
User = B Send User A =>> answer of SDP
User = B Send User A =>> answer of ICE
User = B Send User A =>> answer of ICE
User = B Send User A =>> answer of ICE
User = B Send User A =>> answer of ICE
User = B Send User A =>> answer of ICE
User = B Send User A =>> answer of ICE
User = B Send User A =>> answer of ICE
User = B Send User A =>> answer of ICE
Однако в журналах Android состояние соединения не меняется должным образом после завершения обмена ICE. Соединение остается в следующих состояниях на неопределенный срок:

Код: Выделить всё

Log.d(TAG, "Connection State: " + peerConnection.connectionState());  // Shows "New"
Log.d(TAG, "ICE Connection State: " + peerConnection.iceConnectionState());  // Shows "Checking"
Log.d(TAG, "ICE Gathering State: " + peerConnection.iceGatheringState());   // Shows "Gathering"
Фрагмент моего вызова.

Код: Выделить всё

public class Video_Voice_Call_Fragment extends Fragment {

private static final String TAG = "Call_Fragment";
private static final int PERMISSION_REQUEST_CODE = 1;
public static final int VIDEO_RESOLUTION_WIDTH = 1280;
public static final int VIDEO_RESOLUTION_HEIGHT = 720;
public static final int FPS = 30;

private EglBase rootEglBase;
private PeerConnectionFactory peerConnectionFactory;
private VideoCapturer videoCapturer;
private VideoSource videoSource;
private VideoTrack localVideoTrack;
private AudioTrack localAudioTrack;
private SurfaceViewRenderer localVideoView;
private SurfaceTextureHelper surfaceTextureHelper;
private SurfaceViewRenderer remoteVideoView;
private ImageButton greenCallButton;
private ImageButton redCallButton;
private MediaStream localMediaStream;
OkHttpClient client;
PeerConnection peerConnection,peerConnectionRemote;
private String FCMToken_Other;

TextView CallStatus;
ImageView UserImage;
boolean isCaller = true;
boolean isVoiceCall;

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_video_voice_call, container, false);
}

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
localVideoView = view.findViewById(R.id.call_remoteSurfaceView);
greenCallButton = view.findViewById(R.id.call_connect);
redCallButton = view.findViewById(R.id.call_disconnect);
if (isCaller){
ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) redCallButton.getLayoutParams();
params.startToStart = ConstraintSet.PARENT_ID;
params.setMarginEnd(0);
redCallButton.setLayoutParams(params);
greenCallButton.setVisibility(View.GONE);
}
UserImage = view.findViewById(R.id.call_UserImage);
CallStatus = view.findViewById(R.id.call_Status);
TextView CallName = view.findViewById(R.id.call_callerName);

isVoiceCall = getArguments().getBoolean("VoiceCall");
String ImagePath = getArguments().getString("image");
String Name = getArguments().getString("name");
FCMToken_Other = getArguments().getString("fcm");
int OtherUserID = getArguments().getInt("OtherID");
Session.setOtherUserID(OtherUserID);
Glide.with(getActivity()).load(ImagePath).into(UserImage);
CallStatus.setText("Connecting...");
CallName.setText(Name);

client = new OkHttpClient();

initializePeerConnectionFactory(isVoiceCall);
setupLocalTracks(isVoiceCall);
setupUI(view);
startCall(isVoiceCall);
}

private void initializePeerConnectionFactory(boolean isVoiceCall) {
// Initialize EGL context for rendering
rootEglBase = EglBase.create();

// Initialize PeerConnectionFactory
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions.builder(requireContext())
.setEnableInternalTracer(true)
.createInitializationOptions());

PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
PeerConnectionFactory.Builder builder = PeerConnectionFactory.builder();
options.networkIgnoreMask = 16;
options.disableNetworkMonitor = true;

if (!isVoiceCall){
//Video Call
DefaultVideoEncoderFactory videoEncoderFactory = new DefaultVideoEncoderFactory(rootEglBase.getEglBaseContext(),true,true);
DefaultVideoDecoderFactory videoDecoderFactory = new DefaultVideoDecoderFactory(rootEglBase.getEglBaseContext());
builder.setVideoEncoderFactory(videoEncoderFactory);
builder.setVideoDecoderFactory(videoDecoderFactory);
//Set EGL context
localVideoView.init(rootEglBase.getEglBaseContext(),  null);
localVideoView.setMirror(true);
getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}

peerConnectionFactory = builder.setOptions(options).createPeerConnectionFactory();

}

private void setupLocalTracks(boolean isVoiceCall) {
localMediaStream = peerConnectionFactory.createLocalMediaStream("local_stream");
if (!isVoiceCall){
// Create video capturer
videoCapturer = createVideoCapturer();
// Create video source
videoSource = peerConnectionFactory.createVideoSource(false);
localVideoTrack = peerConnectionFactory.createVideoTrack("local_video", videoSource);
surfaceTextureHelper = SurfaceTextureHelper.create("SurfaceHelper",rootEglBase.getEglBaseContext());
videoCapturer.initialize(surfaceTextureHelper,getActivity(),videoSource.getCapturerObserver());
if (videoCapturer!=null){
videoCapturer.startCapture(VIDEO_RESOLUTION_WIDTH,VIDEO_RESOLUTION_HEIGHT,FPS);
localVideoTrack.addSink(localVideoView);
}
localMediaStream.addTrack(localVideoTrack);
}

// Create audio source and track
AudioSource audioSource = peerConnectionFactory.createAudioSource(new MediaConstraints());
localAudioTrack = peerConnectionFactory.createAudioTrack("local_audio", audioSource);
// Create local media stream
localMediaStream.addTrack(localAudioTrack);

}

private VideoCapturer createVideoCapturer() {
VideoCapturer videoCapturer;
CameraEnumerator enumerator = new Camera2Enumerator(requireContext());

final String[] deviceNames = enumerator.getDeviceNames();

// Find front facing camera
for (String deviceName : deviceNames) {
if (enumerator.isFrontFacing(deviceName)) {
videoCapturer = enumerator.createCapturer(deviceName, null);
if (videoCapturer != null) {
return videoCapturer;
}
}
}

// Front facing camera not found, use the first available
if (deviceNames.length > 0) {
return enumerator.createCapturer(deviceNames[0], null);
} else {
return null;
}
}

private void startCall(boolean isVoiceCall) {

Log.d(TAG, "Starting call...");

// Create an offer and set local description
peerConnection = InitializePeerConnection();

if (!isVoiceCall){
peerConnection.addTrack(localVideoTrack);
}

peerConnection.addTrack(localAudioTrack);

peerConnection.createOffer(new SimpleSdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
Log.d(TAG, "onCreateSuccess: "  + sessionDescription.description);

Websocket.connectWebSocket(new WebSocketCallback() {
@Override
public void onConnectionSuccess() {
//Websocket Connected
Log.d(TAG, "onConnectionSuccess: Websocket Connected");
String message = generateJSON(sessionDescription.description,true,"offer");
peerConnection.setLocalDescription(new SimpleSdpObserver(), sessionDescription);
Websocket.sendMessage(message);
}

@Override
public void onConnectionFailure(String message) {
Log.d(TAG, "onConnectionFailure: Message = "+message);
}

@Override
public void onConnectionClosed(String reason) {
Log.d(TAG, "onConnectionClosed: Reason = "+reason);
}

@Override
public void onConnectionsMessage(String message) {
Log.d(TAG, "onConnectionsMessage: Message = "+message);
// Here User will receive the message from the websocket
try{
JSONObject socketMessage = new JSONObject(message);
String type = socketMessage.getString("type");
Log.d(TAG, "onConnectionsMessage: Type = "+type);
String sdp_ice_VALUE = null ;
if (socketMessage.has("SDP_ICE_info")){
sdp_ice_VALUE = socketMessage.getString("SDP_ICE_info");
}

switch (type) {
case "SDP":
Log.d(TAG, "onConnectionsMessage: Type SDP");
SessionDescription sessionDescription = new SessionDescription(SessionDescription.Type.ANSWER, sdp_ice_VALUE);
peerConnection.setRemoteDescription(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
Log.d(TAG, "onCreateSuccess: ");
}

@Override
public void onSetSuccess() {

Log.d(TAG, "onSetSuccess: Gathering state = " + peerConnection.iceGatheringState());
Log.d(TAG, "onSetSuccess: Connection State = " + peerConnection.iceConnectionState());

}

@Override
public void onCreateFailure(String s) {
Log.d(TAG, "onCreateFailure: " + s);
}

@Override
public void onSetFailure(String s) {
Log.d(TAG, "onSetFailure: "  + s);
}
}, sessionDescription);
//Generate ICE and Send it User B

break;
case "Offline":
Log.d(TAG, "onConnectionsMessage: OFFLINE");
DefaultExecutorSupplier.getInstance().forMainThreadTasks().execute(new Runnable() {
@Override
public void run() {
Toast.makeText(getActivity(), "User Is Not Available", Toast.LENGTH_SHORT).show();
CallStatus.setText("Offline");
closeConnection();
}
});

break;

case "ICE":
// Received ICE from server as answer, This is the last step in exchanging info
Log.d(TAG, "onConnectionsMessage: Message Recevied ICE  = " + type);
addIceCandidates(sdp_ice_VALUE);
break;
}
}catch (JSONException e){
Log.d(TAG, "onMessage: Error "+e.getMessage());
}
}
});

}

@Override
public void onCreateFailure(String s) {
Log.e(TAG, "onCreateFailure: " + s);
}
}, new MediaConstraints());
}

private PeerConnection InitializePeerConnection(){
ArrayList
 iceServers = new ArrayList();
List  STUNList= Arrays.asList(
"stun:stun.l.google.com:19302",
"stun:stun1.l.google.com:19302",
"stun:stun2.l.google.com:19302",
"stun:stun3.l.google.com:19302",
"stun:stun4.l.google.com:19302");
for(String i:STUNList){
PeerConnection.IceServer.Builder iceServerBuilder = PeerConnection.IceServer.builder(i);
//iceServerBuilder.setTlsCertPolicy(PeerConnection.TlsCertPolicy.TLS_CERT_POLICY_INSECURE_NO_CHECK);
iceServerBuilder.setTlsCertPolicy(PeerConnection.TlsCertPolicy.TLS_CERT_POLICY_SECURE);
PeerConnection.IceServer iceServer =  iceServerBuilder.createIceServer();
iceServers.add(iceServer);
}
PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(iceServers);

PeerConnection.Observer observer = new PeerConnection.Observer() {
@Override
public void onSignalingChange(PeerConnection.SignalingState signalingState) {
Log.d(TAG, "onSignalingChange: "+signalingState);
}

@Override
public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
Log.d(TAG, "onIceConnectionChange: "+iceConnectionState);
if (PeerConnection.IceConnectionState.CONNECTED == iceConnectionState){
Log.d(TAG, "onIceConnectionChange: Connection Established");
}
}

@Override
public void onIceConnectionReceivingChange(boolean b) {
Log.d(TAG, "onIceConnectionReceivingChange: Boolean = "+b);
}

@Override
public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
Log.d(TAG, "onIceGatheringChange: "+iceGatheringState);
}

@Override
public void onIceCandidate(IceCandidate iceCandidate) {
Log.d(TAG, "onIceCandidate: 458 "+Session.getUserID());
String ice = generateJSON(iceCandidate.toString(),false,"offer");
Websocket.sendMessage(ice);
}

@Override
public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {
Log.d(TAG, "onIceCandidatesRemoved: "+iceCandidates);
}

@Override
public void onAddStream(MediaStream mediaStream) {
Log.d(TAG, "onAddStream: "+mediaStream);
}

@Override
public void onRemoveStream(MediaStream mediaStream) {
Log.d(TAG, "onRemoveStream: "+mediaStream);
}

@Override
public void onDataChannel(DataChannel dataChannel) {
Log.d(TAG, "onDataChannel: "+dataChannel);
}

@Override
public void onRenegotiationNeeded() {
Log.d(TAG, "onRenegotiationNeeded: ");
}

@Override
public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {
Log.d(TAG, "onAddTrack: "+rtpReceiver+"--- "+mediaStreams);
}
};

return peerConnectionFactory.createPeerConnection(rtcConfig,observer);
}

private void endCall() {
// End the call and release resources
// Code to end the call goes here
if (peerConnection!=null){
Log.d(TAG, "endCall: Connection State ="+peerConnection.connectionState());
Log.d(TAG, "endCall: Ice Connection State ="+peerConnection.iceConnectionState());
Log.d(TAG, "endCall: Ice Gathering State ="+peerConnection.iceGatheringState());
}

}

private class CustomPeerConnectionObserver implements PeerConnection.Observer {
@Override
public void onSignalingChange(PeerConnection.SignalingState signalingState) {
// Handle signaling state change
Log.d(TAG, "onSignalingChange:  "+signalingState);
}

@Override
public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
// Handle ICE connection state change
Log.d(TAG, "onIceConnectionChange: "+iceConnectionState);
}

@Override
public void onIceConnectionReceivingChange(boolean b) {
// Handle ICE connection receiving change
Log.d(TAG, "onIceConnectionReceivingChange: "+b);
}

@Override
public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
// Handle ICE gathering state change
Log.d(TAG, "onIceGatheringChange: "+iceGatheringState);
}

@Override
public void onIceCandidate(IceCandidate iceCandidate) {
// Handle ICE candidate
Log.d(TAG, "onIceCandidate: "+iceCandidate);
}

@Override
public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {
Log.d(TAG, "onIceCandidatesRemoved: "+iceCandidates.length);
}

@Override
public void onAddStream(MediaStream mediaStream) {
// Handle new stream added
Log.d(TAG, "onAddStream: "+mediaStream);
}

@Override
public void onRemoveStream(MediaStream mediaStream) {
// Handle stream removed
Log.d(TAG, "onRemoveStream: "+mediaStream);
}

@Override
public void onDataChannel(DataChannel dataChannel) {
// Handle data channel
Log.d(TAG, "onDataChannel: "+dataChannel);
}

@Override
public void onRenegotiationNeeded() {
// Handle renegotiation needed
}

@Override
public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {

}
}

// Helper class for handling SDP observer events
private static class SimpleSdpObserver implements SdpObserver {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
Log.d(TAG, "onCreateSuccess: Class Session Description = "+sessionDescription.description);
}

@Override
public void onSetSuccess() {
Log.d(TAG, "onSetSuccess: ");
}

@Override
public void onCreateFailure(String s) {}

@Override
public void onSetFailure(String s) {}
}

private void addIceCandidates(String iceCandidates){
IceCandidate iceCandidateObject = new IceCandidate("sdpMid", 0,iceCandidates);
// Add the IceCandidate object to the PeerConnection
peerConnection.addIceCandidate(iceCandidateObject);
}

}
Несмотря на обмен кандидатами ICE, соединение не переходит в состояние «Подключено». Я проверил, что оба пользователя отправляют и получают кандидатов ICE.
Сталкивался ли кто-нибудь с подобной проблемой с WebRTC на Android? Что может помешать соединению ICE выйти за пределы состояния «Проверка»?
Я использую эту библиотеку webrtc https://github.com/rno/WebRTC

Подробнее здесь: https://stackoverflow.com/questions/789 ... -new-state
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение

Вернуться в «Android»