ionic3 即时通讯(待机/后台运行可用)

最近开发记录

 程序要求:工厂内通讯使用,及时联络处理问题,收到消息及时提醒用户处理。目前已测试android4以上
1.保持程序后台运行
2.维持长连接,接收即时消息,后台播放无声音乐,每十秒执行一次,无声音乐长度为1秒
3.接收到信息,显示通知,提醒,语音播报
4.消息存储,根据不同的消息跳转不同的页面

需要用到插件

1.cordova-plugin-background-mode 后台运行
2.cordova-plugin-appminimize 程序最小化
3. 即时消息采用第三方融云
4. cordova-plugin-nativeaudio 播放本地音频
5.cordova-plugin-local-notification 本地消息通知
6.语音播报采用百度语音合成
7.cordova-sqlite-storage 存储消息

1.后台运行插件(ionic3版本,如果是ionic4,按照4的文档添加)

1.1 添加插件
$ ionic cordova plugin add cordova-sqlite-storage
$ npm install --save @ionic-native/sqlite@4

$ ionic cordova plugin add cordova-plugin-appminimize
$ npm install --save @ionic-native/app-minimize@4
1.2 插件添加到应用程序的模块中(此处不讲)
1.3 在app.component中启用(有删减)
import { Component, ViewChild } from '@angular/core';
import { Nav, Platform, App, Keyboard, IonicApp, Events } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
import { AppMinimize } from '@ionic-native/app-minimize';
import { BackgroundMode } from '@ionic-native/background-mode';
import { ToastServiceProvider } from '../providers/toast-service/toast-service';

@Component({
  templateUrl: 'app.html'
})
export class MyApp {
  @ViewChild(Nav) nav: Nav;

  rootPage: any = LoginPage;

  backButtonPressed: boolean = false;  //用于判断返回键是否触发

  constructor(public platform: Platform,
    public statusBar: StatusBar,
    public splashScreen: SplashScreen,
    private toast: ToastServiceProvider,
    private appMinimize: AppMinimize,
    private keyboard: Keyboard,
    private ionicApp: IonicApp,
    private app: App,
    private events: Events,
    private backgroundMode: BackgroundMode,
    private helper: HelperProvider) {
    this.initializeApp();
  }

  initializeApp() {

    this.platform.ready().then(() => {
      // Okay, so the platform is ready and our plugins are available.
      // Here you can do any higher level native things you might need.
      // this.statusBar.styleDefault();
      // this.splashScreen.hide();
 
      //判断是否真机运行
      if (this.helper.isMobile()) {
        this.registerBackButtonAction();//注册返回按键事件
        this.setBackground();//设置后台运行
      }
        this.statusBar.styleDefault();
        this.splashScreen.hide();
        this.nav.setRoot(LoginPage);

    });
  }


  /**
   * 设置后台运行
   */
  setBackground() {

    this.backgroundMode.enable();
    //延迟启用
    setTimeout(() => {
      this.backgroundMode.setDefaults({
        title: ‘正在后台运行,请勿关闭应用’,
        text: ‘点击即可进入应用’,
        icon: 'assets/imgs/logo.png'
      })
    }, 2000);

  }

  /**
   * android 物理返回键处理
   */
  registerBackButtonAction() {
    this.platform.registerBackButtonAction(() => {
      if (this.keyboard.isOpen()) {//如果键盘开启则隐藏键盘
        this.keyboard.close();
        return;
      }

      //如果想点击返回按钮隐藏toast或loading或Overlay就把下面加上
      // this.ionicApp._toastPortal.getActive() || this.ionicApp._loadingPortal.getActive() || this.ionicApp._overlayPortal.getActive()
      let activePortal = this.ionicApp._modalPortal.getActive();
      if (activePortal) {
        activePortal.dismiss().catch(() => { });
        activePortal.onDidDismiss(() => { });
        return;
      }
      let loadingPortal = this.ionicApp._loadingPortal.getActive();
      if (loadingPortal) {
        loadingPortal.dismiss().catch(() => { });
        return;
      }

      if (this.app.getActiveNav().canGoBack() || this.nav.canGoBack()) {
        this.app.navPop();
      } else {
        this.showExit();
      }
      return;
      //  return this.app.getActiveNav().canGoBack() ? this.app.navPop() : this.showExit();
    }, 100);
  }

