com.infthink.demo.webrtc.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.infthink.demo.webrtc.MainActivity.java

Source

/**
 * Copyright (C) 2013-2015, Infthink (Beijing) Technology Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package com.infthink.demo.webrtc;

import java.io.IOException;

import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.IceCandidate;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.SessionDescription;
import org.webrtc.VideoRenderer;
import org.webrtc.VideoRendererGui;
import org.webrtc.VideoRendererGui.ScalingType;

import tv.matchstick.flint.ApplicationMetadata;
import tv.matchstick.flint.ConnectionResult;
import tv.matchstick.flint.Flint;
import tv.matchstick.flint.Flint.ApplicationConnectionResult;
import tv.matchstick.flint.FlintDevice;
import tv.matchstick.flint.FlintManager;
import tv.matchstick.flint.FlintMediaControlIntent;
import tv.matchstick.flint.ResultCallback;
import tv.matchstick.flint.Status;
import android.app.Fragment;
import android.content.pm.ActivityInfo;
import android.graphics.Color;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v7.app.MediaRouteButton;
import android.support.v7.media.MediaRouteSelector;
import android.support.v7.media.MediaRouter;
import android.support.v7.media.MediaRouter.RouteInfo;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;

import com.infthink.demo.webrtc.WebRtcHelper.SignalingParameters;

public class MainActivity extends FragmentActivity
        implements WebRtcHelper.SignalingEvents, PeerConnectionClient.PeerConnectionEvents {

    private static final String TAG = "flint_webrtc";

    private static final String APP_URL = "http://openflint.github.io/android-webrtc-demo/receiver/index.html";

    private PeerConnectionClient mPeerConn;

    private boolean mIceConnected;

    private boolean mStreamAdded = false;

    private View mRootView;
    private TextView mEncoderStatView;
    private View mMenuBar;
    private TextView mRoomName;
    private GLSurfaceView mVideoView;
    private VideoRenderer.Callbacks mLocalRender;
    private VideoRenderer.Callbacks mRemoteRender;
    private ImageButton mVideoScalingButton;

    private TextView mHudView;
    private final LayoutParams mHudLayout = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);

    private ScalingType mScalingType;

    private Toast mLogToast;

    private AppRTCAudioManager mAudioManager = null;

    private WebRtcHelper mWebrtcHelper;

    private SignalingParameters mSignalingParameters;

    private int mStartBitrate = 0;

    private FlintDevice mSelectedDevice;
    private FlintManager mApiClient;
    private Flint.Listener mFlingListener;
    private ConnectionCallbacks mConnectionCallbacks;
    private MediaRouter mMediaRouter;
    private MediaRouteSelector mMediaRouteSelector;
    private MediaRouter.Callback mMediaRouterCallback;
    private WebrtcChannel mWebrtcChannel;
    private MediaRouteButton mMediaRouteButton;
    private int mRouteCount = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);

        setContentView(R.layout.activity_fullscreen);

        // init flint related
        String APPLICATION_ID = "~flint_android_webrtc_demo";
        Flint.FlintApi.setApplicationId(APPLICATION_ID);

        mWebrtcChannel = new MyWebrtcChannel();

        mMediaRouter = MediaRouter.getInstance(getApplicationContext());
        mMediaRouteSelector = new MediaRouteSelector.Builder()
                .addControlCategory(FlintMediaControlIntent.categoryForFlint(APPLICATION_ID)).build();

        mMediaRouterCallback = new MediaRouterCallback();
        mFlingListener = new FlingListener();
        mConnectionCallbacks = new ConnectionCallbacks();

        mIceConnected = false;

        // init views
        mRootView = findViewById(android.R.id.content);
        mEncoderStatView = (TextView) findViewById(R.id.encoder_stat);
        mMenuBar = findViewById(R.id.menubar_fragment);
        mRoomName = (TextView) findViewById(R.id.room_name);
        mVideoView = (GLSurfaceView) findViewById(R.id.glview);

        mMediaRouteButton = (MediaRouteButton) mMenuBar.findViewById(R.id.media_route_button);
        mMediaRouteButton.setRouteSelector(mMediaRouteSelector);

        VideoRendererGui.setView(mVideoView);

        mScalingType = ScalingType.SCALE_ASPECT_FILL;
        mRemoteRender = VideoRendererGui.create(0, 0, 100, 100, mScalingType, false);
        mLocalRender = VideoRendererGui.create(0, 0, 100, 100, mScalingType, true);

        mVideoView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                int visibility = mMenuBar.getVisibility() == View.VISIBLE ? View.INVISIBLE : View.VISIBLE;
                mEncoderStatView.setVisibility(visibility);
                mMenuBar.setVisibility(visibility);
                mRoomName.setVisibility(visibility);
                if (visibility == View.VISIBLE) {
                    mEncoderStatView.bringToFront();
                    mMenuBar.bringToFront();
                    mRoomName.bringToFront();
                    mRootView.invalidate();
                }
            }
        });

        ((ImageButton) findViewById(R.id.button_disconnect)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                logAndToast("Disconnecting call.");
                disconnect();
            }
        });

        ((ImageButton) findViewById(R.id.button_switch_camera)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mPeerConn != null) {
                    mPeerConn.switchCamera();
                }
            }
        });

        ((ImageButton) findViewById(R.id.button_toggle_debug)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                int visibility = mHudView.getVisibility() == View.VISIBLE ? View.INVISIBLE : View.VISIBLE;
                mHudView.setVisibility(visibility);

                // use this to send view switch
                if (mApiClient != null && mApiClient.isConnected()) {
                    mWebrtcChannel.sendSwitchView(mApiClient);
                }
            }
        });

        mVideoScalingButton = (ImageButton) findViewById(R.id.button_scaling_mode);
        mVideoScalingButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mScalingType == ScalingType.SCALE_ASPECT_FILL) {
                    mVideoScalingButton.setBackgroundResource(R.drawable.ic_action_full_screen);
                    mScalingType = ScalingType.SCALE_ASPECT_FIT;
                } else {
                    mVideoScalingButton.setBackgroundResource(R.drawable.ic_action_return_from_full_screen);
                    mScalingType = ScalingType.SCALE_ASPECT_FILL;
                }
                updateVideoView();
            }
        });

        mHudView = new TextView(this);
        mHudView.setTextColor(Color.BLACK);
        mHudView.setBackgroundColor(Color.WHITE);
        mHudView.setAlpha(0.4f);
        mHudView.setTextSize(TypedValue.COMPLEX_UNIT_PT, 5);
        mHudView.setVisibility(View.INVISIBLE);
        addContentView(mHudView, mHudLayout);

        // Create and audio manager that will take care of audio routing,
        // audio modes, audio device enumeration etc.
        mAudioManager = AppRTCAudioManager.create(this);

        // ready to init webrtc params
        mWebrtcHelper = new WebRtcHelper(this);
        mWebrtcHelper.initParams();
    }

    @Override
    protected void onStart() {
        super.onStart();
        mMediaRouter.addCallback(mMediaRouteSelector, mMediaRouterCallback,
                MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
    }

    @Override
    protected void onResume() {
        super.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
    }

    @Override
    protected void onStop() {
        setSelectedDevice(null);
        mMediaRouter.removeCallback(mMediaRouterCallback);
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        disconnect();
        super.onDestroy();
    }

    /**
     * Called when the options menu is first created.
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.action_stop:
            stopApplication();
            break;
        }

        return true;
    }

    public static class MenuBarFragment extends Fragment {
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            return inflater.inflate(R.layout.fragment_menubar, container, false);
        }
    }

    @Override
    public void onParamInitDone(SignalingParameters params) {

        // TODO Auto-generated method stub
        logAndToast("onInitDone...");

        if (mAudioManager != null) {
            // Store existing audio settings and change audio mode to
            // MODE_IN_COMMUNICATION for best possible VoIP performance.
            logAndToast("Initializing the audio manager...");
            mAudioManager.init();
        }
        mSignalingParameters = params;
        abortUnless(PeerConnectionFactory.initializeAndroidGlobals(this, true, true, true,
                VideoRendererGui.getEGLContext()), "Failed to initializeAndroidGlobals");
        logAndToast("Creating peer connection...");
        if (mPeerConn != null) {
            mPeerConn.close();
            mPeerConn = null;
        }

        mPeerConn = new PeerConnectionClient(this, mLocalRender, mRemoteRender, mSignalingParameters, this,
                mStartBitrate);
        /*
         * if (mPeerConn.isHDVideo()) {
         * setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); }
         * else {
         * setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
         * }
         */
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
        if (mApiClient != null && mApiClient.isConnected()) {
            mPeerConn.createOffer();
        }
        // if (mApiClient != null && mApiClient.isConnected()) {
        // mWebrtcChannel.sendHello(mApiClient);
        // }
    }

    @Override
    public void onLocalDescription(SessionDescription sdp) {
        // TODO Auto-generated method stub
        logAndToast("onLocalDescription...");

        if (mWebrtcHelper != null) {
            logAndToast("Sending " + sdp.type + " ...");
            if (mSignalingParameters.initiator) {
                mWebrtcChannel.sendOfferSdp(mApiClient, sdp);
            } else {
                mWebrtcChannel.sendAnswerSdp(mApiClient, sdp);
            }
        }
    }

    @Override
    public void onIceCandidate(IceCandidate candidate) {
        // TODO Auto-generated method stub
        logAndToast("onIceCandidate...");

        if (mWebrtcChannel != null) {
            mWebrtcChannel.sendLocalIceCandidate(mApiClient, candidate);
        }
    }

    @Override
    public void onIceConnected() {
        // TODO Auto-generated method stub
        logAndToast("onIceConnected...");

        // logAndToast("ICE connected");
        mIceConnected = true;
        updateVideoView();
    }

    @Override
    public void onIceDisconnected() {
        // TODO Auto-generated method stub
        logAndToast("onIceDisconnected...");

        mIceConnected = false;
        mStreamAdded = false;
        updateVideoView();
    }

    @Override
    public void onAddStream(MediaStream stream) {
        // TODO Auto-generated method stub
        logAndToast("onAddStream...");
        mStreamAdded = true;
    }

    @Override
    public void onPeerConnectionError(String description) {
        // TODO Auto-generated method stub
        logAndToast("onPeerConnectionError...");
    }

    private class MyWebrtcChannel extends WebrtcChannel {
        public void onMessageReceived(FlintDevice flingDevice, String namespace, String message) {
            Log.e(TAG, "WebrtcChannel: message received:" + message);
            try {
                JSONObject json = new JSONObject(message);
                String type = (String) json.get("type");
                if (type.equals("candidate")) {
                    IceCandidate candidate = new IceCandidate((String) json.get("sdpMid"),
                            json.getInt("sdpMLineIndex"), (String) json.get("candidate"));
                    Log.e(TAG,
                            "onMessageReceived:type[" + type + "]sdpMid[" + (String) json.get("sdpMid")
                                    + "]sdpMLineIndex[" + json.getInt("sdpMLineIndex") + "]candidate["
                                    + (String) json.get("candidate") + "]");
                    onRemoteIceCandidate(candidate);
                } else if (type.equals("answer") || type.equals("offer")) {
                    SessionDescription sdp = new SessionDescription(SessionDescription.Type.fromCanonicalForm(type),
                            (String) json.get("sdp"));
                    Log.e(TAG, "onMessageReceived:type[" + type + "]sdp[" + (String) json.get("sdp") + "]");
                    onRemoteDescription(sdp);
                } else if (type.equals("bye")) {
                    String data = (String) json.get("data");
                    onChannelClose(data);
                } else {
                    onChannelError("Unexpected channel message: " + message);
                }
            } catch (JSONException e) {
                onChannelError("Channel message JSON parsing error: " + e.toString());
            }
        }
    }

    private class MediaRouterCallback extends MediaRouter.Callback {
        @Override
        public void onRouteAdded(MediaRouter router, RouteInfo route) {
            Log.d(TAG, "onRouteAdded");
            if (++mRouteCount == 1) {
                // Show the button when a device is discovered.
                mMediaRouteButton.setVisibility(View.VISIBLE);
            }
        }

        @Override
        public void onRouteRemoved(MediaRouter router, RouteInfo route) {
            Log.d(TAG, "onRouteRemoved");
            if (--mRouteCount == 0) {
                // Hide the button if there are no devices discovered.
                mMediaRouteButton.setVisibility(View.GONE);
            }
        }

        @Override
        public void onRouteSelected(MediaRouter router, RouteInfo route) {
            Log.d(TAG, "onRouteSelected: " + route);
            MainActivity.this.onRouteSelected(route);
        }

        @Override
        public void onRouteUnselected(MediaRouter router, RouteInfo route) {
            Log.d(TAG, "onRouteUnselected: " + route);
            MainActivity.this.onRouteUnselected(route);
        }
    }

    private class FlingListener extends Flint.Listener {
        @Override
        public void onApplicationDisconnected(int statusCode) {
            Log.d(TAG, "Flint.Listener.onApplicationDisconnected: " + statusCode);

            mSelectedDevice = null;
            mMediaRouter.selectRoute(mMediaRouter.getDefaultRoute());

            if (mApiClient == null) {
                return;
            }

            try {
                Flint.FlintApi.removeMessageReceivedCallbacks(mApiClient, mWebrtcChannel.getNamespace());
            } catch (IOException e) {
                Log.w(TAG, "Exception while launching application", e);
            }
        }
    }

    /**
     * Called when a user selects a route.
     */
    private void onRouteSelected(RouteInfo route) {
        Log.d(TAG, "onRouteSelected: " + route.getName());

        FlintDevice device = FlintDevice.getFromBundle(route.getExtras());
        setSelectedDevice(device);
    }

    /**
     * Called when a user unselects a route.
     */
    private void onRouteUnselected(RouteInfo route) {
        if (route != null) {
            Log.d(TAG, "onRouteUnselected: " + route.getName());
        }
        setSelectedDevice(null);
    }

    /**
     * Stop receiver application.
     */
    public void stopApplication() {
        if (mApiClient == null || !mApiClient.isConnected()) {
            return;
        }

        Flint.FlintApi.stopApplication(mApiClient).setResultCallback(new ResultCallback<Status>() {
            @Override
            public void onResult(Status result) {
                if (result.isSuccess()) {
                    //
                }
            }
        });
    }

    private void setSelectedDevice(FlintDevice device) {
        Log.d(TAG, "setSelectedDevice: " + device);
        mSelectedDevice = device;

        if (mSelectedDevice != null) {
            try {
                disconnectApiClient();
                connectApiClient();
            } catch (IllegalStateException e) {
                Log.w(TAG, "Exception while connecting API client", e);
                disconnectApiClient();
            }
        } else {
            if (mApiClient != null) {
                if (mApiClient.isConnected()) {
                    mWebrtcChannel.sendBye(mApiClient);
                }

                // stopApplication();

                disconnectApiClient();
            }

            mMediaRouter.selectRoute(mMediaRouter.getDefaultRoute());
        }
    }

    private void connectApiClient() {
        Flint.FlintOptions apiOptions = Flint.FlintOptions.builder(mSelectedDevice, mFlingListener).build();
        mApiClient = new FlintManager.Builder(this).addApi(Flint.API, apiOptions)
                .addConnectionCallbacks(mConnectionCallbacks).build();
        mApiClient.connect();
    }

    private void disconnectApiClient() {
        if (mApiClient != null) {
            mApiClient.disconnect();
            mApiClient = null;
        }
    }

    private class ConnectionCallbacks implements FlintManager.ConnectionCallbacks {
        @Override
        public void onConnectionSuspended(int cause) {
            Log.d(TAG, "ConnectionCallbacks.onConnectionSuspended");
        }

        @Override
        public void onConnected(Bundle connectionHint) {
            Log.d(TAG, "ConnectionCallbacks.onConnected");
            Flint.FlintApi.launchApplication(mApiClient, APP_URL)
                    .setResultCallback(new ApplicationConnectionResultCallback());
        }

        @Override
        public void onConnectionFailed(ConnectionResult result) {
            Log.d(TAG, "ConnectionFailedListener.onConnectionFailed");
            setSelectedDevice(null);
        }
    }

    private final class ApplicationConnectionResultCallback implements ResultCallback<ApplicationConnectionResult> {
        @Override
        public void onResult(ApplicationConnectionResult result) {
            Status status = result.getStatus();
            ApplicationMetadata appMetaData = result.getApplicationMetadata();

            if (status.isSuccess()) {
                Log.d(TAG, "ConnectionResultCallback: " + appMetaData.getData());
                try {
                    Flint.FlintApi.setMessageReceivedCallbacks(mApiClient, mWebrtcChannel.getNamespace(),
                            mWebrtcChannel);

                    mWebrtcHelper.initParams(); // start another connection?
                } catch (IOException e) {
                    Log.w(TAG, "Exception while launching application", e);
                }
            } else {
                Log.d(TAG, "ConnectionResultCallback. Unable to launch the game. statusCode: "
                        + status.getStatusCode());
            }
        }
    }

    private void logAndToast(String msg) {
        Log.e(TAG, msg);
        if (mLogToast != null) {
            mLogToast.cancel();
        }
        mLogToast = Toast.makeText(this, msg, Toast.LENGTH_SHORT);
        mLogToast.show();
    }

    private void disconnect() {
        if (mWebrtcHelper != null) {
            mWebrtcHelper.disconnect();
            mWebrtcHelper = null;
        }
        if (mPeerConn != null) {
            mPeerConn.close();
            mPeerConn = null;
        }
        if (mAudioManager != null) {
            mAudioManager.close();
            mAudioManager = null;
        }
        finish();
    }

    private void updateVideoView() {
        VideoRendererGui.update(mRemoteRender, 0, 0, 100, 100, mScalingType);
        if (mIceConnected && mStreamAdded) {
            VideoRendererGui.update(mLocalRender, 70, 70, 28, 28, ScalingType.SCALE_ASPECT_FIT);
        } else {
            VideoRendererGui.update(mLocalRender, 0, 0, 100, 100, mScalingType);
        }
    }

    private void onRemoteDescription(SessionDescription sdp) {
        // TODO Auto-generated method stub
        logAndToast("onRemoteDescription... " + sdp);

        if (mPeerConn == null) {
            Log.e(TAG, "onRemoteDescription: peer is null? ignore!");
            return;
        }
        logAndToast("Received remote " + sdp.type + " ...");
        mPeerConn.setRemoteDescription(sdp);
        if (!mSignalingParameters.initiator) {
            logAndToast("Creating ANSWER...");
            // Create answer. Answer SDP will be sent to offering client in
            // PeerConnectionEvents.onLocalDescription event.
            mPeerConn.createAnswer();
        }
    }

    private void onRemoteIceCandidate(IceCandidate candidate) {
        // TODO Auto-generated method stub

        logAndToast("onRemoteIceCandidate: " + candidate + " ...");
        if (mPeerConn != null) {
            mPeerConn.addRemoteIceCandidate(candidate);
        }
    }

    private void onChannelClose(String description) {
        // TODO Auto-generated method stub
        logAndToast("onChannelClose...");

        mIceConnected = false;
        mStreamAdded = false;
        updateVideoView();
    }

    private void onChannelError(String description) {
        // TODO Auto-generated method stub
        logAndToast("onChannelError...: " + description);

        MainActivity.this.onRouteUnselected(null);
    }

    // Poor-man's assert(): die with |msg| unless |condition| is true.
    private static void abortUnless(boolean condition, String msg) {
        if (!condition) {
            throw new RuntimeException(msg);
        }
    }

}