Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

127 rindas
4.0KB

  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: 'xxx-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('.username', username),
  36. ),
  37. m('video[playsinline][autoplay]', {
  38. style: styleVideo,
  39. srcObject: VideoShare.streams[username],
  40. oncreate: ({dom}) => {dom.muted = username === State.username},
  41. onremove: () => VideoShare.resetStream(username),
  42. }),
  43. )
  44. },
  45. }
  46. const Toggle = {
  47. view({attrs: {key, label}}) {
  48. const onclick = () => {
  49. VideoShare[key] = !VideoShare[key]
  50. VideoShare.getStream()
  51. }
  52. const checked = VideoShare[key]
  53. return m('label.styled',
  54. m('input[type=checkbox]', {onclick, checked}),
  55. label,
  56. )
  57. },
  58. }
  59. const VideoShareConfig = {
  60. get video() {
  61. return VideoShare.videoOn
  62. && State.online.length < 10
  63. && params.get('v') !== '0'
  64. && {width: {ideal: 320}, facingMode: 'user', frameRate: 26}
  65. },
  66. get audio() {
  67. return VideoShare.audioOn
  68. && params.get('a') !== '0'
  69. },
  70. view() {
  71. return [
  72. m(Toggle, {key: 'videoOn', label: 'video'}),
  73. m(Toggle, {key: 'audioOn', label: 'audio'}),
  74. ]
  75. },
  76. }
  77. const VideoShare = {
  78. videoOn: true,
  79. audioOn: true,
  80. streams: {},
  81. oncreate(e) {
  82. VideoShare.getStream()
  83. },
  84. resetStream(target) {
  85. if(VideoShare.streams[target]) {
  86. VideoShare.streams[target].getTracks().forEach(tr => tr.stop())
  87. }
  88. VideoShare.streams[target] = new MediaStream()
  89. return VideoShare.streams[target]
  90. },
  91. async getStream() {
  92. VideoShare.resetStream(State.username)
  93. await navigator.mediaDevices.getUserMedia(VideoShareConfig)
  94. .catch(error => console.error(error))
  95. .then(stream => {VideoShare.streams[State.username] = stream})
  96. m.redraw()
  97. State.others.forEach(target =>
  98. signal({kind: 'rpc-needed', value: {kind: 'video', target}})
  99. )
  100. },
  101. view() {
  102. const dims = [
  103. Math.floor((1 + Math.sqrt(4 * State.online.length - 3)) / 2),
  104. Math.ceil(Math.sqrt(State.online.length)),
  105. ].map(n => Array(n || 1).fill('1fr').join(' '))
  106. if(innerHeight > innerWidth) dims.reverse()
  107. const style = {
  108. backgroundColor: 'black',
  109. overflow: 'hidden',
  110. display: 'grid',
  111. gridTemplateRows: dims[0],
  112. gridTemplateColumns: dims[1],
  113. }
  114. return m('.videos', {style},
  115. State.online.map(username => m(Video, {key: username, username}))
  116. )
  117. },
  118. }
  119. addEventListener('join', ({detail: {value: target}}) => {
  120. signal({kind: 'rpc-needed', value: {kind: 'video', target}})
  121. })
  122. addEventListener('load', () => {
  123. Headers.push([VideoShareConfig])
  124. Apps.push([VideoShare, {key: 'stream-container'}])
  125. })