以前我們討論了ARI接口對DTMF事件的處理,其實ARI也支持對通道方面的處理方式,否則ARI就不能成為一個未來Asterisk接口通信的主要方式。Asterisk通過ARI接口可以獲得各種通道的狀態(tài)信息,然后通過ARI對其進行管理控制,按照用戶的業(yè)務(wù)需求做進一步的處理。首先我們簡單說明一下什么是通道和其狀態(tài)的含義。通道狀態(tài)是反映當(dāng)前介于Asterisk和終端設(shè)備之間的通信介質(zhì)的狀態(tài)。通道的狀態(tài)會影響所支持的操作,什么樣的通道狀態(tài)支持什么樣的通道操作,同時也會最終影響終端設(shè)備的操作執(zhí)行。
目前通道可以支持多種狀態(tài),基本常見的幾種狀態(tài)是:
- Down - 通信路徑存在,但是Asterisk和終端之間沒有任何媒體。
- Ringing - 設(shè)備正在振鈴,在Asterisk和設(shè)備之間可能存在媒體。
- Up - 設(shè)備之間已經(jīng)互相應(yīng)答。當(dāng)在UP狀態(tài)時,在Asterisk和設(shè)備之間的媒體可以雙向流動。
注意
某些通道介紹,例如Dahdi通道,可能支持了更多的狀態(tài) (例如,"Pre-ring" 或者 "Dialing Offhook")。當(dāng)處理通道狀態(tài)市,需要訪問Channel data model 的各種變量信息,F(xiàn)在我們主要介紹幾種不同的狀態(tài)處理方式和腳本語言的處理流程。
指示振鈴
Asterisk 可以通知設(shè)備對呼叫方進行振鈴,使用的方式是POST /channels/{channel_id}/ring 的操作模式,或者可以使用
DELETE /channels/{channel_id}/ring 的操作停止振鈴。這里用戶要注意,指示振鈴并不是真正從Asterisk到設(shè)備之間傳輸媒體。Asterisk僅是通知設(shè)備振鈴,但是終端是否振鈴取決于設(shè)備自己本身。
應(yīng)答通道
當(dāng)通道沒有應(yīng)答之前,Asterisk仍然沒有通知設(shè)備如何進行通信。應(yīng)答通道以后,Asterisk才完成了服務(wù)器端和設(shè)備端之間的通信路徑的創(chuàng)建,可以實現(xiàn)媒體的雙向流通。使用的方法是
POST /channels/{channel_id}/answer 操作。
對通道掛機
用戶可以對通道掛機,使用的是DELETE /channels/{channel_id} 操作方式。當(dāng)掛機以后,介于Asterisk和設(shè)備終端之間的通信會結(jié)束,通道離開Stasis的應(yīng)用程序。用戶的應(yīng)用程序通過一個 StasisEnd 事件被提醒。
設(shè)備掛機也是同樣的方式。在這種環(huán)境下,Asterisk和終端之間的通信結(jié)束以后,通道掛機,用戶程序通過一個StasisEnd 事件被通知通道離開了應(yīng)用程序。
一般情況下,一旦通道離開了用戶應(yīng)用程序,用戶就不能收到任何關(guān)于通道的事件信息。但是當(dāng)用戶程序訂閱了通道的所有事件時,無論通道是否在應(yīng)用程序中,如果通道確實掛機,用戶會收到一個ChannelDestroyed事件。
舉例:控制通道的狀態(tài)
這個實例ARI 程序會執(zhí)行以下步驟:
- 等待通道進入到一個Stasis 應(yīng)用程序。
- 當(dāng)通道進入到Stasis程序后,它會對通道指示振鈴,如果通道沒有準(zhǔn)備好振鈴,那么就讓通道現(xiàn)在開始振鈴。
- 幾秒鐘以后,通道應(yīng)答呼叫。
- 一旦通道應(yīng)答了呼叫,我們對通道啟動一個靜音的時段。幾秒鐘以后,通道掛機。
- 如果在這個過程中終端掛機,系統(tǒng)會平滑處理掛機流程。
- 撥號規(guī)則
以下?lián)芴枌嵗齼H表示通道進入到Stasis程序中:
extensions.conf
Python
這個例子中我們使用了ari-py 支持包。這個基本的架構(gòu)類似于channel-dump Python- 用戶可以參考這個實例來學(xué)習(xí)如何使用這個支持包連接ARI。
這里,我們首先需要創(chuàng)建我們的ARI客戶端,并且注冊三個不同的事件-StasisStart,ChannelStateChange,和StasisEnd。
很多流程的處理是在StasisStart中進行,當(dāng)通道進入到我們的應(yīng)用程序時,StasisStart就會被調(diào)用。大部分情況下,這里還要設(shè)置一個Python定時器來初始化通道中的一些指令。
當(dāng)通道應(yīng)答呼叫后,ChannelStateChange 句柄會打印出通道改變狀態(tài)的輸出信息。
最后,系統(tǒng)會把初始化的定時器權(quán)限,然后發(fā)出StasisEnd 的事件。當(dāng)通道離開應(yīng)用程序后,這個事件就會馬上被調(diào)用,通常情況下,可能是呼叫方掛機,或者我們自己掛機。
我們可以啟動一個通道的定時器:
然后注冊三個事件:
這里的StasisStart 事件需要用戶更多注意一下。
- 首先,我們通知通道指令,2秒鐘以后,應(yīng)答這個通道:
這里,我們在通道channel_timers 中存儲了一個定時器,如果用戶掛機以后,我們可以發(fā)出StasisEnd 的事件。
- 一旦程序執(zhí)行進入到answer_channel中,系統(tǒng)應(yīng)答這個通道,在通道中啟動一個靜音播放。注意,我們會繼續(xù)聲明一個answer_channel,這是一個在我們StasisStart 句柄內(nèi)嵌的函數(shù):
- 我們應(yīng)答了通道以后,系統(tǒng)會在4秒鐘內(nèi)啟動另外一個定時器對通道進行掛機處理。當(dāng)定時器被觸發(fā)后,它會調(diào)用hangup_channel。這是通道中最后的掛機程序。另外,我們會在StasisStart句柄中再次聲明hangup_channel:
當(dāng)創(chuàng)建了定時器以后,我們在通道中振鈴時,系統(tǒng)會把定時器信息存儲到程序中的channel_timers 中。在StasisEnd事件句柄中,我們可以取消掉定時器。如果通道離開了我們的應(yīng)用程序后,我們可以觸發(fā)這個定時器來執(zhí)行某些流程或告警信息,例如HTTP返回消息等。
最后,我們打印出在ChannelStateChanged 句柄中的通道狀態(tài)信息。這里打印出通道應(yīng)答的消息:
channel-state.py
完整的channel-state.py 源代碼:
channel-state.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
#!/usr/bin/env python
import ari
import logging
import threading
logging.basicConfig(level = logging.ERROR)
client = ari.connect( 'http://localhost:8088' , 'asterisk' , 'asterisk' )
channel_timers = {}
def stasis_end_cb(channel, ev):
"""Handler for StasisEnd event"""
print "Channel %s just left our application" % channel.json.get( 'name' )
# Cancel any pending timers
timer = channel_timers.get(channel. id )
if timer:
timer.cancel()
del channel_timers[channel. id ]
def stasis_start_cb(channel_obj, ev):
"""Handler for StasisStart event"""
def answer_channel(channel):
"""Callback that will actually answer the channel"""
print "Answering channel %s" % channel.json.get( 'name' )
channel.answer()
channel.startSilence()
# Hang up the channel in 4 seconds
timer = threading.Timer( 4 , hangup_channel, [channel])
channel_timers[channel. id ] = timer
timer.start()
def hangup_channel(channel):
"""Callback that will actually hangup the channel"""
print "Hanging up channel %s" % channel.json.get( 'name' )
channel.hangup()
channel = channel_obj.get( 'channel' )
print "Channel %s has entered the application" % channel.json.get( 'name' )
channel.ring()
# Answer the channel after 2 seconds
timer = threading.Timer( 2 , answer_channel, [channel])
channel_timers[channel. id ] = timer
timer.start()
def channel_state_change_cb(channel, ev):
"""Handler for changes in a channel's state"""
print "Channel %s is now: %s" % (channel.json.get( 'name' ),
channel.json.get( 'state' ))
client.on_channel_event( 'StasisStart' , stasis_start_cb)
client.on_channel_event( 'ChannelStateChange' , channel_state_change_cb)
client.on_channel_event( 'StasisEnd' , stasis_end_cb)
client.run(apps = 'channel-state' )
|
channel-state.py 實際輸出結(jié)果
這是我們終端“alice” 使用PJSIP通道進入到我們應(yīng)用程序時的輸出結(jié)果:
JavaScript (Node.js)
這個例子中我們使用了ari-client 支持包。這個基本的架構(gòu)類似于channel-dump JavaScript- 用戶可以參考這個實例來學(xué)習(xí)如何使用這個支持包連接ARI。
這里,我們首先需要創(chuàng)建我們的ARI客戶端,并且注冊三個不同的事件-StasisStart,ChannelStateChange,和StasisEnd。
很多流程的處理是在StasisStart中進行,當(dāng)通道進入到我們的應(yīng)用程序時,StasisStart就會被調(diào)用。大部分情況下,這里還要設(shè)置一個Python定時器來初始化通道中的一些指令。
當(dāng)通道應(yīng)答呼叫后,ChannelStateChange 句柄會打印出通道改變狀態(tài)的輸出信息。
最后,系統(tǒng)會把初始化的定時器權(quán)限,然后發(fā)出StasisEnd 的事件。當(dāng)通道離開應(yīng)用程序后,這個事件就會馬上被調(diào)用,通常情況下,可能是呼叫方掛機,或者我們自己掛機。
我們可以啟動一個通道的定時器:
然后注冊三個事件:
這里的StasisStart 事件需要用戶更多關(guān)注。
首先我們通知通道指令,2秒鐘以后應(yīng)答通道:
這里,我們在通道channel_timers 中存儲了一個定時器,如果用戶掛機以后,我們可以發(fā)出StasisEnd 的事件。
一旦程序執(zhí)行進入到answer_channel中,系統(tǒng)應(yīng)答這個通道,在通道中啟動一個靜音播放。注意,我們會繼續(xù)聲明一個answer_channel,這是一個在我們StasisStart 句柄內(nèi)嵌的函數(shù):
我們應(yīng)答了通道以后,系統(tǒng)會在4秒鐘內(nèi)啟動另外一個定時器對通道進行掛機處理。當(dāng)定時器被觸發(fā)后,它會調(diào)用hangup_channel。這是通道中最后的掛機程序。另外,我們會在StasisStart句柄中再次聲明hangup_channel:
當(dāng)創(chuàng)建了定時器以后,我們在通道中振鈴時,系統(tǒng)會把定時器信息存儲到程序中的channel_timers 中。在StasisEnd事件句柄中,我們可以取消掉定時器。如果通道離開了我們的應(yīng)用程序后,我們可以觸發(fā)這個定時器來執(zhí)行某些流程或告警信息,例如HTTP返回消息等。
最后,我們打印出在ChannelStateChanged 句柄中的通道狀態(tài)信息。這里打印出通道應(yīng)答的消息:
channel-state.js
完整的channel-state.js 源代碼:
channel-state.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
/*jshint node: true*/
'use strict' ;
var ari = require( 'ari-client' );
var util = require( 'util' );
var timers = {};
ari.connect( 'http://localhost:8088' , 'asterisk' , 'asterisk' , clientLoaded);
// handler for client being loaded
function clientLoaded (err, client) {
if (err) {
throw err;
}
// handler for StasisStart event
function stasisStart(event, channel) {
console.log(util.format(
'Channel %s has entered the application' , channel.name));
channel.ring( function (err) {
if (err) {
throw err;
}
});
// answer the channel after 2 seconds
var timer = setTimeout(answer, 2000);
timers[channel.id] = timer;
// callback that will answer the channel
function answer() {
console.log(util.format( 'Answering channel %s' , channel.name));
channel.answer( function (err) {
if (err) {
throw err;
}
});
channel.startSilence( function (err) {
if (err) {
throw err;
}
});
// hang up the channel in 4 seconds
var timer = setTimeout(hangup, 4000);
timers[channel.id] = timer;
}
// callback that will hangup the channel
function hangup() {
console.log(util.format( 'Hanging up channel %s' , channel.name));
channel.hangup( function (err) {
if (err) {
throw err;
}
});
}
}
// handler for StasisEnd event
function stasisEnd(event, channel) {
console.log(util.format(
'Channel %s just left our application' , channel.name));
var timer = timers[channel.id];
if (timer) {
clearTimeout(timer);
delete timers[channel.id];
}
}
// handler for ChannelStateChange event
function channelStateChange(event, channel) {
console.log(util.format(
'Channel %s is now: %s' , channel.name, channel.state));
}
client.on( 'StasisStart' , stasisStart);
client.on( 'StasisEnd' , stasisEnd);
client.on( 'ChannelStateChange' , channelStateChange);
client.start( 'channel-state' );
}
|
channel-state.js in action
這是我們終端“alice” 使用PJSIP通道進入到我們應(yīng)用程序時的輸出結(jié)果: