export default (modules, q, qa) => { let self let lastLoading = modules.globals.dlStatus > 0 && modules.globals.dlStatus < 3 let videoSource = modules.globals.videoPath // Watch for incomplete downloaded video let downloading = false if (modules.globals.dlStatus > 0 && modules.globals.dlStatus < 3) { downloading = true modules.cacheInfo.pingCache(`${modules.globals.data.videoId}-${videoSource.split("&quality=")[1] || modules.globals.startingFormat.qualityLabel}`, () => { downloading = false self.reloadSource() }, 1000 * 2) } // If video is still downloading and skips to the end, try to reload source and rewind if (downloading) { let lastSafeTime function updateLastSafeTime() { lastSafeTime = modules.globals.video.currentTime if (downloading) setTimeout(updateLastSafeTime, 1000 * 1.5) } updateLastSafeTime() modules.globals.video.addEventListener("ended", () => { modules.controls.hideControls(false) if (downloading) { modules.globals.video.currentTime = lastSafeTime self.reloadSource() } }) } self = { downloadMp4Btn: document.getElementById("download-btn"), downloadOggBtn: document.getElementById("download-ogg-btn"), safeQualities: {}, lastToast: null, setQualityButtons: enable => { for (const btn of qa(".videoControls .settingsPopout .settingsPage[data-name='quality'] .setting.quality")) btn.toggleAttribute("disabled", !enable) self.downloadMp4Btn.toggleAttribute("disabled", !enable) self.downloadOggBtn.toggleAttribute("disabled", !enable) }, startLoadingQuality: quality => { self.setQualityButtons(false) newToastWhenReady("yellow", "loading", `Loading ${quality}...`, true) .then(toast => { self.lastToast = toast; if (!lastLoading) { toast.container.remove() self.lastToast = null } }) }, qualitySelected: quality => { quality = quality || modules.globals.startingFormat.qualityLabel modules.controls.setQualityButtonActive(quality) if (lastLoading) return lastLoading = true self.startLoadingQuality(quality) let rateLimitToast let doFetch doFetch = () => { fetch(`/watch?v=${modules.globals.data.videoId}&quality=${quality}${modules.player.startingTime ? `&t=${modules.player.startingTime}` : ""}`) .then(r => { if (r.status == 200) self.waitForNewVideoLoad(`${modules.globals.data.videoId}`, quality) else if (r.status == 429) { newToastWhenReady("red", "loading", `Too many requests. Trying again in ${r.headers.get("retry-after")} seconds...`, true) .then(toast => rateLimitToast = toast) setTimeout(() => { if (rateLimitToast) setToastRemove(rateLimitToast.container) doFetch() }, 1000 * Number(r.headers.get("retry-after"))) } else { newToastWhenReady("red", "x", `Failed to switch quality (${r.status}: ${r.statusText})`) setTimeout(doFetch, 1000 * 10) } }) } doFetch() }, waitForNewVideoLoad: (videoName, quality) => { modules.cacheInfo.pingCache(`${videoName}-${quality}`, () => self.videoDownloaded(`/getVideo?v=${videoName}&q=${quality}`, quality), 1000 * 3) }, setVideoSource: newSource => { videoSource = newSource self.reloadSource() }, videoDownloaded: (videoName, quality) => { self.setVideoSource(videoName) self.safeQualities[quality] = true modules.controls.onVideoDownloaded(quality) // "Done!" toast if (self.lastToast) newToastWhenReady("green", "check", "Done!") lastLoading = false self.setQualityButtons(true) self.downloadMp4Btn.href = `${videoName}&dl=1` // Remove last "loading" toast if applicable if (self.lastToast && self.lastToast.container) setToastRemove(self.lastToast.container) self.lastToast = null }, reloadSource() { let lastTime = modules.globals.video.currentTime modules.globals.video.src = videoSource modules.globals.video.currentTime = lastTime modules.globals.video.load() modules.globals.video.pause() if (modules.player.lastPlaying) modules.globals.video.play() modules.globals.video.focus() } } // Autoload HD video function preloadHDVideo() { const f = modules.globals.targetFormat self.downloadMp4Btn.innerHTML = `Download ${f.cloudtube__label.replace(" *", "")} (${f.eirtube__size})` lastLoading = true self.startLoadingQuality(f.qualityLabel) self.waitForNewVideoLoad(modules.globals.data.videoId, f.qualityLabel) } const preloadRatelimitRegex = /([\d]+p) in (\d\d?) seconds.../ if (modules.globals.awaitingNewFormat) { let preloadRatelimited = false for (const toast of document.getElementsByClassName("toast-container")) { const innerToast = toast.querySelector(".toast") const text = innerToast.childNodes[1].textContent // Look for ratelimited preload text const match = text.match(preloadRatelimitRegex) if (match) { preloadRatelimited = true const seconds = Number(match[2]) innerToast.childNodes[1].textContent = text.replace("You may download", "Preloading") toast.classList.add("nofade") setTimeout(() => { if (toast) setToastRemove(toast) self.qualitySelected(modules.globals.targetFormat.qualityLabel) }, (seconds + 0.5) * 1000) break } } if (!preloadRatelimited) preloadHDVideo() } self.downloadOggBtn.addEventListener("click", event => { newToastWhenReady("yellow", "loading", "Extracting audio...", true) }) // Reload source if error modules.globals.video.addEventListener("error", () => setTimeout(self.reloadSource, 1000 * 2)) return self }