1 ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE Fakulta elektrotechnická Katedra řídící techniky Implementace VOIP modulu VOIP module implementation Diplomová ...
ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE Fakulta elektrotechnická Katedra řídící techniky
Implementace VOIP modulu
VOIP module implementation
Diplomová práce
Studijní program: Otevřená informatika Studijní obor: Počítačové inženýrství Vedoucí práce: Ing. Tomáš Novotný
Jakub Trmota
Praha 2012
Poděkování Rád bych poděkoval všem lidem, kteří se mi snažili připravit ideální podmínky na psaní této práce. Především bych zde chtěl vzpomenout ty, které jsem byl nucen kvůli psaní nejvíce zanedbávat. Ať už jde o moji skvělou maminku, kamarády, tak hlavně přítelkyni, která celý proces psaní odnášela nejvíce. Bez jejího pochopení bych práci nebyl schopen nikdy dopsat. Zároveň bych chtěl poděkovat svému zaměstnavateli, který mi při dokončovaní práce vyšel v zaměstnání opravdu vstříc. A nakonec nesmím zapomenout na vedoucího mé diplomové práce, který se mě snažil zdárně provést celým procesem tvorby a který mi byl vždy velice ochotně nápomocný při řešení všech problémů.
Prohlášení Prohlašuji, že jsem svou bakalářskou práci vypracoval samostatně a použil jsem pouze podklady (literaturu, projekty, SW atd.) uvedené v přiloženém seznamu.
3.1.2012 V Praze dne ……………………….
…………………………………… podpis
Abstract The work deals with the description of a creation process of a cross-platform VoIP module based on the open SIP protocol. The module is primarily intended for ExtBrain Communicator, but - thanks to the chosen technologies (JavaScript, NPAPI, ActiveX) - its use is possible in a wide variety of browsers and applications across all platforms. The work also includes description of both the module and its use in respective applications, as well as test results.
Abstrakt Práce se zabývá popisem tvorby multiplatformního VOIP modulu založeného na otevřeném protokolu SIP. Modul je primárně určen pro ExtBrain Communicator, ale díky zvoleným technologiím (JavaScript, NPAPI, ActiveX) je jeho využití možné v široké škále prohlížečů a aplikací napříč všemi platformami. Součástí práce je popis modulu, popis jeho použití v aplikacích a výsledky testování.
Struktura práce...............................................................................................................14 Zadání práce .......................................................................................................................15
2.1.
Zadání práce a pokyny pro vypracování .......................................................................15
2.3.1. zap!...........................................................................................................................16 2.3.2. Mozilla VoIP .............................................................................................................17 2.3.3. Linphone-js ..............................................................................................................17 3.
Realizace modulu ...............................................................................................................18 3.1.
Vysvětlení pojmů a popis použitých technologií .........................................................18
3.1.1. VOIP .........................................................................................................................18 3.1.2. SIP.............................................................................................................................19 3.1.3. SDP ...........................................................................................................................21 3.1.4. JavaScript .................................................................................................................23 3.1.5. Mozilla application framework ..............................................................................23 3.1.6. Knihovna pro přenos hlasu .....................................................................................24 3.1.7. FireBreath ................................................................................................................26 3.2.
Použité nástroje pro vývoj .............................................................................................27
3.2.1. Vývoj v JavaScriptu a XULu .....................................................................................27 3.2.2. Vývoj doplňku pro Mozilla Thunderbird ................................................................28 3.2.3. Studium SIPu ...........................................................................................................31 3.3.
Implementace C++ pluginu............................................................................................33
3.3.1. Kompilace PJSIP pod systémem Microsoft Windows ...........................................33 11
3.3.2. Kompilace PJSIP pod systémem Linux ...................................................................35 3.4.
Zapouzdření pluginu pomocí XPCOM ...........................................................................35
3.4.1. Tvorba pluginu pomocí XPCOM .............................................................................36 3.5.
Tvorba pluginu pomocí FireBreath ...............................................................................41
3.5.1. Vytvoření šablony FireBreath .................................................................................42 3.5.2. Kompilace projektu .................................................................................................43 3.5.3. Testování pluginu ....................................................................................................44 3.5.4. Debugování pluginu pod Windows ........................................................................44 3.5.5. Programování pomocí FireBreath ..........................................................................45 3.6.
ExtBrainSIP C++ plugin ...................................................................................................47
3.6.1. Použití PJSIP knihovny ve FireBreath .....................................................................49 3.6.2. Tvorba klientů..........................................................................................................51 3.6.3. Tvorba serverů ........................................................................................................52 3.6.4. Média .......................................................................................................................53 3.7. 4.
ExtBrainSIP JavaScript modul ........................................................................................54 Programátorská příručka ...................................................................................................56
4.1.
C++ plugin .......................................................................................................................56
4.1.1. ExtBrainSIPAPI .........................................................................................................56 4.1.2. UdpClient, TcpClient a TlsClient .............................................................................57 4.1.3. UdpServer, TcpServer a TlsServer ..........................................................................57 4.1.4. Media .......................................................................................................................59 4.2.
JavaScript modul ............................................................................................................64
4.2.1. Veřejné rozhraní......................................................................................................64 4.2.2. Objekt umožňující zpracování zpráv ......................................................................71 4.2.3. Tvorba SIP zpráv ......................................................................................................72 4.2.4. Použití modulu v aplikaci ........................................................................................73 12
5.
Ukázkové rozšíření .............................................................................................................75 5.1.
Programátorské informace ...........................................................................................75
5.1.1. Načtení modulu.......................................................................................................75 5.1.2. Zrušení modulu .......................................................................................................75 5.1.3. Zpracování událostí modulu ...................................................................................76 5.1.4. Práce s účty a seznamem hovorů...........................................................................76 5.1.5. Ostatní funkčnost ....................................................................................................77 5.2.
Příloha A - kódy vybraných SIP odpovědí .....................................................................85
8.2.
Příloha B - seznam chybových návratových kódů ........................................................86
8.3.
Příloha C - Obsah přiloženého DVD...............................................................................88
8.4.
Příloha D - Seznam použité literatury ...........................................................................89
13
1. Úvod Práce se zabývá tvorbou modulu pro VOIP komunikaci a tvorbou ukázkového doplňku, kde je prezentována funkčnost celého modulu. Při práci na modulu jsem nastudoval některé technologie, které bych zde rád popsal. Samozřejmě jsem také řešil spoustu problémů, které bych rád v práci prezentoval, aby moji následovníci nemuseli řešit problémy, se kterými jsem se trápil sám. Největší část práce se bude zabývat VOIP technologií, protokolem SIP a jejich implementací za pomoci jazyka javaScript a C/C++.
1.1.
Struktura práce Práce je rozdělena na několiv částí. První jsou spíše teoretické a zabývají se rozborem zadání,
popisem použitých technologií, použitých doplňků při programování a vysvětlují, jakou jsem zvolil pro modul výslednou strukturu. Další část je pak koncipována jako programátorská příručka a najdete v ní popis jak modul používat ve vlastních aplikacích. Nakonec si představíme výsledné ukázkové rozšíření a popis testování modulu. Většinu práce píši v první osobě množného čísla. Tím bych dal rád najevo, že většina rozhodnutí co se týče struktury aplikace nebyla pouze z mé hlavy, ale hodně podnětných myšlenek přišlo od vedoucího mé diplomové práce. Pouze části, které si můžu celé přivlastnit jsou psané v jednotném čísle. Veškeré ukázky zdrojového kódu budou v této práci uvedeny v novém odstavci a psané neproporciálním fontem, jak je vidět na následující ukázce: std::string printSourceCode(std::string code) { cout << makeNonProporcial(code); }
14
2. Zadání práce 2.1.
Zadání práce a pokyny pro vypracování 1. Nastudujte SIP (Session Initiation Protocol) a implementujte VOIP modul pro ExtBrain Communicator postavený na SIP. Pro přenos zvuku můžete využít existující knihovnu, avšak zvolenou knihovnu zapouzdřete tak, aby šla případně v budoucnu nahradit jinou knihovnou. 2. Pro modul vytvořte přívětivé uživatelské rozhraní. 3. Uživatelské rozhraní nechte důkladně otestovat uživateli. 4. Výslednou práci dobře zdokumentujte.
2.2.
Rozbor zadání VOIP modul je primárně určený pro ExtBrain Communicator. ExtBrain je výzkumný projekt,
jehož cílem je zjednodušit každodenní úkoly výzkumníků, vývojářů softwaru, vedoucím projektů a uživatelům, kteří potřebují získat, zpracovat a vyměňovat si jakékoliv informace. Při vývoji je upřednostňován rozvoj vizuálních nástrojů před implementací složitých algoritmů. Projekt se skládá ze čtyř hlavních částí: ExtBrain Communicator, ExtBrain Commander, ExtBrain Extractor a ExtBrain pro Android. Kromě toho existuje několik menších částí, jako je třeba rozšíření prohlížeče.1 ExtBrain Communicator je rozšíření, které se momentálně vyvíjí pro Mozilla Thunderbird. Implementuje například instant messaging2, správu poznámek a jejich sdílení s kolegy v reálném čase nebo společnou editaci diagramů. Program Mozilla Thunderbird a doplňky pro něj psané běží nad Mozilla application framework. Pro naprogramování modulu jsme jako hlavní jazyk zvolily JavaScript. V současné době je dostatečně univerzální a hlavně platformě nezávislý. Pro moduly, které není možné pomocí JavaScriptu implementovat (jedná se především o samotný přenos audia), jsme se rozhodli využít existující knihovnu a tu zapouzdřit technologií, která je platformě nezávislá (prozatím stačí funkčnost na Microsoft Windows a Linuxu) a umožní jednoduché použití z našeho modulu. Nejdříve jsme použili zapouzdření pomocí XPCOMu. Jelikož je tato technologie závislá na produktech Mozilly, rozhodli jsme se před dokončením práce dát před vylepšování GUI doplňku přednost přepsání 1
2
Zdroj http://extbrain.felk.cvut.cz/
internetová služba, umožňující svým uživatelům sledovat, kteří jejich přátelé jsou právě připojeni, a dle potřeby jim posílat zprávy, soubory apod.
15
pomocí univerzálnější technologie. Výběr padl na framework FireBreath3. Díky tomu je nyní možné využívat náš modul ve většině moderních prohlížečů.
2.3.
Konkurence SIP je otevřený komunikační protokol a je na něm postavena spousta softwarových klientů i
hardwarových telefonů. Jelikož měl být modul podle původního zadání postaven nad Mozilla application frameworkem, snažil se nejdříve porozhlédnout, jestli existuje nějaký podobný projekt. Bohužel jsem nic použitelného, z čeho bych mohl čerpat inspiraci nenalezl. Při přechodu na framework FireBreath jsem, objevil projekt, který se tomu našemu velice podobá - linphone-js.
2.3.1. zap! Jako první jsem nalezl projekt zap!4. Jedná se o implementaci SIPu nad Mozilla application frameworkem. Aplikace má otevřené zdrojové kódy distribuované pod licencí MPL/LGPL/GPL, povedlo se mi je přeložit pod Microsoft Windows i Linuxem. Aplikace nastartovala a dokonce se mi z ní podařilo provést zkušební spojení na testovací SIP uri. Dál jsem se už, bohužel nedostal, registrace na SIP server ani hovor přes jakéhokoliv SIP operátora se nezdařil. Aplikace se od roku 2008 nevyvíjí a ve stávající verzi mi přijde nepoužitelná.
2.3.2. Mozilla VoIP Jako druhý jsem nalezl projekt Mozilla VoIP5. Podle popisu se má jednat o SIP doplněk pro Mozilla Firefox a Mozilla Thunderbird. Bohužel projekt je už dávno zavřený a doplňky se nedají stáhnout.
2.3.3. Linphone-js Linphone je open-source multiplatformní SIP klient a knihovna. Linphone-js6 je plugin založený na frameworku FireBreath, který poskytuje linphone API JavaScriptu a tudíž umožňuje tvorbu SIP klienta přímo v prohlížeči. Tedy přesně to, co umožňuje i náš modul. Přesto, že je idea podobná, při prozkoumávání zdrojového kódu jsem zjistil, že nemá ani zlomek funkcionality našeho modulu a nakonec jsem z něj pro inspiraci nic nepoužil.
3.1.1. VOIP Pojmem VOIP (Voice over Internet Protocol) se rozumí přenos digitalizovaného hlasu prostřednictvím počítačové sítě nebo jiného média, prostupného pro protokol IP. Využívá se pro telefonování prostřednictvím internetu, intranetu nebo jiného datového spojení. Protokoly VOIP Pro přenos hlasu se používá na třetí vrstvě OSI modelu7 protokol IP, na čtvrté vrstvě protokol UDP. V těle jednotlivých UDP datagramů se kromě dalších údajů přenáší malý úsek telefonního hovoru, zakódovaný podle určitého pravidla (algoritmu) k dosažení úspory objemu přenášených dat. Kódovací a dekódovací algoritmy (zkráceně kodeky), mají různá označení (G.711, G.722, G.723, G.726, G.729, …) a jsou standardizovány a ze značné části i patentovány. Kvalitní kodek speciálně vyvinutý pro VOIP a neomezovaný softwarovými patenty je například SPEEX8 a kodek iLBC (tyto protokoly nejsou standardizovány organizací ITU). Kromě UDP datagramů, nesoucích o vrstvu výš v RTP zapouzdřené úseky vlastního hovoru, zahrnuje VOIP přenos ještě další pakety. Jsou to např. ICMP pakety a též datagramy TCP a UDP. Ty řídí přenos, nesou telefonní signalizaci, ověřují dostupnost komunikujících zařízení atd. Celá rodina VOIP protokolu není jediná, ale má řadu variant (implementací), lišících se podle standardu, použitého pro VOIP spojení. V současnosti jsou nejběžnější H.323, který je však na ústupu, a SIP. Používají se i speciální firemní protokoly, jako například Skinny (Cisco) nebo HFA (Siemens). Zajímavým protokolem je IAX2 - protokol softwarových ústředen Asterisk9. Obecně lze říct, že mají podobný přenos hovoru pomocí proudu krátkých úseků nesených v RTP, ale liší se ve službách a signalizaci.
3.1.2. SIP SIP (Session Initiation Protocol)10 je internetový protokol určený pro přenos signalizace nejčastěji v internetové telefonii (ale dá se použít pro jakoukoliv inicializaci relace). Standardně používá UDP přenos na portu 5060, ale může fungovat i nad protokolem TCP (rovněž na portu 5060), nebo šifrovaně pomocí protokolu TLS (defaultně na portu 5061). První verzi protokolu popisoval dokument RFC 2543, ale tato verze se již skoro nepoužívá. Současnou druhou verzi, kterou jsme implementovali v našem modulu popisuje RFC 3261. Protokol pro zajištění VOIP spojení pracuje v součinnosti s dalšími protokoly. Vlastní přenos hovoru se uskutečňuje pomocí protokolu RTP (nebo šifrovaně přes SRTP). Detaily o vlastnostech zahajovaného přenosu popisuje protokol SDP (Session Description Protocol)11, který je přenášen v těle SIP zpráv. Starší standard pro signalizaci v internetové telefonii H.323, byl vyvinut telekomunikační organizací ITU-T a je oproti SIPu výrazně složitější. Protokol SIP proto vznikl jako reakce na tento standard, a snaží se být co nejjednodušší a založený na internetem dobře prověřených principech. Proto vychází z osvědčeného protokolu HTTP a je mu velmi podobný. Také využívá položek podobných osvědčeným položkám SMTP protokolu pro posílání e-mailů. Pro vytvoření a řízení multimediální relace musí SIP zajistit následujících pět činností:
Lokalizace účastníka – nalezení spojení s koncovou stanicí
Zjištění stavu účastníka – zjištění, jestli je účastník schopen relaci navázat (může mít obsazeno, přesměrováno atd.)
Zjištění možností účastníka – zjištění, jaké jsou možnosti účastníka (typ kodeku, max. přenosová rychlost, audio/video atd.)
Vlastní navázání spojení – pomocí protokolu SDP, který popisuje navázané spojení a odkazuje na RTP datový tok
Řízení probíhajícího spojení – případné změny vlastností v průběhu relace a činnosti spojené s jejím ukončováním
Požadavky protokolu SIP je textově orientovaný protokol a metody (příkazy) se v něm píší velkými písmeny (podobně jako HTTP, ze kterého protokol vyšel). Mezi základní metody protokolu patří:
REGISTER – registrace účastníka na SIP Proxy serveru
INVITE – zahájení komunikace o plánované nové relaci
ACK – potvrzení zahájení relace nebo jiné operace
CANCEL – přerušení zahajovaní relace ještě před jejím navázáním nebo zrušení jiného požadavku
BYE – ukončení relace
OPTIONS – požádá o informace o možnostech volajícího, aniž by se sestavilo volání
Existuje několik rozšíření. Mezi ně patří třeba SIMPLE12 (SIP for Instant Messaging and Presence Leveraging Extensions), které také částečně implementuje náš modul. Toto rozšíření umožňuje sdílení stavu uživatele a zasílání zpráv. Rozšiřuje základní metody o tyto tři:
MESSAGE - zaslání zprávy
SUBSCRIBE - přihlášení se k odběru o změně stavu
NOTIFY - zaslání informace o změně stavu (stav je popsán v XML formátu)
Celé rozšíření je popsáno cca 16-ti RFC. Náš modul implementuje RFC 3428 pro zasílání zpráv a částečně RFC 3856 pro informovaní o stavu uživatele. Odpovědi protokolu Odpovědi na požadavky vycházejí opět z HTTP, a používají stovkové rozdělení. Vedle číselného označení mají jednotlivé odpovědi také textovou verzi (uvedené v příloze A). Jsou rozděleny do těchto kategorií:
1xx - průběh – požadavek probíhá bez problémů, ale ještě není ukončen
2xx - úspěch – požadavek byl ukončen bez problémů
3xx - přesměrování – požadavek probíhá, ale ještě se v souvislosti s ním něco očekává
12
4xx - chyba klienta – požadavek je chybný a nemůže být serverem zpracován
5xx - chyba serveru – požadavek je zřejmě v pořádku, ale chyba je na straně serveru
http://en.wikipedia.org/wiki/SIMPLE
20
6xx - fatální chyba – zcela fatální chyba, kterou nelze jakkoliv zpracovat
Příklad SIP komunikace při navázání relace SIP zařízení mohou navázat relaci přímo mezi sebou, ale obvyklejší je, že k tomu použijí jeden nebo několik SIP proxy serverů. Tyto servery navíc mohou plnit (a obvykle plní) funkci tzv. SIP registrátora, na kterém se jednotliví účastníci registrují. Díky tomu je možné propojit reálnou telefonní síť s VOIP pro příchozí hovory. Příklad komunikace je uveden na následujícím obrázku:
Obrázek 2 - Příklad SIP komunikace13
Protokol SIP jsem implementovat podle RFC 3261. K úspěšné implementaci mi velice pomohlo studování komunikace mezi již existujícími softwarovými telefony. Díky tomu jsem odhalil spoustu nedostatků vlastní implementace.
3.1.3. SDP Session Description Protocol je internetový protokol určený k popisu vlastností relace multimediálního přenosu dat. Nepřenáší se pomocí něj vlastní data, ale slouží pro vyjednání parametrů, jako je typ média (video, audio, atd.), transportní protokol (RTP/UDP/IP, H.320, atd.), typ kodeku nebo přenosová rychlost. Je popsaný v RFC 4566. 13
Převzato z http://cs.wikipedia.org/wiki/Session_Initiation_Protocol
21
Popis relace Relace je popsána řadou dvojic atribut-hodnota, vždy po jedné na řádku. Názvy atributů jsou jednopísmenné, následuje je '=' a hodnota atributu. Nepovinné atributy se označují pomocí '=*'. Hodnota je ASCII řetězec nebo posloupnost určitých značek oddělených mezerou. Název atributu je jedinečný v jedné ze tří oblastí (Session, Time nebo Media), ve které je uveden. Syntax SDP je rozšiřitelná a nové atributy jsou ke standardu příležitostně přidávány. Zkrácený přehled popisu relace: Session description v= (verze protokolu) o= (původce a identifikátor relace) s= (jméno relace) i=* (informace relace) u=* (URI popisu) e=* (e-mailová adresa) p=* (telefonní číslo) c=* (informace o spojení – není vyžadována, je-li zahrnuta v all media) b=* (0 či více řádek informací o šířce pásma) 1 či více time descriptions ("t=" a "r=" řádka; viz níže) z=* (úprava časového pásma) k=* (šifrovací klíč) a=* (0 či více řádků atributů relace) Time description t= (doba, po kterou je relace aktivní) r=* (0 či více počtu opakování) Media description, je-li přítomen m= (název média a transportní adresa) i=* (označení média) c=* (informace spojení – volitelné, je-li uvedeno na úrovni Session descr.) b=* (0 či více řádek informací o šířce pásma) k=* (šifrovací klíč) 22
a=* (0 či více řádků atributů relace) V našem modulu se o tvorbu SDP stará externí knihovna, my ho pouze posíláme v těle SIP zpráv.
3.1.4. JavaScript Všechnu funkčnost, u které to půjde, jsme se rozhodly implementovat v JavaScriptu. Poslední roky se zpracování JavaScriptu v prohlížečích stává čím dál rychlejší a výkonnější a samotný JavaScript se začíná používat i na místech, kde to asi ani samy tvůrci původně neplánovaly14. V našem modulu jsme v JavaScriptu nemohli implementovat následující:
zachytávání/přehrávání zvuku a jeho komprese/dekomprese kodeky
UDP přenosová vrstva (TCP a TLS vrstva by šla nahradit pomocí objektu XMLHttpRequest, ale implementace by pro nás nebyla ideální)
UDP/TCP/TLS server pro příchozí volání bez registrace
Podle původního zadání bylo možné využít některé XPCOM moduly, které Mozilla poskytuje. Jedná se hlavně o UDP, TCP a TLS klienta a TCP server. Tyto moduly jsou k dostání na všech platformách, které Mozilla podporuje. Pro základní implementaci SIP protokolu by tyto komponenty stačily. Pro přenos hlasu jsme však museli stejně použít nějakou existující externí knihovnu.
3.1.5. Mozilla application framework Jedná se o sadu platformě nezávislých softwarových komponent, nad kterými běží aplikace z dílny Mozilla (Firefox, Thunderbird, ...), ale i aplikace od jiných výrobců. Z těch známějších můžeme jmenovat například Instantbird15 nebo třeba projekt Pencil16. Sada obsahuje velké množství komponent, které můžeme využít při tvorbě doplňků nebo pro psaní vlastních aplikací, které je možné samostatně spouštět pomocí aplikace XUL runner17. Mezi komponenty Mozilla application frameworku, které budeme využívat při programování modulu patří:
Gecko - open source renderovací jádro pro vykreslování webových stránek. Je napsáno v programovacím jazyce C++ a licencováno pod trojlicencí MPL/GPL/LGPL.
14
Použití JavaScriptu na serverech - http://nodejs.org/ IM komunikační program - http://instantbird.com/ 16 Aplikace pro tvorbu diagramů a prototypování GUI - http://pencil.evolus.vn 17 Jedná se o runtime balíček, který umožňuje zavedení aplikací napsaných pomocí XUL a XPCOM 15
23
Díky licenci a podpoře webových standardů je renderovací jádro používáno v řadě jiných prohlížečů a aplikací. Původně bylo vytvořeno firmou Netscape Communications Corporation, ale nyní je vyvíjeno Mozilla Corporation. Gecko díky svému bohatému API nenabízí pouze možnost renderování webových stránek, ale slouží též k vykreslování grafického rozhraní (XUL). Jedná se o multiplatformní jádro, takže je k dispozici pro řadu platforem jako Microsoft Windows, Linux, Mac OS X a další.
XUL (XML User Interface Language) - jedná se o formát pro tvorbu multiplatformního grafického rozhraní. XUL staví na existujících webových standardech a technologiích jako XML, CSS, JavaScript a DOM.
XPCOM - systém objektů, obdoba MSCOM u Microsoftu. Definuje proces vytváření a rušení komponent, způsob přístupu k nim nebo jejich vlastnictví. V širším smyslu to je soubor rozhraní (interface) pro volání metod komponent.
XPConnect - umožňuje interakci mezi XPCOM objekty a JavaScriptem.
3.1.6. Knihovna pro přenos hlasu Požadavky na knihovnu jsou následující:
podpora přenosu hlasu, jeho de/kódování kodeky používanými v SIPu
podpora Microsoft Windows, Linuxu, případně Mac OS X
open-source licence
implementace v C/C++
Při hledání jsem narazil na spoustu implementací SIP knihovny, ale bez podpory přenosu hlasu, který se musí řešit další knihovnou. Bohužel tyto knihovny řešily přesně to, co budeme programovat v JavaScriptu. Pro přenos hlasu existuje velké množství komerčních knihoven (např.: NOSKI SIP Library18, nebo Juphoon SIP Protocol Stack19. V open-source prostředí je už výběr mnohem menší, nalezl jsem vlastně jenom jednu použitelnou knihovnu - PJSIP. Nejdříve, ale zmíním projekt, který by v budoucnu mohl tuto knihovnu v našem modulu nahradit - WebRTC.
WebRTC20 Jedná se o projekt, který se snaží přinést implementaci RTC (Real Time Communication) přímo do prostředí prohlížeče. Projekt podporují společnosti jako je Google, Mozilla a Opera a časem by se z něj mohl stát univerzální nástroj pro přenos audia a videa přímo z prohlížeče. Podporuje audio kodeky iSAC, iLBC a video kodek VP8. Pro síťovou komunikaci umí využít technologie ICE, STUN i TURN nebo RTP-over-TCP. Pro náš modul je tato knihovna bohužel prozatím nepoužitelná. Nepodporuje pro nás důležité kodeky a nelze pomocí ní vytvořit síťové transporty, které prozatím nejsou v JavaScriptu dostupné (řešení prozatím nebude asi technologie WebSockets21, protože zatím nepodporují pro SIP tak důležitý protokol jako je UDP). PJSIP Jedná se o univerzální SIP knihovnu skládající se z několika modulů (viz. obrázek 3). Každý z modulů se dá využít samostatně, což je pro nás účel ideální, jelikož potřebujeme pouze modul pro přenos audia (později jsme se rozhodli využít i moduly pro vytvoření transportů TCP, UDP a TLS). Knihovna je napsaná v jazyku C a je možné jí zkompilovat pro velké množství systémů. Kromě Microsoft Windows, Linux, Mac OS X i systémy založené na architektuře arm, alpha, powerpc a další. Pro Microsoft Windows jsou vytvořené projekty pro jednoduchou kompilaci pomocí Visual Studia 2005 nebo 2008 (verze 2010 není prozatím podporována).
Extrémně přenosná - stejný kód funguje na Microsoft Windows, Windows Mobile, Linux, Unix, Mac OS X, RTEMS, Symbian OS, Android apod.
Velice malá velikost zkompilovaného kódu
Vysoký výkon
Pro média - podpora konferencí, potlačení ozvěny, detekce ticha, generování tónů, speex/iLBC/GSM/G.711 kodeky, apod.
přenos paketů skrz NAT pomocí ICE, STUN a TURN
Knihovna je momentálně ve verzi 1.12, ale existuje už verze 2 ve stádiu beta, která kromě jiného slibuje podporu video přenosů. Na knihovně se stále aktivně pracuje a při práci na modulu jsme ji dvakrát aktualizovali na novější verzi. Obsahuje kvalitní dokumentaci pro všechny funkce a má vlastní mailingovou konferenci, kterou jsem sám aktivně při vývoji využíval. K dispozici je také velké množství příkladů, které vám pomohou se základními úkony. Knihovna je kompletně testovaná a testy jsou součástí zdrojového kódu. Co mi také usnadnilo rozhodování pro tuto knihovnu je skutečnost, že je na ní postavena spousta programů nejen pro Microsoft Windows, Linux a Mas OS X, ale i Apple iPhone a Google Android. Knihovna obsahuje několik vrstev. Používání těch nejvyšších je velice jednoduché. Bohužel, čím více se potřebujeme dostat do hloubky, což byl náš příklad, nelze už moc čerpat z dokumentace, kde jsou sice jednotlivé příkazy hezky popsané, ale už se nedočteme, co vše musíte připravit a inicializovat, abychom je mohli použít. Zde přichází na řadu mailingová konference, metoda pokusomyl a asi nejúčinnější metoda, procházení zdrojového kódu modulu pjsua, který je ukázkou implementace SIP klienta pomocí této knihovny.
3.1.7. FireBreath FireBreath je framework, který umožňuje velice rychlou a jednoduchou tvorbu pluginů pro prohlížeče. Takto vytvořený plugin poté funguje jako NPAPI plugin v prohlížečích jako je Firefox, Opera, Chrome (a ostatních prohlížečích založených na stejném jádru) a ActiveX komponenta (pouze Microsoft Internet Explorer). FireBreath vyžaduje pro tvorbu pluginů jazyk C++.
26
Framework umožňuje velice jednoduchou komunikaci mezi C++ a JavaScriptem a zároveň obsahuje datové typy pro ukládání JavaScriptových polí, objektů a variabilních prvků. Umí procházet DOM stránky a napojit se na některé události prohlížeče. Framework také umožňuje do okna pluginu vykreslovat grafiku. To by se mohlo hodit v budoucnu pro využití videohovorů.
3.2.
Použité nástroje pro vývoj
3.2.1. Vývoj v JavaScriptu a XULu Osobně neznám žádné vývojové prostředí speciálně určené k vývoji JavaScriptu. Sám jsem zvyklý, programovat v prostředí NetBeans a pro vývoj JavaScriptu a XULu jsem využil toto prostředí. Samotný NetBeans nemá žádný speciální typ projektu pro JavaScript a XUL. Existuje pro ně doplněk foxbeans, který má usnadňovat vývoj doplňků pro Mozillu. Ale přiznám se, že mě osobně spíš doplněk zdržoval od práce, tím, jak se snažil vše zautomatizovat a proto jsem ho nakonec nevyužíval a založil si obyčejný webový projekt, protože NetBeans samy o sobě umí pracovat se soubory v JavaScriptu a XULu (jde o obyčejný XML soubor). I když podpora JavaScriptu (napovídání kódu, kontrola syntaxe apod.) není na tak dobré úrovni, pořád vývoj urychluje.
Obrázek 4 - foxbeans
27
Při přepsání pluginu pomocí FireBreath jsem také pro testování a vývoj využíval Mozilla Firefox s nainstalovaným doplňkem FireBug22 a Google Chrome a jeho integrované ladící nástroje.
Obrázek 5 - Chrome Console
Obrázek 6 - FireBug
3.2.2. Vývoj doplňku pro Mozilla Thunderbird Pro aplikace Firefox a Thunderbird existuje spousta doplňků, které mají zároveň ulehčovat jejich vývoj. Pro svoji práci jsem nakonec využíval pouze tyto tři a program SQLite Administrator pro kontrolu databáze, kterou doplněk využíval.
22
http://getfirebug.com/
28
Console2 Jedná se o doplněk, který umožňuje přehledně zobrazovat chyby v JavaScriptu, XULu, CSS a podobně.
Obrázek 7 - Console2
DOM Inspector Tento doplněk se stará a procházení DOM (Document Object Model23) stránky, nebo i celé aplikace napsané v XULu. Vhodný pro kontrolu, zda je konkrétní prvek správně načten, jestli má správné vlastnosti a podobně.
XPCOMViewer Doplněk vhodný pro kontrolu načtených XPCOM komponent. Používal jsem ho při tvorbě původní XPCOM verze modulu. Po přepsání pomocí FireBreath už nebyl potřeba. Zároveň byl s tímto doplňkem problém, po přechodu jádra Gecko na verzi 2.0 a novější. Doplněk nestíhal reflektovat rychlé uvolňování nových verzí jádra a často nebyl delší čas funkční (zde bohužel nepomáhalo ani ruční povolení doplňku pro novější verzi jádra).
Obrázek 9 - XPCOMViewer
SQLite Administrator Umožňuje procházet strukturu a data SQLite databází, které dovoluje vytvářet Mozilla application framework a náš ukázkový doplněk je také využívá.
Obrázek 10 - SQLite Administrator
30
3.2.3. Studium SIPu Při studiu protokolu SIP jsem sice vycházel z dostupných RFC, ale často jsem se uchýlil i ke zpětnému inženýrství, kdy jsem zkoumal komunikaci mezi existujícími klienty, abych odhalil odlišnosti od mé implementace. Pro tuto činnost jsem používal vynikající program Wireshark. Jako testovací klienty jsem zvolil v diskusních fórech často zmiňovaný X-Lite a vedoucím práce doporučený Phoner. Občas jsem také zkoušel ukázkového klienta pjsua distribuovaného jako demonstrace knihovny PJSIP. Wireshark Program Wireshark24 slouží k zachytávání paketů na co možná nejnižší úrovni. Jelikož zachycených paketů je velké množství, umožňuje je program velice jednoduše filtrovat a to i na úrovní použité protokolu. Program jich zná několik set, takže stačí zadat jenom „sip“ do filtru zpráv a vidíte opravdu jenom zprávy protokolu SIP. Nakonec si můžete jednoduše zobrazit celý seznam odchozích a příchozích zpráv, tak jak šli za sebou. Toto mi při ladění mého modulu velice pomohlo a ušetřilo práci.
Obrázek 11 - Wireshark
24
http://www.wireshark.org/
31
X-Lite25 Program je dostupný v základní verzi zdarma. V té umožňuje nastavit pouze jeden účet a nepodporuje šifrované spojení. I tak mi byl nápomocen při vývoji, protože fungoval se všemi poskytovateli a pomohl mi odhalit některé nedostatky naší implementace SIPu.
Obrázek 12 - X-Lite
Phoner26 Freewarový program, doporučený vedoucím práce, který podporuje snad všechny potřebné SIP technologie. Při vývoji jsem ho využíval jako referenčního klienta.
pjsua27 Ukázková aplikace implementace PJSIP knihovny. Používal jsem jí často pro testování vlastností, které jsem sám chtěl využívat v pluginu.
Obrázek 14 - pjsua
3.3.
Implementace C++ pluginu Plugin je založen na knihovně PJSIP. Nejprve si popíšeme jak knihovnu zkompilovat pod
operačním systémem Microsoft Windows a Linux.
3.3.1. Kompilace PJSIP pod systémem Microsoft Windows Poznámka: na přiloženém DVD je již zkompilovaná verze knihovny pro Microsoft Windows ve verzi debug i release. Znovu kompilovat knihovnu má cenu pokud bychom chtěli něco změnit v její konfiguraci či při přechodu na novější verzi. Požadavky pro úspěšnou kompilaci Pro úspěšnou kompilaci je potřeba:
27
Aktuální zdrojový kód PJSIP (v době psaní verze 1.12)
Microsoft Visual Studio verze 2005 nebo 2008 (verze 2010 není podporována)
Microsoft DirectX SDK (verze June-2010 je k dispozici na přiloženém DVD)
http://www.pjsip.org/pjsua.htm
33
Náš plugin vyžaduje pro šifrovanou komunikaci knihovnu OpenSSL, potřebujeme tedy i OpenSSL SDK (opět na přiloženém DVD)
Postup kompilace 1. Rozbalíme si zdrojový kód PJSIP. 2. Ve Visual Studiu otevřeme solution v souboru pjproject-vs8.sln. 3. Pokud použijeme Visual Studio 2008, otevře se průvodce s migrací solution z verze 2005, stačí kliknout na Finish. 4. V Solution Exploreru vidíme množství projektů, pro úspěšnou kompilaci je nutné pro většinu z nich nastavit cesty k hlavičkovým souborům a knihovnám pro DirectX a OpenSSL. Abychom nemuseli toto nastavovat pro každý projekt zvlášť, doporučuji nastavit cesty pro celé Visual Studio. To provedeme následovně v menu: Tools Options... - Projects and Solutions - VC++ Directories. V roletce Show directories for: vybereme nejprve Include files a do seznamu přidáme cesty \include a \Include. Následně v roletce vybereme Library files a přidáme cesty \lib\VC\static (static proto, že nechce s pluginem distribuovat binární soubory OpenSSL) a \Lib\x86. 5. Nyní je nutné vytvořit konfigurační soubor pro aktuální build. To uděláme tak, že vytvoříme soubor config_site.h v adresáři \pjlib\include\pj. Může mít třeba následující obsah: // Maximum speed #define PJ_CONFIG_MAXIMUM_SPEED // SSL/TLS #define PJ_HAS_SSL_SOCK 1 #define PJSIP_HAS_TLS_TRANSPORT 1
6. Poslední dva řádky jsou důležité, aktivují podporu šifrovaného přenosu. 7. Tím máme vše připravené, nyní vybereme jako systém aktuálního buildu win32 a vybereme typ buildu. Pro spolupráci s FibeBreath musíme vybrat kompilaci oproti statickým knihovnám Microsoftu, tedy Debug-Static nebo Release-Static. 8. Kompilaci zahájíme z menu Build - Build solution. 9. Výsledná knihovna je poté v adresáři \lib
34
3.3.2. Kompilace PJSIP pod systémem Linux Kompilace knihovny v systému Linux je oproti Windows o něco jednodušší. Požadavky pro úspěšnou kompilaci V systému musíme mít nainstalovány následující komponenty. Nejsem schopen popsat jejich instalaci, postup se liší systém od systému:
GNU make
GNU binutils
GNU gcc
OpenSSL hlavičkové soubory a knihovny
Postup kompilace 1. Rozbalíme zdrojový kód PJSIP. 2. Otevřeme si adresář se zdrojovým kódem v konzoli a zadáme následující příkazy: 3. ./configure 4. Pokud chceme výsledný kód knihovny optimalizovat vytvoříme v adresáři se zdrojovým kódem
soubor
user.mak
a
do
něj
vložíme
následující:
export CFLAGS += -O3 -DNDEBUG -fno-builtin 5. make dep 6. make 7. make install (tato operace nainstaluje knihovnu do systému a proto na ni potřebujeme root práva)
3.4.
Zapouzdření pluginu pomocí XPCOM Původní zadání práce počítalo se zapouzdřením binárních komponent pomocí technologie
XPCOM. Když už byla práce ve funkčním stavu, rozhodli jsme se pro změnu, kterou popíši v následující kapitole, ale zde bych rád ve zkratce zmínil jak implementace pomocí XPCOMu vypadá, jaké jsem s ní měl problémy a hlavně, proč jsme se rozhodli pro změnu na FireBreath. Když jsem začínal na práci pracovat, byla aktuální verze jádra Gecko 1.9.x, ale už v tu dobu byla ve vývoji dlouho plánovaná verze 2.0, která upravovala některé postupy co se týče práce s XPCOM komponentami. Od vydání verze 2 se Mozilla rozhodla hlavně kvůli konkurenci na poli prohlížečů pro rychlejší vydávání hlavních verzí a sjednotila verzi jádra s verzí prohlížeče Firefox. 35
Všechny binární doplňky musí být kompilovány oproti aktuální verzi Gecko. Díky rychlejšímu vydávání nových verzí, to pro nás znamenalo častější překompilování pluginu oproti nové verzi jádra, i když nedošlo k žádné změně kódu. Další velká nevýhoda je, že XPCOM komponenty nejsou spouštěny ve vlastním procesu, to znamená, že když náš plugin spadne, shodí i program, ve kterém běží. Poslední velká nevýhoda a pro nás asi hlavní nevýhoda je samotná podstata XPCOMu a to závislost na Mozilla application frameworku. Chtěly jsme, aby plugin byl univerzální a šel používat na více platformách, než jen ten té od Mozilly. Všech těchto nevýhod jsme se zbavili přechodem na framework FireBreath. Ten dokáže vyprodukovat jeden binární soubor pro NPAPI plugin i ActiveX (pouze pod Microsoft Windows). Všechny moderní prohlížeče umí spouštět NPAPI pluginy ve vlastním procesu a tak při pádu pluginu nedochází k pádu prohlížeče.
3.4.1. Tvorba pluginu pomocí XPCOM Pro tvorbu doplňků existuje množství tutoriálů, hlavně pro verzi Gecka 1.9.x. Pro převod na verzi 2.0 a vyšší je potřeba udělat jenom pár úprav, které si zde také popíšeme. Jelikož už náš modul neobsahuje žádné XPCOM komponenty popíšu zde jejich tvorbu velice zjednodušeně. Pro zájemce je na přiloženém DVD i původní verze našich XPCOM komponent. 1. Nejdříve si musíme stáhnout Gecko SDK ve verzi, pro kterou je naše komponenta určena ze stránek Mozilla Developer28. 2. Každá naše komponenta potřebuje unikátní GUID29. 3. Nyní musíme vytvořit IDL30 soubor, který popisuje definici rozhraní. To obsahuje všechny funkce, které chceme mít veřejně přístupné. Do IDL souboru vložíme naše vygenerované GUID. Soubor může vypadat následovně: #include "nsISupports.idl" [scriptable, uuid(__GUID_)] interface IMyComponent : nsISupports
28
https://developer.mozilla.org/en/Gecko_SDK Jedná se o univerzální identifikátor Hezký generátor je k nalezení například zde http://mozilla.pettay.fi/cgibin/mozuuid.pl 30 https://developer.mozilla.org/en/XPIDL 29
36
{ long Add(in long a, in long b); };
4. Nyní z tohoto IDL souboru vygenerujeme utilitou xpidl, která je součásti Gecko SDK hlavičkový soubor a typovou knihovnu XPT (tento soubor poté musí být součásti distribuce modulu).
_DIR_ je celá cesta k adresáři xpcom/idl, který se nachází v Gecko SDK.
příkaz xpidl -m header -I_DIR_ IMyComponent.idl vytvoří hlavičkový soubor IMyComponent.h.
5. IMyComponent.h obsahuje šablonu pro hlavičkové (sekce /* Header file */) i implementační soubor (sekce /* Implementation file */). 6. Náš hlavničkový soubor MyComponent.h začneme podobně jako: #ifndef _MY_COMPONENT_H_ #define _MY_COMPONENT_H_ #include "IMyComponent.h"
7. Následně přidáme popis našeho modulu. Místo _GUID_ musíme vložit naše GUID ve formátu C++. (např.: { 0×12345678, 0×9abc, 0xdef0, { 0×12, 0×34, 0×56, 0×78, 0×9a, 0xbc, 0xde, 0xf0 } }): #define MY_COMPONENT_CONTRACTID "@example.com/XPCOMSample/MyComponent;1" #define MY_COMPONENT_CLASSNAME "A Simple XPCOM Sample" #define MY_COMPONENT_CID _GUID_
8. V souboru IMyComponent.h nahradíme všechny výskyty _MYCLASS_ jménem našeho modulu (MyComponent), zkopírujeme část ze sekce /* Header file */ do MyComponent.h a zakončíme ho #endif. 9. Nyní vezmene část mezi /* Implementation file */ a /* End of implementation class template. */ a vložíme ji do souboru MyComponent.cpp. Na začátku souboru musíme includovat soubor MyComponent.h: #include "MyComponent.h"
10. Nahradíme tělo metody Add funkčním kódem. Zaměníme: 37
Kompilace Tímto máme vše připravené pro kompilaci. Pro kompilaci v systému Microsoft Windows si založíme nový projekt a nastavíme mu následující vlastnosti: General Configuration Type: .dll C/C++ General Additional Include Directories: C:\xulrunner-sdk\include Preprocessor Preprocessor Definitions: XP_WIN;XP_WIN32;XPCOM_GLUE_USE_NSPR Linker General Additional Library Directories: C:\xulrunner-sdk\lib Input Additional Dependencies: nspr4.lib xpcom.lib xpcomglue_s.lib
38
Pro Linux je ukázkový Makefile na přiloženém DVD. Použití modulu V doplňku, ve kterém chceme modul použít, musíme do adresáře components zkopírovat soubory MyComponent.xpt a MyComponent.dll (.so v linuxu, automaticky se načte podle systému správná verze). Abychom modul načetli v JavaScriptu, použijeme následující kód: component = Components.classes["@example.com/XPCOMSample/MyComponent;1"]. createInstance(); component = components.QueryInterface(Components.interfaces.IMyComponent); alert(component.Add(3, 4)); // 7
Změny pro verzi Gecko 2.0 a vyšší Pro správné načtení modulu v rozšíření, musíme modul explicitně uvést v souboru chrome.manifest a to následovně: interfaces components/MyComponent.xpt binary-component components/MyComponent.dll binary-component components/MyComponent.so
Modul už se nelinkuje oproti knihovně xpcomglue_s.lib na Microsoft Windows a libxpcomglue_s.a na Linuxu a Mac OS X, ale oproti xpcomglue_s_nomozalloc.lib na Microsof Windows a libxpcomglue_s_nomozalloc.a na Linuxu a Mac OS X. Je potřeba toto změnit v nastavení projektu před kompilací. Dále je potřeba upravit soubor MyComponentModule.cpp, aby ho nové jádro správně načetlo a to takto: #include "mozilla/ModuleUtils.h" #include "nsIClassInfoImpl.h" #include "MyComponent.h" NS_GENERIC_FACTORY_CONSTRUCTOR(MyComponent) NS_DEFINE_NAMED_CID(MYCOMPONENT_CID);
Problémy při implementaci modulů Když odhlédnu od obecných problémů, které se týkají mé menší zkušenosti s jazykem C/C++ a kompilací pod Linuxem, narazil jsem na jeden větší problém, pro který jsem hledal složitě řešení. Jedná se o nemožnost volat callbacky v JavaScriptu z jiného než hlavního vlákna modulu. Abychom mohli callback zavolat i z jiných vláken, musíme využít rozhraní nsIProxyObjectManager a volat callback přes tuto proxy. Nejprve vytvoříme proxy manager. nsCOMPtr<nsIProxyObjectManager> pIProxyObjectManager(do_GetService("@mozilla.org/xpcomproxy;1", &nsStatus)); if(NS_FAILED(nsStatus)) // Error
Následně pro callback vytvoříme proxy na hlavní vlákno: 40
Nyní již voláme callback newCallback takto: newCallback->callbackFunction();
Nebyl problém přijít na to, jak ProxyObejctManager používat, ale byl problém zjistit, že tohle je řešení na můj problém. Další problém s XPCOM pro mě byla nemožnost jednoduše modul ladit. Nakonec jsem pro debugování použil logování do souboru, které, ale bylo dost nepraktické, zdlouhavé a spatně se na něm hledali chyby. Nakonec jsem se také náhodou při čtení jednoho starého článku na blogu knihovny PJSIP dočetl, že velké množství logovacích zpráv (využíval jsem logovací systém knihovny PJSIP) může způsobit snížení výkonu aplikace. A opravdu při vymazání logování se ztratily některé problémy, které jsme v aplikaci pozorovali (o tom více v sekci testování).
3.5.
Tvorba pluginu pomocí FireBreath Framework FireBreath se snaží maximálně ulehčit zpracování dat a událostí z JavaScriptu. K
tomu nabízí multiplatformní přístup a v jednom pluginu podporu mnoha prohlížečů:
NPAPI pro Microsoft Windows, Linux a Mac OS X: o Gecko/Firefox o Google Chrome o Apple Safari o Opera
ActiveX: o Microsoft Internet Explorer 6, 7, and 8
Překlad na systémech Microsoft Windows, Linux a MacOS X je zajištěn pomocí nástroje CMake. Ten dokáže z konfiguračních souborů vygenerovat pro systém Microsoft Windows projekt Visual Studia a pro Linux klasický Makefile. Nevýhoda použití Cmake je, že vytváří projekty s
41
absolutními cestami, tudíž není vhodné takto vygenerované projekty distribuovat spolu se zdrojovým kódem pluginu a je vhodnější připravit skripty, které projekty vygenerují na každém počítači. Potřebné nástroje pro úspěšnou kompilaci:
nainstalovaný nástroj CMake31 alespoň ve verzi 2.6.0 (pro Windows přiložen na DVD, pro Linux je nutné ho nainstalovat dle konkrétního systému)
Python pro generaci šablony nového pluginu
3.5.1. Vytvoření šablony FireBreath Z webu si stáhneme aktuální balík FireBreath a rozbalíme ho. Pro vytvoření prázdné šablony je nejjednodušší použít přiložený skript v jazyce Python příkazem: python fbgen.py
Průvodce nás provede celým procesem vytvoření doplňku, zadáme data jako název doplňku, verze, tvůrce apod. V adresáři \projects\[PluginName] poté máme vygenerovanou šablonu prázdného doplňku včetně skriptů pro překlad pomocí systému Cmake. Popis vygenerovaných souborů PluginConfig.cmake - nejdůležitější soubor, kde se odehrává nastavení 90% vlastností doplňku. Všechny věci, které jste zadali pomocí průvodce fbgen.py jsou uloženy v tomto souboru. V případě potřeby můžete tyto věci změnit. CMakeLists.txt - soubor s hlavním nastavením systému Cmake. Systémově závislé nastavení je includováno ze souborů Win/projectDef.cmake pro Microsoft Windows a X11/projectDef.cmake pro Linux. Factory.cpp - Obsahuje základní třídu FactoryBase pro váš plugin. Tuto metodu volá FireBreath pro vytvoření pluginu, obsahuje globální inicializační a deinicializační funkce (Plugin::StaticInitialize() a Plugin::StaticDeinitialize()). PluginNamePlugin.(cpp | h) - hlavní přístupný soubor vašeho projektu. Metoda createJSAPI vytváří objekt JSAPI (JavaScript object), který následně interaguje s prostředím JavaScriptu.
31
http://www.cmake.org/
42
PluginNamePluginAPI.(cpp | h) - Toto je defaultní třída, všechny veřejně přístupné funkce a události jsou definovány právě zde. V jednoduchosti se dá říci, že se jedná přímo o objekt JavaScriptu. Win/projectDef.cmake - speciální nastavení nástroje Cmake pro Microsoft Windows. X11/projectDef.cmake - speciální nastavení nástroje Cmake pro Linux. Název výsledného binární soubor vždy začíná „np“. To je důležité pro NPAPI pluginy, protože některé prohlížeče by je bez toho mohli odmítnout načíst.
3.5.2. Kompilace projektu Pod systémem Microsoft Windows Pro kompilaci pluginu je potřeba Visual Studio ve verzi 2005, 2008 nebo 201032. Dále je potřeba mít nainstalovaný systém Cmake a musí být dostupný ze systémové cesty. Nyní stačí zavolat skript prep2005.cmd, prep2008.cmd nebo prep2010.cmd a jako parametr mu předat adresář s pluginem. Např.: prep2008.cmd PluginName
Kvůli podpoře prohlížečů, které prozatím běží jenom na 32bitovém systému, je doporučeno kompilovat pluginy pouze 32bitové, i když existuje skript pro přípravu 64bitové doplňku v prostředí Visual Studio 2010. Skript vytvoří adresář build, který obsahuje soubor FireBreath.sln, otevřeme ho ve Visual Studiu a jeho kompilací získáme výsledný binární soubor pluginu. Ten nalezneme v adresáři build/bin/PluginName/Debug nebo Release. Pro zaregistrování pluginu do systému použijeme příkaz: regsvr32 npNazevPluginu.dll
Pro odregistrování potom: regsvr32 -u npNazevPluginu.dll
32
Jak použít Express edici http://www.firebreath.org/display/documentation/Building+with+Visual+Studio+Express
43
Pod systémem Linux Je potřeba mít nainstalovaný balíček Cmake (v debianu například: apt-get install cmake). Poté už jenom stačí spustit prep skript z adresáře FireBreath podobně jako v systému Microsoft Windows. prepmake.sh PluginName
Je vytvořen adresář buildex. V něm stačí spustit příkaz make a doplněk je zkompilován. Výsledný binární soubor je v adresáři build/bin/PluginName. Pro zaregistrování pluginu do systému musíme doplněk nakopírovat do adresáře ~/.mozilla/plugins/, kde systém hledá defaultně NPAPI pluginy. cp buildex/bin/PluginName/npPluginName.so ~/.mozilla/plugins/.
Pro odregistrování stačí soubor vymazat.
3.5.3. Testování pluginu Pro základní otestování pluginu je při kompilaci vytvořen testovací HTML soubor. Tento soubor doplněk načte v okně prohlížeče a vy ho můžete testovat. Pro tento účel doporučuji použít program Mozilla Firefox s doplňkem FireBug nebo Google Chrome s jeho integrovanou konzolí. Testovací soubor je v adresáři build[ex]/projects/PluginName/gen/FBControl.htm.
3.5.4. Debugování pluginu pod Windows Debugování na systému Windows je velice jednoduché. Otevřeme Visual Studio a stránku s pluginem v prohlížeči. V menu zvolíme Debug - Attach to Process a ze seznamu procesů vyberte ten, na který se má Visual Studio napojit (např. "firefox.exe"). Ve Firefoxu 4 a vyšším pluginy běží v samostatném procesu "plugin-container.exe". Visual Studio debugger se nyní zastaví na breakpointu, který vyberete, popřípadě nám ukáže kód, kde došlo k pádu pluginu. Při tom si můžeme prohlídnout hodnoty proměnných a ostatní věci, na které jsme zvyklí při ladění aplikací. Některé prohlížeče mohou pracovat ve více vláknech najednou (například Chrome). Pro vybrání správného procesu, který spustil váš plugin, nastavte breakpoint někde v kódu a zkontrolujte, zda je stále aktivní, když připojíte debugger k procesu. Pokud se totiž debugger připojí ke špatnému procesu, breakpoint se deaktivuje. To je pro nás signál vyzkoušet jiný proces.
44
3.5.5. Programování pomocí FireBreath Jako první při načtení doplňku musíme JavaScriptu předložit platný vstupní JSAPI object. Ten vytvoříme následovně. Vytvoříme si hlavičkový soubor pojmenovaný třeba MyPluginAPI.h: #include "JSAPIAuto.h" FB_FWD_POINTER class MyPluginAPI : public FB::JSAPIAuto { public: MyPluginAPI(); ~MyPluginAPI() {}; int add(int a, int b); int subtract(int a, int b); int multiply(int a, int b); int divide(int a, int b); };
Třída obsahuje konstruktor a čtyři metody. Tyto metody chceme zpřístupnit v JavaScriptu. Zaregistrujeme je tedy v konstruktoru pomocí metody registerMethod(). To uděláme v soubor MyPluginAPI.cpp takto: #include "MyPluginAPI.h" MyPluginAPI::MyPluginAPI() : FB::JSAPIAuto("MyPluginAPI object") { registerMethod("add",
make_method(this, &MyPluginAPI::add));
registerMethod("subtract",
make_method(this, &MyPluginAPI::subtract));
registerMethod("multiply",
make_method(this, &MyPluginAPI::multiply));
registerMethod("divide",
make_method(this, &MyPluginAPI::divide));
} int MyPluginAPI::add(int a, int b) { return a + b; } int MyPluginAPI::subtract(int a, int b) { return a - b; } int MyPluginAPI::multiply(int a, int b) { return a * b; }
45
int MyPluginAPI::divide(int a, int b) { return a / b; }
To je vše co musíme udělat. Nyní již můžeme metody používat přímo v JavaScriptu. Do HTML stránky vložíme plugin následovně:
Type se vždy skládá z „application/“ a pak následuje x-NázevPluginu, který jsme zadali při tvorbě pluginu. Naše metody poté využijeme následovně: var plugin = function() { return document.getElementById("plugin"); } plugin().add(3,5); // 8 plugin().multiply(3,5); // 15 plugin().divide(15,3); // 5 plugin().subtract(3,5); // -2
Dynamické typování JavaScript je dynamicky typovaný jazyk, na rozdíl od C++. FireBreath dokáže automaticky převádět některé typy. Například pokud funkce očekává na vstupu číslo, dokáže převést řetězec “5“ na číslo 5. Stejně tak, pokud se očekává na vstupu řetězec, je převeden číselný vstup na řetězec. Nepovinné parametry Vaše funkce mohou obsahovat volitelné parametry. Definují se velice jednoduše. Stačí jako typ uvést boost::optional. Např.: void MyPluginAPI::doSomething(int a, boost::optional b, boost::optional c) { if (b) b.get() // int value if (c) c.get() // bool value }
Objekty a funkce JavaScriptu Snadno se také přistupuje k objektům a funkcím JavaScriptu. Jedná se o datový typ FB::JSObjectPtr. Zde je příklad: 46
Pokud zavoláme tuto funkci z JavaScriptu například takto: plugin().doCallback(5, function(param1, param2, param3) { alert(param1); alert(param2); alert(param3); });
Vyskočí nám postupně okna s hodnotami 5, „Retezec“ a 3. Pokud místo funkce předáme objekt, zavoláme na FB::JSObjectPtr funkci InvokeAsync s prvním parametrem názvu funkce v objektu. Můžeme samozřejmě nejdříve otestovat, zda objekt v JavaScriptu tuto funkci vůbec obsahuje a to pomocí metody HasProperty("functionName"). Narozdíl od XPCOMu má FireBreath už v základu vyřešeno asynchronní volání callbacků z jiných vláken než z hlavního.
3.6.
ExtBrainSIP C++ plugin ExtBrainSIP C++ plugin zapouzdřuje pomocí frameworku FireBreath některé moduly z
knihovny PJSIP. Jedná se o následující:
PjMedia - pro přenos hlasu pomocí RTP, generaci SDP, RTP, SRTP, nahrávání a přehrávání audio souborů a další
UDP/TCP/TLS transport - pro tvorbu klientů a serverů
PjNath - pro umožnění komunikace skrze NAT33 a to pomocí protokolů ICE34, TURN35 a STUN36
Jde o využití technologií, které není možné naprogramovat pomocí JavaScriptu. Samotný plugin obsahuje jeden hlavní objekt - továrnu, který se stará o vytváření všech dalších objektů. Ty 33
http://cs.wikipedia.org/wiki/Network_address_translation Jednoduše se dá říct, že sdružuje protokoly STUN a TURN a vybírá použitelný pro konkrétní spojení 35 http://en.wikipedia.org/wiki/Traversal_Using_Relay_NAT 36 http://cs.wikipedia.org/wiki/STUN 34
47
jsou typu JSAPI, to znamená, že se jedná přímo o objekty využitelné v JavaScriptu. Struktura je na obrázku 15. Při vytváření nového klienta, se vytvoří nový objekt a ten továrna vrátí. Při vytváření serveru a médií se vytvoří pouze jeden nový objekt, ten se v továrně uloží a při dalším vyžádání serveru nebo médií se vrátí ten samý objekt - simulace singletonu37.
Obrázek 15 - Struktura pluginu ExtBrainSip
V souboru ExtBrainSIPAPI.cpp definujeme továrnu na vytváření objektů. Do JavaScriptu nevracíme přímo instance objektů, ale musíme si na ně vytvořit ukazatele. Pro každý typ objektu máme vlastní typ ukazatele. Ten si můžeme vytvořit pomocí makra FB_FORWARD_PTR() následovně: FB_FORWARD_PTR(UdpClient) // UdpClientPtr FB_FORWARD_PTR(UdpServer) // UdpServerPtr FB_FORWARD_PTR(TcpClient) // TcpClientPtr FB_FORWARD_PTR(TcpServer) // TcpServerPtr FB_FORWARD_PTR(TlsClient) // TlsClientPtr FB_FORWARD_PTR(TlsServer) // TlsServerPtr FB_FORWARD_PTR(Media)
// MediaPtr
Nyní již můžeme vytvářet objekty pomocí voláním boost::make_shared. Například nového UDP klienta vytvoříme následovně: boost::make_shared(&cp.factory);
37
http://cs.wikipedia.org/wiki/Singleton
48
Továrna má kromě své hlavní funkce ještě dvě doplňkové. Dokáže zjišťovat lokální IP adresu počítače a nastavit soubor pro logování. Zároveň inicializuje a deinicializuje celou PJSIP knihovnu a vytváří pro ní caching pool objekt, který se pak sdílí předáváním jednotlivým objektům. Tím se šetří systémové zdroje, protože si každý objekt nevytváří svůj. V řešení pomocí XPCOMu musel mít každý modul caching pool vlastní. Zdrojové kódy jednotlivých objektů jsou uložené v podadresáři Modules. Aby se správně zkompilovali pomocí Cmake, musíme upravit konfigurační soubor CMakeLists.txt a to následovným upravením sekce file (tučně jsou zvýrazněny nové řádky): file (GLOB GENERAL RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} [^.]*.cpp [^.]*.h [^.]*.cmake Modules/*.h Modules/Media/*.cpp Modules/Media/*.h Modules/TcpClient/*.cpp Modules/TcpClient/*.h Modules/TcpServer/*.cpp Modules/TcpServer/*.h Modules/TlsClient/*.cpp Modules/TlsClient/*.h Modules/TlsServer/*.cpp Modules/TlsServer/*.h Modules/UdpClient/*.cpp Modules/UdpClient/*.h Modules/UdpServer/*.cpp Modules/UdpServer/*.h )
Po jakékoliv změně konfiguračního souboru musíme znovu vygenerovat projekt pomocí prep skriptu!
3.6.1. Použití PJSIP knihovny ve FireBreath Abychom mohli úspěšně používat příkazy z PJSIP knihovny a následně vše přeložit do binárního doplňku, musíme správně nastavit Cmake konfigurační soubory pro každý systém, aby se projekty správně odkazovaly na knihovny a hlavičkové soubory. 49
Nastavení pro Microsoft Windows Nastavení knihoven provedeme v souboru Win/projectDef.cmake. Přidáme je pomocí příkazu target_link_libraries. Adresář pro includování hlavičkových souborů pak nastavíme pomocí include_directories. Pokud máme adresář s knihovnou PJSIP pojmenovaný pjproject a uložený ve stejné adresářové struktuře jako zdrojový kód pluginu, upravíme konfigurační soubor přidáním následujícího kódu: set(PJPROJECT_PATH ${FireBreath_SOURCE_DIR}/../pjproject) target_link_libraries(${PROJECT_NAME} debug "${PJPROJECT_PATH}/lib/libpjproject-i386-Win32-vc8-Debug-Static.lib") target_link_libraries(${PROJECT_NAME} optimized "${PJPROJECT_PATH}/lib/libpjproject-i386-Win32-vc8-Release-Static.lib") include_directories(${PJPROJECT_PATH}/include)
První příkaz nastaví cestu k adresáři do proměnné PJPROJECT_PATH. Následně je přilinkována knihovna pro debug a release build a nastaven adresář pro hledání hlavičkových souborů. Ještě je potřeba přilinkovat knihovny OpenSSL. To provedeme následovně: set(OPENSSL_PATH ${FireBreath_SOURCE_DIR}/../openssl) target_link_libraries(${PROJECT_NAME} debug "${OPENSSL_PATH}/lib/VC/static/libeay32MTd.lib") target_link_libraries(${PROJECT_NAME} debug "${OPENSSL_PATH}/lib/VC/static/ssleay32MTd.lib") target_link_libraries(${PROJECT_NAME} optimized "${OPENSSL_PATH}/lib/VC/static/libeay32MT.lib") target_link_libraries(${PROJECT_NAME} optimized "${OPENSSL_PATH}/lib/VC/static/ssleay32MT.lib") include_directories(${OPENSSL_PATH}/include)
Opět počítáme, že je OpenSSL knihovna na stejné adresářové úrovni jako adresář se zdrojovým kódem pluginu a je uložena v adresáři s názvem openssl. Nastavení pro Linux V Linuxu máme práci ulehčenou o linkování OpenSSL knihoven, protože PJSIP si je umí přilinkovat sám. Pro nastavení knihovny PJSIP musíme využít modul pkgconfig, který PJSIP v Linuxu využívá. Musíme nejprve pomocí něj ověřit. že je knihovna v systému opravdu nainstalovaná a následně 50
přidat do projektu adresáře, které nám tento nástroj vrátí. Všechno provedeme připsáním následujícího kódu do soubor X11/projectDef.cmake: pkg_check_modules(PJPROJECT REQUIRED libpjproject) link_directories(${PJPROJECT_LIBRARY_DIRS}) include_directories(${PJPROJECT_INCLUDE_DIRS}) target_link_libraries(${PROJECT_NAME} ${PJPROJECT_LIBRARIES})
3.6.2. Tvorba klientů Knihovna PJSIP zapouzdřuje použití soketů do několika vrstev což velice usnadňuje jejich použití. Podle toho jak moc chceme ovlivnit komunikaci, použijeme konkrétní vrstvu. Pokud nám stačí jednoduchý příjem a odesílání dat (což nám stačí) nemusíme chodit moc do hloubky. Pro využití protokolu UDP, TCP i TLS je proto tvorba klientů podobná a liší se pouze v použití několika jiných funkcí. Každý klient běží ve vlastním procesu. Při žádosti o spojení se předá cílová adresa, port a JavaScriptový objekt, který může obsahovat callbacky, které klient volá při spojení, uzavření spojení vzdálenou stranou (nelze u UDP klienta, tam se uzavření spojení z principu nedá detekovat), při příjmu dat a při výskytu chyby. Následně je vytvořen nový proces, který pracuje následovně: 1. Inicializuje vzdálenou adresu pomocí funkce pj_sockaddr_init(). 2. Vytvoří se objekt IO Queue (pj_ioqueue_create()), do kterého se ukládá veškerá komunikace a který musíme v cyklu kontrolovat, aby mohla komunikace probíhat. 3. Nyní můžeme vytvořit soket funkcí pj_sock_socket() (u TLS pj_ssl_sock_create()). 4. Vytvoříme aktivní spojení funkcí pj_activesock_create() (u TLS tento krok vynecháváme, aktivní spojení je vytvořeno automaticky v kroku 3). 5. Zahájíme
komunikaci.
UDP
funkcí
pj_activesock_start_recvfrom(),
TCP
pj_activesock_start_connect() a TLS pj_ssl_sock_start_connect(). U UDP klienta začíná čtení dat. U TCP a TLS se pokusíme soket spojit. Pokud se to povede nebo nastane chyba, zavolá se callback on_connect a pokud je spojení úspěšné zahájíme zde čtení dat u TCP pj_activesock_start_read() a u TLS pj_ssl_sock_start_read(). Při každé příchozí zprávě se volá callback on_data_read. 6. Pokud všechny příkazy proběhly bez problému, začne v cyklu (ukončí se při zavření spojení, ať už podnětem od uživatele nebo vzdálené strany) volat funkce
51
pj_ioqueue_poll(). Ta kontroluje IO queue a vyvolává příslušné callbacky pro vytvoření spojení a čtení zpráv. 7. Po ukončení cyklu po sobě klient uklidí. Zavře spojení a deinicializuje všechny použité proměnné. Každý klient má dále možnost zobrazit lokální a vzdálenou IP adresu a port, na který je socket napojen. Data se posílají zavoláním naší funkce send(). Ta volá u UDP klienta funkci pj_activesock_sendto(), TCP klienta pj_activesock_send() a u TLS klienta pj_ssl_sock_send().
3.6.3. Tvorba serverů Tvorba serverů je velice podobná tvorbě klientů. Hlavní rozdíl je v tom, že klient se aktivně připojuje ke vzdálenému serveru, kdežto server naslouchá na zvoleném portu a přijímá příchozí spojení (TCP a TLS) nebo rovnou příchozí pakety (UDP). Všechna aktivní spojení jsou udržována v poli o pevné velikosti. Velikost pole určuje maximální možný počet současných spojení. Při vytváření serveru předám port, na kterém má server naslouchat a JavaScriptový objekt, který obsahuje funkce, které se volají při příjmu nového spojení, při zavření spojení vzdálenou stranou (zase nelze u UDP), při příchozích datech a při výskytu chyby. UDP server U UDP serveru aktivuji přijímání paketů funkcí pj_activesock_start_recvfrom(). Přijde-li nový paket, podívám se do pole uložených spojení, jestli už takovou kombinaci zdrojové adresy a portu mám, pokud ano, mám spojení identifikované, pokud ne, pokusím se ho do pole přidat. Pokud se nevejde (je vyčerpán maximální počet spojení), paket nezpracuji. Jinak zavolám callback JavaScriptu s identifikací spojení a přijatými daty. Pokud se jedná o nové spojení zavolám nejprve callback s oznámením o novém spojení. Pokud chci data poslat, předám serveru kromě samotných dat i identifikaci spojení. Data se odesílají pomocí funkce pj_activesock_sendto(). TCP a TLS server U těchto serverů se uchovává aktivní otevřené spojení a toto spojení přímo ukládám do pole. Pokud přijde paket, mám informaci jakým spojením dorazil, můžu se podívat do pole a podle toho spojení identifikovat nebo přidat nové (je-li to možné) a zavolat callback v JavaScriptu. Inicializace 52
naslouchání se aktivuje funkcemi pj_activesock_start_accept() pro TCP a pj_ssl_sock_start_accept() pro TLS. Při novém příchozím spojení se zavolá callback s informací o úspěšném spojení.
3.6.4. Média Média se starají o celou audio vrstvu aplikace. Umí:
nahrávat a přehrávat WAV soubory
zjišťovat a nastavovat vstupní a výstupní audio zařízení systému
generovat SDP pro zvolené kodeky a při předání vzdáleného SDP inicializovat audio relaci o Podporují protokol RTP i šifrovaný SRTP o UDP přenos i přenos pomocí ICE/STUN/TURN
Při používání je potřeba nejprve inicializovat media endpoint, to uděláme funkcí pjmedia_endpt_create(). Abychom mohli používat mikrofon i reproduktor společně pro více operací zároveň, inicializujeme i conference bridge. Ten umožňuje přidávat různé „porty“ (označení použité v knihovně PJSIP). Port může být třeba jeden přehrávač souborů, rekordér, port je i samotná audio relace. Po vložení portu do conference bridge můžeme spojovat jednotlivé porty mezi sebou (dokonce zvlášť určovat směr spojení). Díky tomu můžeme třeba realizovat i konference, když spojíme několik audio relací dohromady. Pro přidání portu jsem vytvořil pomocnou funkci addPort(). Ta vrací identifikaci vloženého portu v conference bridge, pokud se vložení povede. Pomocí této identifikace
poté
můžeme
jednotlivé
porty
jednoduše
spojovat
pomocí
funkce
pjmedia_conf_connect_port() nebo rozpojovat pjmedia_conf_disconnect_port(). První pozice v conference bridge je rezervovaná pro připojení mikrofonu a reproduktoru. I když je rezervovaná, musíme zvukový port, který dokáže mikrofon a reproduktor ručně vytvořit a do conference bridge je ručně připojit. Pokud bychom toto nechali na samotné knihovně, nemohli bychom takový zvukový port jednoduše upravovat. Náš modul umožňuje mít aktivních více účtů. Aby každý účet mohl mít své vlastní nastavení pro média (třeba jiný typ přenosu UDP/ICE), jsou tyto účty reflektovány i zde. Pro uskutečnění hovoru, musí být založen alespoň jeden účet pomocí funkce AddAccount(). Při založení se mu předají vlastnosti a poté se inicializuje konkrétní SDP kód, který můžeme poslat v SIP zprávě. Toto je rozdíl třeba oproti ukázkové aplikaci pjsua, ta sice také umožňuje mít připojených více účtů zároveň, ale toto nereflektuje na mediální vrstvě a všechny hovory pak sdílí stejné nastavení. 53
Pokud chceme provést hovor, vytvoří se nová relace nad zvoleným účtem. Do této relace se předá vzdálený SDP kód a knihovna se pokusí provést spojení. Momentálně je knihovna nastavena tak, že počet možných relací je omezen na počet účtů. Protože každý účet má možnost mít pouze jednu aktivní relaci. Každý účet si také uchovává informaci jaká session u něj právě běží. Pokud bychom chtěli mít možnost více relací zároveň na jednom účtu, toto chování by se zde muselo upravit. Více o použití tohoto objektu je uvedeno v programátorské příručce.
3.7.
ExtBrainSIP JavaScript modul Zde v krátkosti popíšu strukturu JavaScriptového kódu modulu. Ten je rozdělen do několika
modulů a ty jsou rozděleny do souborů podle určení. Celá struktura je nejlépe patrná z obrázku 16. Více informací k použití je k dispozici v programátorské příručce. Každá barva na obrázku zdůrazňuje provázané moduly a značí jeden JavaScriptový zdrojový soubor.
Obrázek 16 - ExtBrainSIP JavaScript struktura modulu
Jediný objekt, který by měl být veřejně přístupný aplikaci je objekt Sip. Ten má veřejné rozhraní, které umožňuje provádět všechny operace, které modul umožňuje vykonávat. Modul dává aplikaci vědět pomocí událostí, k jejichž odebírání se může aplikace přihlásit. Objekt Sip má také na starosti správu účtů. Každý účet může mít svého vlastního registrátora (Registrar), který má na starosti registraci vašeho účtu oproti SIP serveru. Dále může mít každý účet jeden dialog (Dialog), ten se stará o samotné hovory a jeden objekt Simple, který obhospodařuje instant messaging a kontrolu
54
stavu. Nakonec má každý účet vlastní napojení na Media skrze MediaAccount, který do dialogu vytváří konkrétní MediaSession. Samostatně objekt SIP umožňuje využívat vlastnosti objektu Media. Mezi ně patří hlavně správu kodeků, nahrávaní a přehrávání audio souborů nebo správu a nastavení audio zařízení v systému. Objekty Registrar, Dialog a Simple umožňují odesílání a příjem zpráv. Kvůli tomu je potřeba tyto objekty zaregistrovat do Sipu a objektu Message. Každý takový objekt má v sobě mechanismus, který je schopen se do systému správně zaregistrovat. V Sipu se zaregistruje pro zpracování došlých zpráv a do objektu Message předají své továrny na tvorbu SIP požadavků a odpovědí. Pokud chceme odeslat nějakou zprávu, je třeba zprávě předat platný Transport. Ten můžeme buď vytvořit (Client), o to se stará Sip objekt, při volání konkrétní akce. Načte se typ transport z účtu, vytvoří se a předá objektu, který ho použije při vytváření nové zprávy. Nebo můžeme jako transport použít objektu zděděný od ServerClient, což je jedno aktivní spojení na serveru a vytvoří se automaticky při příchozím spojení. Client i ClientServer sdílejí společné rozhraní Transport. Tomu se v konstruktoru musí předat odkaz na hlavní objekt Sip a jeho metodu pro parsování došlých dat. Ty jsou v objektu Message zparsována do samostatných objektý Request a Response a předány znovu hlavního objektu Sip ke zpracování. Funkce Sip.processMessage poté vezme všechny objekty, které jsou zaregistrované ke zpracování došlých zpráv a postupně se jednoho po druhém zeptá, zda je schopný zpracovat danou zprávu. Pokud objekt odpoví kladně, zpráva je mu předána a objekt Sip se o ní dále již nestará. Pokud se zpráva nedokáže předat žádnému objektu, je na ní vrácen chybový kód, abychom druhou stranu informovali, že nejsme schopni zpracovat její žádost. Taková struktura byla udělána hlavně kvůli tomu, aby se každý objekt staral opravdu jenom o to, co mu náleží a neměl informace o věcech, které k němu logicky nepatří. Například hlavní objekt Sip nemá vědet jak se parsují zprávy, o to se stará objekt Message. Ten sice naopak umí také zprávy vytvořit, ale proč by měl vědět, jak se vytvoří požadavek INVITE, když by to měl vědět objekt Dialog, který tyto požadavky obhospodařuje. Message tedy umí vytvořit šablonu zprávy a Dialog mu předá továrnu, která doplní zbytek dat. Díky této struktuře je náš modul velice jednoduše rozšiřitelný. O spojování audio relací se stará objekt MediaSession. Ten dokáže komunikovat s objektem Media z pluginu a kromě spojení umí třeba dočasně pozastavit relaci nebo relaci ukončit.
55
4. Programátorská příručka 4.1.
C++ plugin Jedná se o popis funkcí využitelných v JavaScriptu.
4.1.1. ExtBrainSIPAPI bool setLogFile(std::string filename) Nastavení logování do souboru. Momentálně je aktivní jenom logování chyb. Velké množství logovacích zpráv snižuje výkon modulu! -
filename : cesta k souboru
char* getLocalIP() Vrátí lokální IP adresu. UdpClientPtr createUdpClient(
Vytvoří a vrátí nový objekt UdpClient. UdpServerPtr getUdpServer()
Vrátí objekt UdpServer („singleton“). TcpClientPtr createTcpClient()
Vytvoří a vrátí nový objekt TcpClient. TcpServerPtr getTcpServer()
Vrátí objekt TcpServer („singleton“). TlsClientPtr createTlsClient()
Vytvoří a vrátí objekt TlsClient. TlsServerPtr getTlsServer()
Vrátí objekt TlsServer („singleton“). MediaPtr getMedia()
Vrátí objekt Media („singleton“).
56
4.1.2. UdpClient, TcpClient a TlsClient short connect(std::string address, int port, const FB::JSObjectPtr& callbacks, boost::optional<std::string> fileCertCa, boost::optional verifyPeer) Započne spojení. Vrací 0 při úspěchu, jinak záporné číslo s popisem chyby.
address : adresa k připojení
port : port k připojení
callbacks : JavaScript objekt s callback funkcemi
{ connect() : volá se při úspěšném spojení readData(data) : byla přijata data close() : volá se při zavření spojení vzdálenou stranou (pouze TCP a TLS) error(errorNo) : volá se při chybě a předá se číslo chyby }
fileCertCa: cesta k certifikátu (pouze TLS)
verifyPeer: zda má klient ověřit certifikát serveru (pouze TLS)
bool isConnected() Vrací zda je klient připojený. short send(std::string data) Odešle data. Vrací 0 při úspěchu, jinak záporné číslo chyby.
data : data k odeslání
int getLocalPort() Vrací číslo lokálního portu spojení. std::string getRemoteAddr() Vrací IP adresu hosta. int getRemotePort() Vrací číslo portu hosta. short close() Zavře spojení
4.1.3. UdpServer, TcpServer a TlsServer short init(int port, const FB::JSObjectPtr& callbacks, boost::optional<std::string> fileCertCa,
57
boost::optional<std::string> fileCert, boost::optional<std::string> fileCertPrivKey, boost::optional<std::string> passCertPrivKey, boost::optional requireClientCert) Začne naslouchat na zvoleném serveru. Vrací 0 při úspěchu jinak záporné číslo chyby.
port : port pro naslouchání
callbacks : JavaScript objekt s callback funkcemi
{ accept(id) : volá se při úspěšném spojení s klientem s id readData(id, data): od klienta s id byla přijata data close(id) : klient s id zavřel spojení (pouze TCP a TLS) error(errorNo) : volá se při chybě a předá se číslo chyby }
fileCertCa : cesta k certifikátu (pouze TLS)
fileCertPrivKey : cesta k soukromému certifikátu (pouze TLS)
passCertPrivKey : heslo k soukromému certifikátu (pouze TLS)
verifyPeer : zda má klient ověřit certifikát serveru (pouze TLS)
bool isListening() Vrací, zda server naslouchá. bool isConnected(short connectionId) Vrací, zda je spojení aktivní.
connectionId : id spojení
short send(short connectionId, std::string data) Odešle zprávu. Vrací 0 při úspěchu, jinak záporné číslo chyby.
connectionId : id spojení
data : data k odeslání
int getLocalPort() Vrací číslo portu, na kterém server naslouchá. std::string getRemoteAddr(short connectionId) Vrací IP adresu vzdáleného hosta.
connectionId : id spojení
int getRemotePort(short connectionId) Vrací port vzdáleného hosta.
connectionId : id spojení
58
short close(short connectionId) Zavře spojení. Vrací 0 při úspěchu jinak záporné číslo chyby. short destroy() Ukončí naslouchání serveru. Vrací 0 při úspěchu, jinak záporné číslo chyby.
4.1.4. Media short init(int port) Iniciaizuje média. Vrací 0 při úspěchu jinak záporné číslo chyby.
port : počáteční port, na kterém se začnou inicializovat jednotlivé účty
short destroy() Zavře média. Vrací 0 při úspěchu jinak záporné číslo chyby. short getMaxAccounts() Vrátí maximální možný počet účtů. short addAccount() Přidá nový účet. Vrací jeho id při úspěchu jinak záporné číslo chyby. short removeAccount(short accountId) Vymaže účet. Vrací 0 při úspěchu jinak záporné číslo chyby.
accountId : číslo účtu k vymazání
short initTransport(short accountId, bool useIce, FB::JSObjectPtr initializedCallback) Inicializuje transportní vrstvu pro média. Vrací 0 při úspěchu jinak záporné číslo chyby.
accountId : číslo účtu
useIce : zda se má použít ICE vrstva
initializedCallback : pokud se má použít ICE vrstva, probíhá její inicializace asynchronně, při jejím dokončení se zavolá tento callback s jedním parametrem, který obsahuje true při úspěšné inicializaci, jinak false
short setStun(short accountId, bool useStun, std::string server, int port) Nastaví STUN server pro použití s ICE transportem. Vrací 0 při úspěchu jinak záporné číslo chyby.
accountId : číslo účtu
useStun : použít STUN (true) nebo nepoužít (false)
server : adresa STUN serveru
59
port : port STUN serveru
short setTurn(short accountId, bool useTurn, std::string server, int port, std::string username, std::string password) Nastaví TURN server pro použití s ICE transportem. Vrací 0 při úspěchu jinak záporné číslo chyby.
accountId : číslo účtu
useTurn : použít TURN (true) nebo nepoužít (false)
address : adresa TURN serveru
port : port TURN serveru
username : uživatelské jméno
password : heslo
std::string getLocalSdp(short accoutnId) Vrací SDP pro účet nebo prázdný řetězec při chybě nebo neinicializovaném transportu.
accountId : číslo účtu
short getMaxSessions() Vrací maximální počet možných audio relací. short createSession(short accountId, short srtp, boost::optional<std::string> remoteSdp) Vytvoří pro účet novou relaci. Vrací číslo relace při úspěchu jinak záporné číslo chyby.
accountId : číslo účtu
srtp : použít šifrování, 0 - nepoužít, 1 - pouze pokud je vyžadováno, 2 - vynutit použití - pokud ho druhá strana nepodporuje, spojení se nezdaří
remoteSdp : pokud jsme volaný, máme k dispozici již SDP volajícího a předáme ho
short connectSession(short sessionId, boost::optional<std::string> remoteSdp) Spojí vytvořenou relaci se vzdálenou stranou. Vrací 0 při úspěchu jinak záporné číslo chyby.
sessionId : číslo relace
remoteSdp : pokud jsme volající až nyní máme SDP druhé strany, zde ho předáme
short playRingbackTone(short sessionId) Pustí do sluchátka vyzváněcí tón. Vrací 0 při úspěchu jinak záporné číslo chyby.
sessionId : číslo relace
short stopRingbackTone(short sessionId) Vypne vyzváněcí tón ve sluchátku. Vrací 0 při úspěchu jinak záporné číslo chyby.
60
sessionId : číslo relace
short listen(short sessionId) Aktivuje výstup do reproduktoru v relaci. Vrací 0 při úspěchu jinak záporné číslo chyby.
sessionId : číslo relace
short speak(short sessionId) Aktivuje plný přenos audio v relaci. Vrací 0 při úspěchu jinak záporné číslo chyby.
sessionId : číslo relace
short silence(short sessionId) Vypne v relaci zvuk. Vrací 0 při úspěchu jinak záporné číslo chyby.
sessionId : číslo relace
short recordSession(short sessionId, std::string playFile, std::string recordFile, bool recordLocal, bool useLocalSpeaker) Volá se místo connectSession. Jsme-li volaná strana, můžeme relaci nahrát do souboru, funkce záznamníku. Vrací 0 při úspěchu jinak záporné číslo chyby.
sessionId : číslo relace
playFile : soubor, který se má přehrát jako úvodní zpráva. Pokud se uvede prázdný řetězec, zpráva se nepřehraje. Pokud je zadaný, po zprávě následuje pípnutí.
recordFile : soubor, kam se má relace nahrát.
recordLocal : zda se do relace nahraje a bude se přenášek zvuk z lokálního mikrofonu
useLocalSpeaker : zda bude v lokálním reproduktoru slyšet průběh relace
short playBusyTone(short sessionId) Pustí do reproduktoru oznamovací tón. Vrací 0 při úspěchu jinak záporné číslo chyby.
sessionId : číslo relace
short linkSessions(short sessionId1, short sessionId2) Spojí dvě relace dohromady - konference. Vrací 0 při úspěchu jinak záporné číslo chyby.
sessionId1 : číslo relace
sessionId2 : číslo relace
short unlinkSessions(short sessionId1, short sessionId2) Rozpojí relace. Vrací 0 při úspěchu jinak záporné číslo chyby.
sessionId1 : číslo relace
61
sessionId2 : číslo relace
short closeSession(short sessionId) Ukončí relaci.
sessionId : číslo relace
std::string getSessionCodec(short sessionId) Vrátí použitý kodek relace.
sessionId : číslo relace
short setEchoCancellation(bool active, short tailMs) Nastaví potlačení ozvěny (je možné měnit během aktivní relace). Vrací 0 při úspěchu jinak záporné číslo chyby.
active : potlačení je aktivní (true) nebo neaktivní (false)
tailMs : kolik milisekund potlačit. -1 pro defaultní hodnotu 200
short setSilenceDetection(bool active) Nastaví detekci ticha (neprojeví se v aktivní relaci). Vrací 0 při úspěchu jinak záporné číslo chyby.
active : detekce je aktivní (true) nebo neaktivní (false)
short setMicVolume(short volume) Nastaví hlasitost mikrofonu. Vrací 0 při úspěchu jinak záporné číslo chyby.
volume : hlasitost 0 - 100
short setSpeakerVolume(short volume) Nastaví hlasitost reproduktoru. Vrací 0 při úspěchu jinak záporné číslo chyby.
volume : hlasitost 0 - 100
short recordWav(std::string file) Spustí nahrávání z mikrofonu do WAV souboru. Může být aktivní pouze jedno. Vrací 0 při úspěchu jinak záporné číslo chyby.
file : soubor pro nahrávání
short stopRecordWav() Zastaví nahrávání do souboru. Vrací 0 při úspěchu jinak záporné číslo chyby. short playWav(std::string file, const FB::JSObjectPtr& wavPlayerEndCallback) Spustí přehrávání WAV souboru do reproduktoru. Vrací 0 při úspěchu jinak záporné číslo chyby.
62
file : soubor pro přehrání
wavPlayerEndCallback : pokud se zadá, zavolá se, pokud přehrávání dorazí na konec souboru
short stopPlayWav() Zastaví přehrávání souboru. Nezavolá callback! Vrací 0 při úspěchu jinak záporné číslo chyby. short getCodecsCount() Vrátí počet použitelných kodeků. std::string getCodecName(short index) Vrátí jméno pro kodek s indexem (0 - [getCodecsCount() - 1])
index : index kodeku
short getCodecPriority(short index) Vrátí číselné vyjádření priority kodeku 0 (nepoužitý kodek) - 255 nebo jinak záporné číslo chyby.
index : index kodeku
short setCodecPriority(std::string name, short priority) Nastaví prioritu kodeku. 0 pro deaktivaci kodeku. Vrací 0 při úspěchu jinak záporné číslo chyby.
name : název kodeku
priority : priorita
short getAudioDeviceCount() Vrací počet všech audio zařízeních v systému. std::string getAudioDeviceName(short index) Vrací název audio zařízení pro index (0 - [getAudioDeviceCount() - 1])
index : index zařízení
bool isAudioDeviceInput(short index) Vrací true, pokud je zařízení vstupní. (Zařízení může být vstupní i výstupní zároveň)
index : index zařízení
bool isAudioDeviceOutput(short index) Vrací true, pokud je zařízení výstupní. (Zařízení může být vstupní i výstupní zároveň)
index : index zařízení
63
short setAudioDevice(short inputIndex, short outputIndex) Nastaví aktuální vstupní a výstupní zařízení. Vrací 0 při úspěchu jinak záporné číslo chyby. Pokud se zadá neexistující index, nastaví defaultní zařízení.
Poznámky k implementaci NPAPI plugin potřebuje všechny vrácené stringy ve formátu UTF-8. Knihovna PJSIP například názvy zařízení v tomto formátu vracet neumí. Z toho důvodu kóduji všechny vrácené stringy metodou base64 a v JavaScriptu je tudíž musím zpětně dekódovat.
4.2.
JavaScript modul Všechny funkce modulu jsou popsány pomocí JSDoc38. Věřím že je zbytečné zde popisovat
každou funkci a každý parametr, proto se pokusím popsat spíše jak funguje veřejné rozhraní, které budou aplikace využívat. Nakonec popíšu podrobněji co je potřeba udělat pro rozšíření modulu a jeho použití v aplikaci.
4.2.1. Veřejné rozhraní Přístupné přes namespace extbrain. Statické funkce a vlastnosti sip.initializeSip(plugin) Tuto statickou metodu voláte v aplikaci jako první a předáváte jí objekt pluginu. Postará se o vytvoření hlavního Sip objektu, který nám vrátí. sip.getSip() Vrací Sip objekt. Pokud předtím nebyla zavolána metoda initializeSip, vyhodí vyjímku. Konstaty: status kódy registrace sip.REGISTRAR_STATUS_UNREGISTERED sip.REGISTRAR_STATUS_REGISTERED sip.REGISTRAR_STATUS_TRYING sip.REGISTRAR_STATUS_FORBIDDEN sip.REGISTRAR_STATUS_TIMEOUT sip.REGISTRAR_STATUS_ERROR
38
http://en.wikipedia.org/wiki/JSDoc
64
Konstaty: status kódy hovory sip.DIALOG_STATUS_NONE sip.DIALOG_STATUS_PROCEED sip.DIALOG_STATUS_ALERTING sip.DIALOG_STATUS_BUSY sip.DIALOG_STATUS_RINGING sip.DIALOG_STATUS_CANCEL sip.DIALOG_STATUS_TIMEOUT sip.DIALOG_STATUS_NOTFOUND sip.DIALOG_STATUS_FORBIDDEN sip.DIALOG_STATUS_ERROR Konstaty: typ hovoru sip.DIALOG_TYPE_IN sip.DIALOG_TYPE_OUT Konstaty: chyby při hovoru sip.DIALOG_ERROR_MEDIA_ACCOUNT_NOT_INITIALIZED sip.DIALOG_ERROR_MEDIA_ACCOUNT_INITIALIZATION_ERROR
Funkce, které je možné volat na instanci objektu destroy : function() Zruší Sip objekt. Vnitřně se postará o zavření všech hovorů, registrací, serverů apod. isDestroyed : function() Vrací true, pokud je objekt v pořádku zrušen. addAccount : function(id, data) Přidá nový účet. Jako data předáváte objekt s vlastnostmi, které chcete změnit ze standardních hodnot. Standardní hodnoty účtu jsou: { displayname : 'Anonymous', username : 'thisis', domain : false, // Nahradní se lokální IP adresou authname : false, password : false, registrarServer : false, registrarPort : constants.SIP_DEFAULT_PORT, // 5060
Pokud použijete ICE přenosovou vrstvu, tak její inicializace probíhá asynchronně a dokud není hotová, není možné spojit hovor. updateAccount : function(id, data) Umožňuje upravit účet. Opět stačí do objektu zadat pouze vlastnosti, které chcete změnit, zbytek zůstane stejný. Není možné měnit vlastnosti přenosové vrstvy hovoru, pokud hovor probíhá. removeAccount : function(id) Vymaže účet. Postará se o řádné ukončení hovoru, pokud probíhá a odregistrování od serveru, jestli je potřeba. getAccountsList : function() Vrátí pole všech ID účtů. getAccount : function(id) Vrátí objekt s účtem. listen : function(type, port, params) Type je typ serveru, který má začít naslouchat na zadaném portu. Pokud nezadáte, použije se defaultní. params je objekt s vlastnostmi pro TLS server:
isListening : function(type) Vrátí true, pokud server přijímá příchozí spojení. stopListening : function(type) Zastaví server. register : function(accountId) Pokusí se zaregistrovat k nastavenému serveru. isRegistered : function(accountId) Vrátí true, zda je účet dobře zaregistrovaný. unregister : function(accountId) Odregistruje účet. invite : function(accountId, uri, displayname) Pokusí se spojit hovor na zadané uri. Zároveň ve zprávě posílá displayname volaného účastníka, pokud zadáme. Všechny změny v hovoru od teď předává událost onDialogStatusChange. answer : function(accountId) Zvedne příchozí hovor. record : function(accountId, welcomeFile, recordFile, recordLocal, useLocalSpeaker) Zvedne příchozí hovor a nahraje ho. Parametry odpovídají funkci recordSession v popisu C++ API. terminate : function(accountId) Při probíhajícím hovoru ho ukončí, při příchozím odmítne a při odchozím zruší požadavek. speak : function(accountId) Připojí do relace reproduktor a mikrofon. silence : function(accountId) Odpojí z relace reproduktor a mikrofon.
67
linkDialogs : function(accountId1, accountId2) Spojí hovory ze dvou účtů do konference. unlinkDialogs : function(accountId1, accountId2) Rozpojí hovory. hasDialog : function(accountId) Vrátí true, pokud má účet aktivní hovor. simpleEnablePresence : function(accountId) Aktivuje správu presence přes SIMPLE pro zadaný účet. simpleDisablePresence : function(accountId) Deaktivuje správu presence přes SIMPLE. simpleAddBuddy : function(accountId, buddy) Začne sledovat kamarádův stav. Jako buddy musíme předat jeho URI. simpleRemoveBuddy : function(accountId, buddy) Přestane sledovat kamarádův stav. simpleChangePresence : function(accountId, presenceXml) Změní váš aktuální stav. Musíte předat správně naformátované XML. simpleSendMessage : function(accountId, messageId, uri, text) Odešle SIMPLE zprávu. messageId je použito pro sledování stavu zprávu. Je zavolána událost onMessageStatusChange, která dostane jako parametr messageId a stav doručení (200 je úspěšné doručení). discoverGlobalIPAddress : function(discover) Pokud je discover true, pokusí se rozpoznat vaši globální adresu. getMaxAcounts : function() Vrátí maximální možný počet účtů, který je schopen plugin přidat. setEchoCancellation : function(active) Aktivuje nebo deaktivuje potlačení ozvěny. setSilenceDetection : function(active) Aktivuje nebo deaktivuje detekci ticha. getCodecs : function() Vrátí pole se všemi názvy kodeků.
68
getCodecsPriority : function() Vrátí pole se všemi prioritami kodeků, indexy odpovídají názvům z předchozí funkce. setCodecsPriority : function(priority) Nastaví priority kodeků. Předá se objekt dvojic název : priorita. 255 je maximální priorita, 0 deaktivuje kodek. Platí pro všechny účty zároveň. setMicVolume : function(volume) Nastaví hlasitost mikrofonu 0-100. setSpeakerVolume : function(volume) Nastaví hlasitost reproduktoru 0-100. recordWav : function(file) Nahraje zvuk z mikrofonu do souboru. stopRecordWav : function() Zastaví nahrávaní do souboru, pokud nějaké je. playWav : function(file, stopCallback) Začne přehrávat soubor. Když dojde nakonec souboru, zavolá callback funkci. stopPlayWav : function() Zastaví přehrávání souboru a nezavolá callback funkci. getAudioDevices : function() Vrátí pole objektů se všemi audio zařízeními v systému. { name : string // Název input : bool // Vstupní zařízení output : bool // Výstupní zařízení }
setAudioDevice : function(inputIndex, outputIndex) Nastaví aktivní vstupní a výstupní audio zařízení. Indexy odpovídají poli z předchozí funkce. addMessageProcessor : function(processor) Zaregistruje nový objekt pro zpracování zpráv. Více dále v kapitole. removeMessageProcessor : function(processor) Odstraní objekt pro zpracování zpráv.
69
addEventListener : function(listener) Zaregistruje nový objekt pro příjem událostí. Podporuje následující události: // Zavolá se při změně stavu registrace onRegistrarStatusChange : function(args) { var accountId = args.accountId; var status = args.status; var code = args.code; // Kód příchozí SIP odpovědí var registered = args.registered; }, // Zavolá se při změně stavu hovoru onDialogStatusChange : function(args) { var accountId = args.accountId; var status = args.status; var code = args.code; // Kód poslední SIP odpovědi var type = args.type; // Odchozí / Příchozí var localUri = args.localUri; var localDisplayname = args.localDisplayname; var remoteUri = args.remoteUri; var remoteDisplayname = args.remoteDisplayname; var codec = args.codec; var error = args.error; // Kód chyby }, // Zavolá se při příchozí SIMPLE zprávě. onSimpleMessage : function(args) { var accountId = args.accountId; var fromUri = args.fromUri; var fromDisplayname = args.fromDisplayname; var toUri = args.toUri; var toDisplayname = args.toDisplayname; var text = args.text; }, // Zavolá se při přijmutí stavu odchozí SIMPLE zprávy onSimpleMessageStatus : function(args) { var accountId = args.accountId; var messageId = args.messageId;
70
var code = args.code; // 200 je úspěšně doručená }, // Zavolá se přijmutí změny stavu kamaráda onNotify : function(args) { var accountId = args.accountId; var fromUri = args.fromUri; var fromDisplayname = args.fromDisplayname; var toUri = args.toUri; var toDisplayname = args.toDisplayname; var presenceXml = args.presenceXml; // Popis stavu }, // Volá se při zpracování zprávy, doporučujeme použít vlastní messageProcessor onProcessMessage : function(messageInstance) { }, // Volá se při příchozí SIP zprávě onReadData : function(args) { var data = args.data; var type = args.type; // udp, tcp, tls }, // Volá se při odeslání SIP zprávy onSendData : function(args) { var data = args.data; var type = args.type; // udp, tcp, tls }, // Volá se při chybě onError : function(args) { var data = args.type; var type = args.message; } removeEventListener : function(listener) Odebere objekt z příjmu událostí.
4.2.2. Objekt umožňující zpracování zpráv Pokud chceme vytvořit objekt umožňující zpracování došlých zpráv, stačí nám zaregistrovat jeho
messageProcessor
pomocí
funkce
sip.addMessageProcessor.
Samotný
objekt 71
messageProcessor má dvě funkce, první je isFor(messageInstance). Té se přidá instance právě zpracovávané zprávy a funkce se musí rozhodnou, zda se chce objekt o zpracování této zprávy postarat a podle toho vrátí buď true, nebo false. False dá šanci ostatním zaregistrovaným objektům. Pokud vrátí funkce true, zavolá se druhá metoda objektu. To je process(sip, messageInstance). Ta dostane v proměnné sip odkaz na hlavní Sip objekt, ze kterého si může vytáhnout například všechny účty. V proměnné messageInstance potom dostane samotný objekt zprávy a záleží už jen na této funkci, jak se zprávou naloží. Hlavní objekt Sip v tuto chvíli zprávu zahazuje.
4.2.3. Tvorba SIP zpráv O tvorbu SIP zpráv se převážně stará objekt Message a jeho potomci Request a Responce. Ty umějí vytvořit kostru žádosti a kostru odpovědi (ta se vytváří na základě předaného objektu obsahující zprávu žádosti). Pokud chceme umět do žádosti nebo odpovědi přidávat vlastní data musíme do objektu Message zaregistrovat naši vlastní továrnu, která se bude starat o rozšíření kostry zprávy. Registraci provedeme následovně (ukázka registrace pro messageProcessor i tvorbu zpráv): /** * Register module to sip and message object. * @param nsSip Sip object * @param nsMessage message object */ Dialog.registerModule = function(nsSip, nsMessage) { // Register to sip nsSip.addMessageProcessor(Dialog.messageProcessor); // Register to messages nsMessage.addRequestCommand(Dialog.COMMAND_INVITE, function(request, account, params, uri, from, to, content) { request.setCommand(Dialog.COMMAND_INVITE, (uri.secure ? 'sips' : 'sip') + ':' + uri.username + '@' + uri.domain); request.addHeader('From', from, (params.fromTag ? {tag : params.fromTag} : {})); request.addHeader('To', to, (params.toTag ? {tag : params.toTag} : {})); if (params.authorize) { request.addHeader((params.authorizeType == 'proxy') ? 'ProxyAuthorization' : 'Authorization', util.authorize(params.authorize, Dialog.COMMAND_INVITE, account.authname, account.password, (uri.secure ?
Důležité funkce jsou zvýrazněny tučně. Nejprve do Sip objektu přidáme další messageProcessor. Následně pomocí funkcí addRequestCommand (pro vlastní úpravu požadavku, zde je důležitý poslední parametr, který pokud je true, bude tento příkaz uveden ve zpravách v parametru „Allow“) a addResponseCommand (pro vlastní úpravu odpovědi) dostupných v namespace message přidáme námi požadovéné úpravy.
4.2.4. Použití modulu v aplikaci Zde si popíšeme kroky nutné k úspěšnému používání modulu ve vlastních aplikacích. Podle použité platformy načteme náš C++ plugin. Nejčastěji podobným kódem:
Teď si musíme načíst náš JavaScriptový kód ze všech souborů, to provedeme následovně: <script type="text/javascript" src="extbrain/sip/constants.js"> <script type="text/javascript" src="extbrain/sip/util.js"> <script type="text/javascript" src="extbrain/sip/transport.js"> <script type="text/javascript" src="extbrain/sip/message.js"> <script type="text/javascript" src="extbrain/sip/media.js"> <script type="text/javascript" src="extbrain/sip/registrar.js"> <script type="text/javascript" src="extbrain/sip/dialog.js"> <script type="text/javascript" src="extbrain/sip/simple.js"> <script type="text/javascript" src="extbrain/sip.js">
73
Je důležité říci našemu modulu, kde se nachází objekt pluginu a modul inicializovat. Oboje provedeme v jednom kroku: var sip = extbrain.sip.initializeSip(document.getElementById(‘plugin’));
Nyní již můžeme s modulem pracovat. Určitě budeme chtít zaregistrovat objekt pro příjem událostí: sip.addEventListener(ourSipEventsListener);
A přidat nějaký účet: sip.addAccount('account1', { displayname : 'LOCAL', username : 'local' });
A nakonec náš modul zrušíme: sip.destroy();
74
5. Ukázkové rozšíření 5.1.
Programátorské informace V této kapitole nebudu popisovat tvorbu uživatelského rozhraní, ale zaměřím se na
praktickou implementaci modulu do rozšíření. Pro jednoduchost je veškerý kód, který implementuje knihovnu v souboru chrome/content/overlay.js v objektu SipModule.
5.1.1. Načtení modulu Aby se plugin nenačítal dříve, než se otevře záložka s rozšířením, závádíme ho do XUL dokumentu dynamicky. Tady si musíme dát pozor, že tag