├── .project ├── .pydevproject ├── LICENSE ├── README.md ├── aes.py ├── amf.py ├── dist.txt ├── multitask.py ├── people.png ├── rtmfp.py ├── rtmp.py ├── rtmpclient.py ├── rtmplite.md ├── rtmpt.py ├── siprtmp-faq.md ├── siprtmp.md ├── siprtmp.png ├── siprtmp.py ├── siprtmp_gevent.py ├── testClient ├── .actionScriptProperties ├── .flexProperties ├── .project ├── bin-debug │ ├── AC_OETags.js │ ├── history │ │ ├── history.css │ │ ├── history.js │ │ └── historyFrame.html │ ├── playerProductInstall.swf │ ├── testClient.html │ └── testClient.swf ├── libs │ └── .ignore └── src │ └── testClient.mxml ├── testP2P ├── Makefile ├── bin-debug │ └── testP2P.swf └── src │ └── testP2P.mxml └── videoPhone ├── .actionScriptProperties ├── .flexProperties ├── .project ├── Makefile ├── bin-release ├── AC_OETags.js ├── VideoPhone.html ├── VideoPhone.swf ├── VideoPhone11.swf ├── VideoPhone45.swf ├── VideoPhone8.swf ├── history │ ├── history.css │ ├── history.js │ └── historyFrame.html └── playerProductInstall.swf ├── html-template ├── AC_OETags.js ├── history │ ├── history.css │ ├── history.js │ └── historyFrame.html ├── index.template.html └── playerProductInstall.swf ├── libs └── .ignore ├── locale ├── en_US │ └── main.properties └── hi_IN │ └── main.properties └── src ├── VideoPhone.mxml ├── _.as ├── model └── Connector.as ├── style └── main.css └── view ├── BButton.mxml ├── Dialpad.mxml ├── HBar.mxml ├── PostIt.mxml ├── TTextInput.mxml ├── VVideo.mxml ├── VideoPlay.mxml ├── VideoPublish.mxml └── View.mxml /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | rtmplite 4 | 5 | 6 | 7 | 8 | 9 | org.python.pydev.PyDevBuilder 10 | 11 | 12 | 13 | 14 | 15 | org.python.pydev.pythonNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.pydevproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | python 2.6 6 | Default 7 | 8 | /rtmplite 9 | 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # What is rtmplite? # 2 | More details in [rtmplite](/rtmplite.md) and [siprtmp](/siprtmp.md) 3 | 4 | > This project was migrated from on May 17, 2015 5 | > Additionally the documentation from was merged on May 17, 2015 6 | > Please see these individual description files for [rtmplite](/rtmplite.md) or [siprtmp](/siprtmp.md) 7 | 8 | **New:** The new RTClite project is a superset of this RTMPlite project. Please migrate to or start using the [RTClite project](https://github.com/theintencity/rtclite) project instead of this. More information at https://github.com/theintencity/rtclite 9 | 10 | # Copyright # 11 | 12 | Copyright (c) 2007-2009, Mamta Singh. 13 | Copyright (c) 2010-2011, Kundan Singh. All rights reserved. 14 | Copyright (c) 2011-2012, Intencity Cloud Technologies. All rights reserved. 15 | Copyright (c) 2011, Cumulus Python. No rights reserved. 16 | 17 | See [contributors](/people.png). 18 | 19 | # RTMP server # 20 | 21 | The main program is rtmp.py. Please see the embedded documentation in that file. 22 | Some parts of the documentation are copied here. Other modules such as amf, util 23 | and multitask are used from elsewhere and contain their respective copyright 24 | notices. 25 | 26 | # SIP-RTMP gateway # 27 | 28 | The siprtmp module implements a SIP-RTMP gateway. Please see the google 29 | code project for details on demo instructions and support information. 30 | The project description is contained in siprtmp.py file itself. The command 31 | line of siprtmp is same as rtmp, hence just replce rtmp by siprtmp in the 32 | following example to start the gateway. 33 | 34 | # RTMP client # 35 | 36 | The rtmpclient module implements a simple RTMP client. Please see the documentation 37 | in rtmpclient.py source file on how it works and how it can be used. 38 | 39 | # RTMFP server # 40 | 41 | The rtmfp module implements a simple and imcomplete RTMFP rendezvous server. Please 42 | see the documentation in rtmfp.py source file on how it works. 43 | 44 | # Getting Started # 45 | 46 | Dependencies: Python 2.6 and Python 2.5 47 | 48 | Typically an application can launch this server as follows: 49 | ``` 50 | $ python rtmp.py -d 51 | ``` 52 | The -d option enables debug trace so you know what is happening in the server. 53 | 54 | To know the command line options use the -h option: 55 | ``` 56 | $ python rtmp.py -h 57 | ``` 58 | 59 | A test client is available in testClient directory, and can be compiled 60 | using Flex Builder. Alternatively, you can use the SWF file to launch 61 | from testClient/bin-debug after starting the server. Once you have 62 | launched the client in the browser, you can connect to 63 | local host by clicking on 'connect' button. Then click on publish 64 | button to publish a stream. Open another browser with 65 | same URL and first connect then play the same stream name. If 66 | everything works fine you should be able to see the video 67 | from first browser to the second browser. Similar, in the first 68 | browser, if you check the record box before publishing, 69 | it will create a new FLV file for the recorded stream. You can 70 | close the publishing stream and play the recorded stream to 71 | see your recording. Note that due to initial delay in timestamp 72 | (in case publish was clicked much later than connect), 73 | your played video will start appearing after some initial delay. 74 | -------------------------------------------------------------------------------- /aes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # (c) 2011, Cumulus Python . No rights reserved. 3 | # Optimized version of pure Python AES. 4 | 5 | ''' 6 | To check the time it takes for 1 iteration of encryption plus decryption of 1000 bytes of data using AES 128 bit key: 7 | $ python -m timeit "import aes; aes._test(repeat=1, mode=aes.CBC, dataSize=1000, keySize=128/8)" 8 | 9 | To print the time taken by individual functions over 200 iterations: 10 | $ python -m cProfile aes.py 11 | 12 | Performance Measurement 13 | ----------------------- 14 | $ python -m timeit "import aes; aes._test(repeat=1);" 15 | 10 loops, best of 3: 29.5 msec per loop 16 | ''' 17 | 18 | import os, sys, math, struct, random 19 | 20 | OFB, CFB, CBC = 0, 1, 2 # mode of operation 21 | SIZE_128, SIZE_192, SIZE_256 = 16, 24, 32 22 | 23 | iv_null = lambda: [0 for i in xrange(16)] 24 | iv_random = lambda: [ord(random.randint(0, 255)) for i in xrange(16)] 25 | 26 | def encrypt(key, data, iv, mode=CBC): 27 | key, keysize = map(ord, key), len(key) 28 | assert keysize in (16, 24, 32), 'invalid key size: %s' % keysize 29 | (mode, length, ciph) = _encrypt(data, mode, key, keysize, iv) # do not store the length 30 | return ''.join(map(chr, ciph)) 31 | 32 | def decrypt(key, data, iv, mode=CBC): 33 | key, data, keysize = map(ord, key), map(ord, data), len(key) 34 | assert keysize in (16, 24, 32), 'invalid key size: %s' % keysize 35 | return _decrypt(data, None, mode, key, keysize, iv) 36 | 37 | def append_PKCS7_padding(s): # return s padded to a multiple of 16-bytes by PKCS7 padding 38 | numpads = 16 - (len(s)%16) 39 | return s + numpads*chr(numpads) 40 | 41 | def strip_PKCS7_padding(s): # return s stripped of PKCS7 padding 42 | if len(s)%16 or not s: raise ValueError("String of len %d can't be PCKS7-padded" % len(s)) 43 | numpads = ord(s[-1]) 44 | if numpads > 16: raise ValueError("String ending with %r can't be PCKS7-padded" % s[-1]) 45 | return s[:-numpads] 46 | 47 | 48 | 49 | def _galois_multiplication(a, b): # Galois multiplication of 8 bit numbers a and b 50 | p = 0 51 | for counter in xrange(8): 52 | if b & 1: p ^= a 53 | hi_bit_set = a & 0x80 54 | a = (a << 1) & 0xff 55 | if hi_bit_set: a ^= 0x1b 56 | b >>= 1 57 | return p 58 | 59 | # galois multiplication table _g1, _g2, ..., Rijndael S-box _sbox, inverted S-box _rsbox, Rcon _rcon 60 | for i in (1, 2, 3, 9, 11, 13, 14): exec("_g%d = [_galois_multiplication(a, %d) for a in xrange(256)]"%(i, i)) 61 | del i 62 | _sbox = map(ord, '\x63\x7c\x77\x7b\xf2\x6b\x6f\xc5\x30\x01\x67\x2b\xfe\xd7\xab\x76\xca\x82\xc9\x7d\xfa\x59\x47\xf0\xad\xd4\xa2\xaf\x9c\xa4\x72\xc0\xb7\xfd\x93\x26\x36\x3f\xf7\xcc\x34\xa5\xe5\xf1\x71\xd8\x31\x15\x04\xc7\x23\xc3\x18\x96\x05\x9a\x07\x12\x80\xe2\xeb\x27\xb2\x75\x09\x83\x2c\x1a\x1b\x6e\x5a\xa0\x52\x3b\xd6\xb3\x29\xe3\x2f\x84\x53\xd1\x00\xed\x20\xfc\xb1\x5b\x6a\xcb\xbe\x39\x4a\x4c\x58\xcf\xd0\xef\xaa\xfb\x43\x4d\x33\x85\x45\xf9\x02\x7f\x50\x3c\x9f\xa8\x51\xa3\x40\x8f\x92\x9d\x38\xf5\xbc\xb6\xda\x21\x10\xff\xf3\xd2\xcd\x0c\x13\xec\x5f\x97\x44\x17\xc4\xa7\x7e\x3d\x64\x5d\x19\x73\x60\x81\x4f\xdc\x22\x2a\x90\x88\x46\xee\xb8\x14\xde\x5e\x0b\xdb\xe0\x32\x3a\x0a\x49\x06\x24\x5c\xc2\xd3\xac\x62\x91\x95\xe4\x79\xe7\xc8\x37\x6d\x8d\xd5\x4e\xa9\x6c\x56\xf4\xea\x65\x7a\xae\x08\xba\x78\x25\x2e\x1c\xa6\xb4\xc6\xe8\xdd\x74\x1f\x4b\xbd\x8b\x8a\x70\x3e\xb5\x66\x48\x03\xf6\x0e\x61\x35\x57\xb9\x86\xc1\x1d\x9e\xe1\xf8\x98\x11\x69\xd9\x8e\x94\x9b\x1e\x87\xe9\xce\x55\x28\xdf\x8c\xa1\x89\x0d\xbf\xe6\x42\x68\x41\x99\x2d\x0f\xb0\x54\xbb\x16') 63 | _rsbox = map(ord, '\x52\x09\x6a\xd5\x30\x36\xa5\x38\xbf\x40\xa3\x9e\x81\xf3\xd7\xfb\x7c\xe3\x39\x82\x9b\x2f\xff\x87\x34\x8e\x43\x44\xc4\xde\xe9\xcb\x54\x7b\x94\x32\xa6\xc2\x23\x3d\xee\x4c\x95\x0b\x42\xfa\xc3\x4e\x08\x2e\xa1\x66\x28\xd9\x24\xb2\x76\x5b\xa2\x49\x6d\x8b\xd1\x25\x72\xf8\xf6\x64\x86\x68\x98\x16\xd4\xa4\x5c\xcc\x5d\x65\xb6\x92\x6c\x70\x48\x50\xfd\xed\xb9\xda\x5e\x15\x46\x57\xa7\x8d\x9d\x84\x90\xd8\xab\x00\x8c\xbc\xd3\x0a\xf7\xe4\x58\x05\xb8\xb3\x45\x06\xd0\x2c\x1e\x8f\xca\x3f\x0f\x02\xc1\xaf\xbd\x03\x01\x13\x8a\x6b\x3a\x91\x11\x41\x4f\x67\xdc\xea\x97\xf2\xcf\xce\xf0\xb4\xe6\x73\x96\xac\x74\x22\xe7\xad\x35\x85\xe2\xf9\x37\xe8\x1c\x75\xdf\x6e\x47\xf1\x1a\x71\x1d\x29\xc5\x89\x6f\xb7\x62\x0e\xaa\x18\xbe\x1b\xfc\x56\x3e\x4b\xc6\xd2\x79\x20\x9a\xdb\xc0\xfe\x78\xcd\x5a\xf4\x1f\xdd\xa8\x33\x88\x07\xc7\x31\xb1\x12\x10\x59\x27\x80\xec\x5f\x60\x51\x7f\xa9\x19\xb5\x4a\x0d\x2d\xe5\x7a\x9f\x93\xc9\x9c\xef\xa0\xe0\x3b\x4d\xae\x2a\xf5\xb0\xc8\xeb\xbb\x3c\x83\x53\x99\x61\x17\x2b\x04\x7e\xba\x77\xd6\x26\xe1\x69\x14\x63\x55\x21\x0c\x7d') 64 | _rcon = map(ord, '\x8d\x01\x02\x04\x08\x10\x20\x40\x80\x1b\x36\x6c\xd8\xab\x4d\x9a\x2f\x5e\xbc\x63\xc6\x97\x35\x6a\xd4\xb3\x7d\xfa\xef\xc5\x91\x39\x72\xe4\xd3\xbd\x61\xc2\x9f\x25\x4a\x94\x33\x66\xcc\x83\x1d\x3a\x74\xe8\xcb\x8d\x01\x02\x04\x08\x10\x20\x40\x80\x1b\x36\x6c\xd8\xab\x4d\x9a\x2f\x5e\xbc\x63\xc6\x97\x35\x6a\xd4\xb3\x7d\xfa\xef\xc5\x91\x39\x72\xe4\xd3\xbd\x61\xc2\x9f\x25\x4a\x94\x33\x66\xcc\x83\x1d\x3a\x74\xe8\xcb\x8d\x01\x02\x04\x08\x10\x20\x40\x80\x1b\x36\x6c\xd8\xab\x4d\x9a\x2f\x5e\xbc\x63\xc6\x97\x35\x6a\xd4\xb3\x7d\xfa\xef\xc5\x91\x39\x72\xe4\xd3\xbd\x61\xc2\x9f\x25\x4a\x94\x33\x66\xcc\x83\x1d\x3a\x74\xe8\xcb\x8d\x01\x02\x04\x08\x10\x20\x40\x80\x1b\x36\x6c\xd8\xab\x4d\x9a\x2f\x5e\xbc\x63\xc6\x97\x35\x6a\xd4\xb3\x7d\xfa\xef\xc5\x91\x39\x72\xe4\xd3\xbd\x61\xc2\x9f\x25\x4a\x94\x33\x66\xcc\x83\x1d\x3a\x74\xe8\xcb\x8d\x01\x02\x04\x08\x10\x20\x40\x80\x1b\x36\x6c\xd8\xab\x4d\x9a\x2f\x5e\xbc\x63\xc6\x97\x35\x6a\xd4\xb3\x7d\xfa\xef\xc5\x91\x39\x72\xe4\xd3\xbd\x61\xc2\x9f\x25\x4a\x94\x33\x66\xcc\x83\x1d\x3a\x74\xe8\xcb') 65 | 66 | 67 | def _core(word, iteration): # core key schedule: rotate 32-bit word 8 bits to left, apply S-box on all 4 parts and XOR the rcon output with first part 68 | word = word[1:] + word[:1] 69 | for i in xrange(4): word[i] = _sbox[word[i]] 70 | word[0] = word[0] ^ _rcon[iteration] 71 | return word 72 | 73 | def _expandKey(key, size, expandedKeySize): # Rijndael's key expansion: expands an 128,192,256 key into an 176,208,240 bytes key 74 | currentSize, rconIteration = 0, 1 75 | expandedKey = [0]*expandedKeySize 76 | for j in xrange(size): expandedKey[j] = key[j] # set the 16, 24, 32 bytes of the expanded key to the input key 77 | currentSize += size 78 | while currentSize < expandedKeySize: 79 | t = expandedKey[currentSize-4:currentSize] # assign the previous 4 bytes to the temporary value t 80 | if currentSize % size == 0: # every 16,24,32 bytes we apply the core schedule to t and increment rconIteration afterwards 81 | t = _core(t, rconIteration) 82 | rconIteration += 1 83 | if size == SIZE_256 and ((currentSize % size) == 16): # For 256-bit keys, we add an extra sbox to the calculation 84 | for l in xrange(4): t[l] = _sbox[t[l]] 85 | for m in xrange(4): # We XOR t with the four-byte block 16,24,32 bytes before the new expanded key. This becomes the next four bytes in the expanded key. 86 | expandedKey[currentSize] = expandedKey[currentSize - size] ^ t[m] 87 | currentSize += 1 88 | return expandedKey 89 | 90 | def _addRoundKey(state, roundKey): # Adds (XORs) the round key to the state. 91 | for i in xrange(16): state[i] ^= roundKey[i] 92 | return state 93 | 94 | def _createRoundKey(expanded, pos): # create a round key from the given expanded key and the position within 95 | subset = expanded[pos:pos+16] 96 | return [subset[0], subset[4], subset[8], subset[12], 97 | subset[1], subset[5], subset[9], subset[13], 98 | subset[2], subset[6], subset[10],subset[14], 99 | subset[3], subset[7], subset[11],subset[15]] 100 | 101 | def _subBytes(state, isInv): # substitute all values from S-Box or inverted S-box 102 | return [_rsbox[x] for x in state] if isInv else [_sbox[x] for x in state] 103 | 104 | def _shiftRows(state, isInv): # shift row all rows by row index 105 | if isInv: 106 | state[4], state[5], state[6], state[7] = state[7], state[4], state[5], state[6] 107 | state[8], state[9], state[10],state[11]= state[10],state[11],state[8], state[9] 108 | state[12],state[13],state[14],state[15]= state[13],state[14],state[15],state[12] 109 | else: 110 | state[4], state[5], state[6], state[7] = state[5], state[6], state[7], state[4] 111 | state[8], state[9], state[10],state[11]= state[10],state[11],state[8], state[9] 112 | state[12],state[13],state[14],state[15]= state[15],state[12],state[13],state[14] 113 | return state 114 | 115 | _mixColumnInv = lambda c0, c1, c2, c3: (_g14[c0] ^ _g9[c3] ^ _g13[c2] ^ _g11[c1], _g14[c1] ^ _g9[c0] ^ _g13[c3] ^ _g11[c2], _g14[c2] ^ _g9[c1] ^ _g13[c0] ^ _g11[c3], _g14[c3] ^ _g9[c2] ^ _g13[c1] ^ _g11[c0]) 116 | _mixColumn = lambda c0, c1, c2, c3: (_g2[c0] ^ _g1[c3] ^ _g1[c2] ^ _g3[c1], _g2[c1] ^ _g1[c0] ^ _g1[c3] ^ _g3[c2], _g2[c2] ^ _g1[c1] ^ _g1[c0] ^ _g3[c3], _g2[c3] ^ _g1[c2] ^ _g1[c1] ^ _g3[c0]) 117 | 118 | def _mixColumns(state, isInv): # galois multiplication of 4x4 matrix 119 | state[0], state[4], state[8], state[12] = (_mixColumnInv if isInv else _mixColumn)(state[0], state[4], state[8], state[12]) 120 | state[1], state[5], state[9], state[13] = (_mixColumnInv if isInv else _mixColumn)(state[1], state[5], state[9], state[13]) 121 | state[2], state[6], state[10],state[14] = (_mixColumnInv if isInv else _mixColumn)(state[2], state[6], state[10],state[14]) 122 | state[3], state[7], state[11],state[15] = (_mixColumnInv if isInv else _mixColumn)(state[3], state[7], state[11],state[15]) 123 | return state 124 | 125 | def _aes_round(state, roundKey): # forward round operations 126 | return _addRoundKey(_mixColumns(_shiftRows(_subBytes(state, False), False), False), roundKey) 127 | 128 | def _aes_invRound(state, roundKey): # inverse round operations 129 | return _mixColumns(_addRoundKey(_subBytes(_shiftRows(state, True), True), roundKey), True) 130 | 131 | def _aes_main(state, expandedKey, nbrRounds): # initial operations, standard round, and final operations of forward direction 132 | state = _addRoundKey(state, _createRoundKey(expandedKey, 0)) 133 | for i in xrange(1, nbrRounds): 134 | state = _addRoundKey(_mixColumns(_shiftRows(_subBytes(state, False), False), False), _createRoundKey(expandedKey, 16*i)) 135 | return _addRoundKey(_shiftRows(_subBytes(state, False), False), _createRoundKey(expandedKey, 16*nbrRounds)) 136 | 137 | def _aes_invMain(state, expandedKey, nbrRounds): # initial operations, standard round, and final operations of inverse direction 138 | state = _addRoundKey(state, _createRoundKey(expandedKey, 16*nbrRounds)) 139 | for i in xrange(nbrRounds-1, 0, -1): 140 | state = _mixColumns(_addRoundKey(_subBytes(_shiftRows(state, True), True), _createRoundKey(expandedKey, 16*i)), True) 141 | return _addRoundKey(_subBytes(_shiftRows(state, True), True), _createRoundKey(expandedKey, 0)) 142 | 143 | _last_key = _last_expanded_key = None 144 | 145 | _rounds = {SIZE_128: 10, SIZE_192: 12, SIZE_256: 14} 146 | 147 | def _aes_block(iput, key, size, isInv): # encrypt 128-bit input block against the given key of given size 148 | global _last_key, _last_expanded_key, _rounds 149 | if size not in _rounds: return None 150 | nbrRounds = _rounds.get(size) 151 | expandedKeySize = 16*(nbrRounds+1) # the expanded keySize 152 | 153 | block = [iput[i+4*j] for i in xrange(4) for j in xrange(4)] 154 | expandedKey = _last_expanded_key if _last_key == key else _expandKey(key, size, expandedKeySize) # expand the key into an 176, 208, 240 bytes key the expanded key 155 | _last_key, _last_expanded_key = key, expandedKey 156 | 157 | block = _aes_invMain(block, expandedKey, nbrRounds) if isInv else _aes_main(block, expandedKey, nbrRounds) # encrypt or decrypt the block using the expandedKey 158 | return [block[i+4*j] for i in xrange(4) for j in xrange(4)] 159 | 160 | def _encrypt(stringIn, mode, key, size, IV): 161 | assert len(key) % size == 0 and len(IV) % 16 == 0 162 | cipherOut, firstRound = [], True 163 | if stringIn != None: 164 | for start in xrange(0, len(stringIn), 16): 165 | plaintext = map(ord, stringIn[start:start+16]) 166 | if len(plaintext) < 16: plaintext += [0]*(16-len(plaintext)) 167 | if mode == CFB: 168 | output = _aes_block(IV if firstRound else iput, key, size, False) 169 | firstRound = False 170 | # TODO: verify the following 171 | ciphertext = [(0 if len(plaintext)-1 < i else plaintext[0]) ^ (0 if len(output)-1 < i else output[i]) for i in xrange(16)] 172 | cipherOut += [ciphertext[k] for k in xrange(end-start)] 173 | iput = ciphertext if mode == CFB else output 174 | elif mode == CBC: 175 | iput = [plaintext[i] ^ IV[i] for i in xrange(16)] if firstRound else [plaintext[i] ^ ciphertext[i] for i in xrange(16)] 176 | firstRound = False 177 | ciphertext = _aes_block(iput, key, size, False) 178 | cipherOut += ciphertext 179 | return mode, len(stringIn), cipherOut 180 | 181 | def _decrypt(cipherIn, originalsize, mode, key, size, IV): 182 | assert len(key) % size == 0 and len(IV) % 16 == 0 183 | stringOut, firstRound = [], True 184 | if cipherIn != None: 185 | for start in xrange(0, len(cipherIn), 16): 186 | ciphertext = cipherIn[start:start+16] 187 | end = start + len(ciphertext) 188 | if mode == CFB or mode == OFB: 189 | output = _aes_block(IV if firstRound else iput, key, size, False) # TODO: verify that it calls encrypt, and not decrypt 190 | firstRound = False 191 | # TODO: verify the following 192 | plaintext = [(0 if len(output)-1 < i else output[0]) ^ (0 if len(ciphertext)-1 < i else ciphertext[i]) for i in xrange(16)] 193 | stringOut += [plaintext[k] for k in xrange(end-start)] 194 | iput = ciphertext if mode == CFB else output 195 | elif mode == CBC: 196 | output = _aes_block(ciphertext, key, size, True) 197 | plaintext = [IV[i] ^ output[i] for i in xrange(16)] if firstRound else [iput[i] ^ output[i] for i in xrange(16)] 198 | firstRound = False 199 | end1 = originalsize if originalsize is not None and originalsize < end else end 200 | stringOut += [plaintext[k] for k in xrange(end1-start)] 201 | iput = ciphertext 202 | return ''.join(map(chr, stringOut)) 203 | 204 | def _test(debug=False, mode=CBC, dataSize=1000, keySize=16, repeat=100): 205 | cleartext = ''.join([chr(random.randint(0, 255)) for i in xrange(dataSize)]) 206 | if debug: print 'cleartext=%r'%(cleartext,) 207 | for i in xrange(repeat): 208 | cypherkey, iv = [random.randint(1,255) for i in xrange(keySize)], [0 for i in xrange(keySize)] 209 | mode1, orig_len, ciph = _encrypt(cleartext, mode, cypherkey, keySize, iv) 210 | if debug: print 'mode=%s, original length=%s (%s)\nencrypted=%s'%(mode, orig_len, len(cleartext), ciph) 211 | decr = _decrypt(ciph, orig_len, mode1, cypherkey, keySize, iv) 212 | if debug: print 'decrypted=%r'%(decr,) 213 | assert decr == cleartext 214 | 215 | def _test2(): 216 | encoded = "%\x01\xf6o\xfd\x00\xb7\x9a\xd8\x01A\xf5\xae\xeb\x91y\x15\x8d\x19@\x9d\x83\x05\xef'\x16\x86|v4~j\x8ejT'\x9f\x97d\xd6\x19\xd5\xfa\xd5C\xeb\xd2g\xfb\xd9 \xc0\x86l\xe6^\x94\x05<\xa0\xe6\xbc\xa1\xbd\xea\x8c\xfe\xd8" 217 | decr = decrypt('Adobe Systems 02', encoded[4:], iv=iv_null()) 218 | assert decr.find('rtmfp://localhost/myapp') >= 0 219 | 220 | if __name__ == "__main__": 221 | _test() 222 | _test2() 223 | -------------------------------------------------------------------------------- /dist.txt: -------------------------------------------------------------------------------- 1 | rtmplite/README 2 | rtmplite/amf.py 3 | rtmplite/multitask.py 4 | rtmplite/rtmp.py 5 | rtmplite/rtmpclient.py 6 | rtmplite/rtmpt.py 7 | rtmplite/siprtmp.py 8 | rtmplite/siprtmp_gevent.py 9 | rtmplite/aes.py 10 | rtmplite/rtmfp.py 11 | rtmplite/test_load.py 12 | rtmplite/testClient/bin-debug/AC_OETags.js 13 | rtmplite/testClient/bin-debug/history/history.css 14 | rtmplite/testClient/bin-debug/history/history.js 15 | rtmplite/testClient/bin-debug/history/historyFrame.html 16 | rtmplite/testClient/bin-debug/playerProductInstall.swf 17 | rtmplite/testClient/bin-debug/testClient.html 18 | rtmplite/testClient/bin-debug/testClient.swf 19 | rtmplite/testClient/src/testClient.mxml 20 | rtmplite/videoPhone/.actionScriptProperties 21 | rtmplite/videoPhone/.flexProperties 22 | rtmplite/videoPhone/.project 23 | rtmplite/videoPhone/Makefile 24 | rtmplite/videoPhone/bin-release/AC_OETags.js 25 | rtmplite/videoPhone/bin-release/playerProductInstall.swf 26 | rtmplite/videoPhone/bin-release/VideoPhone.html 27 | rtmplite/videoPhone/bin-release/VideoPhone.swf 28 | rtmplite/videoPhone/bin-release/VideoPhone11.swf 29 | rtmplite/videoPhone/bin-release/VideoPhone45.swf 30 | rtmplite/videoPhone/bin-release/history/history.css 31 | rtmplite/videoPhone/bin-release/history/history.js 32 | rtmplite/videoPhone/bin-release/history/historyFrame.html 33 | rtmplite/videoPhone/html-template/AC_OETags.js 34 | rtmplite/videoPhone/html-template/index.template.html 35 | rtmplite/videoPhone/html-template/playerProductInstall.swf 36 | rtmplite/videoPhone/html-template/history/history.css 37 | rtmplite/videoPhone/html-template/history/history.js 38 | rtmplite/videoPhone/html-template/history/historyFrame.html 39 | rtmplite/videoPhone/libs/.ignore 40 | rtmplite/videoPhone/locale/en_US/main.properties 41 | rtmplite/videoPhone/locale/hi_IN/main.properties 42 | rtmplite/videoPhone/src/VideoPhone.mxml 43 | rtmplite/videoPhone/src/_.as 44 | rtmplite/videoPhone/src/model/Connector.as 45 | rtmplite/videoPhone/src/style/main.css 46 | rtmplite/videoPhone/src/view/BButton.mxml 47 | rtmplite/videoPhone/src/view/Dialpad.mxml 48 | rtmplite/videoPhone/src/view/HBar.mxml 49 | rtmplite/videoPhone/src/view/PostIt.mxml 50 | rtmplite/videoPhone/src/view/TTextInput.mxml 51 | rtmplite/videoPhone/src/view/VideoPlay.mxml 52 | rtmplite/videoPhone/src/view/VideoPublish.mxml 53 | rtmplite/videoPhone/src/view/View.mxml 54 | rtmplite/videoPhone/src/view/VVideo.mxml 55 | -------------------------------------------------------------------------------- /people.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theintencity/rtmplite/af7fd71d25765ee30c9f9b6c269309bcf657a756/people.png -------------------------------------------------------------------------------- /rtmplite.md: -------------------------------------------------------------------------------- 1 | # Flash RTMP Server in Python # 2 | 3 | > This project was migrated from on May 17, 2015 4 | > Keywords: *RTMP*, *Flash*, *Python*, *FLV*, *Streaming*, *Recording*, *Server*, *Real-time*, *SIP*, *VideoCall*, *Conferencing*, *Client*, *Video* 5 | > Members: *voipresearcher* (owner, founder, lead by providing a project vision and roadmap, initial creator of RTMPlite server and SIP-RTMP gateway implementations), *theintencity* (owner, copyright holder), *kundan10* (project manager, maintenance, improvements, features, bug-fixes, lead the support task for the project), *mamtasingh05* (founder, initial creator of RTMPlite server and SIP-RTMP gateway implementations), *cumulus.python* (added and maintaining RTMFP support), *luke.weber*, *pratnama*, *sebastien.jeanjean*, *juanantonio.ram* 6 | > Links: [Support](http://groups.google.com/group/myprojectguide), [Download](https://github.com/theintencity/rtmplite/tree/downloads), [Demo Audio](http://www.youtube.com/watch?v=-_W2YVCIPg8), [Demo Video](http://www.youtube.com/watch?v=-_W2YVCIPg8) 7 | > License: [GNU GPL v3](http://www.gnu.org/licenses/gpl.html) 8 | > Others: starred by 201 users 9 | 10 | ![logo](/siprtmp.png) 11 | 12 | This is a python implementation of the Flash RTMP server with minimal support needed for real-time streaming and recording using AMF0. It also includes an RTMP client and a SIP-RTMP gateway. 13 | 14 | IMPORTANT Please send any bug/support request to the [support group](http://groups.google.com/group/myprojectguide) instead of directly to the owner 15 | 16 | Advanced application service such as shared object or web API are outside the scope. The goal is to use existing protocols and tools such as web servers as much as possible (API, progressive download). And only use this RTMP server when one needs to interoperate with Flash Player. Another objective is to keep the size small so that one can use it locally on a client machine instead of hosting on a remote server. 17 | 18 | Our white paper on Flash based audio and video communication in the cloud describes the benefits and problems of using Flash Player for communication, describes the motivation and overview of Flash VideoIO API, shows how to build various application scenarios with the API, compares various architecture and API options for interoperability between Flash Player and SIP/RTP, and describes our SIP-RTMP message flow, session negotiation, media transport and media format in detail. You can view the demo video of SIP-RTMP gateway on youtube for [audio call](http://www.youtube.com/watch?v=-_W2YVCIPg8) and [video call](http://www.youtube.com/watch?v=cI4nqBfHsXM). 19 | 20 | ## News ## 21 | 22 | **New** in svn r127: contains support for experimental [RTMFP server in pure python](/rtmfp.py). Have not tested it much! 23 | 24 | **New** in svn r125: H.264 now works between Bria 3 and Flash Player 11.2.202.96 (or later). See [this](http://p2p-sip.blogspot.com/2012/01/translating-h264-between-flash-player.html) article on _Translating H264 between Flash Player and SIP/RTP_ for details. 25 | 26 | 39 | 40 | ## History ## 41 | 42 | The project was started and most of the work was done in 2007. More recently I wrote example test application and finished the server part to make it complete. If you are interested in contributing, feel free to send me a patch with your changes. If you plan to use this software in your project or want to contribute significantly in this project or its features, feel free to send me a note to the [support group](http://groups.google.com/group/myprojectguide). You don't need to subscribe to that group to post a message. **I look forward to hearing from you!** 43 | 44 | There are other open-source RTMP servers available such as rtmpy.org and osflash.org/red5. My implementation is different because it does not use the complex Twisted library as in rtmpy.org and it is pure Python based couple of files instead of hundreds of Java files of Red5. I did use AMF parsing from rtmpy.org though. Secondly my project is a much simpler version of a full Red5 server and useful only for dealing with real-time media and doesn't implement shared object or web server style applications. 45 | 46 | ## Quick Start ## 47 | 48 | The software requires Python 2.6. After uncompressing the download or checking out the sources from SVN, run the server file with -h option to see all the command line options. 49 | ``` 50 | bash$ tar -zxvf rtmplite-7.0.tgz 51 | bash$ cd rtmplite 52 | bash$ python rtmp.py -h 53 | ``` 54 | 55 | To start the server with default options and debug trace, run the following: 56 | ``` 57 | bash$ python rtmp.py -d 58 | ``` 59 | 60 | A test client is available in testClient directory, and can be compiled using Flex Builder. I have already put the compiled SWF file in the bin-debug directory. Open your browser and then open the testClient.html file in your browser. The user interface will allow you to connect to the server to test the connection. You can also test streams by clicking on publish or play buttons. 61 | 62 | See the README file for more information 63 | 64 | ## New Features/Other Projects ## 65 | 66 | **Flash to SIP**: Starting with version 3.0 onwards, the software includes a [SIP-RTMP gateway module](/siprtmp.py) as well. The [siprtmp project page](/siprtmp.md) describes the SIP-RTMP module in detail. The project depends on the SIP stack from the [p2p-sip project](https://github.com/theintencity/p2p-sip). This module allows you to make Flash to SIP calls and vice-versa. With appropriate VoIP account you can also make Flash to phone or web to phone calls. _New:_ the gateway and sample Flash application now allow switching from 16000 (wideband) to 8000 (narrowband) sampling of Speex, so that it can work with certain telephony gateways. _Please read the [FAQ](siprtmp-faq.md)_ 67 | 68 | **Videocity**: The [Internet Videocity](https://github.com/theintencity/videocity) Project is another project that uses rtmplite as an RTMP server. The Videocity project aims at providing open source and free software tools to developers and system engineers to support enterprise and consumer video conferencing using ubiquitous web based Flash Player platform. The video communication is abstracted out as a city, where you own a home with several rooms, decorate your rooms with your favorite photos and videos, invite your friends and family to visit a room by handing out visiting card, or visit other people's rooms to video chat with them or to leave a video message if they are not in their home. 69 | 70 | **Client**: The [rtmpclient](/rtmpclient.py) module in this project implements a Python-based RTMP client that allows you to copy between remote RTMP stream and local file. Unlike rtmpdump project, this one supports both (1) copy from local file to RTMP server by publishing a live stream, and (2) copy from RTMP server to local file by dumping a live stream. This simple tool can be used for various Flash streaming related testing, e.g., by injecting live stream to a Flash Media Server from local FLV file, or dumping a live stream from Flash Media Server to a local FLV file. 71 | 72 | **Flash-VideoIO**: The [Flash-VideoIO](https://github.com/theintencity/flash-videoio) project aims at implementing reusable and generic Flash component with extensive JavaScript API to facilitate audio and video communication scenarios such as video messaging, broadcasting, call and conferencing. It is compatible with rtmplite media server for client server media streams. Take a look at How to do SIP-based VoIP call? as an alternative Javascript based front-end application to connect to siprtmp module of rtmplite server. 73 | 74 | ## License ## 75 | 76 | The software is open source under GNU Public License (GPL). If the viral nature of GPL is not suitable for your deployment, we also sell low cost [alternative commercial license](http://theintencity.com/services.html). In particular, the alternative commercial license allows you to combine pieces of our software with your other proprietary elements. 77 | 78 | ## Contributing ## 79 | If you have patch for a bug-fix or a feature, feel free to send us the patch to the [support group](http://groups.google.com/group/myprojectguide). If you plan to do significant contributions, please let me know and I will add you as a project member so that you can check in files using SVN. Please join the [support group](http://groups.google.com/group/myprojectguide) if you want to contribute or hear about the project announcements. 80 | 81 | Notice: The owners of the project reserve all the rights to source code. All the contributors and committers automatically and implicitly assign all the rights to the owners by contributing to this project. The rights assignment implies that the owners reserve all the rights to publish or distribute the sources or binaries elsewhere under another license for commercial or non-commercial use. Irrespective of the rights, this project will continue to remain open source. 82 | 83 | ## User Comments ## 84 | 85 | (Aug 2009): "I tried your RTMP-SIP gateway this afternoon. It's pretty neat. Awesome. Great job. I like it more than the Red5 project. It's a very good idea to implement it with python and it's lightweight and better integrate with ..." 86 | 87 | (Nov 2009): "I was looking for a lightweight rtmp server and tried out your server at http://code.google.com/p/siprtmp/ and it seems to have been running quite well. Hats off to you. ... Thanks for the great lightweight server." 88 | 89 | If you have any feedback, criticism or comment on siprtmp or rtmplite, feel free to send them to [support group](http://groups.google.com/group/myprojectguide). You don't need to subscribe to that group to post a message. 90 | -------------------------------------------------------------------------------- /rtmpt.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011, Kundan Singh. All rights reserved. see README for details. 2 | 3 | ''' 4 | This is a simple tunnel application that receives connection on RTMPT and forwards on RTMP. 5 | 6 | I have tested this with Flash VideoIO on Flash Player 11 and rtmplite's rtmp.py server. To test it yourself, first start an RTMP server, e.g., 7 | $ python rtmp.py -d 8 | This listens on default TCP port 1935. The -d option enabled debug trace during development. 9 | Now start this tunnel in debug mode listening on port 8080 (RTMPT/HTTP) and forwarding to localhost:1935 (RTMP). 10 | $ python rtmpt.py -l 0.0.0.0:8080 -t 127.0.0.1:1935 -d 11 | By default rtmpt.py listens on port 8080 on RTMPT and forward to localhost:1935 on RTMP, so the -l and -t options above are unnecessary. 12 | Now point your browser to http://myprojectguide.org/p/flash-videoio/test.html for the Flash videoIO test page. 13 | Set the "src" property to rtmpt://localhost:8080/myapp?publish=live to start publishing using RTMPT. 14 | To play the stream, open another browser instance or tab to the same test page, and set the "src" 15 | property to rtmpt://localhost:8080/myapp?play=live 16 | Note that the RTMP server is doing actual media conferencing, whereas this tunnel application just forwards between RTMPT/HTTP and RTMP. 17 | You can have some participants on "rtmp" and others on "rtmpt" as long as both connect to the same back end RTMP server under the same 18 | connection scope. 19 | 20 | Known issues: this tunnel software is in alpha with known issues: 21 | 1. Disconnection of publisher when player disconencts. 22 | ''' 23 | 24 | import random, socket, traceback, SocketServer 25 | 26 | _debug = False 27 | 28 | class Session(object): 29 | def __init__(self): 30 | self.id = str(random.randint(1000000000, 9999999999)) 31 | self._sock, self._timeout, self._pending, self._next = None, 0.020, [], 0 32 | 33 | def connect(self, target_address): 34 | sock = self._sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) 35 | sock.connect(target_address) 36 | sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) # make it non-block 37 | sock.settimeout(self._timeout) 38 | 39 | def close(self): 40 | if self._sock is not None: 41 | self._sock.close() 42 | self._sock = None 43 | 44 | def sendrecv(self, seq, data): 45 | if seq == self._next: 46 | self._next += 1 47 | if _debug: print '=>%r=> (%s)'%(seq, self.id) 48 | if data: self._sock.send(data) 49 | while self._pending: 50 | found = [(i, x[0], x[1]) for i, x in enumerate(self._pending) if x[0] == self._next] 51 | if not found: break 52 | index, seq, data = found[0] 53 | del self._pending[index] 54 | self._next += 1 55 | if _debug: print ' %r=> (%s)'%(seq, self.id) 56 | if data: self._sock.send(data) 57 | try: response = self._sock.recv(8192) 58 | except socket.timeout: response = '' 59 | else: 60 | if _debug: print '=>%r (%s)'%(seq, self.id) 61 | self._pending.append((seq, data)) 62 | response = '' # no need to respond with data in this case 63 | return response 64 | 65 | class tunnel(SocketServer.ThreadingMixIn, SocketServer.TCPServer): 66 | allow_reuse_address = True 67 | 68 | class handler(SocketServer.StreamRequestHandler): 69 | intervals = ('\x01', '\x03', '\x05', '\x09', '\x11', '\x21') 70 | 71 | def handle(self): 72 | if _debug: print 'created new handler for ', self.request 73 | interval, failed = self.intervals[0], 0 74 | try: 75 | while True: 76 | firstline = body = response = None 77 | headers = {} 78 | firstline = self.readline() 79 | if firstline is None: raise ValueError, 'connection closed in reading first line' 80 | if _debug: print firstline 81 | method, path, protocol = firstline.split(' ') 82 | if method != 'POST': raise ValueError, 'invalid method ' + method 83 | while True: 84 | line = self.readline() 85 | if line is None: raise ValueError, 'connection closed in reading headers' 86 | if _debug: print line 87 | if not line: break 88 | name, value = line.split(':', 1) 89 | headers[name.lower().strip()] = value.strip() 90 | ctype, clen, conn = [headers.get(name.lower(), None) for name in ('content-type', 'content-length', 'connection')] 91 | if ctype != 'application/x-fcs': raise ValueError, 'invalid content-type ' + ctype 92 | if clen: clen = int(clen) 93 | if clen > 0: body = self.read(clen) 94 | if path == '/fcs/ident2': 95 | self.send_error(404, 'Not Found') 96 | elif path == '/open/1': 97 | while True: 98 | session = Session() 99 | if session.id not in self.server.sessions: 100 | break 101 | session.close() 102 | try: 103 | session.connect(self.server.target_address) 104 | self.server.sessions[session.id] = session 105 | self.send_response(session.id + '\n') 106 | except socket.error: 107 | session.close() 108 | self.send_error(500, 'Cannot Connect to Server') 109 | else: 110 | parts = path.split('/') 111 | if len(parts) == 4 and parts[1] in ('idle', 'send', 'close'): 112 | ignore, command, sessionId, seq = parts 113 | session, seq = self.server.sessions.get(sessionId, None), int(seq) 114 | if not session: 115 | self.send_error(500, 'Invalid session ' + sessionId) 116 | elif command == 'idle' or command == 'send': 117 | response = session.sendrecv(seq, body if command == 'send' else None) 118 | if response: 119 | interval, failed = self.intervals[0], 0 120 | else: 121 | failed += 1 122 | if failed >= 10: 123 | index = self.intervals.index(interval) 124 | if index < len(self.intervals) - 1: index += 1 125 | interval, failed = self.intervals[index], 0 126 | if _debug: print 'changed interval to 0x%x'%(ord(interval),) 127 | self.send_response(interval + response) 128 | elif command == 'close': 129 | del self.server.sessions[session.id] 130 | session.close() 131 | self.send_response('\x00') 132 | else: 133 | raise ValueError, 'invalid path ' + path 134 | if conn == 'close': 135 | self.wfile.close() 136 | break 137 | except: 138 | if _debug: traceback.print_exc() 139 | self.wfile.close() 140 | 141 | def send_error(self, code, reason): 142 | self.write('HTTP/1.1 %d %s'%(code, reason), 'Content-Length: 0') 143 | 144 | def send_response(self, body): 145 | self.write('HTTP/1.1 200 OK', 'Content-Type: application/x-fcs', 'Content-Length: %d'%(len(body) if body else 0), body=body) 146 | 147 | def write(self, *args, **kwargs): 148 | data = '\r\n'.join(args) + '\r\n\r\n' 149 | if _debug: print data[:-2] 150 | if 'body' in kwargs and kwargs.get('body'): data += kwargs.get('body') 151 | self.wfile.write(data) 152 | 153 | def read(self, length): 154 | return self.rfile.read(length) 155 | 156 | def readline(self): 157 | value = self.rfile.readline() 158 | return None if not value else value.rstrip() if value[-1] == '\n' else value 159 | 160 | def run(server_address = ('0.0.0.0', 8080), target_address = ('127.0.0.1', 1935), 161 | server_class=tunnel, handler_class=handler): 162 | if _debug: print 'starting HTTP server on', server_address, 'target', target_address 163 | server = server_class(server_address, handler_class) 164 | server.target_address = target_address 165 | server.sessions = {} # map from session.id to session 166 | try: 167 | server.serve_forever() 168 | except KeyboardInterrupt: 169 | if _debug: print '\ninterrupted' 170 | server.server_close() 171 | 172 | 173 | # The main routine to start, run and stop the service 174 | if __name__ == '__main__': 175 | from optparse import OptionParser 176 | parser = OptionParser(version='SVN $Revision$, $Date$'.replace('$', '')) 177 | parser.add_option('-l', '--listen', dest='listen', default='0.0.0.0:8080', help="listening transport address. Default '0.0.0.0:8080'") 178 | parser.add_option('-t', '--target', dest='target', default="127.0.0.1:1935", help="target server address. Default is '127.0.0.1:1935'") 179 | parser.add_option('-d', '--verbose', dest='verbose', default=False, action='store_true', help='enable debug trace') 180 | (options, args) = parser.parse_args() 181 | 182 | _debug = options.verbose 183 | listen, target = [(x.partition(':')[0], int(x.partition(':')[2])) for x in (options.listen, options.target)] 184 | run(server_address=listen, target_address=target) 185 | -------------------------------------------------------------------------------- /siprtmp-faq.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions (SIP-RTMP gateway) # 2 | 3 | ### My gateway does not work. What do I do? ### 4 | 5 | Please start your gateway with "-d" command line option, so that it prints detailed trace information including the SIP messages. Then capture the trace, e.g., using Unix "script" command. Finally, send your error report and the trace to [support group](http://groups.google.com/group/myprojectguide). You can post a message from web or via email after joining the group. 6 | 7 | ### Does it work with other SIP servers or clients? ### 8 | 9 | Depends on what the other SIP client or server is. If there are interoperability bugs, we will be happy to resolve those. 10 | 11 | ### Can I directly connect from Flash client to SIP server? ### 12 | 13 | No. You need to go through the siprtmp.py (SIP-RTMP gateway). The path is Flash client connect to SIP-RTMP gateway, which in turn registers and communicates with the SIP server. But you can communicate with any existing SIP server, as long as there are no interoperability bugs, between the gateway and the SIP server. 14 | 15 | ### How do I test a SIP registration and call? ### 16 | 17 | Using the builtin videoPhone.swf applictaion (which is also hosted at http://myprojectguide.org/p/siprtmp). Support you want to register with gateway running on gateway.host.com and SIP server of iptel.org with username myname, password mypass, use the following input text when prompted to register. 18 | Gateway URL: rtmp://gateway.host.com/sip 19 | SIP URL: myname@iptel.org 20 | Auth username: myname 21 | Auth password: mypass 22 | Display name: My Name 23 | Once registered, use the SIP URL of target to make call, e.g., yourname@iptel.org 24 | 25 | ### Can I run the gateway on Amazon EC2 cloud? ### 26 | 27 | Yes, but you will need to use the latest sources of rtmplite and p2p-sip projects from SVN. Both rtmplite ([r61](https://code.google.com/p/rtmplite/source/detail?r=61)) and p2p-sip ([r29](https://code.google.com/p/rtmplite/source/detail?r=29)) were modified to allow specifying an IP address to advertise in SIP/SDP, which can be different than the local IP address. This is useful for EC2, because the node gets local IP address in range 10.x.x.x but can be reached from public IP address which is different. For example, if your EC2 node's private IP is 10.245.221.44 and public IP is 50.17.154.98, then you should start SIP-RTMP gateway with -e option as follows. 28 | ``` 29 | $ python siprtmp.py -d -e 50.17.154.98 30 | ``` 31 | You can know your public IP from the public DNS name of your node, e.g., host name of ec2-50-17-154-98.compute-1.amazonaws.com means IP is 50.17.154.98. I have tested siprtmp.py on EC2 using a call between VideoPhone.swf example and X-Lite via the SIP proxy running on iptel.org. 32 | 33 | There may be issues if you run your own SIP proxy server on another EC2 node since siprtmp.py and your SIP server should now connect using private IP instead of public IP. Please let us know if you have problems. 34 | 35 | ### Why does it pick 127.0.0.1 as the listening IP? ### 36 | 37 | When the Flash side connects to siprtmp, it prints the listening/advertised IP of the corresponding SIP User Agent. If you see local IP address here, then you won't be able to communicate with SIP phones/servers on other machines. The most common reason for this is that your 'hostname' points to local IP in your /etc/hosts file. The siprtmp code uses gethostbyname(gethostname()) to find the listening/advertised IP. 38 | 39 | To work around this, you can supply the -l option with your correct IP address, e.g., 40 | ``` 41 | $ python siprtmp.py -d -l 192.168.0.3 42 | ``` 43 | By default if -l (listening IP) is supplied, but -e (advertised IP) is not, then it uses the same -l value for advertised IP. 44 | 45 | ### Can I run the gateway or server on Google App Engine? ### 46 | 47 | No. The existing API of Google App Engine does not support long lived general purpose TCP connection that can be used for RTMP. The channel API uses another protocol, XMPP, and cannot be used as long-lived connection to your GAE application. A long-lived TCP connection is needed for RTMP media server implementation. 48 | 49 | ### How do I build my own Flash application to connect to the gateway? ### 50 | 51 | The necessary API is documented in the http://code.google.com/p/rtmplite/source/browse/trunk/siprtmp.py source code itself. In particular, you use NetConnection's connect and supply the appropriate parameters. 52 | 53 | ### When dialing out from web client, should I dial phone number or SIP address? ### 54 | 55 | You must dial a SIP address of the form "user@domain" or "user@ip-address". If you have PSTN dialing available via your gateway, then use the "phone-number@ip-address-of-pstn-gateway", e.g., "7140@192.1.2.3" instead of just "7140". 56 | 57 | ### When I call from X-lite it responds with "488 Incompatible SDP". Why? ### 58 | 59 | By default X-lite has only PCMU and PCMA codec enabled. You will need to go to audio options of X-lite and enable the Speex/16000 (wideband codec). 60 | 61 | ### How I do enable Speex to G.711 transcoding ### 62 | 63 | In siprtmp.py (and siprtmp\_gevent.py), we have added support for transcoding between speex and pcmu/pcma voice codecs using the py-audio project. Just follow [py-audio](http://code.google.com/p/py-audio) build instructions only for audiospeex module (ignoring the instructions for audiodev and audiotts) and place the generated audiospeex.so in your PYTHONPATH before starting siprtmp.py. Make sure you have the latest version of rtmplite and p2p-sip from SVN. More details of the codecs related enahancement are in [siprtmp.py](http://code.google.com/p/rtmplite/source/browse/trunk/siprtmp.py) under top level file comments heading "Major Updates". 64 | 65 | ### Does it work with Ekiga? ### 66 | 67 | Yes. Please make sure you use the latest p2p-sip sources from SVN, specially [r39](https://code.google.com/p/rtmplite/source/detail?r=39) or later, which works around the lack of RTP padding support in Ekiga. 68 | 69 | ### Does it work with Asterisk? ### 70 | 71 | Depends on what codec Asterisk supports. The siprtmp gateway supports speex/16000 ~~or 8000 (audio)~~ and x-flv/90000 (video). From what I understand, the Asterisk codec does not support speex. You can compile Asterisk to support speex using speex/8000 and not speex/16000. ~~The VideoPhone and siprtmp gateway now have support to dynamically switch from default 16000 (wideband) to 8000 (narrowband). Please see the next question on how to make siprtmp work with 8000 Hz sampling.~~ 72 | 73 | In short it will not work with Asterisk unless Asterisk supports speex/16000. 74 | 75 | **Update:** We found a bug in Asterisk 1.6.2 (may also be on other 1.6.x) that it sends incorrect Content-Length in 200 OK response (answer) to INVITE if the the INVITE request (offer) has video in SDP, but unsupported by target. To work around this you can comment out the following line in p2p-sip's [src/std/rfc3261.py](http://code.google.com/p/p2p-sip/source/browse/trunk/src/std/rfc3261.py). (on or near line 217) 76 | ``` 77 | if self.body != None and bodyLen != len(body): raise ValueError, ... 78 | ``` 79 | 80 | ### How can I use Speex/8000 (narrowband) instead of Speex/16000 (wideband)? ### 81 | 82 | ~~Flash Player by default uses 16000 Hz sampling for Speex. The new version of siprtmp and rtmplite (version 6.0 and above) support both 8000 and 16000 Hz sampling for Speex. In the VideoPhone Flash application, you can select the narrowband (8000) Speex in the right click menu. The right click menu allows you to switch between narrowband and wideband. The selection must be done before registration, but is saved along with you other registration data if you select "Remember me" option. The selection is supplied to the siprtmp.py gateway in the NetConnection.connect method as the rate attribute, and is used for audio format sampling rate.~~ 83 | 84 | I realized the hard way that even if I change Microphone's rate to 8, the Flash Player still uses 16 kHz for speex encoded stream from Microphone. So in the current form a flash application (e.g., VideoPhone.swf or VideoIO.swf) does not support speex/8000 (narrowband) even though the siprtmp.py gateway can handle it. On the other hand, a flash application can play speex/8000 (narrowband). I am working on modifying the speex wideband payload to narrowband in siprtmp.py if needed. 85 | 86 | ### How to send DTMF touch tone? ### 87 | 88 | I have added a patch sent by another person to both VideoPhone and siprtmp.py. During a call, you can click on the button in the top-left corner of VideoPhone Flash application to show a dial-pad. The dial-pad will allow you to enter digits during a call. It invokes "sendDTMF" method on siprtmp.py, which in turn uses the rfc2833.py module to send out the digits if the remote end had sent "telephone-event" in SDP during session initiation. Please use the latest code from SVN/[r34](https://code.google.com/p/rtmplite/source/detail?r=34) or later to get this patch. 89 | 90 | The following text describes how to use RFC 2198 along with RFC 2833 to send digits, and how to use SIP INFO method to send digits: 91 | 92 | Using RFC2833 is preferred way to send DTMF touch tones. You can use the rfc2833.py and rfc2198.py modules of p2p-sip/39peers project as mentioned in http://39peers.net/download/doc/report.html (search for "Touch-tone interface" towards the end). Since siprtmp.py already has the RTP session, you can use the existing RTP session to 93 | send out the digits. For example if you sending digits "1234" first create the RFC 2833 payloads as: 94 | ``` 95 | dtmfs = rfc2833.createDTMFs("1234") 96 | ``` 97 | This will return a list of DTMF objects. Then use RFC 2198 to create redundant payloads. Suppose your timestamp is 10000, interval of 1600 between digits and negotiated payload-type is 97, then as follows: 98 | ``` 99 | t0, td, pt = 10000, 1600, 97 100 | input = [] 101 | for dtmf in dtmfs: 102 | dtmf = repr(dtmf) # convert DTMF to str 103 | input.append((pt, t0, dtmf)) 104 | t0 += td 105 | payload = rfc2198.createRedundant(input) 106 | ``` 107 | You can now use the payload as the RTP's payload to send out in the existing RTP session. See siprtmp.py's rtmpdata method. Something like the following should work, where fmt should be set to your audio format for touch-tone. 108 | ``` 109 | self.session.media.send(payload=payload, ts=10000, marker=False, fmt=...) 110 | ``` 111 | We will try to add this feature to siprtmp.py soon, in the next version. 112 | 113 | On the other hand, if you do want to use the SIP INFO method, you can use the following example. In siprtmp.py, the Context's self.session represents the voip.Session object of the existing session. Session has a ua object representing the Dialog which has createRequest and sendRequest methods. You can use them as follows. 114 | ``` 115 | ua = self.session.ua 116 | m = ua.createRequest("INFO") 117 | m['Content-Type'] = rfc3261.Header('application/dtmf-relay', 'Content-Type') 118 | m.body = 'Signal=5\nDuration=160' 119 | ua.sendRequest(m) 120 | ``` 121 | This will send out the SIP INFO request in the current dialog/session with Content-Type of "application/dtmf-relay" and content of 122 | ``` 123 | Signal=5 124 | Duration=160 125 | ``` 126 | The Content-Length will automatically be added correctly to the request. 127 | 128 | ### How to send SIP MESSAGE for instant message? ### 129 | 130 | You can send SIP MESSAGE in an established call in siprtmp.py using Context's self.session (which is voip.Session object) as follows. In class Context add the following: 131 | ``` 132 | def rtmp_sendMessage(self, message): 133 | try: yield self.session.send(message) 134 | except: pass 135 | ``` 136 | The Session class uses Content-Type of text/plain to send a SIP MESSAGE with given message as content. 137 | Now you can call "sendMessage" with argument "message" from your Flash application using NetConnection's call method. 138 | 139 | To receive incoming SIP MESSAGE, in Context's `_sessionhandler` method, similar to the check for "close" add the following check. 140 | ``` 141 | if cmd == 'send': self.client.call('receivedMessage', arg) 142 | ``` 143 | Then add a public function of receivedMessage(text:String) in your ActionScript class for NetConnection.client. If you are using Flash-VideoIO, define receivedMessage in your HTML/Javascript page that embeds VideoIO.swf. 144 | 145 | If you want to send a out-of-dialog paging-mode SIP MESSAGE, you need to use sendIM of user object itself. 146 | ``` 147 | def rtmp_sendPagingMessage(self, dest, message): 148 | try: result, reason = yield self.user.sendIM(dest, message) 149 | except: pass 150 | ``` 151 | On incoming side, you will need to modify Context's `_incominghandler` parallel to close as follows: 152 | ``` 153 | elif cmd == 'send': # incoming paging SIP MESSAGE 154 | source, body = arg # arg is actually a tuple 155 | self.client.call("receivedPagingMessage", str(source), str(body)) 156 | ``` 157 | And define public function receivedPagingMessage(src:String, body:String) in your ActionScript. 158 | 159 | We will try to add instant message send/receive in the code, if there are huge demand for this. Please send a request on the support group to demand this feature. 160 | 161 | ### How to use RTMPS on the server? ### 162 | 163 | (contributed by Dmitry) There are no changes needed in rtmplite source code. You need to 164 | 1. install [stunnel](http://www.stunnel.org) 165 | 1. add the following in `stunnel.conf` 166 | ``` 167 | [rtmps] 168 | accept = 443 169 | connect = 1935 170 | ``` 171 | 1. In `NetConnection` object in your Flash application, set the `proxyType` to "best" before doing a connect 172 | ``` 173 | nc.proxyType = "best" 174 | ``` 175 | 1. In `NetConnection` object's `connect()` method use the URL with "rtmps" scheme, e.g., "rtmps://your-server/sip". 176 | 177 | If you want use non-443 port, for example 8443, change `stunnel.conf` and use port number in your URL, e.g., "rtmps://your-server:8443/sip". 178 | 179 | For Flash Player's RTMPS to work the following applies. 180 | 1. The certificate must be signed by a popular CA (not self signed). I believe there are CAs that can sign your certificate for free, but haven't tried them. I haven't been able to get a self signed certificate to work with RTMPS from Flash Player. 181 | 1. Pre-loading a self-signed certificate/CA in browser settings does not work for Flash Player's RTMPS. 182 | 1. The common name (CN) of the certificate must be the host name that you are connecting to. For example, if your certificate is for CN of `server.com` and you are connecting to `rtmps://xyz.server.com` or `rtmps://192.1.2.3` (which is the IP address) then it will **not** work. Alternatively, you can get the CN in your certificate as wildcard `*.server.com` 183 | 184 | ### How do I have multiple clients registered with same SIP address? ### 185 | 186 | Currently the Gateway class in siprtmp.py (in rtmplite project) disconnects other clients with same scope when a new client connects. This prevents multiple registrations for the same SIP address. For example, if first client connects with `rtmp://localhost/sip/alice@iptel.org` and then second client also connects to `rtmp://localhost/sip/alice@iptel.org` then the first client is disconnected. There are two approaches to solving this problem: 187 | 188 | 1) do not disconnect other clients. This can be done by modifying `siprtmp.py::Gateway::onConnect` method. Just comment out one line as shown below. 189 | ``` 190 | def onConnect(self, client, *args): 191 | App.onConnect(self, client, args) 192 | - for c in self.clients: multitask.add(c.connectionClosed()) 193 | client.context = Context(self, client) 194 | ... 195 | ``` 196 | There may be potential problems in audio path, but not sure, since audio path is maintained using named streams within the scope, i.e., `/sip/alice@iptel.org`. 197 | 198 | 2) let the clients connect with random scope, e.g., `rtmp://localhost/sip/alice@iptel.org/72812` where 72812 is some random number uniquely generated for each client. This avoids any audio path problem since each client has separate named streams now. Also, it avoids disconnection of other clients since each client will have potentially different random number. However, `siprtmp.py::Context::rtmp_register` method needs to be modified to extract the SIP address correctly as shown below. 199 | ``` 200 | def rtmp_register(self, login=None, passwd='', display=None, rate='wideband'): 201 | global agent 202 | scheme, ignore, aor = self.client.path.partition('/') 203 | + aor = aor.split('/', 1)[0] # ignore /random in /aor/random if present 204 | if rate == 'narrowband': self._audio.rate = 8000 205 | ... 206 | ``` 207 | I think 2 is a better approach but requires changes both in client (`VideoPhone`) and server (`rtmplite`), whereas 1 requires a change only in server. 208 | 209 | ### Why cannot play media published using wirecast to rtmplite? ### 210 | 211 | You can, but will need to use the Wirecast application instead of default App, and is accessible at `rtmp://your-server/wirecast` when using rtmp.py or siprtmp\_gevent.py as your RTMP server. If you want it to be available at another URL just change the URL-app to application class mapping (look for apps property in both files). 212 | 213 | The problem is that Wirecast sends the AVC seq message only once in the beginning and then does not send it again. Hence if a Flash Player joins the stream after publishing has already started, then the Flash Player never receives the AVC seq message which is crucial in decoding the video. On the other hand Flash Player (11+) with H264Avc video codec mode sends the AVC seq message periodically, before every intra frame. To work around the limitation of Wirecast, I created a sub-class of App to store the published AVC seq messages, and send it to any player who join afterwards if no explicit AVC seq message was sent before intra frame. The sub-class also drops any non-infra frame after a player joins before an AVC seq and an intra frame are sent to the player. 214 | 215 | ### The second call with Asterisk does not work? ### 216 | 217 | Thanks to Roman Tsymbalyuk, we found a problem in the SIP ACK generated by Asterisk. It uses the same branch parameter in the top-via header resulting in incorrect branch (and transaction) matching in my SIP library. Thus, the ACK from Asterisk for the second call is matched with the transaction of the previous ACK, and treated as a retransmission instead of completing the second call. A quick work-around to fix this is as follows (This will be checked in soon). In p2p-sip's src/std/rfc3261.py file's Stack class' `_`receivedRequest method, locate a call to app.createTransaction(r). Modify to add the following change. 218 | ``` 219 | if app: 220 | t = app.createTransaction(r) 221 | + if r.method == 'ACK' and t.id in self.transactions: 222 | + del self.transactions[t.id] # no need to store the ACK transaction. 223 | elif r.method != 'ACK': 224 | self.send(Message.createResponse(404, "Not found", None, None, r)) 225 | ``` -------------------------------------------------------------------------------- /siprtmp.md: -------------------------------------------------------------------------------- 1 | ## SIP-RTMP gateway ## 2 | 3 | > This project was migrated from on May 17, 2015 4 | > Keywords: *SIP*, *RTMP*, *Flash*, *Python*, *VoIP*, *Gateway*, *Phone*, *VideoPhone* 5 | > Members: *voipresearcher* (owner), *theintencity* (owner), *kundan10* 6 | > Links: [Support](http://groups.google.com/group/myprojectguide), [Download](https://github.com/theintencity/rtmplite/tree/downloads) 7 | > License: [GNU GPL v3](http://www.gnu.org/licenses/gpl.html) 8 | > Others: starred by 58 users 9 | 10 | ![logo](/siprtmp.png) 11 | 12 | The goal of this project is to allow [Flash to SIP calls](http://myprojectguide.org/node/6#comment-2) and vice versa. In particular it allows multimedia calls from Flash Player to SIP network and SIP network to Flash Player. The gateway implements translation of signaling as well as media between Flash Player's RTMP and standard SIP, SDP and RTP/RTCP. The client side API allows you or any third-party to build user interface of web-based audio and video phone that uses SIP in the back end. The user applications can be built using ActionScript for web browser as well as standalone AIR. The Gateway can run either as a server hosted by the provider, or as a local application on the client's host. Thus, this software caters to various customers and users. 13 | 14 | IMPORTANT Please send any bug/support request to the [support group](http://groups.google.com/group/myprojectguide) instead of directly to the owner 15 | 16 | News: Added translation of H.264 video and G.711 in Flash Player 11. Use latest from SVN instead of download version for subsequent bug fixes. The translation now works between Bria 3 and Flash Player 11.2.202.96 (beta) after a bug fix in Flash Player. 17 | 18 | News: Added transcoding between speex and pcmu/pcma voice codecs using the [py-audio](https://github.com/theintencity/py-audio) project. 19 | 20 | News: Added gevent-based version of the siprtmp gateway for improved performance. 21 | 22 | News: Added support for -e option to specify external public IP address which is advertised in all SIP/SDP messages. This is useful for running siprtmp.py on Amazon EC2. Please see [FAQ](/siprtmp-faq.md) for details. 23 | 24 | News: Support for narrowband Speex codec has been added in version 6.0 for interoperability with certain telephony gateways which do not support wideband Speex codec. 25 | 26 | News: Added a demo video and subtitles file for getting started with siprtmp. See the [download](https://github.com/theintencity/rtmplite/tree/downloads) folder. 27 | 28 | ## Further documentation ## 29 | 30 | I have written the source code in Python with extensive documentation in the source code itself. The code comment explains how the software works, design choices as well as overview of the client API. Please see the [siprtmp.py source code](/siprtmp.py) for details. Feel free to browse through the client side of the code to know how the API is used in the ActionScript client. 31 | 32 | Our white paper on Flash based audio and video communication in the cloud (section 7) compares various architecture and API options for interoperability between Flash Player and SIP/RTP, and describes our SIP-RTMP message flow, session negotiation, media transport and media format in detail. You can view the demo video of SIP-RTMP gateway on youtube for [audio call](http://www.youtube.com/watch?v=-_W2YVCIPg8) and [video call](http://www.youtube.com/watch?v=cI4nqBfHsXM). 33 | 34 | ## Download and browse source ## 35 | 36 | This project shares the source code repository with the [RTMP Server](/rtmp.py) project. Since I own both the projects, I decided to keep a single source repository to simplify software development. 37 | 38 | **[Download](https://github.com/theintencity/rtmplite/tree/downloads)** the latest version, or get **[source control](https://github.com/theintencity/rtmplite.git)** access to the software. I have also put the current versions of the download in the download section of this page, but I request you to get the latest version from the links above. You will also need to download the dependencies as mentioned in the Quick Start section below. 39 | 40 | ## License ## 41 | 42 | The software is open source under GNU Public License (GPL). If the viral nature of GPL is not suitable for your deployment, we also sell low cost [alternative commercial license](http://theintencity.com/services.html). In particular, the alternative commercial license allows you to combine pieces of our software with your other proprietary elements. 43 | 44 | ## Support and Feedback ## 45 | 46 | If you are a VoIP provider who needs to use this web-to-phone feature for your network, please get in touch with me on how I can help you. I can point you in the right direction from installation, provisioning, trouble shooting to building a client Flash application for your web site. _Please see the [FAQ](/siprtmp-faq.md) as well._ 47 | 48 | If you are a developer who wants to add a new feature to the software or use the software in your project, feel free to post a message to the support group. I can provide direction on which module to look at or modify for your work. 49 | 50 | You can post a message to the [support group](http://groups.google.com/group/myprojectguide). You don't need to subscribe to that group to post a message. **I look forward to hearing from you!** 51 | 52 | ## Quick Start ## 53 | 54 | The software requires Python 2.6. It has an external dependency on the [p2p-sip](http://github.com/theintencity/p2p-sip) project. Please download the latest version of the source code from the [p2p-sip project page](http://github.com/theintencity/p2p-sip). Please follow the instructions on that site on how to install. I have provided the current instructions below, which may change later the project. 55 | 56 | ``` 57 | bash$ tar -zxvf source-*.tgz 58 | bash$ export PYTHONPATH=p2p-sip/src:. 59 | ``` 60 | 61 | Next, download the siprtmp source code from this rtmplite project. Make sure to download version 3.0 or later which includes support for siprtmp module. For support of narrowband codec use version 6.0 or later. 62 | 63 | ``` 64 | bash$ tar -zxvf rtmplite-6.0.tgz 65 | ``` 66 | 67 | Now that you have the p2p-sip and rtmplite directories, you can run the siprtmp module form the rtmplite directory as follows. Make sure to set the PYTHONPATH correctly to point to dependencies. 68 | 69 | ``` 70 | bash$ cd rtmplite 71 | bash$ export PYTHONPATH=../p2p-sip/src:. 72 | bash$ python siprtmp.py -d 73 | ``` 74 | 75 | The siprtmp module takes the same command line as the rtmp module. The difference is that siprtmp module also enables the sip application for SIP-RTMP gateway service. At this point your SIP-RTMP gateway server is running on local host. 76 | 77 | You can visit [http://myprojectguide.org/p/siprtmp](http://myprojectguide.org/p/siprtmp) to view the user interface for making or receiving calls. This is same as the Video Phone client described next under Testing. 78 | 79 | ## Testing ## 80 | 81 | I will describe two test scenarios below which allow you to test the service locally without requiring an external SIP account. The testing employs the sample Video Phone client available in the rtmplite directory under videoPhone subdirectory. The `rtmplite/videoPhone/bin-release` folder contains a VideoPhone.html file which embeds the Flash application for the sample client. 82 | 83 | You will also need two additional software for doing the test: a SIP server and a SIP client. I use [Free X-Lite](http://www.counterpath.com/) SIP user agent because it also supports wide-band speex audio codec, which is required by this SIP-RTMP gateway software. I also use the sipd.py module available in the p2p-sip code I mentioned before, for the SIP server functions. You may be able to use some other SIP server or SIP user agent as long as they are in the same network as your SIP-RTMP gateway, i.e., no firewall or NAT among these. Run the SIP server in another terminal as follows since you have already installed p2p-sip code. The -d option allows you to trace various SIP messages handled by the server. 84 | 85 | ``` 86 | bash$ cd p2p-sip/src 87 | bash$ export PYTHONPATH=app:external:. 88 | bash$ python app/sipd.py -d 89 | ``` 90 | 91 | **First test:** In the first test, open the VideoPhone.html from browsers on two different computers (or if you don't have two different computers, perhaps from two different browsers or browser instances, so that cookies do not mess up your testing). When the Flash widget loads, it first tries grab your devices. You will need to "Allow" access to your devices and also check the "Remember" box in the Flash Player settings so that it stores your preference for this page. 92 | 93 | Next, specify your configuration information in the widget. The first is the gateway URL, which should be rtmp://localhost/sip if you are running the siprtmp gateway locally. Suppose your local IP address is 192.168.1.3 then you will use this in your SIP addresses. The next field is your SIP address. You can pick two random names such as "alice" and "bob" and then use say alice@192.168.1.3 on the first browser and bob@192.168.1.3 on the second browser. The next field is your authentication name which you can use alice and bob respectively on the two browsers. Next is the authentication password which you can put anything as our SIP server in this test does not do authentication. Then the display name can be set as say "Alice 1" and "Bob 2". When it prompts you to remember the configuration, you should check that so that you don't have to go through the whole configuration process next time for this web page. After you click next, it will try to connect to the gateway server, which in turn generates SIP registrations to the SIP server. At this point both your web clients are ready to make and receive calls. At this point, your local video should appear in the widget. 94 | 95 | In the first browser, type sip:bob@192.168.1.3 which is the SIP address of the second browser. Then click on the next button. The second browser should receive an incoming call indicated by the blinking button. Click on the blinking button to accept the call. You may click on the other button to reject the call, alternatively. Once the call is established you should be able to hear and see between these two web clients. There are a few user interface controls that allow you to switch between local video, remote video and picture-in-picture mode. When you want to terminate the call, click the appropriate button on one of the browser's widget, or simply close the browser. The other side should receive the call termination signal and close the call. 96 | 97 | 98 | **Second test:** In the second test, we will interoperate between this Video Phone of first user, alice, and a standard SIP user agent, X-Lite. This will be an audio call test because X-Lite does not understand the RTMP video format used by the Flash Player. Since X-Lite supports wideband Speex audio codec, we can interoperate between our gateway and X-Lite. 99 | 100 | The first step is to install and configure your X-Lite client on the second computer or as a replacement for second browser on your computer. Once installed, open the "Options" dialog box using the right-click menu, and go to the Advanced then Audio Codecs tab. Make sure "Speex Wideband" is among the enabled codecs listed on that page. Also, under the Advanced then Quality of Service tab, make sure that all the options are set to "None" otherwise X-Lite is known to cause problems in certain network conditions. Feel free to explore other settings as appropriate. 101 | 102 | Next, create a new SIP account in X-Lite using the "SIP Account" setting in the right-click menu. Add or modify the account to reflect the second user's credentials such as display name as Bob 2, user name as bob, domain as 192.168.1.3 and enable to register with domain and receive calls. Also set the outbound proxy mode to 192.168.1.3. Under the Topology tab select to use local IP address and do not discover STUN server, since all our testing is in the intra-net. Once you close the SIP Account dialog box, X-Lite will register with our SIP server on behalf of user bob. 103 | 104 | Now you can use the same procedure as before to place a call from widget of the first browser to the X-Lite user. When the phone rings, answer the phone and your should get connected between the first browser and the X-Lite phone. Only audio will work between these two clients. The widget will display blank video of the remote party. 105 | 106 | You may terminate the call either from the widget or X-Lite client. 107 | 108 | For other variations in this test, you can try initiating the call from the X-Lite client by dialing alice which is received by the first browser's widget. Similarly, you can also test canceling an outgoing call or rejecting an incoming call from either of the clients. 109 | 110 | Once you are comfortable testing the set up, you can explore further options in the gateway, the SIP server as well as the client. You may also want to build your own Flash application using the client API to connect to the gateway directly. For Flash Player to allow device access your Flash application must have a minimum dimension of 214x137. This is the dimension of the sample Video Phone application included in the software. 111 | 112 | ### Narrowband Speex ### 113 | 114 | For connecting to telephony gateways such as Asterix, you will need to use narrowband Speex codec. If you would like to use narrowband Speex codec at 8000 Hz instead of the default wideband codec at 16000 Hz, you can right click on your Flash application and choose "Use narrowband Speex" from the menu option. The right click context menu allows you to switch between narrowband and wideband codecs. But you MUST do the selection before it connects to the gateway server. Hence this must be done before you click on the next button on the remember prompt. The settings are saved hence you don't need to switch it next time if you have selected to remember the configuration. Please see the [FAQ](/siprtmp-faq.md) as well. 115 | 116 | ### White-labelled phone ### 117 | 118 | If you are interested in building a white-labelled web-based phone to talk to siprtmp, take a look at How to do SIP-based VoIP call? The VideoPhone application available with siprtmp has its own call control user interface, but you can use flash-videoio project for a clean Javascript enabled application interface to connect to siprtmp. 119 | 120 | ## Deployment ## 121 | 122 | A real-deployment of this software will require far more testing and a few more features. Since there is no NAT and firewall traversal support in the gateway currently, you need to run the siprtmp gateway in the public Internet if you want to deploy this service. Secondly, this gateway should have direct access to the SIP server and assumes that if the SIP client is behind NAT and firewall then the SIP client or server somehow manage to traverse them, such that gateway sees the SIP side on the public Internet. Since the gateway does not implement RTMP tunneling, the connection from browser client to the gateway may not work under certain restricted firewall, such as those that block RTMP TCP port 1935 from client to the server/gateway. 123 | 124 | As I mentioned before, I would love to hear from you if you plan to use this software in your project or deployment! I may also be able to point you to the right direction on how to proceed with the deployment and help troubleshoot this software for your project. 125 | 126 | ## Final Words ## 127 | 128 | This software is provided with a hope to break away from the Flash Player's restrictions, to allow interoperability between Flash applications and SIP network, and to allow Flash and Flex developers to build interesting new Internet Multimedia applications using the SIP technology. As such this software is released under GNU GPL v3, and if you use this software in your project, you will also need to release the source code of your project. I believe a free software should be viral and GNU GPL gives a tool to do so. If the software does not work, you can contribute to fix it. There is no warranty or guarantee on this software. If we share our time and effort, all of us will benefit. 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /siprtmp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theintencity/rtmplite/af7fd71d25765ee30c9f9b6c269309bcf657a756/siprtmp.png -------------------------------------------------------------------------------- /testClient/.actionScriptProperties: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /testClient/.flexProperties: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /testClient/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | testClient 4 | 5 | 6 | 7 | 8 | 9 | com.adobe.flexbuilder.project.flexbuilder 10 | 11 | 12 | 13 | 14 | 15 | com.adobe.flexbuilder.project.flexnature 16 | com.adobe.flexbuilder.project.actionscriptnature 17 | 18 | 19 | -------------------------------------------------------------------------------- /testClient/bin-debug/AC_OETags.js: -------------------------------------------------------------------------------- 1 | // Flash Player Version Detection - Rev 1.6 2 | // Detect Client Browser type 3 | // Copyright(c) 2005-2006 Adobe Macromedia Software, LLC. All rights reserved. 4 | var isIE = (navigator.appVersion.indexOf("MSIE") != -1) ? true : false; 5 | var isWin = (navigator.appVersion.toLowerCase().indexOf("win") != -1) ? true : false; 6 | var isOpera = (navigator.userAgent.indexOf("Opera") != -1) ? true : false; 7 | 8 | function ControlVersion() 9 | { 10 | var version; 11 | var axo; 12 | var e; 13 | 14 | // NOTE : new ActiveXObject(strFoo) throws an exception if strFoo isn't in the registry 15 | 16 | try { 17 | // version will be set for 7.X or greater players 18 | axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7"); 19 | version = axo.GetVariable("$version"); 20 | } catch (e) { 21 | } 22 | 23 | if (!version) 24 | { 25 | try { 26 | // version will be set for 6.X players only 27 | axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6"); 28 | 29 | // installed player is some revision of 6.0 30 | // GetVariable("$version") crashes for versions 6.0.22 through 6.0.29, 31 | // so we have to be careful. 32 | 33 | // default to the first public version 34 | version = "WIN 6,0,21,0"; 35 | 36 | // throws if AllowScripAccess does not exist (introduced in 6.0r47) 37 | axo.AllowScriptAccess = "always"; 38 | 39 | // safe to call for 6.0r47 or greater 40 | version = axo.GetVariable("$version"); 41 | 42 | } catch (e) { 43 | } 44 | } 45 | 46 | if (!version) 47 | { 48 | try { 49 | // version will be set for 4.X or 5.X player 50 | axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.3"); 51 | version = axo.GetVariable("$version"); 52 | } catch (e) { 53 | } 54 | } 55 | 56 | if (!version) 57 | { 58 | try { 59 | // version will be set for 3.X player 60 | axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.3"); 61 | version = "WIN 3,0,18,0"; 62 | } catch (e) { 63 | } 64 | } 65 | 66 | if (!version) 67 | { 68 | try { 69 | // version will be set for 2.X player 70 | axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"); 71 | version = "WIN 2,0,0,11"; 72 | } catch (e) { 73 | version = -1; 74 | } 75 | } 76 | 77 | return version; 78 | } 79 | 80 | // JavaScript helper required to detect Flash Player PlugIn version information 81 | function GetSwfVer(){ 82 | // NS/Opera version >= 3 check for Flash plugin in plugin array 83 | var flashVer = -1; 84 | 85 | if (navigator.plugins != null && navigator.plugins.length > 0) { 86 | if (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]) { 87 | var swVer2 = navigator.plugins["Shockwave Flash 2.0"] ? " 2.0" : ""; 88 | var flashDescription = navigator.plugins["Shockwave Flash" + swVer2].description; 89 | var descArray = flashDescription.split(" "); 90 | var tempArrayMajor = descArray[2].split("."); 91 | var versionMajor = tempArrayMajor[0]; 92 | var versionMinor = tempArrayMajor[1]; 93 | var versionRevision = descArray[3]; 94 | if (versionRevision == "") { 95 | versionRevision = descArray[4]; 96 | } 97 | if (versionRevision[0] == "d") { 98 | versionRevision = versionRevision.substring(1); 99 | } else if (versionRevision[0] == "r") { 100 | versionRevision = versionRevision.substring(1); 101 | if (versionRevision.indexOf("d") > 0) { 102 | versionRevision = versionRevision.substring(0, versionRevision.indexOf("d")); 103 | } 104 | } 105 | var flashVer = versionMajor + "." + versionMinor + "." + versionRevision; 106 | } 107 | } 108 | // MSN/WebTV 2.6 supports Flash 4 109 | else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.6") != -1) flashVer = 4; 110 | // WebTV 2.5 supports Flash 3 111 | else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.5") != -1) flashVer = 3; 112 | // older WebTV supports Flash 2 113 | else if (navigator.userAgent.toLowerCase().indexOf("webtv") != -1) flashVer = 2; 114 | else if ( isIE && isWin && !isOpera ) { 115 | flashVer = ControlVersion(); 116 | } 117 | return flashVer; 118 | } 119 | 120 | // When called with reqMajorVer, reqMinorVer, reqRevision returns true if that version or greater is available 121 | function DetectFlashVer(reqMajorVer, reqMinorVer, reqRevision) 122 | { 123 | versionStr = GetSwfVer(); 124 | if (versionStr == -1 ) { 125 | return false; 126 | } else if (versionStr != 0) { 127 | if(isIE && isWin && !isOpera) { 128 | // Given "WIN 2,0,0,11" 129 | tempArray = versionStr.split(" "); // ["WIN", "2,0,0,11"] 130 | tempString = tempArray[1]; // "2,0,0,11" 131 | versionArray = tempString.split(","); // ['2', '0', '0', '11'] 132 | } else { 133 | versionArray = versionStr.split("."); 134 | } 135 | var versionMajor = versionArray[0]; 136 | var versionMinor = versionArray[1]; 137 | var versionRevision = versionArray[2]; 138 | 139 | // is the major.revision >= requested major.revision AND the minor version >= requested minor 140 | if (versionMajor > parseFloat(reqMajorVer)) { 141 | return true; 142 | } else if (versionMajor == parseFloat(reqMajorVer)) { 143 | if (versionMinor > parseFloat(reqMinorVer)) 144 | return true; 145 | else if (versionMinor == parseFloat(reqMinorVer)) { 146 | if (versionRevision >= parseFloat(reqRevision)) 147 | return true; 148 | } 149 | } 150 | return false; 151 | } 152 | } 153 | 154 | function AC_AddExtension(src, ext) 155 | { 156 | if (src.indexOf('?') != -1) 157 | return src.replace(/\?/, ext+'?'); 158 | else 159 | return src + ext; 160 | } 161 | 162 | function AC_Generateobj(objAttrs, params, embedAttrs) 163 | { 164 | var str = ''; 165 | if (isIE && isWin && !isOpera) 166 | { 167 | str += ' '; 173 | str += ''; 174 | } else { 175 | str += ' 2 | 3 | 4 | 5 | 6 | 7 | 27 | Hidden frame for Browser History support. 28 | 29 | 30 | -------------------------------------------------------------------------------- /testClient/bin-debug/playerProductInstall.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theintencity/rtmplite/af7fd71d25765ee30c9f9b6c269309bcf657a756/testClient/bin-debug/playerProductInstall.swf -------------------------------------------------------------------------------- /testClient/bin-debug/testClient.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 44 | 45 | 46 | 47 | 101 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /testClient/bin-debug/testClient.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theintencity/rtmplite/af7fd71d25765ee30c9f9b6c269309bcf657a756/testClient/bin-debug/testClient.swf -------------------------------------------------------------------------------- /testClient/libs/.ignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theintencity/rtmplite/af7fd71d25765ee30c9f9b6c269309bcf657a756/testClient/libs/.ignore -------------------------------------------------------------------------------- /testClient/src/testClient.mxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /testP2P/Makefile: -------------------------------------------------------------------------------- 1 | 2 | VERSION:=1.0 3 | 4 | #MXMLC45:=/Applications/Adobe\ Flex\ Builder\ 3/sdks/4.5/bin/mxmlc 5 | MXMLC45:=/Applications/Adobe\ Flash\ Builder\ 4/sdks/4.5/bin/mxmlc 6 | 7 | BINARIES:= bin-debug/testP2P.swf 8 | 9 | all: ${BINARIES} 10 | 11 | bin-debug/testP2P.swf: src/testP2P.mxml Makefile 12 | ${MXMLC45} -output $@ -compiler.debug=true \ 13 | -swf-version=12 -target-player 10.3.0 \ 14 | -static-link-runtime-shared-libraries=true -source-path src -- src/testP2P.mxml 15 | 16 | clean: 17 | rm -f bin-debug/testP2P.swf 18 | -------------------------------------------------------------------------------- /testP2P/bin-debug/testP2P.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theintencity/rtmplite/af7fd71d25765ee30c9f9b6c269309bcf657a756/testP2P/bin-debug/testP2P.swf -------------------------------------------------------------------------------- /testP2P/src/testP2P.mxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /videoPhone/.actionScriptProperties: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /videoPhone/.flexProperties: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /videoPhone/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | Video Phone 4 | 5 | 6 | 7 | 8 | 9 | com.adobe.flexbuilder.project.flexbuilder 10 | 11 | 12 | 13 | 14 | 15 | com.adobe.flexbuilder.project.flexnature 16 | com.adobe.flexbuilder.project.actionscriptnature 17 | 18 | 19 | -------------------------------------------------------------------------------- /videoPhone/Makefile: -------------------------------------------------------------------------------- 1 | MXMLC45:=/Applications/Adobe\ Flash\ Builder\ 4/sdks/4.5/bin/mxmlc 2 | MXMLC:=/Applications/Adobe\ Flash\ Builder\ 4/sdks/3.5/bin/mxmlc 3 | 4 | all: bin-release/VideoPhone.swf bin-release/VideoPhone45.swf bin-release/VideoPhone11.swf 5 | 6 | clean: 7 | rm -f bin-release/VideoPhone.swf bin-release/VideoPhone45.swf bin-release/VideoPhone11.swf 8 | 9 | bin-release/VideoPhone.swf: 10 | ${MXMLC} -output $@ -compiler.debug=false \ 11 | -define=CONFIG::sdk4,false -define=CONFIG::player11,false \ 12 | -target-player 10.0.0 \ 13 | -locale=en_US -source-path=locale/{locale} \ 14 | -static-link-runtime-shared-libraries=true -source-path src -- src/VideoPhone.mxml 15 | 16 | bin-release/VideoPhone45.swf: 17 | ${MXMLC45} -output $@ -compiler.debug=false \ 18 | -define=CONFIG::sdk4,true -define=CONFIG::player11,false \ 19 | -swf-version=12 -target-player 10.3.0 \ 20 | -locale=en_US -source-path=locale/{locale} \ 21 | -static-link-runtime-shared-libraries=true -source-path src -- src/VideoPhone.mxml 22 | 23 | bin-release/VideoPhone11.swf: 24 | ${MXMLC45} -output $@ -compiler.debug=false \ 25 | -define=CONFIG::sdk4,true -define=CONFIG::player11,true \ 26 | -swf-version=13 -target-player 11.0 \ 27 | -locale=en_US -source-path=locale/{locale} \ 28 | -static-link-runtime-shared-libraries=true -source-path src -- src/VideoPhone.mxml 29 | 30 | -------------------------------------------------------------------------------- /videoPhone/bin-release/AC_OETags.js: -------------------------------------------------------------------------------- 1 | // Flash Player Version Detection - Rev 1.6 2 | // Detect Client Browser type 3 | // Copyright(c) 2005-2006 Adobe Macromedia Software, LLC. All rights reserved. 4 | var isIE = (navigator.appVersion.indexOf("MSIE") != -1) ? true : false; 5 | var isWin = (navigator.appVersion.toLowerCase().indexOf("win") != -1) ? true : false; 6 | var isOpera = (navigator.userAgent.indexOf("Opera") != -1) ? true : false; 7 | 8 | function ControlVersion() 9 | { 10 | var version; 11 | var axo; 12 | var e; 13 | 14 | // NOTE : new ActiveXObject(strFoo) throws an exception if strFoo isn't in the registry 15 | 16 | try { 17 | // version will be set for 7.X or greater players 18 | axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7"); 19 | version = axo.GetVariable("$version"); 20 | } catch (e) { 21 | } 22 | 23 | if (!version) 24 | { 25 | try { 26 | // version will be set for 6.X players only 27 | axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6"); 28 | 29 | // installed player is some revision of 6.0 30 | // GetVariable("$version") crashes for versions 6.0.22 through 6.0.29, 31 | // so we have to be careful. 32 | 33 | // default to the first public version 34 | version = "WIN 6,0,21,0"; 35 | 36 | // throws if AllowScripAccess does not exist (introduced in 6.0r47) 37 | axo.AllowScriptAccess = "always"; 38 | 39 | // safe to call for 6.0r47 or greater 40 | version = axo.GetVariable("$version"); 41 | 42 | } catch (e) { 43 | } 44 | } 45 | 46 | if (!version) 47 | { 48 | try { 49 | // version will be set for 4.X or 5.X player 50 | axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.3"); 51 | version = axo.GetVariable("$version"); 52 | } catch (e) { 53 | } 54 | } 55 | 56 | if (!version) 57 | { 58 | try { 59 | // version will be set for 3.X player 60 | axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.3"); 61 | version = "WIN 3,0,18,0"; 62 | } catch (e) { 63 | } 64 | } 65 | 66 | if (!version) 67 | { 68 | try { 69 | // version will be set for 2.X player 70 | axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"); 71 | version = "WIN 2,0,0,11"; 72 | } catch (e) { 73 | version = -1; 74 | } 75 | } 76 | 77 | return version; 78 | } 79 | 80 | // JavaScript helper required to detect Flash Player PlugIn version information 81 | function GetSwfVer(){ 82 | // NS/Opera version >= 3 check for Flash plugin in plugin array 83 | var flashVer = -1; 84 | 85 | if (navigator.plugins != null && navigator.plugins.length > 0) { 86 | if (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]) { 87 | var swVer2 = navigator.plugins["Shockwave Flash 2.0"] ? " 2.0" : ""; 88 | var flashDescription = navigator.plugins["Shockwave Flash" + swVer2].description; 89 | var descArray = flashDescription.split(" "); 90 | var tempArrayMajor = descArray[2].split("."); 91 | var versionMajor = tempArrayMajor[0]; 92 | var versionMinor = tempArrayMajor[1]; 93 | var versionRevision = descArray[3]; 94 | if (versionRevision == "") { 95 | versionRevision = descArray[4]; 96 | } 97 | if (versionRevision[0] == "d") { 98 | versionRevision = versionRevision.substring(1); 99 | } else if (versionRevision[0] == "r") { 100 | versionRevision = versionRevision.substring(1); 101 | if (versionRevision.indexOf("d") > 0) { 102 | versionRevision = versionRevision.substring(0, versionRevision.indexOf("d")); 103 | } 104 | } else if (versionRevision[0] == "b") { 105 | versionRevision = versionRevision.substring(1); 106 | } 107 | var flashVer = versionMajor + "." + versionMinor + "." + versionRevision; 108 | } 109 | } 110 | // MSN/WebTV 2.6 supports Flash 4 111 | else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.6") != -1) flashVer = 4; 112 | // WebTV 2.5 supports Flash 3 113 | else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.5") != -1) flashVer = 3; 114 | // older WebTV supports Flash 2 115 | else if (navigator.userAgent.toLowerCase().indexOf("webtv") != -1) flashVer = 2; 116 | else if ( isIE && isWin && !isOpera ) { 117 | flashVer = ControlVersion(); 118 | } 119 | return flashVer; 120 | } 121 | 122 | // When called with reqMajorVer, reqMinorVer, reqRevision returns true if that version or greater is available 123 | function DetectFlashVer(reqMajorVer, reqMinorVer, reqRevision) 124 | { 125 | versionStr = GetSwfVer(); 126 | if (versionStr == -1 ) { 127 | return false; 128 | } else if (versionStr != 0) { 129 | if(isIE && isWin && !isOpera) { 130 | // Given "WIN 2,0,0,11" 131 | tempArray = versionStr.split(" "); // ["WIN", "2,0,0,11"] 132 | tempString = tempArray[1]; // "2,0,0,11" 133 | versionArray = tempString.split(","); // ['2', '0', '0', '11'] 134 | } else { 135 | versionArray = versionStr.split("."); 136 | } 137 | var versionMajor = versionArray[0]; 138 | var versionMinor = versionArray[1]; 139 | var versionRevision = versionArray[2]; 140 | 141 | // is the major.revision >= requested major.revision AND the minor version >= requested minor 142 | if (versionMajor > parseFloat(reqMajorVer)) { 143 | return true; 144 | } else if (versionMajor == parseFloat(reqMajorVer)) { 145 | if (versionMinor > parseFloat(reqMinorVer)) 146 | return true; 147 | else if (versionMinor == parseFloat(reqMinorVer)) { 148 | if (versionRevision >= parseFloat(reqRevision)) 149 | return true; 150 | } 151 | } 152 | return false; 153 | } 154 | } 155 | 156 | function AC_AddExtension(src, ext) 157 | { 158 | var qIndex = src.indexOf('?'); 159 | if ( qIndex != -1) 160 | { 161 | // Add the extention (if needed) before the query params 162 | var path = src.substring(0, qIndex); 163 | if (path.length >= ext.length && path.lastIndexOf(ext) == (path.length - ext.length)) 164 | return src; 165 | else 166 | return src.replace(/\?/, ext+'?'); 167 | } 168 | else 169 | { 170 | // Add the extension (if needed) to the end of the URL 171 | if (src.length >= ext.length && src.lastIndexOf(ext) == (src.length - ext.length)) 172 | return src; // Already have extension 173 | else 174 | return src + ext; 175 | } 176 | } 177 | 178 | function AC_Generateobj(objAttrs, params, embedAttrs) 179 | { 180 | var str = ''; 181 | if (isIE && isWin && !isOpera) 182 | { 183 | str += ' '; 189 | str += ''; 190 | } else { 191 | str += ' 2 | 3 | 6 | 7 | 8 | 9 | 10 | SIP-RTMP gateway: Flash-to-SIP calls 11 | 12 | 13 | 16 | 29 | 30 | 31 | 32 |

