2017年10月11日 星期三

如何實作 Ionic 3 的 WebRTC

 不少人都在詢問如何實作cordovaWebRTC,目前作者使用Peer.JS 就能完成一連串的影像互傳的功能,此篇文章在『Angular2 簡易版的webRTC』已簡單介紹過了,目前要更進一步實作出Cordova 在手機上與電腦也能互傳影像。

一、介紹FireBase
支援real-time的一種雲端服務,更重要的一點是完全免費的喔!!! 簡單的key-valueJSON格式,支援資料庫的匯入和匯出,順帶一提免費版的有些容量是有限制的。

為何需要雲端資料庫FireBase儲存資料,因為Peer.js 會產生一組perid讓對方可以透過perid 互傳影像,所以儲存登入者名稱和peerid…

二、登入FireBase 開啟功能


Google 登入à go to console à新增專案




















點選overview旁邊的設定à專案設定



選擇 firebase 加入網路應用程式

複製以下的設定檔
















一、建立ionic 專案

ionic start myApp tabs


$ ionic cordova plugin add cordova-plugin-firebase
$ npm install --save @ionic-native/firebase 
$ npm install angularfire2 firebase --save

Providers/databaseservice/user.ts (實作資料層)

import { Injectable } from '@angular/core';
import {AngularFireDatabase,FirebaseListObservable } from 'angularfire2/database';

@Injectable()
export class UserService {
   constructor(private _af: AngularFireDatabase) {
   }

   public listUsers(): FirebaseListObservable<any[]>{
      return this._af.list('users');
   }

   public addUser(userName,peerID){
        this.listUsers().push({ userName: userName, peerID: peerID});
   }
  
   public removeUser(userName){
       let removeItem = this._af.list('users', {
           query: {
               orderByChild: "userName",
               equalTo: userName
           }
       });

    // Query the list for the purposes of this example:
       removeItem.subscribe((items) => {
           // Remove the matching item:
           if (items.length) {
               removeItem.remove(items[0].$key)
                   .then(() => console.log('removed ' + items[0].$key))
                   .catch((error) => console.log(error));
           }
       });
   }
}

修改app.module.ts
import { AngularFireModule } from 'angularfire2';
import {AngularFireDatabaseModule} from 'angularfire2/database';
import { UserService } from "../providers/databaseservice/users";

// AF2 Settings
export const firebaseConfig = {
  apiKey: "xxxxxxxxxx",
  authDomain: "xxxxxxxxx.firebaseapp.com",
  databaseURL: "https:// xxxxxxxxx.firebaseio.com",
  storageBucket: "xxxxxxxxx.appspot.com",
  messagingSenderId: "xxxxxxxxx"
};


注入module
imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp),
    AngularFireModule.initializeApp(firebaseConfig),
    AngularFireDatabaseModule
  ],

注入prvider
  providers: [
    StatusBar,
    SplashScreen,
    UserService,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
  ]

ionic g page login  建立登入頁

pages/login/login.html
<ion-header>
  <ion-navbar color="primary">
    <ion-title  align-title="center">登錄</ion-title>
  </ion-navbar>
</ion-header>
<ion-content padding>
  <ion-item>
    <ion-label fixed>名稱</ion-label>
    <ion-input type="text" [(ngModel)]="name"></ion-input>
  </ion-item>

  <ion-item>
    <ion-label fixed>密碼</ion-label>
    <ion-input type="text" [(ngModel)]="password"></ion-input>
  </ion-item>
  <p>
   <button ion-button full round (click)="login(name)">登錄</button>
</ion-content>

pages/login/login.ts
@Component({
  selector: 'page-login',
  templateUrl: 'login.html',
})
export class LoginPage {

  constructor(public navCtrl: NavController, public navParams: NavParams) {
  }

  login(name) {
     登入頁面並傳入參數
     this.navCtrl.push(TabsPage, {
      userName: name
    });
  }
}

pages/tabs/tabs.ts
import { Component } from '@angular/core';
import { HomePage } from '../home/home';
import { NavController,NavParams } from 'ionic-angular';

