YouTube Ultrawide

Crops the top and bottom to fit ultrawide

  1. // ==UserScript==
  2. // @name YouTube Ultrawide
  3. // @version 0.1
  4. // @description Crops the top and bottom to fit ultrawide
  5. // @author uxamend (modified by C4illin)
  6. // @match https://www.youtube.com/*
  7. // @match https://www.youtube-nocookie.com/embed/*
  8. // @exclude-match https://www.youtube.com/ad_frame*
  9. // @exclude-match https://www.youtube.com/ad_companion*
  10. // @exclude-match https://www.youtube.com/embed/
  11. // @exclude-match https://www.youtube.com/video_masthead*
  12. // @icon https://raw.githubusercontent.com/C4illin/Userscripts/master/Youtube-Ultrawide/icon.png
  13. // @grant none
  14. // @run-at document-idle
  15. // @license CC0-1.0
  16. // @compatible firefox version >=64 (older versions untested)
  17. // @compatible chrome version >=71 (older versions untested)
  18. // @namespace https://gf.qytechs.cn/users/217869
  19. // ==/UserScript==
  20.  
  21. // 100% credit to uxamend: (I just changed a couple values to reverse everything)
  22. // https://gf.qytechs.cn/sv/scripts/375648-zoom-and-crop-youtube-videos-to-fill-screen-height
  23.  
  24. "use strict"
  25.  
  26. // ==================================
  27. // User-defined global parameters
  28. // ==================================
  29.  
  30. // For wide screens: Sets the widest aspect ratio that videos will ever be
  31. // cropped to.
  32. var max_cropped_aspect = 21/9 // Express as a fraction, e.g. 21/9, not 21:9.
  33.  
  34. // Sets the aspect ratio of the player for using zoom outside of full-screen.
  35. // This has no effect in full-screen mode.
  36. // To use the screen aspect ratio as the player aspect ratio, set this to zero.
  37. var player_aspect = 0 // Set to zero or a fraction, e.g. 16/9, not 16:9.
  38.  
  39. // Default zoom state outside full-screen (true = enabled, false = disabled)
  40. var def_zoom_n = false
  41.  
  42. // Default zoom state in full-screen (true = enabled, false = disabled)
  43. var def_zoom_f = false
  44.  
  45. // Sets which key will be used to enable and disable zoom
  46. var zoom_shortcut_key = "z"
  47.  
  48. // ==================================
  49.  
  50.  
  51. var debug_logging_on = false
  52. var debug_script_name = "Userscript: Zoom YouTube videos to fill screen width"
  53.  
  54. function debug_log(message) {
  55. if(debug_logging_on){
  56. console.log("[" + debug_script_name + "] " + message)
  57. }
  58. }
  59.  
  60. /**
  61. * set_zoom
  62. * Zooms a specified video to fill specified containing area dimensions.
  63. * Parameters:
  64. * video video to zoom
  65. * cw containing area width
  66. * ch containing area height
  67. * Global parameters read:
  68. * max_cropped_aspect minimum aspect ratio to crop videos down to
  69. */
  70. function set_zoom(video, cw, ch) {
  71. var vs = video.style
  72. var video_aspect = video.videoWidth/video.videoHeight
  73. var containing_aspect = cw/ch
  74.  
  75. // Don't zoom if the endscreen is showing
  76. if(!video.ended) {
  77. // Only zoom and crop videos that are wide enough to crop
  78. if(video_aspect < containing_aspect && video_aspect < max_cropped_aspect) {
  79. debug_log("Video is wider than containing area and max_cropped_aspect. Setting zoom.")
  80.  
  81. var vw = cw // vh = video height
  82.  
  83. // Apply max_cropped_aspect constraint to video width
  84. if (max_cropped_aspect < containing_aspect) {
  85. vh = cw/max_cropped_aspect
  86. debug_log("max_cropped_aspect reached: " + max_cropped_aspect)
  87. }
  88.  
  89. var vh = video_aspect * vw // vw = video width (including cropped portion)
  90.  
  91. var vt = (ch-vh)/2 // vt = top edge position of video
  92. var vl = (cw-vw)/2 // vl = left edge position of video
  93.  
  94. debug_log("Containing area dimensions: " + cw + "x" + ch + "." + "Aspect: " + containing_aspect)
  95. // debug_log("Calculated new video element dimensions: " + vw + "x" + vh + ", origin at " + vl + ", " + vt + ".")
  96. // debug_log("(Underlying video stream resolution: " + video.videoWidth + "x" + video.videoHeight + ".)")
  97. debug_log("max_cropped_aspect: " + max_cropped_aspect + "." )
  98. debug_log("video_aspect: " + video_aspect + ".")
  99.  
  100. // This might appear to risk creating an endless loop via the mutation
  101. // observer. However, it doesn't. I'm guessing that changing the dimensions
  102. // doesn't constitute a mutation, but if it does it can result in at most one
  103. // superfluous execution of set_zoom(). If the first execution causes a
  104. // mutation by changing the video element's dimensions, then the second
  105. // execution, if it is surplus to requirements, should set them to the same
  106. // values, resulting in no mutation and no third execution (until genuinely
  107. // needed).
  108. vs.height = vh + "px"
  109. vs.width = vw + "px"
  110. vs.top = vt + "px"
  111. vs.left = vl + "px"
  112.  
  113. } else {
  114. debug_log("Video is not wide enough to require zoom ("
  115. + video.videoWidth
  116. + "x"
  117. + video.videoHeight
  118. + "). Not setting zoom.")
  119. unzoom(video, cw, ch)
  120. }
  121.  
  122. } else {
  123. debug_log("Video has ended. Not setting zoom. (Otherwise we mess with the endscreen.)")
  124. }
  125. }
  126.  
  127. /**
  128. * unzoom
  129. * Undoes the visual effects of set_zoom.
  130. * Note that unzoom does not gurantee to return the video dimensions exactly to
  131. * their original values, but the visual appearance should be the same (or near
  132. * enough as makes no odds).
  133. * Parameters:
  134. * video video to unzoom
  135. * cw containing area width
  136. * ch containing area height
  137. */
  138. function unzoom(video, cw, ch) {
  139. // It would be better to somehow trigger YouTube's standard video sizing, but
  140. // in the absence of a way to trigger that, we'll just do this.
  141. var vs = video.style
  142. var video_aspect = video.videoWidth/video.videoHeight
  143. var containing_aspect = cw/ch
  144. // Don't unzoom if the endscreen is showing
  145. if(!video.ended) {
  146. debug_log("Unzooming video.")
  147. // Usually the player is sized to fit the video exactly in default view,
  148. // but not for narrow videos, which are pillarboxed with white bars. Rarely,
  149. // the player defaults to 16:9 for all videos, so that wide videos are
  150. // letterboxed with white bars.
  151. //
  152. // In theater mode and full-screen mode, the player has a fixed aspect and
  153. // the video is letter- or pillarboxed with black bars if it doesn't fit
  154. // exactly.
  155. //
  156. // To avoid black bars in default view, we must size the video to fill the
  157. // container in the video's longest dimension only. (Otherwise we could
  158. // just size it to fill the container in both dimensions.)
  159. var w, h, t, l
  160. if(video_aspect == containing_aspect) {
  161. // video that fits the container exactly
  162. w = cw; h = ch; t = 0; l = 0
  163. } else if(video_aspect > containing_aspect) {
  164. // letterboxed video
  165. w = cw; l = 0
  166. h = cw / video_aspect
  167. t = (ch - h) / 2
  168. } else {
  169. // pillarboxed video
  170. h = ch; t = 0
  171. w = ch * video_aspect
  172. l = (cw - w) / 2
  173. }
  174. vs.width = w + "px"
  175. vs.height = h + "px"
  176. vs.top = t + "px"
  177. vs.left = l + "px"
  178. } else {
  179. debug_log("Video has ended. Not unzooming. (Otherwise we mess with the endscreen.)")
  180. }
  181. }
  182.  
  183. /**
  184. * in_theater_mode
  185. * Returns true if we're in Theater mode.
  186. */
  187. function in_theater_mode() {
  188. return (document.getElementById("player-theater-container") &&
  189. document.getElementById("player-theater-container").childElementCount > 0 &&
  190. !document.fullscreenElement)
  191. }
  192.  
  193. /**
  194. * set_player_aspect
  195. * Changes the aspect ratio of the video player element to the specified aspect
  196. * ratio, as interpreted by YouTube's default CSS.
  197. * Parameters:
  198. * aspect aspect ratio to use
  199. * theater_default if true, set theater mode to the default aspect ratio
  200. * instead of the specified aspect ratio
  201. */
  202. function set_player_aspect(aspect, theater_default=false) {
  203. debug_log("Setting player aspect to " + aspect + ".")
  204. // We need to set overflow to hidden on the movie-player otherwise the video
  205. // overhangs in miniplayer mode. Get it by class name rather than id, for
  206. // compatibility with embedded videos
  207. document.getElementsByClassName("html5-video-player")[0].style.setProperty("overflow", "hidden")
  208. // For embedded videos, we don't need to do anything.
  209. // For default view
  210. if(document.getElementsByTagName("ytd-watch-flexy")[0]) {
  211. var ytdwfs = document.getElementsByTagName("ytd-watch-flexy")[0].style
  212. ytdwfs.setProperty("--ytd-watch-flexy-width-ratio", aspect)
  213. ytdwfs.setProperty("--ytd-watch-flexy-height-ratio", 1)
  214. }
  215. // For theater mode
  216. var ptc = document.getElementById("player-theater-container")
  217. if(in_theater_mode() && !theater_default) {
  218. debug_log("Setting theater mode height.")
  219. // 56px for masthead; --ytd-masthead-height is not always set, so can't use
  220. // that unfortunately
  221. ptc.style.setProperty("max-height", "calc(100vh - 56px)")
  222. ptc.style.setProperty("height", "calc((" + (1/aspect) + ") * 100vw)")
  223. } else {
  224. debug_log("Unsetting theater mode height.")
  225. if(ptc) {
  226. ptc.style.removeProperty("max-height")
  227. ptc.style.removeProperty("height")
  228. }
  229. }
  230. }
  231.  
  232. /**
  233. * apply_player_aspect
  234. * To facilitate zoom and crop when the movie_player is not full-screen, this sets
  235. * the aspect ratio of the movie_player to follow the player_aspect setting.
  236. * Calling with the zoom parameter set to false returns the player to the YouTube
  237. * default of matching the video aspect ratio.
  238. * Parameters:
  239. * zoom if true, use player_aspect
  240. * if false, use the actual video aspect (YouTube default)
  241. * Global parameters read:
  242. * player_aspect the aspect ratio to use, or zero (indicating to use the
  243. * screen aspect)
  244. */
  245. function apply_player_aspect(zoom=true) {
  246. var video = document.getElementsByClassName("html5-main-video")[0]
  247. var video_aspect = video.videoWidth/video.videoHeight
  248. if(zoom) {
  249. if(player_aspect == 0) {
  250. debug_log("Adjusting player aspect ratio to match screen.")
  251. var screen_aspect = window.screen.width/window.screen.height
  252. if(video_aspect > screen_aspect) {
  253. set_player_aspect(screen_aspect)
  254. } else {
  255. debug_log("No need to change player aspect; video is not wide enough.")
  256. }
  257. } else {
  258. debug_log("Adjusting player aspect ratio to configured value.")
  259. if(video_aspect > player_aspect) {
  260. set_player_aspect(player_aspect)
  261. } else {
  262. debug_log("No need to change player aspect; video is not wide enough.")
  263. }
  264. }
  265. } else {
  266. debug_log("Restoring player aspect ratio to match video.")
  267. set_player_aspect(video_aspect, true)
  268. // N.B. If video_aspect is narrow, the expected behaviour of set_player_aspect
  269. // is that YouTube's CSS may result in the video being pillarboxed, due to the
  270. // maximum height constraint.
  271. }
  272. }
  273.  
  274. /**
  275. * set_zoom_to_window
  276. * Zooms a video to fill the window dimensions.
  277. * Parameters:
  278. * video the video to set zoom for
  279. * zoom if false, will unzoom instead of setting zoom
  280. * Global parameters read:
  281. * max_cropped_aspect minimum aspect ratio to crop videos down to
  282. */
  283. function set_zoom_to_window(video, zoom=true) {
  284. if(zoom) {
  285. set_zoom(video,
  286. window.innerWidth,
  287. window.innerHeight)
  288. } else {
  289. unzoom(video,
  290. window.innerWidth,
  291. window.innerHeight)
  292. }
  293. }
  294.  
  295. /**
  296. * set_zoom_to_movie_player
  297. * Zooms a video to fill its containing movie_player element in the DOM. When not in
  298. * full-screen mode, also changes the size of the movie_player to follow the
  299. * player_aspect setting (else there'll be no zoom and crop).
  300. * Parameters:
  301. * video the video to set zoom for
  302. * zoom if false, will unzoom instead of setting zoom
  303. * Global parameters read:
  304. * max_cropped_aspect minimum aspect ratio to crop videos down to
  305. * player_aspect aspect ratio setting for non-full-screen movie_player
  306. */
  307. function set_zoom_to_movie_player(video, zoom=true) {
  308. if(!document.fullscreenElement) {
  309. // The movie_player is the grandparent node of the video element.
  310. // Open question: Is it more likely for the ID of the relevant element to
  311. // change (so that selecting it as the grandparent is the best strategy), or
  312. // for its position in the DOM tree to change (so that selecting it by ID is
  313. // the best strategy)?
  314. if(zoom) {
  315. apply_player_aspect(true)
  316. set_zoom(video,
  317. video.parentNode.parentNode.clientWidth,
  318. video.parentNode.parentNode.clientHeight)
  319. } else {
  320. unzoom(video,
  321. video.parentNode.parentNode.clientWidth,
  322. video.parentNode.parentNode.clientHeight)
  323. }
  324. } else {
  325. apply_player_aspect(false)
  326. // In full-screen mode, the movie-player is not necessarily the same size as
  327. // the screen, which can cause a slight offset. Use set_zoom_to_window instead
  328. // for this case.
  329. set_zoom_to_window(video, zoom)
  330. }
  331. zoom_button.update()
  332. }
  333.  
  334. /**
  335. * mo_callback
  336. * Callback function for mutation observer, to re-apply zoom if the video element
  337. * mutates. E.g. when an ad starts or stops playing, or in other circumstances
  338. * when the video might change dimensions or become reset to its default,
  339. * letterboxed state.
  340. */
  341. function mo_callback(mutation_list, observer) {
  342. mutation_list.forEach((mutation) => {
  343. if(mutation.type == "attributes"){
  344. // We have to check whether zoom "should" be on, because the
  345. // fullscreenchange event may not be fast enough, in which case we will
  346. // catch the mutations caused by exiting full-screen.
  347. if(zoom_should_be_on()) {
  348. debug_log("Video element mutated.")
  349. set_zoom_to_movie_player(mutation.target)
  350. } else {
  351. debug_log("Video element mutated but zoom should be off.")
  352. zoom_off()
  353. }
  354. }
  355. })
  356. }
  357.  
  358. var mo = new MutationObserver(mo_callback)
  359.  
  360. function observe_video_mutations(video) {
  361. mo.observe(video, {"attributes" : true})
  362. }
  363.  
  364. /**
  365. * zoom_on
  366. * Unconditionally apply zoom and keep it applied until zoom_off is called.
  367. */
  368. function zoom_on() {
  369. debug_log("Turning zoom on.")
  370. var video = document.getElementsByClassName("html5-main-video")[0]
  371. set_zoom_to_movie_player(video)
  372. observe_video_mutations(video)
  373. }
  374.  
  375. /**
  376. * zoom_off
  377. * Unconditionally stop applying zoom, until zoom_on is called.
  378. */
  379. function zoom_off() {
  380. debug_log("Turning zoom off.")
  381. mo.disconnect()
  382. var video = document.getElementsByClassName("html5-main-video")[0]
  383. apply_player_aspect(false)
  384. set_zoom_to_movie_player(video, false)
  385. }
  386.  
  387. // Manual zoom state outside full-screen
  388. var man_enab_n = def_zoom_n
  389.  
  390. // Manual zoom state in full-screen
  391. var man_enab_f = def_zoom_f
  392.  
  393. /**
  394. * zoom_should_be_on
  395. * Returns true if we're in a state where zoom is supposed to currently be
  396. * enabled, else false.
  397. */
  398. function zoom_should_be_on() {
  399. return ((man_enab_n && !document.fullscreenElement)
  400. || (man_enab_f && document.fullscreenElement))
  401. }
  402.  
  403. /**
  404. * zoom_on_or_off
  405. * Puts zoom into the correct on/off state, as per zoom_should_be_on.
  406. */
  407. function zoom_on_or_off() {
  408. if(zoom_should_be_on()) {
  409. setTimeout(zoom_on, 200)
  410. } else {
  411. zoom_off()
  412. }
  413. }
  414.  
  415. /**
  416. * toggle_manual_enab
  417. * Changes the manual override zoom state for the current display mode; either
  418. * full-screen or non-full-screen.
  419. */
  420. function toggle_manual_enab() {
  421. debug_log("Toggling manual enable state.")
  422. if(document.fullscreenElement){
  423. man_enab_f = !man_enab_f
  424. if(man_enab_f) debug_log("Set full-screen zoom enabled.")
  425. else debug_log("Set full-screen zoom disabled.")
  426. } else {
  427. man_enab_n = !man_enab_n
  428. if(man_enab_n) debug_log("Set non-full-screen zoom enabled.")
  429. else debug_log("Set non-full-screen zoom disabled.")
  430. }
  431. zoom_on_or_off()
  432. }
  433.  
  434. /**
  435. * handle_keydown
  436. * Event handler for any keydown events, to trigger appropriate actions.
  437. */
  438. function handle_keydown(e) {
  439. debug_log('"' + e.key + '" key was pressed.')
  440. if(e.key == zoom_shortcut_key.toLowerCase()) toggle_manual_enab()
  441. if(e.key == zoom_shortcut_key.toUpperCase()) toggle_manual_enab()
  442. if(e.key == "i") {
  443. // Workaround for bug: exiting miniplayer directly into normal view does not
  444. // trigger reapplication of zoom. Pressing 'i' seems to be the only way to
  445. // trigger this bug, so detecting the pressing of 'i' seems like a good way
  446. // to fix it.
  447. zoom_on_or_off()
  448. }
  449. }
  450.  
  451. /**
  452. * watch_for_fullscreen
  453. * Start watching for changes to the full-screen state, and make sure the correct
  454. * zoom state is applied at each transition of full-screen state.
  455. * N.B. There may be a slight delay in reaction to changes in full-screen state.
  456. */
  457. function watch_for_fullscreen() {
  458. debug_log("Adding fullscreenchange event listener.")
  459. document.addEventListener(
  460. 'fullscreenchange',
  461. function() {
  462. debug_log("Full-screen state changed.")
  463. zoom_on_or_off()
  464. }
  465. )
  466. }
  467.  
  468. /**
  469. * watch_for_keypresses
  470. * Start watching for keydown events and handle them when they occur.
  471. */
  472. function watch_for_keypresses() {
  473. debug_log("Adding keydown event listener.")
  474. document.addEventListener(
  475. 'keydown',
  476. handle_keydown
  477. )
  478. }
  479.  
  480. /**
  481. * create_zoom_button
  482. * Adds a zoom button to the YouTube player controls, which toggles manual override of
  483. * zoom state.
  484. * Returns:
  485. * an object representing the button
  486. */
  487. function create_zoom_button() {
  488. var right_controls
  489. var size_button
  490. var tooltip_showing = false
  491. var button
  492. var icon_path
  493.  
  494. /**
  495. * set_zoom_button_mode
  496. * Sets the zoom button to an appropriate mode for the current zoom state.
  497. */
  498. function set_zoom_button_mode() {
  499. var l
  500.  
  501. if(zoom_should_be_on()) {
  502. icon_path.setAttribute("d",
  503. "m 8,11 0,14 20,0 0,-14 -20,0 z m 2,4 16,0 0,6 -16,0 0,-6 z"
  504. )
  505. l = "Normal (" + zoom_shortcut_key + ")"
  506. } else {
  507. icon_path.setAttribute("d",
  508. "m 4,11 0,14 3,0 0,-2 -1,0 0,-10 1,0 0,-2 -3,0 z\
  509. m 4,0 0,14 20,0 0,-14 -20,0 z\
  510. m 21,0 0,2 1,0 0,10 -1,0 0,2 3,0 0,-14 -3,0 z\
  511. m -19,2 16,0 0,10 -16,0 0,-10 z"
  512. )
  513. l = "Widescreen (" + zoom_shortcut_key + ")"
  514. }
  515.  
  516. button.setAttribute("aria-label", l)
  517. button.setAttribute("title", l)
  518. }
  519. /**
  520. * create_zoom_button_icon
  521. * Adds the icon to the zoom button during initial creation of the button.
  522. */
  523. function create_zoom_button_icon() {
  524. // Create icon SVG element
  525. var s = document.createElementNS("http://www.w3.org/2000/svg", "svg")
  526. s.setAttribute("height", "100%")
  527. s.setAttribute("version", "1.1")
  528. s.setAttribute("viewBox", "0 0 36 36")
  529. s.setAttribute("width", "100%")
  530.  
  531. var p_id = "zac-path-1"
  532.  
  533. // Apply shadow
  534. var sh = document.createElementNS("http://www.w3.org/2000/svg", "use")
  535. sh.setAttribute("class", "ytp-svg-shadow")
  536. sh.setAttribute("href", "#" + p_id)
  537.  
  538. // Create icon path
  539. var p = document.createElementNS("http://www.w3.org/2000/svg", "path")
  540. p.setAttribute("id", p_id)
  541. p.setAttribute("class", "ytp-svg-fill")
  542.  
  543. // Append path and shadow to SVG
  544. s.appendChild(sh)
  545. s.appendChild(p)
  546.  
  547. // Append icon to button
  548. button.appendChild(s)
  549.  
  550. icon_path = p
  551. }
  552. /**
  553. * show_zoom_button_tooltip
  554. * Shows the tooltip associated with the zoom button in a style mimicking that
  555. * of the other YouTube player buttons.
  556. * Parameters:
  557. * show if false, the tooltip will be hidden instead of shown
  558. */
  559. function show_zoom_button_tooltip(show=true) {
  560. // Position calculations
  561. var bbcr = button.getBoundingClientRect()
  562. var tt_horiz_cen = bbcr.left + bbcr.width/2 // tooltip horizontal centre
  563.  
  564. var tt_top_offset = 57 // How far above the button should the tooltip be?
  565.  
  566. // For some reason, the existing tooltips are at a different offset in full-screen.
  567. if(document.fullscreenElement) {
  568. tt_top_offset = 75
  569. }
  570.  
  571. var tt_top = bbcr.top + bbcr.height/2 - tt_top_offset // tooltip top
  572.  
  573. // YouTube has an existing tooltip DOM structure that it reuses for all of its
  574. // player tooltips, but it's easier and more reliable to just create our own,
  575. // using the same classes.
  576.  
  577. // Try to get our existing tooltip from DOM from previous run
  578. var tt = document.getElementById("zac-tt")
  579.  
  580. var tt_text
  581.  
  582. if(!tt) {
  583. // Create tool-tip DOM structure if not present.
  584. var mp = document.getElementsByClassName("html5-video-player")[0]
  585. var tt_text_wrapper = document.createElement("div")
  586. tt = document.createElement("div")
  587. tt_text = document.createElement("span")
  588.  
  589. tt.setAttribute("class", "ytp-tooltip ytp-bottom")
  590. tt.setAttribute("id", "zac-tt")
  591. tt.style.setProperty("position", "fixed")
  592.  
  593. tt_text_wrapper.setAttribute("class", "ytp-tooltip-text-wrapper")
  594.  
  595. tt_text.setAttribute("class", "ytp-tooltip-text")
  596. tt_text.setAttribute("id", "zac-tt-text")
  597.  
  598. tt.appendChild(tt_text_wrapper)
  599. tt_text_wrapper.appendChild(tt_text)
  600. mp.appendChild(tt)
  601. } else {
  602. // If DOM structure already present, get tooltip text.
  603. tt_text = document.getElementById("zac-tt-text")
  604. }
  605.  
  606. if(show) { // show
  607. tt.style.setProperty("top", tt_top + "px")
  608. tt_text.innerHTML = button.getAttribute("aria-label")
  609. tt.style.removeProperty("display") // show the tooltip
  610.  
  611. // Calculate horizontal position. Tooltip must be showing before
  612. // its width can be queried.
  613. var tt_width = tt.getBoundingClientRect().width
  614. tt.style.setProperty("left", tt_horiz_cen - tt_width / 2 + "px")
  615. debug_log("tt_width = " + tt_width)
  616. debug_log("tt_horiz_cen = " + tt_horiz_cen)
  617. debug_log("tt left position = " + (tt_horiz_cen - tt_width / 2))
  618.  
  619. // Remove button title, else the browser may (will) display it as a
  620. // tooltip, in addition to ours.
  621. button.removeAttribute("title")
  622. } else { // hide
  623. tt.style.setProperty("display", "none")
  624. tt_text.innerHTML = ""
  625. button.setAttribute("title", button.getAttribute("aria-label"))
  626. }
  627.  
  628. tooltip_showing = show
  629.  
  630. // All of that just for a tooltip that matches the others. And it's
  631. // still not perfect. Sheesh.
  632. }
  633. /**
  634. * update
  635. * Ensures the zoom button reflects the current state.
  636. */
  637. function update() {
  638. set_zoom_button_mode()
  639. show_zoom_button_tooltip(tooltip_showing)
  640. }
  641. var button_object
  642. function add_button() {
  643. right_controls = document.getElementsByClassName("ytp-right-controls")[0]
  644. size_button = document.getElementsByClassName("ytp-size-button") [0]
  645. if(right_controls && size_button) {
  646. debug_log("Adding zoom and crop toggle button.")
  647. // Remove existing button if present (sometimes it persists even after a page reload)
  648. var existing_button = document.getElementById("zac-zoom-button")
  649. if(existing_button) {
  650. debug_log("Destroying old zoom and crop toggle button.")
  651. right_controls.removeChild(existing_button)
  652. }
  653. // Create button
  654. button = document.createElement("button")
  655. button.setAttribute("class", "ytp-button")
  656. button.setAttribute("id", "zac-zoom-button")
  657.  
  658. create_zoom_button_icon()
  659. set_zoom_button_mode()
  660.  
  661. // Add button to controls
  662. right_controls.insertBefore(button, size_button)
  663.  
  664. // Set event handlers
  665. button.addEventListener("click", toggle_manual_enab)
  666. button.addEventListener("mouseover", function(){show_zoom_button_tooltip()})
  667. button.addEventListener("mouseout", function(){show_zoom_button_tooltip(false)})
  668. button.addEventListener("focus", function(){show_zoom_button_tooltip()})
  669. button.addEventListener("blur", function(){show_zoom_button_tooltip(false)})
  670.  
  671. button_object = {
  672. update : update
  673. }
  674.  
  675. } else {
  676. // Keep trying until we have somewhere to put the button.
  677. debug_log("Can't add zoom and crop toggle button yet. Retrying in 200ms.")
  678. setTimeout(add_button, 200)
  679. }
  680. }
  681. add_button()
  682. return button_object
  683. }
  684.  
  685. // Initialise
  686. watch_for_fullscreen()
  687. watch_for_keypresses()
  688. var zoom_button = create_zoom_button()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址