You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

118 line
3.5KB

  1. const Toggle = {
  2. view({attrs: {label, value}}) {
  3. const onclick = () => {
  4. StreamContainer[value] = !StreamContainer[value]
  5. requestStream()
  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 requestStream = async () => {
  33. await navigator.mediaDevices
  34. .getUserMedia(VideoConfig)
  35. .then(stream =>
  36. signal({kind: 'stream', value: {source: State.username, stream}})
  37. )
  38. .catch(() => {
  39. const stream = new MediaStream()
  40. signal({kind: 'stream', value: {source: State.username, stream}})
  41. })
  42. }
  43. const Video = {
  44. setUp: (username) => ({dom}) => {
  45. if(username === State.username) {
  46. dom.muted = true
  47. requestStream()
  48. }
  49. videoElements[username] = dom
  50. },
  51. tearDown: (username) => () => {
  52. const stream = videoElements[username].srcObject
  53. if(stream) stream.getTracks().forEach(tr => tr.stop())
  54. videoElements[username] = null
  55. delete videoElements[username]
  56. },
  57. view({attrs: {username}}) {
  58. const styleOuter = {
  59. position: 'relative',
  60. display: 'block',
  61. color: 'white',
  62. overflow: 'hidden',
  63. }
  64. const styleMeta = {
  65. position: 'absolute',
  66. display: 'flex',
  67. alignItems: 'center',
  68. justifyContent: 'center',
  69. height: '100%',
  70. width: '100%',
  71. fontFamily: 'monospace',
  72. fontSize: 'xxx-large',
  73. }
  74. const styleVideo = {
  75. objectFit: Settings.get('blackBars') ? 'contain' : 'cover',
  76. width: '100%',
  77. height: '100%',
  78. transform: username === State.username ? 'scaleX(-1)' : 'scaleX(1)',
  79. }
  80. return m('.video-container', {style: styleOuter},
  81. m('.video-info', {style: styleMeta},
  82. m('.username', username),
  83. ),
  84. m('video[playsinline][autoplay]', {
  85. style: styleVideo,
  86. oncreate: this.setUp(username),
  87. onremove: this.tearDown(username),
  88. }),
  89. )
  90. },
  91. }
  92. const StreamContainer = {
  93. videoOn: true,
  94. audioOn: true,
  95. view() {
  96. const dims = [
  97. Math.floor((1 + Math.sqrt(4 * State.online.length - 3)) / 2),
  98. Math.ceil(Math.sqrt(State.online.length)),
  99. ].map(n => Array(n || 1).fill('1fr').join(' '))
  100. if(innerHeight > innerWidth) dims.reverse()
  101. const style = {
  102. backgroundColor: 'black',
  103. overflow: 'hidden',
  104. display: 'grid',
  105. gridTemplateRows: dims[0],
  106. gridTemplateColumns: dims[1],
  107. }
  108. return m('.videos', {style},
  109. State.online.map(username => m(Video, {key: username, username}))
  110. )
  111. },
  112. }
  113. addEventListener('load', () => {
  114. Headers.push([VideoConfig])
  115. Apps.push([StreamContainer, {key: 'stream-container'}])
  116. })