  //双击退出提示框
  showExit() {
    if (this.backButtonPressed) { //当触发标志为true时,即2秒内双击返回按键则退出APP
      // this.platform.exitApp();
      this.appMinimize.minimize();
    } else {
      this.toast.showToast('再次返回将自动切换到后台运行');
      this.backButtonPressed = true;
      setTimeout(() => this.backButtonPressed = false, 2000);//2秒内没有再次点击返回则将触发标志标记为false
    }
  }
}

关联文件ToastServiceProvider

import { Injectable } from '@angular/core';
import { ToastController } from 'ionic-angular';
/*
  Generated class for the ToastServiceProvider provider.

  See https://angular.io/guide/dependency-injection for more info on providers
  and Angular DI.
*/
@Injectable()
export class ToastServiceProvider {
  toast: any;//Toast
  constructor(private toastCtrl: ToastController) {
  }

  /**
       * 吐司提示(默认中间)
       */
  showToast(mes: string, position?: string) {

    if (this.toast) {
      this.toast.dismiss();
    }
    this.toast = this.toastCtrl.create({
      message: mes,
      duration: 2000,
      position: position ? position : 'middle'
    });
    this.toast.present();
  }

}

HelperProvider

import { Injectable } from '@angular/core';
import { Platform,} from 'ionic-angular';
import { Network } from '@ionic-native/network';;

/*
  Generated class for the HelperProvider provider.

  See https://angular.io/guide/dependency-injection for more info on providers
  and Angular DI.
*/
@Injectable()
export class HelperProvider {
  constructor(private platform: Platform,
    private network: Network) {
  }

  /**
      * 是否真机环境
      * @return {boolean}
      */
  isMobile(): boolean {
    return this.platform.is('mobile') && !this.platform.is('mobileweb');
  }

  /**
   * 是否android真机环境
   * @return {boolean}
   */
  isAndroid(): boolean {
    return this.isMobile() && this.platform.is('android');
  }

  /**
   * 是否android6以下
   */
  isAndroidLow():boolean{
    return this.isAndroid() && this.platform.version().major < 6;
  }

  /**
   * 是否ios真机环境
   * @return {boolean}
   */
  isIos(): boolean {
    return this.isMobile() && (this.platform.is('ios') || this.platform.is('ipad') || this.platform.is('iphone'));
  }


  /**
  * 获取网络类型 如`unknown`, `ethernet`, `wifi`, `2g`, `3g`, `4g`, `cellular`, `none`
  */
  getNetworkType(): string {
    if (!this.isMobile()) {
      return 'wifi';
    }
    return this.network.type;
  }

  /**
   * 判断是否有网络
   * @returns {boolean}
   */
  isConnecting(): boolean {
    if (this.getNetworkType() == 'none') {
      //没有网络
    }
    return this.getNetworkType() != 'none';
  }

  /**
    * 获取随机数
    */
  generateMixed(n: number) {
    let res = "";
    var chars = ['2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];//随机数 
    for (var i = 0; i < n; i++) {
      var id = Math.ceil(Math.random() * 31);
      res += chars[id];
    }
    return res;
  }

}

2 集成融云

由于测试期间使用融云web SDK在android6以下出现连接融云服务器受限,出现403状态码,最后为了解决该问题,暂时在android6以下版本采用融云cordova插件代替

2.1 添加融云cordova插件

融云cordova地址

$ ionic cordova plugin add cordova-plugin-rongcloud-im
2.2 添加web版融云

融云web文档地址

ionic3 即时通讯(待机/后台运行可用)_第1张图片
image.png
ionic3 即时通讯(待机/后台运行可用)_第2张图片
image.png
2.3 在app.component中
/// 

如图:


ionic3 即时通讯(待机/后台运行可用)_第3张图片
image.png
2.4 创建RongcloudServieProvider
$ ionic g provider RongcloudServie
2.5 修改RongcloudServieProvider

StorageServiceProvider参考
SqliteServiceProvider 参考

import { HelperProvider } from './../helper/helper';
import { Injectable } from '@angular/core';
import { Events } from 'ionic-angular';
import { StorageServiceProvider } from '../../providers/storage-service/storage-service';
import { SqliteServiceProvider } from '../../providers/sqlite-service/sqlite-service';
declare let RongCloudLibPlugin;
/*
  Generated class for the RongcloudServieProvider provider.

  See https://angular.io/guide/dependency-injection for more info on providers
  and Angular DI.
*/
@Injectable()
export class RongcloudServieProvider {
    IM_KEY = 'xxxxxxxxxxxxxxxxx';//融云官网申请的应用key
    constructor(public events: Events,
        private sqlite: SqliteServiceProvider,
        private storageService: StorageServiceProvider,
        private helper: HelperProvider) {

    }

