package org.deviceconnect.android.deviceplugin.webrtc.core;
import android.content.Context;
import android.content.Intent;
import android.hardware.Camera;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.util.Log;
import org.deviceconnect.android.deviceplugin.webrtc.BuildConfig;
import org.deviceconnect.android.deviceplugin.webrtc.WebRTCApplication;
import org.deviceconnect.android.deviceplugin.webrtc.service.WebRTCService;
import org.deviceconnect.android.deviceplugin.webrtc.setting.SettingUtil;
import org.deviceconnect.android.deviceplugin.webrtc.util.AudioUtils;
import org.deviceconnect.android.deviceplugin.webrtc.util.CameraUtils;
import org.deviceconnect.android.event.Event;
import org.deviceconnect.android.event.EventManager;
import org.deviceconnect.android.profile.VideoChatProfile;
import org.webrtc.EglBase;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class WebRTCController {
/**
* Tag for debugging.
*/
private static final String TAG = "WEBRTC";
/**
* Defined a stun server.
*/
private static final String STUN_SERVER = "stun:stun.skyway.io:3478";
private MySurfaceViewRenderer mLocalRender;
private MySurfaceViewRenderer mRemoteRender;
private AudioTrackExternal mAudioTrackExternal;
private WebRTCApplication mApplication;
private PeerConfig mConfig;
private PeerOption mOption;
private Peer mPeer;
private static final int DEFAULT_WIDTH = 240;
private static final int DEFAULT_HEIGHT = 320;
private static final int DEFAULT_FPS = 30;
/**
* Connection of WebRTC.
*/
private MediaConnection mConnection;
private ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
private String mAddressId;
private boolean mOffer;
private WebRTCController(final WebRTCApplication app, final PeerConfig config, final PeerOption option,
final String addressId, final boolean offer, final WebRTCEventListener listener) {
mApplication = app;
mConfig = config;
mOption = option;
mWebRTCEventListener = listener;
mAddressId = addressId;
mOffer = offer;
mApplication.getPeer(mConfig, new WebRTCApplication.OnGetPeerCallback() {
@Override
public void onGetPeer(final Peer peer) {
if (peer != null) {
mPeer = peer;
if (mWebRTCEventListener != null) {
mWebRTCEventListener.onFoundPeer(WebRTCController.this);
}
if (mAddressId != null) {
if (mOffer) {
answer(mAddressId);
} else {
call(mAddressId);
}
}
} else {
if (mWebRTCEventListener != null) {
mWebRTCEventListener.onNotFoundPeer(WebRTCController.this);
}
}
}
});
}
private void setLocalRender(final MySurfaceViewRenderer localRender) {
mLocalRender = localRender;
}
private void setRemoteRender(final MySurfaceViewRenderer remoteRender) {
mRemoteRender = remoteRender;
}
private void setAudioTrackExternal(final AudioTrackExternal audioTrackExternal) {
mAudioTrackExternal = audioTrackExternal;
}
private void runOnUiThread(final Runnable run) {
mExecutorService.execute(run);
}
public Peer getPeer() {
return mPeer;
}
public String getAddressId() {
return mAddressId;
}
public MySurfaceViewRenderer getLocalRender() {
return mLocalRender;
}
public MySurfaceViewRenderer getRemoteRender() {
return mRemoteRender;
}
public void onPause() {
if (mConnection != null) {
mConnection.stopVideo();
}
}
public void onResume() {
if (mConnection != null) {
mConnection.startVideo();
}
}
/**
* Makes a phone call.
* @param addressId address
*/
public void call(final String addressId) {
if (BuildConfig.DEBUG) {
Log.i(TAG, "@@ answer");
}
if (mPeer == null) {
throw new IllegalStateException("Peer is not set.");
}
runOnUiThread(new Runnable() {
@Override
public void run() {
mConnection = mPeer.call(addressId, mOption, mOnMediaEventListener);
if (mConnection == null) {
if (mWebRTCEventListener != null) {
mWebRTCEventListener.onCallFailed(WebRTCController.this);
}
}
}
});
}
/**
* Takes a phone call.
* @param addressId address
*/
public void answer(final String addressId) {
if (BuildConfig.DEBUG) {
Log.i(TAG, "@@ answer");
}
if (mPeer == null) {
throw new IllegalStateException("Peer is not set.");
}
runOnUiThread(new Runnable() {
@Override
public void run() {
mConnection = mPeer.answer(addressId, mOption, mOnMediaEventListener);
if (mConnection == null) {
if (mWebRTCEventListener != null) {
mWebRTCEventListener.onAnswerFailed(WebRTCController.this);
}
}
}
});
}
/**
* Hang up a call.
*/
public void hangup() {
if (BuildConfig.DEBUG) {
Log.d(TAG, "@@@ VideoChatActivity::hangup");
}
if (mConnection != null) {
mConnection.close();
mConnection = null;
}
mRemoteRender.release();
mLocalRender.release();
EglBase eglBase = mOption.getEglBase();
if (eglBase != null) {
eglBase.release();
}
}
private MediaConnection.OnMediaEventListener mOnMediaEventListener
= new MediaConnection.OnMediaEventListener() {
@Override
public void onAddStream(final MediaStream mediaStream) {
if (BuildConfig.DEBUG) {
Log.i(TAG, "@@@ MediaConnection.onAddStream");
}
sendCallEvent();
runOnUiThread(new Runnable() {
@Override
public void run() {
mediaStream.setVideoRender(mRemoteRender);
}
});
}
@Override
public void onRemoveStream(final MediaStream mediaStream) {
if (BuildConfig.DEBUG) {
Log.i(TAG, "@@@ MediaConnection.onRemoveStream");
}
runOnUiThread(new Runnable() {
@Override
public void run() {
mediaStream.setVideoRender(null);
}
});
}
@Override
public void onClose() {
if (BuildConfig.DEBUG) {
Log.i(TAG, "@@@ MediaConnection.onClose");
}
sendHangupEvent();
}
@Override
public void onError() {
if (BuildConfig.DEBUG) {
Log.i(TAG, "@@@ MediaConnection.onError");
}
if (mWebRTCEventListener != null) {
mWebRTCEventListener.onError(WebRTCController.this);
}
}
};
/**
* Notifies call event.
*/
private void sendCallEvent() {
final String LOCALHOST = "localhost";
List<Event> events = EventManager.INSTANCE.getEventList(
WebRTCService.PLUGIN_ID,
VideoChatProfile.PROFILE_NAME, null, VideoChatProfile.ATTR_ONCALL);
if (events.size() != 0) {
Bundle local = new Bundle();
Bundle remote = new Bundle();
Bundle videoParam = new Bundle();
Bundle audioParam = new Bundle();
String ipAddr = getIPAddress(mApplication.getApplicationContext());
// Set local parameter
String uri = mLocalRender.getUrl();
if (uri == null) {
uri = "";
} else if (uri.contains(LOCALHOST)) {
uri = uri.replaceAll(LOCALHOST, ipAddr);
}
videoParam.putString(VideoChatProfile.PARAM_URI, uri);
String mimeType = mLocalRender.getMimeType();
if (mimeType == null) {
mimeType = "";
}
videoParam.putString(VideoChatProfile.PARAM_MIMETYPE, mimeType);
videoParam.putInt(VideoChatProfile.PARAM_FRAMERATE, DEFAULT_FPS);
videoParam.putInt(VideoChatProfile.PARAM_WIDTH, mLocalRender.getFrameWidth());
videoParam.putInt(VideoChatProfile.PARAM_HEIGHT, mLocalRender.getFrameHeight());
local.putBundle(VideoChatProfile.PARAM_VIDEO, (Bundle) videoParam.clone());
// Set remote parameter
uri = mRemoteRender.getUrl();
if (uri == null) {
uri = "";
} else if (uri.contains(LOCALHOST)) {
uri = uri.replaceAll(LOCALHOST, ipAddr);
}
videoParam.putString(VideoChatProfile.PARAM_URI, uri);
mimeType = mLocalRender.getMimeType();
if (mimeType == null) {
mimeType = "";
}
videoParam.putString(VideoChatProfile.PARAM_MIMETYPE, mimeType);
videoParam.putInt(VideoChatProfile.PARAM_FRAMERATE, 30);
videoParam.putInt(VideoChatProfile.PARAM_WIDTH, mRemoteRender.getFrameWidth());
videoParam.putInt(VideoChatProfile.PARAM_HEIGHT, mRemoteRender.getFrameHeight());
remote.putBundle(VideoChatProfile.PARAM_VIDEO, (Bundle) videoParam.clone());
if (mAudioTrackExternal != null) {
uri = mAudioTrackExternal.getUrl();
if (uri == null) {
uri = "";
} else if (uri.contains(LOCALHOST)) {
uri = uri.replaceAll(LOCALHOST, ipAddr);
}
audioParam.putString(VideoChatProfile.PARAM_URI, uri);
audioParam.putString(VideoChatProfile.PARAM_MIMETYPE, mAudioTrackExternal.getMimeType());
audioParam.putInt(VideoChatProfile.PARAM_SAMPLERATE, mAudioTrackExternal.getSampleRate());
audioParam.putInt(VideoChatProfile.PARAM_CHANNELS, mAudioTrackExternal.getChannels());
audioParam.putInt(VideoChatProfile.PARAM_SAMPLESIZE, mAudioTrackExternal.getSampleSize());
audioParam.putInt(VideoChatProfile.PARAM_BLOCKSIZE, mAudioTrackExternal.getBlockSize());
remote.putBundle(VideoChatProfile.PARAM_AUDIO, (Bundle) audioParam.clone());
}
Bundle[] args = new Bundle[1];
args[0] = new Bundle();
args[0].putString(VideoChatProfile.PARAM_NAME, getAddressId());
args[0].putString(VideoChatProfile.PARAM_ADDRESSID, getAddressId());
args[0].putBundle(VideoChatProfile.PARAM_REMOTE, remote);
args[0].putBundle(VideoChatProfile.PARAM_LOCAL, local);
for (Event e : events) {
Intent event = EventManager.createEventMessage(e);
event.putExtra(VideoChatProfile.PARAM_ONCALL, args);
mApplication.sendBroadcast(event);
}
}
if (mWebRTCEventListener != null) {
mWebRTCEventListener.onConnected(WebRTCController.this);
}
}
/**
* Notifies hang up event.
*/
private void sendHangupEvent() {
List<Event> events = EventManager.INSTANCE.getEventList(
WebRTCService.PLUGIN_ID,
VideoChatProfile.PROFILE_NAME, null, VideoChatProfile.ATTR_ONHANGUP);
if (events.size() != 0) {
Bundle arg = new Bundle();
arg.putString(VideoChatProfile.PARAM_NAME, getAddressId());
arg.putString(VideoChatProfile.PARAM_ADDRESSID, getAddressId());
for (Event e : events) {
Intent event = EventManager.createEventMessage(e);
event.putExtra(VideoChatProfile.PARAM_HANGUP, arg);
mApplication.sendBroadcast(event);
}
}
if (mWebRTCEventListener != null) {
mWebRTCEventListener.onDisconnected(WebRTCController.this);
}
}
private WebRTCEventListener mWebRTCEventListener;
public interface WebRTCEventListener {
void onFoundPeer(WebRTCController controller);
void onNotFoundPeer(WebRTCController controller);
void onCallFailed(WebRTCController controller);
void onAnswerFailed(WebRTCController controller);
void onConnected(WebRTCController controller);
void onDisconnected(WebRTCController controller);
void onError(WebRTCController controller);
}
public static String getIPAddress(final Context context) {
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
int ipAddress = wifiManager.getConnectionInfo().getIpAddress();
return String.format("%d.%d.%d.%d", (ipAddress & 0xff), (ipAddress >> 8 & 0xff),
(ipAddress >> 16 & 0xff), (ipAddress >> 24 & 0xff));
}
public static class Builder {
private int mVideoWidth = DEFAULT_WIDTH;
private int mVideoHeight = DEFAULT_HEIGHT;
private int mVideoFps = DEFAULT_FPS;
private int mVideoFacing = Camera.CameraInfo.CAMERA_FACING_FRONT;
private boolean mIsLandscape = false;
private PeerOption.VideoType mVideoType = PeerOption.VideoType.CAMERA;
private PeerOption.AudioType mAudioType;
private Context mContext;
private EglBase mEglBase;
private String mVideoUri;
private String mAudioUri;
private MySurfaceViewRenderer mLocalRender;
private MySurfaceViewRenderer mRemoteRender;
private AudioTrackExternal mAudioTrackExternal;
private PeerConfig mConfig;
private WebRTCApplication mApplication;
private WebRTCEventListener mWebRTCEventListener;
private String mAddressId;
private boolean mOffer;
private PeerOption.AudioSampleRate mAudioSampleRate = PeerOption.AudioSampleRate.RATE_48000;
private PeerOption.AudioBitDepth mAudioBitDepth = PeerOption.AudioBitDepth.PCM_FLOAT;
private PeerOption.AudioChannel mAudioChannel = PeerOption.AudioChannel.MONAURAL;
public Builder setVideoWidth(final int width) {
mVideoWidth = width;
return this;
}
public Builder setVideoHeight(final int height) {
mVideoHeight = height;
return this;
}
public Builder setVideoFps(final int fps) {
mVideoFps = fps;
return this;
}
public Builder setVideoFacing(final int facing) {
mVideoFacing = facing;
return this;
}
public void setAddressId(final String addressId) {
mAddressId = addressId;
}
public void setOffer(final boolean offer) {
mOffer = offer;
}
public void setAudioSampleRate(final int audioSampleRate) {
mAudioSampleRate = PeerOption.AudioSampleRate.valueOf(audioSampleRate);
}
public void setAudioBitDepth(final String audioBitDepth) {
switch (audioBitDepth) {
case VideoChatProfile.PARAM_PCM_8BIT:
mAudioBitDepth = PeerOption.AudioBitDepth.PCM_8BIT;
break;
case VideoChatProfile.PARAM_PCM_16BIT:
mAudioBitDepth = PeerOption.AudioBitDepth.PCM_16BIT;
break;
case VideoChatProfile.PARAM_PCM_FLOAT:
default:
mAudioBitDepth = PeerOption.AudioBitDepth.PCM_FLOAT;
break;
}
}
public void setAudioChannel(final String audioChannel) {
switch (audioChannel) {
case VideoChatProfile.PARAM_STEREO:
mAudioChannel = PeerOption.AudioChannel.STEREO;
break;
case VideoChatProfile.PARAM_MONAURAL:
default:
mAudioChannel = PeerOption.AudioChannel.MONAURAL;
break;
}
}
public Builder setVideoUri(final String uri) {
mVideoUri = uri;
if ("true".equals(uri)) {
mVideoType = PeerOption.VideoType.CAMERA;
} else if ("false".equals(uri)) {
mVideoType = PeerOption.VideoType.NONE;
} else {
mVideoType = PeerOption.VideoType.EXTERNAL_RESOURCE;
}
return this;
}
public Builder setAudioUri(final String uri) {
mAudioUri = uri;
if ("true".equals(uri)) {
mAudioType = PeerOption.AudioType.MICROPHONE;
} else if ("false".equals(uri)) {
mAudioType = PeerOption.AudioType.NONE;
} else {
mAudioType = PeerOption.AudioType.EXTERNAL_RESOURCE;
}
return this;
}
public Builder setLocalRender(final MySurfaceViewRenderer render) {
mLocalRender = render;
return this;
}
public Builder setRemoteRender(final MySurfaceViewRenderer render) {
mRemoteRender = render;
return this;
}
public Builder setAudioTrackExternal(final AudioTrackExternal audioTrackExternal) {
mAudioTrackExternal = audioTrackExternal;
return this;
}
public Builder setConfig(final PeerConfig config) {
mConfig = config;
return this;
}
public Builder setContext(final Context context) {
mContext = context;
return this;
}
public Builder setApplication(final WebRTCApplication application) {
mApplication = application;
return this;
}
public Builder setEglBase(final EglBase eglBase) {
mEglBase = eglBase;
return this;
}
public Builder setLandscape(final boolean isLandscape) {
mIsLandscape = isLandscape;
return this;
}
public Builder setWebRTCEventListener(final WebRTCEventListener listener) {
mWebRTCEventListener = listener;
return this;
}
public WebRTCController create() {
if (mContext == null) {
throw new IllegalArgumentException("Context is not set.");
}
if (mConfig == null) {
throw new IllegalArgumentException("PeerConfig is not set.");
}
if (mApplication == null) {
throw new IllegalArgumentException("Application is not set.");
}
if (mEglBase == null) {
throw new IllegalArgumentException("EglBase is not set.");
}
if (mLocalRender == null) {
throw new IllegalArgumentException("localRender is not set.");
}
if (mRemoteRender == null) {
throw new IllegalArgumentException("remoteRender is not set.");
}
if (mAddressId == null) {
throw new IllegalArgumentException("addressId is not set.");
}
if (mIsLandscape) {
int tmp = mVideoWidth;
mVideoWidth = mVideoHeight;
mVideoHeight = tmp;
}
switch (mVideoType) {
default:
case CAMERA: {
String cameraText = SettingUtil.getCameraParam(mContext);
CameraUtils.CameraFormat cameraFormat = CameraUtils.textToFormat(cameraText);
if (cameraFormat == null) {
cameraFormat = CameraUtils.getDefaultFormat(mVideoWidth, mVideoHeight);
}
mVideoWidth = cameraFormat.getWidth();
mVideoHeight = cameraFormat.getHeight();
mVideoFps = cameraFormat.getMaxFrameRate();
mVideoFacing = cameraFormat.getFacing();
} break;
case NONE:
case EXTERNAL_RESOURCE:
break;
}
String audioText = SettingUtil.getAudioParam(mContext);
AudioUtils.AudioFormat audioFormat = AudioUtils.textToFormat(audioText);
if (audioFormat == null) {
audioFormat = AudioUtils.getDefaultFormat();
}
if (BuildConfig.DEBUG) {
Log.i(TAG, "mVideoUri: " + mVideoUri);
Log.i(TAG, "mVideoWidth: " + mVideoWidth);
Log.i(TAG, "mVideoHeight: " + mVideoHeight);
Log.i(TAG, "mVideoFps: " + mVideoFps);
Log.i(TAG, "mVideoFacing: " + mVideoFacing);
Log.i(TAG, "mVideoType: " + mVideoType);
Log.i(TAG, "mAudioUri: " + mAudioUri);
Log.i(TAG, "mAudioSampleRate: " + mAudioSampleRate);
Log.i(TAG, "mAudioBitDepth: " + mAudioBitDepth);
Log.i(TAG, "mAudioChannel: " + mAudioChannel);
}
PeerOption option = new PeerOption();
option.setVideoWidth(mVideoWidth);
option.setVideoHeight(mVideoHeight);
option.setVideoFps(mVideoFps);
option.setVideoFacing(mVideoFacing);
option.setVideoType(mVideoType);
option.setVideoUri(mVideoUri);
option.setVideoRender(mLocalRender);
option.setAudioType(mAudioType);
option.setAudioUri(mAudioUri);
option.setNoAudioProcessing(audioFormat.isNoAudioProcessing());
option.addIceServer(STUN_SERVER);
option.setContext(mContext);
option.setEglBase(mEglBase);
option.setAudioSampleRate(mAudioSampleRate);
option.setAudioBitDepth(mAudioBitDepth);
option.setAudioChannel(mAudioChannel);
WebRTCController ctl = new WebRTCController(mApplication, mConfig, option,
mAddressId, mOffer, mWebRTCEventListener);
ctl.setLocalRender(mLocalRender);
ctl.setRemoteRender(mRemoteRender);
ctl.setAudioTrackExternal(mAudioTrackExternal);
return ctl;
}
}
}