From 7eda64929303667b357ab1296c04e99845c183f1 Mon Sep 17 00:00:00 2001 From: Svallinn <41585298+Svallinn@users.noreply.github.com> Date: Sat, 6 Mar 2021 19:03:40 +0000 Subject: [PATCH] Support for the 't' parameter in links (#1090) This allows users to specify the timestamp of a video (in seconds) - by inputting a link into the search bar - by making use of the protocol link (freetube://), p.e in a browser --- src/renderer/App.js | 7 ++-- src/renderer/components/top-nav/top-nav.js | 5 ++- src/renderer/store/modules/utils.js | 24 ++++++++---- src/renderer/views/Watch/Watch.js | 43 ++++++++++++++++++---- 4 files changed, 59 insertions(+), 20 deletions(-) diff --git a/src/renderer/App.js b/src/renderer/App.js index 2eab0c06..c821fad6 100644 --- a/src/renderer/App.js +++ b/src/renderer/App.js @@ -284,10 +284,11 @@ export default Vue.extend({ const v = this electron.ipcRenderer.on('openUrl', function (event, url) { if (url) { - v.$store.dispatch('getVideoIdFromUrl', url).then((result) => { - if (result) { + v.$store.dispatch('getVideoParamsFromUrl', url).then(({ videoId, timestamp }) => { + if (videoId) { v.$router.push({ - path: `/watch/${result}` + path: `/watch/${videoId}`, + query: timestamp ? { timestamp } : {} }) } }) diff --git a/src/renderer/components/top-nav/top-nav.js b/src/renderer/components/top-nav/top-nav.js index 035321d6..653df6ec 100644 --- a/src/renderer/components/top-nav/top-nav.js +++ b/src/renderer/components/top-nav/top-nav.js @@ -89,14 +89,15 @@ export default Vue.extend({ searchInput.blur() } - const videoId = await this.$store.dispatch('getVideoIdFromUrl', query) + const { videoId, timestamp } = await this.$store.dispatch('getVideoParamsFromUrl', query) const playlistId = await this.$store.dispatch('getPlaylistIdFromUrl', query) console.log(playlistId) if (videoId) { this.$router.push({ - path: `/watch/${videoId}` + path: `/watch/${videoId}`, + query: timestamp ? { timestamp } : {} }) } else if (playlistId) { this.$router.push({ diff --git a/src/renderer/store/modules/utils.js b/src/renderer/store/modules/utils.js index 2daf9dc2..4bd882e6 100644 --- a/src/renderer/store/modules/utils.js +++ b/src/renderer/store/modules/utils.js @@ -190,43 +190,53 @@ const actions = { return date.getTime() - timeSpan }, - getVideoIdFromUrl (_, url) { + getVideoParamsFromUrl (_, url) { /** @type {URL} */ let urlObject + const paramsObject = { videoId: null, timestamp: null } try { urlObject = new URL(url) } catch (e) { - return false + return paramsObject + } + + function extractParams(videoId) { + paramsObject.videoId = videoId + paramsObject.timestamp = urlObject.searchParams.get('t') } const extractors = [ // anything with /watch?v= function() { if (urlObject.pathname === '/watch' && urlObject.searchParams.has('v')) { - return urlObject.searchParams.get('v') + extractParams(urlObject.searchParams.get('v')) + return paramsObject } }, // youtu.be function() { if (urlObject.host === 'youtu.be' && urlObject.pathname.match(/^\/[A-Za-z0-9_-]+$/)) { - return urlObject.pathname.slice(1) + extractParams(urlObject.pathname.slice(1)) + return paramsObject } }, // youtube.com/embed function() { if (urlObject.pathname.match(/^\/embed\/[A-Za-z0-9_-]+$/)) { - return urlObject.pathname.replace('/embed/', '') + extractParams(urlObject.pathname.replace('/embed/', '')) + return paramsObject } }, // cloudtube function() { if (urlObject.host.match(/^cadence\.(gq|moe)$/) && urlObject.pathname.match(/^\/cloudtube\/video\/[A-Za-z0-9_-]+$/)) { - return urlObject.pathname.slice('/cloudtube/video/'.length) + extractParams(urlObject.pathname.slice('/cloudtube/video/'.length)) + return paramsObject } } ] - return extractors.reduce((a, c) => a || c(), null) || false + return extractors.reduce((a, c) => a || c(), null) || paramsObject }, getPlaylistIdFromUrl (_, url) { diff --git a/src/renderer/views/Watch/Watch.js b/src/renderer/views/Watch/Watch.js index a5c39390..aa014949 100644 --- a/src/renderer/views/Watch/Watch.js +++ b/src/renderer/views/Watch/Watch.js @@ -74,6 +74,7 @@ export default Vue.extend({ downloadLinks: [], watchingPlaylist: false, playlistId: '', + timestamp: null, playNextTimeout: null } }, @@ -156,6 +157,7 @@ export default Vue.extend({ this.downloadLinks = [] this.checkIfPlaylist() + this.checkIfTimestamp() switch (this.backendPreference) { case 'local': @@ -177,6 +179,7 @@ export default Vue.extend({ this.useTheatreMode = this.defaultTheatreMode this.checkIfPlaylist() + this.checkIfTimestamp() if (!this.usingElectron) { this.getVideoInformationInvidious() @@ -682,18 +685,32 @@ export default Vue.extend({ console.log(historyIndex) - if (historyIndex !== -1 && !this.isLive) { - const watchProgress = this.historyCache[historyIndex].watchProgress + if (!this.isLive) { + if (this.timestamp) { + if (this.timestamp < 0) { + this.$refs.videoPlayer.player.currentTime(0) + } else if (this.timestamp > (this.videoLengthSeconds - 10)) { + this.$refs.videoPlayer.player.currentTime(this.videoLengthSeconds - 10) + } else { + this.$refs.videoPlayer.player.currentTime(this.timestamp) + } + } else if (historyIndex !== -1) { + const watchProgress = this.historyCache[historyIndex].watchProgress - if (watchProgress < (this.videoLengthSeconds - 10)) { - this.$refs.videoPlayer.player.currentTime(watchProgress) + if (watchProgress < (this.videoLengthSeconds - 10)) { + this.$refs.videoPlayer.player.currentTime(watchProgress) + } } } - if (this.rememberHistory && historyIndex !== -1) { - this.addToHistory(this.historyCache[historyIndex].watchProgress) - } else if (this.rememberHistory) { - this.addToHistory(0) + if (this.rememberHistory) { + if (this.timestamp) { + this.addToHistory(this.timestamp) + } else if (historyIndex !== -1) { + this.addToHistory(this.historyCache[historyIndex].watchProgress) + } else { + this.addToHistory(0) + } } }, @@ -711,6 +728,16 @@ export default Vue.extend({ } }, + checkIfTimestamp: function () { + if (typeof (this.$route.query) !== 'undefined') { + try { + this.timestamp = parseInt(this.$route.query.timestamp) + } catch { + this.timestamp = null + } + } + }, + getLegacyFormats: function () { this.$store .dispatch('ytGetVideoInformation', this.videoId)