1. 程式人生 > >Android 匯入EaseUI後實現音視訊功能

Android 匯入EaseUI後實現音視訊功能

專案中使用EaseUI可以快速的實現簡單的聊天功能,但是由於EaseUI中沒有實現音視訊功能,因此如果需要實現音視訊功能,需要自己手動實現,不過由於EaseUI實現了大部分的功能,所以,只需要在此基礎上修改即可

1.首先需要音視訊的的介面
VideoCall佈局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:animateLayoutChanges="true" >
<com.hyphenate.media.EMOppositeSurfaceView android:id="@+id/opposite_surface" android:layout_width="match_parent" android:layout_height
="match_parent" />
<RelativeLayout android:id="@+id/ll_btns" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingLeft="20dp" android:paddingRight="20dp" > <LinearLayout
android:id="@+id/ll_top_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_marginTop="5dp" android:gravity="center_horizontal" android:orientation="vertical" >
<TextView android:id="@+id/tv_call_state" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="12dp" android:paddingRight="12dp" android:textColor="@color/voip_interface_text_color" android:textSize="22sp" android:visibility="visible" /> <com.hyphenate.easeui.widget.MyChronometer android:id="@+id/chronometer" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Chronometer" android:textColor="#fff" android:visibility="invisible" tools:ignore="HardcodedText" /> <TextView android:id="@+id/tv_is_p2p" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#fff" /> <TextView android:id="@+id/tv_nick" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:text="張三" android:textColor="@android:color/white" android:textSize="20sp" tools:ignore="HardcodedText" /> </LinearLayout> <!-- 演示視訊錄製功能 --> <Button android:id="@+id/btn_record_video" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/recording_video" android:layout_below="@id/ll_top_container" android:visibility="gone" /> <Button android:id="@+id/btn_switch_camera" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/switch_camera" android:layout_below="@id/btn_record_video" /> <Button android:id="@+id/btn_capture_image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/capture_image" android:layout_below="@id/btn_switch_camera" /> <SeekBar android:id="@+id/seekbar_y_detal" android:layout_width="200dp" android:layout_height="wrap_content" android:layout_below="@id/btn_capture_image" android:max="200" android:progress="100" /> <!-- <Button android:layout_marginTop="3dp" android:id="@+id/btn_toggle_video_stream" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="暫停視訊" android:layout_below="@id/btn_record_video" /> --> <TextView android:id="@+id/tv_call_monitor" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@+id/local_surface" android:layout_alignParentRight="true" android:textColor="#afff" android:textSize="12sp" android:layout_marginBottom="6dp" /> <com.hyphenate.media.EMLocalSurfaceView android:id="@+id/local_surface" android:layout_width="100dp" android:layout_height="120dp" android:layout_marginTop="10dp" android:layout_alignParentRight="true" /> <!--android:layout_above="@+id/ll_surface_baseline"--> <LinearLayout android:id="@+id/ll_surface_baseline" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:orientation="vertical" android:paddingTop="8dp" > <View android:layout_width="match_parent" android:layout_height="1px" /> <LinearLayout android:id="@+id/ll_bottom_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:paddingBottom="18dp" > <LinearLayout android:id="@+id/ll_voice_control" android:layout_width="fill_parent" android:layout_height="wrap_content" tools:ignore="DisableBaselineAlignment"> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center" android:orientation="vertical" > <ImageView android:id="@+id/iv_mute" android:layout_width="match_parent" android:layout_height="wrap_content" android:scaleType="fitCenter" android:src="@drawable/em_icon_mute_normal" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="3dp" android:gravity="center" android:text="@string/mute" android:textColor="#666167" /> </LinearLayout> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center" android:orientation="vertical" > <ImageView android:id="@+id/iv_handsfree" android:layout_width="match_parent" android:layout_height="wrap_content" android:scaleType="fitCenter" android:src="@drawable/em_icon_speaker_normal" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="3dp" android:gravity="center" android:text="@string/Hands_free" android:textColor="#666167" /> </LinearLayout> </LinearLayout> <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" > <Button android:id="@+id/btn_hangup_call" android:layout_width="fill_parent" android:layout_height="55dp" android:background="@drawable/em_call_hangup_bg" android:gravity="center" android:text="@string/hang_up" android:textColor="@android:color/white" android:textSize="20sp" android:visibility="invisible" /> <LinearLayout android:id="@+id/ll_coming_call" android:layout_width="match_parent" android:layout_height="wrap_content" > <Button android:id="@+id/btn_refuse_call" android:layout_width="wrap_content" android:layout_height="50dp" android:layout_weight="1" android:background="@drawable/em_call_hangup_bg" android:gravity="center" android:text="@string/hang_up" android:textColor="@android:color/white" android:textSize="20sp" /> <Button android:id="@+id/btn_answer_call" android:layout_width="wrap_content" android:layout_height="50dp" android:layout_marginLeft="50dp" android:layout_weight="1" android:background="@drawable/em_call_answer_bg" android:gravity="center" android:text="@string/answer" android:textColor="@android:color/white" android:textSize="20sp" /> </LinearLayout> </FrameLayout> </LinearLayout> </LinearLayout> </RelativeLayout> <TextView android:id="@+id/tv_network_status" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:textColor="@android:color/white" android:layout_centerInParent="true" /> </RelativeLayout>