    /**
       * 注册实例化RongYunIm服务
       * Ming 2018-10-29
       * @memberof RongcloudServieProvider
       */
    init() {
        if (this.helper.isAndroidLow()) {
            RongCloudLibPlugin.init({ appKey: IM_KEY },(ret, err)=>{
                // alert(ret.status);
         });
        } else {
            RongIMLib.RongIMClient.init(IM_KEY);
        }
    }


    /**
     * 链接融云服务器状态回掉事件
     * Ming 2018-10-29
     * @param 
     * @memberof RongcloudServieProvider
     */
    connectionStatusListener() {
        if (this.helper.isAndroidLow()) {
            RongCloudLibPlugin.setConnectionStatusListener((ret, err)=>{
                this.onListenerStatus(ret.result.connectionStatus);
            });
           
        } else {
            RongIMLib.RongIMClient.setConnectionStatusListener({
                onChanged: (status) => {
                    this.onListenerStatus(status);
                }
            });
        }

    }

    onListenerStatus(status) {
        switch (status) {
            case RongIMLib.ConnectionStatus.CONNECTED:
                // this.coms.closeLoading(loading);
                console.log('聊天服务链接成功');

                break;
            case RongIMLib.ConnectionStatus.CONNECTING:
                // this.coms.toastInfo("正在链接聊天服务", 1000, 'top');
                break;
            // case RongIMLib.ConnectionStatus.DISCONNECTED:
            case RongIMLib.ConnectionStatus.NETWORK_UNAVAILABLE:
                // this.coms.toastError('服务已经断开 正为您重新连接');
                this.reconnect();
                // alert(moment());
                break;
            case RongIMLib.ConnectionStatus.KICKED_OFFLINE_BY_OTHER_CLIENT:
                //   this.logoutAndClear();
                //   this.coms.toastInfo("其他设备登录成功,您已经被迫下线!", 2000, 'top');
                // this.alertService.showAlert('你已在其他设备登录成功,可能导致此设备无法正常接收信息');
                // this.reconnect();
                break;
            case RongIMLib.ConnectionStatus.DOMAIN_INCORRECT:
                this.alertService.showAlert('域名解析失败');
                this.reconnect();
                // this.coms.toastError("域名解析失败", 2000, 'top');
                break;
            case RongIMLib.ConnectionStatus.DISCONNECTED:
                // this.alertService.showAlert('已断开连接');
                this.reconnect();
                // this.coms.toastError("域名解析失败", 2000, 'top');
                break;
            case RongIMLib.ConnectionStatus.CONNECTION_CLOSED:
                // this.alertService.showAlert('连接已关闭');
                // this.reconnect();
                this.connect(this.globalData.rongcloudtoken);
                // this.coms.toastError("域名解析失败", 2000, 'top');
                break;
        }
    }

    /**
     * 收到消息的监听回掉事件
     * Ming 2018-10-29
     * @param 
     * @memberof RongcloudServieProvider
     */
    async receiveMessageListener() {
        if (this.helper.isAndroidLow()) {
            RongCloudLibPlugin.setOnReceiveMessageListener((ret, err) => {
                this.onReceived(ret.result.message);
            })
        } else {
            RongIMLib.RongIMClient.setOnReceiveMessageListener({
                // 接收到的消息
                onReceived: (message) => {
                    this.onReceived(message)
                }
            })
        }

    }

