-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
157 lines (134 loc) · 5.06 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
(function () {
/** Main Interface */
class PlaylistDuration {
constructor() {
PlaylistDuration.cleanup()
const statsBlock = document.getElementById('stats')
this.element = document.createElement('yt-formatted-string')
this.element.id = 'YPDTimeElement'
this.element.classList = 'style-scope ytd-playlist-sidebar-primary-info-renderer'
statsBlock.appendChild(this.element)
this.locked = false
this.lib = {
getSecondsFromTimestamp: (timestamp) => {
let [sec=0, min=0, hr=0] = timestamp.split(':').reverse()
sec = Number(sec)
sec += min * 60
sec += hr * 60 * 60
return sec
},
getTimestampFromSeconds: (totalSeconds) => {
const sec = totalSeconds % 60
const min = Math.floor((totalSeconds/60) % 60)
const hr = Math.floor(totalSeconds/60/60)
let timeStr = `${sec}s`
if (min || hr) { timeStr = `${min}m ${timeStr}` }
if (hr) { timeStr = `${hr}h ${timeStr}` }
return timeStr
},
getPlaylistVideoCount: () => {
const stats = document.getElementById('stats')
// Return a string like '42 videos'
const videoCountString = Array.from(stats.children)
.find(item => item.innerText.toLowerCase().includes('video'))
.innerText
/**
* Match to digits, pull from the fullest match,
* cast to Number, and if there's a problem, default to 0
*/
return Number(
videoCountString
.match(/\d+/g)
[0]
) || 0
},
getLoadedVideoCount: () => {
const videos = document.getElementsByTagName('ytd-playlist-video-renderer')
return videos.length || 0
}
}
}
refresh() {
if (this.locked) { return }
const videos = document.getElementsByTagName('ytd-playlist-video-list-renderer')[0]
const times = videos.getElementsByClassName('ytd-thumbnail-overlay-time-status-renderer')
/** times is an HTMLElement or something, not an array,
* so it doesn't have a .forEach method. We can invoke it
* from the Array.prototype and it'll work the way we want
*/// ¯\_(ツ)_/¯
let newTime = 0;
Array.prototype.forEach.call(times, item => {
newTime += this.lib.getSecondsFromTimestamp(item.innerText)
})
let timestamp = this.lib.getTimestampFromSeconds(newTime)
if (this.lib.getPlaylistVideoCount() > times.length) {
timestamp += '+'
}
this.element.innerText = timestamp
this.cycleLock()
}
cycleLock() {
this.locked = true;
setTimeout((() => {
this.locked = false
}).bind(this), 1000)
}
static cleanup() {
const oldElement = document.getElementById('YPDTimeElement')
if (oldElement) { oldElement.remove() }
}
}
/** Extension/Event Handlers */
let timeElement = null
function initYPD() {
if (window.location.pathname.includes('playlist')) {
timeElement = new PlaylistDuration()
delayedRefresh()
}
}
function negateYPD() {
timeElement = null
PlaylistDuration.cleanup()
}
function delayedRefresh() {
if (!timeElement) { return }
setInterval(timeElement.refresh.bind(timeElement), 1000)
}
/** Fires once when YouTube is first loaded */
window.addEventListener('load', initYPD)
/**
* YouTube manages its own history and state and acts
* as a full web-app, so 'onload' and 'load' events
* don't fire because as far as the browser is concerned
* the page is already loaded. YouTube self-manages
* navigation, so we need to hook into the start and stop
* events for that navigation
*/
window.addEventListener('yt-navigate-start', negateYPD)
window.addEventListener('yt-navigate-finish', initYPD)
/**
* Fires all the time and is a convenient hook for
* refreshing timestamp data. Ex: Initial playlist
* only loads 100 videos until user scrolls through
* the list, and this event will fire VERY OFTEN
* including when those extra videos load
*/
window.addEventListener('yt-visibility-refresh', () => {
// Short-circuits if timeElement is null
timeElement && timeElement.refresh()
})
window.addEventListener('yt-page-data-updated', () => {
timeElement && timeElement.refresh()
})
// listeners
// [
// 'yt-page-data-updated',
// 'yt-navigate-start',
// 'yt-navigate-finish',
// // 'yt-visibility-refresh',
// 'yt-player-requested',
// 'yt-page-type-changed',
// 'yt-navigate',
// 'load'
// ].forEach(event => window.addEventListener(event, console.log))
})()