VioceCall佈局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#252C39"
    android:orientation="vertical"
    android:id="@+id/root_layout"
    android:paddingLeft="20dp"
    android:paddingRight="20dp" >

   <LinearLayout
        android:id="@+id/topLayout"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginTop="5dp"
        android:layout_weight="1"
        android:gravity="center_horizontal"
        android:orientation="vertical" >

        <TextView
            android:id="@+id/tv_call_state"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/voip_interface_text_color"
            android:textSize="22sp"
            android:paddingLeft="12dp"
            android:paddingRight="12dp"
            android:visibility="visible" />

        <com.hyphenate.easeui.widget.MyChronometer
            android:visibility="invisible"
            android:id="@+id/chronometer"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#fff"
            android:text="Chronometer" />

        <TextView 
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/tv_is_p2p"
            android:textColor="#fff"
            />

        <TextView
            android:id="@+id/tv_calling_duration"
            android:layout_width="wrap_content"
            android:layout_height="25dp"
            android:textColor="@color/voip_interface_text_color"
            android:textSize="15sp"
            android:visibility="visible" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:gravity="center_horizontal"
        android:orientation="vertical"
        android:layout_weight="2" >

        <ImageView
            android:id="@+id/swing_card"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:scaleType="fitXY"
            android:layout_marginTop="10dp"
            android:src="@drawable/em_default_avatar" />

        <TextView
            android:id="@+id/tv_nick"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
              android:textColor="@android:color/white"
              android:textSize="20sp"
              android:text="張三"
            tools:ignore="HardcodedText" />
        <TextView
            android:id="@+id/tv_network_status"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:textColor="@android:color/white" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_weight="2"
        android:orientation="vertical" >

        <LinearLayout
            android:id="@+id/ll_voice_control"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            tools:ignore="DisableBaselineAlignment">

            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:gravity="center"
                android:orientation="vertical" >

                <ImageView
                    android:id="@+id/iv_mute"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:scaleType="fitCenter"
                    android:src="@drawable/em_icon_mute_normal" />

                <TextView
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="3dp"
                    android:gravity="center"
                    android:textColor="#666167"
                    android:text="@string/mute" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:gravity="center"
                android:orientation="vertical" >

                <ImageView
                    android:id="@+id/iv_handsfree"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:scaleType="fitCenter"
                    android:src="@drawable/em_icon_speaker_normal" />

                <TextView
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="3dp"
                    android:gravity="center"
                    android:textColor="#666167"
                    android:text="@string/Hands_free" />
            </LinearLayout>
        </LinearLayout>

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp" >

            <Button
                android:id="@+id/btn_hangup_call"
                android:layout_width="fill_parent"
                android:layout_height="60dp"
                android:background="@drawable/em_call_hangup_bg"
                android:gravity="center"
                android:text="@string/hang_up"
                android:textColor="@android:color/white"
                android:textSize="20sp"
                android:visibility="invisible" />

            <LinearLayout
                android:id="@+id/ll_coming_call"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" >

                <Button
                    android:id="@+id/btn_refuse_call"
                    android:layout_width="wrap_content"
                    android:layout_height="60dp"
                    android:layout_weight="1"
                    android:background="@drawable/em_call_hangup_bg"
                    android:gravity="center"
                    android:text="@string/hang_up"
                    android:textColor="@android:color/white"
                    android:textSize="20sp" />

                <Button
                    android:id="@+id/btn_answer_call"
                    android:layout_width="wrap_content"
                    android:layout_height="60dp"
                    android:layout_marginLeft="30dp"
                    android:layout_weight="1"
                    android:background="@drawable/em_call_answer_bg"
                    android:gravity="center"
                    android:text="@string/answer"
                    android:textColor="@android:color/white"
                    android:textSize="20sp" />
            </LinearLayout>
        </FrameLayout>
    </LinearLayout>

