android linphone sip話機簡單使用

由於公司項目涉及到sip通話這塊,所以嘗試了pjsip庫 android自帶的sip庫和linphone庫
自帶的sip庫掛斷各種回調在測試大多是不回調的。
pjsip庫好用回調也準確,但是在公司對接的線路中,可以撥打但是沒有聲音,如果是接聽其它端打過來的則有聲音。好像是線路路由方面的問題,所以最後只能放棄該框架。最後選擇了linphone這個庫

引入遠程庫包
implementation "org.linphone:linphone-sdk-android:4.2-182-g1eb2091"

  新建服務LinphoneService

public class LinphoneService extends Service {
    private static LinphoneService sInstance;
    private int retry=0;
    private int successSize=0;
    private int zhuceSize=0;
    private Handler mHandler;
    private Timer mTimer;
    private Core mCore;
    public CoreListenerStub mCoreListener;
    AccountCreator mAccountCreator;
    private String name="",pwd="",domain="";
    Call myCall;
    private boolean isComing=false;
    private boolean isExit=false;
    TimerTask lTask;
    public static boolean isReady() {
        return sInstance != null;
    }

    public static LinphoneService getInstance() {
        return sInstance;
    }

    public static Core getCore() {
        return sInstance.mCore;
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
//            MyNotificationManager.getInstance().getManager().createNotificationChannel(MyNotificationManager.getInstance().getChannel());
//            startForeground(1,MyNotificationManager.getInstance().getBuilder().setChannelId("1").build());
            MyNotificationManager.getInstance().createService(this,10086);
        }
        String basePath = getFilesDir().getAbsolutePath();
        Factory.instance().setLogCollectionPath(basePath);
        Factory.instance().enableLogCollection(LogCollectionState.Enabled);
        Factory.instance().setDebugMode(false, getString(R.string.app_name));
        mHandler = new Handler();
        // This will be our main Core listener, it will change activities depending on events
        mCoreListener = new CoreListenerStub() {
            @Override
            public void onRegistrationStateChanged(Core core, ProxyConfig cfg, RegistrationState state, String message) {
                if (state == RegistrationState.Ok) {
//                    successSize++;
//                    Toast.makeText(LinphoneService.this, "註冊成功!"+successSize + message, Toast.LENGTH_SHORT).show();
                    MyLogUtils.testLog("state==註冊成功");
                } else if (state == RegistrationState.Failed) {
                    MyLogUtils.testLog("state==註冊失敗");
//                    retry++;
//                        new Handler().postDelayed(new Runnable() {
//                            @Override
//                            public void run() {
//                                init(name,pwd,domain);
//                            }
//                        },retry*10000);
                }
            }
            @Override
            public void onCallStateChanged(Core core, Call call, Call.State state, String message) {
                System.out.println("state=="+state.name()+"  message=="+message+"call"+call.getRemoteAddressAsString());
                if (state == Call.State.IncomingReceived) {//來電
//                    if(isComing) {return;}
                    isComing=true;
                    myCall=call;
//                    if(!App.isInToGroupCall){
//                        Intent intent = new Intent(LinphoneService.this, CallActivity.class);
//                        intent.putExtra("type","0");
//                        intent.putExtra("user",call.getRemoteAddressAsString());
//                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//                        startActivity(intent);
//                    }
//                    CallParams params = getCore().createCallParams(call);
//                    params.enableVideo(false);
//                    call.acceptWithParams(params);
                } else if (state == Call.State.Connected) {
                    isComing=true;
                    // This stats means the call has been established, let's start the call activity
//                    Toast.makeText(LinphoneService.this, "接通", Toast.LENGTH_SHORT).show();
                }else if (state == Call.State.End) {
//                    Toast.makeText(LinphoneService.this, "通話結束", Toast.LENGTH_SHORT).show();
//                    if(!App.isInToGroupCall){
//                        EventBus.getDefault().post(new CallEvent());
//                    }
                }else if(state == Call.State.Released){
                    isComing=false;
//                    Toast.makeText(LinphoneService.this, "就緒", Toast.LENGTH_SHORT).show();
                } else if(state== Call.State.StreamsRunning){
                    isComing=true;
//                    Toast.makeText(LinphoneService.this, "通話中", Toast.LENGTH_SHORT).show();
                }
            }
        };