    /**
     * 处理消息 (根据自己实际需求处理)
     * @param message 
     */
    async onReceived(message) {

        if (message && message.content.extra) {
            let msg = JSON.parse(message.content.extra);
            if (msg) {
                    //保存数据
                    this.sqlite.executeSql('INSERT INTO message(userId,title, state, content, sendId, sendTime, msgType, isWarning, referenceId, receivers,isValid,isDeleted,createTime,referenceType,referenceCode) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
                        [this.globalData.userId.toString(), msg.title.toString(), '0', msg.content ? msg.content.toString() : '', msg.sendId.toString(), msg.sendTime.toString(), msg.msgType.toString(), msg.isWarning, msg.referenceId ? msg.referenceId.toString() : '', JSON.stringify(msg.receivers), msg.isValid, msg.isDeleted, new Date().getTime().toString(), msg.referenceType ? msg.referenceType.toString() : '',msg.referenceCode?msg.referenceCode.toString():""]).then(() => {
                this.events.publish('message:new', msg);//广播通知有信息
             });
            
        }
    }





    /**
     * 获取未读消息
     * 此接口必须在init()方法之后,连接融云服务器 connect 之前调用。
     * Ming 2018-10-29
     * @param {*} token 传入链接的Token
     * @param {(hasMessage) => {}} successCallback  执行成功后的回掉事件
     * @param {(err) => {}} errorCallback 执行错误后的回掉事件
     * @memberof RongcloudServieProvider
     */
    hasRemoteUnreadMessages(token): Promise {
        return new Promise((resolve, reject) => {
            RongIMLib.RongIMClient.getInstance().hasRemoteUnreadMessages(token, {
                onSuccess: (hasMessage) => {
                    resolve(hasMessage)
                    console.log('未读消息' + hasMessage)
                },
                onError: (err) => {
                    reject(err)
                }
            });
        })
    }

    // 获取未读消息总数
    getUnreadMessageNumber(): Promise {
        return new Promise((resolve, reject) => {
            RongIMLib.RongIMClient.getInstance().getTotalUnreadCount({
                onSuccess: (count) => {
                    resolve(count);
                },
                onError: (error) => {
                    reject(error)
                }
            });
        })
    }
    /**
     * 发送信息
     */
    sendMessage(conversationtype: RongIMLib.ConversationType, targetId: string, msg: any): Promise {
        return new Promise((resolve, reject) => {
            if(this.helper.isAndroidLow()){
                RongCloudLibPlugin.sendTextMessage({
                    conversationType: conversationtype,
                    targetId: targetId,
                    text: msg
                },(ret, err)=>{
                    resolve(ret);
                })
            } else {
                RongIMLib.RongIMClient.getInstance().sendMessage(
                    conversationtype,
                    targetId,
                    msg,
                    {
                        onSuccess: (message) => {
                            resolve(message);
                        },
                        onError: (errorCode) => {
                            let info = '';
                            switch (errorCode) {
                                case RongIMLib.ErrorCode.TIMEOUT:
                                    info = '超时';
                                    break;
                                case RongIMLib.ErrorCode.REJECTED_BY_BLACKLIST:
                                    info = '在黑名单中,无法向对方发送消息';
                                    break;
                                case RongIMLib.ErrorCode.NOT_IN_DISCUSSION:
                                    info = '不在讨论组中';
                                    break;
                                case RongIMLib.ErrorCode.NOT_IN_GROUP:
                                    info = '不在群组中';
                                    break;
                                case RongIMLib.ErrorCode.NOT_IN_CHATROOM:
                                    info = '不在聊天室中';
                                    break;
                                default:
                                    info = 'x';
                                    break;
                            }
    
                            reject(info)
    
                        },
                        onBefore: (messageId) => {
                            reject(messageId)
                        }
                    }
                );
            }
            
        })
    }
    /**
     * 通过融云返回的TokenId链接融云服务器
     * 传入链接的Token
     * @param {*} token
     * @param {(userId) => {}} successCallback  执行成功后的回掉事件并得到用户UserId
     * @param {() => {}} tokenIncorrectCallback Token失效事件
     * @param {(errorCode) => {}} errorCallback 执行错误后的回掉事件并放回错误编码
     * @memberof RongcloudServieProvider
     */
    connect(token): Promise {
        return new Promise((resolve, reject) => {
            if (this.helper.isAndroidLow()) {
                RongCloudLibPlugin.connect({
                    token: token
                },(ret, err)=>{
                    if (ret.status == 'success') {
                        // alert(ret.result.userId);
                        resolve(ret.result.userId);
                    } else {
                        resolve(ret);
                    }
                })
            } else {
                RongIMLib.RongIMClient.connect(token, {
                    onSuccess: (userId) => {
                        resolve(userId);
                        // this.setHeartbeat();//开启心跳
                    },
                    onTokenIncorrect: () => {
                        reject('token无效');
                    },
                    onError: (errorCode) => {
                        let info = '';
                        switch (errorCode) {
                            case RongIMLib.ErrorCode.TIMEOUT:
                                info = '超时';
                                break;
                            case RongIMLib.ConnectionState.IDENTIFIER_REJECTED:
                                info = 'appkey不正确';
                                break;
                            case RongIMLib.ConnectionState.SERVER_UNAVAILABLE:
                                info = '服务器不可用';
                                break;
                        }
                        reject(info)
                    }
                })
            }

        })
    }

    getCurrentConnectionStatus() {
        if (this.helper.isAndroidLow()) {
            return RongCloudLibPlugin.getConnectionStatus();
        } else {
            return RongIMLib.RongIMClient.getInstance().getCurrentConnectionStatus();
        }

    }
    /**
     * 融云重新连接
     */
    reconnect(): Promise {
        if (this.storageService.sessionRead('rongcloud') == 'yes') {
            // this.loadingService.showLoading('正在重新连接聊天服务器');
            return new Promise((resolve, reject) => {
                if (this.helper.isAndroidLow()) {
                    this.connect(this.globalData.rongcloudtoken);
                } else {
                    RongIMLib.RongIMClient.reconnect({
                        onSuccess: (userId) => {
                            // this.loadingService.hideLoading();
                            // console.log("融云重连成功." + userId);
                            resolve(userId);
                        },
                        onTokenIncorrect: () => {
                            // console.log('token无效');
                            // this.loadingService.hideLoading();
                            reject('token无效');
                            this.alertService.showAlert('融云重连失败token无效');
                        },
                        onError: (errorCode) => {
                            // console.log("融云重连失败" + errorCode);
                            // this.loadingService.hideLoading();
                            reject(errorCode);
                            // this.alertService.showAlert('融云重连失败' + errorCode);

                        }
                    }, {
                            auto: true,
                            url: 'cdn.ronghub.com/RongIMLib-2.3.2.min.js',
                            rate: [100, 1000, 3000, 6000, 10000]
                        });
                }

            })
        }
    }
    /**
     * 断开融云连接
     */
    disconnectRy() {
        if (this.helper.isAndroidLow()) {
            // 描述:断开后是否接收 Push
            RongCloudLibPlugin.disconnect({ isReceivePush: false },(ret, err)=>{
                // alert(ret.status);
         });
        } else {
            RongIMLib.RongIMClient.getInstance().disconnect();
        }

    }
    /**
     * 登出融云
     */
    logoutRy() {
        try {
            if (this.helper.isAndroidLow()) {
                RongCloudLibPlugin.logout((ret, err)=>{
                    // alert(ret.status);
             });
            } else {
                RongIMLib.RongIMClient.getInstance().logout();
            }
        } catch (e) {
            console.log(e)
        }

    }

}

2.6 在app.component中调用及监听,修改app.component为
import { Component, ViewChild } from '@angular/core';
import { Nav, Platform, App, Keyboard, IonicApp, Events } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
import { AppMinimize } from '@ionic-native/app-minimize';
import { BackgroundMode } from '@ionic-native/background-mode';
import { ToastServiceProvider } from '../providers/toast-service/toast-service';
import { RongcloudServieProvider } from '../providers/rongcloud-servie/rongcloud-servie';
import { SqliteServiceProvider } from '../providers/sqlite-service/sqlite-service';

@Component({
  templateUrl: 'app.html'
})
export class MyApp {
  @ViewChild(Nav) nav: Nav;