@Component({
  templateUrl: 'tabs.html'
})
export class TabsPage {
  userName; 宣告參數
  tab1Root = HomePage;

  constructor(params: NavParams) {
     this.userName = params.get("userName"); 取得參數
  }
}  

pages/home/home.html
<ion-header>
  <ion-navbar>
    <ion-title>{{viewUserName}} 歡迎</ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
    <h1>{{mypeerid}}</h1>
      <ion-card>
        <ion-card-content>
            <video #myvideo></video>
        </ion-card-content>
    </ion-card>
    <ion-item *ngFor="let user of users | async"
        item="item" (click)="startCall(user.userName,user.peerID)">
        {{user.userName}} - {{user.$key}}</ion-item>

     <button ion-button full (click)="videoconnect()">開始通話</button>
     <button ion-button full (click)="stopCall(viewUserName)">結束通話</button>
</ion-content>

pages/home/home.ts
import { Component, ViewChild, OnInit } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { FirebaseListObservable } from 'angularfire2/database'; 注入fireBase的相關模組
import { UserService } from "../../providers/databaseservice/users";

declare var Peer: any;   宣告peer的物件
@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})

export class HomePage implements OnInit {
  title = 'app';
  mypeerid: any;
  peer;
  anotherid;
  key;
  @ViewChild('myvideo') myVideo: any取得myVideo的元素
  users: FirebaseListObservable<any[]> fireBase的陣列集合
  viewUserName:any;
  _streamCopy;
  constructor(public navCtrl: NavController, public navParams: NavParams, public userService: UserService) {
    this.users = userService.listUsers(); 取得資料users的集合
    this.viewUserName=navParams.data;
  }

  ngOnInit() {
    let video = this.myVideo.nativeElement;
    this.peer = new Peer({ host: 'peerjs-server.herokuapp.com', secure: true, port: 443 })
    this.mypeerid = this.peer.id;
    setTimeout(() => {
      this.mypeerid = this.peer.id;
       this.userService.addUser(this.viewUserName,this.peer.id );
    }, 1000);
     
    this.peer.on('connection', function (conn) {
      conn.on('data', function (data) {
        console.log(data);
      });
    });

    var n = <any>navigator;
    n.getUserMedia = (n.getUserMedia || n.webkitGetUserMedia || n.mozGetUserMedia || n.msGetUserMedia);
    this.peer.on('call', function (call) {
      n.getUserMedia({ video: true, audio: false }, function (stream) {
        call.answer(stream);
        call.on('stream', function (remotestream) {
          video.src = URL.createObjectURL(remotestream);
          video.play();
        })
      }, function (err) {
        console.log('Failed to get stream', err);
      })
    })
  }

  startCall(userName,anotherID) {
    this.anotherid = anotherID;
    var conn = this.peer.connect(anotherID);
    conn.on('open', function () {
      conn.send('Message from that id');
    });
  }

   this.peer.on('disconnected', function () {
console.log("disconnected");
});

  videoconnect(){
    let video = this.myVideo.nativeElement;
    var localvar = this.peer;
    var fname = this.anotherid;
    var n = <any>navigator;
    console.log("another peerID:"+this.anotherid);
    n.getUserMedia = ( n.getUserMedia || n.webkitGetUserMedia || n.mozGetUserMedia  || n.msGetUserMedia );
   
    n.getUserMedia({
      video:  { width: 700, height: 720 } , audio:false
    }, function(stream) {
      var call = localvar.call(fname, stream);
      call.on('stream', function(remotestream) {
        video.src = URL.createObjectURL(remotestream);
        video.play();
      })
    }, function(err){
      alert(err.name);
      console.log('Failed to get stream', err);
    })
  }

  stopCall(userName) {
    this.userService.removeUser(userName);
    this.peer.destroy();
  }
}

Ionic cordova build android

設定Android的權限

Platforms/android/ AndroidManifest.xml
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />


圖片示範



