Youtube Live Clock

show duration for livestreams and present time for archives

当前为 2025-05-15 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Youtube Live Clock
  3. // @name:zh-TW Youtube Live Clock
  4. // @namespace https://greasyfork.org/scripts/453367
  5. // @version 1.8.0
  6. // @description show duration for livestreams and present time for archives
  7. // @description:zh-TW 顯示直播及直播存檔當下的時間
  8. // @author Derek
  9. // @match *://www.youtube.com/*
  10. // @run-at document-start
  11. // @grant GM_addStyle
  12. // ==/UserScript==
  13.  
  14. (() => {
  15. //you can choose your ideal date format by changing the FORMAT's value below
  16. const FORMAT = 1
  17. /*
  18. 1: 2022/10/31 06:37:10 (default)
  19. 2: 10/31/2022 06:37:10
  20. 3: 31/10/2022 06:37:10
  21. 4: Mon 31/10/2022 06:37:10
  22. 5: Monday 31/10/2022 06:37:10
  23. 6: Monday 31 October 2022 06:37:10
  24. */
  25.  
  26. GM_addStyle(`
  27. .ytp-chrome-bottom .ytp-time-display {
  28. display: flex !important;
  29. }
  30. #present-time {
  31. margin: 0 10px 0 5px !important;
  32. }
  33. `)
  34.  
  35. const $ = (element) => document.querySelector(element)
  36.  
  37. const abbr = {
  38. week: { Sun: 'Sunday', Mon: 'Monday', Tue: 'Tuesday', Wed: 'Wednesday', Thu: 'Thursday', Fri: 'Friday', Sat: 'Saturday' },
  39. monthFull: { Jan: 'January', Feb: 'February', Mar: 'March', Apr: 'April', May: 'May', Jun: 'June', Jul: 'July', Aug: 'August', Sep: 'September', Oct: 'October', Nov: 'November', Dec: 'December' },
  40. month: { Jan: '01', Feb: '02', Mar: '03', Apr: '04', May: '05', Jun: '06', Jul: '07', Aug: '08', Sep: '09', Oct: '10', Nov: '11', Dec: '12' }
  41. }
  42.  
  43. const twoDigit = (num) => num.toString().padStart(2, '0')
  44.  
  45. const timeFormat = (time) => {
  46. const second = time % 60
  47. const minute = Math.floor((time / 60) % 60)
  48. const hour = Math.floor(time / 3600)
  49. return hour > 0 ? `${hour}:${twoDigit(minute)}:${twoDigit(second)}` : `${minute}:${twoDigit(second)}`
  50. }
  51.  
  52. const dateFormat = (presentTime) => {
  53. const [week, month, day, year, time] = presentTime.toString().split(' ')
  54. return {
  55. 1: ` (${year}/${abbr.month[month]}/${day} ${time})`,
  56. 2: ` (${abbr.month[month]}/${day}/${year} ${time})`,
  57. 3: ` (${day}/${abbr.month[month]}/${year} ${time})`,
  58. 4: ` (${week} ${day}/${abbr.month[month]}/${year} ${time})`,
  59. 5: ` (${abbr.week[week]} ${day}/${abbr.month[month]}/${year} ${time})`,
  60. 6: ` (${abbr.week[week]} ${day} ${abbr.monthFull[month]} ${year} ${time})`
  61. }[FORMAT]
  62. }
  63.  
  64. let videoId, timeDisplay, progressBar, liveData, publication, observer
  65. let videoData = null
  66.  
  67. const waitElements = () => {
  68. return new Promise((resolve) => {
  69. const observer = new MutationObserver(() => {
  70. timeDisplay = $('.ytp-chrome-bottom .ytp-time-display')
  71. progressBar = $('.ytp-chrome-bottom .ytp-progress-bar')
  72.  
  73. if (timeDisplay && progressBar) {
  74. if (videoData !== $('#microformat script')) {
  75. videoData = $('#microformat script')
  76. observer.disconnect()
  77. resolve()
  78. }
  79. }
  80. })
  81. observer.observe(document.body, { attributes: false, childList: true, subtree: true })
  82. })
  83. }
  84.  
  85. const getLiveClock = () => {
  86. let clockElement = $('#present-time')
  87. if (!clockElement) {
  88. clockElement = document.createElement('span')
  89. clockElement.id = 'present-time'
  90. timeDisplay.insertBefore(clockElement, timeDisplay.childNodes[1])
  91. }
  92. return clockElement
  93. }
  94.  
  95. const updateLiveTime = () => {
  96. const progressTime = progressBar.getAttribute('aria-valuenow')
  97. return publication.endDate ? dateFormat(new Date(Date.parse(publication.startDate) + progressTime * 1000)) : timeFormat(progressTime)
  98. }
  99.  
  100. const main = async (vid) => {
  101. if (videoId === vid) return
  102. videoId = vid
  103.  
  104. if (observer) observer.disconnect()
  105. await waitElements()
  106.  
  107. liveData = JSON.parse(videoData.textContent)
  108. if (!liveData.publication) {
  109. if ($('#present-time')) $('#present-time').remove()
  110. return
  111. }
  112. publication = liveData.publication[0]
  113.  
  114. let liveClock = getLiveClock()
  115. liveClock.textContent = updateLiveTime()
  116.  
  117. observer = new MutationObserver(() => { liveClock.textContent = updateLiveTime() })
  118. observer.observe(progressBar, { characterData: true, attributeFilter: ['aria-valuenow'] })
  119. }
  120.  
  121. document.addEventListener('yt-navigate-finish', (event) => {
  122. const url = event.detail.endpoint.commandMetadata.webCommandMetadata.url
  123. if (url.startsWith('/watch?v=') || url.startsWith('/live/')) main(url.match(/[A-z0-9_-]{11}/)[0])
  124. })
  125. })()