Chia Yu Pai

Front-End, HTML5, Javascript, CSS3

Posts match “ javascript ” tag:

Javascript Getter / Setter

| Comments

Modern browsers' (IE9 up, IE8 only on DOM Object) Javascript Engine supported Getter / Setter for Object

IE6-8 could listen onpropertychange event

我們有兩個常見的方法去實作,例子如下:

Sample A:
透過 __defineSetter____defineGetter__ 預設方法榜定

Dog = function (name) {
    this.name = name || 'Doggy';
  
  // Define setter with __defineSetter__ method.

  this.__defineSetter__("name", function(newVal){
    this.bark('My new name is ' + newVal);
  });
}

Dog.prototype.bark = function (msg){
    msg = msg || "I'm " + this.name;
    alert('Bark! ' + msg);
}

Carl = new Dog();
Carl.name = Carl // Alert: Bark! My new name is Carl

Carl.name // Carl

Sample B:
直接使用 setget 關鍵字定義

Dog = function (name) {
    this._name = name || 'Doggy'; // Default name without alert.

}

Dog.prototype = {
    bark: function (msg) {
    msg = msg || "I'm " + this.name;
        alert('Bark! ' + msg);
  },
  // Define Setter

  set name(newVal) {
    this._name = newVal;
        this.bark('My new name is ' + newVal);
  },
  // Define Getter

  get name() {
    return this._name;
  }
}

Carl = new Dog();
Carl.name = Carl // Alert: Bark! My new name is Carl

Carl.name // Carl

透過 Setter / Getter 的設定可以輕鬆的監聽物件方法的改變,目前最常用到的非屬 two-way binding 的專案。可以直接用十分抽象的方法讓使用者改變 Javascript 中物件的屬性值來換取 DOM 的直接內容變化,達到驚人的易用水準。

Frameworks:

angular 1.2 ngRoute

| Comments

angular 在 1.16 版後將原本的 $route, $routeProvider, $routeParams 獨立出來
從 1.0.x 升級的使用者會無法直接使用原本的 Route Service
必須在對 app 引入 ngRoute module 方可使用
此外 ngRoute 的代碼也是獨立出來,不再包在 angular.js 之中

新的使用方法如下:

script(src='http://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.3/angular.min.js')
script(src='http://code.angularjs.org/1.2.0-rc.3/angular-route.min.js') 

<script>
angular.module('yourAppName', ['ngRoute'], function ($routeProvider){
    $routeProvider
    .when('/', {
        controller: yourController
      templateUrl: templateFileRoute
    });
});
</script>

WebRTC Peer to Peer With Socket.io

| Comments

知識

WebRTC 是最新的瀏覽器應用之一,提供 Web Application Client 彼此互連的功能實作基礎
在標準之中,主要有三大 API 項目

  1. MediaStream
  2. RTCPeerConnection
  3. RTCDataChannel

其中又以 RTCPeerConnection 為整個協定的基礎
WebRTC 就像是稍早的 WebSocket 一般,不同於 stateless HTTP 協定
而是較像是傳統語言的 Socket,使用較精簡的協定內容,讓反應速度可以提升到很高的水準
另外本文所有語法使用 Coffeescript 撰寫,相關訊息請洽官方網站

API

MediaStream

這個 API 其實就是 getUserMedia 的正式名稱,提供開發著一個統一的介面存取使用者的多媒體介面
舉凡視訊鏡頭、麥克風及螢幕節圖等,支援度在各大瀏覽器也算中上
需要多媒體應用的可以多加研究,在此因為非關 WebRTC 核心部分就不多提了
詳細用法可參考 MDN 的說明
Navigator.getUserMedia - MDN

RTCPeerConnection

如同其名,這個物件算是整個協定的核心項目
因為過去 WebRTC 的標準還沒訂定,網路上很多各種不同的資訊十分凌亂
基本上,當兩台 Client 需要做直接連線時,理想上我們可以直接透過 IP 互連的方式
取得最快的 Route,但是因為網際網路實際的發展並非如此
在我們的電腦前眾多的 NAT / IP Router 等打亂了簡單的 IP 直連可能
於是我們必須借助 STUN Server 替我們記錄真實的轉送流程
才能正常送到目標的對象手上,目前 Google 有提供一組免費的 STUN Server 如下
stun:stun.l.google.com:19302

除了 IP Route 的問題之外
我們要如何找到對方存在呢?使用 DHT 等無中央式的網路似乎可行
但是這樣會大幅拉長配對時間,就失去的 WebRTC 相較於傳統 HTTP 連線的最大優勢:速度
因此我們還是透過中央伺服器的方式協助我們的使用者做交換訊息的工作
在此就以 Socket.io 作為範例,解釋整個流程

首先,WebRTC 的 Client 端會分為 Offer 與 Answer 兩種角色
如同字面上意思 Offer 端為連線的發起者,Answer 則為被動方的角色
在啟用一個新的 WebRTC Connection 時,我們必須建立一個 Offer,並取得它的 Description

RTCPeerConnection = RTCPeerConnection or webkitRTCPeerConnection or mozRTCPeerConnection
# 提供 STUN Server
iceServers =
  iceServers: [{
    url: 'stun:stun.l.google.com:19302'
  }]