  rootPage: any = LoginPage;

  backButtonPressed: boolean = false;  //用于判断返回键是否触发

  constructor(public platform: Platform,
    public statusBar: StatusBar,
    public splashScreen: SplashScreen,
    private toast: ToastServiceProvider,
    private appMinimize: AppMinimize,
    private keyboard: Keyboard,
    private ionicApp: IonicApp,
    private app: App,
    private events: Events,
    private backgroundMode: BackgroundMode,
    private rongService: RongcloudServieProvider,
    private sqlite: SqliteServiceProvider,
    private helper: HelperProvider) {
    this.initializeApp();
  }

  initializeApp() {
       //监听登录成功 登录成功或失败都需要触发一个event事件
      this.events.subscribe('login:success', (res) => {
        //登录成功拿到后台返回的用户对应的token
        this.connectRongCloud(res.token);
      });
      //监听用户退出登录
      this.events.subscribe('login:out', () => {
         this.onExit();
      })
    this.platform.ready().then(() => {
      // Okay, so the platform is ready and our plugins are available.
      // Here you can do any higher level native things you might need.
      // this.statusBar.styleDefault();
      // this.splashScreen.hide();
 
      //初始化融云
      this.rongService.init();

      //判断是否真机运行
      if (this.helper.isMobile()) {
        this.registerBackButtonAction();//注册返回按键事件
        this.setBackground();//设置后台运行
      }
        //确保异步执行完后才隐藏启动动画
      this.events.subscribe('db:create', () => {
        //创建数据库与表成功后才关闭动画跳转页面
        this.statusBar.styleDefault();
        this.splashScreen.hide();
        this.nav.setRoot(LoginPage);
      })

      //初始化创建数据库
      this.sqlite.createDb();

    });
  }


