您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

160 行
4.7KB

  1. const Toggle = {
  2. view({attrs: {label, value}}) {
  3. const onclick = () => {
  4. StreamContainer[value] = !StreamContainer[value]
  5. updateSelfVideo()
  6. }
  7. const checked = StreamContainer[value]
  8. return m('label.styled',
  9. m('input[type=checkbox]', {checked, onclick}),
  10. label,
  11. )
  12. }
  13. }
  14. const VideoConfig = {
  15. get video() {
  16. return StreamContainer.videoOn
  17. && State.online.length < 10
  18. && params.get('v') !== '0'
  19. && {width: {ideal: 320}, facingMode: 'user', frameRate: 26}
  20. },
  21. get audio() {
  22. return StreamContainer.audioOn
  23. && params.get('a') !== '0'
  24. },
  25. view() {
  26. return [
  27. m(Toggle, {label: 'video', value: 'videoOn'}),
  28. m(Toggle, {label: 'audio', value: 'audioOn'}),
  29. ]
  30. }
  31. }
  32. const updateSelfVideo = async () => {
  33. const video = document.querySelector('video.self')
  34. video.srcObject.getTracks().forEach(track => {
  35. track.stop()
  36. video.srcObject.removeTrack(track)
  37. delete track
  38. })
  39. if(StreamContainer.videoOn || StreamContainer.audioOn) {
  40. await navigator.mediaDevices
  41. .getUserMedia(VideoConfig)
  42. .then(s => s.getTracks().forEach(t => video.srcObject.addTrack(t)))
  43. .catch(e => console.error(e))
  44. }
  45. video.srcObject = video.srcObject // safari
  46. wire({kind: 'peerInfo', value: {type: 'request'}})
  47. }
  48. const updateOtherVideo = (target, dom) => {
  49. dom.srcObject = new MediaStream()
  50. let rpc = null
  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. }
  65. }
  66. const Video = {
  67. setUp: (username) => ({dom}) => {
  68. dom.username = username
  69. dom.srcObject = new MediaStream()
  70. if(username === State.username) {
  71. dom.classList.add('self')
  72. dom.muted = true
  73. updateSelfVideo()
  74. }
  75. else {
  76. updateOtherVideo(username, dom)
  77. }
  78. },
  79. tearDown: (username) => ({dom}) => {
  80. removeEventListener('peerInfo', dom.listener)
  81. dom.srcObject.getTracks().forEach(track => {
  82. track.stop()
  83. dom.srcObject.removeTrack(track)
  84. delete track
  85. })
  86. },
  87. view({attrs: {username}}) {
  88. const styleOuter = {
  89. position: 'relative',
  90. display: 'block',
  91. color: 'white',
  92. overflow: 'hidden',
  93. }
  94. const styleMeta = {
  95. position: 'absolute',
  96. display: 'flex',
  97. alignItems: 'center',
  98. justifyContent: 'center',
  99. height: '100%',
  100. width: '100%',
  101. fontFamily: 'monospace',
  102. fontSize: 'xxx-large',
  103. }
  104. const styleVideo = {
  105. objectFit: Settings.get('blackBars') ? 'contain' : 'cover',
  106. width: '100%',
  107. height: '100%',
  108. transform: username === State.username ? 'scaleX(-1)' : 'scaleX(1)',
  109. }
  110. return m('.video-container', {style: styleOuter},
  111. m('.video-info', {style: styleMeta},
  112. m('.username', username),
  113. ),
  114. m('video[playsinline][autoplay]', {
  115. style: styleVideo,
  116. oncreate: this.setUp(username),
  117. onremove: this.tearDown(username),
  118. }),
  119. )
  120. },
  121. }
  122. const StreamContainer = {
  123. videoOn: true,
  124. audioOn: true,
  125. view() {
  126. const dims = [
  127. Math.floor((1 + Math.sqrt(4 * State.online.length - 3)) / 2),
  128. Math.ceil(Math.sqrt(State.online.length)),
  129. ].map(n => Array(n || 1).fill('1fr').join(' '))
  130. if(innerHeight > innerWidth) dims.reverse()
  131. const style = {
  132. backgroundColor: 'black',
  133. height: '100%',
  134. overflow: 'hidden',
  135. display: 'grid',
  136. gridTemplateRows: dims[0],
  137. gridTemplateColumns: dims[1],
  138. }
  139. return m('.videos', {style},
  140. State.online.map(username => m(Video, {key: username, username}))
  141. )
  142. },
  143. }
  144. const signalPeerStop = (username) => {
  145. signal({kind: 'peerInfo', value: {type: 'stop'}, source: username})
  146. }
  147. addEventListener('pagehide', () => State.online.forEach(signalPeerStop))
  148. addEventListener('logout', () => State.online.forEach(signalPeerStop))
  149. addEventListener('load', () => {
  150. Headers.push([VideoConfig])
  151. Apps.push([StreamContainer, {key: 'stream-container'}])
  152. })