</LinearLayout>

視訊介面VideoCallActivity:

public class VideoCallActivity extends CallActivity implements OnClickListener {

    private boolean isMuteState;
    private boolean isHandsfreeState;
    private boolean isAnswered;
    private boolean endCallTriggerByMe = false;
    private boolean monitor = true;

    private TextView callStateTextView;

    private LinearLayout comingBtnContainer;
    private Button refuseBtn;
    private Button answerBtn;
    private Button hangupBtn;
    private ImageView muteImage;
    private ImageView handsFreeImage;
    private TextView nickTextView;
    private Chronometer chronometer;
    private LinearLayout voiceContronlLayout;
    private RelativeLayout rootContainer;
    private LinearLayout topContainer;
    private LinearLayout bottomContainer;
    private TextView monitorTextView;
    private TextView netwrokStatusVeiw;

    private Handler uiHandler;

    private boolean isInCalling;
    boolean isRecording = false;
//    private Button recordBtn;
    private EMVideoCallHelper callHelper;
    private Button toggleVideoBtn;

    private BrightnessDataProcess dataProcessor = new BrightnessDataProcess();



    // dynamic adjust brightness
    class BrightnessDataProcess implements EMCameraDataProcessor {
        byte yDelta = 0;
        synchronized void setYDelta(byte yDelta) {
            Log.d("VideoCallActivity", "brigntness uDelta:" + yDelta);
            this.yDelta = yDelta;
        }