  /**
   * 设置后台运行
   */
  setBackground() {

    this.backgroundMode.enable();
    //延迟启用
    setTimeout(() => {
      this.backgroundMode.setDefaults({
        title: ‘正在后台运行,请勿关闭应用’,
        text: ‘点击即可进入应用’,
        icon: 'assets/imgs/logo.png'
      })
    }, 2000);

  }

  /**
   * android 物理返回键处理
   */
  registerBackButtonAction() {
    this.platform.registerBackButtonAction(() => {
      if (this.keyboard.isOpen()) {//如果键盘开启则隐藏键盘
        this.keyboard.close();
        return;
      }

      //如果想点击返回按钮隐藏toast或loading或Overlay就把下面加上
      // this.ionicApp._toastPortal.getActive() || this.ionicApp._loadingPortal.getActive() || this.ionicApp._overlayPortal.getActive()
      let activePortal = this.ionicApp._modalPortal.getActive();
      if (activePortal) {
        activePortal.dismiss().catch(() => { });
        activePortal.onDidDismiss(() => { });
        return;
      }
      let loadingPortal = this.ionicApp._loadingPortal.getActive();
      if (loadingPortal) {
        loadingPortal.dismiss().catch(() => { });
        return;
      }

      if (this.app.getActiveNav().canGoBack() || this.nav.canGoBack()) {
        this.app.navPop();
      } else {
        this.showExit();
      }
      return;
      //  return this.app.getActiveNav().canGoBack() ? this.app.navPop() : this.showExit();
    }, 100);
  }

  //双击退出提示框
  showExit() {
    if (this.backButtonPressed) { //当触发标志为true时,即2秒内双击返回按键则退出APP
      // this.platform.exitApp();
      this.appMinimize.minimize();
    } else {
      this.toast.showToast('再次返回将自动切换到后台运行');
      this.backButtonPressed = true;
      setTimeout(() => this.backButtonPressed = false, 2000);//2秒内没有再次点击返回则将触发标志标记为false
    }
  }


  /**
   * 连接融云
   */
  connectRongCloud(token) {
      this.events.subscribe('message:new', message => {
        if (this.helper.isMobile()) {
          // 如果后台运行,则把程序从后台切换到前台运行
          this.backgroundMode.moveToForeground();
        } 
      });

      this.rongService.connectionStatusListener();

      // 监听融云的服务...
      this.rongService.receiveMessageListener();

      this.rongService.connect(token).then((userId) => {
        // 成功连接融云服务
        console.log('登录成功:' + userId);
      }).catch((error) => {
        // 登录失败处理
        this.toast.showToast('登录聊天服务器失败' + error);
      })
  }

}

/**
   * 退出登录,清除用户登录信息
   */
  onExit() {
    this.rongService.logoutRy();
    this.nav.setRoot(LoginPage);
  }

3 添加播放音频与本地通知

3.1 添加插件
$ ionic cordova plugin add cordova-plugin-nativeaudio
$ npm install --save @ionic-native/native-audio@4

$ ionic cordova plugin add cordova-plugin-local-notification
$ npm install --save @ionic-native/local-notifications@4
3.2 插件添加到应用程序的模块中、
3.3 创建AudioServiceProvider
$ ionic g provider AudioService
3.4 修改AudioServiceProvider
import { StorageServiceProvider } from './../storage-service/storage-service';
import { HelperProvider } from './../helper/helper';
import { Injectable } from '@angular/core';
import { NativeAudio } from '@ionic-native/native-audio';

/*
  Generated class for the AudioServiceProvider provider.

  See https://angular.io/guide/dependency-injection for more info on providers
  and Angular DI.
*/
@Injectable()
export class AudioServiceProvider {