        try {
            copyIfNotExist(R.raw.linphonerc_default, basePath + "/.linphonerc");
            copyFromPackage(R.raw.linphonerc_factory, "linphonerc");
        } catch (IOException ioe) {
            Log.e(ioe);
        }
        try {
            configureCore(basePath);
        } catch (Exception e) {

        }
    }
    public void jieting(){//接聽電話
        if (mCore==null){
            MyToastUtils.showToast("接聽失敗!");
            return;
        }
        CallParams params = mCore.createCallParams(myCall);
                    params.enableVideo(false);
                    myCall.acceptWithParams(params);
    }
    public void init(String name,String pwd,String domain){//註冊賬號
//        if(isExit) {
//            String basePath = getFilesDir().getAbsolutePath();
//            try {
//                copyIfNotExist(R.raw.linphonerc_default, basePath + "/.linphonerc");
//                copyFromPackage(R.raw.linphonerc_factory, "linphonerc");
//            } catch (IOException ioe) {
//                Log.e(ioe);
//            }
//            try {
//                configureCore(basePath);
//            } catch (Exception e) {
//
//            }
//            mCore.start();}
        this.name=name;
        this.pwd=pwd;
        this.domain=domain;
        if(TextUtils.isEmpty(this.name)||TextUtils.isEmpty(this.pwd)||TextUtils.isEmpty(this.domain)){
            return;
        }
        if(mCore==null){
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        init(name,pwd,domain);
                    }
                },500);
                return;
        }
//        zhuceSize++;
//        Toast.makeText(this, "開始註冊"+zhuceSize, Toast.LENGTH_SHORT).show();
        isExit=false;
        mAccountCreator=mCore.createAccountCreator(null);
        mAccountCreator.setUsername(name);
        mAccountCreator.setDomain(domain);
        mAccountCreator.setPassword(pwd);
        mAccountCreator.setTransport(TransportType.Udp);
//        mAccountCreator.setTransport(TransportType.Tls);
        ProxyConfig cfg = mAccountCreator.createProxyConfig();
        cfg.setExpires(1);
        mCore.setDefaultProxyConfig(cfg);
    }
    public void logOut(){//登出賬號
        if (mAccountCreator==null) return;
//        if(!isExit)  {
//            mCore.stop();
//            mCore = null;
//        }
        isExit=true;
        ProxyConfig cfg = mAccountCreator.createProxyConfig();
        cfg.setExpires(0);//註銷
        mCore.setDefaultProxyConfig(cfg);
//        mCore.clearCallLogs();
        mCore.removeProxyConfig(cfg);



//        mAccountCreator=null;
//        mCore.clearProxyConfig();
//        mCore.clearAllAuthInfo();
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);

        // If our Service is already running, no need to continue
        if (sInstance != null) {
            return START_STICKY;
        }

        // Our Service has been started, we can keep our reference on it
        // From now one the Launcher will be able to call onServiceReady()
        sInstance = this;

        // Core must be started after being created and configured
