Advertisement
pfelipm

Google Meet Grid View (por Chris Gamble)

Mar 26th, 2020
313
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.96 KB | None | 0 0
  1. // ==UserScript==
  2. // @name Google Meet Grid View
  3. // @namespace https://fugi.tech/
  4. // @version 1.3
  5. // @description Adds a toggle to use a grid layout in Google Meets
  6. // @author Chris Gamble
  7. // @include https://meet.google.com/*
  8. // @grant none
  9. // @run-at document-idle
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. const gridOn =
  14. '<svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M10,4V8H14V4H10M16,4V8H20V4H16M16,10V14H20V10H16M16,16V20H20V16H16M14,20V16H10V20H14M8,20V16H4V20H8M8,14V10H4V14H8M8,8V4H4V8H8M10,14H14V10H10V14M4,2H20A2,2 0 0,1 22,4V20A2,2 0 0,1 20,22H4C2.92,22 2,21.1 2,20V4A2,2 0 0,1 4,2Z" /></svg>'
  15. const gridOff =
  16. '<svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M0,2.77L1.28,1.5L22.5,22.72L21.23,24L19.23,22H4C2.92,22 2,21.1 2,20V4.77L0,2.77M10,4V7.68L8,5.68V4H6.32L4.32,2H20A2,2 0 0,1 22,4V19.7L20,17.7V16H18.32L16.32,14H20V10H16V13.68L14,11.68V10H12.32L10.32,8H14V4H10M16,4V8H20V4H16M16,20H17.23L16,18.77V20M4,8H5.23L4,6.77V8M10,14H11.23L10,12.77V14M14,20V16.77L13.23,16H10V20H14M8,20V16H4V20H8M8,14V10.77L7.23,10H4V14H8Z" /></svg>'
  17. let RefreshVideoProxyHandler
  18.  
  19. // Create the styles we need
  20. const s = document.createElement('style')
  21. s.innerText =
  22. '.__gmeet-vid-container{display:grid;grid-auto-rows:1fr;top:48px!important;right:0!important;left:0!important;bottom:0!important}.__gmeet-vid-container>div{position:relative!important;margin-top:0!important;top:0!important;left:0!important;height:100%!important;width:100%!important;background:0 0!important}'
  23. document.body.append(s)
  24.  
  25. // Find the video container
  26. let runInterval = null
  27. let container = null
  28. let toggleButton = null
  29.  
  30. // Define toggle functions
  31. const enableGridMode = () => {
  32. // This continually probes the number of participants & screen size to ensure videos are max possible size regardless of window layout
  33. runInterval = setInterval(() => {
  34. const w = innerWidth / 16
  35. const h = (innerHeight - 48) / 9
  36. const n = container.children.length
  37. let size = 0
  38. let col
  39. for (col = 1; col < 9; col++) {
  40. let s = Math.min(w / col, h / Math.ceil(n / col))
  41. if (s < size) {
  42. col--
  43. break
  44. }
  45. size = s
  46. }
  47. container.style.gridTemplateColumns = `repeat(${col}, 1fr)`
  48. }, 250)
  49. container.classList.add('__gmeet-vid-container')
  50. toggleButton.innerHTML = gridOn
  51. }
  52. const disableGridMode = () => {
  53. clearInterval(runInterval)
  54. container.classList.remove('__gmeet-vid-container')
  55. toggleButton.innerHTML = gridOff
  56. runInterval = null
  57. }
  58. const toggleGridMode = () => {
  59. runInterval ? disableGridMode() : enableGridMode()
  60. }
  61.  
  62. // Make the button to perform the toggle
  63. // This runs on a loop since you can join/leave the meeting repeatedly without changing the page
  64. setInterval(() => {
  65. const participantButton = document.querySelector('[aria-label="Mostrar las opciones de participantes"]')
  66. const participantVideo = document.querySelector('[data-participant-id]')
  67. if (!participantButton || participantButton.__grid_ran || !participantVideo) return
  68. container = participantVideo.parentElement
  69. participantButton.__grid_ran = true
  70.  
  71. const buttons = participantButton.parentElement
  72. buttons.prepend(buttons.children[1].cloneNode())
  73.  
  74. toggleButton = document.createElement('div')
  75. toggleButton.classList = buttons.children[1].classList
  76. toggleButton.style.display = 'flex'
  77. toggleButton.innerHTML = gridOff
  78. toggleButton.onclick = toggleGridMode
  79. buttons.prepend(toggleButton)
  80.  
  81. if (window.default_MeetingsUi) {
  82. for (let v of Object.values(window.default_MeetingsUi)) {
  83. if (v && v.prototype) {
  84. const p = Object.getOwnPropertyDescriptor(v.prototype, 'Aa')
  85. if (p && p.value && p.value.toString().includes('this.La.get')) {
  86. if (!v.prototype.Aa.__grid_ran) {
  87. const p = new Proxy(v.prototype.Aa, RefreshVideoProxyHandler)
  88. p.__grid_ran = true
  89. v.prototype.Aa = p
  90. }
  91. }
  92. }
  93. }
  94. }
  95. }, 250)
  96.  
  97. const LayoutVideoProxyHandler = Lv => ({
  98. get: function(target, name) {
  99. let ret = Reflect.get(target, name)
  100. if (typeof ret === 'function') {
  101. ret = ret.bind(target)
  102. }
  103.  
  104. if (runInterval && name == 'get') {
  105. return idx => ({
  106. Aa: Ba => {
  107. try {
  108. return GridLayout.call(Lv, Ba)
  109. } catch (e) {
  110. console.error(e)
  111. return ret(idx).Aa(Ba)
  112. }
  113. },
  114. })
  115. }
  116.  
  117. return ret
  118. },
  119. })
  120.  
  121. RefreshVideoProxyHandler = {
  122. apply: function(target, thisArg, argumentsList) {
  123. if (!thisArg.La.__grid_ran) {
  124. const p = new Proxy(thisArg.La, LayoutVideoProxyHandler(thisArg))
  125. p.__grid_ran = true
  126. thisArg.La = p
  127. }
  128. return target.apply(thisArg, argumentsList)
  129. },
  130. }
  131.  
  132. // Used to forcibly load every video frame
  133. function GridLayout(orderingInput) {
  134. const VideoList = orderingInput.constructor
  135. const VideoElem = Object.values(window.default_MeetingsUi)
  136. .filter(i => typeof i === 'function')
  137. .filter(i => i.toString().includes('.attribution'))[0]
  138.  
  139. const magicKey = Object.entries(new VideoElem(999)).find(e => e[1] === 999)[0]
  140.  
  141. const addUniqueVideoElem = (a, b, c) => {
  142. if (b && !a.some(e => e[magicKey] === b)) {
  143. const d = new VideoElem(b, { attribution: true })
  144. if (c) c(d)
  145. a.push(d)
  146. }
  147. }
  148. const isSpacesStr = i => typeof i === 'string' && i.startsWith('spaces/')
  149.  
  150. // magicSet(true) enables the "You're presenting to everyone" screen
  151. // magicSet(1 || 2) ensures multiple screens can be shown. Unsure the difference between 1 and 2
  152. const magicSet = val => {
  153. return elem => {
  154. for (const [k, v] of Object.entries(elem)) {
  155. if (typeof v === typeof val && k !== 'attribution') {
  156. elem[k] = val
  157. }
  158. }
  159. }
  160. }
  161.  
  162. let newBa, importantObject
  163. for (let v of Object.values(this)) {
  164. if (v && typeof v === 'object') {
  165. for (let vv of Object.values(v)) {
  166. if (Array.isArray(vv) && vv.length && vv.every(isSpacesStr)) {
  167. if (newBa && vv != newBa) {
  168. console.log('Invalid newBa search!', newBa, vv)
  169. throw new Error('Failed')
  170. } else {
  171. newBa = vv
  172. importantObject = v
  173. }
  174. }
  175. }
  176. }
  177. }
  178.  
  179. let videoMap
  180. for (let v of Object.values(importantObject)) {
  181. if (v instanceof Map && v.size && Array.from(v.keys()).every(isSpacesStr)) {
  182. videoMap = v
  183. }
  184. }
  185.  
  186. let ownVideo = null
  187. for (let v of Object.values(importantObject)) {
  188. if (v && typeof v === 'object' && v['$goog_Thenable']) {
  189. for (let vv of Object.values(v)) {
  190. if (isSpacesStr(vv)) {
  191. ownVideo = videoMap.get(vv) || null
  192. }
  193. }
  194. }
  195. }
  196.  
  197. let ret = []
  198. // TODO: Google meets also injects two other video elements here
  199. // I suspect one of them is the presenter?
  200. for (const v of newBa) {
  201. addUniqueVideoElem(ret, videoMap.get(v), magicSet(2))
  202. }
  203. if (!ret.length) {
  204. addUniqueVideoElem(ret, ownVideo, magicSet(true))
  205. }
  206.  
  207. ret.sort((a,b) => a[magicKey].name.localeCompare(b[magicKey].name))
  208. magicSet(0)(ret[0])
  209.  
  210. // Build a video list from the ordered output
  211. return new VideoList(ret)
  212. }
  213. })()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement