Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

194 Zeilen
6.4KB

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