  time:any;
  audio: HTMLAudioElement;
  constructor(private nativeAudio: NativeAudio,
    private helper: HelperProvider,
    private storageService: StorageServiceProvider) {

    // this.nativeAudio.preloadComplex('bell', 'assets/bell.mp3', 1, 1, 0).catch(e => console.log(e));
    //缓存无声音频
    this.nativeAudio.preloadComplex('no', 'assets/no.mp3', 1, 1, 0).catch(e => console.log(e));
    // this.nativeAudio.preloadComplex('prompt', 'assets/prompt.mp3', 1, 1, 0).catch(e => console.log(e));
   //添加收到消息提示声音,为了兼容低版本vivo和web端
    this.audio = document.createElement('audio');
    this.audio.src = 'http://xxx/assets/file/prompt.mp3';
    this.audio.autoplay = false;
  }



  /**
   * 播放无声音音乐
   */
  backgroundPlay() {
    if(this.time){
      clearInterval(this.time);
    }
    this.time = setInterval(()=>{
      setTimeout(() => {
        try{
          this.nativeAudio.play('no');
        }catch(err){
          console.log(err)
        }
        
      }, 9000);
    },10000)
  }

  /**
   * 停止播放背景音乐
   */
  stopBackground(){
    clearInterval(this.time);
    this.time = null;
    this.nativeAudio.stop('no');
  }

  /**
   * 播放消息提示声音
   */
  playMsg(){
      // this.nativeAudio.play('prompt');
      this.audio.play();
  }
  
}

3.5 修改app.component,追加代码
import { AudioServiceProvider } from '../providers/audio-service/audio-service';
import { LocalNotifications } from '@ionic-native/local-notifications';

  constructor(private audioService: AudioServiceProvider,
    private localNotifications: LocalNotifications) {}


 /**
   * 设置后台运行
   */
  setBackground() {
    this.audioService.backgroundPlay();
  }




 /**
   * 连接融云
   */
  connectRongCloud() {
      this.events.subscribe('message:new', message => {
        // 播放消息声音
        this.audioService.playMsg();

        if (this.helper.isMobile()) {
          //显示通知
          this.localNotifications.schedule({
            id: message.id,
            icon: 'res://ic_chat',
            smallIcon: 'res://ic_chat',
            sound: null,
            silent: true,
            title:message.title,
            text: message.content,
            data: message
          });

          //监听点击事件
          this.localNotifications.on('click').subscribe(res => {
            //清除所有消息
            this.localNotifications.clearAll();
          });
        } 
      });

  }

4 集成百度语音合成

一开始采用cordova-plugin-tts进行播报,设置语言为中文,但在vivo手机出现只读英文,中文被过滤无法播报,好像是编码问题,后来选用百度语音合成,因为免费

4.1 创建应用

点击注册并创建应用

ionic3 即时通讯(待机/后台运行可用)_第4张图片
image.png

成功后会得到AppID,API Key,Secret Key

4.2 Java后台设置

文档链接

后台写一个接口代码,播放参数前端传给后台

/**
     * 测试单聊
     *
     * @param tex
     * @return
     * @throws Exception
     */
    @RequestMapping("getAudio")
    public void getAudio(HttpServletRequest request, Model model,
                         @RequestParam("lan") String lan,
                         @RequestParam("ctp") Integer ctp,
                         @RequestParam("tex") String tex,
                         @RequestParam("cuid") String cuid,
                         @RequestParam("spd") String spd,
                         @RequestParam("pit") String pit,
                         @RequestParam("vol") String vol,
                         @RequestParam("per") String per,
                         HttpServletResponse response
    ) throws Exception {
        HashMap option = new HashMap<>();
        option.put("cuid", cuid);
        option.put("spd", spd);
        option.put("pit", pit);
        option.put("vol", vol);
        option.put("per", per);
        byte[] buff = ttsUtils.textToSpeech(tex, lan, ctp, option);
        if (buff != null) {
            response.setContentType("audio/mp3");
            OutputStream outputStream = response.getOutputStream();
            outputStream.write(buff);
            outputStream.close();
        }
    }
4.3 创建TtsServiceProvider
$ ionic g provider TtsService

填写代码

import { StorageServiceProvider } from './../storage-service/storage-service';
import { Injectable } from '@angular/core';
import { ConfigProvider } from '../config/config';

/*
  Generated class for the TtsServiceProvider provider.

  See https://angular.io/guide/dependency-injection for more info on providers
  and Angular DI.
*/
@Injectable()
export class TtsServiceProvider {
  audio: any;
  options = {
    lan: "zh",//语言
    ctp: '1',
    cuid: '',//唯一标识
    tex: '',//播放内容
    spd: 5,//语速,取值0-15,默认为5中语速
    pit: 5,//音调,取值0-15,默认为5中语调
    vol: 15,//音量,取值0-15,默认为5中音量
    per: 0//发音人选择, 0为普通女声,1为普通男生,3为情感合成-度逍遥,4为情感合成-度丫丫,默认为普通女声
  }