//        mCore.start();//這句會導致服務一啓動就註冊。
        // We also MUST call the iterate() method of the Core on a regular basis
        TimerTask lTask =
                new TimerTask() {
                    @Override
                    public void run() {
                        mHandler.post(
                                new Runnable() {
                                    @Override
                                    public void run() {
                                        if (isExit){
                                            return;
                                        }
                                        if (mCore != null) {
                                            try {
                                                mCore.iterate();
                                            }catch (Exception e){

                                            }
                                        }
                                    }
                                });
                    }
                };
        mTimer = new Timer("Linphone scheduler");
        mTimer.schedule(lTask, 0, 20);

        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        mCore.removeListener(mCoreListener);
        mTimer.cancel();
        mCore.stop();
        // A stopped Core can be started again
        // To ensure resources are freed, we must ensure it will be garbage collected
        mCore = null;
        // Don't forget to free the singleton as well
        sInstance = null;

        super.onDestroy();
    }

    @Override
    public void onTaskRemoved(Intent rootIntent) {
        // For this sample we will kill the Service at the same time we kill the app
        stopSelf();

        super.onTaskRemoved(rootIntent);
    }

    private void configureCore(String basePath) {
        mCore = Factory.instance()
                .createCore(basePath + "/.linphonerc", basePath + "/linphonerc", this);
        mCore.enableEchoCancellation(true);//回聲消除
        mCore.enableAdaptiveRateControl(true);//自適應碼率控制
        mCore.addListener(mCoreListener);
        App.isAddCallListen=true;
        // We will create a directory for user signed certificates if needed
        String userCerts = basePath + "/user-certs";
        File f = new File(userCerts);
        if (!f.exists()) {
            if (!f.mkdir()) {
                Log.e(userCerts + " can't be created.");
            }
        }
        mCore.setUserCertificatesPath(userCerts);
    }

    private void dumpDeviceInformation() {
        StringBuilder sb = new StringBuilder();
        sb.append("DEVICE=").append(Build.DEVICE).append("\n");
        sb.append("MODEL=").append(Build.MODEL).append("\n");
        sb.append("MANUFACTURER=").append(Build.MANUFACTURER).append("\n");
        sb.append("SDK=").append(Build.VERSION.SDK_INT).append("\n");
        sb.append("Supported ABIs=");
        for (String abi : Version.getCpuAbis()) {
            sb.append(abi).append(", ");
        }
        sb.append("\n");
        Log.i(sb.toString());
    }

    private void dumpInstalledLinphoneInformation() {
        PackageInfo info = null;
        try {
            info = getPackageManager().getPackageInfo(getPackageName(), 0);
        } catch (PackageManager.NameNotFoundException nnfe) {
            Log.e(nnfe);
        }

        if (info != null) {
            Log.i(
                    "[Service] Linphone version is ",
                    info.versionName + " (" + info.versionCode + ")");
        } else {
            Log.i("[Service] Linphone version is unknown");
        }
    }

    private void copyIfNotExist(int ressourceId, String target) throws IOException {
        File lFileToCopy = new File(target);
        if (!lFileToCopy.exists()) {
            copyFromPackage(ressourceId, lFileToCopy.getName());
        }
    }

    private void copyFromPackage(int ressourceId, String target) throws IOException {
        FileOutputStream lOutputStream = openFileOutput(target, 0);
        InputStream lInputStream = getResources().openRawResource(ressourceId);
        int readByte;
        byte[] buff = new byte[8048];
        while ((readByte = lInputStream.read(buff)) != -1) {
            lOutputStream.write(buff, 0, readByte);
        }
        lOutputStream.flush();
        lOutputStream.close();
        lInputStream.close();
    }
}

//撥打電話

address格式爲  坐席號碼@域名 如[email protected]
    public static void call(String address){
        Core core = LinphoneService.getCore();
        if (core==null||TextUtils.isEmpty(address)){
            Toast.makeText(context, "撥打失敗!", Toast.LENGTH_SHORT).show();
            return;
        }
        Address addressToCall = core.interpretUrl(address);
        CallParams params = core.createCallParams(null);
        params.enableVideo(false);
        if (addressToCall != null) {
            core.inviteAddressWithParams(addressToCall, params);
        }
    }

//掛斷電話

public static void guaduan(){
    Core core = LinphoneService.getCore();
    if(core==null) {
        MyToastUtils.showToast("掛斷失敗!");
        return;
    }
    if (core.getCallsNb() > 0) {
        Call call = core.getCurrentCall();
        if (call == null) {
            // Current call can be null if paused for example
            call = core.getCalls()[0];
        }
        call.terminate();
    }
}

//項目中遇到一個問題就是註冊到線上之後如何註銷賬號發現linphone庫並沒有提供api,所以查詢了一下網上的資料發現要發送指令Expires=0所以寫了如下語句。

ProxyConfig cfg = mAccountCreator.createProxyConfig();

cfg.setExpires(0);//註銷

測試發現在APP沒有銷燬的情況下調用註銷確實可以登出話機不再收到通話。

不過將APP殺死重新啓動 發現sip自動註冊了。

這樣就導致會突然收到sip電話的響鈴,但是並無界面,需求是在指定頁面才能進行通話,這樣肯定不符合需求。

發現是LinphoneService中的

mCore.start();//這句會導致服務一啓動就註冊。

所以將這句註釋。問題解決。

退出界面的時候就調用LinphoneService的logout方法即可,在撥打界面註冊時先調用mCore.start();再進行init註冊即可。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章