Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

150 lines
4.7KB

  1. addEventListener('rpc-new', ({detail}) => {
  2. const {uid, kind, target} = detail.value
  3. if(kind === 'video') {
  4. const sender = VideoShare.streams[State.username]
  5. const receiver = VideoShare.resetStream(target)
  6. signal({kind: 'rpc-setup', value: {uid, sender, receiver}})
  7. }
  8. })
  9. const Video = {
  10. view({attrs: {username}}) {
  11. const styleOuter = {
  12. position: 'relative',
  13. display: 'block',
  14. color: 'white',
  15. overflow: 'hidden',
  16. }
  17. const styleMeta = {
  18. position: 'absolute',
  19. display: 'flex',
  20. alignItems: 'center',
  21. justifyContent: 'center',
  22. height: '100%',
  23. width: '100%',
  24. fontFamily: 'monospace',
  25. fontSize: 'x-large',
  26. }
  27. const styleVideo = {
  28. objectFit: Settings.get('blackBars') ? 'contain' : 'cover',
  29. width: '100%',
  30. height: '100%',
  31. transform: username === State.username ? 'scaleX(-1)' : 'scaleX(1)',
  32. }
  33. return m('.video-container', {style: styleOuter},
  34. m('.video-info', {style: styleMeta},
  35. m(`.label-${username}`, username),
  36. ),
  37. m('video[playsinline][autoplay]', {
  38. style: styleVideo,
  39. oncreate: ({dom}) => {
  40. dom.muted = username === State.username
  41. dom.srcObject = VideoShare.streams[username]
  42. },
  43. onupdate: ({dom}) => {
  44. if(dom.srcObject !== VideoShare.streams[username]) {
  45. dom.srcObject = VideoShare.streams[username]
  46. }
  47. },
  48. onremove: () => VideoShare.resetStream(username),
  49. }),
  50. )
  51. },
  52. }
  53. const Toggle = {
  54. view({attrs: {key, label}}) {
  55. const onclick = () => {
  56. VideoShare[key] = !VideoShare[key]
  57. VideoShare.getStream()
  58. }
  59. const checked = VideoShare[key]
  60. return m('label.styled',
  61. m('input[type=checkbox]', {onclick, checked}),
  62. label,
  63. )
  64. },
  65. }
  66. const VideoShareConfig = {
  67. get video() {
  68. return VideoShare.videoOn
  69. && State.online.length < 10
  70. && params.get('v') !== '0'
  71. && {width: {ideal: 320}, facingMode: 'user', frameRate: 26}
  72. },
  73. get audio() {
  74. return VideoShare.audioOn
  75. && params.get('a') !== '0'
  76. },
  77. view() {
  78. return [
  79. m(Toggle, {key: 'videoOn', label: 'video'}),
  80. m(Toggle, {key: 'audioOn', label: 'audio'}),
  81. ]
  82. },
  83. }
  84. const VideoShare = {
  85. videoOn: true,
  86. audioOn: true,
  87. streams: {},
  88. oncreate(e) {
  89. VideoShare.getStream()
  90. },
  91. resetStream(target) {
  92. if(VideoShare.streams[target]) {
  93. VideoShare.streams[target].getTracks().forEach(tr => tr.stop())
  94. }
  95. VideoShare.streams[target] = new MediaStream()
  96. return VideoShare.streams[target]
  97. },
  98. async getStream() {
  99. VideoShare.resetStream(State.username)
  100. await navigator.mediaDevices.getUserMedia(VideoShareConfig)
  101. .catch(error => console.error(error))
  102. .then(stream => {VideoShare.streams[State.username] = stream})
  103. m.redraw()
  104. State.others.forEach(target => {
  105. signal({kind: 'rpc-needed', value: {kind: 'video', target}})
  106. })
  107. },
  108. reLayout({dom}) {
  109. const COUNT = dom.children.length || 1
  110. let [fnx, fny] = [
  111. Math.floor((1 + Math.sqrt(4 * COUNT - 3)) / 2),
  112. Math.ceil(Math.sqrt(COUNT)),
  113. ]
  114. let max = 0
  115. for([nx, ny] of [[1, COUNT], [fnx, fny], [COUNT, 1]]) {
  116. let h = dom.clientHeight / ny
  117. let w = dom.clientWidth / nx
  118. if(w > h) {
  119. w = Math.min(w, h * 4/3)
  120. }
  121. else {
  122. h = Math.min(h, w * 3/4)
  123. }
  124. if(h * w > max) {
  125. max = h * w
  126. fnx = nx
  127. fny = ny
  128. }
  129. }
  130. dom.style['grid-template-columns'] = Array(fnx).fill('1fr').join(' ')
  131. dom.style['grid-template-rows'] = Array(fny).fill('1fr').join(' ')
  132. },
  133. view() {
  134. const style = {
  135. backgroundColor: 'black',
  136. overflow: 'hidden',
  137. display: 'grid',
  138. }
  139. const oncreate = VideoShare.reLayout
  140. const onupdate = VideoShare.reLayout
  141. return m('.videos', {style, oncreate, onupdate},
  142. State.online.map(username => m(Video, {key: username, username}))
  143. )
  144. },
  145. }
  146. addEventListener('load', () => {
  147. Headers.push([VideoShareConfig])
  148. Apps.push([VideoShare, {key: 'stream-container'}])
  149. })