  constructor( private storageService: StorageServiceProvider) {

  }

  /**
   * 初始化百度tts
   */
  init() {
    this.audio = document.createElement('audio');
    this.audio.autoplay = false;
    //如果第一次启动,参数不存在,则存储默认参数
    if (!this.storageService.read("baiduTTS")) {
      this.storageService.write("baiduTTS", this.options);
    }
  }

  /**
   * 播放内容
   * @param text 
   */
  speak(text: string) {
      //读取最新存储语音设置
      this.options = this.storageService.read("baiduTTS");
      this.options.cuid = 'xxx';//此处应为用户唯一标识
      //url编码
      this.options.tex = encodeURIComponent(text);
      //设置地址为后台播放地址
      this.audio.src = 'http://xxx/message/getAudio' + '?' + this.toBodyString(this.options);
      this.audio.play();

    }

  }

  /**
   * 停止播放
   */
  stop() {
    this.audio.pause();
  }

  /**
   *
   * @param obj
   * @return {string}
   *  声明: var obj= {};
   *  调用: toQueryString(obj);
   *  返回: "name=%E5%B0%8F%E5%86%9B&age=23"
   */
  toBodyString(obj) {
    let ret = [];
    for (let key in obj) {
      if (key != 'constructor') {
        key = encodeURIComponent(key);
        let values = obj[key];
        if (values && values.constructor == Array) {//数组
          let queryValues = [];
          for (let i = 0, len = values.length, value; i < len; i++) {
            value = values[i];
            queryValues.push(this.toQueryPair(key, value));
          }
          ret = ret.concat(queryValues);
        } else { //字符串
          ret.push(this.toQueryPair(key, values));
        }
      }
    }
    return ret.join('&');
  }

  private toQueryPair(key, value) {
    if (typeof value == 'undefined') {
      return key;
    }
    return key + '=' + encodeURIComponent(value === null ? '' : String(value));
  }
}

4.4 修改app.component,追加代码
import { TtsServiceProvider } from '../providers/tts-service/tts-service';

constructor(private ttsService: TtsServiceProvider) {}

 initializeApp() {
    this.platform.ready().then(() => {
      // Okay, so the platform is ready and our plugins are available.
      // Here you can do any higher level native things you might need.
      
      // 初始化百度tts
      this.ttsService.init();
    });
  }

/**
   * 连接融云
   */
  connectRongCloud() {
      this.events.subscribe('message:new', message => {
        // 播放文本
        this.ttsService.speak( message.content);

        if (this.helper.isMobile()) {
          this.localNotifications.on('click').subscribe(res => {
            //清除该条
            this.audioService.stopPlay();
            //停止播放
            this.ttsService.stop();
            this.localNotifications.clearAll();
          });
        } 
      });

  }



代码有删减,可能存在缺失代码问题,可惜的是本地通知在待机状态下并不能出现

你可能感兴趣的:(ionic3 即时通讯(待机/后台运行可用))