4 則留言:

  1. Here i have coming error,

    "ERROR Error: Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays.
    at NgForOf.ngOnChanges (common.js:2570)
    at checkAndUpdateDirectiveInline (core.js:12365)
    at checkAndUpdateNodeInline (core.js:13893)
    at checkAndUpdateNode (core.js:13836)
    at debugCheckAndUpdateNode (core.js:14729)
    at debugCheckDirectivesFn (core.js:14670)
    at Object.eval [as updateDirectives] (HomePage.html:14)
    at Object.debugUpdateDirectives [as updateDirectives] (core.js:14655)
    at checkAndUpdateView (core.js:13802)
    at callViewAction (core.js:14153)"

    回覆刪除
  2. 作者已經移除這則留言。

    回覆刪除
  3. 想詢問一下最近實作參考此文章用cordova與WebRTC , 完全教學文章內容一樣
    在打包Ionic cordova build android 會現錯誤訊息
    angularfire2 與 firebase 當時開發版本是多少??
    用最新版本會出錯 , 但是降本又錯其他訊息 ,一直無解
    是否可以再次更新一下內容?

    回覆刪除
    回覆
    1. {
      "name": "ionicwebRTC",
      "version": "0.0.1",
      "author": "Ionic Framework",
      "homepage": "http://ionicframework.com/",
      "private": true,
      "scripts": {
      "clean": "ionic-app-scripts clean",
      "build": "ionic-app-scripts build",
      "lint": "ionic-app-scripts lint",
      "ionic:build": "ionic-app-scripts build",
      "ionic:serve": "ionic-app-scripts serve"
      },
      "dependencies": {
      "@angular/common": "4.1.3",
      "@angular/compiler": "4.1.3",
      "@angular/compiler-cli": "4.1.3",
      "@angular/core": "4.1.3",
      "@angular/forms": "4.1.3",
      "@angular/http": "4.1.3",
      "@angular/platform-browser": "4.1.3",
      "@angular/platform-browser-dynamic": "4.1.3",
      "@ionic-native/core": "3.12.1",
      "@ionic-native/splash-screen": "3.12.1",
      "@ionic-native/status-bar": "3.12.1",
      "@ionic/storage": "2.0.1",
      "angular": "^1.3.0",
      "angularfire": "^2.3.0",
      "angularfire2": "^4.0.0-rc.2",
      "autoprefixer": "^7.1.4",
      "body-parser": "^1.18.2",
      "clean-css": "^4.1.9",
      "cordova-android": "^6.2.3",
      "cordova-browser": "^4.1.0",
      "cordova-plugin-device": "^1.1.4",
      "cordova-plugin-splashscreen": "^4.0.3",
      "cordova-plugin-statusbar": "^2.2.2",
      "cordova-plugin-webrtc": "^0.1.1",
      "cordova-plugin-whitelist": "^1.3.1",
      "cors": "^2.8.4",
      "express": "^4.15.5",
      "firebase": "^4.4.0",
      "ionic-angular": "3.6.1",
      "ionic-plugin-keyboard": "^2.2.1",
      "ionicons": "3.0.0",
      "magic-string": "^0.22.4",
      "node-sass": "^4.5.3",
      "postcss": "^6.0.12",
      "promise-polyfill": "6.0.2",
      "proxy-middleware": "^0.15.0",
      "rollup": "^0.50.0",
      "rollup-pluginutils": "^2.0.1",
      "rxjs": "5.4.0",
      "start": "^5.1.0",
      "sw-toolbox": "3.6.0",
      "tiny-lr": "^1.0.5",
      "tslint": "^5.7.0",
      "webpack": "^3.6.0",
      "ws": "^3.2.0",
      "zone.js": "0.8.12"
      },
      "devDependencies": {
      "@ionic/app-scripts": "^2.1.4",
      "fs-extra": "^4.0.2",
      "object-keys": "^0.2.0",
      "typescript": "2.3.4"
      },
      "description": "An Ionic project",
      "cordova": {
      "plugins": {
      "cordova-plugin-webrtc": {},
      "cordova-plugin-device": {},
      "cordova-plugin-splashscreen": {},
      "cordova-plugin-statusbar": {},
      "cordova-plugin-whitelist": {},
      "ionic-plugin-keyboard": {}
      },
      "platforms": [
      "android",
      "browser"
      ]
      }
      }

      刪除