1 | {EventEmitter} = eio
|
2 | PeerConnection = window.PeerConnection or window.webkitPeerConnection00 or window.webkitRTCPeerConnection
|
3 | IceCandidate = window.RTCIceCandidate
|
4 | SessionDescription = window.RTCSessionDescription
|
5 | URL = window.URL or window.webkitURL or window.msURL or window.oURL
|
6 | getUserMedia = navigator.getUserMedia or navigator.webkitGetUserMedia or navigator.mozGetUserMedia or navigator.msGetUserMedia
|
7 |
|
8 | class RTC extends EventEmitter
|
9 | constructor: (opts={}) ->
|
10 | opts.host ?= window.location.hostname
|
11 | opts.port ?= (if window.location.port.length > 0 then parseInt window.location.port else 80)
|
12 | opts.secure ?= (window.location.protocol is 'https:')
|
13 | opts.path ?= "/holla"
|
14 |
|
15 | @socket = new eio.Socket opts
|
16 | @socket.on "open", @emit.bind "connected"
|
17 | @socket.on "close", @emit.bind "disconnected"
|
18 | @socket.on "error", @emit.bind "error"
|
19 | @socket.on "message", (msg) =>
|
20 | msg = JSON.parse msg
|
21 | if msg.type is "presence"
|
22 | @emit "presence", msg.args
|
23 | @emit "presence.#{msg.args.name}", msg.args.online
|
24 | return
|
25 | return unless msg.type is "offer"
|
26 | c = new Call @, msg.from, false
|
27 | @emit "call", c
|
28 | return
|
29 |
|
30 | register: (name, cb) ->
|
31 | @socket.send JSON.stringify
|
32 | type: "register"
|
33 | args:
|
34 | name: name
|
35 |
|
36 | handle = (msg) =>
|
37 | msg = JSON.parse msg
|
38 | return unless msg.type is "register"
|
39 | @socket.removeListener "message", handle
|
40 | if msg.args.result is true
|
41 | @user = name
|
42 | @authorized = true
|
43 | @emit "authorized"
|
44 | cb? msg.args.result
|
45 | @socket.on "message", handle
|
46 |
|
47 | call: (user) -> new Call @, user, true
|
48 |
|
49 | ready: (fn) ->
|
50 | if @authorized
|
51 | fn()
|
52 | else
|
53 | @once 'authorized', fn
|
54 |
|
55 | class Call extends EventEmitter
|
56 | constructor: (@parent, @user, @isCaller) ->
|
57 | @startTime = new Date
|
58 | @socket = @parent.socket
|
59 |
|
60 | @pc = @createConnection()
|
61 | if @isCaller
|
62 | @socket.send JSON.stringify
|
63 | type: "offer"
|
64 | to: @user
|
65 | @emit "calling"
|
66 | @socket.on "message", @handleMessage
|
67 |
|
68 | createConnection: ->
|
69 | pc = new PeerConnection holla.config
|
70 | pc.onconnecting = =>
|
71 | @emit 'connecting'
|
72 | return
|
73 | pc.onopen = =>
|
74 | @emit 'connected'
|
75 | return
|
76 | pc.onicecandidate = (evt) =>
|
77 | if evt.candidate
|
78 | @socket.send JSON.stringify
|
79 | type: "candidate"
|
80 | to: @user
|
81 | args:
|
82 | candidate: evt.candidate
|
83 | return
|
84 |
|
85 | pc.onaddstream = (evt) =>
|
86 | @remoteStream = evt.stream
|
87 | @_ready = true
|
88 | @emit "ready", @remoteStream
|
89 | return
|
90 | pc.onremovestream = (evt) =>
|
91 | console.log evt
|
92 | return
|
93 |
|
94 | return pc
|
95 |
|
96 | handleMessage: (msg) =>
|
97 | msg = JSON.parse msg
|
98 | return unless msg.from is @user
|
99 | if msg.type is "answer"
|
100 | return @emit "rejected" unless msg.args.accepted
|
101 | @emit "answered"
|
102 | @initSDP()
|
103 | else if msg.type is "candidate"
|
104 | @pc.addIceCandidate new IceCandidate msg.args.candidate
|
105 | else if msg.type is "sdp"
|
106 | @pc.setRemoteDescription new SessionDescription msg.args
|
107 | @emit "sdp"
|
108 | else if msg.type is "hangup"
|
109 | @emit "hangup"
|
110 | else if msg.type is "chat"
|
111 | @emit "chat", msg.args.message
|
112 | return
|
113 |
|
114 | addStream: (s) ->
|
115 | @pc.addStream s
|
116 | return @
|
117 |
|
118 | ready: (fn) ->
|
119 | if @_ready
|
120 | fn @remoteStream
|
121 | else
|
122 | @once 'ready', fn
|
123 | return @
|
124 |
|
125 | duration: ->
|
126 | s = @endTime.getTime() if @endTime?
|
127 | s ?= Date.now()
|
128 | e = @startTime.getTime()
|
129 | return (s-e)/1000
|
130 |
|
131 | chat: (msg) ->
|
132 | @socket.send JSON.stringify
|
133 | type: "chat"
|
134 | to: @user
|
135 | args:
|
136 | message: msg
|
137 | return @
|
138 |
|
139 | answer: ->
|
140 | @startTime = new Date
|
141 | @socket.send JSON.stringify
|
142 | type: "answer"
|
143 | to: @user
|
144 | args:
|
145 | accepted: true
|
146 | @initSDP()
|
147 | return @
|
148 |
|
149 | decline: ->
|
150 | @socket.send JSON.stringify
|
151 | type: "answer"
|
152 | to: @user
|
153 | args:
|
154 | accepted: false
|
155 | return @
|
156 |
|
157 | end: ->
|
158 | @endTime = new Date
|
159 | @pc.close()
|
160 | @socket.send JSON.stringify
|
161 | type: "hangup"
|
162 | to: @user
|
163 | @emit "hangup"
|
164 | return @
|
165 |
|
166 | initSDP: ->
|
167 | done = (desc) =>
|
168 | @pc.setLocalDescription desc
|
169 | @socket.send JSON.stringify
|
170 | type: "sdp"
|
171 | to: @user
|
172 | args: desc
|
173 |
|
174 | err = (e) -> console.log e
|
175 |
|
176 | return @pc.createOffer done, err if @isCaller
|
177 | return @pc.createAnswer done, err if @pc.remoteDescription
|
178 | @once "sdp", =>
|
179 | @pc.createAnswer done, err
|
180 |
|
181 |
|
182 | holla =
|
183 | Call: Call
|
184 | RTC: RTC
|
185 | supported: PeerConnection? and getUserMedia?
|
186 | connect: (host) -> new RTC host
|
187 | config:
|
188 | iceServers: [url: "stun:stun.l.google.com:19302"]
|
189 |
|
190 | streamToBlob: (s) -> URL.createObjectURL s
|
191 | pipe: (stream, el) ->
|
192 | uri = holla.streamToBlob stream
|
193 | if typeof el is "string"
|
194 | document.getElementById(el).src
|
195 | else if el.jquery
|
196 | el.attr 'src', uri
|
197 | else
|
198 | el.src = uri
|
199 | return holla
|
200 |
|
201 | createStream: (opt, cb) ->
|
202 | return cb "Missing getUserMedia" unless getUserMedia?
|
203 | err = cb
|
204 | succ = (s) -> cb null, s
|
205 | getUserMedia.call navigator, opt, succ, err
|
206 | return holla
|
207 |
|
208 | createFullStream: (cb) -> holla.createStream {video:true,audio:true}, cb
|
209 | createVideoStream: (cb) -> holla.createStream {video:true,audio:false}, cb
|
210 | createAudioStream: (cb) -> holla.createStream {video:true,audio:false}, cb
|
211 |
|
212 | window.holla = holla |
\ | No newline at end of file |