index.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. import mqtt from 'mqtt'
  2. import uuid from 'uuid/v4'
  3. const ID = uuid()
  4. const separator = '( ͡° ͜ʖ ͡°)'
  5. const RTC = {}
  6. const client = mqtt.connect('wss://broker.peerjs.com')
  7. let localStream
  8. client.on('message', async (topic, message) => {
  9. const msg = message.toString()
  10. let cmd
  11. let payload
  12. if (msg.includes(separator)) {
  13. cmd = msg.split(separator)[0]
  14. payload = msg.split(separator)[1]
  15. } else {
  16. cmd = msg
  17. }
  18. switch (cmd) {
  19. case 'joinroom': {
  20. if (payload !== ID) client.publish(payload, `roomuser${separator}${ID}/${topic}`)
  21. break
  22. }
  23. case 'roomuser':
  24. console.log('roomuser payload', payload)
  25. let p = payload.split('/')
  26. const id = p.shift()
  27. call(id, p.join('/'))
  28. break
  29. case 'ice': {
  30. const ice = JSON.parse(payload)
  31. const caller = ice.id
  32. const room = ice.room
  33. try {
  34. console.log('ICE', ice)
  35. const candidate = new global.RTCIceCandidate(ice.candidate)
  36. console.log('CANDIDATE', candidate)
  37. if (candidate) {
  38. RTC[room][caller].addIceCandidate(candidate)
  39. }
  40. } catch (e) {
  41. console.log(e.message)
  42. }
  43. break
  44. }
  45. case 'answer': {
  46. console.log('ANSWER')
  47. const a = JSON.parse(payload)
  48. const caller = a.id
  49. const room = a.room
  50. const answer = a.answer
  51. try {
  52. await RTC[room][caller].setRemoteDescription(answer)
  53. } catch (e) {
  54. console.log(e.message)
  55. }
  56. break
  57. }
  58. case 'offer': {
  59. console.log('OFFER')
  60. const p = JSON.parse(payload)
  61. const caller = p.id
  62. const room = p.room
  63. const offer = p.offer
  64. const peerconn = new global.RTCPeerConnection({
  65. iceServers: [
  66. { urls: 'stun:0.peerjs.com:3478' },
  67. { urls: 'turn:0.peerjs.com:3478', username: 'peerjs', credential: 'peerjsp' }
  68. ]
  69. })
  70. RTC[room][caller] = peerconn
  71. peerconn.onicecandidate = event => {
  72. console.log('local ice', event)
  73. if (event.candidate) {
  74. client.publish(caller, `ice${separator}${JSON.stringify({
  75. id: ID,
  76. room,
  77. candidate: event.candidate
  78. })}`)
  79. }
  80. }
  81. peerconn.onsignalingstatechange = event => {
  82. console.log('STATE', peerconn.signalingState)
  83. }
  84. peerconn.oniceconnectionstatechange = event => {
  85. console.log('ICE STATE', peerconn.iceConnectionState)
  86. }
  87. peerconn.ontrack = event => {
  88. RTC[room].onRemoteStream(event.streams[0])
  89. }
  90. peerconn.onremovetrack = event => {
  91. console.log('ON REMOVE TRACK', event)
  92. }
  93. peerconn.ondatachannel = dataChannel => {
  94. RTC[room].onDataStream(dataChannel.channel)
  95. }
  96. try {
  97. const desc = new global.RTCSessionDescription(offer)
  98. await peerconn.setRemoteDescription(desc)
  99. // dataChannel comes from the offer, must not create one
  100. if (room.split('/')[0] !== 'data') {
  101. await processMedia(caller, room.split('/')[0], room, peerconn)
  102. }
  103. const answer = await peerconn.createAnswer()
  104. await peerconn.setLocalDescription(answer)
  105. client.publish(caller, `answer${separator}${JSON.stringify({
  106. id: ID,
  107. room: room,
  108. answer: peerconn.localDescription
  109. })}`)
  110. } catch (e) {
  111. console.log('setRemoteDescription', e.message)
  112. }
  113. }
  114. }
  115. })
  116. client.on('connect', () => {
  117. client.subscribe(ID, err => {
  118. if (err) console.error(err)
  119. })
  120. })
  121. function Call (room, onRemoteStream, onLocalStream, onDataStream) {
  122. RTC[room] = {
  123. onRemoteStream,
  124. onLocalStream,
  125. onDataStream
  126. }
  127. client.subscribe(room, err => {
  128. if (err) console.error(err)
  129. })
  130. console.log('publish to', room, `joinroom${separator}${ID}`)
  131. client.publish(room, `joinroom${separator}${ID}`)
  132. }
  133. export default class Peer {
  134. constructor (room, { mode, secret, onRemoteStream, onLocalStream, onDataStream }) {
  135. const _room = `${mode || 'video'}/${room}${secret}`
  136. this.room = _room
  137. Call(_room, onRemoteStream, onLocalStream, onDataStream)
  138. }
  139. video (yes) {
  140. const videoTrack = localStream.getVideoTracks()
  141. videoTrack[0].enabled = yes
  142. }
  143. close () {
  144. for (let k of Object.keys(RTC[this.room])) {
  145. close(RTC[this.room][k])
  146. }
  147. delete RTC[this.room]
  148. }
  149. }
  150. window.Peer = Peer
  151. async function call (id, room) {
  152. const peerconn = new global.RTCPeerConnection({
  153. iceServers: [
  154. { urls: 'stun:0.peerjs.com:3478' },
  155. { urls: 'turn:0.peerjs.com:3478', username: 'peerjs', credential: 'peerjsp' }
  156. ]
  157. })
  158. console.log('ROOM', room)
  159. RTC[room][id] = peerconn
  160. peerconn.onnegotiationneeded = async () => {
  161. const offer = await peerconn.createOffer()
  162. try {
  163. await peerconn.setLocalDescription(offer)
  164. } catch (e) {
  165. console.log('setLocalDescription', e.message)
  166. }
  167. client.publish(id, `offer${separator}${JSON.stringify({
  168. id: ID,
  169. room,
  170. offer: peerconn.localDescription
  171. })}`)
  172. }
  173. peerconn.oniceconnectionstatechange = event => {
  174. if (peerconn.iceConnectionState === 'disconnected') {
  175. console.error('iceConnectionState disconnected')
  176. }
  177. }
  178. peerconn.ontrack = event => {
  179. console.log('new track', event.streams[0])
  180. RTC[room].onRemoteStream(event.streams[0])
  181. }
  182. peerconn.onremovetrack = event => {
  183. console.log('ON REMOVE TRACK', event)
  184. }
  185. peerconn.onicecandidate = (event) => {
  186. if (event.candidate) {
  187. client.publish(id, `ice${separator}${JSON.stringify({
  188. id: ID,
  189. room: room,
  190. candidate: event.candidate
  191. })}`)
  192. }
  193. }
  194. const mode = room.split('/')[0]
  195. processMedia(id, mode, room, peerconn)
  196. }
  197. async function processMedia (id, mode, room, peerconn) {
  198. switch (mode) {
  199. case 'audio': {
  200. if (!localStream) {
  201. localStream = await navigator.mediaDevices.getUserMedia({
  202. video: false,
  203. audio: true
  204. })
  205. RTC[room].onLocalStream(localStream)
  206. }
  207. const tracks = localStream.getTracks()
  208. for (let t of tracks) {
  209. peerconn.addTrack(t, localStream)
  210. }
  211. break
  212. }
  213. case 'video': {
  214. if (!localStream) {
  215. localStream = await navigator.mediaDevices.getUserMedia({
  216. video: true,
  217. audio: true
  218. })
  219. RTC[room].onLocalStream(localStream)
  220. }
  221. const videoTracks = localStream.getVideoTracks()
  222. RTC[room].videoSender = peerconn.addTrack(videoTracks[0], localStream)
  223. const audioTracks = localStream.getAudioTracks()
  224. RTC[room].audioSender = peerconn.addTrack(audioTracks[0], localStream)
  225. break
  226. }
  227. case 'data':
  228. let dataChannel = peerconn.createDataChannel(id)
  229. dataChannel.addEventListener('open', () => {
  230. RTC[room].onDataStream(dataChannel)
  231. })
  232. }
  233. }
  234. function close (mediaConnection) {
  235. try {
  236. mediaConnection.ontrack = null
  237. mediaConnection.onremovetrack = null
  238. mediaConnection.onremovestream = null
  239. mediaConnection.onicecandidate = null
  240. mediaConnection.oniceconnectionstatechange = null
  241. mediaConnection.onsignalingstatechange = null
  242. mediaConnection.onicegatheringstatechange = null
  243. mediaConnection.onnegotiationneeded = null
  244. mediaConnection.close()
  245. mediaConnection = null
  246. } catch (e) {
  247. console.log('Trying to close', mediaConnection)
  248. }
  249. }