        // data size is width*height*2
        // the first width*height is Y, second part is UV
        // the storage layout detailed please refer 2.x demo CameraHelper.onPreviewFrame
        @Override
        public synchronized void onProcessData(byte[] data, Camera camera, final int width, final int height, final int rotateAngel) {
            int wh = width * height;
            for (int i = 0; i < wh; i++) {
                int d = (data[i] & 0xFF) + yDelta;
                d = d < 16 ? 16 : d;
                d = d > 235 ? 235 : d;
                data[i] = (byte)d;
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if(savedInstanceState != null){
            finish();
            return;
        }
        setContentView(R.layout.em_activity_video_call);
        DemoHelper.getInstance().isVideoCalling = true;
        callType = 1;

        getWindow().addFlags(
                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                        | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);

        uiHandler = new Handler();

        callStateTextView = (TextView) findViewById(R.id.tv_call_state);
        comingBtnContainer = (LinearLayout) findViewById(R.id.ll_coming_call);
        rootContainer = (RelativeLayout) findViewById(R.id.root_layout);
        refuseBtn = (Button) findViewById(R.id.btn_refuse_call);
        answerBtn = (Button) findViewById(R.id.btn_answer_call);
        hangupBtn = (Button) findViewById(R.id.btn_hangup_call);
        muteImage = (ImageView) findViewById(R.id.iv_mute);
        handsFreeImage = (ImageView) findViewById(R.id.iv_handsfree);
        callStateTextView = (TextView) findViewById(R.id.tv_call_state);
        nickTextView = (TextView) findViewById(R.id.tv_nick);
        chronometer = (Chronometer) findViewById(R.id.chronometer);
        voiceContronlLayout = (LinearLayout) findViewById(R.id.ll_voice_control);
        RelativeLayout btnsContainer = (RelativeLayout) findViewById(R.id.ll_btns);
        topContainer = (LinearLayout) findViewById(R.id.ll_top_container);
        bottomContainer = (LinearLayout) findViewById(R.id.ll_bottom_container);
        monitorTextView = (TextView) findViewById(R.id.tv_call_monitor);
        netwrokStatusVeiw = (TextView) findViewById(R.id.tv_network_status);
//        recordBtn = (Button) findViewById(R.id.btn_record_video);
        Button switchCameraBtn = (Button) findViewById(R.id.btn_switch_camera);
        Button captureImageBtn = (Button) findViewById(R.id.btn_capture_image);
        SeekBar YDeltaSeekBar = (SeekBar) findViewById(R.id.seekbar_y_detal);

        refuseBtn.setOnClickListener(this);
        answerBtn.setOnClickListener(this);
        hangupBtn.setOnClickListener(this);
        muteImage.setOnClickListener(this);
        handsFreeImage.setOnClickListener(this);
        rootContainer.setOnClickListener(this);
//        recordBtn.setOnClickListener(this);
        switchCameraBtn.setOnClickListener(this);
        captureImageBtn.setOnClickListener(this);

        YDeltaSeekBar.setOnSeekBarChangeListener(new YDeltaSeekBarListener());

        msgid = UUID.randomUUID().toString();
        isInComingCall = getIntent().getBooleanExtra("isComingCall", false);
        username = getIntent().getStringExtra("username");

        nickTextView.setText(username);

        // local surfaceview
        localSurface = (EMLocalSurfaceView) findViewById(R.id.local_surface);
        localSurface.setZOrderMediaOverlay(true);
        localSurface.setZOrderOnTop(true);

        // remote surfaceview
        oppositeSurface = (EMOppositeSurfaceView) findViewById(R.id.opposite_surface);

        // set call state listener
        addCallStateListener();
        if (!isInComingCall) {// outgoing call
            soundPool = new SoundPool(1, AudioManager.STREAM_RING, 0);
            outgoing = soundPool.load(this, R.raw.em_outgoing, 1);

            comingBtnContainer.setVisibility(View.INVISIBLE);
            hangupBtn.setVisibility(View.VISIBLE);
            String st = getResources().getString(R.string.Are_connected_to_each_other);
            callStateTextView.setText(st);
            EMClient.getInstance().callManager().setSurfaceView(localSurface, oppositeSurface);
            handler.sendEmptyMessage(MSG_CALL_MAKE_VIDEO);
            handler.postDelayed(new Runnable() {
                public void run() {
                    streamID = playMakeCallSounds();
                }
            }, 300);
        } else { // incoming call

            callStateTextView.setText("Ringing");
            if(EMClient.getInstance().callManager().getCallState() == EMCallStateChangeListener.CallState.IDLE
                    || EMClient.getInstance().callManager().getCallState() == EMCallStateChangeListener.CallState.DISCONNECTED) {
                // the call has ended
                finish();
                return;
            }
            voiceContronlLayout.setVisibility(View.INVISIBLE);
            localSurface.setVisibility(View.INVISIBLE);
            Uri ringUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
            audioManager.setMode(AudioManager.MODE_RINGTONE);
            audioManager.setSpeakerphoneOn(true);
            ringtone = RingtoneManager.getRingtone(this, ringUri);
            ringtone.play();
            EMClient.getInstance().callManager().setSurfaceView(localSurface, oppositeSurface);
        }

        final int MAKE_CALL_TIMEOUT = 50 * 1000;
        handler.removeCallbacks(timeoutHangup);
        handler.postDelayed(timeoutHangup, MAKE_CALL_TIMEOUT);

        // get instance of call helper, should be called after setSurfaceView was called
        callHelper = EMClient.getInstance().callManager().getVideoCallHelper();

        EMClient.getInstance().callManager().setCameraDataProcessor(dataProcessor);
    }

    class YDeltaSeekBarListener implements SeekBar.OnSeekBarChangeListener {

        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            dataProcessor.setYDelta((byte)(20.0f * (progress - 50) / 50.0f)); 
        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
        }

        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
        }

    }

