2020-04-06 11:01:17 +00:00
package eu.siacs.conversations.xmpp.jingle ;
import android.content.Context ;
2020-04-14 17:06:39 +00:00
import android.os.Build ;
2020-04-13 10:02:34 +00:00
import android.os.Handler ;
import android.os.Looper ;
2020-04-06 13:45:06 +00:00
import android.util.Log ;
2020-04-06 11:01:17 +00:00
2020-04-14 17:06:39 +00:00
import com.google.common.base.Optional ;
import com.google.common.base.Preconditions ;
2020-05-02 15:15:46 +00:00
import com.google.common.collect.ImmutableSet ;
import com.google.common.collect.Iterables ;
2020-04-06 11:01:17 +00:00
import com.google.common.util.concurrent.Futures ;
import com.google.common.util.concurrent.ListenableFuture ;
import com.google.common.util.concurrent.MoreExecutors ;
import com.google.common.util.concurrent.SettableFuture ;
import org.webrtc.AudioSource ;
import org.webrtc.AudioTrack ;
2020-04-07 09:36:28 +00:00
import org.webrtc.Camera1Enumerator ;
2020-04-14 17:06:39 +00:00
import org.webrtc.Camera2Enumerator ;
2020-04-16 07:03:39 +00:00
import org.webrtc.CameraEnumerationAndroid ;
2020-04-14 17:06:39 +00:00
import org.webrtc.CameraEnumerator ;
2020-04-07 09:36:28 +00:00
import org.webrtc.CameraVideoCapturer ;
2020-04-08 11:30:12 +00:00
import org.webrtc.CandidatePairChangeEvent ;
2020-04-06 11:01:17 +00:00
import org.webrtc.DataChannel ;
2020-04-14 17:06:39 +00:00
import org.webrtc.DefaultVideoDecoderFactory ;
import org.webrtc.DefaultVideoEncoderFactory ;
import org.webrtc.EglBase ;
2020-04-06 11:01:17 +00:00
import org.webrtc.IceCandidate ;
import org.webrtc.MediaConstraints ;
import org.webrtc.MediaStream ;
2020-04-20 15:05:27 +00:00
import org.webrtc.MediaStreamTrack ;
2020-04-06 11:01:17 +00:00
import org.webrtc.PeerConnection ;
import org.webrtc.PeerConnectionFactory ;
import org.webrtc.RtpReceiver ;
2020-04-20 15:05:27 +00:00
import org.webrtc.RtpTransceiver ;
2020-04-06 11:01:17 +00:00
import org.webrtc.SdpObserver ;
import org.webrtc.SessionDescription ;
2020-04-14 17:06:39 +00:00
import org.webrtc.SurfaceTextureHelper ;
2020-04-07 09:36:28 +00:00
import org.webrtc.VideoSource ;
import org.webrtc.VideoTrack ;
2020-05-21 09:13:46 +00:00
import org.webrtc.audio.JavaAudioDeviceModule ;
import org.webrtc.voiceengine.WebRtcAudioEffects ;
2020-04-06 11:01:17 +00:00
2020-04-16 07:03:39 +00:00
import java.util.ArrayList ;
import java.util.Collections ;
2020-04-06 11:01:17 +00:00
import java.util.List ;
2020-04-13 10:02:34 +00:00
import java.util.Set ;
2020-04-06 11:01:17 +00:00
import javax.annotation.Nonnull ;
import javax.annotation.Nullable ;
2020-04-06 13:45:06 +00:00
import eu.siacs.conversations.Config ;
2020-04-13 10:02:34 +00:00
import eu.siacs.conversations.services.AppRTCAudioManager ;
2020-05-21 13:39:59 +00:00
import eu.siacs.conversations.services.XmppConnectionService ;
2020-04-06 13:45:06 +00:00
2020-04-06 11:01:17 +00:00
public class WebRTCWrapper {
2020-04-20 09:38:02 +00:00
private static final String EXTENDED_LOGGING_TAG = WebRTCWrapper . class . getSimpleName ( ) ;
2020-05-21 09:13:46 +00:00
//we should probably keep this in sync with: https://github.com/signalapp/Signal-Android/blob/master/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java#L296
private static final Set < String > HARDWARE_AEC_BLACKLIST = new ImmutableSet . Builder < String > ( )
. add ( " Pixel " )
. add ( " Pixel XL " )
. add ( " Moto G5 " )
. add ( " Moto G (5S) Plus " )
. add ( " Moto G4 " )
. add ( " TA-1053 " )
. add ( " Mi A1 " )
. add ( " Mi A2 " )
. add ( " E5823 " ) // Sony z5 compact
. add ( " Redmi Note 5 " )
. add ( " FP2 " ) // Fairphone FP2
. add ( " MI 5 " )
. build ( ) ;
2020-04-16 07:03:39 +00:00
private static final int CAPTURING_RESOLUTION = 1920 ;
private static final int CAPTURING_MAX_FRAME_RATE = 30 ;
2020-04-14 17:06:39 +00:00
private final EventCallback eventCallback ;
private final AppRTCAudioManager . AudioManagerEvents audioManagerEvents = new AppRTCAudioManager . AudioManagerEvents ( ) {
@Override
public void onAudioDeviceChanged ( AppRTCAudioManager . AudioDevice selectedAudioDevice , Set < AppRTCAudioManager . AudioDevice > availableAudioDevices ) {
eventCallback . onAudioDeviceChanged ( selectedAudioDevice , availableAudioDevices ) ;
}
} ;
private final Handler mainHandler = new Handler ( Looper . getMainLooper ( ) ) ;
2020-04-07 09:36:28 +00:00
private VideoTrack localVideoTrack = null ;
private VideoTrack remoteVideoTrack = null ;
2020-04-06 11:01:17 +00:00
private final PeerConnection . Observer peerConnectionObserver = new PeerConnection . Observer ( ) {
@Override
public void onSignalingChange ( PeerConnection . SignalingState signalingState ) {
2020-04-20 15:05:27 +00:00
Log . d ( EXTENDED_LOGGING_TAG , " onSignalingChange( " + signalingState + " ) " ) ;
2020-04-13 16:30:12 +00:00
//this is called after removeTrack or addTrack
//and should then trigger a content-add or content-remove or something
//https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/removeTrack
2020-04-06 13:45:06 +00:00
}
2020-04-06 11:01:17 +00:00
2020-04-06 13:45:06 +00:00
@Override
public void onConnectionChange ( PeerConnection . PeerConnectionState newState ) {
2020-04-07 11:15:24 +00:00
eventCallback . onConnectionChange ( newState ) ;
2020-04-06 11:01:17 +00:00
}
@Override
public void onIceConnectionChange ( PeerConnection . IceConnectionState iceConnectionState ) {
}
2020-04-08 11:30:12 +00:00
@Override
public void onSelectedCandidatePairChanged ( CandidatePairChangeEvent event ) {
Log . d ( Config . LOGTAG , " remote candidate selected: " + event . remote ) ;
Log . d ( Config . LOGTAG , " local candidate selected: " + event . local ) ;
}
2020-04-06 11:01:17 +00:00
@Override
public void onIceConnectionReceivingChange ( boolean b ) {
}
@Override
public void onIceGatheringChange ( PeerConnection . IceGatheringState iceGatheringState ) {
2020-04-23 17:51:58 +00:00
Log . d ( EXTENDED_LOGGING_TAG , " onIceGatheringChange( " + iceGatheringState + " ) " ) ;
2020-04-06 11:01:17 +00:00
}
@Override
public void onIceCandidate ( IceCandidate iceCandidate ) {
eventCallback . onIceCandidate ( iceCandidate ) ;
}
@Override
public void onIceCandidatesRemoved ( IceCandidate [ ] iceCandidates ) {
}
@Override
public void onAddStream ( MediaStream mediaStream ) {
2020-04-20 15:05:27 +00:00
Log . d ( EXTENDED_LOGGING_TAG , " onAddStream(numAudioTracks= " + mediaStream . audioTracks . size ( ) + " ,numVideoTracks= " + mediaStream . videoTracks . size ( ) + " ) " ) ;
2020-04-06 11:01:17 +00:00
}
@Override
public void onRemoveStream ( MediaStream mediaStream ) {
}
@Override
public void onDataChannel ( DataChannel dataChannel ) {
}
@Override
public void onRenegotiationNeeded ( ) {
}
@Override
public void onAddTrack ( RtpReceiver rtpReceiver , MediaStream [ ] mediaStreams ) {
2020-04-20 15:05:27 +00:00
final MediaStreamTrack track = rtpReceiver . track ( ) ;
Log . d ( EXTENDED_LOGGING_TAG , " onAddTrack(kind= " + ( track = = null ? " null " : track . kind ( ) ) + " ,numMediaStreams= " + mediaStreams . length + " ) " ) ;
2021-05-03 11:06:42 +00:00
if ( track instanceof VideoTrack ) {
remoteVideoTrack = ( VideoTrack ) track ;
2021-03-29 08:57:56 +00:00
}
2020-04-20 15:05:27 +00:00
}
2020-04-06 11:01:17 +00:00
2020-04-20 15:05:27 +00:00
@Override
public void onTrack ( RtpTransceiver transceiver ) {
Log . d ( EXTENDED_LOGGING_TAG , " onTrack(mid= " + transceiver . getMid ( ) + " ,media= " + transceiver . getMediaType ( ) + " ) " ) ;
2020-04-06 11:01:17 +00:00
}
} ;
@Nullable
private PeerConnection peerConnection = null ;
2020-04-13 10:53:23 +00:00
private AudioTrack localAudioTrack = null ;
2020-04-13 10:02:34 +00:00
private AppRTCAudioManager appRTCAudioManager = null ;
2020-05-21 13:39:59 +00:00
private ToneManager toneManager = null ;
2020-04-14 17:06:39 +00:00
private Context context = null ;
private EglBase eglBase = null ;
2020-04-17 12:16:39 +00:00
private CapturerChoice capturerChoice ;
2020-04-06 11:01:17 +00:00
2020-04-29 07:10:15 +00:00
WebRTCWrapper ( final EventCallback eventCallback ) {
2020-04-06 11:01:17 +00:00
this . eventCallback = eventCallback ;
}
2020-05-21 09:13:46 +00:00
private static void dispose ( final PeerConnection peerConnection ) {
try {
peerConnection . dispose ( ) ;
} catch ( final IllegalStateException e ) {
Log . e ( Config . LOGTAG , " unable to dispose of peer connection " , e ) ;
}
}
@Nullable
private static CapturerChoice of ( CameraEnumerator enumerator , final String deviceName , Set < String > availableCameras ) {
final CameraVideoCapturer capturer = enumerator . createCapturer ( deviceName , null ) ;
if ( capturer = = null ) {
return null ;
}
final ArrayList < CameraEnumerationAndroid . CaptureFormat > choices = new ArrayList < > ( enumerator . getSupportedFormats ( deviceName ) ) ;
Collections . sort ( choices , ( a , b ) - > b . width - a . width ) ;
for ( final CameraEnumerationAndroid . CaptureFormat captureFormat : choices ) {
if ( captureFormat . width < = CAPTURING_RESOLUTION ) {
return new CapturerChoice ( capturer , captureFormat , availableCameras ) ;
}
}
return null ;
}
2021-03-16 14:21:01 +00:00
private static boolean isFrontFacing ( final CameraEnumerator cameraEnumerator , final String deviceName ) {
try {
return cameraEnumerator . isFrontFacing ( deviceName ) ;
} catch ( final NullPointerException e ) {
return false ;
}
}
2020-05-21 13:39:59 +00:00
public void setup ( final XmppConnectionService service , final AppRTCAudioManager . SpeakerPhonePreference speakerPhonePreference ) throws InitializationException {
2020-05-09 17:48:54 +00:00
try {
PeerConnectionFactory . initialize (
2020-05-21 13:39:59 +00:00
PeerConnectionFactory . InitializationOptions . builder ( service ) . createInitializationOptions ( )
2020-05-09 17:48:54 +00:00
) ;
} catch ( final UnsatisfiedLinkError e ) {
2020-06-18 18:37:56 +00:00
throw new InitializationException ( " Unable to initialize PeerConnectionFactory " , e ) ;
}
try {
this . eglBase = EglBase . create ( ) ;
} catch ( final RuntimeException e ) {
throw new InitializationException ( " Unable to create EGL base " , e ) ;
2020-05-09 17:48:54 +00:00
}
2020-05-21 13:39:59 +00:00
this . context = service ;
this . toneManager = service . getJingleConnectionManager ( ) . toneManager ;
2020-04-13 10:02:34 +00:00
mainHandler . post ( ( ) - > {
2020-05-21 13:39:59 +00:00
appRTCAudioManager = AppRTCAudioManager . create ( service , speakerPhonePreference ) ;
toneManager . setAppRtcAudioManagerHasControl ( true ) ;
2020-04-13 12:55:07 +00:00
appRTCAudioManager . start ( audioManagerEvents ) ;
eventCallback . onAudioDeviceChanged ( appRTCAudioManager . getSelectedAudioDevice ( ) , appRTCAudioManager . getAudioDevices ( ) ) ;
2020-04-13 10:02:34 +00:00
} ) ;
2020-04-06 11:01:17 +00:00
}
2020-04-29 07:10:15 +00:00
synchronized void initializePeerConnection ( final Set < Media > media , final List < PeerConnection . IceServer > iceServers ) throws InitializationException {
2020-04-14 17:06:39 +00:00
Preconditions . checkState ( this . eglBase ! = null ) ;
2020-04-15 10:07:19 +00:00
Preconditions . checkNotNull ( media ) ;
Preconditions . checkArgument ( media . size ( ) > 0 , " media can not be empty when initializing peer connection " ) ;
2020-05-21 09:13:46 +00:00
final boolean setUseHardwareAcousticEchoCanceler = WebRtcAudioEffects . canUseAcousticEchoCanceler ( ) & & ! HARDWARE_AEC_BLACKLIST . contains ( Build . MODEL ) ;
Log . d ( Config . LOGTAG , String . format ( " setUseHardwareAcousticEchoCanceler(%s) model=%s " , setUseHardwareAcousticEchoCanceler , Build . MODEL ) ) ;
2020-04-14 17:06:39 +00:00
PeerConnectionFactory peerConnectionFactory = PeerConnectionFactory . builder ( )
. setVideoDecoderFactory ( new DefaultVideoDecoderFactory ( eglBase . getEglBaseContext ( ) ) )
. setVideoEncoderFactory ( new DefaultVideoEncoderFactory ( eglBase . getEglBaseContext ( ) , true , true ) )
2020-05-21 09:13:46 +00:00
. setAudioDeviceModule ( JavaAudioDeviceModule . builder ( context )
. setUseHardwareAcousticEchoCanceler ( setUseHardwareAcousticEchoCanceler )
. createAudioDeviceModule ( )
)
2020-04-14 17:06:39 +00:00
. createPeerConnectionFactory ( ) ;
2020-04-07 09:36:28 +00:00
2021-03-16 14:21:01 +00:00
final PeerConnection . RTCConfiguration rtcConfig = new PeerConnection . RTCConfiguration ( iceServers ) ;
rtcConfig . tcpCandidatePolicy = PeerConnection . TcpCandidatePolicy . DISABLED ; //XEP-0176 doesn't support tcp
rtcConfig . continualGatheringPolicy = PeerConnection . ContinualGatheringPolicy . GATHER_CONTINUALLY ;
rtcConfig . sdpSemantics = PeerConnection . SdpSemantics . UNIFIED_PLAN ;
final PeerConnection peerConnection = peerConnectionFactory . createPeerConnection ( rtcConfig , peerConnectionObserver ) ;
if ( peerConnection = = null ) {
throw new InitializationException ( " Unable to create PeerConnection " ) ;
}
2020-04-07 09:36:28 +00:00
2020-04-17 12:16:39 +00:00
final Optional < CapturerChoice > optionalCapturerChoice = media . contains ( Media . VIDEO ) ? getVideoCapturer ( ) : Optional . absent ( ) ;
2020-04-07 09:36:28 +00:00
2020-04-17 12:16:39 +00:00
if ( optionalCapturerChoice . isPresent ( ) ) {
this . capturerChoice = optionalCapturerChoice . get ( ) ;
final CameraVideoCapturer capturer = this . capturerChoice . cameraVideoCapturer ;
2020-04-14 17:06:39 +00:00
final VideoSource videoSource = peerConnectionFactory . createVideoSource ( false ) ;
SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper . create ( " webrtc " , eglBase . getEglBaseContext ( ) ) ;
capturer . initialize ( surfaceTextureHelper , requireContext ( ) , videoSource . getCapturerObserver ( ) ) ;
2020-04-17 12:16:39 +00:00
Log . d ( Config . LOGTAG , String . format ( " start capturing at %dx%d@%d " , capturerChoice . captureFormat . width , capturerChoice . captureFormat . height , capturerChoice . getFrameRate ( ) ) ) ;
capturer . startCapture ( capturerChoice . captureFormat . width , capturerChoice . captureFormat . height , capturerChoice . getFrameRate ( ) ) ;
2020-04-07 09:36:28 +00:00
2020-04-14 17:06:39 +00:00
this . localVideoTrack = peerConnectionFactory . createVideoTrack ( " my-video-track " , videoSource ) ;
2020-04-07 09:36:28 +00:00
2021-03-16 14:21:01 +00:00
peerConnection . addTrack ( this . localVideoTrack ) ;
2020-04-07 09:36:28 +00:00
}
2020-04-15 10:07:19 +00:00
if ( media . contains ( Media . AUDIO ) ) {
//set up audio track
final AudioSource audioSource = peerConnectionFactory . createAudioSource ( new MediaConstraints ( ) ) ;
this . localAudioTrack = peerConnectionFactory . createAudioTrack ( " my-audio-track " , audioSource ) ;
2021-03-16 14:21:01 +00:00
peerConnection . addTrack ( this . localAudioTrack ) ;
2020-04-06 11:01:17 +00:00
}
2020-04-06 13:45:06 +00:00
peerConnection . setAudioPlayout ( true ) ;
peerConnection . setAudioRecording ( true ) ;
2020-04-06 11:01:17 +00:00
this . peerConnection = peerConnection ;
}
2020-04-13 12:55:07 +00:00
2020-04-29 07:10:15 +00:00
synchronized void close ( ) {
2020-04-08 13:27:17 +00:00
final PeerConnection peerConnection = this . peerConnection ;
2020-04-17 12:16:39 +00:00
final CapturerChoice capturerChoice = this . capturerChoice ;
2020-04-15 10:07:19 +00:00
final AppRTCAudioManager audioManager = this . appRTCAudioManager ;
final EglBase eglBase = this . eglBase ;
2020-04-08 13:27:17 +00:00
if ( peerConnection ! = null ) {
2020-04-28 18:15:23 +00:00
dispose ( peerConnection ) ;
2020-04-18 16:22:10 +00:00
this . peerConnection = null ;
2020-04-08 13:27:17 +00:00
}
2020-04-13 10:02:34 +00:00
if ( audioManager ! = null ) {
2020-05-21 13:39:59 +00:00
toneManager . setAppRtcAudioManagerHasControl ( false ) ;
2020-04-13 10:02:34 +00:00
mainHandler . post ( audioManager : : stop ) ;
}
2020-04-14 19:06:26 +00:00
this . localVideoTrack = null ;
this . remoteVideoTrack = null ;
2020-04-17 12:16:39 +00:00
if ( capturerChoice ! = null ) {
2020-04-14 19:06:26 +00:00
try {
2020-04-17 12:16:39 +00:00
capturerChoice . cameraVideoCapturer . stopCapture ( ) ;
2020-04-14 19:06:26 +00:00
} catch ( InterruptedException e ) {
2020-04-15 10:07:19 +00:00
Log . e ( Config . LOGTAG , " unable to stop capturing " ) ;
2020-04-14 19:06:26 +00:00
}
}
2020-04-15 10:07:19 +00:00
if ( eglBase ! = null ) {
eglBase . release ( ) ;
2020-04-18 16:22:10 +00:00
this . eglBase = null ;
}
}
2020-04-29 07:10:15 +00:00
synchronized void verifyClosed ( ) {
2020-04-18 16:22:10 +00:00
if ( this . peerConnection ! = null
| | this . eglBase ! = null
| | this . localVideoTrack ! = null
| | this . remoteVideoTrack ! = null ) {
2020-04-21 10:00:13 +00:00
final IllegalStateException e = new IllegalStateException ( " WebRTCWrapper hasn't been closed properly " ) ;
Log . e ( Config . LOGTAG , " verifyClosed() failed. Going to throw " , e ) ;
throw e ;
2020-04-15 10:07:19 +00:00
}
2020-04-08 13:27:17 +00:00
}
2020-05-02 15:15:46 +00:00
boolean isCameraSwitchable ( ) {
final CapturerChoice capturerChoice = this . capturerChoice ;
return capturerChoice ! = null & & capturerChoice . availableCameras . size ( ) > 1 ;
}
2020-05-03 09:08:11 +00:00
boolean isFrontCamera ( ) {
final CapturerChoice capturerChoice = this . capturerChoice ;
return capturerChoice = = null | | capturerChoice . isFrontCamera ;
}
ListenableFuture < Boolean > switchCamera ( ) {
2020-05-02 15:15:46 +00:00
final CapturerChoice capturerChoice = this . capturerChoice ;
if ( capturerChoice = = null ) {
return Futures . immediateFailedFuture ( new IllegalStateException ( " CameraCapturer has not been initialized " ) ) ;
}
2020-05-03 09:08:11 +00:00
final SettableFuture < Boolean > future = SettableFuture . create ( ) ;
2020-05-02 15:15:46 +00:00
capturerChoice . cameraVideoCapturer . switchCamera ( new CameraVideoCapturer . CameraSwitchHandler ( ) {
@Override
public void onCameraSwitchDone ( boolean isFrontCamera ) {
2020-05-03 09:08:11 +00:00
capturerChoice . isFrontCamera = isFrontCamera ;
future . set ( isFrontCamera ) ;
2020-05-02 15:15:46 +00:00
}
@Override
public void onCameraSwitchError ( final String message ) {
future . setException ( new IllegalStateException ( String . format ( " Unable to switch camera %s " , message ) ) ) ;
}
} ) ;
return future ;
}
2020-04-15 17:16:47 +00:00
boolean isMicrophoneEnabled ( ) {
2020-04-13 10:53:23 +00:00
final AudioTrack audioTrack = this . localAudioTrack ;
if ( audioTrack = = null ) {
throw new IllegalStateException ( " Local audio track does not exist (yet) " ) ;
}
2020-05-03 09:54:31 +00:00
try {
return audioTrack . enabled ( ) ;
} catch ( final IllegalStateException e ) {
//sometimes UI might still be rendering the buttons when a background thread has already ended the call
return false ;
}
2020-04-13 10:53:23 +00:00
}
2020-07-09 17:14:28 +00:00
boolean setMicrophoneEnabled ( final boolean enabled ) {
2020-04-13 10:53:23 +00:00
final AudioTrack audioTrack = this . localAudioTrack ;
if ( audioTrack = = null ) {
throw new IllegalStateException ( " Local audio track does not exist (yet) " ) ;
}
2020-07-09 17:14:28 +00:00
try {
audioTrack . setEnabled ( enabled ) ;
return true ;
} catch ( final IllegalStateException e ) {
Log . d ( Config . LOGTAG , " unable to toggle microphone " , e ) ;
//ignoring race condition in case MediaStreamTrack has been disposed
return false ;
}
2020-04-13 10:53:23 +00:00
}
2020-04-29 07:10:15 +00:00
boolean isVideoEnabled ( ) {
2020-04-15 17:16:47 +00:00
final VideoTrack videoTrack = this . localVideoTrack ;
if ( videoTrack = = null ) {
2021-03-26 11:54:23 +00:00
return false ;
2020-04-15 17:16:47 +00:00
}
return videoTrack . enabled ( ) ;
}
2020-04-29 07:10:15 +00:00
void setVideoEnabled ( final boolean enabled ) {
2020-04-15 17:16:47 +00:00
final VideoTrack videoTrack = this . localVideoTrack ;
if ( videoTrack = = null ) {
throw new IllegalStateException ( " Local video track does not exist " ) ;
}
videoTrack . setEnabled ( enabled ) ;
}
2020-04-29 07:10:15 +00:00
ListenableFuture < SessionDescription > createOffer ( ) {
2020-04-06 11:01:17 +00:00
return Futures . transformAsync ( getPeerConnectionFuture ( ) , peerConnection - > {
final SettableFuture < SessionDescription > future = SettableFuture . create ( ) ;
peerConnection . createOffer ( new CreateSdpObserver ( ) {
@Override
public void onCreateSuccess ( SessionDescription sessionDescription ) {
future . set ( sessionDescription ) ;
}
@Override
public void onCreateFailure ( String s ) {
future . setException ( new IllegalStateException ( " Unable to create offer: " + s ) ) ;
}
} , new MediaConstraints ( ) ) ;
return future ;
} , MoreExecutors . directExecutor ( ) ) ;
}
2020-04-29 07:10:15 +00:00
ListenableFuture < SessionDescription > createAnswer ( ) {
2020-04-06 11:01:17 +00:00
return Futures . transformAsync ( getPeerConnectionFuture ( ) , peerConnection - > {
final SettableFuture < SessionDescription > future = SettableFuture . create ( ) ;
peerConnection . createAnswer ( new CreateSdpObserver ( ) {
@Override
public void onCreateSuccess ( SessionDescription sessionDescription ) {
future . set ( sessionDescription ) ;
}
@Override
public void onCreateFailure ( String s ) {
future . setException ( new IllegalStateException ( " Unable to create answer: " + s ) ) ;
}
} , new MediaConstraints ( ) ) ;
return future ;
} , MoreExecutors . directExecutor ( ) ) ;
}
2020-04-29 07:10:15 +00:00
ListenableFuture < Void > setLocalDescription ( final SessionDescription sessionDescription ) {
2020-04-20 09:38:02 +00:00
Log . d ( EXTENDED_LOGGING_TAG , " setting local description: " ) ;
for ( final String line : sessionDescription . description . split ( eu . siacs . conversations . xmpp . jingle . SessionDescription . LINE_DIVIDER ) ) {
Log . d ( EXTENDED_LOGGING_TAG , line ) ;
}
2020-04-06 11:01:17 +00:00
return Futures . transformAsync ( getPeerConnectionFuture ( ) , peerConnection - > {
final SettableFuture < Void > future = SettableFuture . create ( ) ;
peerConnection . setLocalDescription ( new SetSdpObserver ( ) {
@Override
public void onSetSuccess ( ) {
future . set ( null ) ;
}
@Override
2020-04-29 13:32:27 +00:00
public void onSetFailure ( final String s ) {
2020-04-06 13:45:06 +00:00
future . setException ( new IllegalArgumentException ( " unable to set local session description: " + s ) ) ;
2020-04-06 11:01:17 +00:00
}
} , sessionDescription ) ;
return future ;
} , MoreExecutors . directExecutor ( ) ) ;
}
2020-04-29 07:10:15 +00:00
ListenableFuture < Void > setRemoteDescription ( final SessionDescription sessionDescription ) {
2020-04-20 09:38:02 +00:00
Log . d ( EXTENDED_LOGGING_TAG , " setting remote description: " ) ;
for ( final String line : sessionDescription . description . split ( eu . siacs . conversations . xmpp . jingle . SessionDescription . LINE_DIVIDER ) ) {
Log . d ( EXTENDED_LOGGING_TAG , line ) ;
}
2020-04-06 11:01:17 +00:00
return Futures . transformAsync ( getPeerConnectionFuture ( ) , peerConnection - > {
final SettableFuture < Void > future = SettableFuture . create ( ) ;
peerConnection . setRemoteDescription ( new SetSdpObserver ( ) {
@Override
public void onSetSuccess ( ) {
future . set ( null ) ;
}
@Override
public void onSetFailure ( String s ) {
2020-04-06 13:45:06 +00:00
future . setException ( new IllegalArgumentException ( " unable to set remote session description: " + s ) ) ;
2020-04-06 11:01:17 +00:00
}
} , sessionDescription ) ;
return future ;
} , MoreExecutors . directExecutor ( ) ) ;
}
@Nonnull
private ListenableFuture < PeerConnection > getPeerConnectionFuture ( ) {
final PeerConnection peerConnection = this . peerConnection ;
if ( peerConnection = = null ) {
return Futures . immediateFailedFuture ( new IllegalStateException ( " initialize PeerConnection first " ) ) ;
} else {
return Futures . immediateFuture ( peerConnection ) ;
}
}
2020-04-29 07:10:15 +00:00
void addIceCandidate ( IceCandidate iceCandidate ) {
2020-04-07 12:22:12 +00:00
requirePeerConnection ( ) . addIceCandidate ( iceCandidate ) ;
}
2020-04-14 17:06:39 +00:00
private CameraEnumerator getCameraEnumerator ( ) {
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . LOLLIPOP ) {
return new Camera2Enumerator ( requireContext ( ) ) ;
} else {
return new Camera1Enumerator ( ) ;
}
}
2020-04-16 07:03:39 +00:00
private Optional < CapturerChoice > getVideoCapturer ( ) {
2020-04-14 17:06:39 +00:00
final CameraEnumerator enumerator = getCameraEnumerator ( ) ;
2020-05-02 15:15:46 +00:00
final Set < String > deviceNames = ImmutableSet . copyOf ( enumerator . getDeviceNames ( ) ) ;
2020-04-14 19:06:26 +00:00
for ( final String deviceName : deviceNames ) {
2020-07-09 18:11:09 +00:00
if ( isFrontFacing ( enumerator , deviceName ) ) {
2020-05-03 09:08:11 +00:00
final CapturerChoice capturerChoice = of ( enumerator , deviceName , deviceNames ) ;
if ( capturerChoice = = null ) {
return Optional . absent ( ) ;
}
capturerChoice . isFrontCamera = true ;
return Optional . of ( capturerChoice ) ;
2020-04-14 17:06:39 +00:00
}
}
2020-05-02 15:15:46 +00:00
if ( deviceNames . size ( ) = = 0 ) {
2020-04-14 17:06:39 +00:00
return Optional . absent ( ) ;
} else {
2020-05-02 15:15:46 +00:00
return Optional . fromNullable ( of ( enumerator , Iterables . get ( deviceNames , 0 ) , deviceNames ) ) ;
2020-04-16 07:03:39 +00:00
}
}
2020-04-07 12:22:12 +00:00
public PeerConnection . PeerConnectionState getState ( ) {
return requirePeerConnection ( ) . connectionState ( ) ;
}
2020-04-16 07:03:39 +00:00
EglBase . Context getEglBaseContext ( ) {
2020-04-14 17:06:39 +00:00
return this . eglBase . getEglBaseContext ( ) ;
}
2020-04-29 07:10:15 +00:00
Optional < VideoTrack > getLocalVideoTrack ( ) {
2020-04-14 17:06:39 +00:00
return Optional . fromNullable ( this . localVideoTrack ) ;
}
2020-04-29 07:10:15 +00:00
Optional < VideoTrack > getRemoteVideoTrack ( ) {
2020-04-14 17:06:39 +00:00
return Optional . fromNullable ( this . remoteVideoTrack ) ;
}
2020-04-07 12:22:12 +00:00
private PeerConnection requirePeerConnection ( ) {
2020-04-06 11:01:17 +00:00
final PeerConnection peerConnection = this . peerConnection ;
if ( peerConnection = = null ) {
2020-08-01 12:18:00 +00:00
throw new PeerConnectionNotInitialized ( ) ;
2020-04-06 11:01:17 +00:00
}
2020-04-07 12:22:12 +00:00
return peerConnection ;
2020-04-06 13:45:06 +00:00
}
2020-04-14 17:06:39 +00:00
private Context requireContext ( ) {
final Context context = this . context ;
if ( context = = null ) {
throw new IllegalStateException ( " call setup first " ) ;
}
return context ;
}
2020-04-29 07:10:15 +00:00
AppRTCAudioManager getAudioManager ( ) {
2020-04-13 10:53:23 +00:00
return appRTCAudioManager ;
}
2020-04-14 17:06:39 +00:00
public interface EventCallback {
void onIceCandidate ( IceCandidate iceCandidate ) ;
void onConnectionChange ( PeerConnection . PeerConnectionState newState ) ;
void onAudioDeviceChanged ( AppRTCAudioManager . AudioDevice selectedAudioDevice , Set < AppRTCAudioManager . AudioDevice > availableAudioDevices ) ;
}
2020-04-06 11:01:17 +00:00
private static abstract class SetSdpObserver implements SdpObserver {
@Override
public void onCreateSuccess ( org . webrtc . SessionDescription sessionDescription ) {
throw new IllegalStateException ( " Not able to use SetSdpObserver " ) ;
}
@Override
public void onCreateFailure ( String s ) {
throw new IllegalStateException ( " Not able to use SetSdpObserver " ) ;
}
}
private static abstract class CreateSdpObserver implements SdpObserver {
@Override
public void onSetSuccess ( ) {
throw new IllegalStateException ( " Not able to use CreateSdpObserver " ) ;
}
@Override
public void onSetFailure ( String s ) {
throw new IllegalStateException ( " Not able to use CreateSdpObserver " ) ;
}
}
2020-04-29 07:10:15 +00:00
static class InitializationException extends Exception {
2020-04-09 13:22:03 +00:00
2020-06-18 18:37:56 +00:00
private InitializationException ( final String message , final Throwable throwable ) {
super ( message , throwable ) ;
2020-05-09 17:48:54 +00:00
}
private InitializationException ( final String message ) {
2020-04-09 13:22:03 +00:00
super ( message ) ;
}
}
2020-04-16 07:03:39 +00:00
2020-08-01 12:18:00 +00:00
public static class PeerConnectionNotInitialized extends IllegalStateException {
private PeerConnectionNotInitialized ( ) {
super ( " initialize PeerConnection first " ) ;
}
}
2020-04-16 07:03:39 +00:00
private static class CapturerChoice {
private final CameraVideoCapturer cameraVideoCapturer ;
private final CameraEnumerationAndroid . CaptureFormat captureFormat ;
2020-05-02 15:15:46 +00:00
private final Set < String > availableCameras ;
2020-05-03 09:08:11 +00:00
private boolean isFrontCamera = false ;
2020-04-16 07:03:39 +00:00
2020-05-02 15:15:46 +00:00
CapturerChoice ( CameraVideoCapturer cameraVideoCapturer , CameraEnumerationAndroid . CaptureFormat captureFormat , Set < String > cameras ) {
2020-04-16 07:03:39 +00:00
this . cameraVideoCapturer = cameraVideoCapturer ;
this . captureFormat = captureFormat ;
2020-05-02 15:15:46 +00:00
this . availableCameras = cameras ;
2020-04-16 07:03:39 +00:00
}
2020-04-29 07:10:15 +00:00
int getFrameRate ( ) {
2020-04-16 07:03:39 +00:00
return Math . max ( captureFormat . framerate . min , Math . min ( CAPTURING_MAX_FRAME_RATE , captureFormat . framerate . max ) ) ;
}
}
2020-04-06 11:01:17 +00:00
}