Here are the examples of the java api org.webrtc.PeerConnection taken from open source projects. By voting up you can indicate which examples are most useful and appropriate.
65 Examples
19
Source : PeerConnectionWrapper.java
with GNU General Public License v3.0
from XecureIT
with GNU General Public License v3.0
from XecureIT
public clreplaced PeerConnectionWrapper {
private static final String TAG = PeerConnectionWrapper.clreplaced.getSimpleName();
private static final PeerConnection.IceServer STUN_SERVER = new PeerConnection.IceServer("stun:stun1.l.google.com:19302");
@NonNull
private final PeerConnection peerConnection;
@NonNull
private final AudioTrack audioTrack;
@NonNull
private final AudioSource audioSource;
@Nullable
private final VideoCapturer videoCapturer;
@Nullable
private final VideoSource videoSource;
@Nullable
private final VideoTrack videoTrack;
public PeerConnectionWrapper(@NonNull Context context, @NonNull PeerConnectionFactory factory, @NonNull PeerConnection.Observer observer, @NonNull VideoRenderer.Callbacks localRenderer, @NonNull List<PeerConnection.IceServer> turnServers, boolean hideIp) {
List<PeerConnection.IceServer> iceServers = new LinkedList<>();
iceServers.add(STUN_SERVER);
iceServers.addAll(turnServers);
MediaConstraints constraints = new MediaConstraints();
MediaConstraints audioConstraints = new MediaConstraints();
PeerConnection.RTCConfiguration configuration = new PeerConnection.RTCConfiguration(iceServers);
configuration.bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE;
configuration.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.REQUIRE;
if (hideIp) {
configuration.iceTransportsType = PeerConnection.IceTransportsType.RELAY;
}
constraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
audioConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
this.peerConnection = factory.createPeerConnection(configuration, constraints, observer);
this.videoCapturer = createVideoCapturer(context);
MediaStream mediaStream = factory.createLocalMediaStream("ARDAMS");
this.audioSource = factory.createAudioSource(audioConstraints);
this.audioTrack = factory.createAudioTrack("ARDAMSa0", audioSource);
this.audioTrack.setEnabled(false);
mediaStream.addTrack(audioTrack);
if (videoCapturer != null) {
this.videoSource = factory.createVideoSource(videoCapturer);
this.videoTrack = factory.createVideoTrack("ARDAMSv0", videoSource);
this.videoTrack.addRenderer(new VideoRenderer(localRenderer));
this.videoTrack.setEnabled(false);
mediaStream.addTrack(videoTrack);
} else {
this.videoSource = null;
this.videoTrack = null;
}
this.peerConnection.addStream(mediaStream);
}
public void setVideoEnabled(boolean enabled) {
if (this.videoTrack != null) {
this.videoTrack.setEnabled(enabled);
}
if (this.videoCapturer != null) {
try {
if (enabled)
this.videoCapturer.startCapture(1280, 720, 30);
else
this.videoCapturer.stopCapture();
} catch (InterruptedException e) {
Log.w(TAG, e);
}
}
}
public void setAudioEnabled(boolean enabled) {
this.audioTrack.setEnabled(enabled);
}
public DataChannel createDataChannel(String name) {
DataChannel.Init dataChannelConfiguration = new DataChannel.Init();
dataChannelConfiguration.ordered = true;
return this.peerConnection.createDataChannel(name, dataChannelConfiguration);
}
public SessionDescription createOffer(MediaConstraints mediaConstraints) throws PeerConnectionException {
final SettableFuture<SessionDescription> future = new SettableFuture<>();
peerConnection.createOffer(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sdp) {
future.set(sdp);
}
@Override
public void onCreateFailure(String error) {
future.setException(new PeerConnectionException(error));
}
@Override
public void onSetSuccess() {
throw new replacedertionError();
}
@Override
public void onSetFailure(String error) {
throw new replacedertionError();
}
}, mediaConstraints);
try {
return correctSessionDescription(future.get());
} catch (InterruptedException e) {
throw new replacedertionError(e);
} catch (ExecutionException e) {
throw new PeerConnectionException(e);
}
}
public SessionDescription createAnswer(MediaConstraints mediaConstraints) throws PeerConnectionException {
final SettableFuture<SessionDescription> future = new SettableFuture<>();
peerConnection.createAnswer(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sdp) {
future.set(sdp);
}
@Override
public void onCreateFailure(String error) {
future.setException(new PeerConnectionException(error));
}
@Override
public void onSetSuccess() {
throw new replacedertionError();
}
@Override
public void onSetFailure(String error) {
throw new replacedertionError();
}
}, mediaConstraints);
try {
return correctSessionDescription(future.get());
} catch (InterruptedException e) {
throw new replacedertionError(e);
} catch (ExecutionException e) {
throw new PeerConnectionException(e);
}
}
public void setRemoteDescription(SessionDescription sdp) throws PeerConnectionException {
final SettableFuture<Boolean> future = new SettableFuture<>();
peerConnection.setRemoteDescription(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sdp) {
}
@Override
public void onCreateFailure(String error) {
}
@Override
public void onSetSuccess() {
future.set(true);
}
@Override
public void onSetFailure(String error) {
future.setException(new PeerConnectionException(error));
}
}, sdp);
try {
future.get();
} catch (InterruptedException e) {
throw new replacedertionError(e);
} catch (ExecutionException e) {
throw new PeerConnectionException(e);
}
}
public void setLocalDescription(SessionDescription sdp) throws PeerConnectionException {
final SettableFuture<Boolean> future = new SettableFuture<>();
peerConnection.setLocalDescription(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sdp) {
throw new replacedertionError();
}
@Override
public void onCreateFailure(String error) {
throw new replacedertionError();
}
@Override
public void onSetSuccess() {
future.set(true);
}
@Override
public void onSetFailure(String error) {
future.setException(new PeerConnectionException(error));
}
}, sdp);
try {
future.get();
} catch (InterruptedException e) {
throw new replacedertionError(e);
} catch (ExecutionException e) {
throw new PeerConnectionException(e);
}
}
public void dispose() {
if (this.videoCapturer != null) {
try {
this.videoCapturer.stopCapture();
} catch (InterruptedException e) {
Log.w(TAG, e);
}
this.videoCapturer.dispose();
}
if (this.videoSource != null) {
this.videoSource.dispose();
}
this.audioSource.dispose();
this.peerConnection.close();
this.peerConnection.dispose();
}
public boolean addIceCandidate(IceCandidate candidate) {
return this.peerConnection.addIceCandidate(candidate);
}
@Nullable
private CameraVideoCapturer createVideoCapturer(@NonNull Context context) {
boolean camera2EnumeratorIsSupported = false;
try {
camera2EnumeratorIsSupported = Camera2Enumerator.isSupported(context);
} catch (final Throwable throwable) {
Log.w(TAG, "Camera2Enumator.isSupport() threw.", throwable);
}
Log.w(TAG, "Camera2 enumerator supported: " + camera2EnumeratorIsSupported);
CameraEnumerator enumerator;
if (camera2EnumeratorIsSupported)
enumerator = new Camera2Enumerator(context);
else
enumerator = new Camera1Enumerator(true);
String[] deviceNames = enumerator.getDeviceNames();
for (String deviceName : deviceNames) {
if (enumerator.isFrontFacing(deviceName)) {
Log.w(TAG, "Creating front facing camera capturer.");
final CameraVideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
if (videoCapturer != null) {
Log.w(TAG, "Found front facing capturer: " + deviceName);
return videoCapturer;
}
}
}
for (String deviceName : deviceNames) {
if (!enumerator.isFrontFacing(deviceName)) {
Log.w(TAG, "Creating other camera capturer.");
final CameraVideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
if (videoCapturer != null) {
Log.w(TAG, "Found other facing capturer: " + deviceName);
return videoCapturer;
}
}
}
Log.w(TAG, "Video capture not supported!");
return null;
}
private SessionDescription correctSessionDescription(SessionDescription sessionDescription) {
String updatedSdp = sessionDescription.description.replaceAll("(a=fmtp:111 ((?!cbr=).)*)\r?\n", "$1;cbr=1\r\n");
updatedSdp = updatedSdp.replaceAll(".+urn:ietf:params:rtp-hdrext:ssrc-audio-level.*\r?\n", "");
return new SessionDescription(sessionDescription.type, updatedSdp);
}
public static clreplaced PeerConnectionException extends Exception {
public PeerConnectionException(String error) {
super(error);
}
public PeerConnectionException(Throwable throwable) {
super(throwable);
}
}
}
19
Source : FancyWebRTC.java
with MIT License
from triniwiz
with MIT License
from triniwiz
/**
* Created by Osei Fortune on 8/15/18
*/
public clreplaced FancyWebRTC {
private PeerConnectionFactory factory;
private PeerConnection connection;
private Map<String, MediaStream> localMediaStreams;
private Map<String, MediaStream> remoteMediaStreams;
private MediaConstraints defaultConstraints;
private ArrayList<IceCandidate> remoteIceCandidates;
private FancyWebRTCListener listener;
private WeakReference<FancyWebRTC> ref;
private boolean videoEnabled = true;
private boolean audioEnabled = true;
public static String[] WEBRTC_PERMISSIONS = new String[] { android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO };
public static int WEBRTC_PERMISSIONS_REQUEST_CODE = 12345;
private PeerConnection.RTCConfiguration configuration;
static final ExecutorService executor = Executors.newSingleThreadExecutor();
private final Map<String, MediaData> tracks = new HashMap<>();
private final Map<String, DataChannel> dataChannels = new HashMap<>();
private final Map<String, FancyWebRTCListener.GetUserMediaListener> getUserMediaListenerMap = new HashMap<>();
private final Context appContext;
public static String Tag = "co.fitcom.fancywebrtc.logger";
clreplaced MediaData {
public final MediaSource mediaSource;
public final MediaStreamTrack track;
public final FancyWebRTCCapturer capturer;
public MediaData(MediaSource mediaSource, MediaStreamTrack track, FancyWebRTCCapturer capturer) {
this.mediaSource = mediaSource;
this.track = track;
this.capturer = capturer;
}
}
public static void init(Context context) {
executor.execute(() -> {
FancyWebRTCEglUtils.getRootEglBase();
PeerConnectionFactory.InitializationOptions.Builder builder = PeerConnectionFactory.InitializationOptions.builder(context);
builder.setEnableInternalTracer(true);
PeerConnectionFactory.initialize(builder.createInitializationOptions());
});
}
public FancyWebRTC(Context context, boolean videoEnabled, boolean audioEnabled, ArrayList<PeerConnection.IceServer> iceServers) {
initialize(videoEnabled, audioEnabled, iceServers);
this.appContext = context;
}
private void initialize(final boolean videoEnabled, final boolean audioEnabled, @Nullable final ArrayList<PeerConnection.IceServer> iceServers) {
ref = new WeakReference<>(this);
this.videoEnabled = videoEnabled;
this.audioEnabled = audioEnabled;
executor.execute(new Runnable() {
@Override
public void run() {
PeerConnectionFactory.Builder builder = PeerConnectionFactory.builder();
PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
builder.setOptions(options);
factory = builder.createPeerConnectionFactory();
remoteIceCandidates = new ArrayList<>();
localMediaStreams = new HashMap<>();
remoteMediaStreams = new HashMap<>();
defaultConstraints = new MediaConstraints();
defaultConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
defaultConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
configuration = new PeerConnection.RTCConfiguration(iceServers);
VideoEncoderFactory encoderFactory;
VideoDecoderFactory decoderFactory;
if (FancyWebRTCEglUtils.getRootEglBaseContext() != null) {
encoderFactory = new DefaultVideoEncoderFactory(FancyWebRTCEglUtils.getRootEglBaseContext(), false, false);
decoderFactory = new DefaultVideoDecoderFactory(FancyWebRTCEglUtils.getRootEglBaseContext());
} else {
encoderFactory = new SoftwareVideoEncoderFactory();
decoderFactory = new SoftwareVideoDecoderFactory();
}
builder.setVideoDecoderFactory(decoderFactory);
builder.setVideoEncoderFactory(encoderFactory);
configuration.enableDtlsSrtp = true;
configuration.enableRtpDataChannel = true;
configuration.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED;
configuration.bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE;
configuration.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.REQUIRE;
configuration.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY;
configuration.keyType = PeerConnection.KeyType.ECDSA;
}
});
}
public enum DataChannelState {
CONNECTING, OPEN, CLOSING, CLOSED
}
public void toggleSpeaker() {
AudioManager manager = (AudioManager) appContext.getSystemService(Context.AUDIO_SERVICE);
if (manager != null) {
if (manager.isSpeakerphoneOn()) {
manager.setSpeakerphoneOn(false);
} else {
manager.setSpeakerphoneOn(true);
}
}
}
public void toggleMic() {
AudioManager manager = (AudioManager) appContext.getSystemService(Context.AUDIO_SERVICE);
if (manager != null) {
if (manager.isMicrophoneMute()) {
manager.setMicrophoneMute(false);
} else {
manager.setMicrophoneMute(true);
}
}
}
public void speakerEnabled(boolean enabled) {
AudioManager manager = (AudioManager) appContext.getSystemService(Context.AUDIO_SERVICE);
if (manager != null) {
manager.setSpeakerphoneOn(enabled);
}
}
public void micEnabled(boolean enabled) {
AudioManager manager = (AudioManager) appContext.getSystemService(Context.AUDIO_SERVICE);
if (manager != null) {
manager.setMicrophoneMute(enabled);
}
}
public enum DataChannelMessageType {
TEXT, BINARY
}
public void setListener(FancyWebRTCListener listener) {
this.listener = listener;
}
public void connect() {
if (connection != null)
return;
executor.execute(new Runnable() {
@Override
public void run() {
connection = factory.createPeerConnection(configuration, new PeerConnection.Observer() {
@Override
public void onSignalingChange(PeerConnection.SignalingState signalingState) {
if (listener != null) {
listener.webRTCClientOnSignalingChange(ref.get(), signalingState);
}
}
@Override
public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
if (listener != null) {
listener.webRTCClientOnIceConnectionChange(ref.get(), iceConnectionState);
}
}
@Override
public void onIceConnectionReceivingChange(boolean b) {
if (listener != null) {
listener.webRTCClientOnIceConnectionReceivingChange(ref.get(), b);
}
}
@Override
public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
if (listener != null) {
listener.webRTCClientOnIceGatheringChange(ref.get(), iceGatheringState);
}
}
@Override
public void onIceCandidate(IceCandidate iceCandidate) {
if (listener != null) {
listener.webRTCClientDidGenerateIceCandidate(ref.get(), iceCandidate);
}
}
@Override
public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {
if (listener != null) {
listener.webRTCClientOnIceCandidatesRemoved(ref.get(), iceCandidates);
}
}
@Override
public void onAddStream(MediaStream mediaStream) {
if (listener != null) {
if (mediaStream.videoTracks.size() > 0) {
listener.webRTCClientDidReceiveStream(ref.get(), mediaStream);
}
}
}
@Override
public void onRemoveStream(MediaStream mediaStream) {
if (listener != null) {
listener.webRTCClientOnRemoveStream(ref.get(), mediaStream);
}
}
@Override
public void onDataChannel(DataChannel dataChannel) {
dataChannels.put(dataChannel.label(), dataChannel);
registerDataChannelObserver(dataChannel.label());
}
@Override
public void onRenegotiationNeeded() {
if (listener != null) {
listener.webRTCClientOnRenegotiationNeeded(ref.get());
}
}
@Override
public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {
}
@Override
public void onTrack(RtpTransceiver rtpTransceiver) {
}
});
}
});
}
private void registerDataChannelObserver(String name) {
final DataChannel dataChannel = dataChannels.get(name);
executor.execute(new Runnable() {
@Override
public void run() {
if (dataChannel != null) {
dataChannel.registerObserver(new DataChannel.Observer() {
@Override
public void onBufferedAmountChange(long l) {
}
@Override
public void onStateChange() {
if (listener != null) {
listener.webRTCClientDataChannelStateChanged(ref.get(), dataChannel.label(), dataChannel.state());
}
}
@Override
public void onMessage(DataChannel.Buffer buffer) {
byte[] bytes;
if (buffer.data.hasArray()) {
bytes = buffer.data.array();
} else {
bytes = new byte[buffer.data.remaining()];
buffer.data.get(bytes);
}
DataChannelMessageType type;
String data;
if (buffer.binary) {
type = DataChannelMessageType.BINARY;
data = Base64.encodeToString(bytes, Base64.NO_WRAP);
} else {
type = DataChannelMessageType.TEXT;
data = new String(bytes, Charset.forName("UTF-8"));
}
if (listener != null) {
listener.webRTCClientDataChannelMessageType(ref.get(), dataChannel.label(), data, type);
}
}
});
}
}
});
}
public void enableTrack(String id, boolean enabled) {
MediaData mediaData = tracks.get(id);
if (mediaData != null) {
if (mediaData.capturer != null) {
if (enabled) {
mediaData.track.setEnabled(true);
mediaData.capturer.startVideo(appContext);
} else {
mediaData.track.setEnabled(false);
mediaData.capturer.stopVideo();
}
} else {
mediaData.track.setEnabled(enabled);
}
}
}
public void switchCamera(String id) {
final MediaData mediaData = tracks.get(id);
executor.execute(new Runnable() {
@Override
public void run() {
if (mediaData != null) {
if (mediaData.capturer != null) {
mediaData.capturer.toggleCamera();
}
}
}
});
}
public FancyWebRTCListener getListener() {
return listener;
}
public void disconnect() {
executor.execute(new Runnable() {
@Override
public void run() {
if (connection == null)
return;
for (MediaStream stream : localMediaStreams.values()) {
stream.dispose();
}
connection.close();
connection.dispose();
}
});
}
public void dataChannelSend(String name, final String data, final DataChannelMessageType type) {
final DataChannel channel = dataChannels.get(name);
executor.execute(new Runnable() {
@Override
public void run() {
if (channel != null) {
byte[] dataArray = null;
switch(type) {
case BINARY:
dataArray = Base64.decode(data, Base64.NO_WRAP);
break;
case TEXT:
try {
dataArray = data.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
return;
}
break;
}
ByteBuffer byteBuffer = ByteBuffer.wrap(dataArray);
DataChannel.Buffer buffer = new DataChannel.Buffer(byteBuffer, type == DataChannelMessageType.BINARY);
channel.send(buffer);
}
}
});
}
public void dataChannelClose(String name) {
final DataChannel channel = dataChannels.get(name);
executor.execute(new Runnable() {
@Override
public void run() {
if (channel != null) {
channel.close();
}
}
});
}
public void dataChannelCreate(final String name) {
final DataChannel.Init initData = new DataChannel.Init();
executor.execute(new Runnable() {
@Override
public void run() {
DataChannel channel = connection.createDataChannel(name, initData);
dataChannels.put(name, channel);
registerDataChannelObserver(name);
}
});
}
public void handleAnswerReceived(final SessionDescription sdp) {
executor.execute(new Runnable() {
@Override
public void run() {
if (connection == null || sdp == null)
return;
SessionDescription newSdp = new SessionDescription(SessionDescription.Type.ANSWER, sdp.description);
connection.setRemoteDescription(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
}
@Override
public void onSetSuccess() {
}
@Override
public void onCreateFailure(String s) {
}
@Override
public void onSetFailure(String s) {
if (listener != null) {
listener.webRTCClientDidReceiveError(ref.get(), s);
}
}
}, newSdp);
}
});
}
public void createAnswerForOfferReceived(final SessionDescription remoteSdp, MediaConstraints constraints) {
executor.execute(new Runnable() {
@Override
public void run() {
if (connection == null || remoteSdp == null)
return;
if (connection.getRemoteDescription() != null && (connection.getRemoteDescription().type == SessionDescription.Type.ANSWER && remoteSdp.type == SessionDescription.Type.ANSWER))
return;
connection.setRemoteDescription(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
}
@Override
public void onSetSuccess() {
handleRemoteDescriptionSet();
connection.createAnswer(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
handleSdpGenerated(sessionDescription);
if (listener != null) {
listener.webRTCClientStartCallWithSdp(ref.get(), sessionDescription);
}
}
@Override
public void onSetSuccess() {
}
@Override
public void onCreateFailure(String s) {
}
@Override
public void onSetFailure(String s) {
if (listener != null) {
listener.webRTCClientDidReceiveError(ref.get(), s);
}
}
}, defaultConstraints);
}
@Override
public void onCreateFailure(String s) {
}
@Override
public void onSetFailure(String s) {
if (listener != null) {
listener.webRTCClientDidReceiveError(ref.get(), s);
}
}
}, remoteSdp);
}
});
}
public void addIceCandidate(final IceCandidate iceCandidate) {
executor.execute(new Runnable() {
@Override
public void run() {
if (connection != null && connection.getRemoteDescription() != null) {
connection.addIceCandidate(iceCandidate);
} else {
remoteIceCandidates.add(iceCandidate);
}
}
});
}
public MediaStream getLocalMediaStream(String id) {
return localMediaStreams.get(id);
}
public MediaStream getRemoteMediaStream(String id) {
return remoteMediaStreams.get(id);
}
public void addLocalStream(final MediaStream stream) {
executor.execute(new Runnable() {
@Override
public void run() {
localMediaStreams.put(stream.getId(), stream);
connection.addStream(stream);
}
});
}
public void addRemoteStream(MediaStream stream) {
remoteMediaStreams.put(stream.getId(), stream);
}
public void makeOffer(MediaConstraints constraints) {
executor.execute(new Runnable() {
@Override
public void run() {
if (connection != null) {
connection.createOffer(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
handleSdpGenerated(sessionDescription);
}
@Override
public void onSetSuccess() {
}
@Override
public void onCreateFailure(String s) {
if (listener != null) {
listener.webRTCClientDidReceiveError(ref.get(), s);
}
}
@Override
public void onSetFailure(String s) {
}
}, defaultConstraints);
}
}
});
}
public void getUserMedia(final Quality quality, final FancyWebRTCListener.GetUserMediaListener getUserMediaListener) {
final String streamId = randomId();
if (!FancyWebRTC.hasPermissions(appContext)) {
boolean videoPermission = ContextCompat.checkSelfPermission(appContext, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
boolean audioPermission = ContextCompat.checkSelfPermission(appContext, android.Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED;
String error = "";
if (!videoPermission && !audioPermission) {
error = "Camera and Record Audio Permission needed.";
} else if (!videoPermission) {
error = "Camera Permission needed.";
} else if (!audioPermission) {
error = "Record Audio Permission needed.";
}
getUserMediaListener.webRTCClientOnGetUserMediaDidReceiveError(ref.get(), error);
return;
}
executor.execute(new Runnable() {
@Override
public void run() {
MediaStream stream = factory.createLocalMediaStream(streamId);
FancyWebRTCCapturer capturer = createCapturer(quality);
VideoSource videoSource = factory.createVideoSource(false);
String videoTrackId = randomId();
VideoTrack videoTrack = factory.createVideoTrack(videoTrackId, videoSource);
if (videoEnabled && ContextCompat.checkSelfPermission(appContext, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
videoTrack.setEnabled(true);
} else {
videoTrack.setEnabled(false);
}
capturer.setVideoSource(videoSource);
capturer.startVideo(appContext);
tracks.put(videoTrackId, new MediaData(videoSource, videoTrack, capturer));
stream.addTrack(videoTrack);
AudioSource audioSource = factory.createAudioSource(new MediaConstraints());
String audioTrackId = randomId();
AudioTrack audioTrack = factory.createAudioTrack(audioTrackId, audioSource);
if (audioEnabled && (ContextCompat.checkSelfPermission(appContext, android.Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED)) {
audioTrack.setEnabled(true);
} else {
audioTrack.setEnabled(false);
}
tracks.put(audioTrack.id(), new MediaData(audioSource, audioTrack, null));
stream.addTrack(audioTrack);
getUserMediaListener.webRTCClientOnGetUserMedia(ref.get(), stream);
}
});
}
private void handleRemoteDescriptionSet() {
executor.execute(new Runnable() {
@Override
public void run() {
for (IceCandidate iceCandidate : remoteIceCandidates) {
connection.addIceCandidate(iceCandidate);
}
remoteIceCandidates.clear();
}
});
}
private void handleSdpGenerated(final SessionDescription sdp) {
executor.execute(new Runnable() {
@Override
public void run() {
if (connection == null)
return;
if (connection.getLocalDescription() != null && (connection.getLocalDescription().type == SessionDescription.Type.ANSWER && sdp.type == SessionDescription.Type.ANSWER))
return;
connection.setLocalDescription(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
}
@Override
public void onSetSuccess() {
if (listener != null) {
listener.webRTCClientStartCallWithSdp(ref.get(), sdp);
}
}
@Override
public void onCreateFailure(String s) {
}
@Override
public void onSetFailure(String s) {
if (listener != null) {
listener.webRTCClientDidReceiveError(ref.get(), s);
}
}
}, sdp);
}
});
}
public FancyWebRTCCapturer createCapturer(Quality quality) {
CameraEnumerator enumerator;
if (Camera2Enumerator.isSupported(appContext)) {
enumerator = new Camera2Enumerator(appContext);
} else {
enumerator = new Camera1Enumerator(false);
}
String[] deviceNames = enumerator.getDeviceNames();
FancyWebRTCCapturer videoCapturer = null;
for (String deviceName : deviceNames) {
List<CameraEnumerationAndroid.CaptureFormat> formatList = enumerator.getSupportedFormats(deviceName);
// Default to lowest
CameraEnumerationAndroid.CaptureFormat selectedFormat = formatList.get(formatList.size() - 1);
for (CameraEnumerationAndroid.CaptureFormat format : enumerator.getSupportedFormats(deviceName)) {
if (quality == Quality.LOWEST) {
selectedFormat = formatList.get(formatList.size() - 1);
break;
} else if (quality == Quality.HIGHEST) {
selectedFormat = formatList.get(0);
break;
} else if (quality == Quality.MAX_480P && format.height == 480) {
selectedFormat = format;
break;
} else if (quality == Quality.MAX_720P && format.height == 720) {
selectedFormat = format;
break;
} else if (quality == Quality.MAX_1080P && format.height == 1080) {
selectedFormat = format;
break;
} else if (quality == Quality.MAX_2160P && format.height == 2160) {
selectedFormat = format;
break;
}
}
if (enumerator.isFrontFacing(deviceName)) {
CameraVideoCapturer capturer = enumerator.createCapturer(deviceName, null);
if (capturer != null) {
FancyWebRTCCapturer fancyWebRTCCapturer = new FancyWebRTCCapturer(ref.get(), capturer);
fancyWebRTCCapturer.setFormat(selectedFormat);
return fancyWebRTCCapturer;
}
}
}
for (String deviceName : deviceNames) {
if (!enumerator.isFrontFacing(deviceName)) {
List<CameraEnumerationAndroid.CaptureFormat> formatList = enumerator.getSupportedFormats(deviceName);
// Default to lowest
CameraEnumerationAndroid.CaptureFormat selectedFormat = formatList.get(formatList.size() - 1);
for (CameraEnumerationAndroid.CaptureFormat format : enumerator.getSupportedFormats(deviceName)) {
if (quality == Quality.LOWEST) {
selectedFormat = formatList.get(formatList.size() - 1);
break;
} else if (quality == Quality.HIGHEST) {
selectedFormat = formatList.get(0);
break;
} else if (quality == Quality.MAX_480P && format.height == 480) {
selectedFormat = format;
break;
} else if (quality == Quality.MAX_720P && format.height == 720) {
selectedFormat = format;
break;
} else if (quality == Quality.MAX_1080P && format.height == 1080) {
selectedFormat = format;
break;
} else if (quality == Quality.MAX_2160P && format.height == 2160) {
selectedFormat = format;
break;
}
}
CameraVideoCapturer capturer = enumerator.createCapturer(deviceName, null);
if (capturer != null) {
FancyWebRTCCapturer fancyWebRTCCapturer = new FancyWebRTCCapturer(ref.get(), capturer);
fancyWebRTCCapturer.setFormat(selectedFormat);
return fancyWebRTCCapturer;
}
}
}
return videoCapturer;
}
private String randomId() {
return UUID.randomUUID().toString();
}
public static boolean hasPermissions(Context context) {
return ContextCompat.checkSelfPermission(context, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(context, android.Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED;
}
public static void requestPermissions(Context context) {
androidx.core.app.ActivityCompat.requestPermissions((Activity) context, WEBRTC_PERMISSIONS, WEBRTC_PERMISSIONS_REQUEST_CODE);
}
public enum Quality {
MAX_480P,
MAX_720P,
MAX_1080P,
MAX_2160P,
HIGHEST,
LOWEST
}
}
19
Source : FancyRTCPeerConnection.java
with MIT License
from triniwiz
with MIT License
from triniwiz
/**
* Created by triniwiz on 1/7/19
*/
public clreplaced FancyRTCPeerConnection {
private FancyRTCConfiguration configuration;
private PeerConnection connection;
static PeerConnectionFactory factory;
private Context context;
static final ExecutorService executor = Executors.newSingleThreadExecutor();
private FancyOnConnectionStateChangeListener onConnectionStateChangeListener;
private FancyOnTrackListener onTrackListener;
private FancyOnRemoveTrackListener onRemoveTrackListener;
private FancyOnIceGatheringStateChangeListener onIceGatheringStateChangeListener;
private FancyOnNegotiationNeededListener onNegotiationNeededListener;
private FancyOnSignalingStateChangeListener onSignalingStateChangeListener;
private FancyOnIceCandidateListener onIceCandidateListener;
private FancyOnDataChannelListener onDataChannelListener;
private FancyOnAddStreamListener onAddStreamListener;
private FancyOnRemoveStreamListener onRemoveStreamListener;
private FancyOnIceConnectionChangeListener onIceConnectionChangeListener;
private Map<String, RtpReceiver> remoteReceivers = new HashMap<>();
private Map<String, RtpTransceiver> remoteTransceivers = new HashMap<>();
static {
initFactory();
}
private static void initFactory() {
executor.execute(() -> {
PeerConnectionFactory.Builder builder = PeerConnectionFactory.builder();
PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
builder.setOptions(options);
VideoEncoderFactory encoderFactory;
VideoDecoderFactory decoderFactory;
if (FancyWebRTCEglUtils.getRootEglBaseContext() != null) {
encoderFactory = new DefaultVideoEncoderFactory(FancyWebRTCEglUtils.getRootEglBaseContext(), true, true);
decoderFactory = new DefaultVideoDecoderFactory(FancyWebRTCEglUtils.getRootEglBaseContext());
} else {
encoderFactory = new SoftwareVideoEncoderFactory();
decoderFactory = new SoftwareVideoDecoderFactory();
}
builder.setVideoDecoderFactory(decoderFactory);
builder.setVideoEncoderFactory(encoderFactory);
factory = builder.createPeerConnectionFactory();
});
}
private void init() {
executor.execute(() -> connection = factory.createPeerConnection(configuration.getConfiguration(), new PeerConnection.Observer() {
@Override
public void onConnectionChange(PeerConnection.PeerConnectionState newState) {
if (onConnectionStateChangeListener != null) {
onConnectionStateChangeListener.onChange();
}
}
@Override
public void onSignalingChange(PeerConnection.SignalingState signalingState) {
if (onSignalingStateChangeListener != null) {
onSignalingStateChangeListener.onSignalingStateChange();
}
}
@Override
public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
if (onIceConnectionChangeListener != null) {
onIceConnectionChangeListener.onChange();
}
}
@Override
public void onIceConnectionReceivingChange(boolean b) {
}
@Override
public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
if (onIceGatheringStateChangeListener != null) {
onIceGatheringStateChangeListener.onIceGatheringStateChange();
}
}
@Override
public void onIceCandidate(IceCandidate iceCandidate) {
if (onIceCandidateListener != null) {
onIceCandidateListener.onIceCandidate(new FancyRTCIceCandidate(iceCandidate));
}
}
@Override
public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {
}
@Override
public void onAddStream(MediaStream mediaStream) {
if (onAddStreamListener != null) {
onAddStreamListener.onAddStream(new FancyRTCMediaStream(mediaStream));
}
}
@Override
public void onRemoveStream(MediaStream mediaStream) {
if (onRemoveStreamListener != null) {
onRemoveStreamListener.onRemoveStream(new FancyRTCMediaStream(mediaStream));
}
}
@Override
public void onDataChannel(DataChannel dataChannel) {
if (onDataChannelListener != null) {
onDataChannelListener.onDataChannel(new FancyRTCDataChannelEvent(new FancyRTCDataChannel(dataChannel)));
}
}
@Override
public void onRenegotiationNeeded() {
if (onNegotiationNeededListener != null) {
onNegotiationNeededListener.onNegotiationNeeded();
}
}
@Override
public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {
remoteReceivers.put(FancyUtils.getUUID(), rtpReceiver);
if (onTrackListener != null) {
if (configuration.getSdpSemantics() == FancyRTCSdpSemantics.PLAN_B) {
List<FancyRTCMediaStream> list = new ArrayList<>();
for (MediaStream stream : mediaStreams) {
list.add(new FancyRTCMediaStream(stream));
}
FancyRTCTrackEvent event = new FancyRTCTrackEvent(new FancyRTCRtpReceiver(rtpReceiver), list, new FancyRTCMediaStreamTrack(rtpReceiver.track()), null);
onTrackListener.onTrack(event);
}
}
}
@Override
public void onTrack(RtpTransceiver rtpTransceiver) {
remoteTransceivers.put(FancyUtils.getUUID(), rtpTransceiver);
if (onTrackListener != null) {
FancyRTCTrackEvent event = new FancyRTCTrackEvent(new FancyRTCRtpReceiver(rtpTransceiver.getReceiver()), null, new FancyRTCMediaStreamTrack(rtpTransceiver.getReceiver().track()), new FancyRTCRtpTransceiver(rtpTransceiver));
onTrackListener.onTrack(event);
}
}
}));
}
public FancyRTCPeerConnection(Context context) {
this.context = context;
configuration = new FancyRTCConfiguration();
init();
}
public FancyRTCPeerConnection(Context context, FancyRTCConfiguration configuration) {
this.context = context;
this.configuration = configuration;
init();
}
public FancyRTCSessionDescription getLocalDescription() {
if (connection == null)
return null;
SessionDescription sdp = connection.getLocalDescription();
if (sdp == null)
return null;
return FancyRTCSessionDescription.fromRTCSessionDescription(sdp);
}
public FancyRTCSessionDescription getRemoteDescription() {
if (connection == null)
return null;
SessionDescription sdp = connection.getRemoteDescription();
if (sdp == null)
return null;
return FancyRTCSessionDescription.fromRTCSessionDescription(sdp);
}
public FancyRTCPeerConnectionState getConnectionState() {
if (connection != null) {
switch(connection.iceConnectionState()) {
case NEW:
return FancyRTCPeerConnectionState.NEW;
case CLOSED:
return FancyRTCPeerConnectionState.CLOSED;
case FAILED:
return FancyRTCPeerConnectionState.FAILED;
case CHECKING:
case COMPLETED:
case CONNECTED:
return FancyRTCPeerConnectionState.CONNECTED;
case DISCONNECTED:
return FancyRTCPeerConnectionState.DISCONNECTED;
}
}
return FancyRTCPeerConnectionState.NEW;
}
public static interface FancyOnIceConnectionChangeListener {
public void onChange();
}
public void setOnIceConnectionChange(FancyOnIceConnectionChangeListener listener) {
onIceConnectionChangeListener = listener;
}
public static interface FancyOnConnectionStateChangeListener {
public void onChange();
}
public void setOnConnectionStateChange(FancyOnConnectionStateChangeListener listener) {
onConnectionStateChangeListener = listener;
}
public static interface FancyOnTrackListener {
public void onTrack(FancyRTCTrackEvent event);
}
public void setOnTrackListener(FancyOnTrackListener listener) {
onTrackListener = listener;
}
public static interface FancyOnRemoveTrackListener {
public void onRemoveTrack();
}
public void setOnRemoveTrackListener(FancyOnRemoveTrackListener listener) {
onRemoveTrackListener = listener;
}
public static interface FancyOnRemoveStreamListener {
public void onRemoveStream(FancyRTCMediaStream stream);
}
public void setOnRemoveStreamListener(FancyOnRemoveStreamListener listener) {
onRemoveStreamListener = listener;
}
public static interface FancyOnIceGatheringStateChangeListener {
public void onIceGatheringStateChange();
}
public void setOnIceGatheringStateChangeListener(FancyOnIceGatheringStateChangeListener listener) {
onIceGatheringStateChangeListener = listener;
}
public static interface FancyOnAddStreamListener {
public void onAddStream(FancyRTCMediaStream stream);
}
public void setOnAddStreamListener(FancyOnAddStreamListener listener) {
onAddStreamListener = listener;
}
public static interface FancyOnNegotiationNeededListener {
public void onNegotiationNeeded();
}
public void setOnNegotiationNeededListener(FancyOnNegotiationNeededListener listener) {
onNegotiationNeededListener = listener;
}
public static interface FancyOnSignalingStateChangeListener {
public void onSignalingStateChange();
}
public void setOnSignalingStateChangeListener(FancyOnSignalingStateChangeListener listener) {
onSignalingStateChangeListener = listener;
}
public static interface FancyOnIceCandidateListener {
public void onIceCandidate(FancyRTCIceCandidate candidate);
}
public void setOnIceCandidateListener(FancyOnIceCandidateListener listener) {
onIceCandidateListener = listener;
}
public static interface FancyOnDataChannelListener {
public void onDataChannel(FancyRTCDataChannelEvent event);
}
public void setOnDataChannelListener(FancyOnDataChannelListener listener) {
onDataChannelListener = listener;
}
public List<FancyRTCIceServer> getDefaultIceServers() {
List<FancyRTCIceServer> list = new ArrayList<>();
String[] defaultIceServers = new String[] { "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 server : defaultIceServers) {
list.add(new FancyRTCIceServer(server));
}
return list;
}
public void addIceCandidate(FancyRTCIceCandidate candidate) {
executor.execute(() -> {
if (connection != null) {
connection.addIceCandidate(candidate.getIceCandidate());
}
});
}
public void addTrack(FancyRTCMediaStreamTrack track, List<String> streamIds) {
executor.execute(() -> {
if (connection != null) {
connection.addTrack(track.getMediaStreamTrack(), streamIds);
}
});
}
public void close() {
executor.execute(() -> {
if (connection != null) {
connection.close();
}
});
}
public static interface SdpCreateListener {
public void onSuccess(FancyRTCSessionDescription description);
public void onError(String error);
}
public static interface SdpSetListener {
public void onSuccess();
public void onError(String error);
}
public FancyRTCDataChannel createDataChannel(String label, FancyRTCDataChannelInit init) {
if (connection != null) {
DataChannel channel = connection.createDataChannel(label, init.getInit());
return new FancyRTCDataChannel(channel);
}
return null;
}
public void dispose() {
executor.execute(() -> {
if (connection != null) {
connection.dispose();
}
});
}
public void createAnswer(FancyRTCMediaConstraints mediaConstraints, SdpCreateListener listener) {
executor.execute(() -> {
if (connection != null) {
if (!mediaConstraints.mandatory.contains(new FancyRTCMediaConstraints.KeyValue("OfferToReceiveVideo", "true")) || !mediaConstraints.mandatory.contains(new FancyRTCMediaConstraints.KeyValue("OfferToReceiveVideo", "false"))) {
mediaConstraints.mandatory.add(new FancyRTCMediaConstraints.KeyValue("OfferToReceiveVideo", "true"));
}
if (!mediaConstraints.mandatory.contains(new FancyRTCMediaConstraints.KeyValue("OfferToReceiveAudio", "true")) || !mediaConstraints.mandatory.contains(new FancyRTCMediaConstraints.KeyValue("OfferToReceiveAudio", "false"))) {
mediaConstraints.mandatory.add(new FancyRTCMediaConstraints.KeyValue("OfferToReceiveAudio", "true"));
}
connection.createAnswer(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
listener.onSuccess(FancyRTCSessionDescription.fromRTCSessionDescription(sessionDescription));
}
@Override
public void onSetSuccess() {
}
@Override
public void onCreateFailure(String s) {
listener.onError(s);
}
@Override
public void onSetFailure(String s) {
}
}, mediaConstraints.getMediaConstraints());
}
});
}
public void createOffer(FancyRTCMediaConstraints mediaConstraints, SdpCreateListener listener) {
executor.execute(() -> {
if (connection != null) {
if (!mediaConstraints.mandatory.contains(new FancyRTCMediaConstraints.KeyValue("OfferToReceiveVideo", "true")) || !mediaConstraints.mandatory.contains(new FancyRTCMediaConstraints.KeyValue("OfferToReceiveVideo", "false"))) {
mediaConstraints.mandatory.add(new FancyRTCMediaConstraints.KeyValue("OfferToReceiveVideo", "true"));
}
if (!mediaConstraints.mandatory.contains(new FancyRTCMediaConstraints.KeyValue("OfferToReceiveAudio", "true")) || !mediaConstraints.mandatory.contains(new FancyRTCMediaConstraints.KeyValue("OfferToReceiveAudio", "false"))) {
mediaConstraints.mandatory.add(new FancyRTCMediaConstraints.KeyValue("OfferToReceiveAudio", "true"));
}
connection.createOffer(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
listener.onSuccess(FancyRTCSessionDescription.fromRTCSessionDescription(sessionDescription));
}
@Override
public void onSetSuccess() {
}
@Override
public void onCreateFailure(String s) {
listener.onError(s);
}
@Override
public void onSetFailure(String s) {
}
}, mediaConstraints.getMediaConstraints());
}
});
}
public void setLocalDescription(FancyRTCSessionDescription sdp, SdpSetListener listener) {
executor.execute(() -> {
if (connection != null) {
connection.setLocalDescription(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
}
@Override
public void onSetSuccess() {
listener.onSuccess();
}
@Override
public void onCreateFailure(String s) {
}
@Override
public void onSetFailure(String s) {
listener.onError(s);
}
}, sdp.getSessionDescription());
}
});
}
public void setRemoteDescription(FancyRTCSessionDescription sdp, SdpSetListener listener) {
executor.execute(() -> {
if (connection != null) {
connection.setRemoteDescription(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
}
@Override
public void onSetSuccess() {
listener.onSuccess();
}
@Override
public void onCreateFailure(String s) {
}
@Override
public void onSetFailure(String s) {
listener.onError(s);
}
}, sdp.getSessionDescription());
}
});
}
public PeerConnection getConnection() {
return connection;
}
public List<FancyRTCRtpSender> getSenders() {
List<FancyRTCRtpSender> senders = new ArrayList<>();
for (RtpSender sender : connection.getSenders()) {
senders.add(new FancyRTCRtpSender(sender));
}
return senders;
}
AudioDeviceModule createJavaAudioDevice() {
return JavaAudioDeviceModule.builder(context).setSamplesReadyCallback(new JavaAudioDeviceModule.SamplesReadyCallback() {
@Override
public void onWebRtcAudioRecordSamplesReady(JavaAudioDeviceModule.AudioSamples audioSamples) {
}
}).setUseHardwareAcousticEchoCanceler(true).setUseHardwareNoiseSuppressor(true).setAudioRecordErrorCallback(new JavaAudioDeviceModule.AudioRecordErrorCallback() {
@Override
public void onWebRtcAudioRecordInitError(String s) {
}
@Override
public void onWebRtcAudioRecordStartError(JavaAudioDeviceModule.AudioRecordStartErrorCode audioRecordStartErrorCode, String s) {
}
@Override
public void onWebRtcAudioRecordError(String s) {
}
}).setAudioTrackErrorCallback(new JavaAudioDeviceModule.AudioTrackErrorCallback() {
@Override
public void onWebRtcAudioTrackInitError(String s) {
}
@Override
public void onWebRtcAudioTrackStartError(JavaAudioDeviceModule.AudioTrackStartErrorCode audioTrackStartErrorCode, String s) {
}
@Override
public void onWebRtcAudioTrackError(String s) {
}
}).createAudioDeviceModule();
}
}
19
Source : PeerConnectionWrapper.java
with GNU Affero General Public License v3.0
from threema-ch
with GNU Affero General Public License v3.0
from threema-ch
/**
* Wrapper around the WebRTC PeerConnection.
*
* This handles everything from creating the peer connection
* to destroying it afterwards.
*/
@SameThread
public clreplaced PeerConnectionWrapper {
private static final String THREEMA_DC_LABEL = "THREEMA";
// Logger
private final Logger logger = LoggerFactory.getLogger(PeerConnectionWrapper.clreplaced);
// Worker thread handler
@NonNull
private final HandlerExecutor handler;
// WebRTC / SaltyRTC
@NonNull
private final PeerConnectionFactory factory;
@NonNull
private final org.webrtc.PeerConnection pc;
@NonNull
private final WebRTCTask task;
private final boolean allowIpv6;
// State
@NonNull
private CompletableFuture<Void> readyToSetRemoteDescription = new CompletableFuture<>();
@NonNull
private CompletableFuture<Void> readyToAddRemoteCandidates = new CompletableFuture<>();
@NonNull
private CompletableFuture<Void> readyToSendLocalCandidates = new CompletableFuture<>();
@NonNull
private PeerConnectionState state = PeerConnectionState.NEW;
private boolean disposed = false;
// Listener
@NonNull
private final PeerConnectionListener listener;
/**
* Return a PeerConnectionFactory instance used for Threema Web.
*/
public static PeerConnectionFactory getPeerConnectionFactory() {
return PeerConnectionFactory.builder().createPeerConnectionFactory();
}
/**
* Return the RTCConfiguration used for Threema Web.
*/
public static PeerConnection.RTCConfiguration getRTCConfiguration(@NonNull final Logger logger) throws Exception {
// Set ICE servers
final List<org.webrtc.PeerConnection.IceServer> iceServers = new ArrayList<>();
final APIConnector.TurnServerInfo turnServerInfo = Config.getTurnServerCache().getTurnServers();
final List<String> turnServers = Arrays.asList(turnServerInfo.turnUrls);
StreamSupport.stream(turnServers).map(server -> PeerConnection.IceServer.builder(server).setUsername(turnServerInfo.turnUsername).setPreplacedword(turnServerInfo.turnPreplacedword).createIceServer()).forEach(iceServers::add);
logger.debug("Using ICE servers: {}", turnServers);
// Set up RTC configuration
final PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(iceServers);
rtcConfig.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_ONCE;
rtcConfig.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN;
return rtcConfig;
}
/**
* Initialize a peer connection.
*/
public PeerConnectionWrapper(@NonNull final String logPrefix, @NonNull final Context appContext, @NonNull final HandlerExecutor handler, @NonNull final WebRTCTask task, @NonNull final TemporaryTaskEventHandler temporaryTaskEventHandler, final boolean allowIpv6, @NonNull final PeerConnectionListener listener) throws Exception {
// Set logger prefix
if (logger instanceof ThreemaLogger) {
((ThreemaLogger) logger).setPrefix(logPrefix);
}
logger.info("Initialize WebRTC PeerConnection");
// Initialise WebRTC for Android
WebRTCUtil.initializeAndroidGlobals(appContext);
this.factory = getPeerConnectionFactory();
// Store handler, listener, task and set message handler
this.handler = handler;
this.listener = listener;
this.task = task;
temporaryTaskEventHandler.replace(this.task, new TaskMessageHandler());
this.allowIpv6 = allowIpv6;
// Create peer connection
final PeerConnection peerConnection = factory.createPeerConnection(getRTCConfiguration(logger), new PeerConnectionObserver());
if (peerConnection == null) {
throw new RuntimeException("Could not create peer connection: createPeerConnection returned null");
}
this.pc = peerConnection;
}
/**
* If the instance is disposed, throw an exception.
*/
private void ensureNotDisposed() {
if (this.disposed) {
throw new IllegalStateException("PeerConnection is disposed");
}
}
/**
* Handler for incoming task messages.
*/
@AnyThread
private clreplaced TaskMessageHandler implements org.saltyrtc.tasks.webrtc.events.MessageHandler {
@Override
public void onOffer(@NonNull final Offer offer) {
PeerConnectionWrapper.this.readyToSetRemoteDescription.thenRunAsync(new Runnable() {
@Override
@WorkerThread
public void run() {
if (PeerConnectionWrapper.this.disposed) {
logger.warn("Ignoring offer, peer connection already disposed");
} else {
PeerConnectionWrapper.this.onOfferReceived(offer);
}
}
}, PeerConnectionWrapper.this.handler.getExecutor());
}
@Override
public void onAnswer(@NonNull final Answer answer) {
logger.warn("Ignoring answer");
}
@Override
public void onCandidates(@NonNull final Candidate[] candidates) {
PeerConnectionWrapper.this.readyToAddRemoteCandidates.thenRunAsync(new Runnable() {
@Override
@WorkerThread
public void run() {
if (PeerConnectionWrapper.this.disposed) {
logger.warn("Ignoring candidates, peer connection already disposed");
} else {
PeerConnectionWrapper.this.onIceCandidatesReceived(candidates);
}
}
}, PeerConnectionWrapper.this.handler.getExecutor());
}
}
/**
* A WebRTC offer was received. Set the remote description.
*/
@AnyThread
private void onOfferReceived(@NonNull final Offer offer) {
logger.info("Offer received, applying as remote description");
this.pc.setRemoteDescription(new SdpObserver() {
@Override
@AnyThread
public void onCreateSuccess(@NonNull final SessionDescription description) {
// Unreachable
}
@Override
@AnyThread
public void onCreateFailure(@NonNull final String error) {
// Unreachable
}
@Override
@AnyThread
public void onSetSuccess() {
PeerConnectionWrapper.this.onRemoteDescriptionSet();
}
@Override
@AnyThread
public void onSetFailure(@NonNull final String error) {
logger.error("Could not apply remote description: {}", error);
}
}, new SessionDescription(SessionDescription.Type.OFFER, offer.getSdp()));
}
/**
* The remote description was set. Create and send an answer.
*/
@AnyThread
private void onRemoteDescriptionSet() {
logger.info("Remote description applied successfully, creating answer");
this.pc.createAnswer(new SdpObserver() {
@Nullable
private SessionDescription description;
@Override
@AnyThread
public synchronized void onCreateSuccess(@NonNull final SessionDescription description) {
logger.info("Created answer");
this.description = description;
PeerConnectionWrapper.this.pc.setLocalDescription(this, description);
}
@Override
@AnyThread
public void onCreateFailure(@NonNull final String error) {
logger.error("Could not create answer: {}", error);
}
@Override
@AnyThread
public synchronized void onSetSuccess() {
logger.info("Local description applied successfully, sending answer");
final Answer answer = new Answer(Objects.requireNonNull(this.description).description);
PeerConnectionWrapper.this.handler.post(new Runnable() {
@Override
@WorkerThread
public void run() {
logger.debug("Sending answer");
try {
// Send the answer
PeerConnectionWrapper.this.task.sendAnswer(answer);
// Signal that local ICE candidates may be sent now
PeerConnectionWrapper.this.readyToSendLocalCandidates.complete(null);
} catch (ConnectionException error) {
logger.error("Could not send answer", error);
}
}
});
}
@Override
@AnyThread
public void onSetFailure(@NonNull final String error) {
logger.error("Could not set local description: {}", error);
}
}, new MediaConstraints());
// Signal that remote ICE candidates may be added now (delayed to rule
// out weird state bugs in libwebrtc)
PeerConnectionWrapper.this.handler.post(new Runnable() {
@Override
@WorkerThread
public void run() {
PeerConnectionWrapper.this.readyToAddRemoteCandidates.complete(null);
}
});
}
/**
* One or more ICE candidates were received. Add them.
*/
@AnyThread
private void onIceCandidatesReceived(@NonNull final Candidate[] candidates) {
int added = 0;
for (Candidate candidate : candidates) {
// Ignore without m-line
if (candidate.getSdpMLineIndex() == null) {
logger.warn("Received candidate without SdpMLineIndex, ignoring: {}", candidate.getSdp());
continue;
}
// Ignore candidates with empty SDP
if (candidate.getSdp() == null || candidate.getSdp().trim().equals("")) {
logger.warn("Received candidate with empty SDP, ignoring");
continue;
}
// Ignore IPv6 (if requested)
if (!this.allowIpv6 && SdpUtil.isIpv6Candidate(candidate.getSdp())) {
logger.info("Ignoring IPv6 candidate due to settings: {}", candidate.getSdp());
continue;
}
// Add candidate
logger.info("Adding peer ICE candidate: {}", candidate.getSdp());
this.pc.addIceCandidate(new IceCandidate(candidate.getSdpMid(), candidate.getSdpMLineIndex(), candidate.getSdp()));
added++;
}
logger.info("Added {} ICE candidate(s) from peer", added);
if (added < candidates.length) {
logger.info("Ignored {} remote candidate(s) from peer", candidates.length - added);
}
}
/**
* Return the wrapped PeerConnection.
*/
public org.webrtc.PeerConnection getPeerConnection() {
this.ensureNotDisposed();
return this.pc;
}
/**
* Return the peer connection state.
*/
@NonNull
public synchronized PeerConnectionState getState() {
return this.state;
}
/**
* Set the peer connection state and notify listeners.
*/
@AnyThread
private synchronized void setState(@NonNull final PeerConnectionState state) {
final PeerConnectionState current = this.state;
if (this.disposed) {
logger.warn("PeerConnection is disposed, ignoring state change from {} to {}", current, state);
return;
}
// Update state
this.state = state;
logger.info("PeerConnectionState changed to {}", state);
// Fire state event
this.listener.onStateChanged(current, state);
}
/**
* Close the peer connection and dispose allocated resources.
*
* This results in a terminal state. After calling this method,
* the instance MUST not be used anymore.
*/
public void dispose() {
logger.info("dispose()");
if (this.disposed) {
logger.warn("Not disposing: Already disposed");
return;
}
synchronized (this) {
// Mark this instance as disposed
this.disposed = true;
}
// Close and dispose peer connection.
// (The `dispose()` method implicitly calls `close()`)
logger.trace("Closing peer connection");
pc.close();
logger.trace("Disposing peer connection");
pc.dispose();
logger.trace("Disposed peer connection");
// Dispose the peer connection factory.
logger.trace("Disposing factory");
factory.dispose();
logger.trace("Disposed factory");
logger.info("All native resources disposed");
synchronized (this) {
// Set state to CLOSED
this.state = PeerConnectionState.CLOSED;
// Fire state event
this.listener.onStateChanged(this.state, PeerConnectionState.CLOSED);
}
}
private clreplaced PeerConnectionObserver implements org.webrtc.PeerConnection.Observer {
@Override
@AnyThread
public void onSignalingChange(@NonNull final org.webrtc.PeerConnection.SignalingState state) {
logger.info("Signaling state change to {}", state.name());
}
@Override
@AnyThread
public void onIceConnectionChange(@NonNull final IceConnectionState state) {
logger.info("ICE connection state change to {}", state.name());
switch(state) {
case NEW:
PeerConnectionWrapper.this.setState(PeerConnectionState.NEW);
break;
case CHECKING:
case DISCONNECTED:
PeerConnectionWrapper.this.setState(PeerConnectionState.CONNECTING);
break;
case CONNECTED:
case COMPLETED:
PeerConnectionWrapper.this.setState(PeerConnectionState.CONNECTED);
break;
case FAILED:
PeerConnectionWrapper.this.setState(PeerConnectionState.FAILED);
PeerConnectionWrapper.this.logStatus();
break;
case CLOSED:
PeerConnectionWrapper.this.setState(PeerConnectionState.CLOSED);
break;
default:
logger.error("Unknown ICE connection state: {}", state);
}
}
@Override
@AnyThread
public void onIceConnectionReceivingChange(final boolean noIdeaWhatThisIs) {
}
@Override
@AnyThread
public void onIceGatheringChange(@NonNull final IceGatheringState state) {
logger.info("ICE gathering state change to {}", state.name());
}
/**
* A new ICE candidate was generated. Send it to the peer.
*/
@Override
@AnyThread
public void onIceCandidate(@NonNull final IceCandidate candidate) {
logger.info("New local ICE candidate: {}", candidate.sdp);
// Check if loopback
if (SdpUtil.isLoopbackCandidate(candidate.sdp)) {
logger.info("Ignored local loopback candidate");
return;
}
// Check if IPv6
if (!allowIpv6 && SdpUtil.isIpv6Candidate(candidate.sdp)) {
logger.info("Ignored local IPv6 candidate (disabled via preferences)");
return;
}
// Send candidate when ready
PeerConnectionWrapper.this.readyToSendLocalCandidates.thenRunAsync(new Runnable() {
@Override
@WorkerThread
public void run() {
logger.debug("Sending ICE candidate");
try {
final Candidate[] candidates = new Candidate[] { new Candidate(candidate.sdp, candidate.sdpMid, candidate.sdpMLineIndex) };
PeerConnectionWrapper.this.task.sendCandidates(candidates);
} catch (ConnectionException error) {
logger.error("Could not send ICE candidate", error);
}
}
}, PeerConnectionWrapper.this.handler.getExecutor());
}
@Override
@AnyThread
public void onIceCandidatesRemoved(@NonNull final IceCandidate[] iceCandidates) {
// Legacy nonsense
if (logger.isInfoEnabled()) {
logger.info("Ignoring removed candidates: {}", Arrays.toString(iceCandidates));
}
}
@Override
@AnyThread
public void onRenegotiationNeeded() {
logger.info("Negotiation needed");
PeerConnectionWrapper.this.setState(PeerConnectionState.CONNECTING);
// Signal that a remote description may now be safely set (delayed to rule
// out weird state bugs in libwebrtc)
PeerConnectionWrapper.this.handler.post(new Runnable() {
@Override
@WorkerThread
public void run() {
PeerConnectionWrapper.this.readyToSetRemoteDescription.complete(null);
}
});
}
@Override
@AnyThread
public void onAddTrack(@NonNull final RtpReceiver rtpReceiver, @NonNull final MediaStream[] mediaStreams) {
logger.error("onAddTrack (in web client)");
}
@Override
@AnyThread
public void onAddStream(@NonNull final MediaStream mediaStream) {
logger.error("onAddStream (in web client)");
}
@Override
@AnyThread
public void onRemoveStream(@NonNull final MediaStream mediaStream) {
logger.error("onRemoveStream (in web client)");
}
@Override
@AnyThread
public void onDataChannel(@NonNull final DataChannel dc) {
final String label = dc.label();
logger.info("New data channel: {}", label);
if (!THREEMA_DC_LABEL.equals(label)) {
logger.warn("Ignoring new data channel (wrong label).");
return;
}
// Fire data channel event
PeerConnectionWrapper.this.listener.onDataChannel(dc);
}
}
@AnyThread
public long getMaxMessageSize() {
// Sigh... still not supported by libwebrtc, so fallback to a
// well-known (and, frankly, terribly small) value.
return 64 * 1024;
}
/**
* Log connection status info to the Android log.
*/
@AnyThread
private synchronized void logStatus() {
logger.debug("*** CONNECTION STATUS");
logger.debug("Aggregated state: {}", this.state);
logger.debug("ICE connection state: {}", this.pc.iceConnectionState());
logger.debug("ICE gathering state: {}", this.pc.iceGatheringState());
logger.debug("Signaling state: {}", this.pc.signalingState());
logger.debug("*** END CONNECTION STATUS");
}
}
19
Source : WebDiagnosticsActivity.java
with GNU Affero General Public License v3.0
from threema-ch
with GNU Affero General Public License v3.0
from threema-ch
@SuppressWarnings("FieldCanBeLocal")
@UiThread
public clreplaced WebDiagnosticsActivity extends ThreemaToolbarActivity implements TextEntryDialog.TextEntryDialogClickListener {
private static final Logger logger = LoggerFactory.getLogger(WebDiagnosticsActivity.clreplaced);
private static final String DIALOG_TAG_SEND_VOIP_DEBUG = "svd";
// Config
private static final String WS_HOST = "saltyrtc-ee.threema.ch";
private static final String WS_BASE_URL = "wss://" + WS_HOST;
private static final String WS_PATH = "ffffffffffffffff000000000000eeeeeeee000000000000ffffffffffffffff";
private static final String WS_PROTOCOL = "v1.saltyrtc.org";
private static final int WS_CONNECT_TIMEOUT_MS = 10000;
private static final int WS_TEST_TIMEOUT_MS = WS_CONNECT_TIMEOUT_MS + 3000;
private static final int RTC_TEST_TIMEOUT_MS = 12000;
// Threema services
@Nullable
private ContactService contactService;
// Views
@Nullable
private ProgressBar progressBar;
@Nullable
private TextView introText;
@Nullable
private TextView doneText;
@Nullable
private Button copyButton;
@Nullable
private Button sendButton;
@Nullable
private View footerButtons;
// String that will be copied to clipboard
@Nullable
private String clipboardString;
// Event logging
@NonNull
private final List<String> eventLog = new ArrayList<>();
@Nullable
private ArrayAdapter<String> adapter;
private long startTime = 0;
// Websocket
@Nullable
private WebSocket ws;
private boolean wsDone = false;
// WebRTC
@Nullable
private PeerConnection pc;
@Nullable
private PeerConnectionFactory pcFactory;
private final AtomicInteger candidateCount = new AtomicInteger(0);
private boolean rtcDone = false;
// Executor service that should be used for running creation / destruction
// of the peer connection and related objects.
@Nullable
private ScheduledExecutorService webrtcExecutor;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
logger.trace("onCreate");
super.onCreate(savedInstanceState);
final ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setreplacedle(R.string.webclient_diagnostics);
}
}
@Override
protected boolean initActivity(Bundle savedInstanceState) {
logger.trace("initActivity");
if (!super.initActivity(savedInstanceState)) {
return false;
}
// Initialize services
try {
this.contactService = this.serviceManager.getContactService();
} catch (MasterKeyLockedException | FileSystemNotPresentException e) {
logger.error("Could not initialize services", e);
}
// Get view references
this.progressBar = findViewById(R.id.webclient_diagnostics_loading);
this.introText = findViewById(R.id.webclient_diagnostics_intro);
this.doneText = findViewById(R.id.webclient_diagnostics_done);
this.copyButton = findViewById(R.id.webclient_diagnostics_copy_button);
this.sendButton = findViewById(R.id.webclient_diagnostics_send_button);
this.footerButtons = findViewById(R.id.webclient_diagnostics_footer_buttons);
// Wire up start button
final Button startButton = findViewById(R.id.webclient_diagnostics_start);
startButton.setOnClickListener(view -> {
startButton.setVisibility(View.GONE);
WebDiagnosticsActivity.this.startTests();
});
// Wire up copy button
replacedert this.copyButton != null;
this.copyButton.setOnClickListener(view -> {
if (!TestUtil.empty(this.clipboardString)) {
WebDiagnosticsActivity.this.copyToClipboard(this.clipboardString);
}
});
// Wire up send button
replacedert this.sendButton != null;
this.sendButton.setOnClickListener(view -> {
if (!TestUtil.empty(this.clipboardString)) {
WebDiagnosticsActivity.this.prepareSendToSupport();
}
});
// Initialize event log
final ListView eventLog = findViewById(R.id.webclient_diagnostics_event_log);
this.adapter = new ArrayAdapter<>(this, R.layout.item_webrtc_debug_list, this.eventLog);
eventLog.setAdapter(this.adapter);
return true;
}
@Override
public int getLayoutResource() {
return R.layout.activity_webclient_debug;
}
@Override
protected void onStart() {
logger.trace("onStart");
this.webrtcExecutor = Executors.newSingleThreadScheduledExecutor();
super.onStart();
}
@Override
protected void onStop() {
logger.trace("onStop");
this.cleanup();
super.onStop();
}
@AnyThread
private void resetStartTime() {
this.startTime = System.nanoTime();
}
@AnyThread
private void addLogSeparator() {
this.addToLog("----------------", false);
}
@AnyThread
private void addToLog(final String value, boolean timestamp) {
final long elapsedNs = System.nanoTime() - this.startTime;
final String logLine = timestamp ? String.format("+%sms %s", elapsedNs / 1000 / 1000, value) : value;
this.clipboardString += logLine + "\n";
RuntimeUtil.runOnUiThread(() -> {
synchronized (WebDiagnosticsActivity.this.eventLog) {
logger.info(logLine);
WebDiagnosticsActivity.this.eventLog.add(logLine);
if (WebDiagnosticsActivity.this.adapter != null) {
WebDiagnosticsActivity.this.adapter.notifyDataSetChanged();
}
}
});
}
@AnyThread
private void addToLog(final String value) {
this.addToLog(value, true);
}
@UiThread
private void copyToClipboard(@NonNull String text) {
final ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
if (clipboard != null) {
final String label = getString(R.string.webclient_diagnostics);
final ClipData clip = ClipData.newPlainText(label, text);
clipboard.setPrimaryClip(clip);
Toast.makeText(getApplicationContext(), getString(R.string.voip_webrtc_debug_copied), Toast.LENGTH_LONG).show();
}
}
private void prepareSendToSupport() {
TextEntryDialog dialog = TextEntryDialog.newInstance(R.string.send_to_support, R.string.enter_description, R.string.send, R.string.cancel, 5, 3000, 1);
dialog.show(getSupportFragmentManager(), DIALOG_TAG_SEND_VOIP_DEBUG);
}
@SuppressLint("StaticFieldLeak")
private void sendToSupport(@NonNull String caption) {
final MessageService messageService;
try {
messageService = serviceManager.getMessageService();
} catch (ThreemaException e) {
logger.error("Exception", e);
return;
}
if (this.contactService == null || messageService == null) {
return;
}
new AsyncTask<Void, Void, ContactMessageReceiver>() {
@Override
protected ContactMessageReceiver doInBackground(Void... voids) {
try {
final ContactModel contactModel = contactService.getOrCreateByIdenreplacedy(THREEMA_SUPPORT_IDENreplacedY, true);
return contactService.createReceiver(contactModel);
} catch (Exception e) {
return null;
}
}
@Override
protected void onPostExecute(ContactMessageReceiver messageReceiver) {
try {
messageService.sendText(clipboardString + "\n---\n" + caption + "\n---\n" + ConfigUtils.getDeviceInfo(WebDiagnosticsActivity.this, false) + "\n" + "Threema " + ConfigUtils.getFullAppVersion(WebDiagnosticsActivity.this) + "\n" + getMyIdenreplacedy(), messageReceiver);
Toast.makeText(getApplicationContext(), R.string.message_sent, Toast.LENGTH_LONG).show();
finish();
return;
} catch (Exception e1) {
logger.error("Exception", e1);
}
Toast.makeText(getApplicationContext(), R.string.an_error_occurred, Toast.LENGTH_LONG).show();
}
}.execute();
}
@UiThread
private void startTests() {
logger.info("*** Starting Threema Web Diagnostics Test");
this.eventLog.clear();
this.clipboardString = "";
this.resetStartTime();
this.addToLog("Starting Threema Web Diagnostics...", false);
// Update UI visibility
replacedert this.progressBar != null;
this.progressBar.setVisibility(View.VISIBLE);
replacedert this.introText != null;
this.introText.setVisibility(View.GONE);
replacedert this.doneText != null;
this.doneText.setVisibility(View.GONE);
replacedert this.footerButtons != null;
this.footerButtons.setVisibility(View.GONE);
// Print connectivity info
this.queryConnectivityInfo();
// Start with WebSocket test
this.startWsTest();
}
@UiThread
private void queryConnectivityInfo() {
final Context appContext = getApplicationContext();
final ConnectivityManager connectivityManager = (ConnectivityManager) appContext.getSystemService(Context.CONNECTIVITY_SERVICE);
this.addLogSeparator();
if (Build.VERSION.SDK_INT >= 21) {
// Ignore Android 4
// Add available networks
final Network[] networks = connectivityManager.getAllNetworks();
this.addToLog("Networks (" + networks.length + "):", false);
for (Network network : networks) {
final NetworkInfo info = connectivityManager.getNetworkInfo(network);
final String typeName = info.getTypeName();
final String fullType = info.getSubtypeName().isEmpty() ? typeName : typeName + "/" + info.getSubtypeName();
final String detailedState = info.getDetailedState().toString();
final String failover = "failover=" + info.isFailover();
final String available = "available=" + info.isAvailable();
final String roaming = "roaming=" + info.isRoaming();
this.addToLog("- " + fullType + ", " + detailedState + ", " + failover + ", " + available + ", " + roaming, false);
}
} else {
this.addToLog("API level " + Build.VERSION.SDK_INT + ", ignoring network info");
}
this.addLogSeparator();
try {
final List<String> addresses = new ArrayList<>();
for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
final NetworkInterface intf = en.nextElement();
for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
final InetAddress inetAddress = enumIpAddr.nextElement();
if (!inetAddress.isLoopbackAddress()) {
final String addr = inetAddress.getHostAddress();
if (inetAddress.isLinkLocalAddress()) {
addresses.add(addr + " [link-local]");
} else {
addresses.add(addr);
}
}
}
}
Collections.sort(addresses);
this.addToLog("Non-loopback interfaces (" + addresses.size() + "):", false);
for (String addr : addresses) {
this.addToLog("- " + addr, false);
}
} catch (SocketException e) {
this.addToLog("Socket exception when enumerating network interfaces: " + e.toString());
}
}
/**
* Start the WebSocket test.
*/
@UiThread
private synchronized void startWsTest() {
this.wsDone = false;
final Handler handler = new Handler();
handler.postDelayed(() -> {
if (!wsDone) {
WebDiagnosticsActivity.this.failWs("WS test timed out");
}
}, WS_TEST_TIMEOUT_MS);
RuntimeUtil.runInAsyncTask(() -> {
final boolean success = WebDiagnosticsActivity.this.testWebsocket();
if (!success) {
addToLog("Initializing WebSocket test failed.");
}
});
}
/**
* Start the WebRTC test.
*/
@UiThread
private synchronized void startRtcTest() {
this.rtcDone = false;
this.candidateCount.set(0);
final Handler handler = new Handler();
handler.postDelayed(() -> {
if (!rtcDone) {
WebDiagnosticsActivity.this.addToLog("WebRTC test timed out");
WebDiagnosticsActivity.this.onRtcComplete(this.candidateCount.get() > 0);
}
}, RTC_TEST_TIMEOUT_MS);
RuntimeUtil.runInAsyncTask(() -> {
final boolean success = WebDiagnosticsActivity.this.testWebRTC();
if (!success) {
addToLog("Initializing WebRTC test failed.");
}
});
}
/**
* Initialize the WebSocket tests.
*
* If something during the initialization fails, return false.
*/
@AnyThread
private boolean testWebsocket() {
this.addLogSeparator();
this.resetStartTime();
this.addToLog("Starting WS tests");
// Get configuration
// Note: Below needs to be kept in sync with how dual stack mode is applied to the
// SaltyRTC WebSocket code.
replacedert this.preferenceService != null;
DualStackMode dualStackMode = DualStackMode.BOTH;
if (!this.preferenceService.allowWebrtcIpv6()) {
dualStackMode = DualStackMode.IPV4_ONLY;
}
this.addToLog("Setting: dualStackMode=" + dualStackMode.name());
// Create WebSocket
final String url = WS_BASE_URL + "/" + WS_PATH;
logger.info("Connecting to " + url);
try {
this.ws = new WebSocketFactory().setConnectionTimeout(WS_CONNECT_TIMEOUT_MS).setSSLSocketFactory(ConfigUtils.getSSLSocketFactory(WS_HOST)).setVerifyHostname(true).setDualStackMode(dualStackMode).createSocket(url).addProtocol(WS_PROTOCOL).addListener(this.wsListener);
} catch (IOException e) {
this.failWs("IOException when creating WebSocket: " + e.getMessage(), e);
return false;
}
// Connect
try {
this.addToLog("Connecting to WebSocket");
replacedert this.ws != null;
this.ws.connect();
} catch (WebSocketException e) {
this.failWs("WebSocketException when connecting: " + e.getMessage(), e);
return false;
}
return true;
}
/**
* Initialize the WebRTC tests.
*
* If something during the initialization fails, return false.
*/
@AnyThread
private boolean testWebRTC() {
this.addLogSeparator();
this.resetStartTime();
this.addToLog("Starting WebRTC tests");
// Get configuration
replacedert this.preferenceService != null;
final boolean allowIpv6 = this.preferenceService.allowWebrtcIpv6();
this.addToLog("Setting: allowWebrtcIpv6=" + allowIpv6);
// Set up peer connection
replacedert this.webrtcExecutor != null;
this.webrtcExecutor.execute(() -> {
WebRTCUtil.initializeAndroidGlobals(this.getApplicationContext());
final PeerConnection.RTCConfiguration rtcConfig;
try {
rtcConfig = PeerConnectionWrapper.getRTCConfiguration(logger);
} catch (Exception e) {
this.addToLog("Could not get RTC configuration: " + e.getMessage());
return;
}
rtcConfig.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_ONCE;
this.addToLog("Using " + rtcConfig.iceServers.size() + " ICE servers:");
for (PeerConnection.IceServer server : rtcConfig.iceServers) {
this.addToLog("- " + server.urls.toString());
}
// Instantiate peer connection
this.pcFactory = PeerConnectionWrapper.getPeerConnectionFactory();
this.pc = this.pcFactory.createPeerConnection(rtcConfig, this.pcObserver);
if (this.pc == null) {
this.addToLog("Could not create peer connection");
return;
}
// Create a data channel and a offer to kick off ICE gathering
this.pc.createDataChannel("trigger-ice-gathering", new DataChannel.Init());
this.pc.createOffer(this.sdpObserver, new MediaConstraints());
});
return true;
}
private final WebSocketListener wsListener = new DefaultNoopWebSocketListener() {
// State changes
@Override
public void onStateChanged(WebSocket websocket, WebSocketState newState) {
addToLog("WS state changed to " + newState.name());
}
@Override
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) {
final Socket socket;
try {
socket = websocket.getConnectedSocket();
} catch (WebSocketException e) {
addToLog("Unable to retrieve connected socket: " + e.toString());
return;
}
final String local = socket.getLocalAddress().getHostAddress() + ":" + socket.getLocalPort();
final String remote = socket.getInetAddress().getHostAddress() + ":" + socket.getPort();
addToLog("WS connected (" + local + " -> " + remote + ")");
}
@Override
public void onDisconnected(WebSocket websocket, WebSocketFrame serverCloseFrame, WebSocketFrame clientCloseFrame, boolean closedByServer) {
if (closedByServer) {
int code = serverCloseFrame.getCloseCode();
addToLog("WS closed by server with code " + code + " (" + CloseCode.explain(code) + ")");
} else {
int code = clientCloseFrame.getCloseCode();
addToLog("WS closed by us with code " + code + " (" + CloseCode.explain(code) + ")");
}
final boolean success = !closedByServer && clientCloseFrame.getCloseCode() == 1000;
WebDiagnosticsActivity.this.onWsComplete(success);
}
// Data
@Override
public void onTextMessage(WebSocket websocket, String text) {
addToLog("WS received text message, aborting");
websocket.disconnect();
}
@Override
public void onTextMessage(WebSocket websocket, byte[] data) {
addToLog("WS received text message, aborting");
websocket.disconnect();
}
@Override
public void onBinaryMessage(WebSocket websocket, byte[] binary) {
addToLog("WS received " + binary.length + " bytes");
// This should be the server-hello message
// Validate length
if (binary.length < 81) {
addToLog("Invalid message length: " + binary.length);
websocket.disconnect(1000);
return;
}
// Wrap message
final Box box = new Box(ByteBuffer.wrap(binary), SignalingChannelNonce.TOTAL_LENGTH);
// Validate nonce
final SignalingChannelNonce nonce = new SignalingChannelNonce(ByteBuffer.wrap(box.getNonce()));
if (nonce.getSource() != 0) {
addToLog("Invalid nonce source: " + nonce.getSource());
websocket.disconnect(1000);
return;
}
if (nonce.getDestination() != 0) {
addToLog("Invalid nonce destination: " + nonce.getDestination());
websocket.disconnect(1000);
return;
}
if (nonce.getOverflow() != 0) {
addToLog("Invalid nonce overflow: " + nonce.getOverflow());
websocket.disconnect(1000);
return;
}
// Validate data
// Data should start with 0x82 (fixmap with 2 entries) followed by a string
// with either the value "type" or "key".
final byte[] data = box.getData();
short byte1 = UnsignedHelper.readUnsignedByte(data[0]);
short byte2 = UnsignedHelper.readUnsignedByte(data[1]);
short byte3 = UnsignedHelper.readUnsignedByte(data[2]);
short byte4 = UnsignedHelper.readUnsignedByte(data[3]);
short byte5 = UnsignedHelper.readUnsignedByte(data[4]);
short byte6 = UnsignedHelper.readUnsignedByte(data[5]);
if (byte1 != 0x82) {
addToLog("Invalid data (does not start with 0x82)");
websocket.disconnect(1000);
return;
}
if (byte2 == 0xa3 && byte3 == 'k' && byte4 == 'e' && byte5 == 'y') {
addToLog("Received server-hello message!");
} else if (byte2 == 0xa4 && byte3 == 't' && byte4 == 'y' && byte5 == 'p' && byte6 == 'e') {
addToLog("Received server-hello message!");
} else {
addToLog("Received invalid message (bad data)");
}
websocket.disconnect(1000);
}
// Errors
@Override
public void onConnectError(WebSocket websocket, WebSocketException cause) {
WebDiagnosticsActivity.this.failWs("WS connect error: " + cause.toString());
}
@Override
public void onError(WebSocket websocket, WebSocketException cause) {
WebDiagnosticsActivity.this.failWs("WS error: " + cause.toString());
}
@Override
public void onFrameError(WebSocket websocket, WebSocketException cause, WebSocketFrame frame) {
WebDiagnosticsActivity.this.failWs("WS frame error: " + cause.toString());
}
@Override
public void onMessageError(WebSocket websocket, WebSocketException cause, List<WebSocketFrame> frames) {
WebDiagnosticsActivity.this.failWs("WS message error: " + cause.toString());
}
@Override
public void onSendError(WebSocket websocket, WebSocketException cause, WebSocketFrame frame) {
WebDiagnosticsActivity.this.failWs("WS send error: " + cause.toString());
}
};
private final PeerConnection.Observer pcObserver = new DefaultNoopPeerConnectionObserver() {
@Override
public void onSignalingChange(PeerConnection.SignalingState signalingState) {
if (WebDiagnosticsActivity.this.pc == null) {
return;
}
WebDiagnosticsActivity.this.addToLog("PC signaling state change to " + signalingState.name());
}
@Override
public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
if (WebDiagnosticsActivity.this.pc == null) {
return;
}
WebDiagnosticsActivity.this.addToLog("ICE connection state change to " + iceConnectionState.name());
switch(iceConnectionState) {
case NEW:
case CHECKING:
case CONNECTED:
case COMPLETED:
case DISCONNECTED:
case CLOSED:
break;
case FAILED:
WebDiagnosticsActivity.this.failRtc("ICE failed");
break;
}
}
@Override
public void onIceConnectionReceivingChange(boolean b) {
if (WebDiagnosticsActivity.this.pc == null) {
return;
}
WebDiagnosticsActivity.this.addToLog("ICE connection receiving: " + b);
}
@Override
public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
if (WebDiagnosticsActivity.this.pc == null) {
return;
}
WebDiagnosticsActivity.this.addToLog("ICE gathering state change to " + iceGatheringState.name());
switch(iceGatheringState) {
case NEW:
case GATHERING:
break;
case COMPLETE:
WebDiagnosticsActivity.this.onRtcComplete(true);
break;
}
}
@Override
public void onIceCandidate(IceCandidate candidate) {
if (WebDiagnosticsActivity.this.pc == null) {
return;
}
WebDiagnosticsActivity.this.addToLog(WebRTCUtil.iceCandidateToString(candidate));
if (candidate == null) {
WebDiagnosticsActivity.this.onRtcComplete(true);
} else {
WebDiagnosticsActivity.this.candidateCount.incrementAndGet();
WebDiagnosticsActivity.this.addToLog(WebRTCUtil.iceCandidateToString(candidate));
}
}
@Override
public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {
if (WebDiagnosticsActivity.this.pc == null) {
return;
}
for (IceCandidate candidate : iceCandidates) {
WebDiagnosticsActivity.this.addToLog("Removed: " + WebRTCUtil.iceCandidateToString(candidate));
}
}
@Override
public void onRenegotiationNeeded() {
if (WebDiagnosticsActivity.this.pc == null) {
return;
}
WebDiagnosticsActivity.this.addToLog("ICE renegotiation needed");
}
};
private final SdpObserver sdpObserver = new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
WebDiagnosticsActivity.this.addToLog("SDP create success");
replacedert WebDiagnosticsActivity.this.webrtcExecutor != null;
WebDiagnosticsActivity.this.webrtcExecutor.execute(() -> {
if (WebDiagnosticsActivity.this.pc != null) {
WebDiagnosticsActivity.this.pc.setLocalDescription(this, sessionDescription);
} else {
WebDiagnosticsActivity.this.failRtc("Could not set local description: Peer connection is null");
}
});
}
@Override
public void onSetSuccess() {
WebDiagnosticsActivity.this.addToLog("SDP set success");
}
@Override
public void onCreateFailure(String s) {
WebDiagnosticsActivity.this.addToLog("SDP create failure");
WebDiagnosticsActivity.this.failRtc("Could not create SDP: " + s);
}
@Override
public void onSetFailure(String s) {
WebDiagnosticsActivity.this.addToLog("SDP set failure");
WebDiagnosticsActivity.this.failRtc("Could not set SDP: " + s);
}
};
@AnyThread
private void failWs(@NonNull String message) {
this.failWs(message, null);
}
@AnyThread
private void failWs(@NonNull String message, @Nullable Exception e) {
if (e != null) {
logger.error("WS Exception", e);
}
this.addToLog(message);
this.onWsComplete(false);
}
@AnyThread
private void failRtc(@NonNull String message) {
this.addToLog(message);
this.onRtcComplete(false);
}
/**
* Test is complete.
*/
@AnyThread
private void onWsComplete(boolean success) {
this.addToLog("WS tests complete (success=" + success + ")");
this.cleanupWs();
this.wsDone = true;
if (success) {
this.runOnUiThread(this::startRtcTest);
} else {
RuntimeUtil.runOnUiThread(this::onComplete);
}
}
/**
* Test is complete.
*/
@AnyThread
private void onRtcComplete(boolean success) {
this.addToLog("WebRTC tests complete (success=" + success + ")");
this.cleanupRtc();
this.rtcDone = true;
RuntimeUtil.runOnUiThread(this::onComplete);
}
@UiThread
private void onComplete() {
final Handler handler = new Handler();
handler.postDelayed(() -> {
logger.info("*** Finished Threema Web Diagnostics Test");
this.addLogSeparator();
this.addToLog("Done.", false);
RuntimeUtil.runOnUiThread(() -> {
replacedert progressBar != null;
progressBar.setVisibility(View.GONE);
replacedert introText != null;
introText.setVisibility(View.GONE);
replacedert doneText != null;
doneText.setVisibility(View.VISIBLE);
replacedert footerButtons != null;
footerButtons.setVisibility(View.VISIBLE);
});
}, 200);
}
@AnyThread
private synchronized void cleanupWs() {
logger.trace("cleanupWs");
if (this.ws != null) {
this.ws.clearListeners();
this.ws.disconnect();
this.ws = null;
}
}
@AnyThread
private synchronized void cleanupRtc() {
logger.trace("cleanupRtc");
if (this.pc != null) {
replacedert this.webrtcExecutor != null;
this.webrtcExecutor.execute(this.pc::dispose);
this.pc = null;
}
if (this.pcFactory != null) {
replacedert this.webrtcExecutor != null;
this.webrtcExecutor.execute(this.pcFactory::dispose);
this.pcFactory = null;
}
}
@AnyThread
private synchronized void cleanup() {
logger.info("Cleaning up resources");
this.cleanupWs();
this.cleanupRtc();
if (this.webrtcExecutor != null) {
this.webrtcExecutor.shutdown();
try {
if (!this.webrtcExecutor.awaitTermination(800, TimeUnit.MILLISECONDS)) {
this.webrtcExecutor.shutdownNow();
}
} catch (InterruptedException e) {
this.webrtcExecutor.shutdownNow();
}
this.webrtcExecutor = null;
}
}
@Override
public void onYes(String tag, String text) {
if (DIALOG_TAG_SEND_VOIP_DEBUG.equals(tag)) {
sendToSupport(text);
}
}
@Override
public void onNo(String tag) {
}
@Override
public void onNeutral(String tag) {
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// noinspection SwitchStatementWithTooFewBranches
switch(item.gereplacedemId()) {
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}
19
Source : WebRTCWrapper.java
with GNU General Public License v3.0
from snikket-im
with GNU General Public License v3.0
from snikket-im
public clreplaced WebRTCWrapper {
private static final String EXTENDED_LOGGING_TAG = WebRTCWrapper.clreplaced.getSimpleName();
// 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(// Sony z5 compact
"E5823").add("Redmi Note 5").add(// Fairphone FP2
"FP2").add("MI 5").build();
private static final int CAPTURING_RESOLUTION = 1920;
private static final int CAPTURING_MAX_FRAME_RATE = 30;
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());
private VideoTrack localVideoTrack = null;
private VideoTrack remoteVideoTrack = null;
private final PeerConnection.Observer peerConnectionObserver = new PeerConnection.Observer() {
@Override
public void onSignalingChange(PeerConnection.SignalingState signalingState) {
Log.d(EXTENDED_LOGGING_TAG, "onSignalingChange(" + signalingState + ")");
// 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
}
@Override
public void onConnectionChange(PeerConnection.PeerConnectionState newState) {
eventCallback.onConnectionChange(newState);
}
@Override
public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
}
@Override
public void onSelectedCandidatePairChanged(CandidatePairChangeEvent event) {
Log.d(Config.LOGTAG, "remote candidate selected: " + event.remote);
Log.d(Config.LOGTAG, "local candidate selected: " + event.local);
}
@Override
public void onIceConnectionReceivingChange(boolean b) {
}
@Override
public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
Log.d(EXTENDED_LOGGING_TAG, "onIceGatheringChange(" + iceGatheringState + ")");
}
@Override
public void onIceCandidate(IceCandidate iceCandidate) {
eventCallback.onIceCandidate(iceCandidate);
}
@Override
public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {
}
@Override
public void onAddStream(MediaStream mediaStream) {
Log.d(EXTENDED_LOGGING_TAG, "onAddStream(numAudioTracks=" + mediaStream.audioTracks.size() + ",numVideoTracks=" + mediaStream.videoTracks.size() + ")");
final List<VideoTrack> videoTracks = mediaStream.videoTracks;
if (videoTracks.size() > 0) {
remoteVideoTrack = videoTracks.get(0);
Log.d(Config.LOGTAG, "remote video track enabled?=" + remoteVideoTrack.enabled());
} else {
Log.d(Config.LOGTAG, "no remote video tracks found");
}
}
@Override
public void onRemoveStream(MediaStream mediaStream) {
}
@Override
public void onDataChannel(DataChannel dataChannel) {
}
@Override
public void onRenegotiationNeeded() {
}
@Override
public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {
final MediaStreamTrack track = rtpReceiver.track();
Log.d(EXTENDED_LOGGING_TAG, "onAddTrack(kind=" + (track == null ? "null" : track.kind()) + ",numMediaStreams=" + mediaStreams.length + ")");
}
@Override
public void onTrack(RtpTransceiver transceiver) {
Log.d(EXTENDED_LOGGING_TAG, "onTrack(mid=" + transceiver.getMid() + ",media=" + transceiver.getMediaType() + ")");
}
};
@Nullable
private PeerConnection peerConnection = null;
private AudioTrack localAudioTrack = null;
private AppRTCAudioManager appRTCAudioManager = null;
private ToneManager toneManager = null;
private Context context = null;
private EglBase eglBase = null;
private CapturerChoice capturerChoice;
WebRTCWrapper(final EventCallback eventCallback) {
this.eventCallback = eventCallback;
}
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;
}
public void setup(final XmppConnectionService service, final AppRTCAudioManager.SpeakerPhonePreference speakerPhonePreference) throws InitializationException {
try {
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions.builder(service).createInitializationOptions());
} catch (final UnsatisfiedLinkError e) {
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);
}
this.context = service;
this.toneManager = service.getJingleConnectionManager().toneManager;
mainHandler.post(() -> {
appRTCAudioManager = AppRTCAudioManager.create(service, speakerPhonePreference);
toneManager.setAppRtcAudioManagerHasControl(true);
appRTCAudioManager.start(audioManagerEvents);
eventCallback.onAudioDeviceChanged(appRTCAudioManager.getSelectedAudioDevice(), appRTCAudioManager.getAudioDevices());
});
}
synchronized void initializePeerConnection(final Set<Media> media, final List<PeerConnection.IceServer> iceServers) throws InitializationException {
Preconditions.checkState(this.eglBase != null);
Preconditions.checkNotNull(media);
Preconditions.checkArgument(media.size() > 0, "media can not be empty when initializing peer connection");
final boolean setUseHardwareAcousticEchoCanceler = WebRtcAudioEffects.creplacedeAcousticEchoCanceler() && !HARDWARE_AEC_BLACKLIST.contains(Build.MODEL);
Log.d(Config.LOGTAG, String.format("setUseHardwareAcousticEchoCanceler(%s) model=%s", setUseHardwareAcousticEchoCanceler, Build.MODEL));
PeerConnectionFactory peerConnectionFactory = PeerConnectionFactory.builder().setVideoDecoderFactory(new DefaultVideoDecoderFactory(eglBase.getEglBaseContext())).setVideoEncoderFactory(new DefaultVideoEncoderFactory(eglBase.getEglBaseContext(), true, true)).setAudioDeviceModule(JavaAudioDeviceModule.builder(context).setUseHardwareAcousticEchoCanceler(setUseHardwareAcousticEchoCanceler).createAudioDeviceModule()).createPeerConnectionFactory();
final MediaStream stream = peerConnectionFactory.createLocalMediaStream("my-media-stream");
final Optional<CapturerChoice> optionalCapturerChoice = media.contains(Media.VIDEO) ? getVideoCapturer() : Optional.absent();
if (optionalCapturerChoice.isPresent()) {
this.capturerChoice = optionalCapturerChoice.get();
final CameraVideoCapturer capturer = this.capturerChoice.cameraVideoCapturer;
final VideoSource videoSource = peerConnectionFactory.createVideoSource(false);
SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create("webrtc", eglBase.getEglBaseContext());
capturer.initialize(surfaceTextureHelper, requireContext(), videoSource.getCapturerObserver());
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());
this.localVideoTrack = peerConnectionFactory.createVideoTrack("my-video-track", videoSource);
stream.addTrack(this.localVideoTrack);
}
if (media.contains(Media.AUDIO)) {
// set up audio track
final AudioSource audioSource = peerConnectionFactory.createAudioSource(new MediaConstraints());
this.localAudioTrack = peerConnectionFactory.createAudioTrack("my-audio-track", audioSource);
stream.addTrack(this.localAudioTrack);
}
final PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(iceServers);
// XEP-0176 doesn't support tcp
rtcConfig.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED;
rtcConfig.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY;
final PeerConnection peerConnection = peerConnectionFactory.createPeerConnection(rtcConfig, peerConnectionObserver);
if (peerConnection == null) {
throw new InitializationException("Unable to create PeerConnection");
}
peerConnection.addStream(stream);
peerConnection.setAudioPlayout(true);
peerConnection.setAudioRecording(true);
this.peerConnection = peerConnection;
}
synchronized void close() {
final PeerConnection peerConnection = this.peerConnection;
final CapturerChoice capturerChoice = this.capturerChoice;
final AppRTCAudioManager audioManager = this.appRTCAudioManager;
final EglBase eglBase = this.eglBase;
if (peerConnection != null) {
dispose(peerConnection);
this.peerConnection = null;
}
if (audioManager != null) {
toneManager.setAppRtcAudioManagerHasControl(false);
mainHandler.post(audioManager::stop);
}
this.localVideoTrack = null;
this.remoteVideoTrack = null;
if (capturerChoice != null) {
try {
capturerChoice.cameraVideoCapturer.stopCapture();
} catch (InterruptedException e) {
Log.e(Config.LOGTAG, "unable to stop capturing");
}
}
if (eglBase != null) {
eglBase.release();
this.eglBase = null;
}
}
synchronized void verifyClosed() {
if (this.peerConnection != null || this.eglBase != null || this.localVideoTrack != null || this.remoteVideoTrack != null) {
final IllegalStateException e = new IllegalStateException("WebRTCWrapper hasn't been closed properly");
Log.e(Config.LOGTAG, "verifyClosed() failed. Going to throw", e);
throw e;
}
}
boolean isCameraSwitchable() {
final CapturerChoice capturerChoice = this.capturerChoice;
return capturerChoice != null && capturerChoice.availableCameras.size() > 1;
}
boolean isFrontCamera() {
final CapturerChoice capturerChoice = this.capturerChoice;
return capturerChoice == null || capturerChoice.isFrontCamera;
}
ListenableFuture<Boolean> switchCamera() {
final CapturerChoice capturerChoice = this.capturerChoice;
if (capturerChoice == null) {
return Futures.immediateFailedFuture(new IllegalStateException("CameraCapturer has not been initialized"));
}
final SettableFuture<Boolean> future = SettableFuture.create();
capturerChoice.cameraVideoCapturer.switchCamera(new CameraVideoCapturer.CameraSwitchHandler() {
@Override
public void onCameraSwitchDone(boolean isFrontCamera) {
capturerChoice.isFrontCamera = isFrontCamera;
future.set(isFrontCamera);
}
@Override
public void onCameraSwitchError(final String message) {
future.setException(new IllegalStateException(String.format("Unable to switch camera %s", message)));
}
});
return future;
}
boolean isMicrophoneEnabled() {
final AudioTrack audioTrack = this.localAudioTrack;
if (audioTrack == null) {
throw new IllegalStateException("Local audio track does not exist (yet)");
}
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;
}
}
boolean setMicrophoneEnabled(final boolean enabled) {
final AudioTrack audioTrack = this.localAudioTrack;
if (audioTrack == null) {
throw new IllegalStateException("Local audio track does not exist (yet)");
}
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;
}
}
boolean isVideoEnabled() {
final VideoTrack videoTrack = this.localVideoTrack;
if (videoTrack == null) {
throw new IllegalStateException("Local video track does not exist");
}
return videoTrack.enabled();
}
void setVideoEnabled(final boolean enabled) {
final VideoTrack videoTrack = this.localVideoTrack;
if (videoTrack == null) {
throw new IllegalStateException("Local video track does not exist");
}
videoTrack.setEnabled(enabled);
}
ListenableFuture<SessionDescription> createOffer() {
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());
}
ListenableFuture<SessionDescription> createAnswer() {
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());
}
ListenableFuture<Void> setLocalDescription(final SessionDescription sessionDescription) {
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);
}
return Futures.transformAsync(getPeerConnectionFuture(), peerConnection -> {
final SettableFuture<Void> future = SettableFuture.create();
peerConnection.setLocalDescription(new SetSdpObserver() {
@Override
public void onSetSuccess() {
future.set(null);
}
@Override
public void onSetFailure(final String s) {
future.setException(new IllegalArgumentException("unable to set local session description: " + s));
}
}, sessionDescription);
return future;
}, MoreExecutors.directExecutor());
}
ListenableFuture<Void> setRemoteDescription(final SessionDescription sessionDescription) {
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);
}
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) {
future.setException(new IllegalArgumentException("unable to set remote session description: " + s));
}
}, 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);
}
}
void addIceCandidate(IceCandidate iceCandidate) {
requirePeerConnection().addIceCandidate(iceCandidate);
}
private CameraEnumerator getCameraEnumerator() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return new Camera2Enumerator(requireContext());
} else {
return new Camera1Enumerator();
}
}
private Optional<CapturerChoice> getVideoCapturer() {
final CameraEnumerator enumerator = getCameraEnumerator();
final Set<String> deviceNames = ImmutableSet.copyOf(enumerator.getDeviceNames());
for (final String deviceName : deviceNames) {
if (isFrontFacing(enumerator, deviceName)) {
final CapturerChoice capturerChoice = of(enumerator, deviceName, deviceNames);
if (capturerChoice == null) {
return Optional.absent();
}
capturerChoice.isFrontCamera = true;
return Optional.of(capturerChoice);
}
}
if (deviceNames.size() == 0) {
return Optional.absent();
} else {
return Optional.fromNullable(of(enumerator, Iterables.get(deviceNames, 0), deviceNames));
}
}
private static boolean isFrontFacing(final CameraEnumerator cameraEnumerator, final String deviceName) {
try {
return cameraEnumerator.isFrontFacing(deviceName);
} catch (final NullPointerException e) {
return false;
}
}
public PeerConnection.PeerConnectionState getState() {
return requirePeerConnection().connectionState();
}
EglBase.Context getEglBaseContext() {
return this.eglBase.getEglBaseContext();
}
Optional<VideoTrack> getLocalVideoTrack() {
return Optional.fromNullable(this.localVideoTrack);
}
Optional<VideoTrack> getRemoteVideoTrack() {
return Optional.fromNullable(this.remoteVideoTrack);
}
private PeerConnection requirePeerConnection() {
final PeerConnection peerConnection = this.peerConnection;
if (peerConnection == null) {
throw new PeerConnectionNotInitialized();
}
return peerConnection;
}
private Context requireContext() {
final Context context = this.context;
if (context == null) {
throw new IllegalStateException("call setup first");
}
return context;
}
AppRTCAudioManager getAudioManager() {
return appRTCAudioManager;
}
public interface EventCallback {
void onIceCandidate(IceCandidate iceCandidate);
void onConnectionChange(PeerConnection.PeerConnectionState newState);
void onAudioDeviceChanged(AppRTCAudioManager.AudioDevice selectedAudioDevice, Set<AppRTCAudioManager.AudioDevice> availableAudioDevices);
}
private static abstract clreplaced 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 clreplaced 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");
}
}
static clreplaced InitializationException extends Exception {
private InitializationException(final String message, final Throwable throwable) {
super(message, throwable);
}
private InitializationException(final String message) {
super(message);
}
}
public static clreplaced PeerConnectionNotInitialized extends IllegalStateException {
private PeerConnectionNotInitialized() {
super("initialize PeerConnection first");
}
}
private static clreplaced CapturerChoice {
private final CameraVideoCapturer cameraVideoCapturer;
private final CameraEnumerationAndroid.CaptureFormat captureFormat;
private final Set<String> availableCameras;
private boolean isFrontCamera = false;
CapturerChoice(CameraVideoCapturer cameraVideoCapturer, CameraEnumerationAndroid.CaptureFormat captureFormat, Set<String> cameras) {
this.cameraVideoCapturer = cameraVideoCapturer;
this.captureFormat = captureFormat;
this.availableCameras = cameras;
}
int getFrameRate() {
return Math.max(captureFormat.framerate.min, Math.min(CAPTURING_MAX_FRAME_RATE, captureFormat.framerate.max));
}
}
}
19
Source : WebRTCWrapper.java
with GNU General Public License v3.0
from snikket-im
with GNU General Public License v3.0
from snikket-im
private PeerConnection requirePeerConnection() {
final PeerConnection peerConnection = this.peerConnection;
if (peerConnection == null) {
throw new PeerConnectionNotInitialized();
}
return peerConnection;
}
19
Source : RemoteParticipant.java
with Apache License 2.0
from sergiopaniego
with Apache License 2.0
from sergiopaniego
public void setPeerConnection(PeerConnection peerConnection) {
this.peerConnection = peerConnection;
}
19
Source : PeersManager.java
with Apache License 2.0
from sergiopaniego
with Apache License 2.0
from sergiopaniego
/**
* Created by sergiopaniegoblanco on 18/02/2018.
*/
public clreplaced PeersManager {
private PeerConnection localPeer;
private PeerConnectionFactory peerConnectionFactory;
private CustomWebSocketListener webSocketAdapter;
private WebSocket webSocket;
private LinearLayout views_container;
private AudioTrack localAudioTrack;
private VideoTrack localVideoTrack;
private VideoRenderer localRenderer;
private SurfaceViewRenderer localVideoView;
private VideoCapturer videoGrabberAndroid;
private VideoConferenceActivity activity;
public PeersManager(VideoConferenceActivity activity, LinearLayout views_container, SurfaceViewRenderer localVideoView) {
this.views_container = views_container;
this.localVideoView = localVideoView;
this.activity = activity;
}
public PeerConnection getLocalPeer() {
return localPeer;
}
public AudioTrack getLocalAudioTrack() {
return localAudioTrack;
}
public VideoTrack getLocalVideoTrack() {
return localVideoTrack;
}
public PeerConnectionFactory getPeerConnectionFactory() {
return peerConnectionFactory;
}
public void setWebSocket(WebSocket webSocket) {
this.webSocket = webSocket;
}
public CustomWebSocketListener getWebSocketAdapter() {
return webSocketAdapter;
}
public void setWebSocketAdapter(CustomWebSocketListener webSocketAdapter) {
this.webSocketAdapter = webSocketAdapter;
}
public WebSocket getWebSocket() {
return webSocket;
}
public void start() {
PeerConnectionFactory.initializeAndroidGlobals(activity, true);
PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
peerConnectionFactory = new PeerConnectionFactory(options);
videoGrabberAndroid = createVideoGrabber();
MediaConstraints constraints = new MediaConstraints();
VideoSource videoSource = peerConnectionFactory.createVideoSource(videoGrabberAndroid);
localVideoTrack = peerConnectionFactory.createVideoTrack("100", videoSource);
AudioSource audioSource = peerConnectionFactory.createAudioSource(constraints);
localAudioTrack = peerConnectionFactory.createAudioTrack("101", audioSource);
if (videoGrabberAndroid != null) {
videoGrabberAndroid.startCapture(1000, 1000, 30);
}
localRenderer = new VideoRenderer(localVideoView);
localVideoTrack.addRenderer(localRenderer);
MediaConstraints sdpConstraints = new MediaConstraints();
sdpConstraints.mandatory.add(new MediaConstraints.KeyValuePair("offerToReceiveAudio", "true"));
sdpConstraints.mandatory.add(new MediaConstraints.KeyValuePair("offerToReceiveVideo", "true"));
createLocalPeerConnection(sdpConstraints);
}
private VideoCapturer createVideoGrabber() {
VideoCapturer videoCapturer;
videoCapturer = createCameraGrabber(new Camera1Enumerator(false));
return videoCapturer;
}
private VideoCapturer createCameraGrabber(CameraEnumerator enumerator) {
final String[] deviceNames = enumerator.getDeviceNames();
for (String deviceName : deviceNames) {
if (enumerator.isFrontFacing(deviceName)) {
VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
if (videoCapturer != null) {
return videoCapturer;
}
}
}
for (String deviceName : deviceNames) {
if (!enumerator.isFrontFacing(deviceName)) {
VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
if (videoCapturer != null) {
return videoCapturer;
}
}
}
return null;
}
private void createLocalPeerConnection(MediaConstraints sdpConstraints) {
final List<PeerConnection.IceServer> iceServers = new ArrayList<>();
PeerConnection.IceServer iceServer = new PeerConnection.IceServer("stun:stun.l.google.com:19302");
iceServers.add(iceServer);
localPeer = peerConnectionFactory.createPeerConnection(iceServers, sdpConstraints, new CustomPeerConnectionObserver("localPeerCreation") {
@Override
public void onIceCandidate(IceCandidate iceCandidate) {
super.onIceCandidate(iceCandidate);
Map<String, String> iceCandidateParams = new HashMap<>();
iceCandidateParams.put("sdpMid", iceCandidate.sdpMid);
iceCandidateParams.put("sdpMLineIndex", Integer.toString(iceCandidate.sdpMLineIndex));
iceCandidateParams.put("candidate", iceCandidate.sdp);
if (webSocketAdapter.getUserId() != null) {
iceCandidateParams.put("endpointName", webSocketAdapter.getUserId());
webSocketAdapter.sendJson(webSocket, "onIceCandidate", iceCandidateParams);
} else {
webSocketAdapter.addIceCandidate(iceCandidateParams);
}
}
});
}
public void createLocalOffer(MediaConstraints sdpConstraints) {
localPeer.createOffer(new CustomSdpObserver("localCreateOffer") {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
super.onCreateSuccess(sessionDescription);
localPeer.setLocalDescription(new CustomSdpObserver("localSetLocalDesc"), sessionDescription);
Map<String, String> localOfferParams = new HashMap<>();
localOfferParams.put("audioActive", "true");
localOfferParams.put("videoActive", "true");
localOfferParams.put("doLoopback", "false");
localOfferParams.put("frameRate", "30");
localOfferParams.put("typeOfVideo", "CAMERA");
localOfferParams.put("sdpOffer", sessionDescription.description);
if (webSocketAdapter.getId() > 1) {
webSocketAdapter.sendJson(webSocket, "publishVideo", localOfferParams);
} else {
webSocketAdapter.setLocalOfferParams(localOfferParams);
}
}
}, sdpConstraints);
}
public void createRemotePeerConnection(RemoteParticipant remoteParticipant) {
final List<PeerConnection.IceServer> iceServers = new ArrayList<>();
PeerConnection.IceServer iceServer = new PeerConnection.IceServer("stun:stun.l.google.com:19302");
iceServers.add(iceServer);
MediaConstraints sdpConstraints = new MediaConstraints();
sdpConstraints.mandatory.add(new MediaConstraints.KeyValuePair("offerToReceiveAudio", "true"));
sdpConstraints.mandatory.add(new MediaConstraints.KeyValuePair("offerToReceiveVideo", "true"));
PeerConnection remotePeer = peerConnectionFactory.createPeerConnection(iceServers, sdpConstraints, new CustomPeerConnectionObserver("remotePeerCreation", remoteParticipant) {
@Override
public void onIceCandidate(IceCandidate iceCandidate) {
super.onIceCandidate(iceCandidate);
Map<String, String> iceCandidateParams = new HashMap<>();
iceCandidateParams.put("sdpMid", iceCandidate.sdpMid);
iceCandidateParams.put("sdpMLineIndex", Integer.toString(iceCandidate.sdpMLineIndex));
iceCandidateParams.put("candidate", iceCandidate.sdp);
iceCandidateParams.put("endpointName", getRemoteParticipant().getId());
webSocketAdapter.sendJson(webSocket, "onIceCandidate", iceCandidateParams);
}
@Override
public void onAddStream(MediaStream mediaStream) {
super.onAddStream(mediaStream);
activity.gotRemoteStream(mediaStream, getRemoteParticipant());
}
});
MediaStream mediaStream = peerConnectionFactory.createLocalMediaStream("105");
mediaStream.addTrack(localAudioTrack);
mediaStream.addTrack(localVideoTrack);
remotePeer.addStream(mediaStream);
remoteParticipant.setPeerConnection(remotePeer);
}
public void hangup() {
if (webSocketAdapter != null && localPeer != null) {
webSocketAdapter.sendJson(webSocket, "leaveRoom", new HashMap<String, String>());
webSocket.disconnect();
localPeer.close();
Map<String, RemoteParticipant> participants = webSocketAdapter.getParticipants();
for (RemoteParticipant remoteParticipant : participants.values()) {
remoteParticipant.getPeerConnection().close();
views_container.removeView(remoteParticipant.getView());
}
}
if (localVideoTrack != null) {
localVideoTrack.removeRenderer(localRenderer);
localVideoView.clearImage();
videoGrabberAndroid.dispose();
}
}
}
19
Source : WebRTC.java
with GNU Affero General Public License v3.0
from RooyeKhat-Media
with GNU Affero General Public License v3.0
from RooyeKhat-Media
public clreplaced WebRTC {
private static final String VIDEO_TRACK_ID = "ARDAMSv0";
private static final int VIDEO_RESOLUTION_WIDTH = 720;
private static final int VIDEO_RESOLUTION_HEIGHT = 480;
private static final int FPS = 30;
private PeerConnection peerConnection;
private PeerConnectionFactory peerConnectionFactory;
private MediaStream mediaStream;
private String offerSdp;
private MediaConstraints mediaConstraints;
private MediaConstraints audioConstraints;
private VideoCapturer videoCapturer;
private VideoTrack videoTrackFromCamera;
VideoSource videoSource;
private ProtoSignalingOffer.SignalingOffer.Type callTYpe;
private static WebRTC webRTCInstance;
public static WebRTC getInstance() {
if (webRTCInstance == null) {
webRTCInstance = new WebRTC();
}
return webRTCInstance;
}
public void muteSound() {
if (mediaStream == null) {
return;
}
for (AudioTrack audioTrack : mediaStream.audioTracks) {
audioTrack.setEnabled(false);
}
}
public void switchCamera() {
if (Camera.getNumberOfCameras() > 1) {
if (videoCapturer instanceof CameraVideoCapturer) {
((CameraVideoCapturer) videoCapturer).switchCamera(null);
}
}
}
public void unMuteSound() {
if (mediaStream == null) {
return;
}
for (AudioTrack audioTrack : mediaStream.audioTracks) {
audioTrack.setEnabled(true);
}
}
private void addAudioTrack(MediaStream mediaStream) {
AudioSource audioSource = peerConnectionFactoryInstance().createAudioSource(audioConstraintsGetInstance());
AudioTrack audioTrack = peerConnectionFactoryInstance().createAudioTrack("ARDAMSa0", audioSource);
audioTrack.setEnabled(true);
mediaStream.addTrack(audioTrack);
}
public void setCallType(ProtoSignalingOffer.SignalingOffer.Type callTYpe) {
this.callTYpe = callTYpe;
}
private void addVideoTrack(MediaStream mediaStream) {
if (callTYpe == ProtoSignalingOffer.SignalingOffer.Type.VIDEO_CALLING) {
videoCapturer = createCameraCapturer(new Camera1Enumerator(false));
videoSource = peerConnectionFactoryInstance().createVideoSource(videoCapturer);
videoCapturer.startCapture(VIDEO_RESOLUTION_WIDTH, VIDEO_RESOLUTION_HEIGHT, FPS);
videoTrackFromCamera = peerConnectionFactoryInstance().createVideoTrack(VIDEO_TRACK_ID, videoSource);
videoTrackFromCamera.setEnabled(true);
videoTrackFromCamera.addSink(new VideoSink() {
@Override
public void onFrame(VideoFrame videoFrame) {
if (G.onVideoCallFrame != null) {
G.onVideoCallFrame.onPeerFrame(videoFrame);
}
}
});
mediaStream.addTrack(videoTrackFromCamera);
}
}
private VideoCapturer createCameraCapturer(CameraEnumerator enumerator) {
final String[] deviceNames = enumerator.getDeviceNames();
// First, try to find front facing camera
for (String deviceName : deviceNames) {
if (enumerator.isFrontFacing(deviceName)) {
VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
if (videoCapturer != null) {
return videoCapturer;
}
}
}
// Front facing camera not found, try something else
for (String deviceName : deviceNames) {
if (!enumerator.isFrontFacing(deviceName)) {
VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
if (videoCapturer != null) {
return videoCapturer;
}
}
}
return null;
}
public void pauseVideoCapture() {
if (videoCapturer != null) {
try {
videoCapturer.stopCapture();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void startVideoCapture() {
if (videoCapturer != null) {
try {
videoCapturer.startCapture(VIDEO_RESOLUTION_WIDTH, VIDEO_RESOLUTION_HEIGHT, FPS);
} catch (RuntimeException e) {
e.printStackTrace();
}
}
}
/**
* First, we initiate the PeerConnectionFactory with our application context and some options.
*/
private PeerConnectionFactory peerConnectionFactoryInstance() {
if (peerConnectionFactory == null) {
Set<String> HARDWARE_AEC_WHITELIST = new HashSet<String>() {
{
add("D5803");
add("FP1");
add("SM-A500FU");
add("XT1092");
}
};
Set<String> OPEN_SL_ES_WHITELIST = new HashSet<String>() {
{
}
};
if (Build.VERSION.SDK_INT >= 11) {
if (HARDWARE_AEC_WHITELIST.contains(Build.MODEL)) {
WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(false);
} else {
WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(true);
}
if (OPEN_SL_ES_WHITELIST.contains(Build.MODEL)) {
WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(false);
} else {
WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true);
}
}
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions.builder(G.context).createInitializationOptions());
peerConnectionFactory = PeerConnectionFactory.builder().createPeerConnectionFactory();
}
return peerConnectionFactory;
}
PeerConnection peerConnectionInstance() {
if (peerConnection == null) {
List<PeerConnection.IceServer> iceServers = new ArrayList<>();
Realm realm = Realm.getDefaultInstance();
RealmCallConfig realmCallConfig = realm.where(RealmCallConfig.clreplaced).findFirst();
for (RealmIceServer ice : realmCallConfig.getIceServer()) {
iceServers.add(new PeerConnection.IceServer(ice.getUrl(), ice.getUsername(), ice.getCredential()));
}
realm.close();
PeerConnection.RTCConfiguration configuration = new PeerConnection.RTCConfiguration(iceServers);
configuration.bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE;
configuration.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.REQUIRE;
configuration.iceTransportsType = PeerConnection.IceTransportsType.RELAY;
PeerConnection.Observer observer = new PeerConnectionObserver();
MediaConstraints mediaConstraints = mediaConstraintsGetInstance();
peerConnection = peerConnectionFactoryInstance().createPeerConnection(iceServers, mediaConstraints, observer);
mediaStream = peerConnectionFactoryInstance().createLocalMediaStream("ARDAMS");
addAudioTrack(mediaStream);
addVideoTrack(mediaStream);
peerConnection.addStream(mediaStream);
}
return peerConnection;
}
public void createOffer(final long userIdCallee) {
peerConnectionInstance().createOffer(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
offerSdp = sessionDescription.description;
new RequestSignalingOffer().signalingOffer(userIdCallee, callTYpe, sessionDescription.description);
}
@Override
public void onSetSuccess() {
}
@Override
public void onCreateFailure(String s) {
}
@Override
public void onSetFailure(String s) {
}
}, mediaConstraintsGetInstance());
}
public void setOfferLocalDescription() {
setLocalDescription(SessionDescription.Type.OFFER, offerSdp);
}
public void createAnswer() {
peerConnectionInstance().createAnswer(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
setLocalDescription(SessionDescription.Type.ANSWER, sessionDescription.description);
Log.i("WWW", "onCreateSuccess sessionDescription.description : " + sessionDescription.description);
Log.i("WWW", "onCreateSuccess sessionDescription.type : " + sessionDescription.type);
acceptCall(sessionDescription.description);
}
@Override
public void onSetSuccess() {
}
@Override
public void onCreateFailure(String s) {
}
@Override
public void onSetFailure(String s) {
}
}, mediaConstraintsGetInstance());
}
private void setLocalDescription(final SessionDescription.Type type, String sdp) {
peerConnectionInstance().setLocalDescription(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
}
@Override
public void onSetSuccess() {
Log.i("WWW", "onSetSuccess");
}
@Override
public void onCreateFailure(String s) {
}
@Override
public void onSetFailure(String s) {
}
}, new SessionDescription(type, sdp));
}
private MediaConstraints mediaConstraintsGetInstance() {
if (mediaConstraints == null) {
mediaConstraints = new MediaConstraints();
mediaConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
}
return mediaConstraints;
}
private MediaConstraints audioConstraintsGetInstance() {
if (audioConstraints == null) {
audioConstraints = new MediaConstraints();
audioConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
}
return audioConstraints;
}
private void acceptCall(String sdp) {
new RequestSignalingAccept().signalingAccept(sdp);
}
public void leaveCall() {
// don't need for close/dispose here, this action will be doing in onLeave callback
// close();
// dispose();
/**
* set peer connection null for try again
*/
// clearConnection();
new RequestSignalingLeave().signalingLeave();
}
public void close() {
if (peerConnection != null) {
peerConnection.close();
}
}
void dispose() {
try {
if (peerConnection != null) {
peerConnection.dispose();
}
if (peerConnectionFactory != null) {
peerConnectionFactory.dispose();
}
if (videoCapturer != null) {
videoCapturer.stopCapture();
videoCapturer = null;
}
} catch (RuntimeException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
void clearConnection() {
peerConnectionFactory = null;
peerConnection = null;
webRTCInstance = null;
}
}
19
Source : MainActivity.java
with Apache License 2.0
from rome753
with Apache License 2.0
from rome753
public clreplaced MainActivity extends AppCompatActivity implements SignalingClient.Callback {
PeerConnectionFactory peerConnectionFactory;
PeerConnection peerConnection;
SurfaceViewRenderer localView;
SurfaceViewRenderer remoteView;
MediaStream mediaStream;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EglBase.Context eglBaseContext = EglBase.create().getEglBaseContext();
// create PeerConnectionFactory
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions.builder(this).createInitializationOptions());
PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
DefaultVideoEncoderFactory defaultVideoEncoderFactory = new DefaultVideoEncoderFactory(eglBaseContext, true, true);
DefaultVideoDecoderFactory defaultVideoDecoderFactory = new DefaultVideoDecoderFactory(eglBaseContext);
peerConnectionFactory = PeerConnectionFactory.builder().setOptions(options).setVideoEncoderFactory(defaultVideoEncoderFactory).setVideoDecoderFactory(defaultVideoDecoderFactory).createPeerConnectionFactory();
SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", eglBaseContext);
// create VideoCapturer
VideoCapturer videoCapturer = createCameraCapturer(true);
VideoSource videoSource = peerConnectionFactory.createVideoSource(videoCapturer.isScreencast());
videoCapturer.initialize(surfaceTextureHelper, getApplicationContext(), videoSource.getCapturerObserver());
videoCapturer.startCapture(480, 640, 30);
localView = findViewById(R.id.localView);
localView.setMirror(true);
localView.init(eglBaseContext, null);
// create VideoTrack
VideoTrack videoTrack = peerConnectionFactory.createVideoTrack("100", videoSource);
// // display in localView
videoTrack.addSink(localView);
remoteView = findViewById(R.id.remoteView);
remoteView.setMirror(false);
remoteView.init(eglBaseContext, null);
AudioSource audioSource = peerConnectionFactory.createAudioSource(new MediaConstraints());
AudioTrack audioTrack = peerConnectionFactory.createAudioTrack("101", audioSource);
mediaStream = peerConnectionFactory.createLocalMediaStream("mediaStream");
mediaStream.addTrack(videoTrack);
mediaStream.addTrack(audioTrack);
SignalingClient.get().setCallback(this);
call();
}
private void call() {
List<PeerConnection.IceServer> iceServers = new ArrayList<>();
iceServers.add(PeerConnection.IceServer.builder("stun:stun.l.google.com:19302").createIceServer());
peerConnection = peerConnectionFactory.createPeerConnection(iceServers, new PeerConnectionAdapter("localconnection") {
@Override
public void onIceCandidate(IceCandidate iceCandidate) {
super.onIceCandidate(iceCandidate);
SignalingClient.get().sendIceCandidate(iceCandidate);
}
@Override
public void onAddStream(MediaStream mediaStream) {
super.onAddStream(mediaStream);
VideoTrack remoteVideoTrack = mediaStream.videoTracks.get(0);
runOnUiThread(() -> {
remoteVideoTrack.addSink(remoteView);
});
}
});
peerConnection.addStream(mediaStream);
}
private VideoCapturer createCameraCapturer(boolean isFront) {
Camera1Enumerator enumerator = new Camera1Enumerator(false);
final String[] deviceNames = enumerator.getDeviceNames();
// First, try to find front facing camera
for (String deviceName : deviceNames) {
if (isFront ? enumerator.isFrontFacing(deviceName) : enumerator.isBackFacing(deviceName)) {
VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
if (videoCapturer != null) {
return videoCapturer;
}
}
}
return null;
}
@Override
public void onCreateRoom() {
}
@Override
public void onPeerJoined() {
}
@Override
public void onSelfJoined() {
peerConnection.createOffer(new SdpAdapter("local offer sdp") {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
super.onCreateSuccess(sessionDescription);
peerConnection.setLocalDescription(new SdpAdapter("local set local"), sessionDescription);
SignalingClient.get().sendSessionDescription(sessionDescription);
}
}, new MediaConstraints());
}
@Override
public void onPeerLeave(String msg) {
}
@Override
public void onOfferReceived(JSONObject data) {
runOnUiThread(() -> {
peerConnection.setRemoteDescription(new SdpAdapter("localSetRemote"), new SessionDescription(SessionDescription.Type.OFFER, data.optString("sdp")));
peerConnection.createAnswer(new SdpAdapter("localAnswerSdp") {
@Override
public void onCreateSuccess(SessionDescription sdp) {
super.onCreateSuccess(sdp);
peerConnection.setLocalDescription(new SdpAdapter("localSetLocal"), sdp);
SignalingClient.get().sendSessionDescription(sdp);
}
}, new MediaConstraints());
});
}
@Override
public void onAnswerReceived(JSONObject data) {
peerConnection.setRemoteDescription(new SdpAdapter("localSetRemote"), new SessionDescription(SessionDescription.Type.ANSWER, data.optString("sdp")));
}
@Override
public void onIceCandidateReceived(JSONObject data) {
peerConnection.addIceCandidate(new IceCandidate(data.optString("id"), data.optInt("label"), data.optString("candidate")));
}
}
19
Source : MainActivity.java
with Apache License 2.0
from rome753
with Apache License 2.0
from rome753
public clreplaced MainActivity extends AppCompatActivity {
PeerConnectionFactory peerConnectionFactory;
PeerConnection peerConnectionLocal;
PeerConnection peerConnectionRemote;
SurfaceViewRenderer localView;
SurfaceViewRenderer remoteView;
MediaStream mediaStreamLocal;
MediaStream mediaStreamRemote;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EglBase.Context eglBaseContext = EglBase.create().getEglBaseContext();
// create PeerConnectionFactory
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions.builder(this).createInitializationOptions());
PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
DefaultVideoEncoderFactory defaultVideoEncoderFactory = new DefaultVideoEncoderFactory(eglBaseContext, true, true);
DefaultVideoDecoderFactory defaultVideoDecoderFactory = new DefaultVideoDecoderFactory(eglBaseContext);
peerConnectionFactory = PeerConnectionFactory.builder().setOptions(options).setVideoEncoderFactory(defaultVideoEncoderFactory).setVideoDecoderFactory(defaultVideoDecoderFactory).createPeerConnectionFactory();
SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", eglBaseContext);
// create VideoCapturer
VideoCapturer videoCapturer = createCameraCapturer(true);
VideoSource videoSource = peerConnectionFactory.createVideoSource(videoCapturer.isScreencast());
videoCapturer.initialize(surfaceTextureHelper, getApplicationContext(), videoSource.getCapturerObserver());
videoCapturer.startCapture(480, 640, 30);
localView = findViewById(R.id.localView);
localView.setMirror(true);
localView.init(eglBaseContext, null);
// create VideoTrack
VideoTrack videoTrack = peerConnectionFactory.createVideoTrack("100", videoSource);
// // display in localView
// videoTrack.addSink(localView);
SurfaceTextureHelper remoteSurfaceTextureHelper = SurfaceTextureHelper.create("RemoteCaptureThread", eglBaseContext);
// create VideoCapturer
VideoCapturer remoteVideoCapturer = createCameraCapturer(false);
VideoSource remoteVideoSource = peerConnectionFactory.createVideoSource(remoteVideoCapturer.isScreencast());
remoteVideoCapturer.initialize(remoteSurfaceTextureHelper, getApplicationContext(), remoteVideoSource.getCapturerObserver());
remoteVideoCapturer.startCapture(480, 640, 30);
remoteView = findViewById(R.id.remoteView);
remoteView.setMirror(false);
remoteView.init(eglBaseContext, null);
// create VideoTrack
VideoTrack remoteVideoTrack = peerConnectionFactory.createVideoTrack("102", remoteVideoSource);
// // display in remoteView
// remoteVideoTrack.addSink(remoteView);
mediaStreamLocal = peerConnectionFactory.createLocalMediaStream("mediaStreamLocal");
mediaStreamLocal.addTrack(videoTrack);
mediaStreamRemote = peerConnectionFactory.createLocalMediaStream("mediaStreamRemote");
mediaStreamRemote.addTrack(remoteVideoTrack);
call(mediaStreamLocal, mediaStreamRemote);
}
private void call(MediaStream localMediaStream, MediaStream remoteMediaStream) {
List<PeerConnection.IceServer> iceServers = new ArrayList<>();
peerConnectionLocal = peerConnectionFactory.createPeerConnection(iceServers, new PeerConnectionAdapter("localconnection") {
@Override
public void onIceCandidate(IceCandidate iceCandidate) {
super.onIceCandidate(iceCandidate);
peerConnectionRemote.addIceCandidate(iceCandidate);
}
@Override
public void onAddStream(MediaStream mediaStream) {
super.onAddStream(mediaStream);
VideoTrack remoteVideoTrack = mediaStream.videoTracks.get(0);
runOnUiThread(() -> {
remoteVideoTrack.addSink(localView);
});
}
});
peerConnectionRemote = peerConnectionFactory.createPeerConnection(iceServers, new PeerConnectionAdapter("remoteconnection") {
@Override
public void onIceCandidate(IceCandidate iceCandidate) {
super.onIceCandidate(iceCandidate);
peerConnectionLocal.addIceCandidate(iceCandidate);
}
@Override
public void onAddStream(MediaStream mediaStream) {
super.onAddStream(mediaStream);
VideoTrack localVideoTrack = mediaStream.videoTracks.get(0);
runOnUiThread(() -> {
localVideoTrack.addSink(remoteView);
});
}
});
peerConnectionLocal.addStream(localMediaStream);
peerConnectionLocal.createOffer(new SdpAdapter("local offer sdp") {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
super.onCreateSuccess(sessionDescription);
// todo crashed here
peerConnectionLocal.setLocalDescription(new SdpAdapter("local set local"), sessionDescription);
peerConnectionRemote.addStream(remoteMediaStream);
peerConnectionRemote.setRemoteDescription(new SdpAdapter("remote set remote"), sessionDescription);
peerConnectionRemote.createAnswer(new SdpAdapter("remote answer sdp") {
@Override
public void onCreateSuccess(SessionDescription sdp) {
super.onCreateSuccess(sdp);
peerConnectionRemote.setLocalDescription(new SdpAdapter("remote set local"), sdp);
peerConnectionLocal.setRemoteDescription(new SdpAdapter("local set remote"), sdp);
}
}, new MediaConstraints());
}
}, new MediaConstraints());
}
private VideoCapturer createCameraCapturer(boolean isFront) {
Camera1Enumerator enumerator = new Camera1Enumerator(false);
final String[] deviceNames = enumerator.getDeviceNames();
// First, try to find front facing camera
for (String deviceName : deviceNames) {
if (isFront ? enumerator.isFrontFacing(deviceName) : enumerator.isBackFacing(deviceName)) {
VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
if (videoCapturer != null) {
return videoCapturer;
}
}
}
return null;
}
}
19
Source : Stream.java
with MIT License
from qunarcorp
with MIT License
from qunarcorp
/**
* Created by xinbo.wang on 2017-04-05.
*/
public clreplaced Stream {
protected PeerConnection peerConnection;
protected boolean localStream;
protected IConferenceView view;
protected boolean isDispose;
public Stream(boolean isLocal, IConferenceView view) {
this.localStream = isLocal;
this.view = view;
}
public void updateMainSpeaker() {
}
public void dispose() {
peerConnection.dispose();
isDispose = true;
}
public boolean isDispose() {
return isDispose;
}
public void subscribe() {
}
}
19
Source : RtcEventLog.java
with MIT License
from pcgpcgpcg
with MIT License
from pcgpcgpcg
public clreplaced RtcEventLog {
private static final String TAG = "RtcEventLog";
private static final int OUTPUT_FILE_MAX_BYTES = 10_000_000;
private final PeerConnection peerConnection;
private RtcEventLogState state = RtcEventLogState.INACTIVE;
enum RtcEventLogState {
INACTIVE, STARTED, STOPPED
}
public RtcEventLog(PeerConnection peerConnection) {
if (peerConnection == null) {
throw new NullPointerException("The peer connection is null.");
}
this.peerConnection = peerConnection;
}
public void start(final File outputFile) {
if (state == RtcEventLogState.STARTED) {
Log.e(TAG, "RtcEventLog has already started.");
return;
}
final ParcelFileDescriptor fileDescriptor;
try {
fileDescriptor = ParcelFileDescriptor.open(outputFile, ParcelFileDescriptor.MODE_READ_WRITE | ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_TRUNCATE);
} catch (IOException e) {
Log.e(TAG, "Failed to create a new file", e);
return;
}
// Preplacedes ownership of the file to WebRTC.
boolean success = peerConnection.startRtcEventLog(fileDescriptor.detachFd(), OUTPUT_FILE_MAX_BYTES);
if (!success) {
Log.e(TAG, "Failed to start RTC event log.");
return;
}
state = RtcEventLogState.STARTED;
Log.d(TAG, "RtcEventLog started.");
}
public void stop() {
if (state != RtcEventLogState.STARTED) {
Log.e(TAG, "RtcEventLog was not started.");
return;
}
peerConnection.stopRtcEventLog();
state = RtcEventLogState.STOPPED;
Log.d(TAG, "RtcEventLog stopped.");
}
}
19
Source : JanusConnection2.java
with MIT License
from pcgpcgpcg
with MIT License
from pcgpcgpcg
public clreplaced JreplacedConnection2 {
public BigInteger handleId;
public PeerConnection peerConnection;
public PeerConnectionClient2.SDPObserver sdpObserver;
public VideoTrack videoTrack;
public boolean type;
}
19
Source : JanusConnection.java
with MIT License
from pcgpcgpcg
with MIT License
from pcgpcgpcg
public clreplaced JreplacedConnection {
public BigInteger handleId;
public PeerConnection peerConnection;
public PeerConnectionClient.SDPObserver sdpObserver;
public VideoTrack videoTrack;
public SurfaceViewRenderer videoRender;
public boolean type;
}
19
Source : PeerConnectionChannel.java
with Apache License 2.0
from open-webrtc-toolkit
with Apache License 2.0
from open-webrtc-toolkit
// /@cond
public abstract clreplaced PeerConnectionChannel implements PeerConnection.Observer, SdpObserver, DataChannel.Observer {
// For P2P, key is peer id, for conference, key is Publication/Subscription id.
public final String key;
protected final PeerConnectionChannelObserver observer;
protected final ExecutorService callbackExecutor = Executors.newSingleThreadExecutor();
private final ExecutorService pcExecutor = Executors.newSingleThreadExecutor();
private final List<IceCandidate> queuedRemoteCandidates;
private final Object remoteIceLock = new Object();
private final Object disposeLock = new Object();
protected PeerConnection peerConnection;
protected PeerConnection.SignalingState signalingState;
protected PeerConnection.IceConnectionState iceConnectionState;
protected DataChannel localDataChannel;
protected List<VideoCodec> videoCodecs;
protected List<AudioCodec> audioCodecs;
protected Integer videoMaxBitrate = null, audioMaxBitrate = null;
protected ArrayList<String> queuedMessage;
private MediaConstraints sdpConstraints;
private SessionDescription localSdp;
private boolean disposed = false;
protected boolean onError = false;
// <MediaStream id, RtpSender>
private ConcurrentHashMap<String, RtpSender> videoRtpSenders, audioRtpSenders;
protected PeerConnectionChannel(String key, PeerConnection.RTCConfiguration configuration, boolean receiveVideo, boolean receiveAudio, PeerConnectionChannelObserver observer) {
this.key = key;
this.observer = observer;
videoRtpSenders = new ConcurrentHashMap<>();
audioRtpSenders = new ConcurrentHashMap<>();
queuedRemoteCandidates = new LinkedList<>();
queuedMessage = new ArrayList<>();
sdpConstraints = new MediaConstraints();
sdpConstraints.mandatory.add(new KeyValuePair("OfferToReceiveAudio", String.valueOf(receiveAudio)));
sdpConstraints.mandatory.add(new KeyValuePair("OfferToReceiveVideo", String.valueOf(receiveVideo)));
peerConnection = PCFactoryProxy.instance().createPeerConnection(configuration, this);
RCHECK(peerConnection);
signalingState = peerConnection.signalingState();
}
// Utility method for getting the mediastream as it is package privileged.
protected static MediaStream GetMediaStream(Stream localStream) {
return localStream.mediaStream;
}
public boolean disposed() {
synchronized (disposeLock) {
return disposed;
}
}
protected void createOffer() {
DCHECK(pcExecutor);
pcExecutor.execute(() -> {
if (disposed()) {
return;
}
Log.d(LOG_TAG, "create offer");
peerConnection.createOffer(PeerConnectionChannel.this, sdpConstraints);
});
}
protected void createAnswer() {
DCHECK(pcExecutor);
pcExecutor.execute(() -> {
if (disposed()) {
return;
}
Log.d(LOG_TAG, "creating answer");
peerConnection.createAnswer(PeerConnectionChannel.this, sdpConstraints);
});
}
public void processSignalingMessage(JSONObject data) throws JSONException {
String signalingType = data.getString("type");
if (signalingType.equals("candidates")) {
IceCandidate candidate = new IceCandidate(data.getString("sdpMid"), data.getInt("sdpMLineIndex"), data.getString("candidate"));
addOrQueueCandidate(candidate);
} else if (signalingType.equals("offer") || signalingType.equals("answer")) {
// When gets an SDP during onError, it means that i'm waiting for peer to re-configure
// itself to keep compatibility with me.
if (onError) {
// Ignore the SDP only once.
onError = false;
return;
}
String sdpString = data.getString("sdp");
SessionDescription remoteSdp = new SessionDescription(SessionDescription.Type.fromCanonicalForm(signalingType), sdpString);
setRemoteDescription(remoteSdp);
}
}
private void addOrQueueCandidate(final IceCandidate iceCandidate) {
if (disposed()) {
return;
}
DCHECK(pcExecutor);
DCHECK(iceCandidate);
pcExecutor.execute(() -> {
if (disposed()) {
return;
}
if (peerConnection.signalingState() == PeerConnection.SignalingState.STABLE) {
Log.d(LOG_TAG, "add ice candidate");
peerConnection.addIceCandidate(iceCandidate);
} else {
synchronized (remoteIceLock) {
Log.d(LOG_TAG, "queue ice candidate");
queuedRemoteCandidates.add(iceCandidate);
}
}
});
}
protected void drainRemoteCandidates() {
DCHECK(pcExecutor);
DCHECK(queuedRemoteCandidates);
synchronized (remoteIceLock) {
for (final IceCandidate candidate : queuedRemoteCandidates) {
pcExecutor.execute(() -> {
if (disposed()) {
return;
}
Log.d(LOG_TAG, "add ice candidate");
peerConnection.addIceCandidate(candidate);
queuedRemoteCandidates.remove(candidate);
});
}
}
}
private void setRemoteDescription(final SessionDescription remoteDescription) {
pcExecutor.execute(() -> {
if (disposed()) {
return;
}
peerConnection.setRemoteDescription(PeerConnectionChannel.this, remoteDescription);
});
}
protected void addStream(final MediaStream mediaStream) {
DCHECK(mediaStream);
DCHECK(pcExecutor);
pcExecutor.execute(() -> {
if (disposed()) {
return;
}
ArrayList<String> streamIds = new ArrayList<>();
streamIds.add(mediaStream.getId());
for (AudioTrack audioTrack : mediaStream.audioTracks) {
RtpSender audioSender = peerConnection.addTrack(audioTrack, streamIds);
audioRtpSenders.put(mediaStream.getId(), audioSender);
}
for (VideoTrack videoTrack : mediaStream.videoTracks) {
RtpSender videoSender = peerConnection.addTrack(videoTrack, streamIds);
videoRtpSenders.put(mediaStream.getId(), videoSender);
}
});
}
protected void removeStream(String mediaStreamId) {
DCHECK(pcExecutor);
pcExecutor.execute(() -> {
if (disposed()) {
return;
}
Log.d(LOG_TAG, "remove stream");
if (audioRtpSenders.get(mediaStreamId) != null) {
peerConnection.removeTrack(audioRtpSenders.get(mediaStreamId));
}
if (videoRtpSenders.get(mediaStreamId) != null) {
peerConnection.removeTrack(videoRtpSenders.get(mediaStreamId));
}
});
}
protected void createDataChannel() {
DCHECK(pcExecutor);
DCHECK(localDataChannel == null);
pcExecutor.execute(() -> {
if (disposed()) {
return;
}
DataChannel.Init init = new DataChannel.Init();
localDataChannel = peerConnection.createDataChannel("message", init);
localDataChannel.registerObserver(PeerConnectionChannel.this);
});
}
public void getConnectionStats(final ActionCallback<RTCStatsReport> callback) {
DCHECK(pcExecutor);
pcExecutor.execute(() -> {
if (disposed()) {
callback.onFailure(new OwtError("Invalid stats."));
return;
}
peerConnection.getStats(callback::onSuccess);
});
}
private SessionDescription preferCodecs(SessionDescription sdp, boolean video) {
LinkedHashSet<String> preferredCodecs = new LinkedHashSet<>();
if (video) {
for (VideoCodec codec : this.videoCodecs) {
preferredCodecs.add(codec.name);
}
preferredCodecs.add("red");
preferredCodecs.add("ulpfec");
} else {
for (AudioCodec codec : audioCodecs) {
preferredCodecs.add(codec.name);
}
preferredCodecs.add("CN");
preferredCodecs.add("telephone-event");
}
return preferCodec(sdp, preferredCodecs, video);
}
private SessionDescription preferCodec(SessionDescription originalSdp, LinkedHashSet<String> preferredCodecs, boolean video) {
String[] lines = originalSdp.description.split("(\r\n|\n)");
ArrayList<String> newLines = new ArrayList<>();
int audioMLineIndex = -1;
int videoMLineIndex = -1;
// <codecName, payloadType>
HashMap<String, ArrayList<String>> preferredPayloadTypes = new HashMap<>();
// skipped all video payload types when dealing with audio codecs, and vice versa.
HashSet<String> misMatchedPayloadTypes = new HashSet<>();
for (int i = 0; i < lines.length; i++) {
String line = lines[i];
if (line.startsWith("a=rtpmap:")) {
String payloadType = line.split(" ")[0].split(":")[1];
String codecName = line.split(" ")[1].split("/")[0];
boolean typeMismatched = video ? VideoCodec.get(codecName) == VideoCodec.INVALID : AudioCodec.get(codecName) == AudioCodec.INVALID;
boolean codecPreferred = preferredCodecs.contains(codecName);
boolean rtxPreferred = codecName.equals("rtx") && containsValue(preferredPayloadTypes, lines[i + 1].split("apt=")[1]);
if (codecPreferred || rtxPreferred) {
putEntry(preferredPayloadTypes, codecName, payloadType);
} else if (typeMismatched && !codecName.equals("rtx")) {
misMatchedPayloadTypes.add(payloadType);
} else {
continue;
}
} else if (line.startsWith("a=rtcp-fb:") || line.startsWith("a=fmtp:")) {
String payloadType = line.split(" ")[0].split(":")[1];
if (!misMatchedPayloadTypes.contains(payloadType) && !containsValue(preferredPayloadTypes, payloadType)) {
continue;
}
} else if (line.startsWith("m=audio")) {
audioMLineIndex = newLines.size();
} else if (line.startsWith("m=video")) {
videoMLineIndex = newLines.size();
}
newLines.add(line);
}
if (!video && audioMLineIndex != -1) {
newLines.set(audioMLineIndex, changeMLine(newLines.get(audioMLineIndex), preferredCodecs, preferredPayloadTypes));
}
if (video && videoMLineIndex != -1) {
newLines.set(videoMLineIndex, changeMLine(newLines.get(videoMLineIndex), preferredCodecs, preferredPayloadTypes));
}
String newSdp = joinString(newLines, "\r\n", true);
return new SessionDescription(originalSdp.type, newSdp);
}
private boolean containsValue(HashMap<String, ArrayList<String>> payloadTypes, String value) {
for (ArrayList<String> v : payloadTypes.values()) {
for (String s : v) {
if (s.equals(value)) {
return true;
}
}
}
return false;
}
private void putEntry(HashMap<String, ArrayList<String>> payloadTypes, String key, String value) {
if (payloadTypes.containsKey(key)) {
payloadTypes.get(key).add(value);
} else {
ArrayList<String> valueList = new ArrayList<>();
valueList.add(value);
payloadTypes.put(key, valueList);
}
}
private String changeMLine(String mLine, LinkedHashSet<String> preferredCodecs, HashMap<String, ArrayList<String>> preferredPayloadTypes) {
List<String> oldMLineParts = Arrays.asList(mLine.split(" "));
List<String> mLineHeader = oldMLineParts.subList(0, 3);
ArrayList<String> newMLineParts = new ArrayList<>(mLineHeader);
for (String preferredCodec : preferredCodecs) {
if (preferredPayloadTypes.containsKey(preferredCodec)) {
newMLineParts.addAll(preferredPayloadTypes.get(preferredCodec));
}
}
if (preferredPayloadTypes.containsKey("rtx")) {
newMLineParts.addAll(preferredPayloadTypes.get("rtx"));
}
return joinString(newMLineParts, " ", false);
}
private String joinString(ArrayList<String> strings, String delimiter, boolean delimiterAtEnd) {
Iterator<String> iter = strings.iterator();
if (!iter.hasNext()) {
return "";
}
StringBuilder buffer = new StringBuilder(iter.next());
while (iter.hasNext()) {
buffer.append(delimiter).append(iter.next());
}
if (delimiterAtEnd) {
buffer.append(delimiter);
}
return buffer.toString();
}
private void setMaxBitrate(RtpSender sender, Integer bitrate) {
if (sender == null || bitrate == null || bitrate.intValue() <= 0) {
return;
}
RtpParameters rtpParameters = sender.getParameters();
if (rtpParameters == null) {
Log.e(LOG_TAG, "Null rtp paramters");
return;
}
for (RtpParameters.Encoding encoding : rtpParameters.encodings) {
encoding.maxBitrateBps = bitrate * 1000;
}
if (!sender.setParameters(rtpParameters)) {
Log.e(LOG_TAG, "Failed to configure max audio/video bitrate");
}
}
protected void setMaxBitrate(String mediaStreamId) {
DCHECK(peerConnection);
if (videoRtpSenders.get(mediaStreamId) != null) {
setMaxBitrate(videoRtpSenders.get(mediaStreamId), videoMaxBitrate);
}
if (audioRtpSenders.get(mediaStreamId) != null) {
setMaxBitrate(audioRtpSenders.get(mediaStreamId), audioMaxBitrate);
}
}
protected void dispose() {
pcExecutor.execute(() -> {
synchronized (disposeLock) {
disposed = true;
if (peerConnection != null) {
peerConnection.dispose();
}
peerConnection = null;
}
});
}
// SdpObserver
@Override
public void onCreateSuccess(final SessionDescription sessionDescription) {
localSdp = sessionDescription;
if (audioCodecs != null) {
localSdp = preferCodecs(localSdp, false);
}
if (videoCodecs != null) {
localSdp = preferCodecs(localSdp, true);
}
callbackExecutor.execute(() -> {
if (disposed) {
return;
}
observer.onLocalDescription(key, localSdp);
});
pcExecutor.execute(() -> {
if (disposed) {
return;
}
peerConnection.setLocalDescription(PeerConnectionChannel.this, localSdp);
});
}
@Override
abstract public void onSetSuccess();
@Override
abstract public void onCreateFailure(final String error);
@Override
abstract public void onSetFailure(final String error);
// PeerConnection.Observer interface
@Override
abstract public void onSignalingChange(PeerConnection.SignalingState signalingState);
@Override
abstract public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState);
@Override
public void onIceConnectionReceivingChange(boolean b) {
}
@Override
public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
}
@Override
abstract public void onIceCandidate(IceCandidate iceCandidate);
@Override
abstract public void onIceCandidatesRemoved(IceCandidate[] iceCandidates);
@Override
abstract public void onAddStream(MediaStream mediaStream);
@Override
abstract public void onRemoveStream(final MediaStream mediaStream);
@Override
public void onDataChannel(final DataChannel dataChannel) {
if (disposed) {
return;
}
callbackExecutor.execute(() -> {
localDataChannel = dataChannel;
localDataChannel.registerObserver(PeerConnectionChannel.this);
});
}
@Override
abstract public void onRenegotiationNeeded();
@Override
public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {
}
// DataChannel.Observer interface
@Override
public void onBufferedAmountChange(long l) {
}
@Override
public void onStateChange() {
if (disposed) {
return;
}
callbackExecutor.execute(() -> {
if (localDataChannel.state() == DataChannel.State.OPEN) {
for (int i = 0; i < queuedMessage.size(); i++) {
ByteBuffer byteBuffer = ByteBuffer.wrap(queuedMessage.get(i).getBytes(Charset.forName("UTF-8")));
DataChannel.Buffer buffer = new DataChannel.Buffer(byteBuffer, false);
localDataChannel.send(buffer);
}
queuedMessage.clear();
}
});
}
@Override
public void onMessage(final DataChannel.Buffer buffer) {
if (disposed) {
return;
}
callbackExecutor.execute(() -> {
ByteBuffer data = buffer.data;
final byte[] bytes = new byte[data.capacity()];
data.get(bytes);
String message = new String(bytes);
observer.onDataChannelMessage(key, message);
});
}
public interface PeerConnectionChannelObserver {
void onIceCandidate(String key, IceCandidate candidate);
void onIceCandidatesRemoved(String key, IceCandidate[] candidates);
void onLocalDescription(String key, SessionDescription localSdp);
/**
* @param recoverable true means SDK gotta reconfigure the peerconnection instead of
* triggering failure actions.
*/
void onError(String key, String errorMsg, boolean recoverable);
void onEnded(String key);
void onAddStream(String key, RemoteStream remoteStream);
void onDataChannelMessage(String key, String message);
void onRenegotiationRequest(String key);
}
}
19
Source : MainActivity.java
with MIT License
from Nitrillo
with MIT License
from Nitrillo
public clreplaced MainActivity extends AppCompatActivity {
private static final String SIGNALING_URI = "http://your.ip.here:7000";
private static final String VIDEO_TRACK_ID = "video1";
private static final String AUDIO_TRACK_ID = "audio1";
private static final String LOCAL_STREAM_ID = "stream1";
private static final String SDP_MID = "sdpMid";
private static final String SDP_M_LINE_INDEX = "sdpMLineIndex";
private static final String SDP = "sdp";
private static final String CREATEOFFER = "createoffer";
private static final String OFFER = "offer";
private static final String ANSWER = "answer";
private static final String CANDIDATE = "candidate";
private PeerConnectionFactory peerConnectionFactory;
private VideoSource localVideoSource;
private PeerConnection peerConnection;
private MediaStream localMediaStream;
private VideoRenderer otherPeerRenderer;
private Socket socket;
private boolean createOffer = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AudioManager audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.setSpeakerphoneOn(true);
PeerConnectionFactory.initializeAndroidGlobals(// Context
this, // Audio Enabled
true, // Video Enabled
true, // Hardware Acceleration Enabled
true, // Render EGL Context
null);
peerConnectionFactory = new PeerConnectionFactory();
VideoCapturerAndroid vc = VideoCapturerAndroid.create(VideoCapturerAndroid.getNameOfFrontFacingDevice(), null);
localVideoSource = peerConnectionFactory.createVideoSource(vc, new MediaConstraints());
VideoTrack localVideoTrack = peerConnectionFactory.createVideoTrack(VIDEO_TRACK_ID, localVideoSource);
localVideoTrack.setEnabled(true);
AudioSource audioSource = peerConnectionFactory.createAudioSource(new MediaConstraints());
AudioTrack localAudioTrack = peerConnectionFactory.createAudioTrack(AUDIO_TRACK_ID, audioSource);
localAudioTrack.setEnabled(true);
localMediaStream = peerConnectionFactory.createLocalMediaStream(LOCAL_STREAM_ID);
localMediaStream.addTrack(localVideoTrack);
localMediaStream.addTrack(localAudioTrack);
GLSurfaceView videoView = (GLSurfaceView) findViewById(R.id.glview_call);
VideoRendererGui.setView(videoView, null);
try {
otherPeerRenderer = VideoRendererGui.createGui(0, 0, 100, 100, VideoRendererGui.ScalingType.SCALE_ASPECT_FILL, true);
VideoRenderer renderer = VideoRendererGui.createGui(50, 50, 50, 50, VideoRendererGui.ScalingType.SCALE_ASPECT_FILL, true);
localVideoTrack.addRenderer(renderer);
} catch (Exception e) {
e.printStackTrace();
}
}
public void onConnect(View button) {
if (peerConnection != null)
return;
ArrayList<PeerConnection.IceServer> iceServers = new ArrayList<>();
iceServers.add(new PeerConnection.IceServer("stun:stun.l.google.com:19302"));
peerConnection = peerConnectionFactory.createPeerConnection(iceServers, new MediaConstraints(), peerConnectionObserver);
peerConnection.addStream(localMediaStream);
try {
socket = IO.socket(SIGNALING_URI);
socket.on(CREATEOFFER, new Emitter.Listener() {
@Override
public void call(Object... args) {
createOffer = true;
peerConnection.createOffer(sdpObserver, new MediaConstraints());
}
}).on(OFFER, new Emitter.Listener() {
@Override
public void call(Object... args) {
try {
JSONObject obj = (JSONObject) args[0];
SessionDescription sdp = new SessionDescription(SessionDescription.Type.OFFER, obj.getString(SDP));
peerConnection.setRemoteDescription(sdpObserver, sdp);
peerConnection.createAnswer(sdpObserver, new MediaConstraints());
} catch (JSONException e) {
e.printStackTrace();
}
}
}).on(ANSWER, new Emitter.Listener() {
@Override
public void call(Object... args) {
try {
JSONObject obj = (JSONObject) args[0];
SessionDescription sdp = new SessionDescription(SessionDescription.Type.ANSWER, obj.getString(SDP));
peerConnection.setRemoteDescription(sdpObserver, sdp);
} catch (JSONException e) {
e.printStackTrace();
}
}
}).on(CANDIDATE, new Emitter.Listener() {
@Override
public void call(Object... args) {
try {
JSONObject obj = (JSONObject) args[0];
peerConnection.addIceCandidate(new IceCandidate(obj.getString(SDP_MID), obj.getInt(SDP_M_LINE_INDEX), obj.getString(SDP)));
} catch (JSONException e) {
e.printStackTrace();
}
}
});
socket.connect();
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
SdpObserver sdpObserver = new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
peerConnection.setLocalDescription(sdpObserver, sessionDescription);
try {
JSONObject obj = new JSONObject();
obj.put(SDP, sessionDescription.description);
if (createOffer) {
socket.emit(OFFER, obj);
} else {
socket.emit(ANSWER, obj);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
@Override
public void onSetSuccess() {
}
@Override
public void onCreateFailure(String s) {
}
@Override
public void onSetFailure(String s) {
}
};
PeerConnection.Observer peerConnectionObserver = new PeerConnection.Observer() {
@Override
public void onSignalingChange(PeerConnection.SignalingState signalingState) {
Log.d("RTCAPP", "onSignalingChange:" + signalingState.toString());
}
@Override
public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
Log.d("RTCAPP", "onIceConnectionChange:" + iceConnectionState.toString());
}
@Override
public void onIceConnectionReceivingChange(boolean b) {
}
@Override
public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
}
@Override
public void onIceCandidate(IceCandidate iceCandidate) {
try {
JSONObject obj = new JSONObject();
obj.put(SDP_MID, iceCandidate.sdpMid);
obj.put(SDP_M_LINE_INDEX, iceCandidate.sdpMLineIndex);
obj.put(SDP, iceCandidate.sdp);
socket.emit(CANDIDATE, obj);
} catch (JSONException e) {
e.printStackTrace();
}
}
@Override
public void onAddStream(MediaStream mediaStream) {
mediaStream.videoTracks.getFirst().addRenderer(otherPeerRenderer);
}
@Override
public void onRemoveStream(MediaStream mediaStream) {
}
@Override
public void onDataChannel(DataChannel dataChannel) {
}
@Override
public void onRenegotiationNeeded() {
}
};
}
19
Source : RTCCall.java
with GNU General Public License v3.0
from meshenger-app
with GNU General Public License v3.0
from meshenger-app
public clreplaced RTCCall implements DataChannel.Observer {
enum CallState {
CONNECTING,
RINGING,
CONNECTED,
DISMISSED,
ENDED,
ERROR
}
private final String StateChangeMessage = "StateChange";
private final String CameraDisabledMessage = "CameraDisabled";
private final String CameraEnabledMessage = "CameraEnabled";
private PeerConnectionFactory factory;
private PeerConnection connection;
private MediaConstraints constraints;
private String offer;
private SurfaceViewRenderer remoteRenderer;
private SurfaceViewRenderer localRenderer;
private EglBase.Context sharedContext;
private CameraVideoCapturer capturer;
private MediaStream upStream;
private DataChannel dataChannel;
private boolean videoEnabled;
private Context context;
private Contact contact;
private byte[] ownPublicKey;
private byte[] ownSecretKey;
private List<PeerConnection.IceServer> iceServers;
private OnStateChangeListener listener;
private MainService.MainBinder binder;
public CallState state;
public Socket commSocket;
static public RTCCall startCall(Context context, MainService.MainBinder binder, Contact contact, OnStateChangeListener listener) {
return new RTCCall(context, binder, contact, listener);
}
// called for incoming calls
public RTCCall(Context context, MainService.MainBinder binder, Contact contact, Socket commSocket, String offer) {
this.context = context;
this.contact = contact;
this.commSocket = commSocket;
this.listener = null;
this.binder = binder;
this.ownPublicKey = binder.getSettings().getPublicKey();
this.ownSecretKey = binder.getSettings().getSecretKey();
this.offer = offer;
// usually empty
this.iceServers = new ArrayList<>();
for (String server : this.binder.getSettings().getIceServers()) {
this.iceServers.add(PeerConnection.IceServer.builder(server).createIceServer());
}
initRTC(context);
}
// called for outgoing calls
private RTCCall(Context context, MainService.MainBinder binder, Contact contact, OnStateChangeListener listener) {
this.context = context;
this.contact = contact;
this.commSocket = null;
this.listener = listener;
this.binder = binder;
this.ownPublicKey = binder.getSettings().getPublicKey();
this.ownSecretKey = binder.getSettings().getSecretKey();
log("RTCCall created");
// usually empty
this.iceServers = new ArrayList<>();
for (String server : binder.getSettings().getIceServers()) {
this.iceServers.add(PeerConnection.IceServer.builder(server).createIceServer());
}
initRTC(context);
if (AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES) {
context.setTheme(R.style.AppTheme_Dark);
} else {
context.setTheme(R.style.AppTheme_Light);
}
new Thread(() -> {
connection = factory.createPeerConnection(Collections.emptyList(), new DefaultObserver() {
@Override
public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
super.onIceGatheringChange(iceGatheringState);
byte[] otherPublicKey = new byte[Sodium.crypto_sign_publickeybytes()];
if (iceGatheringState == PeerConnection.IceGatheringState.COMPLETE) {
log("transferring offer...");
try {
commSocket = contact.createSocket();
if (commSocket == null) {
log("cannot establish connection");
reportStateChange(CallState.ERROR);
// RTCCall.this.binder.addCallEvent(contact, CallEvent.Type.OUTGOING_ERROR);
return;
}
InetSocketAddress remote_address = (InetSocketAddress) commSocket.getRemoteSocketAddress();
log("outgoing call from remote address: " + remote_address);
// remember latest working address
contact.setLastWorkingAddress(new InetSocketAddress(remote_address.getAddress(), MainService.serverPort));
log("connect..");
PacketReader pr = new PacketReader(commSocket);
reportStateChange(CallState.CONNECTING);
{
JSONObject obj = new JSONObject();
obj.put("action", "call");
obj.put("offer", connection.getLocalDescription().description);
byte[] encrypted = Crypto.encryptMessage(obj.toString(), contact.getPublicKey(), ownPublicKey, ownSecretKey);
if (encrypted == null) {
log("encryption failed");
closeCommSocket();
reportStateChange(CallState.ERROR);
// RTCCall.this.binder.addCallEvent(contact, CallEvent.Type.OUTGOING_ERROR);
return;
}
PacketWriter pw = new PacketWriter(commSocket);
pw.writeMessage(encrypted);
}
{
byte[] response = pr.readMessage();
String decrypted = Crypto.decryptMessage(response, otherPublicKey, ownPublicKey, ownSecretKey);
if (decrypted == null || !Arrays.equals(contact.getPublicKey(), otherPublicKey)) {
log("decryption failed");
closeCommSocket();
reportStateChange(CallState.ERROR);
// RTCCall.this.binder.addCallEvent(contact, CallEvent.Type.OUTGOING_ERROR);
return;
}
JSONObject obj = new JSONObject(decrypted);
if (!obj.optString("action", "").equals("ringing")) {
closeCommSocket();
reportStateChange(CallState.ERROR);
// RTCCall.this.binder.addCallEvent(contact, CallEvent.Type.OUTGOING_ERROR);
return;
}
log("ringing...");
reportStateChange(CallState.RINGING);
}
{
byte[] response = pr.readMessage();
String decrypted = Crypto.decryptMessage(response, otherPublicKey, ownPublicKey, ownSecretKey);
if (decrypted == null || !Arrays.equals(contact.getPublicKey(), otherPublicKey)) {
closeCommSocket();
reportStateChange(CallState.ERROR);
return;
}
JSONObject obj = new JSONObject(decrypted);
String action = obj.getString("action");
if (action.equals("connected")) {
reportStateChange(CallState.CONNECTED);
handleAnswer(obj.getString("answer"));
// contact accepted receiving call
// RTCCall.this.binder.addCallEvent(contact, CallEvent.Type.OUTGOING_ACCEPTED);
} else if (action.equals("dismissed")) {
closeCommSocket();
reportStateChange(CallState.DISMISSED);
// contact declined receiving call
// RTCCall.this.binder.addCallEvent(contact, CallEvent.Type.OUTGOING_DECLINED);
} else {
log("unknown action reply: " + action);
closeCommSocket();
reportStateChange(CallState.ERROR);
// RTCCall.this.binder.addCallEvent(contact, CallEvent.Type.OUTGOING_ERROR);
}
}
} catch (Exception e) {
closeCommSocket();
e.printStackTrace();
reportStateChange(CallState.ERROR);
// RTCCall.this.binder.addCallEvent(contact, CallEvent.Type.OUTGOING_ERROR);
}
}
}
@Override
public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
log("onIceGatheringChange.onIceConnectionChange " + iceConnectionState.name());
super.onIceConnectionChange(iceConnectionState);
if (iceConnectionState == PeerConnection.IceConnectionState.DISCONNECTED) {
reportStateChange(CallState.ENDED);
}
}
@Override
public void onAddStream(MediaStream mediaStream) {
super.onAddStream(mediaStream);
handleMediaStream(mediaStream);
}
@Override
public void onDataChannel(DataChannel dataChannel) {
super.onDataChannel(dataChannel);
RTCCall.this.dataChannel = dataChannel;
dataChannel.registerObserver(RTCCall.this);
}
});
connection.addStream(createStream());
this.dataChannel = connection.createDataChannel("data", new DataChannel.Init());
this.dataChannel.registerObserver(this);
connection.createOffer(new DefaultSdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
super.onCreateSuccess(sessionDescription);
connection.setLocalDescription(new DefaultSdpObserver(), sessionDescription);
}
}, constraints);
}).start();
}
private void closeCommSocket() {
log("closeCommSocket");
if (this.commSocket != null) {
try {
this.commSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
this.commSocket = null;
}
}
private void closePeerConnection() {
log("closePeerConnection");
if (this.connection != null) {
try {
this.connection.close();
} catch (Exception e) {
e.printStackTrace();
}
this.connection = null;
}
}
public void setRemoteRenderer(SurfaceViewRenderer remoteRenderer) {
this.remoteRenderer = remoteRenderer;
}
public void switchFrontFacing() {
if (this.capturer != null) {
this.capturer.switchCamera(null);
}
}
@Override
public void onBufferedAmountChange(long l) {
// nothing to do
}
@Override
public void onStateChange() {
// nothing to do
}
@Override
public void onMessage(DataChannel.Buffer buffer) {
byte[] data = new byte[buffer.data.remaining()];
buffer.data.get(data);
String s = new String(data);
JSONObject object = null;
try {
log("onMessage: " + s);
object = new JSONObject(s);
if (object.has(StateChangeMessage)) {
String state = object.getString(StateChangeMessage);
switch(state) {
case CameraEnabledMessage:
case CameraDisabledMessage:
{
setRemoteVideoEnabled(state.equals(CameraEnabledMessage));
break;
}
}
}
} catch (JSONException e) {
e.printStackTrace();
}
}
private void setRemoteVideoEnabled(boolean enabled) {
new Handler(Looper.getMainLooper()).post(() -> {
if (enabled) {
this.remoteRenderer.setBackgroundColor(Color.TRANSPARENT);
} else {
TypedValue typedValue = new TypedValue();
Resources.Theme theme = this.context.getTheme();
theme.resolveAttribute(R.attr.backgroundCardColor, typedValue, true);
@ColorInt
int color = typedValue.data;
this.remoteRenderer.setBackgroundColor(color);
}
});
}
public boolean isVideoEnabled() {
return this.videoEnabled;
}
public void setVideoEnabled(boolean enabled) {
this.videoEnabled = enabled;
try {
if (enabled) {
this.capturer.startCapture(500, 500, 30);
} else {
this.capturer.stopCapture();
}
JSONObject object = new JSONObject();
object.put(StateChangeMessage, enabled ? CameraEnabledMessage : CameraDisabledMessage);
log("setVideoEnabled: " + object);
dataChannel.send(new DataChannel.Buffer(ByteBuffer.wrap(object.toString().getBytes()), false));
} catch (JSONException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/*private void initLocalRenderer() {
if (this.localRenderer != null) {
log("really initng " + (this.sharedContext == null));
this.localRenderer.init(this.sharedContext, null);
this.localCameraTrack.addSink(localRenderer);
this.capturer.startCapture(500, 500, 30);
}
}*/
/*private void initVideoTrack() {
this.sharedContext = EglBase.create().getEglBaseContext();
this.capturer = createCapturer(true);
this.localCameraTrack = factory.createVideoTrack("video1", factory.createVideoSource(capturer));
}*/
private CameraVideoCapturer createCapturer() {
CameraEnumerator enumerator = new Camera1Enumerator();
for (String name : enumerator.getDeviceNames()) {
if (enumerator.isFrontFacing(name)) {
return enumerator.createCapturer(name, null);
}
}
return null;
}
public void releaseCamera() {
if (this.capturer != null) {
try {
this.capturer.stopCapture();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (this.remoteRenderer != null) {
this.remoteRenderer.release();
}
if (this.localRenderer != null) {
this.localRenderer.release();
}
}
private void handleMediaStream(MediaStream stream) {
log("handleMediaStream");
if (this.remoteRenderer == null || stream.videoTracks.size() == 0) {
return;
}
new Handler(Looper.getMainLooper()).post(() -> {
// remoteRenderer.setBackgroundColor(Color.TRANSPARENT);
remoteRenderer.init(this.sharedContext, null);
stream.videoTracks.get(0).addSink(remoteRenderer);
});
}
private MediaStream createStream() {
upStream = factory.createLocalMediaStream("stream1");
AudioTrack audio = factory.createAudioTrack("audio1", factory.createAudioSource(new MediaConstraints()));
upStream.addTrack(audio);
upStream.addTrack(getVideoTrack());
// this.capturer.startCapture(500, 500, 30);
return upStream;
}
private VideoTrack getVideoTrack() {
this.capturer = createCapturer();
return factory.createVideoTrack("video1", factory.createVideoSource(this.capturer.isScreencast()));
}
private void initRTC(Context c) {
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions.builder(c).createInitializationOptions());
factory = PeerConnectionFactory.builder().createPeerConnectionFactory();
constraints = new MediaConstraints();
constraints.optional.add(new MediaConstraints.KeyValuePair("offerToReceiveAudio", "true"));
constraints.optional.add(new MediaConstraints.KeyValuePair("offerToReceiveVideo", "false"));
constraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
// initVideoTrack();
}
private void handleAnswer(String remoteDesc) {
connection.setRemoteDescription(new DefaultSdpObserver() {
@Override
public void onSetSuccess() {
super.onSetSuccess();
log("onSetSuccess");
}
@Override
public void onSetFailure(String s) {
super.onSetFailure(s);
log("onSetFailure: " + s);
}
}, new SessionDescription(SessionDescription.Type.ANSWER, remoteDesc));
}
private void reportStateChange(CallState state) {
this.state = state;
if (this.listener != null) {
this.listener.OnStateChange(state);
}
}
public void accept(OnStateChangeListener listener) {
this.listener = listener;
new Thread(() -> {
connection = factory.createPeerConnection(this.iceServers, new DefaultObserver() {
@Override
public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
super.onIceGatheringChange(iceGatheringState);
if (iceGatheringState == PeerConnection.IceGatheringState.COMPLETE) {
log("onIceGatheringChange");
try {
PacketWriter pw = new PacketWriter(commSocket);
JSONObject obj = new JSONObject();
obj.put("action", "connected");
obj.put("answer", connection.getLocalDescription().description);
byte[] encrypted = Crypto.encryptMessage(obj.toString(), contact.getPublicKey(), ownPublicKey, ownSecretKey);
if (encrypted != null) {
pw.writeMessage(encrypted);
reportStateChange(CallState.CONNECTED);
} else {
reportStateChange(CallState.ERROR);
}
// new Thread(new SpeakerRunnable(commSocket)).start();
} catch (Exception e) {
e.printStackTrace();
reportStateChange(CallState.ERROR);
}
}
}
@Override
public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
log("accept.onIceConnectionChange " + iceConnectionState.name());
super.onIceConnectionChange(iceConnectionState);
if (iceConnectionState == PeerConnection.IceConnectionState.DISCONNECTED) {
reportStateChange(CallState.ENDED);
}
}
@Override
public void onAddStream(MediaStream mediaStream) {
log("onAddStream");
super.onAddStream(mediaStream);
handleMediaStream(mediaStream);
}
@Override
public void onDataChannel(DataChannel dataChannel) {
super.onDataChannel(dataChannel);
RTCCall.this.dataChannel = dataChannel;
dataChannel.registerObserver(RTCCall.this);
}
});
connection.addStream(createStream());
// this.dataChannel = connection.createDataChannel("data", new DataChannel.Init());
log("setting remote description");
connection.setRemoteDescription(new DefaultSdpObserver() {
@Override
public void onSetSuccess() {
super.onSetSuccess();
log("creating answer...");
connection.createAnswer(new DefaultSdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
log("onCreateSuccess");
super.onCreateSuccess(sessionDescription);
connection.setLocalDescription(new DefaultSdpObserver(), sessionDescription);
}
@Override
public void onCreateFailure(String s) {
super.onCreateFailure(s);
log("onCreateFailure: " + s);
}
}, constraints);
}
}, new SessionDescription(SessionDescription.Type.OFFER, offer));
}).start();
}
public void decline() {
new Thread(() -> {
try {
log("declining...");
if (this.commSocket != null) {
PacketWriter pw = new PacketWriter(commSocket);
byte[] encrypted = Crypto.encryptMessage("{\"action\":\"dismissed\"}", this.contact.getPublicKey(), this.ownPublicKey, this.ownSecretKey);
pw.writeMessage(encrypted);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
cleanup();
}
}).start();
}
public void cleanup() {
closeCommSocket();
if (this.upStream != null && state == CallState.CONNECTED) {
/*for(AudioTrack track : this.upStream.audioTracks){
track.setEnabled(false);
track.dispose();
}
for(VideoTrack track : this.upStream.videoTracks) track.dispose();*/
closePeerConnection();
// factory.dispose();
}
}
public void hangUp() {
new Thread(() -> {
try {
if (this.commSocket != null) {
PacketWriter pw = new PacketWriter(this.commSocket);
byte[] encrypted = Crypto.encryptMessage("{\"action\":\"dismissed\"}", this.contact.getPublicKey(), this.ownPublicKey, this.ownSecretKey);
pw.writeMessage(encrypted);
}
closeCommSocket();
closePeerConnection();
reportStateChange(CallState.ENDED);
} catch (IOException e) {
e.printStackTrace();
reportStateChange(CallState.ERROR);
}
}).start();
}
public interface OnStateChangeListener {
void OnStateChange(CallState state);
}
private void log(String s) {
Log.d(this, s);
}
}
19
Source : WebRTC.java
with GNU Affero General Public License v3.0
from KianIranian-STDG
with GNU Affero General Public License v3.0
from KianIranian-STDG
public clreplaced WebRTC {
private static String TAG = "iGapCall WebRTC ";
private static final String VIDEO_TRACK_ID = "ARDAMSv0";
private static final int VIDEO_RESOLUTION_WIDTH = 1920;
private static final int VIDEO_RESOLUTION_HEIGHT = 1080;
private static final int FPS = 30;
private PeerConnection peerConnection;
private PeerConnectionFactory peerConnectionFactory;
private MediaStream mediaStream;
private VideoFrameListener frameListener;
// this is filled for offer
private String localSDP;
private String remoteSDP;
private MediaConstraints mediaConstraints;
private MediaConstraints audioConstraints;
private VideoCapturer videoCapturer;
private ProtoSignalingOffer.SignalingOffer.Type callTYpe;
private EglBase.Context eglBaseContext = null;
private static volatile WebRTC instance = null;
public static WebRTC getInstance() {
WebRTC localInstance = instance;
if (localInstance == null) {
synchronized (WebRTC.clreplaced) {
localInstance = instance;
if (localInstance == null) {
instance = localInstance = new WebRTC();
}
}
}
return localInstance;
}
public static boolean isAlive() {
return instance != null;
}
public void toggleSound(boolean isEnable) {
if (mediaStream == null) {
return;
}
for (AudioTrack audioTrack : mediaStream.audioTracks) {
audioTrack.setEnabled(isEnable);
}
}
@Deprecated
public void muteSound() {
if (mediaStream == null) {
return;
}
for (AudioTrack audioTrack : mediaStream.audioTracks) {
audioTrack.setEnabled(false);
}
}
public void switchCamera() {
if (Camera.getNumberOfCameras() > 1) {
if (videoCapturer instanceof CameraVideoCapturer) {
((CameraVideoCapturer) videoCapturer).switchCamera(null);
}
}
}
@Deprecated
public void unMuteSound() {
if (mediaStream == null) {
return;
}
for (AudioTrack audioTrack : mediaStream.audioTracks) {
audioTrack.setEnabled(true);
}
}
private void addAudioTrack(MediaStream mediaStream) {
AudioSource audioSource = peerConnectionFactoryInstance().createAudioSource(audioConstraintsGetInstance());
AudioTrack audioTrack = peerConnectionFactoryInstance().createAudioTrack("ARDAMSa0", audioSource);
audioTrack.setEnabled(true);
mediaStream.addTrack(audioTrack);
}
@Deprecated
public void setCallType(ProtoSignalingOffer.SignalingOffer.Type callTYpe) {
this.callTYpe = callTYpe;
}
public EglBase.Context getEglBaseContext() {
if (eglBaseContext == null)
eglBaseContext = EglBase.create().getEglBaseContext();
return eglBaseContext;
}
private void addVideoTrack(MediaStream mediaStream) {
if (CallManager.getInstance().getCallType() == ProtoSignalingOffer.SignalingOffer.Type.VIDEO_CALLING) {
videoCapturer = createCameraCapturer(new Camera1Enumerator(false));
VideoSource videoSource = peerConnectionFactoryInstance().createVideoSource(videoCapturer.isScreencast());
SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", getEglBaseContext());
videoCapturer.initialize(surfaceTextureHelper, G.context, videoSource.getCapturerObserver());
videoCapturer.startCapture(VIDEO_RESOLUTION_WIDTH, VIDEO_RESOLUTION_HEIGHT, FPS);
VideoTrack videoTrackFromCamera = peerConnectionFactoryInstance().createVideoTrack(VIDEO_TRACK_ID, videoSource);
videoTrackFromCamera.setEnabled(true);
videoTrackFromCamera.addSink(videoFrame -> {
if (frameListener != null)
frameListener.onLocalFrame(videoFrame);
// if (G.onVideoCallFrame != null) {
// G.onVideoCallFrame.onPeerFrame(videoFrame);
// }
});
mediaStream.addTrack(videoTrackFromCamera);
}
}
private VideoCapturer createCameraCapturer(CameraEnumerator enumerator) {
final String[] deviceNames = enumerator.getDeviceNames();
// First, try to find front facing camera
for (String deviceName : deviceNames) {
if (enumerator.isFrontFacing(deviceName)) {
VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
if (videoCapturer != null) {
return videoCapturer;
}
}
}
// Front facing camera not found, try something else
for (String deviceName : deviceNames) {
if (!enumerator.isFrontFacing(deviceName)) {
VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
if (videoCapturer != null) {
return videoCapturer;
}
}
}
return null;
}
public void pauseVideoCapture() {
if (videoCapturer != null) {
try {
videoCapturer.stopCapture();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void startVideoCapture() {
if (videoCapturer != null) {
try {
videoCapturer.startCapture(VIDEO_RESOLUTION_WIDTH, VIDEO_RESOLUTION_HEIGHT, FPS);
} catch (RuntimeException e) {
e.printStackTrace();
}
}
}
/**
* First, we initiate the PeerConnectionFactory with our application context and some options.
*/
private PeerConnectionFactory peerConnectionFactoryInstance() {
if (peerConnectionFactory == null) {
initializePeerConnectionFactory();
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions.builder(G.context).createInitializationOptions());
PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
DefaultVideoEncoderFactory defaultVideoEncoderFactory = new DefaultVideoEncoderFactory(eglBaseContext, true, true);
DefaultVideoDecoderFactory defaultVideoDecoderFactory = new DefaultVideoDecoderFactory(eglBaseContext);
peerConnectionFactory = PeerConnectionFactory.builder().setOptions(options).setVideoEncoderFactory(defaultVideoEncoderFactory).setVideoDecoderFactory(defaultVideoDecoderFactory).createPeerConnectionFactory();
WebRtcAudioTrack.setErrorCallback(new WebRtcAudioTrack.ErrorCallback() {
@Override
public void onWebRtcAudioTrackInitError(String s) {
}
@Override
public void onWebRtcAudioTrackStartError(WebRtcAudioTrack.AudioTrackStartErrorCode audioTrackStartErrorCode, String s) {
}
@Override
public void onWebRtcAudioTrackError(String s) {
}
});
WebRtcAudioRecord.setErrorCallback(new WebRtcAudioRecord.WebRtcAudioRecordErrorCallback() {
@Override
public void onWebRtcAudioRecordInitError(String s) {
}
@Override
public void onWebRtcAudioRecordStartError(WebRtcAudioRecord.AudioRecordStartErrorCode audioRecordStartErrorCode, String s) {
}
@Override
public void onWebRtcAudioRecordError(String s) {
}
});
}
return peerConnectionFactory;
}
private void initializePeerConnectionFactory() {
try {
Set<String> HARDWARE_AEC_BLACKLIST = new HashSet<String>() {
{
add("Pixel");
add("Pixel XL");
add("Moto G5");
add("Moto G (5S) Plus");
add("Moto G4");
add("TA-1053");
add("Mi A1");
// Sony z5 compact
add("E5823");
}
};
Set<String> OPEN_SL_ES_WHITELIST = new HashSet<String>() {
{
add("Pixel");
add("Pixel XL");
}
};
// PeerConnectionFactory.startInternalTracingCapture(
// Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "webrtc-trace.txt");
if (WebRtcAudioUtils.isAcousticEchoCancelerSupported()) {
WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(true);
}
if (WebRtcAudioUtils.isAutomaticGainControlSupported()) {
WebRtcAudioUtils.setWebRtcBasedAutomaticGainControl(true);
}
if (WebRtcAudioUtils.isNoiseSuppressorSupported()) {
WebRtcAudioUtils.setWebRtcBasedNoiseSuppressor(true);
}
// if (HARDWARE_AEC_BLACKLIST.contains(Build.MODEL)) {
// WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(true);
// }
if (!OPEN_SL_ES_WHITELIST.contains(Build.MODEL)) {
WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true);
}
} catch (UnsatisfiedLinkError e) {
e.printStackTrace();
}
}
public PeerConnection peerConnectionInstance() {
if (peerConnection == null) {
List<PeerConnection.IceServer> iceServers = new ArrayList<>();
DbManager.getInstance().doRealmTask(realm -> {
RealmCallConfig realmCallConfig = realm.where(RealmCallConfig.clreplaced).findFirst();
for (RealmIceServer ice : realmCallConfig.getIceServer()) {
iceServers.add(new PeerConnection.IceServer(ice.getUrl(), ice.getUsername(), ice.getCredential()));
}
});
PeerConnection.RTCConfiguration configuration = new PeerConnection.RTCConfiguration(iceServers);
configuration.bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE;
configuration.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.REQUIRE;
configuration.iceTransportsType = PeerConnection.IceTransportsType.RELAY;
PeerConnection.Observer observer = new PeerConnectionObserver();
MediaConstraints mediaConstraints = mediaConstraintsGetInstance();
peerConnection = peerConnectionFactoryInstance().createPeerConnection(iceServers, mediaConstraints, observer);
mediaStream = peerConnectionFactoryInstance().createLocalMediaStream("ARDAMS");
addAudioTrack(mediaStream);
addVideoTrack(mediaStream);
peerConnection.addStream(mediaStream);
}
return peerConnection;
}
public void createOffer(final long userIdCallee) {
peerConnectionInstance().createOffer(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
if (localSDP != null) {
return;
}
localSDP = sessionDescription.description;
CallManager.getInstance().makeOffer(userIdCallee, localSDP);
// new RequestSignalingOffer().signalingOffer(userIdCallee, callTYpe, localSDP);
}
@Override
public void onSetSuccess() {
}
@Override
public void onCreateFailure(String s) {
}
@Override
public void onSetFailure(String s) {
}
}, mediaConstraintsGetInstance());
}
public void setOfferLocalDescription() {
setLocalDescription(SessionDescription.Type.OFFER, localSDP);
}
public void createAnswer() {
peerConnectionInstance().createAnswer(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
localSDP = sessionDescription.description;
setLocalDescription(SessionDescription.Type.ANSWER, localSDP);
CallManager.getInstance().makeAccept(sessionDescription.description);
}
@Override
public void onSetSuccess() {
}
@Override
public void onCreateFailure(String s) {
}
@Override
public void onSetFailure(String s) {
}
}, mediaConstraintsGetInstance());
}
private void setLocalDescription(final SessionDescription.Type type, String sdp) {
peerConnectionInstance().setLocalDescription(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
}
@Override
public void onSetSuccess() {
}
@Override
public void onCreateFailure(String s) {
}
@Override
public void onSetFailure(String s) {
}
}, new SessionDescription(type, sdp));
}
private MediaConstraints mediaConstraintsGetInstance() {
if (mediaConstraints == null) {
mediaConstraints = new MediaConstraints();
mediaConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
}
return mediaConstraints;
}
private MediaConstraints audioConstraintsGetInstance() {
if (audioConstraints == null) {
audioConstraints = new MediaConstraints();
audioConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
}
return audioConstraints;
}
@Deprecated
private void acceptCall(String sdp) {
new RequestSignalingAccept().signalingAccept(sdp);
}
@Deprecated
public void leaveCall() {
}
public void close() {
try {
if (videoCapturer != null) {
videoCapturer.stopCapture();
videoCapturer.dispose();
videoCapturer = null;
}
if (peerConnection != null) {
peerConnection.close();
peerConnection.dispose();
peerConnection = null;
}
if (peerConnectionFactory != null) {
peerConnectionFactory.dispose();
peerConnectionFactory = null;
}
instance = null;
} catch (RuntimeException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// My new code just changing the place
public void setRemoteDesc(SessionDescription sdp) {
peerConnectionInstance().setRemoteDescription(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
}
@Override
public void onSetSuccess() {
CallManager.getInstance().onSdpSuccess();
}
@Override
public void onCreateFailure(String s) {
}
@Override
public void onSetFailure(String s) {
}
}, sdp);
}
public void holdVideoCall(boolean callHold) {
if (callHold) {
pauseVideoCapture();
} else {
startVideoCapture();
}
}
public interface VideoFrameListener {
void onLocalFrame(VideoFrame frame);
void onRemoteFrame(VideoFrame frame);
}
public void setFrameListener(VideoFrameListener frameListener) {
this.frameListener = frameListener;
}
public VideoFrameListener getFrameListener() {
return frameListener;
}
}
19
Source : CallActivity.java
with GNU General Public License v3.0
from Jhuster
with GNU General Public License v3.0
from Jhuster
public clreplaced CallActivity extends AppCompatActivity {
private static final int VIDEO_RESOLUTION_WIDTH = 1280;
private static final int VIDEO_RESOLUTION_HEIGHT = 720;
private static final int VIDEO_FPS = 30;
private TextView mLogcatView;
private Button mStartCallBtn;
private Button mEndCallBtn;
private static final String TAG = "CallActivity";
public static final String VIDEO_TRACK_ID = "ARDAMSv0";
public static final String AUDIO_TRACK_ID = "ARDAMSa0";
private EglBase mRootEglBase;
private PeerConnection mPeerConnection;
private PeerConnectionFactory mPeerConnectionFactory;
private SurfaceTextureHelper mSurfaceTextureHelper;
private SurfaceViewRenderer mLocalSurfaceView;
private SurfaceViewRenderer mRemoteSurfaceView;
private VideoTrack mVideoTrack;
private AudioTrack mAudioTrack;
private VideoCapturer mVideoCapturer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_call);
mLogcatView = findViewById(R.id.LogcatView);
mStartCallBtn = findViewById(R.id.StartCallButton);
mEndCallBtn = findViewById(R.id.EndCallButton);
RTCSignalClient.getInstance().setSignalEventListener(mOnSignalEventListener);
String serverAddr = getIntent().getStringExtra("ServerAddr");
String roomName = getIntent().getStringExtra("RoomName");
RTCSignalClient.getInstance().joinRoom(serverAddr, UUID.randomUUID().toString(), roomName);
mRootEglBase = EglBase.create();
mLocalSurfaceView = findViewById(R.id.LocalSurfaceView);
mRemoteSurfaceView = findViewById(R.id.RemoteSurfaceView);
mLocalSurfaceView.init(mRootEglBase.getEglBaseContext(), null);
mLocalSurfaceView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
mLocalSurfaceView.setMirror(true);
mLocalSurfaceView.setEnableHardwareScaler(false);
mRemoteSurfaceView.init(mRootEglBase.getEglBaseContext(), null);
mRemoteSurfaceView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
mRemoteSurfaceView.setMirror(true);
mRemoteSurfaceView.setEnableHardwareScaler(true);
mRemoteSurfaceView.setZOrderMediaOverlay(true);
ProxyVideoSink videoSink = new ProxyVideoSink();
videoSink.setTarget(mLocalSurfaceView);
mPeerConnectionFactory = createPeerConnectionFactory(this);
// NOTE: this _must_ happen while PeerConnectionFactory is alive!
Logging.enableLogToDebugOutput(Logging.Severity.LS_VERBOSE);
mVideoCapturer = createVideoCapturer();
mSurfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", mRootEglBase.getEglBaseContext());
VideoSource videoSource = mPeerConnectionFactory.createVideoSource(false);
mVideoCapturer.initialize(mSurfaceTextureHelper, getApplicationContext(), videoSource.getCapturerObserver());
mVideoTrack = mPeerConnectionFactory.createVideoTrack(VIDEO_TRACK_ID, videoSource);
mVideoTrack.setEnabled(true);
mVideoTrack.addSink(videoSink);
AudioSource audioSource = mPeerConnectionFactory.createAudioSource(new MediaConstraints());
mAudioTrack = mPeerConnectionFactory.createAudioTrack(AUDIO_TRACK_ID, audioSource);
mAudioTrack.setEnabled(true);
}
@Override
protected void onResume() {
super.onResume();
mVideoCapturer.startCapture(VIDEO_RESOLUTION_WIDTH, VIDEO_RESOLUTION_HEIGHT, VIDEO_FPS);
}
@Override
protected void onPause() {
super.onPause();
try {
mVideoCapturer.stopCapture();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
doEndCall();
mLocalSurfaceView.release();
mRemoteSurfaceView.release();
mVideoCapturer.dispose();
mSurfaceTextureHelper.dispose();
PeerConnectionFactory.stopInternalTracingCapture();
PeerConnectionFactory.shutdownInternalTracer();
RTCSignalClient.getInstance().leaveRoom();
}
public static clreplaced ProxyVideoSink implements VideoSink {
private VideoSink mTarget;
@Override
synchronized public void onFrame(VideoFrame frame) {
if (mTarget == null) {
Log.d(TAG, "Dropping frame in proxy because target is null.");
return;
}
mTarget.onFrame(frame);
}
synchronized void setTarget(VideoSink target) {
this.mTarget = target;
}
}
public static clreplaced SimpleSdpObserver implements SdpObserver {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
Log.i(TAG, "SdpObserver: onCreateSuccess !");
}
@Override
public void onSetSuccess() {
Log.i(TAG, "SdpObserver: onSetSuccess");
}
@Override
public void onCreateFailure(String msg) {
Log.e(TAG, "SdpObserver onCreateFailure: " + msg);
}
@Override
public void onSetFailure(String msg) {
Log.e(TAG, "SdpObserver onSetFailure: " + msg);
}
}
public void onClickStartCallButton(View v) {
doStartCall();
}
public void onClickEndCallButton(View v) {
doEndCall();
}
private void updateCallState(boolean idle) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (idle) {
mStartCallBtn.setVisibility(View.VISIBLE);
mEndCallBtn.setVisibility(View.GONE);
mRemoteSurfaceView.setVisibility(View.GONE);
} else {
mStartCallBtn.setVisibility(View.GONE);
mEndCallBtn.setVisibility(View.VISIBLE);
mRemoteSurfaceView.setVisibility(View.VISIBLE);
}
}
});
}
public void doStartCall() {
logcatOnUI("Start Call, Wait ...");
if (mPeerConnection == null) {
mPeerConnection = createPeerConnection();
}
MediaConstraints mediaConstraints = new MediaConstraints();
mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
mediaConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
mPeerConnection.createOffer(new SimpleSdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
Log.i(TAG, "Create local offer success: \n" + sessionDescription.description);
mPeerConnection.setLocalDescription(new SimpleSdpObserver(), sessionDescription);
JSONObject message = new JSONObject();
try {
message.put("userId", RTCSignalClient.getInstance().getUserId());
message.put("msgType", RTCSignalClient.MESSAGE_TYPE_OFFER);
message.put("sdp", sessionDescription.description);
RTCSignalClient.getInstance().sendMessage(message);
} catch (JSONException e) {
e.printStackTrace();
}
}
}, mediaConstraints);
}
public void doEndCall() {
logcatOnUI("End Call, Wait ...");
hanup();
JSONObject message = new JSONObject();
try {
message.put("userId", RTCSignalClient.getInstance().getUserId());
message.put("msgType", RTCSignalClient.MESSAGE_TYPE_HANGUP);
RTCSignalClient.getInstance().sendMessage(message);
} catch (JSONException e) {
e.printStackTrace();
}
}
public void doAnswerCall() {
logcatOnUI("Answer Call, Wait ...");
if (mPeerConnection == null) {
mPeerConnection = createPeerConnection();
}
MediaConstraints sdpMediaConstraints = new MediaConstraints();
Log.i(TAG, "Create answer ...");
mPeerConnection.createAnswer(new SimpleSdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
Log.i(TAG, "Create answer success !");
mPeerConnection.setLocalDescription(new SimpleSdpObserver(), sessionDescription);
JSONObject message = new JSONObject();
try {
message.put("userId", RTCSignalClient.getInstance().getUserId());
message.put("msgType", RTCSignalClient.MESSAGE_TYPE_ANSWER);
message.put("sdp", sessionDescription.description);
RTCSignalClient.getInstance().sendMessage(message);
} catch (JSONException e) {
e.printStackTrace();
}
}
}, sdpMediaConstraints);
updateCallState(false);
}
private void hanup() {
logcatOnUI("Hanup Call, Wait ...");
if (mPeerConnection == null) {
return;
}
mPeerConnection.close();
mPeerConnection = null;
logcatOnUI("Hanup Done.");
updateCallState(true);
}
public PeerConnection createPeerConnection() {
Log.i(TAG, "Create PeerConnection ...");
PeerConnection.RTCConfiguration configuration = new PeerConnection.RTCConfiguration(new ArrayList<>());
PeerConnection connection = mPeerConnectionFactory.createPeerConnection(configuration, mPeerConnectionObserver);
if (connection == null) {
Log.e(TAG, "Failed to createPeerConnection !");
return null;
}
connection.addTrack(mVideoTrack);
connection.addTrack(mAudioTrack);
return connection;
}
public PeerConnectionFactory createPeerConnectionFactory(Context context) {
final VideoEncoderFactory encoderFactory;
final VideoDecoderFactory decoderFactory;
encoderFactory = new DefaultVideoEncoderFactory(mRootEglBase.getEglBaseContext(), false, /* enableIntelVp8Encoder */
true);
decoderFactory = new DefaultVideoDecoderFactory(mRootEglBase.getEglBaseContext());
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions.builder(context).setEnableInternalTracer(true).createInitializationOptions());
PeerConnectionFactory.Builder builder = PeerConnectionFactory.builder().setVideoEncoderFactory(encoderFactory).setVideoDecoderFactory(decoderFactory);
builder.setOptions(null);
return builder.createPeerConnectionFactory();
}
/*
* Read more about Camera2 here
* https://developer.android.com/reference/android/hardware/camera2/package-summary.html
**/
private VideoCapturer createVideoCapturer() {
if (Camera2Enumerator.isSupported(this)) {
return createCameraCapturer(new Camera2Enumerator(this));
} else {
return createCameraCapturer(new Camera1Enumerator(true));
}
}
private VideoCapturer createCameraCapturer(CameraEnumerator enumerator) {
final String[] deviceNames = enumerator.getDeviceNames();
// First, try to find front facing camera
Log.d(TAG, "Looking for front facing cameras.");
for (String deviceName : deviceNames) {
if (enumerator.isFrontFacing(deviceName)) {
Logging.d(TAG, "Creating front facing camera capturer.");
VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
if (videoCapturer != null) {
return videoCapturer;
}
}
}
// Front facing camera not found, try something else
Log.d(TAG, "Looking for other cameras.");
for (String deviceName : deviceNames) {
if (!enumerator.isFrontFacing(deviceName)) {
Logging.d(TAG, "Creating other camera capturer.");
VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
if (videoCapturer != null) {
return videoCapturer;
}
}
}
return null;
}
private PeerConnection.Observer mPeerConnectionObserver = new PeerConnection.Observer() {
@Override
public void onSignalingChange(PeerConnection.SignalingState signalingState) {
Log.i(TAG, "onSignalingChange: " + signalingState);
}
@Override
public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
Log.i(TAG, "onIceConnectionChange: " + iceConnectionState);
}
@Override
public void onIceConnectionReceivingChange(boolean b) {
Log.i(TAG, "onIceConnectionChange: " + b);
}
@Override
public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
Log.i(TAG, "onIceGatheringChange: " + iceGatheringState);
}
@Override
public void onIceCandidate(IceCandidate iceCandidate) {
Log.i(TAG, "onIceCandidate: " + iceCandidate);
try {
JSONObject message = new JSONObject();
message.put("userId", RTCSignalClient.getInstance().getUserId());
message.put("msgType", RTCSignalClient.MESSAGE_TYPE_CANDIDATE);
message.put("label", iceCandidate.sdpMLineIndex);
message.put("id", iceCandidate.sdpMid);
message.put("candidate", iceCandidate.sdp);
RTCSignalClient.getInstance().sendMessage(message);
} catch (JSONException e) {
e.printStackTrace();
}
}
@Override
public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {
for (int i = 0; i < iceCandidates.length; i++) {
Log.i(TAG, "onIceCandidatesRemoved: " + iceCandidates[i]);
}
mPeerConnection.removeIceCandidates(iceCandidates);
}
@Override
public void onAddStream(MediaStream mediaStream) {
Log.i(TAG, "onAddStream: " + mediaStream.videoTracks.size());
}
@Override
public void onRemoveStream(MediaStream mediaStream) {
Log.i(TAG, "onRemoveStream");
}
@Override
public void onDataChannel(DataChannel dataChannel) {
Log.i(TAG, "onDataChannel");
}
@Override
public void onRenegotiationNeeded() {
Log.i(TAG, "onRenegotiationNeeded");
}
@Override
public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {
MediaStreamTrack track = rtpReceiver.track();
if (track instanceof VideoTrack) {
Log.i(TAG, "onAddVideoTrack");
VideoTrack remoteVideoTrack = (VideoTrack) track;
remoteVideoTrack.setEnabled(true);
ProxyVideoSink videoSink = new ProxyVideoSink();
videoSink.setTarget(mRemoteSurfaceView);
remoteVideoTrack.addSink(videoSink);
}
}
};
private RTCSignalClient.OnSignalEventListener mOnSignalEventListener = new RTCSignalClient.OnSignalEventListener() {
@Override
public void onConnected() {
logcatOnUI("Signal Server Connected !");
}
@Override
public void onConnecting() {
logcatOnUI("Signal Server Connecting !");
}
@Override
public void onDisconnected() {
logcatOnUI("Signal Server Connecting !");
}
@Override
public void onRemoteUserJoined(String userId) {
logcatOnUI("Remote User Joined: " + userId);
}
@Override
public void onRemoteUserLeft(String userId) {
logcatOnUI("Remote User Leaved: " + userId);
}
@Override
public void onBroadcastReceived(JSONObject message) {
Log.i(TAG, "onBroadcastReceived: " + message);
try {
String userId = message.getString("userId");
int type = message.getInt("msgType");
switch(type) {
case RTCSignalClient.MESSAGE_TYPE_OFFER:
onRemoteOfferReceived(userId, message);
break;
case RTCSignalClient.MESSAGE_TYPE_ANSWER:
onRemoteAnswerReceived(userId, message);
break;
case RTCSignalClient.MESSAGE_TYPE_CANDIDATE:
onRemoteCandidateReceived(userId, message);
break;
case RTCSignalClient.MESSAGE_TYPE_HANGUP:
onRemoteHangup(userId);
break;
}
} catch (JSONException e) {
e.printStackTrace();
}
}
private void onRemoteOfferReceived(String userId, JSONObject message) {
logcatOnUI("Receive Remote Call ...");
if (mPeerConnection == null) {
mPeerConnection = createPeerConnection();
}
try {
String description = message.getString("sdp");
mPeerConnection.setRemoteDescription(new SimpleSdpObserver(), new SessionDescription(SessionDescription.Type.OFFER, description));
doAnswerCall();
} catch (JSONException e) {
e.printStackTrace();
}
}
private void onRemoteAnswerReceived(String userId, JSONObject message) {
logcatOnUI("Receive Remote Answer ...");
try {
String description = message.getString("sdp");
mPeerConnection.setRemoteDescription(new SimpleSdpObserver(), new SessionDescription(SessionDescription.Type.ANSWER, description));
} catch (JSONException e) {
e.printStackTrace();
}
updateCallState(false);
}
private void onRemoteCandidateReceived(String userId, JSONObject message) {
logcatOnUI("Receive Remote Candidate ...");
try {
IceCandidate remoteIceCandidate = new IceCandidate(message.getString("id"), message.getInt("label"), message.getString("candidate"));
mPeerConnection.addIceCandidate(remoteIceCandidate);
} catch (JSONException e) {
e.printStackTrace();
}
}
private void onRemoteHangup(String userId) {
logcatOnUI("Receive Remote Hanup Event ...");
hanup();
}
};
private void logcatOnUI(String msg) {
Log.i(TAG, msg);
runOnUiThread(new Runnable() {
@Override
public void run() {
String output = mLogcatView.getText() + "\n" + msg;
mLogcatView.setText(output);
}
});
}
}
19
Source : Peer.java
with MIT License
from ddssingsong
with MIT License
from ddssingsong
/**
* Created by dds on 2020/3/11.
* [email protected]
*/
public clreplaced Peer implements SdpObserver, PeerConnection.Observer {
private final static String TAG = "dds_Peer";
private final PeerConnection pc;
private final String mUserId;
private List<IceCandidate> queuedRemoteCandidates;
private SessionDescription localSdp;
private final PeerConnectionFactory mFactory;
private final List<PeerConnection.IceServer> mIceLis;
private final IPeerEvent mEvent;
private boolean isOffer;
public MediaStream _remoteStream;
public SurfaceViewRenderer renderer;
public ProxyVideoSink sink;
public Peer(PeerConnectionFactory factory, List<PeerConnection.IceServer> list, String userId, IPeerEvent event) {
mFactory = factory;
mIceLis = list;
mEvent = event;
mUserId = userId;
queuedRemoteCandidates = new ArrayList<>();
this.pc = createPeerConnection();
Log.d("dds_test", "create Peer:" + mUserId);
}
public PeerConnection createPeerConnection() {
PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(mIceLis);
if (mFactory != null) {
return mFactory.createPeerConnection(rtcConfig, this);
} else {
return null;
}
}
public void setOffer(boolean isOffer) {
this.isOffer = isOffer;
}
// 创建offer
public void createOffer() {
if (pc == null)
return;
Log.d("dds_test", "createOffer");
pc.createOffer(this, offerOrAnswerConstraint());
}
// 创建answer
public void createAnswer() {
if (pc == null)
return;
Log.d("dds_test", "createAnswer");
pc.createAnswer(this, offerOrAnswerConstraint());
}
// 设置LocalDescription
public void setLocalDescription(SessionDescription sdp) {
Log.d("dds_test", "setLocalDescription");
if (pc == null)
return;
pc.setLocalDescription(this, sdp);
}
// 设置RemoteDescription
public void setRemoteDescription(SessionDescription sdp) {
if (pc == null)
return;
Log.d("dds_test", "setRemoteDescription");
pc.setRemoteDescription(this, sdp);
}
// 添加本地流
public void addLocalStream(MediaStream stream) {
if (pc == null)
return;
Log.d("dds_test", "addLocalStream" + mUserId);
pc.addStream(stream);
}
// 添加RemoteIceCandidate
public synchronized void addRemoteIceCandidate(final IceCandidate candidate) {
Log.d("dds_test", "addRemoteIceCandidate");
if (pc != null) {
if (queuedRemoteCandidates != null) {
Log.d("dds_test", "addRemoteIceCandidate 2222");
synchronized (Peer.clreplaced) {
if (queuedRemoteCandidates != null) {
queuedRemoteCandidates.add(candidate);
}
}
} else {
Log.d("dds_test", "addRemoteIceCandidate1111");
pc.addIceCandidate(candidate);
}
}
}
// 移除RemoteIceCandidates
public void removeRemoteIceCandidates(final IceCandidate[] candidates) {
if (pc == null) {
return;
}
drainCandidates();
pc.removeIceCandidates(candidates);
}
public void createRender(EglBase mRootEglBase, Context context, boolean isOverlay) {
renderer = new SurfaceViewRenderer(context);
renderer.init(mRootEglBase.getEglBaseContext(), new RendererCommon.RendererEvents() {
@Override
public void onFirstFrameRendered() {
Log.d(TAG, "createRender onFirstFrameRendered");
}
@Override
public void onFrameResolutionChanged(int videoWidth, int videoHeight, int rotation) {
Log.d(TAG, "createRender onFrameResolutionChanged");
}
});
renderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
renderer.setMirror(true);
renderer.setZOrderMediaOverlay(isOverlay);
sink = new ProxyVideoSink();
sink.setTarget(renderer);
if (_remoteStream != null && _remoteStream.videoTracks.size() > 0) {
_remoteStream.videoTracks.get(0).addSink(sink);
}
}
// 关闭Peer
public void close() {
if (renderer != null) {
renderer.release();
renderer = null;
}
if (sink != null) {
sink.setTarget(null);
}
if (pc != null) {
try {
pc.close();
pc.dispose();
} catch (Exception e) {
}
}
}
// ------------------------------Observer-------------------------------------
@Override
public void onSignalingChange(PeerConnection.SignalingState signalingState) {
Log.i(TAG, "onSignalingChange: " + signalingState);
}
@Override
public void onIceConnectionChange(PeerConnection.IceConnectionState newState) {
Log.i(TAG, "onIceConnectionChange: " + newState);
if (newState == PeerConnection.IceConnectionState.DISCONNECTED || newState == PeerConnection.IceConnectionState.FAILED) {
mEvent.onDisconnected(mUserId);
}
}
@Override
public void onIceConnectionReceivingChange(boolean receiving) {
Log.i(TAG, "onIceConnectionReceivingChange:" + receiving);
}
@Override
public void onIceGatheringChange(PeerConnection.IceGatheringState newState) {
Log.i(TAG, "onIceGatheringChange:" + newState.toString());
}
@Override
public void onIceCandidate(IceCandidate candidate) {
// 发送IceCandidate
mEvent.onSendIceCandidate(mUserId, candidate);
}
@Override
public void onIceCandidatesRemoved(IceCandidate[] candidates) {
Log.i(TAG, "onIceCandidatesRemoved:");
}
@Override
public void onAddStream(MediaStream stream) {
Log.i(TAG, "onAddStream:");
stream.audioTracks.get(0).setEnabled(true);
_remoteStream = stream;
if (mEvent != null) {
mEvent.onRemoteStream(mUserId, stream);
}
}
@Override
public void onRemoveStream(MediaStream stream) {
Log.i(TAG, "onRemoveStream:");
if (mEvent != null) {
mEvent.onRemoveStream(mUserId, stream);
}
}
@Override
public void onDataChannel(DataChannel dataChannel) {
Log.i(TAG, "onDataChannel:");
}
@Override
public void onRenegotiationNeeded() {
Log.i(TAG, "onRenegotiationNeeded:");
}
@Override
public void onAddTrack(RtpReceiver receiver, MediaStream[] mediaStreams) {
Log.i(TAG, "onAddTrack:" + mediaStreams.length);
}
// -------------SdpObserver--------------------
@Override
public void onCreateSuccess(SessionDescription origSdp) {
Log.d(TAG, "sdp创建成功 " + origSdp.type);
String sdpString = origSdp.description;
final SessionDescription sdp = new SessionDescription(origSdp.type, sdpString);
localSdp = sdp;
setLocalDescription(sdp);
}
@Override
public void onSetSuccess() {
Log.d(TAG, "sdp连接成功 " + pc.signalingState().toString());
if (pc == null)
return;
// 发送者
if (isOffer) {
if (pc.getRemoteDescription() == null) {
Log.d(TAG, "Local SDP set succesfully");
if (!isOffer) {
// 接收者,发送Answer
mEvent.onSendAnswer(mUserId, localSdp);
} else {
// 发送者,发送自己的offer
mEvent.onSendOffer(mUserId, localSdp);
}
} else {
Log.d(TAG, "Remote SDP set succesfully");
drainCandidates();
}
} else {
if (pc.getLocalDescription() != null) {
Log.d(TAG, "Local SDP set succesfully");
if (!isOffer) {
// 接收者,发送Answer
mEvent.onSendAnswer(mUserId, localSdp);
} else {
// 发送者,发送自己的offer
mEvent.onSendOffer(mUserId, localSdp);
}
drainCandidates();
} else {
Log.d(TAG, "Remote SDP set succesfully");
}
}
}
@Override
public void onCreateFailure(String error) {
Log.i(TAG, " SdpObserver onCreateFailure:" + error);
}
@Override
public void onSetFailure(String error) {
Log.i(TAG, "SdpObserver onSetFailure:" + error);
}
private void drainCandidates() {
Log.i("dds_test", "drainCandidates");
synchronized (Peer.clreplaced) {
if (queuedRemoteCandidates != null) {
Log.d(TAG, "Add " + queuedRemoteCandidates.size() + " remote candidates");
for (IceCandidate candidate : queuedRemoteCandidates) {
pc.addIceCandidate(candidate);
}
queuedRemoteCandidates = null;
}
}
}
private MediaConstraints offerOrAnswerConstraint() {
MediaConstraints mediaConstraints = new MediaConstraints();
ArrayList<MediaConstraints.KeyValuePair> keyValuePairs = new ArrayList<>();
keyValuePairs.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
keyValuePairs.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
mediaConstraints.mandatory.addAll(keyValuePairs);
return mediaConstraints;
}
// ----------------------------回调-----------------------------------
public interface IPeerEvent {
void onSendIceCandidate(String userId, IceCandidate candidate);
void onSendOffer(String userId, SessionDescription description);
void onSendAnswer(String userId, SessionDescription description);
void onRemoteStream(String userId, MediaStream stream);
void onRemoveStream(String userId, MediaStream stream);
void onDisconnected(String userId);
}
}
19
Source : RTCCall.java
with GNU General Public License v3.0
from dakhnod
with GNU General Public License v3.0
from dakhnod
public clreplaced RTCCall implements DataChannel.Observer {
enum CallState {
CONNECTING,
RINGING,
CONNECTED,
DISMISSED,
ENDED,
ERROR
}
private final String StateChangeMessage = "StateChange";
private final String CameraDisabledMessage = "CameraDisabled";
private final String CameraEnabledMessage = "CameraEnabled";
private OnStateChangeListener listener;
Socket commSocket;
private PeerConnectionFactory factory;
private PeerConnection connection;
private MediaConstraints constraints;
private String offer;
private SurfaceViewRenderer remoteRenderer;
private SurfaceViewRenderer localRenderer;
private EglBase.Context sharedContext;
private CameraVideoCapturer capturer;
private MediaStream upStream;
private DataChannel dataChannel;
private boolean videoEnabled;
private Context context;
public CallState state;
public void setRemoteRenderer(SurfaceViewRenderer remoteRenderer) {
this.remoteRenderer = remoteRenderer;
}
private RTCCall(Contact target, String username, String identifier, OnStateChangeListener listener, Context context) {
log("starting call to " + target.getAddress());
initRTC(context);
this.context = context;
context.setTheme(AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES ? R.style.AppTheme_Dark : R.style.AppTheme_Light);
log("init RTC done");
this.listener = listener;
new Thread(() -> {
log("creating PeerConnection");
connection = factory.createPeerConnection(Collections.emptyList(), new DefaultObserver() {
@Override
public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
super.onIceGatheringChange(iceGatheringState);
if (iceGatheringState == PeerConnection.IceGatheringState.COMPLETE) {
log("transferring offer...");
try {
commSocket = new Socket(target.getAddress().replace("%zone", "%wlan0"), MainService.serverPort);
OutputStream os = commSocket.getOutputStream();
reportStateChange(CallState.CONNECTING);
JSONObject object = new JSONObject();
object.put("action", "call");
object.put("username", username);
object.put("identifier", identifier);
object.put("offer", connection.getLocalDescription().description);
os.write((object.toString() + "\n").getBytes());
BufferedReader reader = new BufferedReader(new InputStreamReader(commSocket.getInputStream()));
String response = reader.readLine();
JSONObject responseObject = new JSONObject(response);
if (!responseObject.getString("action").equals("ringing")) {
commSocket.close();
reportStateChange(CallState.ERROR);
return;
}
log("ringing...");
reportStateChange(CallState.RINGING);
response = reader.readLine();
responseObject = new JSONObject(response);
if (responseObject.getString("action").equals("connected")) {
log("connected");
reportStateChange(CallState.CONNECTED);
log("answer: " + responseObject.getString("answer"));
handleAnswer(responseObject.getString("answer"));
} else if (responseObject.getString("action").equals("dismissed")) {
reportStateChange(CallState.DISMISSED);
commSocket.close();
} else {
reportStateChange(CallState.ERROR);
commSocket.close();
}
} catch (Exception e) {
e.printStackTrace();
reportStateChange(CallState.ERROR);
}
}
}
@Override
public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
log("change " + iceConnectionState.name());
super.onIceConnectionChange(iceConnectionState);
if (iceConnectionState == PeerConnection.IceConnectionState.DISCONNECTED) {
reportStateChange(CallState.ENDED);
}
}
@Override
public void onAddStream(MediaStream mediaStream) {
super.onAddStream(mediaStream);
handleMediaStream(mediaStream);
}
@Override
public void onDataChannel(DataChannel dataChannel) {
super.onDataChannel(dataChannel);
RTCCall.this.dataChannel = dataChannel;
dataChannel.registerObserver(RTCCall.this);
}
});
log("PeerConnection created");
connection.addStream(createStream());
this.dataChannel = connection.createDataChannel("data", new DataChannel.Init());
this.dataChannel.registerObserver(this);
connection.createOffer(new DefaultSdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
super.onCreateSuccess(sessionDescription);
connection.setLocalDescription(new DefaultSdpObserver(), sessionDescription);
}
}, constraints);
}).start();
}
public void switchFrontFacing() {
if (this.capturer == null)
return;
this.capturer.switchCamera(null);
}
@Override
public void onBufferedAmountChange(long l) {
}
@Override
public void onStateChange() {
}
@Override
public void onMessage(DataChannel.Buffer buffer) {
byte[] data = new byte[buffer.data.remaining()];
buffer.data.get(data);
String s = new String(data);
JSONObject object = null;
try {
object = new JSONObject(s);
if (object.has(StateChangeMessage)) {
String state = object.getString(StateChangeMessage);
switch(state) {
case CameraEnabledMessage:
case CameraDisabledMessage:
{
setRemoteVideoEnabled(state.equals(CameraEnabledMessage));
break;
}
}
}
} catch (JSONException e) {
e.printStackTrace();
}
}
private void setRemoteVideoEnabled(boolean enabled) {
log("setting remote video enabled: " + enabled);
new Handler(Looper.getMainLooper()).post(() -> {
if (enabled) {
this.remoteRenderer.setBackgroundColor(Color.TRANSPARENT);
} else {
TypedValue typedValue = new TypedValue();
Resources.Theme theme = this.context.getTheme();
theme.resolveAttribute(R.attr.backgroundCardColor, typedValue, true);
@ColorInt
int color = typedValue.data;
this.remoteRenderer.setBackgroundColor(color);
}
});
}
public boolean isVideoEnabled() {
return this.videoEnabled;
}
public void setVideoEnabled(boolean enabled) {
this.videoEnabled = enabled;
try {
if (enabled) {
this.capturer.startCapture(500, 500, 30);
} else {
this.capturer.stopCapture();
}
JSONObject object = new JSONObject();
object.put(StateChangeMessage, enabled ? CameraEnabledMessage : CameraDisabledMessage);
dataChannel.send(new DataChannel.Buffer(ByteBuffer.wrap(object.toString().getBytes()), false));
} catch (JSONException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/*private void initLocalRenderer() {
if (this.localRenderer == null) return;
log("really initng " + (this.sharedContext == null));
this.localRenderer.init(this.sharedContext, null);
this.localCameraTrack.addSink(localRenderer);
this.capturer.startCapture(500, 500, 30);
}*/
/*private void initVideoTrack() {
this.sharedContext = EglBase.create().getEglBaseContext();
this.capturer = createCapturer(true);
this.localCameraTrack = factory.createVideoTrack("video1", factory.createVideoSource(capturer));
}*/
private CameraVideoCapturer createCapturer() {
CameraEnumerator enumerator = new Camera1Enumerator();
for (String name : enumerator.getDeviceNames()) {
if (enumerator.isFrontFacing(name)) {
return enumerator.createCapturer(name, null);
}
}
return null;
}
public void releaseCamera() {
if (this.capturer != null) {
try {
this.capturer.stopCapture();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (this.remoteRenderer != null)
this.remoteRenderer.release();
if (this.localRenderer != null)
this.localRenderer.release();
}
private void handleMediaStream(MediaStream stream) {
log("handling video stream");
if (this.remoteRenderer == null || stream.videoTracks.size() == 0)
return;
new Handler(Looper.getMainLooper()).post(() -> {
// remoteRenderer.setBackgroundColor(Color.TRANSPARENT);
remoteRenderer.init(this.sharedContext, null);
stream.videoTracks.get(0).addSink(remoteRenderer);
});
}
private MediaStream createStream() {
upStream = factory.createLocalMediaStream("stream1");
AudioTrack audio = factory.createAudioTrack("audio1", factory.createAudioSource(new MediaConstraints()));
upStream.addTrack(audio);
upStream.addTrack(getVideoTrack());
// this.capturer.startCapture(500, 500, 30);
return upStream;
}
private VideoTrack getVideoTrack() {
this.capturer = createCapturer();
return factory.createVideoTrack("video1", factory.createVideoSource(this.capturer));
}
private void initRTC(Context c) {
log("initializing");
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions.builder(c).createInitializationOptions());
log("initialized");
factory = PeerConnectionFactory.builder().createPeerConnectionFactory();
log("created");
constraints = new MediaConstraints();
constraints.optional.add(new MediaConstraints.KeyValuePair("offerToReceiveAudio", "true"));
constraints.optional.add(new MediaConstraints.KeyValuePair("offerToReceiveVideo", "false"));
constraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
// initVideoTrack();
}
static public RTCCall startCall(Contact target, String username, String identifier, OnStateChangeListener listener, Context context) {
return new RTCCall(target, username, identifier, listener, context);
}
public RTCCall(Socket commSocket, Context context, String offer) {
this.commSocket = commSocket;
initRTC(context);
this.offer = offer;
}
private void handleAnswer(String remoteDesc) {
log("setting remote desc");
connection.setRemoteDescription(new DefaultSdpObserver() {
@Override
public void onSetSuccess() {
super.onSetSuccess();
log("success");
}
@Override
public void onSetFailure(String s) {
super.onSetFailure(s);
log("failure: " + s);
}
}, new SessionDescription(SessionDescription.Type.ANSWER, remoteDesc));
}
private void reportStateChange(CallState state) {
this.state = state;
if (this.listener != null) {
this.listener.OnStateChange(state);
}
}
public void accept(OnStateChangeListener listener) {
this.listener = listener;
new Thread(() -> {
connection = factory.createPeerConnection(Collections.emptyList(), new DefaultObserver() {
@Override
public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
super.onIceGatheringChange(iceGatheringState);
if (iceGatheringState == PeerConnection.IceGatheringState.COMPLETE) {
log("transferring answer");
try {
JSONObject o = new JSONObject();
o.put("action", "connected");
o.put("answer", connection.getLocalDescription().description);
commSocket.getOutputStream().write((o.toString() + "\n").getBytes());
reportStateChange(CallState.CONNECTED);
// new Thread(new SpeakerRunnable(commSocket)).start();
} catch (Exception e) {
e.printStackTrace();
reportStateChange(CallState.ERROR);
}
}
}
@Override
public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
log("change");
super.onIceConnectionChange(iceConnectionState);
if (iceConnectionState == PeerConnection.IceConnectionState.DISCONNECTED) {
reportStateChange(CallState.ENDED);
}
}
@Override
public void onAddStream(MediaStream mediaStream) {
log("onAddStream");
super.onAddStream(mediaStream);
handleMediaStream(mediaStream);
}
@Override
public void onDataChannel(DataChannel dataChannel) {
super.onDataChannel(dataChannel);
RTCCall.this.dataChannel = dataChannel;
dataChannel.registerObserver(RTCCall.this);
}
});
connection.addStream(createStream());
// this.dataChannel = connection.createDataChannel("data", new DataChannel.Init());
log("setting remote description");
connection.setRemoteDescription(new DefaultSdpObserver() {
@Override
public void onSetSuccess() {
super.onSetSuccess();
log("creating answer...");
connection.createAnswer(new DefaultSdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
log("success");
super.onCreateSuccess(sessionDescription);
connection.setLocalDescription(new DefaultSdpObserver(), sessionDescription);
}
@Override
public void onCreateFailure(String s) {
super.onCreateFailure(s);
log("failure: " + s);
}
}, constraints);
}
}, new SessionDescription(SessionDescription.Type.OFFER, offer));
}).start();
}
public void decline() {
new Thread(() -> {
try {
log("declining...");
commSocket.getOutputStream().write("{\"action\":\"dismissed\"}\n".getBytes());
commSocket.getOutputStream().flush();
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
public void cleanup() {
if (this.upStream != null && state == CallState.CONNECTED) {
/*for(AudioTrack track : this.upStream.audioTracks){
track.setEnabled(false);
track.dispose();
}
for(VideoTrack track : this.upStream.videoTracks) track.dispose();*/
if (this.connection != null)
this.connection.close();
// factory.dispose();
}
if (commSocket != null) {
try {
commSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void hangUp() {
new Thread(() -> {
try {
if (commSocket != null) {
commSocket.getOutputStream().write("{\"action\":\"dismissed\"}\n".getBytes());
commSocket.close();
}
if (connection != null)
connection.close();
reportStateChange(CallState.ENDED);
} catch (IOException e) {
e.printStackTrace();
reportStateChange(CallState.ERROR);
}
}).start();
}
public interface OnStateChangeListener {
void OnStateChange(CallState state);
}
private void log(String s) {
Log.d(RTCCall.clreplaced.getSimpleName(), s);
}
}
19
Source : PeerConnectionWrapper.java
with GNU General Public License v3.0
from bcmapp
with GNU General Public License v3.0
from bcmapp
public clreplaced PeerConnectionWrapper {
private static final String TAG = PeerConnectionWrapper.clreplaced.getSimpleName();
private static final PeerConnection.IceServer STUN_SERVER = new PeerConnection.IceServer("stun:stun1.l.google.com:19302");
@NonNull
private final PeerConnection peerConnection;
@NonNull
private AudioTrack audioTrack;
@NonNull
private AudioSource audioSource;
@NonNull
private final Camera camera;
@Nullable
private final VideoSource videoSource;
@Nullable
private final VideoTrack videoTrack;
@Nullable
private MediaStream mediaStream;
@NonNull
private List<PeerConnection.IceServer> iceServers;
public PeerConnectionWrapper(@NonNull Context context, @NonNull PeerConnectionFactory factory, @NonNull PeerConnection.Observer observer, @NonNull VideoSink localRenderer, @NonNull List<PeerConnection.IceServer> turnServers, @NonNull CameraEventListener cameraEventListener, @NonNull EglBase eglBase, boolean hideIp) {
List<PeerConnection.IceServer> iceServers = new LinkedList<>();
iceServers.add(STUN_SERVER);
iceServers.addAll(turnServers);
this.iceServers = iceServers;
MediaConstraints constraints = new MediaConstraints();
MediaConstraints audioConstraints = new MediaConstraints();
PeerConnection.RTCConfiguration configuration = new PeerConnection.RTCConfiguration(iceServers);
configuration.bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE;
configuration.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.REQUIRE;
if (hideIp) {
configuration.iceTransportsType = PeerConnection.IceTransportsType.RELAY;
}
constraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
audioConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
this.peerConnection = factory.createPeerConnection(configuration, constraints, observer);
this.peerConnection.setAudioPlayout(false);
this.peerConnection.setAudioRecording(false);
this.mediaStream = factory.createLocalMediaStream("ARDAMS");
this.audioSource = factory.createAudioSource(audioConstraints);
this.audioTrack = factory.createAudioTrack("ARDAMSa0", audioSource);
this.audioTrack.setEnabled(false);
mediaStream.addTrack(audioTrack);
this.camera = new Camera(context, cameraEventListener);
if (camera.capturer != null) {
this.videoSource = factory.createVideoSource(false);
this.videoTrack = factory.createVideoTrack("ARDAMSv0", videoSource);
camera.capturer.initialize(SurfaceTextureHelper.create("WebRTC-SurfaceTextureHelper", eglBase.getEglBaseContext()), context, videoSource.getCapturerObserver());
this.videoTrack.addSink(localRenderer);
this.videoTrack.setEnabled(false);
mediaStream.addTrack(videoTrack);
} else {
this.videoSource = null;
this.videoTrack = null;
}
this.peerConnection.addStream(mediaStream);
}
public void reInitAudioTrack(@NonNull PeerConnectionFactory factory) {
this.peerConnection.removeStream(this.mediaStream);
this.mediaStream.removeTrack(audioTrack);
MediaConstraints audioConstraints = new MediaConstraints();
audioConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
this.audioSource = factory.createAudioSource(audioConstraints);
this.audioTrack = factory.createAudioTrack("ARDAMSa0", audioSource);
this.audioTrack.setEnabled(false);
this.mediaStream.addTrack(audioTrack);
this.peerConnection.addStream(this.mediaStream);
}
public void setVideoEnabled(boolean enabled) {
if (this.videoTrack != null) {
this.videoTrack.setEnabled(enabled);
}
camera.setEnabled(enabled);
}
public void flipCamera() {
camera.flip();
}
public CameraState getCameraState() {
return new CameraState(camera.getActiveDirection(), camera.getCount());
}
public void setCommunicationMode() {
this.peerConnection.setAudioPlayout(true);
this.peerConnection.setAudioRecording(true);
}
public void setAudioEnabled(boolean enabled) {
this.audioTrack.setEnabled(enabled);
}
public DataChannel createDataChannel(String name) {
DataChannel.Init dataChannelConfiguration = new DataChannel.Init();
dataChannelConfiguration.ordered = true;
return this.peerConnection.createDataChannel(name, dataChannelConfiguration);
}
public List<PeerConnection.IceServer> getServerList() {
return iceServers;
}
public SessionDescription createOffer(MediaConstraints mediaConstraints) throws PeerConnectionException {
final SettableFuture<SessionDescription> future = new SettableFuture<>();
peerConnection.createOffer(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sdp) {
future.set(sdp);
}
@Override
public void onCreateFailure(String error) {
future.setException(new PeerConnectionException(error));
}
@Override
public void onSetSuccess() {
throw new replacedertionError();
}
@Override
public void onSetFailure(String error) {
throw new replacedertionError();
}
}, mediaConstraints);
try {
return correctSessionDescription(future.get());
} catch (InterruptedException e) {
throw new replacedertionError(e);
} catch (ExecutionException e) {
throw new PeerConnectionException(e);
}
}
public SessionDescription createAnswer(MediaConstraints mediaConstraints) throws PeerConnectionException {
final SettableFuture<SessionDescription> future = new SettableFuture<>();
peerConnection.createAnswer(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sdp) {
future.set(sdp);
}
@Override
public void onCreateFailure(String error) {
future.setException(new PeerConnectionException(error));
}
@Override
public void onSetSuccess() {
throw new replacedertionError();
}
@Override
public void onSetFailure(String error) {
throw new replacedertionError();
}
}, mediaConstraints);
try {
return correctSessionDescription(future.get());
} catch (InterruptedException e) {
throw new replacedertionError(e);
} catch (ExecutionException e) {
throw new PeerConnectionException(e);
}
}
public void setRemoteDescription(SessionDescription sdp) throws PeerConnectionException {
final SettableFuture<Boolean> future = new SettableFuture<>();
peerConnection.setRemoteDescription(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sdp) {
}
@Override
public void onCreateFailure(String error) {
}
@Override
public void onSetSuccess() {
future.set(true);
}
@Override
public void onSetFailure(String error) {
future.setException(new PeerConnectionException(error));
}
}, sdp);
try {
future.get();
} catch (InterruptedException e) {
throw new replacedertionError(e);
} catch (ExecutionException e) {
throw new PeerConnectionException(e);
}
}
public void setLocalDescription(SessionDescription sdp) throws PeerConnectionException {
final SettableFuture<Boolean> future = new SettableFuture<>();
peerConnection.setLocalDescription(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sdp) {
throw new replacedertionError();
}
@Override
public void onCreateFailure(String error) {
throw new replacedertionError();
}
@Override
public void onSetSuccess() {
future.set(true);
}
@Override
public void onSetFailure(String error) {
future.setException(new PeerConnectionException(error));
}
}, sdp);
try {
future.get();
} catch (InterruptedException e) {
throw new replacedertionError(e);
} catch (ExecutionException e) {
throw new PeerConnectionException(e);
}
}
public void dispose() {
this.camera.dispose();
if (this.videoSource != null) {
this.videoSource.dispose();
}
this.audioSource.dispose();
this.peerConnection.close();
this.peerConnection.dispose();
}
public boolean addIceCandidate(IceCandidate candidate) {
return this.peerConnection.addIceCandidate(candidate);
}
private SessionDescription correctSessionDescription(SessionDescription sessionDescription) {
String updatedSdp = sessionDescription.description.replaceAll("(a=fmtp:111 ((?!cbr=).)*)\r?\n", "$1;cbr=1\r\n");
updatedSdp = updatedSdp.replaceAll(".+urn:ietf:params:rtp-hdrext:ssrc-audio-level.*\r?\n", "");
return new SessionDescription(sessionDescription.type, updatedSdp);
}
public static clreplaced PeerConnectionException extends Exception {
public PeerConnectionException(String error) {
super(error);
}
public PeerConnectionException(Throwable throwable) {
super(throwable);
}
}
private static clreplaced Camera implements CameraVideoCapturer.CameraSwitchHandler {
@Nullable
private final CameraVideoCapturer capturer;
private final CameraEventListener cameraEventListener;
private final int cameraCount;
private CameraState.Direction activeDirection;
private boolean enabled;
Camera(@NonNull Context context, @NonNull CameraEventListener cameraEventListener) {
this.cameraEventListener = cameraEventListener;
CameraEnumerator enumerator = getCameraEnumerator(context);
cameraCount = enumerator.getDeviceNames().length;
CameraVideoCapturer cameraVideoCapturer = createVideoCapturer(enumerator, FRONT);
if (cameraVideoCapturer != null) {
activeDirection = FRONT;
} else {
cameraVideoCapturer = createVideoCapturer(enumerator, BACK);
if (cameraVideoCapturer != null) {
activeDirection = BACK;
} else {
activeDirection = NONE;
}
}
capturer = cameraVideoCapturer;
}
void flip() {
if (capturer == null || cameraCount < 2) {
ALog.w(TAG, "Only have " + cameraCount + " cameras in this device.");
return;
}
activeDirection = PENDING;
capturer.switchCamera(this);
}
void setEnabled(boolean enabled) {
this.enabled = enabled;
if (capturer == null) {
return;
}
try {
if (enabled) {
capturer.startCapture(1280, 720, 30);
} else {
capturer.stopCapture();
}
} catch (InterruptedException e) {
ALog.w(TAG, "Get interrupted while trying to stop video capture. " + e.getMessage());
}
}
void dispose() {
if (capturer != null) {
capturer.dispose();
}
}
int getCount() {
return cameraCount;
}
@NonNull
CameraState.Direction getActiveDirection() {
return enabled ? activeDirection : NONE;
}
@Nullable
CameraVideoCapturer getCapturer() {
return capturer;
}
@Nullable
private CameraVideoCapturer createVideoCapturer(@NonNull CameraEnumerator enumerator, @NonNull CameraState.Direction direction) {
String[] deviceNames = enumerator.getDeviceNames();
for (String deviceName : deviceNames) {
if ((direction == FRONT && enumerator.isFrontFacing(deviceName)) || (direction == BACK && enumerator.isBackFacing(deviceName))) {
return enumerator.createCapturer(deviceName, null);
}
}
return null;
}
@NonNull
private CameraEnumerator getCameraEnumerator(@NonNull Context context) {
boolean camera2EnumeratorIsSupported = false;
try {
camera2EnumeratorIsSupported = Camera2Enumerator.isSupported(context);
} catch (final Throwable throwable) {
ALog.w(TAG, "Camera2Enumator.isSupport() threw. " + throwable.getMessage());
}
ALog.i(TAG, "Camera2 enumerator supported: " + camera2EnumeratorIsSupported);
return camera2EnumeratorIsSupported ? new Camera2Enumerator(context) : new Camera1Enumerator(true);
}
@Override
public void onCameraSwitchDone(boolean isFrontFacing) {
activeDirection = isFrontFacing ? FRONT : BACK;
cameraEventListener.onCameraSwitchCompleted(new CameraState(getActiveDirection(), getCount()));
}
@Override
public void onCameraSwitchError(String errorMessage) {
ALog.e(TAG, "onCameraSwitchError: " + errorMessage);
cameraEventListener.onCameraSwitchCompleted(new CameraState(getActiveDirection(), getCount()));
}
}
public interface CameraEventListener {
void onCameraSwitchCompleted(@NonNull CameraState newCameraState);
}
}
19
Source : WebRtcActivity.java
with Apache License 2.0
from awslabs
with Apache License 2.0
from awslabs
public clreplaced WebRtcActivity extends AppCompatActivity {
private static final String TAG = "KVSWebRtcActivity";
private static final String AudioTrackID = "KvsAudioTrack";
private static final String VideoTrackID = "KvsVideoTrack";
private static final String LOCAL_MEDIA_STREAM_LABEL = "KvsLocalMediaStream";
private static final int VIDEO_SIZE_WIDTH = 400;
private static final int VIDEO_SIZE_HEIGHT = 300;
private static final int VIDEO_FPS = 30;
private static final String CHANNEL_ID = "WebRtcDataChannel";
private static final boolean ENABLE_INTEL_VP8_ENCODER = true;
private static final boolean ENABLE_H264_HIGH_PROFILE = true;
private static volatile SignalingServiceWebSocketClient client;
private PeerConnectionFactory peerConnectionFactory;
private VideoSource videoSource;
private VideoTrack localVideoTrack;
private AudioManager audioManager;
private int originalAudioMode;
private boolean originalSpeakerphoneOn;
private AudioTrack localAudioTrack;
private SurfaceViewRenderer localView;
private SurfaceViewRenderer remoteView;
private PeerConnection localPeer;
private EglBase rootEglBase = null;
private VideoCapturer videoCapturer;
private final List<IceServer> peerIceServers = new ArrayList<>();
private boolean gotException = false;
private String recipientClientId;
private int mNotificationId = 0;
private boolean master = true;
private boolean isAudioSent = false;
private EditText dataChannelText = null;
private Button sendDataChannelButton = null;
private String mChannelArn;
private String mClientId;
private String mWssEndpoint;
private String mRegion;
private boolean mCameraFacingFront = true;
private AWSCredentials mCreds = null;
// Map to keep track of established peer connections by IDs
private HashMap<String, PeerConnection> peerConnectionFoundMap = new HashMap<String, PeerConnection>();
// Map to keep track of ICE candidates received for a client ID before peer connection is established
private HashMap<String, Queue<IceCandidate>> pendingIceCandidatesMap = new HashMap<String, Queue<IceCandidate>>();
private void initWsConnection() {
final String masterEndpoint = mWssEndpoint + "?X-Amz-ChannelARN=" + mChannelArn;
final String viewerEndpoint = mWssEndpoint + "?X-Amz-ChannelARN=" + mChannelArn + "&X-Amz-ClientId=" + mClientId;
URI signedUri;
runOnUiThread(new Runnable() {
@Override
public void run() {
mCreds = KinesisVideoWebRtcDemoApp.getCredentialsProvider().getCredentials();
}
});
signedUri = getSignedUri(masterEndpoint, viewerEndpoint);
if (master) {
createLocalPeerConnection();
}
final String wsHost = signedUri.toString();
final SignalingListener signalingListener = new SignalingListener() {
@Override
public void onSdpOffer(final Event offerEvent) {
Log.d(TAG, "Received SDP Offer: Setting Remote Description ");
final String sdp = Event.parseOfferEvent(offerEvent);
localPeer.setRemoteDescription(new KinesisVideoSdpObserver(), new SessionDescription(SessionDescription.Type.OFFER, sdp));
recipientClientId = offerEvent.getSenderClientId();
Log.d(TAG, "Received SDP offer for client ID: " + recipientClientId + ".Creating answer");
createSdpAnswer();
}
@Override
public void onSdpAnswer(final Event answerEvent) {
Log.d(TAG, "SDP answer received from signaling");
final String sdp = Event.parseSdpEvent(answerEvent);
final SessionDescription sdpAnswer = new SessionDescription(SessionDescription.Type.ANSWER, sdp);
localPeer.setRemoteDescription(new KinesisVideoSdpObserver(), sdpAnswer);
Log.d(TAG, "Answer Client ID: " + answerEvent.getSenderClientId());
peerConnectionFoundMap.put(answerEvent.getSenderClientId(), localPeer);
// Check if ICE candidates are available in the queue and add the candidate
handlePendingIceCandidates(answerEvent.getSenderClientId());
}
@Override
public void onIceCandidate(Event message) {
Log.d(TAG, "Received IceCandidate from remote ");
final IceCandidate iceCandidate = Event.parseIceCandidate(message);
if (iceCandidate != null) {
checkAndAddIceCandidate(message, iceCandidate);
} else {
Log.e(TAG, "Invalid Ice candidate");
}
}
@Override
public void onError(Event errorMessage) {
Log.e(TAG, "Received error message" + errorMessage);
}
@Override
public void onException(Exception e) {
Log.e(TAG, "Signaling client returned exception " + e.getMessage());
gotException = true;
}
};
if (wsHost != null) {
try {
client = new SignalingServiceWebSocketClient(wsHost, signalingListener, Executors.newFixedThreadPool(10));
Log.d(TAG, "Client connection " + (client.isOpen() ? "Successful" : "Failed"));
} catch (Exception e) {
gotException = true;
}
if (isValidClient()) {
Log.d(TAG, "Client connected to Signaling service " + client.isOpen());
if (!master) {
Log.d(TAG, "Signaling service is connected: " + // Viewer
"Sending offer as viewer to remote peer");
createSdpOffer();
}
} else {
Log.e(TAG, "Error in connecting to signaling service");
gotException = true;
}
}
}
private boolean isValidClient() {
return client != null && client.isOpen();
}
private void handlePendingIceCandidates(String clientId) {
// Add any pending ICE candidates from the queue for the client ID
Log.d(TAG, "Pending ice candidates found? " + pendingIceCandidatesMap.get(clientId));
Queue<IceCandidate> pendingIceCandidatesQueueByClientId = pendingIceCandidatesMap.get(clientId);
while (pendingIceCandidatesQueueByClientId != null && !pendingIceCandidatesQueueByClientId.isEmpty()) {
final IceCandidate iceCandidate = pendingIceCandidatesQueueByClientId.peek();
final PeerConnection peer = peerConnectionFoundMap.get(clientId);
final boolean addIce = peer.addIceCandidate(iceCandidate);
Log.d(TAG, "Added ice candidate after SDP exchange " + iceCandidate + " " + (addIce ? "Successfully" : "Failed"));
pendingIceCandidatesQueueByClientId.remove();
}
// After sending pending ICE candidates, the client ID's peer connection need not be tracked
pendingIceCandidatesMap.remove(clientId);
}
private void checkAndAddIceCandidate(Event message, IceCandidate iceCandidate) {
// if answer/offer is not received, it means peer connection is not found. Hold the received ICE candidates in the map.
if (!peerConnectionFoundMap.containsKey(message.getSenderClientId())) {
Log.d(TAG, "SDP exchange is not complete. Ice candidate " + iceCandidate + " + added to pending queue");
// If the entry for the client ID already exists (in case of subsequent ICE candidates), update the queue
if (pendingIceCandidatesMap.containsKey(message.getSenderClientId())) {
Queue<IceCandidate> pendingIceCandidatesQueueByClientId = pendingIceCandidatesMap.get(message.getSenderClientId());
pendingIceCandidatesQueueByClientId.add(iceCandidate);
pendingIceCandidatesMap.put(message.getSenderClientId(), pendingIceCandidatesQueueByClientId);
} else // If the first ICE candidate before peer connection is received, add entry to map and ICE candidate to a queue
{
Queue<IceCandidate> pendingIceCandidatesQueueByClientId = new LinkedList<>();
pendingIceCandidatesQueueByClientId.add(iceCandidate);
pendingIceCandidatesMap.put(message.getSenderClientId(), pendingIceCandidatesQueueByClientId);
}
} else // This is the case where peer connection is established and ICE candidates are received for the established
// connection
{
Log.d(TAG, "Peer connection found already");
// Remote sent us ICE candidates, add to local peer connection
final PeerConnection peer = peerConnectionFoundMap.get(message.getSenderClientId());
final boolean addIce = peer.addIceCandidate(iceCandidate);
Log.d(TAG, "Added ice candidate " + iceCandidate + " " + (addIce ? "Successfully" : "Failed"));
}
}
@Override
protected void onDestroy() {
Thread.setDefaultUncaughtExceptionHandler(null);
audioManager.setMode(originalAudioMode);
audioManager.setSpeakerphoneOn(originalSpeakerphoneOn);
if (rootEglBase != null) {
rootEglBase.release();
rootEglBase = null;
}
if (remoteView != null) {
remoteView.release();
remoteView = null;
}
if (localPeer != null) {
localPeer.dispose();
localPeer = null;
}
if (videoSource != null) {
videoSource.dispose();
videoSource = null;
}
if (videoCapturer != null) {
try {
videoCapturer.stopCapture();
} catch (InterruptedException e) {
Log.e(TAG, "Failed to stop webrtc video capture. ", e);
}
videoCapturer = null;
}
if (localView != null) {
localView.release();
localView = null;
}
if (client != null) {
client.disconnect();
client = null;
}
peerConnectionFoundMap.clear();
pendingIceCandidatesMap.clear();
finish();
super.onDestroy();
}
@Override
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Start websocket after adding local audio/video tracks
initWsConnection();
if (!gotException && isValidClient()) {
Toast.makeText(this, "Signaling Connected", Toast.LENGTH_LONG).show();
} else {
notifySignalingConnectionFailed();
}
}
private void notifySignalingConnectionFailed() {
finish();
Toast.makeText(this, "Connection error to signaling", Toast.LENGTH_LONG).show();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
Intent intent = getIntent();
mChannelArn = intent.getStringExtra(KEY_CHANNEL_ARN);
mWssEndpoint = intent.getStringExtra(KEY_WSS_ENDPOINT);
mClientId = intent.getStringExtra(KEY_CLIENT_ID);
if (mClientId == null || mClientId.isEmpty()) {
mClientId = UUID.randomUUID().toString();
}
master = intent.getBooleanExtra(KEY_IS_MASTER, true);
isAudioSent = intent.getBooleanExtra(KEY_SEND_AUDIO, false);
ArrayList<String> mUserNames = intent.getStringArrayListExtra(KEY_ICE_SERVER_USER_NAME);
ArrayList<String> mPreplacedwords = intent.getStringArrayListExtra(KEY_ICE_SERVER_PreplacedWORD);
ArrayList<Integer> mTTLs = intent.getIntegerArrayListExtra(KEY_ICE_SERVER_TTL);
ArrayList<List<String>> mUrisList = (ArrayList<List<String>>) intent.getSerializableExtra(KEY_ICE_SERVER_URI);
mRegion = intent.getStringExtra(KEY_REGION);
mCameraFacingFront = intent.getBooleanExtra(KEY_CAMERA_FRONT_FACING, true);
rootEglBase = EglBase.create();
// TODO: add ui to control TURN only option
PeerConnection.IceServer stun = PeerConnection.IceServer.builder(String.format("stun:stun.kinesisvideo.%s.amazonaws.com:443", mRegion)).createIceServer();
peerIceServers.add(stun);
if (mUrisList != null) {
for (int i = 0; i < mUrisList.size(); i++) {
String turnServer = mUrisList.get(i).toString();
if (turnServer != null) {
IceServer iceServer = IceServer.builder(turnServer.replace("[", "").replace("]", "")).setUsername(mUserNames.get(i)).setPreplacedword(mPreplacedwords.get(i)).createIceServer();
Log.d(TAG, "IceServer details (TURN) = " + iceServer.toString());
peerIceServers.add(iceServer);
}
}
}
setContentView(R.layout.activity_webrtc_main);
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions.builder(this).createInitializationOptions());
peerConnectionFactory = PeerConnectionFactory.builder().setVideoDecoderFactory(new DefaultVideoDecoderFactory(rootEglBase.getEglBaseContext())).setVideoEncoderFactory(new DefaultVideoEncoderFactory(rootEglBase.getEglBaseContext(), ENABLE_INTEL_VP8_ENCODER, ENABLE_H264_HIGH_PROFILE)).createPeerConnectionFactory();
videoCapturer = createVideoCapturer();
// Local video view
localView = findViewById(R.id.local_view);
localView.init(rootEglBase.getEglBaseContext(), null);
localView.setEnableHardwareScaler(true);
videoSource = peerConnectionFactory.createVideoSource(false);
SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create(Thread.currentThread().getName(), rootEglBase.getEglBaseContext());
videoCapturer.initialize(surfaceTextureHelper, this.getApplicationContext(), videoSource.getCapturerObserver());
localVideoTrack = peerConnectionFactory.createVideoTrack(VideoTrackID, videoSource);
localVideoTrack.addSink(localView);
if (isAudioSent) {
AudioSource audioSource = peerConnectionFactory.createAudioSource(new MediaConstraints());
localAudioTrack = peerConnectionFactory.createAudioTrack(AudioTrackID, audioSource);
localAudioTrack.setEnabled(true);
}
audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
originalAudioMode = audioManager.getMode();
originalSpeakerphoneOn = audioManager.isSpeakerphoneOn();
// Start capturing video
videoCapturer.startCapture(VIDEO_SIZE_WIDTH, VIDEO_SIZE_HEIGHT, VIDEO_FPS);
localVideoTrack.setEnabled(true);
remoteView = findViewById(R.id.remote_view);
remoteView.init(rootEglBase.getEglBaseContext(), null);
dataChannelText = findViewById(R.id.data_channel_text);
sendDataChannelButton = findViewById(R.id.send_data_channel_text);
createNotificationChannel();
}
private VideoCapturer createVideoCapturer() {
VideoCapturer videoCapturer;
Logging.d(TAG, "Create camera");
videoCapturer = createCameraCapturer(new Camera1Enumerator(false));
return videoCapturer;
}
private VideoCapturer createCameraCapturer(CameraEnumerator enumerator) {
final String[] deviceNames = enumerator.getDeviceNames();
Logging.d(TAG, "Enumerating cameras");
for (String deviceName : deviceNames) {
if (mCameraFacingFront ? enumerator.isFrontFacing(deviceName) : enumerator.isBackFacing(deviceName)) {
Logging.d(TAG, "Camera created");
VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
if (videoCapturer != null) {
return videoCapturer;
}
}
}
return null;
}
private void createLocalPeerConnection() {
PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(peerIceServers);
rtcConfig.bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE;
rtcConfig.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN;
rtcConfig.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY;
rtcConfig.keyType = PeerConnection.KeyType.ECDSA;
rtcConfig.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.REQUIRE;
rtcConfig.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.ENABLED;
localPeer = peerConnectionFactory.createPeerConnection(rtcConfig, new KinesisVideoPeerConnection() {
@Override
public void onIceCandidate(IceCandidate iceCandidate) {
super.onIceCandidate(iceCandidate);
Message message = createIceCandidateMessage(iceCandidate);
Log.d(TAG, "Sending IceCandidate to remote peer " + iceCandidate.toString());
client.sendIceCandidate(message);
/* Send to Peer */
}
@Override
public void onAddStream(MediaStream mediaStream) {
super.onAddStream(mediaStream);
Log.d(TAG, "Adding remote video stream (and audio) to the view");
addRemoteStreamToVideoView(mediaStream);
}
@Override
public void onDataChannel(DataChannel dataChannel) {
super.onDataChannel(dataChannel);
dataChannel.registerObserver(new DataChannel.Observer() {
@Override
public void onBufferedAmountChange(long l) {
// no op on receiver side
}
@Override
public void onStateChange() {
Log.d(TAG, "Remote Data Channel onStateChange: state: " + dataChannel.state().toString());
}
@Override
public void onMessage(DataChannel.Buffer buffer) {
runOnUiThread(new Runnable() {
@Override
public void run() {
byte[] bytes;
if (buffer.data.hasArray()) {
bytes = buffer.data.array();
} else {
bytes = new byte[buffer.data.remaining()];
buffer.data.get(bytes);
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID).setSmallIcon(R.mipmap.ic_launcher).setLargeIcon(BitmapFactory.decodeResource(getApplicationContext().getResources(), R.mipmap.ic_launcher)).setContentreplacedle("Message from Peer!").setContentText(new String(bytes, Charset.defaultCharset())).setPriority(NotificationCompat.PRIORITY_MAX).setAutoCancel(true);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getApplicationContext());
// notificationId is a unique int for each notification that you must define
notificationManager.notify(mNotificationId++, builder.build());
Toast.makeText(getApplicationContext(), "New message from peer, check notification.", Toast.LENGTH_SHORT).show();
}
});
}
});
}
});
if (localPeer != null) {
localPeer.getStats(new RTCStatsCollectorCallback() {
@Override
public void onStatsDelivered(RTCStatsReport rtcStatsReport) {
Map<String, RTCStats> statsMap = rtcStatsReport.getStatsMap();
Set<Map.Entry<String, RTCStats>> entries = statsMap.entrySet();
for (Map.Entry<String, RTCStats> entry : entries) {
Log.d(TAG, "Stats: " + entry.getKey() + " ," + entry.getValue());
}
}
});
}
addDataChannelToLocalPeer();
addStreamToLocalPeer();
}
private Message createIceCandidateMessage(IceCandidate iceCandidate) {
String sdpMid = iceCandidate.sdpMid;
int sdpMLineIndex = iceCandidate.sdpMLineIndex;
String sdp = iceCandidate.sdp;
String messagePayload = "{\"candidate\":\"" + sdp + "\",\"sdpMid\":\"" + sdpMid + "\",\"sdpMLineIndex\":" + sdpMLineIndex + "}";
String senderClientId = (master) ? "" : mClientId;
return new Message("ICE_CANDIDATE", recipientClientId, senderClientId, new String(Base64.encode(messagePayload.getBytes(), Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP)));
}
private void addStreamToLocalPeer() {
MediaStream stream = peerConnectionFactory.createLocalMediaStream(LOCAL_MEDIA_STREAM_LABEL);
if (!stream.addTrack(localVideoTrack)) {
Log.e(TAG, "Add video track failed");
}
localPeer.addTrack(stream.videoTracks.get(0), Collections.singletonList(stream.getId()));
if (isAudioSent) {
if (!stream.addTrack(localAudioTrack)) {
Log.e(TAG, "Add audio track failed");
}
if (stream.audioTracks.size() > 0) {
localPeer.addTrack(stream.audioTracks.get(0), Collections.singletonList(stream.getId()));
Log.d(TAG, "Sending audio track ");
}
}
}
private void addDataChannelToLocalPeer() {
Log.d(TAG, "Data channel addDataChannelToLocalPeer");
DataChannel localDataChannel = localPeer.createDataChannel("data-channel-of-" + mClientId, new DataChannel.Init());
localDataChannel.registerObserver(new DataChannel.Observer() {
@Override
public void onBufferedAmountChange(long l) {
Log.d(TAG, "Local Data Channel onBufferedAmountChange called with amount " + l);
}
@Override
public void onStateChange() {
Log.d(TAG, "Local Data Channel onStateChange: state: " + localDataChannel.state().toString());
if (sendDataChannelButton != null) {
runOnUiThread(() -> {
if (localDataChannel.state() == DataChannel.State.OPEN) {
sendDataChannelButton.setEnabled(true);
} else {
sendDataChannelButton.setEnabled(false);
}
});
}
}
@Override
public void onMessage(DataChannel.Buffer buffer) {
// Send out data, no op on sender side
}
});
sendDataChannelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
localDataChannel.send(new DataChannel.Buffer(ByteBuffer.wrap(dataChannelText.getText().toString().getBytes(Charset.defaultCharset())), false));
dataChannelText.setText("");
}
});
}
// when mobile sdk is viewer
private void createSdpOffer() {
MediaConstraints sdpMediaConstraints = new MediaConstraints();
sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
if (localPeer == null) {
createLocalPeerConnection();
}
localPeer.createOffer(new KinesisVideoSdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
super.onCreateSuccess(sessionDescription);
localPeer.setLocalDescription(new KinesisVideoSdpObserver(), sessionDescription);
Message sdpOfferMessage = Message.createOfferMessage(sessionDescription, mClientId);
if (isValidClient()) {
client.sendSdpOffer(sdpOfferMessage);
} else {
notifySignalingConnectionFailed();
}
}
}, sdpMediaConstraints);
}
// when local is set to be the master
private void createSdpAnswer() {
localPeer.createAnswer(new KinesisVideoSdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
Log.d(TAG, "Creating answer : success");
super.onCreateSuccess(sessionDescription);
localPeer.setLocalDescription(new KinesisVideoSdpObserver(), sessionDescription);
Message answer = Message.createAnswerMessage(sessionDescription, master, recipientClientId);
client.sendSdpAnswer(answer);
peerConnectionFoundMap.put(recipientClientId, localPeer);
handlePendingIceCandidates(recipientClientId);
}
}, new MediaConstraints());
}
private void addRemoteStreamToVideoView(MediaStream stream) {
final VideoTrack remoteVideoTrack = stream.videoTracks != null && stream.videoTracks.size() > 0 ? stream.videoTracks.get(0) : null;
AudioTrack remoteAudioTrack = stream.audioTracks != null && stream.audioTracks.size() > 0 ? stream.audioTracks.get(0) : null;
if (remoteAudioTrack != null) {
remoteAudioTrack.setEnabled(true);
Log.d(TAG, "remoteAudioTrack received: State=" + remoteAudioTrack.state().name());
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.setSpeakerphoneOn(true);
}
if (remoteVideoTrack != null) {
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
Log.d(TAG, "remoteVideoTrackId=" + remoteVideoTrack.id() + " videoTrackState=" + remoteVideoTrack.state());
resizeLocalView();
remoteVideoTrack.addSink(remoteView);
resizeRemoteView();
} catch (Exception e) {
Log.e(TAG, "Error in setting remote video view" + e);
}
}
});
} else {
Log.e(TAG, "Error in setting remote track");
}
}
private URI getSignedUri(String masterEndpoint, String viewerEndpoint) {
URI signedUri;
if (master) {
signedUri = AwsV4Signer.sign(URI.create(masterEndpoint), mCreds.getAWSAccessKeyId(), mCreds.getAWSSecretKey(), mCreds instanceof AWSSessionCredentials ? ((AWSSessionCredentials) mCreds).getSessionToken() : "", URI.create(mWssEndpoint), mRegion);
} else {
signedUri = AwsV4Signer.sign(URI.create(viewerEndpoint), mCreds.getAWSAccessKeyId(), mCreds.getAWSSecretKey(), mCreds instanceof AWSSessionCredentials ? ((AWSSessionCredentials) mCreds).getSessionToken() : "", URI.create(mWssEndpoint), mRegion);
}
return signedUri;
}
@SuppressLint("ClickableViewAccessibility")
private void resizeLocalView() {
DisplayMetrics displayMetrics = new DisplayMetrics();
final ViewGroup.LayoutParams lp = localView.getLayoutParams();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
lp.height = (int) (displayMetrics.heightPixels * 0.25);
lp.width = (int) (displayMetrics.widthPixels * 0.25);
localView.setLayoutParams(lp);
localView.setOnTouchListener(new View.OnTouchListener() {
private final int mMarginRight = displayMetrics.widthPixels;
private final int mMarginBottom = displayMetrics.heightPixels;
private int deltaOfDownXAndMargin, deltaOfDownYAndMargin;
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
final int X = (int) motionEvent.getRawX();
final int Y = (int) motionEvent.getRawY();
switch(motionEvent.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
FrameLayout.LayoutParams lParams = (FrameLayout.LayoutParams) lp;
deltaOfDownXAndMargin = X + lParams.rightMargin;
deltaOfDownYAndMargin = Y + lParams.bottomMargin;
return true;
case MotionEvent.ACTION_MOVE:
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) lp;
layoutParams.rightMargin = deltaOfDownXAndMargin - X;
layoutParams.bottomMargin = deltaOfDownYAndMargin - Y;
// shouldn't be out of screen
if (layoutParams.rightMargin >= mMarginRight - lp.width) {
layoutParams.rightMargin = mMarginRight - lp.width;
}
if (layoutParams.bottomMargin >= mMarginBottom - lp.height) {
layoutParams.bottomMargin = mMarginBottom - lp.height;
}
if (layoutParams.rightMargin <= 0) {
layoutParams.rightMargin = 0;
}
if (layoutParams.bottomMargin <= 0) {
layoutParams.bottomMargin = 0;
}
localView.setLayoutParams(layoutParams);
}
return false;
}
});
}
private void resizeRemoteView() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
DisplayMetrics displayMetrics = new DisplayMetrics();
final ViewGroup.LayoutParams lp = remoteView.getLayoutParams();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
lp.height = (int) (displayMetrics.heightPixels * 0.75);
lp.width = (int) (displayMetrics.widthPixels * 0.75);
remoteView.setLayoutParams(lp);
localView.bringToFront();
}
}
private void createNotificationChannel() {
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel clreplaced is new and not in the support library
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = getString(R.string.data_channel_notification);
String description = getString(R.string.data_channel_notification_description);
int importance = NotificationManager.IMPORTANCE_HIGH;
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
channel.setDescription(description);
// Register the channel with the system; you can't change the importance
// or other notification behaviors after this
NotificationManager notificationManager = getSystemService(NotificationManager.clreplaced);
notificationManager.createNotificationChannel(channel);
}
}
}
18
Source : WebRTCWrapper.java
with GNU General Public License v3.0
from snikket-im
with GNU General Public License v3.0
from snikket-im
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);
}
}
18
Source : WebSocketTask.java
with Apache License 2.0
from sergiopaniego
with Apache License 2.0
from sergiopaniego
/**
* Created by sergiopaniegoblanco on 18/02/2018.
*/
public clreplaced WebSocketTask extends AsyncTask<VideoConferenceActivity, Void, Void> {
private static final String TAG = "WebSocketTask";
private static final String TOKEN_URL = "https://demos.openvidu.io:4443/api/tokens";
private static final String AUTH_TOKEN = "Basic T1BFTlZJRFVBUFA6TVlfU0VDUkVU";
private VideoConferenceActivity activity;
private PeerConnection localPeer;
private String sessionName;
private String participantName;
private String socketAddress;
private PeerConnectionFactory peerConnectionFactory;
private AudioTrack localAudioTrack;
private VideoTrack localVideoTrack;
private PeersManager peersManager;
private boolean isCancelled = false;
private OkHttpClient client;
private final TrustManager[] trustManagers = new TrustManager[] { new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
@Override
public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {
Log.i(TAG, ": authType: " + authType);
}
@Override
public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {
Log.i(TAG, ": authType: " + authType);
}
} };
public WebSocketTask(VideoConferenceActivity activity, PeersManager peersManager, String sessionName, String participantName, String socketAddress) {
this.activity = activity;
this.peersManager = peersManager;
this.localPeer = peersManager.getLocalPeer();
this.sessionName = sessionName;
this.participantName = participantName;
this.socketAddress = socketAddress;
this.peerConnectionFactory = peersManager.getPeerConnectionFactory();
this.localAudioTrack = peersManager.getLocalAudioTrack();
this.localVideoTrack = peersManager.getLocalVideoTrack();
this.client = new OkHttpClient();
}
public void setCancelled(boolean cancelled) {
isCancelled = cancelled;
}
@Override
protected Void doInBackground(VideoConferenceActivity... parameters) {
try {
String json = "{\"session\": \"SessionA\"}";
RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json);
Request request = new Request.Builder().url(TOKEN_URL).header("Authorization", AUTH_TOKEN).post(body).build();
Response response = client.newCall(request).execute();
String responseString = response.body().string();
String token = "";
try {
JSONObject jsonObject = new JSONObject(responseString);
token = (String) jsonObject.get("token");
} catch (JSONException e) {
e.printStackTrace();
}
WebSocketFactory factory = new WebSocketFactory();
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagers, new java.security.SecureRandom());
factory.setSSLContext(sslContext);
socketAddress = getSocketAddress();
peersManager.setWebSocket(new WebSocketFactory().createSocket(socketAddress));
peersManager.setWebSocketAdapter(new CustomWebSocketListener(parameters[0], peersManager, sessionName, participantName, activity.getViewsContainer(), socketAddress, token));
peersManager.getWebSocket().addListener(peersManager.getWebSocketAdapter());
if (!isCancelled) {
peersManager.getWebSocket().connect();
}
} catch (IOException | KeyManagementException | WebSocketException | NoSuchAlgorithmException | IllegalArgumentException e) {
Handler mainHandler = new Handler(activity.getMainLooper());
Runnable myRunnable = new Runnable() {
@Override
public void run() {
Toast toast = Toast.makeText(activity, activity.getResources().getString(R.string.no_connection), Toast.LENGTH_LONG);
toast.show();
activity.hangup();
}
};
mainHandler.post(myRunnable);
isCancelled = true;
}
return null;
}
private String getSocketAddress() {
String baseAddress = socketAddress;
String secureWebSocketPrefix = "wss://";
String insecureWebSocketPrefix = "ws://";
if (baseAddress.split(secureWebSocketPrefix).length == 1 && baseAddress.split(insecureWebSocketPrefix).length == 1) {
baseAddress = secureWebSocketPrefix.concat(baseAddress);
}
String portSuffix = ":4443";
if (baseAddress.split(portSuffix).length == 1 && !baseAddress.regionMatches(true, baseAddress.length() - portSuffix.length(), portSuffix, 0, portSuffix.length())) {
baseAddress = baseAddress.concat(portSuffix);
}
return baseAddress;
}
@Override
protected void onProgressUpdate(Void... progress) {
Log.i(TAG, "PROGRESS " + Arrays.toString(progress));
}
@Override
protected void onPostExecute(Void results) {
if (!isCancelled) {
MediaConstraints sdpConstraints = new MediaConstraints();
sdpConstraints.mandatory.add(new MediaConstraints.KeyValuePair("offerToReceiveAudio", "true"));
sdpConstraints.mandatory.add(new MediaConstraints.KeyValuePair("offerToReceiveVideo", "true"));
MediaStream stream = peerConnectionFactory.createLocalMediaStream("102");
stream.addTrack(localAudioTrack);
stream.addTrack(localVideoTrack);
localPeer.addStream(stream);
peersManager.createLocalOffer(sdpConstraints);
} else {
isCancelled = false;
}
}
}
18
Source : RemoteParticipant.java
with Apache License 2.0
from sergiopaniego
with Apache License 2.0
from sergiopaniego
/**
* Created by sergiopaniegoblanco on 08/02/2018.
*/
public clreplaced RemoteParticipant {
private String id;
private MediaStream mediaStream;
private PeerConnection peerConnection;
private AudioTrack audioTrack;
private VideoTrack videoTrack;
private SurfaceViewRenderer videoView;
private View view;
private TextView participantNameText;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public MediaStream getMediaStream() {
return mediaStream;
}
public void setMediaStream(MediaStream mediaStream) {
this.mediaStream = mediaStream;
}
public PeerConnection getPeerConnection() {
return peerConnection;
}
public void setPeerConnection(PeerConnection peerConnection) {
this.peerConnection = peerConnection;
}
public AudioTrack getAudioTrack() {
return audioTrack;
}
public void setAudioTrack(AudioTrack audioTrack) {
this.audioTrack = audioTrack;
}
public VideoTrack getVideoTrack() {
return videoTrack;
}
public void setVideoTrack(VideoTrack videoTrack) {
this.videoTrack = videoTrack;
}
public SurfaceViewRenderer getVideoView() {
return videoView;
}
public void setVideoView(SurfaceViewRenderer videoView) {
this.videoView = videoView;
}
public View getView() {
return view;
}
public void setView(View view) {
this.view = view;
}
public TextView getParticipantNameText() {
return participantNameText;
}
public void setParticipantNameText(TextView participantNameText) {
this.participantNameText = participantNameText;
}
}
18
Source : CustomWebSocketListener.java
with Apache License 2.0
from sergiopaniego
with Apache License 2.0
from sergiopaniego
/**
* Created by sergiopaniegoblanco on 02/12/2017.
*/
public clreplaced CustomWebSocketListener implements WebSocketListener {
private static final String TAG = "CustomWebSocketAdapter";
private static final String JSON_RPCVERSION = "2.0";
private static final int PING_MESSAGE_INTERVAL = 3;
private VideoConferenceActivity videoConferenceActivity;
private PeerConnection localPeer;
private int id;
private List<Map<String, String>> iceCandidatesParams;
private Map<String, String> localOfferParams;
private String userId;
private String sessionName;
private String participantName;
private LinearLayout views_container;
private Map<String, RemoteParticipant> participants;
private String remoteParticipantId;
private PeersManager peersManager;
private String socketAddress;
private String token;
public CustomWebSocketListener(VideoConferenceActivity videoConferenceActivity, PeersManager peersManager, String sessionName, String participantName, LinearLayout views_container, String socketAddress, String token) {
this.videoConferenceActivity = videoConferenceActivity;
this.peersManager = peersManager;
this.localPeer = peersManager.getLocalPeer();
this.id = 0;
this.sessionName = sessionName;
this.participantName = participantName;
this.views_container = views_container;
this.socketAddress = socketAddress;
this.iceCandidatesParams = new ArrayList<>();
this.participants = new HashMap<>();
this.token = token;
}
public Map<String, RemoteParticipant> getParticipants() {
return participants;
}
public String getUserId() {
return userId;
}
public int getId() {
return id;
}
private void updateId() {
id++;
}
@Override
public void onStateChanged(WebSocket websocket, WebSocketState newState) throws Exception {
Log.i(TAG, "State changed: " + newState.name());
}
@Override
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception {
Log.i(TAG, "Connected");
pingMessageHandler(websocket);
String regex = "(room)+";
String baseAddress = socketAddress.split(regex)[0];
Map<String, String> joinRoomParams = new HashMap<>();
joinRoomParams.put(JSONConstants.METADATA, "{\"clientData\": \"" + participantName + "\"}");
joinRoomParams.put("recorder", "false");
joinRoomParams.put("secret", "MY_SECRET");
joinRoomParams.put("session", sessionName);
joinRoomParams.put("token", token);
sendJson(websocket, "joinRoom", joinRoomParams);
if (localOfferParams != null) {
sendJson(websocket, "publishVideo", localOfferParams);
}
}
private void pingMessageHandler(final WebSocket webSocket) {
long initialDelay = 0L;
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
executor.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
Map<String, String> pingParams = new HashMap<>();
if (id == 0) {
pingParams.put("interval", "3000");
}
sendJson(webSocket, "ping", pingParams);
}
}, initialDelay, PING_MESSAGE_INTERVAL, TimeUnit.SECONDS);
}
@Override
public void onConnectError(WebSocket websocket, WebSocketException cause) throws Exception {
Log.i(TAG, "Connect error: " + cause);
}
@Override
public void onDisconnected(WebSocket websocket, WebSocketFrame serverCloseFrame, WebSocketFrame clientCloseFrame, boolean closedByServer) throws Exception {
Log.i(TAG, "Disconnected " + serverCloseFrame.getCloseReason() + " " + clientCloseFrame.getCloseReason() + " " + closedByServer);
}
@Override
public void onFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
Log.i(TAG, "Frame");
}
@Override
public void onContinuationFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
Log.i(TAG, "Continuation Frame");
}
@Override
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
Log.i(TAG, "Text Frame");
}
@Override
public void onBinaryFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
Log.i(TAG, "Binary Frame");
}
@Override
public void onCloseFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
Log.i(TAG, "Close Frame");
}
@Override
public void onPingFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
Log.i(TAG, "Ping Frame");
}
@Override
public void onPongFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
Log.i(TAG, "Pong Frame");
}
@Override
public void onTextMessage(final WebSocket websocket, String text) throws Exception {
Log.i(TAG, "Text Message " + text);
JSONObject json = new JSONObject(text);
if (json.has(JSONConstants.RESULT)) {
handleResult(websocket, json);
} else {
handleMethod(websocket, json);
}
}
private void handleResult(final WebSocket webSocket, JSONObject json) throws JSONException {
JSONObject result = new JSONObject(json.getString(JSONConstants.RESULT));
if (result.has(JSONConstants.SDP_ANSWER)) {
saveAnswer(result);
} else if (result.has(JSONConstants.SESSION_ID)) {
if (result.has(JSONConstants.VALUE)) {
if (result.getJSONArray(JSONConstants.VALUE).length() > 0) {
addParticipantsAlreadyInRoom(result, webSocket);
}
this.userId = result.getString(JSONConstants.ID);
for (Map<String, String> iceCandidate : iceCandidatesParams) {
iceCandidate.put("endpointName", this.userId);
sendJson(webSocket, "onIceCandidate", iceCandidate);
}
}
} else if (result.has(JSONConstants.VALUE)) {
Log.i(TAG, "pong");
} else {
Log.e(TAG, "Unrecognized " + result);
}
}
private void addParticipantsAlreadyInRoom(JSONObject result, final WebSocket webSocket) throws JSONException {
for (int i = 0; i < result.getJSONArray(JSONConstants.VALUE).length(); i++) {
remoteParticipantId = result.getJSONArray(JSONConstants.VALUE).getJSONObject(i).getString(JSONConstants.ID);
final RemoteParticipant remoteParticipant = new RemoteParticipant();
remoteParticipant.setId(remoteParticipantId);
participants.put(remoteParticipantId, remoteParticipant);
createVideoView(remoteParticipant);
setRemoteParticipantName(new JSONObject(result.getJSONArray(JSONConstants.VALUE).getJSONObject(i).getString(JSONConstants.METADATA)).getString("clientData"), remoteParticipant);
peersManager.createRemotePeerConnection(remoteParticipant);
remoteParticipant.getPeerConnection().createOffer(new CustomSdpObserver("remoteCreateOffer") {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
super.onCreateSuccess(sessionDescription);
remoteParticipant.getPeerConnection().setLocalDescription(new CustomSdpObserver("remoteSetLocalDesc"), sessionDescription);
Map<String, String> remoteOfferParams = new HashMap<>();
remoteOfferParams.put("sdpOffer", sessionDescription.description);
remoteOfferParams.put("sender", remoteParticipantId + "_CAMERA");
sendJson(webSocket, "receiveVideoFrom", remoteOfferParams);
}
}, new MediaConstraints());
}
}
private void handleMethod(final WebSocket webSocket, JSONObject json) throws JSONException {
if (!json.has(JSONConstants.PARAMS)) {
Log.e(TAG, "No params");
} else {
final JSONObject params = new JSONObject(json.getString(JSONConstants.PARAMS));
String method = json.getString(JSONConstants.METHOD);
switch(method) {
case JSONConstants.ICE_CANDIDATE:
iceCandidateMethod(params);
break;
case JSONConstants.PARTICIPANT_JOINED:
participantJoinedMethod(params);
break;
case JSONConstants.PARTICIPANT_PUBLISHED:
participantPublishedMethod(params, webSocket);
break;
case JSONConstants.PARTICIPANT_LEFT:
participantLeftMethod(params);
break;
default:
throw new JSONException("Can't understand method: " + method);
}
}
}
private void iceCandidateMethod(JSONObject params) throws JSONException {
if (params.getString("endpointName").equals(userId)) {
saveIceCandidate(params, null);
} else {
saveIceCandidate(params, params.getString("endpointName"));
}
}
private void participantJoinedMethod(JSONObject params) throws JSONException {
final RemoteParticipant remoteParticipant = new RemoteParticipant();
remoteParticipant.setId(params.getString(JSONConstants.ID));
participants.put(params.getString(JSONConstants.ID), remoteParticipant);
createVideoView(remoteParticipant);
setRemoteParticipantName(new JSONObject(params.getString(JSONConstants.METADATA)).getString("clientData"), remoteParticipant);
peersManager.createRemotePeerConnection(remoteParticipant);
}
private void createVideoView(final RemoteParticipant remoteParticipant) {
Handler mainHandler = new Handler(videoConferenceActivity.getMainLooper());
Runnable myRunnable = new Runnable() {
@Override
public void run() {
View rowView = videoConferenceActivity.getLayoutInflater().inflate(R.layout.peer_video, null);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
lp.setMargins(0, 0, 0, 20);
rowView.setLayoutParams(lp);
int rowId = View.generateViewId();
rowView.setId(rowId);
views_container.addView(rowView);
SurfaceViewRenderer videoView = (SurfaceViewRenderer) ((ViewGroup) rowView).getChildAt(0);
remoteParticipant.setVideoView(videoView);
videoView.setMirror(false);
EglBase rootEglBase = EglBase.create();
videoView.init(rootEglBase.getEglBaseContext(), null);
videoView.setZOrderMediaOverlay(true);
View textView = ((ViewGroup) rowView).getChildAt(1);
remoteParticipant.setParticipantNameText((TextView) textView);
remoteParticipant.setView(rowView);
}
};
mainHandler.post(myRunnable);
}
private void participantPublishedMethod(JSONObject params, final WebSocket webSocket) throws JSONException {
remoteParticipantId = params.getString(JSONConstants.ID);
RemoteParticipant remoteParticipantPublished = participants.get(remoteParticipantId);
remoteParticipantPublished.getPeerConnection().createOffer(new CustomSdpObserver("remoteCreateOffer", remoteParticipantPublished) {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
super.onCreateSuccess(sessionDescription);
getRemoteParticipant().getPeerConnection().setLocalDescription(new CustomSdpObserver("remoteSetLocalDesc"), sessionDescription);
Map<String, String> remoteOfferParams = new HashMap<>();
remoteOfferParams.put("sdpOffer", sessionDescription.description);
remoteOfferParams.put("sender", getRemoteParticipant().getId() + "_webcam");
sendJson(webSocket, "receiveVideoFrom", remoteOfferParams);
}
}, new MediaConstraints());
}
private void participantLeftMethod(JSONObject params) throws JSONException {
final String participantId = params.getString("name");
participants.get(participantId).getPeerConnection().close();
Handler mainHandler = new Handler(videoConferenceActivity.getMainLooper());
Runnable myRunnable = new Runnable() {
@Override
public void run() {
views_container.removeView(participants.get(participantId).getView());
}
};
mainHandler.post(myRunnable);
RemoteParticipant remoteParticipantToDelete = participants.get(participantId);
participants.remove(remoteParticipantToDelete);
}
private void setRemoteParticipantName(final String name, final RemoteParticipant participant) {
Handler mainHandler = new Handler(videoConferenceActivity.getMainLooper());
Runnable myRunnable = new Runnable() {
@Override
public void run() {
videoConferenceActivity.setRemoteParticipantName(name, participant);
}
};
mainHandler.post(myRunnable);
}
@Override
public void onBinaryMessage(WebSocket websocket, byte[] binary) throws Exception {
Log.i(TAG, "Binary Message");
}
@Override
public void onSendingFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
Log.i(TAG, "Sending Frame");
}
@Override
public void onFrameSent(WebSocket websocket, WebSocketFrame frame) throws Exception {
Log.i(TAG, "Frame sent");
}
@Override
public void onFrameUnsent(WebSocket websocket, WebSocketFrame frame) throws Exception {
Log.i(TAG, "Frame unsent");
}
@Override
public void onThreadCreated(WebSocket websocket, ThreadType threadType, Thread thread) throws Exception {
Log.i(TAG, "Thread created");
}
@Override
public void onThreadStarted(WebSocket websocket, ThreadType threadType, Thread thread) throws Exception {
Log.i(TAG, "Thread started");
}
@Override
public void onThreadStopping(WebSocket websocket, ThreadType threadType, Thread thread) throws Exception {
Log.i(TAG, "Thread stopping");
}
@Override
public void onError(WebSocket websocket, WebSocketException cause) throws Exception {
Log.i(TAG, "Error! " + cause);
}
@Override
public void onFrameError(WebSocket websocket, WebSocketException cause, WebSocketFrame frame) throws Exception {
Log.i(TAG, "Frame error");
}
@Override
public void onMessageError(WebSocket websocket, WebSocketException cause, List<WebSocketFrame> frames) throws Exception {
Log.i(TAG, "Message error! " + cause);
}
@Override
public void onMessageDecompressionError(WebSocket websocket, WebSocketException cause, byte[] compressed) throws Exception {
Log.i(TAG, "Message Decompression Error");
}
@Override
public void onTextMessageError(WebSocket websocket, WebSocketException cause, byte[] data) throws Exception {
Log.i(TAG, "Text Message Error! " + cause);
}
@Override
public void onSendError(WebSocket websocket, WebSocketException cause, WebSocketFrame frame) throws Exception {
Log.i(TAG, "Send Error! " + cause);
}
@Override
public void onUnexpectedError(WebSocket websocket, WebSocketException cause) throws Exception {
Log.i(TAG, "Unexpected error! " + cause);
}
@Override
public void handleCallbackError(WebSocket websocket, Throwable cause) throws Exception {
Log.i(TAG, "Handle callback error! " + cause);
}
@Override
public void onSendingHandshake(WebSocket websocket, String requestLine, List<String[]> headers) throws Exception {
Log.i(TAG, "Sending Handshake! Hello!");
}
private void saveIceCandidate(JSONObject json, String endPointName) throws JSONException {
IceCandidate iceCandidate = new IceCandidate(json.getString("sdpMid"), Integer.parseInt(json.getString("sdpMLineIndex")), json.getString("candidate"));
if (endPointName == null) {
localPeer.addIceCandidate(iceCandidate);
} else {
participants.get(endPointName).getPeerConnection().addIceCandidate(iceCandidate);
}
}
private void saveAnswer(JSONObject json) throws JSONException {
SessionDescription sessionDescription = new SessionDescription(SessionDescription.Type.ANSWER, json.getString("sdpAnswer"));
if (localPeer.getRemoteDescription() == null) {
localPeer.setRemoteDescription(new CustomSdpObserver("localSetRemoteDesc"), sessionDescription);
} else {
participants.get(remoteParticipantId).getPeerConnection().setRemoteDescription(new CustomSdpObserver("remoteSetRemoteDesc"), sessionDescription);
}
}
public void sendJson(WebSocket webSocket, String method, Map<String, String> params) {
try {
JSONObject paramsJson = new JSONObject();
for (Map.Entry<String, String> param : params.entrySet()) {
paramsJson.put(param.getKey(), param.getValue());
}
JSONObject jsonObject = new JSONObject();
if (method.equals(JSONConstants.JOIN_ROOM)) {
jsonObject.put(JSONConstants.ID, 1).put(JSONConstants.PARAMS, paramsJson);
} else if (paramsJson.length() > 0) {
jsonObject.put(JSONConstants.ID, getId()).put(JSONConstants.PARAMS, paramsJson);
} else {
jsonObject.put(JSONConstants.ID, getId());
}
jsonObject.put("jsonrpc", JSON_RPCVERSION).put(JSONConstants.METHOD, method);
String jsonString = jsonObject.toString();
updateId();
webSocket.sendText(jsonString);
} catch (JSONException e) {
Log.i(TAG, "JSONException raised on sendJson");
}
}
public void addIceCandidate(Map<String, String> iceCandidateParams) {
iceCandidatesParams.add(iceCandidateParams);
}
public void setLocalOfferParams(Map<String, String> offerParams) {
this.localOfferParams = offerParams;
}
}
18
Source : MainActivity.java
with Apache License 2.0
from rome753
with Apache License 2.0
from rome753
@Override
public void onPeerJoined(String socketId) {
PeerConnection peerConnection = getOrCreatePeerConnection(socketId);
peerConnection.createOffer(new SdpAdapter("createOfferSdp:" + socketId) {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
super.onCreateSuccess(sessionDescription);
peerConnection.setLocalDescription(new SdpAdapter("setLocalSdp:" + socketId), sessionDescription);
SignalingClient.get().sendSessionDescription(sessionDescription, socketId);
}
}, new MediaConstraints());
}
18
Source : PeerConnectionClient.java
with MIT License
from crossle
with MIT License
from crossle
private void getStats(final BigInteger handleId) {
PeerConnection peerConnection = peerConnectionMap.get(handleId).peerConnection;
boolean success = peerConnection.getStats(new StatsObserver() {
@Override
public void onComplete(final StatsReport[] reports) {
events.onPeerConnectionStatsReady(reports);
}
}, null);
if (!success) {
Log.e(TAG, "getStats() returns false!");
}
}
17
Source : MainActivity.java
with Apache License 2.0
from rome753
with Apache License 2.0
from rome753
private synchronized PeerConnection getOrCreatePeerConnection(String socketId) {
PeerConnection peerConnection = peerConnectionMap.get(socketId);
if (peerConnection != null) {
return peerConnection;
}
peerConnection = peerConnectionFactory.createPeerConnection(iceServers, new PeerConnectionAdapter("PC:" + socketId) {
@Override
public void onIceCandidate(IceCandidate iceCandidate) {
super.onIceCandidate(iceCandidate);
SignalingClient.get().sendIceCandidate(iceCandidate, socketId);
}
@Override
public void onAddStream(MediaStream mediaStream) {
super.onAddStream(mediaStream);
VideoTrack remoteVideoTrack = mediaStream.videoTracks.get(0);
runOnUiThread(() -> {
remoteVideoTrack.addSink(remoteViews[remoteViewsIndex++]);
});
}
});
peerConnection.addStream(mediaStream);
peerConnectionMap.put(socketId, peerConnection);
return peerConnection;
}
17
Source : MainActivity.java
with Apache License 2.0
from rome753
with Apache License 2.0
from rome753
@Override
public void onAnswerReceived(JSONObject data) {
String socketId = data.optString("from");
PeerConnection peerConnection = getOrCreatePeerConnection(socketId);
peerConnection.setRemoteDescription(new SdpAdapter("setRemoteSdp:" + socketId), new SessionDescription(SessionDescription.Type.ANSWER, data.optString("sdp")));
}
17
Source : MainActivity.java
with Apache License 2.0
from rome753
with Apache License 2.0
from rome753
@Override
public void onOfferReceived(JSONObject data) {
runOnUiThread(() -> {
final String socketId = data.optString("from");
PeerConnection peerConnection = getOrCreatePeerConnection(socketId);
peerConnection.setRemoteDescription(new SdpAdapter("setRemoteSdp:" + socketId), new SessionDescription(SessionDescription.Type.OFFER, data.optString("sdp")));
peerConnection.createAnswer(new SdpAdapter("localAnswerSdp") {
@Override
public void onCreateSuccess(SessionDescription sdp) {
super.onCreateSuccess(sdp);
peerConnectionMap.get(socketId).setLocalDescription(new SdpAdapter("setLocalSdp:" + socketId), sdp);
SignalingClient.get().sendSessionDescription(sdp, socketId);
}
}, new MediaConstraints());
});
}
17
Source : CallActivity.java
with GNU General Public License v3.0
from Jhuster
with GNU General Public License v3.0
from Jhuster
public PeerConnection createPeerConnection() {
Log.i(TAG, "Create PeerConnection ...");
PeerConnection.RTCConfiguration configuration = new PeerConnection.RTCConfiguration(new ArrayList<>());
PeerConnection connection = mPeerConnectionFactory.createPeerConnection(configuration, mPeerConnectionObserver);
if (connection == null) {
Log.e(TAG, "Failed to createPeerConnection !");
return null;
}
connection.addTrack(mVideoTrack);
connection.addTrack(mAudioTrack);
return connection;
}
17
Source : VideoChatHelper.java
with MIT License
from fengli12321
with MIT License
from fengli12321
private String getKeyFromConnectionDic(PeerConnection peerConnection) {
String socketId = null;
for (Map.Entry<String, Peer> entry : peers.entrySet()) {
if (peerConnection.equals(entry.getValue().pc)) {
socketId = entry.getKey();
}
}
if (socketId == null) {
FLLog.i("错误:未找到相应的socketId");
}
return socketId;
}
17
Source : VideoChatHelper.java
with MIT License
from fengli12321
with MIT License
from fengli12321
/*
* 为所有连接添加流
* */
private void addStream() {
for (Map.Entry<String, Peer> entry : peers.entrySet()) {
Peer peer = entry.getValue();
PeerConnection connection = peer.pc;
if (localMediaStream == null) {
FLLog.i("添加本地流时,本地流为空");
} else {
connection.addStream(localMediaStream);
}
}
}
17
Source : VideoChatHelper.java
with MIT License
from fengli12321
with MIT License
from fengli12321
/*
* 创建点对点连接
*
* */
private PeerConnection createPeerConnection(String connectionId, Peer peer) {
if (factory == null) {
FLLog.i("工厂为空");
return null;
}
if (ICEServers == null) {
ICEServers = new LinkedList<>();
ICEServers.add(defaultSTUNServer("stun:23.21.150.121"));
ICEServers.add(defaultSTUNServer("stun:stun.l.google.com:19302"));
}
PeerConnection peerConnection = factory.createPeerConnection(ICEServers, peerConnectionConstraints(), peer);
return peerConnection;
}
17
Source : VideoChatHelper.java
with MIT License
from fengli12321
with MIT License
from fengli12321
/**
* 为所有连接创建offer
*/
private void createOffers() {
for (Map.Entry<String, Peer> entry : peers.entrySet()) {
Peer peer = entry.getValue();
PeerConnection connection = peer.pc;
FLLog.i("createoffer调用");
connection.createOffer(peer, offerOrAnswerConstraint());
}
}
16
Source : WebRTCWrapper.java
with GNU General Public License v3.0
from snikket-im
with GNU General Public License v3.0
from snikket-im
@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);
}
}
16
Source : MainActivity.java
with Apache License 2.0
from rome753
with Apache License 2.0
from rome753
@Override
public void onIceCandidateReceived(JSONObject data) {
String socketId = data.optString("from");
PeerConnection peerConnection = getOrCreatePeerConnection(socketId);
peerConnection.addIceCandidate(new IceCandidate(data.optString("id"), data.optInt("label"), data.optString("candidate")));
}
16
Source : WebRTCModule.java
with Apache License 2.0
from react-native-webrtc-kit
with Apache License 2.0
from react-native-webrtc-kit
/**
* peerConnectionClose(valueTag: ValueTag)
*/
@ReactMethod
public void peerConnectionClose(@NonNull String valueTag) {
Log.d(getName(), "peerConnectionClose() - valueTag=" + valueTag);
final PeerConnection peerConnection = repository.getPeerConnectionByValueTag(valueTag);
if (peerConnection == null) {
return;
}
repository.removePeerConnectionByValueTag(valueTag);
peerConnection.dispose();
}
16
Source : VideoChatHelper.java
with MIT License
from fengli12321
with MIT License
from fengli12321
/**
* 关闭peerConnection
*
* @param connectionId 连接id
*/
private void closePeerConnection(String connectionId) {
Peer peer = peers.get(connectionId);
PeerConnection connection = peer.pc;
if (connection != null) {
connection.close();
}
peers.remove(connectionId);
callBack.onCloseWithUserId(connectionId);
}
15
Source : WebRTCWrapper.java
with GNU General Public License v3.0
from snikket-im
with GNU General Public License v3.0
from snikket-im
synchronized void close() {
final PeerConnection peerConnection = this.peerConnection;
final CapturerChoice capturerChoice = this.capturerChoice;
final AppRTCAudioManager audioManager = this.appRTCAudioManager;
final EglBase eglBase = this.eglBase;
if (peerConnection != null) {
dispose(peerConnection);
this.peerConnection = null;
}
if (audioManager != null) {
toneManager.setAppRtcAudioManagerHasControl(false);
mainHandler.post(audioManager::stop);
}
this.localVideoTrack = null;
this.remoteVideoTrack = null;
if (capturerChoice != null) {
try {
capturerChoice.cameraVideoCapturer.stopCapture();
} catch (InterruptedException e) {
Log.e(Config.LOGTAG, "unable to stop capturing");
}
}
if (eglBase != null) {
eglBase.release();
this.eglBase = null;
}
}
15
Source : WebRTCModule.java
with Apache License 2.0
from react-native-webrtc-kit
with Apache License 2.0
from react-native-webrtc-kit
/**
* peerConnectionSetConfiguration(valueTag: ValueTag, configuration: RTCConfiguration)
*/
@ReactMethod
public void peerConnectionSetConfiguration(@NonNull ReadableMap configurationJson, @NonNull String valueTag) {
Log.d(getName(), "peerConnectionSetConfiguration()");
final PeerConnection.RTCConfiguration configuration = rtcConfiguration(configurationJson);
final PeerConnection peerConnection = repository.getPeerConnectionByValueTag(valueTag);
if (peerConnection == null)
return;
peerConnection.setConfiguration(configuration);
}
14
Source : WebRTCModule.java
with Apache License 2.0
from react-native-webrtc-kit
with Apache License 2.0
from react-native-webrtc-kit
/**
* peerConnectionSetRemoteDescription(valueTag: ValueTag, sdp: RTCSessionDescription): Promise<void>
*/
@ReactMethod
public void peerConnectionSetRemoteDescription(@NonNull ReadableMap sdpJson, @NonNull String valueTag, @NonNull Promise promise) {
Log.d(getName(), "peerConnectionSetRemoteDescription()");
final PeerConnection peerConnection = repository.getPeerConnectionByValueTag(valueTag);
if (peerConnection == null) {
promise.reject("NotFoundError", "peer connection is not found");
return;
}
final SdpObserver observer = new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
promise.reject("FatalError", "this must not be called");
}
@Override
public void onSetSuccess() {
promise.resolve(null);
}
@Override
public void onCreateFailure(String s) {
promise.reject("FatalError", "this must not be called");
}
@Override
public void onSetFailure(String s) {
promise.reject("SetRemoteDescriptionFailed", s);
}
};
peerConnection.setRemoteDescription(observer, sessionDescription(sdpJson));
}
14
Source : WebRTCModule.java
with Apache License 2.0
from react-native-webrtc-kit
with Apache License 2.0
from react-native-webrtc-kit
/**
* peerConnectionInit(valueTag: ValueTag, configuration: RTCConfiguration, constraints: RTCMediaConstraints)
* TODO: MediaConstraintsがdeprecated扱いになっているがどうするべきか?とりあえず現状はconstraints引数を無視するようにしているが・・・
*/
@ReactMethod
public void peerConnectionInit(@NonNull ReadableMap configurationJson, @Nullable ReadableMap constraintsJson, @NonNull String valueTag) {
Log.d(getName(), "peerConnectionInit() - valueTag=" + valueTag);
final PeerConnection.RTCConfiguration configuration = rtcConfiguration(configurationJson);
final WebRTCPeerConnectionObserver observer = new WebRTCPeerConnectionObserver(reactContext);
final PeerConnection peerConnection = peerConnectionFactory.createPeerConnection(configuration, observer);
if (peerConnection == null) {
throw new IllegalStateException("createPeerConnection failed");
}
final Pair<String, PeerConnection> peerConnectionPair = new Pair<>(valueTag, peerConnection);
// observerもrepositoryに保存するようにして、close時に明示的にかつ確実に破棄するようにしたほうが良いかもしれないが、
// Java側で明示的に破棄してもしなくても結局はlibwebrtcのobserverのラッパーでしかないため、
// libwebrtc側できちんと破棄されればJava側もそのうち適切にGCされて消えることがわかったので、
// ひとまずこの状態で大丈夫
observer.peerConnectionPair = peerConnectionPair;
repository.addPeerConnection(peerConnectionPair);
}
14
Source : PeerConnectionClient.java
with MIT License
from crossle
with MIT License
from crossle
private void createPeerConnectionInternal(EglBase.Context renderEGLContext, BigInteger handleId) {
if (factory == null || isError) {
Log.e(TAG, "Peerconnection factory is not created");
return;
}
Log.d(TAG, "PCConstraints: " + pcConstraints.toString());
Log.d(TAG, "EGLContext: " + renderEGLContext);
factory.setVideoHwAccelerationOptions(renderEGLContext, renderEGLContext);
PeerConnection peerConnection = createPeerConnection(handleId, true);
mediaStream = factory.createLocalMediaStream("ARDAMS");
mediaStream.addTrack(createVideoTrack(videoCapturer));
mediaStream.addTrack(createAudioTrack());
peerConnection.addStream(mediaStream);
findVideoSender(handleId);
}
14
Source : PeerConnectionClient.java
with MIT License
from crossle
with MIT License
from crossle
private void findVideoSender(final BigInteger handleId) {
PeerConnection peerConnection = peerConnectionMap.get(handleId).peerConnection;
for (RtpSender sender : peerConnection.getSenders()) {
if (sender.track() != null) {
String trackType = sender.track().kind();
if (trackType.equals(VIDEO_TRACK_TYPE)) {
Log.d(TAG, "Found video sender.");
localVideoSender = sender;
}
}
}
}
13
Source : PeersManager.java
with Apache License 2.0
from sergiopaniego
with Apache License 2.0
from sergiopaniego
public void createRemotePeerConnection(RemoteParticipant remoteParticipant) {
final List<PeerConnection.IceServer> iceServers = new ArrayList<>();
PeerConnection.IceServer iceServer = new PeerConnection.IceServer("stun:stun.l.google.com:19302");
iceServers.add(iceServer);
MediaConstraints sdpConstraints = new MediaConstraints();
sdpConstraints.mandatory.add(new MediaConstraints.KeyValuePair("offerToReceiveAudio", "true"));
sdpConstraints.mandatory.add(new MediaConstraints.KeyValuePair("offerToReceiveVideo", "true"));
PeerConnection remotePeer = peerConnectionFactory.createPeerConnection(iceServers, sdpConstraints, new CustomPeerConnectionObserver("remotePeerCreation", remoteParticipant) {
@Override
public void onIceCandidate(IceCandidate iceCandidate) {
super.onIceCandidate(iceCandidate);
Map<String, String> iceCandidateParams = new HashMap<>();
iceCandidateParams.put("sdpMid", iceCandidate.sdpMid);
iceCandidateParams.put("sdpMLineIndex", Integer.toString(iceCandidate.sdpMLineIndex));
iceCandidateParams.put("candidate", iceCandidate.sdp);
iceCandidateParams.put("endpointName", getRemoteParticipant().getId());
webSocketAdapter.sendJson(webSocket, "onIceCandidate", iceCandidateParams);
}
@Override
public void onAddStream(MediaStream mediaStream) {
super.onAddStream(mediaStream);
activity.gotRemoteStream(mediaStream, getRemoteParticipant());
}
});
MediaStream mediaStream = peerConnectionFactory.createLocalMediaStream("105");
mediaStream.addTrack(localAudioTrack);
mediaStream.addTrack(localVideoTrack);
remotePeer.addStream(mediaStream);
remoteParticipant.setPeerConnection(remotePeer);
}
See More Examples