Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

206 linhas
6.3KB

  1. const VideoConfig = Object.seal({
  2. videoOn: true,
  3. audioOn: true,
  4. get videoCover() {
  5. return params.get('c') === '1'
  6. },
  7. get video() {
  8. return VideoConfig.videoOn
  9. && params.get('v') !== '0'
  10. && {width: {ideal: 320}, facingMode: 'user', frameRate: 26}
  11. },
  12. get audio() {
  13. return VideoConfig.audioOn
  14. && params.get('a') !== '0'
  15. },
  16. toggle: (property) => () => {
  17. VideoConfig[property] = !VideoConfig[property]
  18. updateSelfVideo()
  19. },
  20. view() {
  21. return [
  22. m('button', {onclick: VideoConfig.toggle('videoOn')}, 'video'),
  23. m('button', {onclick: VideoConfig.toggle('audioOn')}, 'audio'),
  24. ]
  25. }
  26. })
  27. const updateSelfVideo = async () => {
  28. const video = document.querySelector('video.self')
  29. video.srcObject.getTracks().forEach(track => {
  30. track.stop()
  31. video.srcObject.removeTrack(track)
  32. delete track
  33. })
  34. if(VideoConfig.videoOn || VideoConfig.audioOn) {
  35. await navigator.mediaDevices
  36. .getUserMedia(VideoConfig)
  37. .then(s => s.getTracks().forEach(t => video.srcObject.addTrack(t)))
  38. .catch(e => console.error(e))
  39. }
  40. video.srcObject = video.srcObject // safari
  41. wire({kind: 'peerInfo', value: {type: 'request'}})
  42. }
  43. const updateOtherVideo = (target, dom) => {
  44. dom.srcObject = new MediaStream()
  45. let rpc = null
  46. const rpcConfig = {iceServers: [{
  47. urls: ['stun:stun.pico.chat:5349', 'turn:turn.pico.chat:5349'],
  48. username: 'roderic',
  49. credential: 'tomodachi',
  50. }]}
  51. const stopRpc = () => {
  52. rpc && rpc.close()
  53. dom.srcObject.getTracks().forEach(track => {
  54. track.stop()
  55. dom.srcObject.removeTrack(track)
  56. delete track
  57. })
  58. }
  59. const resetRpc = () => {
  60. stopRpc()
  61. rpc = new RTCPeerConnection(rpcConfig)
  62. document.querySelector('video.self').srcObject.getTracks()
  63. .forEach(t => rpc.addTrack(t))
  64. rpc.onicecandidate = ({candidate}) => {
  65. if(candidate && candidate.candidate) {
  66. const value = {type: 'candidate', candidate}
  67. wire({kind: 'peerInfo', value, target})
  68. }
  69. }
  70. rpc.ontrack = ({track}) => {
  71. dom.srcObject.addTrack(track)
  72. dom.srcObject = dom.srcObject
  73. }
  74. }
  75. dom.listener = async ({detail: {source, value}}) => {
  76. if(source !== target) {
  77. return
  78. }
  79. console.log(source, value.type)
  80. if(value.type === 'request') {
  81. resetRpc()
  82. const localOffer = await rpc.createOffer()
  83. await rpc.setLocalDescription(localOffer)
  84. wire({kind: 'peerInfo', value: localOffer, target})
  85. }
  86. else if(value.type === 'offer') {
  87. resetRpc()
  88. const remoteOffer = new RTCSessionDescription(value)
  89. await rpc.setRemoteDescription(remoteOffer)
  90. const localAnswer = await rpc.createAnswer()
  91. await rpc.setLocalDescription(localAnswer)
  92. wire({kind: 'peerInfo', value: localAnswer, target})
  93. }
  94. else if(value.type === 'answer') {
  95. const remoteAnswer = new RTCSessionDescription(value)
  96. await rpc.setRemoteDescription(remoteAnswer)
  97. }
  98. else if(value.type === 'candidate') {
  99. const candidate = new RTCIceCandidate(value.candidate)
  100. await rpc.addIceCandidate(candidate)
  101. }
  102. else if(value.type === 'stop') {
  103. stopRpc()
  104. }
  105. }
  106. addEventListener('peerInfo', dom.listener)
  107. }
  108. const Video = {
  109. setUp: (username) => ({dom}) => {
  110. dom.srcObject = new MediaStream()
  111. if(username === State.username) {
  112. dom.classList.add('self')
  113. dom.muted = true
  114. updateSelfVideo()
  115. }
  116. else {
  117. updateOtherVideo(username, dom)
  118. }
  119. },
  120. tearDown: (username) => ({dom}) => {
  121. removeEventListener('peerInfo', dom.listener)
  122. dom.srcObject.getTracks().forEach(track => {
  123. track.stop()
  124. dom.srcObject.removeTrack(track)
  125. delete track
  126. })
  127. },
  128. view({attrs: {username}}) {
  129. const styleOuter = {
  130. position: 'relative',
  131. display: 'block',
  132. backgroundColor: 'black',
  133. color: 'white',
  134. overflow: 'hidden',
  135. }
  136. const styleMeta = {
  137. position: 'absolute',
  138. display: 'flex',
  139. alignItems: 'center',
  140. justifyContent: 'center',
  141. height: '100%',
  142. width: '100%',
  143. fontFamily: 'monospace',
  144. fontSize: 'xxx-large',
  145. }
  146. const styleVideo = {
  147. objectFit: VideoConfig.videoCover ? 'cover' : 'contain',
  148. width: '100%',
  149. height: '100%',
  150. transform: 'scaleX(-1)',
  151. }
  152. return m('.video-container', {style: styleOuter},
  153. m('.video-info', {style: styleMeta},
  154. m('.username', username),
  155. ),
  156. m('video[playsinline][autoplay]', {
  157. style: styleVideo,
  158. oncreate: this.setUp(username),
  159. onremove: this.tearDown(username),
  160. }),
  161. )
  162. },
  163. }
  164. const StreamContainer = {
  165. getColumns() {
  166. const n = State.online.length
  167. if(n > 2 * 2) return '1fr 1fr 1fr'
  168. if(n > 1 * 1) return '1fr 1fr'
  169. return '1fr'
  170. },
  171. getRows() {
  172. const n = State.online.length
  173. if(n > 2 * 3) return '1fr 1fr 1fr'
  174. if(n > 1 * 2) return '1fr 1fr'
  175. return '1fr'
  176. },
  177. view() {
  178. const dims = [StreamContainer.getRows(), StreamContainer.getColumns()]
  179. if(innerHeight > innerWidth) dims.reverse()
  180. const style = {
  181. overflow: 'hidden',
  182. display: 'grid',
  183. padding: '1px',
  184. gridGap: '1px',
  185. gridTemplateRows: dims[0],
  186. gridTemplateColumns: dims[1],
  187. }
  188. return m('.videos', {style},
  189. State.online.map(username => m(Video, {key: username, username}))
  190. )
  191. },
  192. }
  193. const signalPeerStop = (username) => signal({kind: 'peerInfo', value: {type: 'stop'}, source: username})
  194. addEventListener('pagehide', () => State.online.forEach(signalPeerStop))
  195. addEventListener('logout', () => State.online.forEach(signalPeerStop))