    /**
     * set call state listener
     */
    void addCallStateListener() {
        callStateListener = new EMCallStateChangeListener() {

            @Override
            public void onCallStateChanged(final CallState callState, final CallError error) {
                switch (callState) {

                case CONNECTING: // is connecting
                    runOnUiThread(new Runnable() {

                        @Override
                        public void run() {
                            callStateTextView.setText(R.string.Are_connected_to_each_other);
                        }

                    });
                    break;
                case CONNECTED: // connected
                    runOnUiThread(new Runnable() {

                        @Override
                        public void run() {
                            callStateTextView.setText(R.string.have_connected_with);
                        }

                    });
                    break;

                case ACCEPTED: // call is accepted
                    handler.removeCallbacks(timeoutHangup);
                    runOnUiThread(new Runnable() {

                        @Override
                        public void run() {
                            try {
                                if (soundPool != null)
                                    soundPool.stop(streamID);
                                EMLog.d("EMCallManager", "soundPool stop ACCEPTED");
                            } catch (Exception e) {
                            }
                            openSpeakerOn();
                            ((TextView)findViewById(R.id.tv_is_p2p)).setText(EMClient.getInstance().callManager().isDirectCall()
                                    ? R.string.direct_call : R.string.relay_call);
                            handsFreeImage.setImageResource(R.drawable.em_icon_speaker_on);
                            isHandsfreeState = true;
                            isInCalling = true;
                            chronometer.setVisibility(View.VISIBLE);
                            chronometer.setBase(SystemClock.elapsedRealtime());
                            // call durations start
                            chronometer.start();
                            nickTextView.setVisibility(View.INVISIBLE);
                            callStateTextView.setText(R.string.In_the_call);
//                            recordBtn.setVisibility(View.VISIBLE);
                            callingState = CallingState.NORMAL;
                            startMonitor();
                        }

                    });
                    break;
                case NETWORK_DISCONNECTED:
                    runOnUiThread(new Runnable() {
                        public void run() {
                            netwrokStatusVeiw.setVisibility(View.VISIBLE);
                            netwrokStatusVeiw.setText(R.string.network_unavailable);
                        }
                    });
                    break;
                case NETWORK_UNSTABLE:
                    runOnUiThread(new Runnable() {
                        public void run() {
                            netwrokStatusVeiw.setVisibility(View.VISIBLE);
                            if(error == CallError.ERROR_NO_DATA){
                                netwrokStatusVeiw.setText(R.string.no_call_data);
                            }else{
                                netwrokStatusVeiw.setText(R.string.network_unstable);
                            }
                        }
                    });
                    break;
                case NETWORK_NORMAL:
                    runOnUiThread(new Runnable() {
                        public void run() {
                            netwrokStatusVeiw.setVisibility(View.INVISIBLE);
                        }
                    });
                    break;
                case VIDEO_PAUSE:
                    runOnUiThread(new Runnable() {
                        public void run() {
                            Toast.makeText(getApplicationContext(), "VIDEO_PAUSE", Toast.LENGTH_SHORT).show();
                        }
                    });
                    break;
                case VIDEO_RESUME:
                    runOnUiThread(new Runnable() {
                        public void run() {
                            Toast.makeText(getApplicationContext(), "VIDEO_RESUME", Toast.LENGTH_SHORT).show();
                        }
                    });
                    break;
                case VOICE_PAUSE:
                    runOnUiThread(new Runnable() {
                        public void run() {