# 啟用 RTPDataChannel
optionalRtpDataChannels =
  optional: [{
    RtpDataChannels: true
  }]

# 建立一個新的 Connection
peer = new RTCPeerConnection iceServers, optionalRtpDataChannels

# 建立 Offer 方法
createOffer = ->
  # 建立 DataChannel 接管資料的傳輸
  dataChannel = peer.createDataChannel 'RTCDataChannel',
    reliable: false
  
  # 這是一些事件的監聽與錯誤檢測
  channel.onmessage = (event)->
    console.debug "offer receive a message #{event.data}"

  channel.onopen = ->
    console.debug 'channel open'

  channel.onclose = (e)->
    console.error e

  channel.onerror = (e)->
    console.error e
   
  # 透過 Peer 建立 Offer
  peer.createOffer (sessionDescription)->
    # 設定本地端 Description
    peer.setLocalDescription sessionDescription
    
    # 傳送回 Server 本地端的 Description
    socket.emit 'sourceOffer', 
      description: sessionDescription  
  , null, {}

createOffer 提供三個參數,首先當然是產生了 Offer Description 後的 callback 事件
第二個是如果產生失敗時的 Error callback,最後則是對於多媒體的限制與選項 MediaConstraints
別忘了,伺服器端需要使用 Socket.io 轉送這個 Description 給目標連線對象

接著,接收方則需要建立一個 Answer 去準備相關的回應

socket.on 'sourceOffer', (data)->
  # 接收時記得把原始物件轉換為 RTCSessionDescription 的實例
  answerSDP = new RTCSessionDescription data.description
  createAnswer answerSDP

# 建立 Answer 方法
createAnswer = (offerSDP)->
  # 一樣建立 DataChannel 接管資料的傳輸
  dataChannel = peer.createDataChannel 'RTCDataChannel',
    reliable: false

  ###
  略過事件監聽與除錯部分,請參考 Offer 的設定
  ###

  # 設定好遠端的 Description
  peer.setRemoteDescription offerSDP
  
  # 建立 Answer
  peer.createAnswer (sessionDescription)->
    # 一樣先儲存 Local Description
    peer.setLocalDescription sessionDescription

    # 把本地端的 Description 丟回給 Offer
    socket.emit 'sourceAnswer', 
      description: sessionDescription.description
  , null, {}

最後我們在回到 Offer 發起端

socket.on 'sourceAnswer', (data)->
  # 一樣轉成 RTCSessionDescription
  answerSDP = new RTCSessionDescription data.description
  # 設定為遠端的 Description
  peer.setRemoteDescription answerSDP

到這裡,我們已經完成基本的辨識
offer.localDescription = answer.remoteDescription
answer.localDescription = offer.remoteDescription
兩方各持有對方的 Description 描述

接著我們要使用 STUN Server 給我們的 candidate 建立連線
所以需要在 peer 加入監聽事件,將拿到的 candidate 轉送給另一方

# ICE
peer.onicecandidate = (event)->
  return if !peer or !event or !event.candidate
  socket.emit 'candidate', event.candidate

所以當然也有接收方

socket.on 'candidate', (data)->
  # 將 candidate 給 peer 讓他找到我們的路徑
  peer.addIceCandidate new RTCIceCandidate
    sdpMLineIndex: data.candidate.sdpMLineIndex
    candidate: data.candidate.candidate

至此連線就建立完成
複習一下,
Offer -> Answer -> Offer -> Offer Candidate <-> Answer Candidate

RTCDataChannel

最後其實關於它的基本東西都說完了
在連線建立起來後它負責的就是傳輸的部份了
透過 send 方法可以自由的在雙方傳送資訊
對方就會在 channel.onmessage 收到囉!


更多的問題歡迎留言討論
也可參考 W3C 官方的標準說明書

Angular Service Event

| Comments

AngularJS 的 Event 模組只能在 controller 中使用
當需要跨越不同的 controller scope 使用時
就無法直接使用內建的 $on, $emit 來實作 Event model
有兩種(或更多)解法來完成這樣的跨 controller 需求

Service variable and $scope.$watch

controller 的 $watch 可以監控 Service 的值
透過 injector service 便可做到不同 controller 之間的訊息溝通

angular.module('testApp')
  .controller('TestCtrl', function ($scope, Watcher) {
    $scope.$watch(function(){
      return Watcher.status;
    }, function (newStatus) {
        if (newStatus === 'done') {
        // ...do something

      }
    });
    // Remember:

    // if you want listen values in object, you have to add third parameter with true.

  )
  .controller('TestCtrl2', function ($scope, Watcher) {
    // ...some async code

    var callback = function () {
        Watcher.status = 'done';
    }
  )
  .service('Watcher', function () {
    this.status = null;
  
    return this;
  });
}

Service Scope

直接建立一個 Scope 來支援內建的 Event model
轉發他的 $on, $emit 來給其他 controller 使用

angular.module('testApp')
  .controller('TestCtrl', function ($scope, Watcher) {
    Watcher.$on('fetched', function () {
        // ...do something

    });
    
    // ...some async code

    var callback = function () {
        Watcher.$emit('fetched');
    }
  )
  .service('Watcher', function ($rootScope) {
    _scope = $rootScope.$new();
    
    this.$on = _scope.$on;
    this.$emit = _scope.$emit;
  
    return this;
  });