SIP-RTMP gateway demonstration

33 | 34 |

35 | This page allows you to demonstrate Flash to SIP calls and vice-versa using the 36 | SIP-RTMP gateway software. With appropriate SIP based VoIP account, you can also 37 | accomplish Flash-to-phone or web-to-phone calls. Please visit the open source SIP-RTMP 38 | gateway project page at 39 | http://code.google.com/p/siprtmp/ 40 | for details on this project such as demo instructions, support and contributions.

41 | 42 |
43 | 115 | 136 |
137 | 138 |

139 | Please use the following HTML embed code to include this Flash application on your 140 | web site.

141 |
142 | <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
143 |     id="VideoPhone" width="214" height="137"
144 |     codebase="http://fpdownload.macromedia.com/get/flashplayer/current/swflash.cab">
145 |   <param name="movie" value="http://myprojectguide.org/p/siprtmp/VideoPhone.swf" />
146 |   <param name="quality" value="high" />
147 |   <param name="bgcolor" value="#869ca7" />
148 |   <param name="allowScriptAccess" value="sameDomain" />
149 |   <param name="allowFullScreen" value="true" />
150 |   <embed src="http://myprojectguide.org/p/siprtmp/VideoPhone.swf" quality="high" bgcolor="#869ca7"
151 |     width="214" height="137" name="VideoPhone" align="middle"
152 |     play="true" loop="false" quality="high" allowScriptAccess="sameDomain"
153 |     allowFullScreen="true" type="application/x-shockwave-flash" 
154 |     pluginspage="http://www.adobe.com/go/getflashplayer">
155 |     </embed>
156 | </object>
157 | 
158 | 159 | 160 | -------------------------------------------------------------------------------- /videoPhone/bin-release/VideoPhone.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theintencity/rtmplite/af7fd71d25765ee30c9f9b6c269309bcf657a756/videoPhone/bin-release/VideoPhone.swf -------------------------------------------------------------------------------- /videoPhone/bin-release/VideoPhone11.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theintencity/rtmplite/af7fd71d25765ee30c9f9b6c269309bcf657a756/videoPhone/bin-release/VideoPhone11.swf -------------------------------------------------------------------------------- /videoPhone/bin-release/VideoPhone45.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theintencity/rtmplite/af7fd71d25765ee30c9f9b6c269309bcf657a756/videoPhone/bin-release/VideoPhone45.swf -------------------------------------------------------------------------------- /videoPhone/bin-release/VideoPhone8.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theintencity/rtmplite/af7fd71d25765ee30c9f9b6c269309bcf657a756/videoPhone/bin-release/VideoPhone8.swf -------------------------------------------------------------------------------- /videoPhone/bin-release/history/history.css: -------------------------------------------------------------------------------- 1 | /* This CSS stylesheet defines styles used by required elements in a flex application page that supports browser history */ 2 | 3 | #ie_historyFrame { width: 0px; height: 0px; display:none } 4 | #firefox_anchorDiv { width: 0px; height: 0px; display:none } 5 | #safari_formDiv { width: 0px; height: 0px; display:none } 6 | #safari_rememberDiv { width: 0px; height: 0px; display:none } 7 | -------------------------------------------------------------------------------- /videoPhone/bin-release/history/historyFrame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 27 | Hidden frame for Browser History support. 28 | 29 | 30 | -------------------------------------------------------------------------------- /videoPhone/bin-release/playerProductInstall.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theintencity/rtmplite/af7fd71d25765ee30c9f9b6c269309bcf657a756/videoPhone/bin-release/playerProductInstall.swf -------------------------------------------------------------------------------- /videoPhone/html-template/AC_OETags.js: -------------------------------------------------------------------------------- 1 | // Flash Player Version Detection - Rev 1.6 2 | // Detect Client Browser type 3 | // Copyright(c) 2005-2006 Adobe Macromedia Software, LLC. All rights reserved. 4 | var isIE = (navigator.appVersion.indexOf("MSIE") != -1) ? true : false; 5 | var isWin = (navigator.appVersion.toLowerCase().indexOf("win") != -1) ? true : false; 6 | var isOpera = (navigator.userAgent.indexOf("Opera") != -1) ? true : false; 7 | 8 | function ControlVersion() 9 | { 10 | var version; 11 | var axo; 12 | var e; 13 | 14 | // NOTE : new ActiveXObject(strFoo) throws an exception if strFoo isn't in the registry 15 | 16 | try { 17 | // version will be set for 7.X or greater players 18 | axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7"); 19 | version = axo.GetVariable("$version"); 20 | } catch (e) { 21 | } 22 | 23 | if (!version) 24 | { 25 | try { 26 | // version will be set for 6.X players only 27 | axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6"); 28 | 29 | // installed player is some revision of 6.0 30 | // GetVariable("$version") crashes for versions 6.0.22 through 6.0.29, 31 | // so we have to be careful. 32 | 33 | // default to the first public version 34 | version = "WIN 6,0,21,0"; 35 | 36 | // throws if AllowScripAccess does not exist (introduced in 6.0r47) 37 | axo.AllowScriptAccess = "always"; 38 | 39 | // safe to call for 6.0r47 or greater 40 | version = axo.GetVariable("$version"); 41 | 42 | } catch (e) { 43 | } 44 | } 45 | 46 | if (!version) 47 | { 48 | try { 49 | // version will be set for 4.X or 5.X player 50 | axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.3"); 51 | version = axo.GetVariable("$version"); 52 | } catch (e) { 53 | } 54 | } 55 | 56 | if (!version) 57 | { 58 | try { 59 | // version will be set for 3.X player 60 | axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.3"); 61 | version = "WIN 3,0,18,0"; 62 | } catch (e) { 63 | } 64 | } 65 | 66 | if (!version) 67 | { 68 | try { 69 | // version will be set for 2.X player 70 | axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"); 71 | version = "WIN 2,0,0,11"; 72 | } catch (e) { 73 | version = -1; 74 | } 75 | } 76 | 77 | return version; 78 | } 79 | 80 | // JavaScript helper required to detect Flash Player PlugIn version information 81 | function GetSwfVer(){ 82 | // NS/Opera version >= 3 check for Flash plugin in plugin array 83 | var flashVer = -1; 84 | 85 | if (navigator.plugins != null && navigator.plugins.length > 0) { 86 | if (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]) { 87 | var swVer2 = navigator.plugins["Shockwave Flash 2.0"] ? " 2.0" : ""; 88 | var flashDescription = navigator.plugins["Shockwave Flash" + swVer2].description; 89 | var descArray = flashDescription.split(" "); 90 | var tempArrayMajor = descArray[2].split("."); 91 | var versionMajor = tempArrayMajor[0]; 92 | var versionMinor = tempArrayMajor[1]; 93 | var versionRevision = descArray[3]; 94 | if (versionRevision == "") { 95 | versionRevision = descArray[4]; 96 | } 97 | if (versionRevision[0] == "d") { 98 | versionRevision = versionRevision.substring(1); 99 | } else if (versionRevision[0] == "r") { 100 | versionRevision = versionRevision.substring(1); 101 | if (versionRevision.indexOf("d") > 0) { 102 | versionRevision = versionRevision.substring(0, versionRevision.indexOf("d")); 103 | } 104 | } else if (versionRevision[0] == "b") { 105 | versionRevision = versionRevision.substring(1); 106 | } 107 | var flashVer = versionMajor + "." + versionMinor + "." + versionRevision; 108 | } 109 | } 110 | // MSN/WebTV 2.6 supports Flash 4 111 | else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.6") != -1) flashVer = 4; 112 | // WebTV 2.5 supports Flash 3 113 | else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.5") != -1) flashVer = 3; 114 | // older WebTV supports Flash 2 115 | else if (navigator.userAgent.toLowerCase().indexOf("webtv") != -1) flashVer = 2; 116 | else if ( isIE && isWin && !isOpera ) { 117 | flashVer = ControlVersion(); 118 | } 119 | return flashVer; 120 | } 121 | 122 | // When called with reqMajorVer, reqMinorVer, reqRevision returns true if that version or greater is available 123 | function DetectFlashVer(reqMajorVer, reqMinorVer, reqRevision) 124 | { 125 | versionStr = GetSwfVer(); 126 | if (versionStr == -1 ) { 127 | return false; 128 | } else if (versionStr != 0) { 129 | if(isIE && isWin && !isOpera) { 130 | // Given "WIN 2,0,0,11" 131 | tempArray = versionStr.split(" "); // ["WIN", "2,0,0,11"] 132 | tempString = tempArray[1]; // "2,0,0,11" 133 | versionArray = tempString.split(","); // ['2', '0', '0', '11'] 134 | } else { 135 | versionArray = versionStr.split("."); 136 | } 137 | var versionMajor = versionArray[0]; 138 | var versionMinor = versionArray[1]; 139 | var versionRevision = versionArray[2]; 140 | 141 | // is the major.revision >= requested major.revision AND the minor version >= requested minor 142 | if (versionMajor > parseFloat(reqMajorVer)) { 143 | return true; 144 | } else if (versionMajor == parseFloat(reqMajorVer)) { 145 | if (versionMinor > parseFloat(reqMinorVer)) 146 | return true; 147 | else if (versionMinor == parseFloat(reqMinorVer)) { 148 | if (versionRevision >= parseFloat(reqRevision)) 149 | return true; 150 | } 151 | } 152 | return false; 153 | } 154 | } 155 | 156 | function AC_AddExtension(src, ext) 157 | { 158 | var qIndex = src.indexOf('?'); 159 | if ( qIndex != -1) 160 | { 161 | // Add the extention (if needed) before the query params 162 | var path = src.substring(0, qIndex); 163 | if (path.length >= ext.length && path.lastIndexOf(ext) == (path.length - ext.length)) 164 | return src; 165 | else 166 | return src.replace(/\?/, ext+'?'); 167 | } 168 | else 169 | { 170 | // Add the extension (if needed) to the end of the URL 171 | if (src.length >= ext.length && src.lastIndexOf(ext) == (src.length - ext.length)) 172 | return src; // Already have extension 173 | else 174 | return src + ext; 175 | } 176 | } 177 | 178 | function AC_Generateobj(objAttrs, params, embedAttrs) 179 | { 180 | var str = ''; 181 | if (isIE && isWin && !isOpera) 182 | { 183 | str += ' '; 189 | str += ''; 190 | } else { 191 | str += ' 2 | 3 | 4 | 5 | 6 | 7 | 27 | Hidden frame for Browser History support. 28 | 29 | 30 | -------------------------------------------------------------------------------- /videoPhone/html-template/index.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | SIP-RTMP gateway: Flash-to-SIP calls 11 | 12 | 13 | 16 | 29 | 30 | 31 | 32 |

