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.

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