SIP-RTMP gateway demonstration

33 | 34 |

35 | This page allows you to demonstrate Flash to SIP calls and vice-versa using the 36 | SIP-RTMP gateway software. With appropriate SIP based VoIP account, you can also 37 | accomplish Flash-to-phone or web-to-phone calls. Please visit the open source SIP-RTMP 38 | gateway project page at 39 | http://code.google.com/p/siprtmp/ 40 | for details on this project such as demo instructions, support and contributions.

41 | 42 |
43 | 110 | 131 |
132 | 133 |

134 | Please use the following HTML embed code to include this Flash application on your 135 | web site.

136 |
137 | <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
138 |     id="${application}" width="214" height="137"
139 |     codebase="http://fpdownload.macromedia.com/get/flashplayer/current/swflash.cab">
140 |   <param name="movie" value="http://myprojectguide.org/p/siprtmp/${swf}.swf" />
141 |   <param name="quality" value="high" />
142 |   <param name="bgcolor" value="${bgcolor}" />
143 |   <param name="allowScriptAccess" value="sameDomain" />
144 |   <param name="allowFullScreen" value="true" />
145 |   <embed src="http://myprojectguide.org/p/siprtmp/${swf}.swf" quality="high" bgcolor="${bgcolor}"
146 |     width="214" height="137" name="${application}" align="middle"
147 |     play="true" loop="false" quality="high" allowScriptAccess="sameDomain"
148 |     allowFullScreen="true" type="application/x-shockwave-flash" 
149 |     pluginspage="http://www.adobe.com/go/getflashplayer">
150 |     </embed>
151 | </object>
152 | 
153 | 154 | 155 | -------------------------------------------------------------------------------- /videoPhone/html-template/playerProductInstall.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theintencity/rtmplite/af7fd71d25765ee30c9f9b6c269309bcf657a756/videoPhone/html-template/playerProductInstall.swf -------------------------------------------------------------------------------- /videoPhone/libs/.ignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theintencity/rtmplite/af7fd71d25765ee30c9f9b6c269309bcf657a756/videoPhone/libs/.ignore -------------------------------------------------------------------------------- /videoPhone/locale/en_US/main.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theintencity/rtmplite/af7fd71d25765ee30c9f9b6c269309bcf657a756/videoPhone/locale/en_US/main.properties -------------------------------------------------------------------------------- /videoPhone/locale/hi_IN/main.properties: -------------------------------------------------------------------------------- 1 | Full_screen=पूरा स्क्रीन 2 | Settings=सेटिंग्स 3 | Initializing=सुरु हो रहा है 4 | Disconnected_from_service=संपर्क टूट गया 5 | Logged_in_as_{0}=आपका नाम {0} 6 | Call_cancelled=कॉल कैंसल हो गयी 7 | Call_terminated=कॉल ख़तम हो गयी 8 | Calling_out_{0}={0} को कॉल किया 9 | Call_from_{0}={0} कॉल केर रहे हैं 10 | Call_connected=कॉल में हैं 11 | reason=कारण 12 | Must_be_connected_to_invite=कॉल करने से पहले कनेक्ट करें 13 | Use_the_box_above_to_configure_your_gateway_URL_and_SIP_account_first._Once_logged_in_you_can_make_or_receive_SIP_calls._Use_right-click_menu_to_go_full-screen.=पहले उपर सेटिंग्स से सर्विस और SIP अकाउंट करें. फिर आप कॉल केर सकते हैं या कॉल ले सकते हैं. 14 | 15 | -------------------------------------------------------------------------------- /videoPhone/src/VideoPhone.mxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 25 | 27 | 28 | 29 | 30 | 31 | = 0) 82 | connector[s] = this.parameters[s]; 83 | } 84 | } 85 | 86 | private function activateDevice():void 87 | { 88 | var cam:Camera = Camera.getCamera(); 89 | if (cam != null && cam.muted) { 90 | Security.showSettings(SecurityPanel.PRIVACY); 91 | } 92 | } 93 | 94 | private function installJavaScriptAPI():void 95 | { 96 | try { 97 | ExternalInterface.addCallback("connect", connector.connect); 98 | ExternalInterface.addCallback("disconnect", connector.disconnect); 99 | ExternalInterface.addCallback("invite", connector.invite); 100 | ExternalInterface.addCallback("bye", connector.bye); 101 | ExternalInterface.addCallback("accept", connector.accept); 102 | ExternalInterface.addCallback("reject", connector.reject); 103 | } 104 | catch (e:Error) { 105 | trace('error ' + e.message); 106 | } 107 | } 108 | 109 | private function installContextMenu():void 110 | { 111 | var menu:ContextMenu = this.contextMenu; 112 | menu.hideBuiltInItems(); 113 | 114 | var fullscreen:ContextMenuItem = new ContextMenuItem(_("Full screen")); 115 | menu.customItems.push(fullscreen); 116 | fullscreen.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, main.fullScreen); 117 | 118 | var configure:ContextMenuItem = new ContextMenuItem(_("Configure")); 119 | menu.customItems.push(configure); 120 | configure.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, main.configure); 121 | 122 | var buffer:String = connector.bufferTime == 0.0 ? _("Add play buffering") : _("Remove play buffering"); 123 | var buffering:ContextMenuItem = new ContextMenuItem(buffer); 124 | menu.customItems.push(buffering); 125 | buffering.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, bufferingHandler); 126 | 127 | trace("installed context menu"); 128 | } 129 | 130 | private function bufferingHandler(event:ContextMenuEvent):void 131 | { 132 | var buffering:ContextMenuItem = event.currentTarget as ContextMenuItem; 133 | if (buffering != null) { 134 | connector.bufferTime = buffering.caption == _("Add play buffering") ? 0.1 : 0.0; 135 | buffering.caption = connector.bufferTime == 0.0 ? _("Add play buffering") : _("Remove play buffering"); 136 | } 137 | } 138 | 139 | ]]> 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /videoPhone/src/_.as: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2009, Mamta Singh. See README for details. */ 2 | package { 3 | import mx.resources.ResourceManager; 4 | import mx.resources.Locale; 5 | 6 | /** 7 | * The resource bundle to embed. 8 | */ 9 | [ResourceBundle("main")] 10 | 11 | /** 12 | * The function _ is used to localise the strings. See examples in rest of the code on 13 | * how this is used. This works in conjunction with the appropriate locale file. 14 | */ 15 | public function _(format:String, ...args):String 16 | { 17 | var result:String = ResourceManager.getInstance().getString("main", format.split(" ").join("_"), args); 18 | if (result == null) { 19 | result = format; 20 | for (var i:int=0; i 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /videoPhone/src/view/Dialpad.mxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | /** 13 | * Dispatched when the user clicks on a key-pad button. The data property of the event 14 | * contains the key code as String, e.g, "3" or "#". 15 | */ 16 | [Event(name="dialpadKey",type="flash.events.DataEvent")] 17 | 18 | 19 | 20 | 27 | 28 | 29 | 33 | 34 | 36 | 37 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /videoPhone/src/view/HBar.mxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 11 | -------------------------------------------------------------------------------- /videoPhone/src/view/PostIt.mxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 18 | 19 | 20 | /** 21 | * Dispatched when the user clicks on any embedded link in the text of this component. 22 | * The text property of the event contains the link information that was clicked. 23 | */ 24 | [Event(name="link", type="flash.events.TextEvent")] 25 | 26 | 27 | 28 | =0; --i) { 127 | var c:DisplayObject = parent.getChildAt(i) as PostIt; 128 | if (c != null) { 129 | var move:Move = new Move(c); 130 | move.yBy = (p != null ? -p.height : -c.height) - MARGIN; 131 | move.duration = DISAPPEAR_TIME; 132 | trace("move by " + move.yBy + " p=" + c.width + "," + c.height); 133 | move.play(); 134 | p = c; // previous child 135 | } 136 | } 137 | return post; 138 | } 139 | 140 | //-------------------------------------- 141 | // PRIVATE METHODS 142 | //-------------------------------------- 143 | 144 | /** 145 | * When the PostIt is created, and if it is not sticky, start a timer to disappear this 146 | * PostIt. 147 | */ 148 | private function creationCompleteHandler(event:Event):void 149 | { 150 | if (!sticky) { 151 | timer = new Timer(DISAPPEAR_AFTER, 1); 152 | timer.addEventListener(TimerEvent.TIMER, timerHandler, false, 0, true); 153 | timer.start(); 154 | } 155 | } 156 | 157 | /** 158 | * When the disappear timer expires, play the Fade effect on this PostIt, and also 159 | * start another timer to remove. The durations of both timer and effect are same. 160 | */ 161 | private function timerHandler(event:TimerEvent):void 162 | { 163 | timer = null; 164 | var fade:Fade = new Fade(this); 165 | fade.duration = DISAPPEAR_TIME; 166 | fade.alphaTo = 0; 167 | fade.play(); 168 | 169 | removeTimer = new Timer(DISAPPEAR_TIME, 1); 170 | removeTimer.addEventListener(TimerEvent.TIMER, removeHandler, false, 0, true); 171 | removeTimer.start(); 172 | } 173 | 174 | /** 175 | * When the remove timer expires, around the same time when the Fade effect 176 | * completes, then remove this PostIt from it's parent. 177 | */ 178 | private function removeHandler(event:TimerEvent):void 179 | { 180 | removeTimer = null; 181 | if (this.parent != null) 182 | this.parent.removeChild(this); 183 | } 184 | 185 | /** 186 | * When the user clicks on the close button, irrespective of whether the PostIt is 187 | * sticky or not, start the Fade effect and remove timer. Also hide the close button 188 | * so that user doesn't click on it again. 189 | */ 190 | private function closeThis(event:Event):void 191 | { 192 | Button(event.currentTarget).visible=false; 193 | timerHandler(null); 194 | } 195 | 196 | ]]> 197 | 198 | 199 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | -------------------------------------------------------------------------------- /videoPhone/src/view/TTextInput.mxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /videoPhone/src/view/VVideo.mxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | 14 | 15 | 193 | 194 | 195 | 198 | 199 | 200 | 201 | 202 | 203 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 227 | 228 | 229 | 230 | 231 | 234 | 235 | 236 | 237 | 238 | 239 | -------------------------------------------------------------------------------- /videoPhone/src/view/VideoPlay.mxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 15 | 16 | 17 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /videoPhone/src/view/VideoPublish.mxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 15 | 16 | 17 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /videoPhone/src/view/View.mxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 12 | 13 | 14 | 0 ? 0 : 1.0); 133 | }; 134 | 135 | var removed:Function = function(event:Event):void { 136 | var ui:UIComponent = event.currentTarget as UIComponent; 137 | timer.removeEventListener(TimerEvent.TIMER, timerHandler); 138 | ui.removeEventListener(Event.REMOVED_FROM_STAGE, removed); 139 | }; 140 | ui.addEventListener(Event.REMOVED_FROM_STAGE, removed); 141 | 142 | timer.addEventListener(TimerEvent.TIMER, timerHandler); 143 | timer.start(); 144 | } 145 | } 146 | 147 | /** 148 | * The method causes the supplied UIComponent object to automatically hide when the 149 | * user rolls the mouse outside the application, and show when the user rolls back 150 | * mouse inside the application. This method is self-contained. 151 | */ 152 | private function autoHide(object:Object):void 153 | { 154 | var ui:UIComponent = object as UIComponent; 155 | if (ui != null) { 156 | var showHandler:Function = function(event:Event):void { 157 | ui.visible = true; 158 | trace('show handler called'); 159 | }; 160 | var hideHandler:Function = function(event:Event):void { 161 | ui.visible = false; 162 | }; 163 | 164 | var parent:EventDispatcher = Application.application as EventDispatcher; 165 | 166 | parent.addEventListener(MouseEvent.ROLL_OVER, showHandler); 167 | parent.addEventListener(MouseEvent.ROLL_OUT, hideHandler); 168 | 169 | var removed:Function = function(event:Event):void { 170 | var ui:UIComponent = event.currentTarget as UIComponent; 171 | parent.removeEventListener(MouseEvent.ROLL_OVER, showHandler); 172 | parent.removeEventListener(MouseEvent.ROLL_OUT, hideHandler); 173 | ui.removeEventListener(Event.REMOVED_FROM_STAGE, removed); 174 | }; 175 | ui.addEventListener(Event.REMOVED_FROM_STAGE, removed); 176 | } 177 | } 178 | 179 | /** 180 | * When the user clicks on a dial-pad key, we update the dial's text such that 181 | * if the previous text was not a phone number, a new phone number is started, otherwise 182 | * the new digit is appended to an existing phone number. The phone numbers have a 183 | * prefix of "tel:" whereas regular SIP address use "sip:". 184 | * If the call is already active then the new digits are just sent using connector's 185 | * API method. 186 | */ 187 | private function dialpadHandler(event:DataEvent):void 188 | { 189 | trace("dialpad key=" + event.data); 190 | if (currentState == Connector.CONNECTED) { 191 | if (dial != null) { 192 | if (dial.text.substr(0,4) == 'tel:' && connector.isDigit(dial.text.substr(4))) 193 | dial.text += event.data; 194 | else 195 | dial.text = 'tel:' + event.data; 196 | } 197 | } 198 | else if (currentState == Connector.ACTIVE) { 199 | if (event.data.length > 0) 200 | connector.sendDigit(event.data); 201 | } 202 | } 203 | 204 | /** 205 | * Set the focus to the currentTarget of the event. 206 | * We need to set the focus after a timeout otherwise it throws a null object exception in Flash Player 11. 207 | */ 208 | private function addedHandler(event:Event):void 209 | { 210 | var handler:Function = function(ev:Event):void { 211 | try { 212 | Timer(ev.currentTarget).removeEventListener("timer", handler); 213 | if (event.currentTarget is TextInput) 214 | TextInput(event.currentTarget).setFocus(); 215 | else if (event.currentTarget is ComboBox) 216 | ComboBox(event.currentTarget).setFocus(); 217 | } catch (e:Error) { 218 | // ignore. 219 | } 220 | }; 221 | 222 | var t:Timer = new Timer(200, 1); 223 | t.addEventListener("timer", handler); 224 | t.start(); 225 | } 226 | ]]> 227 | 228 | 229 | 230 | 231 | 232 | 233 | 260 | 261 | 262 | 263 | 264 | 265 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 361 | 363 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 408 | 409 | 412 | 414 | 415 | 416 | 417 | 418 | --------------------------------------------------------------------------------