SponsorBlock (#1130)
* SponsorBlock: enable/url settings * SponsorBlock: fetch and display skipped fragments * SponsorBlock: skip sponsor blocks * npm add node-forge * SponsorBlock: use hash prefix API * SponsorBlock: configurable toast on skipped segment * SponsorBlock: add /api/ to url, remove trailing slash
This commit is contained in:
parent
1096310c9b
commit
440b04bbf0
|
@ -13739,8 +13739,7 @@
|
|||
"node-forge": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
|
||||
"integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA=="
|
||||
},
|
||||
"node-gyp": {
|
||||
"version": "7.1.2",
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
"markdown": "^0.5.0",
|
||||
"material-design-icons": "^3.0.1",
|
||||
"nedb": "^1.8.0",
|
||||
"node-forge": "^0.10.0",
|
||||
"opml-to-json": "^1.0.1",
|
||||
"rss-parser": "^3.12.0",
|
||||
"socks-proxy-agent": "^5.0.0",
|
||||
|
|
|
@ -66,6 +66,10 @@ export default Vue.extend({
|
|||
thumbnail: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
videoId: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
|
@ -149,6 +153,14 @@ export default Vue.extend({
|
|||
|
||||
autoplayVideos: function () {
|
||||
return this.$store.getters.getAutoplayVideos
|
||||
},
|
||||
|
||||
useSponsorBlock: function () {
|
||||
return this.$store.getters.getUseSponsorBlock
|
||||
},
|
||||
|
||||
sponsorBlockShowSkippedToast: function () {
|
||||
return this.$store.getters.getSponsorBlockShowSkippedToast
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
|
@ -274,6 +286,109 @@ export default Vue.extend({
|
|||
}
|
||||
})
|
||||
}
|
||||
setTimeout(() => { this.fetchSponsorBlockInfo() }, 100)
|
||||
},
|
||||
|
||||
fetchSponsorBlockInfo() {
|
||||
if (this.useSponsorBlock) {
|
||||
this.$store.dispatch('sponsorBlockSkipSegments', {
|
||||
videoId: this.videoId,
|
||||
categories: ['sponsor']
|
||||
}).then((skipSegments) => {
|
||||
this.player.on('timeupdate', () => {
|
||||
this.skipSponsorBlocks(skipSegments)
|
||||
})
|
||||
skipSegments.forEach(({
|
||||
category,
|
||||
segment: [startTime, endTime]
|
||||
}) => {
|
||||
this.addSponsorBlockMarker({
|
||||
time: startTime,
|
||||
duration: endTime - startTime,
|
||||
color: this.sponsorBlockCategoryColor(category)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
skipSponsorBlocks(skipSegments) {
|
||||
const currentTime = this.player.currentTime()
|
||||
let newTime = null
|
||||
let skippedCategory = null
|
||||
skipSegments.forEach(({ category, segment: [startTime, endTime] }) => {
|
||||
if (startTime <= currentTime && currentTime < endTime) {
|
||||
newTime = endTime
|
||||
skippedCategory = category
|
||||
}
|
||||
})
|
||||
if (newTime !== null) {
|
||||
if (this.sponsorBlockShowSkippedToast) {
|
||||
this.showSkippedSponsorSegmentInformation(skippedCategory)
|
||||
}
|
||||
this.player.currentTime(newTime)
|
||||
}
|
||||
},
|
||||
|
||||
showSkippedSponsorSegmentInformation(category) {
|
||||
const translatedCategory = this.sponsorBlockTranslatedCategory(category)
|
||||
this.showToast({
|
||||
message: `${this.$t('Video.Skipped segment')} ${translatedCategory}`
|
||||
})
|
||||
},
|
||||
|
||||
sponsorBlockTranslatedCategory(category) {
|
||||
switch (category) {
|
||||
case 'sponsor':
|
||||
return this.$t('Video.Sponsor Block category.sponsor')
|
||||
case 'intro':
|
||||
return this.$t('Video.Sponsor Block category.intro')
|
||||
case 'outro':
|
||||
return this.$t('Video.Sponsor Block category.outro')
|
||||
case 'selfpromo':
|
||||
return this.$t('Video.Sponsor Block category.self-promotion')
|
||||
case 'interaction':
|
||||
return this.$t('Video.Sponsor Block category.interaction')
|
||||
case 'music_offtopic':
|
||||
return this.$t('Video.Sponsor Block category.music offtopic')
|
||||
default:
|
||||
console.error(`Unknown translation for SponsorBlock category ${category}`)
|
||||
return category
|
||||
}
|
||||
},
|
||||
|
||||
sponsorBlockCategoryColor(category) {
|
||||
// TODO: allow to set these colors in settings
|
||||
switch (category) {
|
||||
case 'sponsor':
|
||||
return '#00d400'
|
||||
case 'intro':
|
||||
return '#00ffff'
|
||||
case 'outro':
|
||||
return '#0202ed'
|
||||
case 'selfpromo':
|
||||
return '#ffff00'
|
||||
case 'interaction':
|
||||
return '#cc00ff'
|
||||
case 'music_offtopic':
|
||||
return '#ff9900'
|
||||
default:
|
||||
console.error(`Unknown SponsorBlock category ${category}`)
|
||||
return 'yellow'
|
||||
}
|
||||
},
|
||||
|
||||
addSponsorBlockMarker(marker) {
|
||||
const markerDiv = videojs.dom.createEl('div', {}, {})
|
||||
|
||||
markerDiv.className = 'sponsorBlockMarker'
|
||||
markerDiv.style.height = '100%'
|
||||
markerDiv.style.position = 'absolute'
|
||||
markerDiv.style['background-color'] = marker.color
|
||||
markerDiv.style.width = (marker.duration / this.player.duration()) * 100 + '%'
|
||||
markerDiv.style.marginLeft = (marker.time / this.player.duration()) * 100 + '%'
|
||||
|
||||
this.player.el().querySelector('.vjs-progress-holder').appendChild(markerDiv)
|
||||
},
|
||||
|
||||
checkAspectRatio() {
|
||||
|
@ -1186,6 +1301,7 @@ export default Vue.extend({
|
|||
},
|
||||
|
||||
...mapActions([
|
||||
'showToast',
|
||||
'calculateColorLuminance'
|
||||
])
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 85%;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 680px) {
|
||||
.card {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 500px) {
|
||||
.sponsorBlockSettingsFlexBox {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
import Vue from 'vue'
|
||||
import { mapActions } from 'vuex'
|
||||
import FtCard from '../ft-card/ft-card.vue'
|
||||
import FtToggleSwitch from '../ft-toggle-switch/ft-toggle-switch.vue'
|
||||
import FtInput from '../ft-input/ft-input.vue'
|
||||
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'SponsorBlockSettings',
|
||||
components: {
|
||||
'ft-card': FtCard,
|
||||
'ft-toggle-switch': FtToggleSwitch,
|
||||
'ft-input': FtInput,
|
||||
'ft-flex-box': FtFlexBox
|
||||
},
|
||||
computed: {
|
||||
useSponsorBlock: function () {
|
||||
return this.$store.getters.getUseSponsorBlock
|
||||
},
|
||||
sponsorBlockUrl: function () {
|
||||
return this.$store.getters.getSponsorBlockUrl
|
||||
},
|
||||
sponsorBlockShowSkippedToast: function () {
|
||||
return this.$store.getters.getSponsorBlockShowSkippedToast
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleUpdateSponsorBlock: function (value) {
|
||||
this.updateUseSponsorBlock(value)
|
||||
},
|
||||
|
||||
handleUpdateSponsorBlockUrl: function (value) {
|
||||
const sponsorBlockUrlWithoutTrailingSlash = value.replace(/\/$/, '')
|
||||
const sponsorBlockUrlWithoutApiSuffix = sponsorBlockUrlWithoutTrailingSlash.replace(/\/api$/, '')
|
||||
this.updateSponsorBlockUrl(sponsorBlockUrlWithoutApiSuffix)
|
||||
},
|
||||
|
||||
handleUpdateSponsorBlockShowSkippedToast: function (value) {
|
||||
this.updateSponsorBlockShowSkippedToast(value)
|
||||
},
|
||||
|
||||
...mapActions([
|
||||
'updateUseSponsorBlock',
|
||||
'updateSponsorBlockUrl',
|
||||
'updateSponsorBlockShowSkippedToast'
|
||||
])
|
||||
}
|
||||
})
|
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<ft-card
|
||||
class="relative card"
|
||||
>
|
||||
<h3
|
||||
class="videoTitle"
|
||||
>
|
||||
{{ $t("Settings.SponsorBlock Settings.SponsorBlock Settings") }}
|
||||
</h3>
|
||||
<ft-flex-box class="sponsorBlockSettingsFlexBox">
|
||||
<ft-toggle-switch
|
||||
:label="$t('Settings.SponsorBlock Settings.Enable SponsorBlock')"
|
||||
:default-value="useSponsorBlock"
|
||||
@change="handleUpdateSponsorBlock"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
<ft-flex-box class="sponsorBlockSettingsFlexBox">
|
||||
<ft-toggle-switch
|
||||
:label="$t('Settings.SponsorBlock Settings.Notify when sponsor segment is skipped')"
|
||||
:default-value="sponsorBlockShowSkippedToast"
|
||||
@change="handleUpdateSponsorBlockShowSkippedToast"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
<ft-flex-box>
|
||||
<ft-input
|
||||
:placeholder="$t('Settings.SponsorBlock Settings[\'SponsorBlock API Url (Default is https://sponsor.ajay.app)\']')"
|
||||
:show-arrow="false"
|
||||
:show-label="true"
|
||||
:value="sponsorBlockUrl"
|
||||
@input="handleUpdateSponsorBlockUrl"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
</ft-card>
|
||||
</template>
|
||||
|
||||
<script src="./sponsor-block-settings.js" />
|
||||
<style scoped src="./sponsor-block-settings.css" />
|
|
@ -78,7 +78,10 @@ const state = {
|
|||
hidePopularVideos: false,
|
||||
hidePlaylists: false,
|
||||
hideLiveChat: false,
|
||||
hideActiveSubscriptions: false
|
||||
hideActiveSubscriptions: false,
|
||||
useSponsorBlock: false,
|
||||
sponsorBlockUrl: 'https://sponsor.ajay.app',
|
||||
sponsorBlockShowSkippedToast: true
|
||||
}
|
||||
|
||||
const getters = {
|
||||
|
@ -264,6 +267,18 @@ const getters = {
|
|||
|
||||
getHideActiveSubscriptions: () => {
|
||||
return state.hideActiveSubscriptions
|
||||
},
|
||||
|
||||
getUseSponsorBlock: () => {
|
||||
return state.useSponsorBlock
|
||||
},
|
||||
|
||||
getSponsorBlockUrl: () => {
|
||||
return state.sponsorBlockUrl
|
||||
},
|
||||
|
||||
getSponsorBlockShowSkippedToast: () => {
|
||||
return state.sponsorBlockShowSkippedToast
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -417,6 +432,14 @@ const actions = {
|
|||
case 'hideActiveSubscriptions':
|
||||
commit('setHideActiveSubscriptions', result.value)
|
||||
break
|
||||
case 'useSponsorBlock':
|
||||
commit('setUseSponsorBlock', result.value)
|
||||
break
|
||||
case 'sponsorBlockUrl':
|
||||
commit('setSponsorBlockUrl', result.value)
|
||||
break
|
||||
case 'sponsorBlockShowSkippedToast':
|
||||
commit('setSponsorBlockShowSkippedToast', result.value)
|
||||
}
|
||||
})
|
||||
resolve()
|
||||
|
@ -786,6 +809,30 @@ const actions = {
|
|||
commit('setHideLiveChat', hideLiveChat)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
updateUseSponsorBlock ({ commit }, useSponsorBlock) {
|
||||
settingsDb.update({ _id: 'useSponsorBlock' }, { _id: 'useSponsorBlock', value: useSponsorBlock }, { upsert: true }, (err, numReplaced) => {
|
||||
if (!err) {
|
||||
commit('setUseSponsorBlock', useSponsorBlock)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
updateSponsorBlockUrl ({ commit }, sponsorBlockUrl) {
|
||||
settingsDb.update({ _id: 'sponsorBlockUrl' }, { _id: 'sponsorBlockUrl', value: sponsorBlockUrl }, { upsert: true }, (err, numReplaced) => {
|
||||
if (!err) {
|
||||
commit('setSponsorBlockUrl', sponsorBlockUrl)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
updateSponsorBlockShowSkippedToast ({ commit }, sponsorBlockShowSkippedToast) {
|
||||
settingsDb.update({ _id: 'sponsorBlockShowSkippedToast' }, { _id: 'sponsorBlockShowSkippedToast', value: sponsorBlockShowSkippedToast }, { upsert: true }, (err, numReplaced) => {
|
||||
if (!err) {
|
||||
commit('setSponsorBlockShowSkippedToast', sponsorBlockShowSkippedToast)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -944,6 +991,15 @@ const mutations = {
|
|||
},
|
||||
setHideActiveSubscriptions (state, hideActiveSubscriptions) {
|
||||
state.hideActiveSubscriptions = hideActiveSubscriptions
|
||||
},
|
||||
setUseSponsorBlock (state, useSponsorBlock) {
|
||||
state.useSponsorBlock = useSponsorBlock
|
||||
},
|
||||
setSponsorBlockUrl (state, sponsorBlockUrl) {
|
||||
state.sponsorBlockUrl = sponsorBlockUrl
|
||||
},
|
||||
setSponsorBlockShowSkippedToast (state, sponsorBlockShowSkippedToast) {
|
||||
state.sponsorBlockShowSkippedToast = sponsorBlockShowSkippedToast
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import $ from 'jquery'
|
||||
import forge from 'node-forge'
|
||||
|
||||
const state = {}
|
||||
const getters = {}
|
||||
|
||||
const actions = {
|
||||
sponsorBlockSkipSegments ({ rootState }, { videoId, categories }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const messageDigestSha256 = forge.md.sha256.create()
|
||||
messageDigestSha256.update(videoId)
|
||||
const videoIdHashPrefix = messageDigestSha256.digest().toHex().substring(0, 4)
|
||||
const requestUrl = `${rootState.settings.sponsorBlockUrl}/api/skipSegments/${videoIdHashPrefix}?categories=${JSON.stringify(categories)}`
|
||||
|
||||
$.getJSON(requestUrl, (response) => {
|
||||
const segments = response
|
||||
.filter((result) => result.videoID === videoId)
|
||||
.flatMap((result) => result.segments)
|
||||
resolve(segments)
|
||||
}).fail((xhr, textStatus, error) => {
|
||||
console.log(xhr)
|
||||
console.log(textStatus)
|
||||
console.log(requestUrl)
|
||||
console.log(error)
|
||||
reject(xhr)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const mutations = {}
|
||||
|
||||
export default {
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations
|
||||
}
|
|
@ -9,6 +9,7 @@ import PrivacySettings from '../../components/privacy-settings/privacy-settings.
|
|||
import DataSettings from '../../components/data-settings/data-settings.vue'
|
||||
import DistractionSettings from '../../components/distraction-settings/distraction-settings.vue'
|
||||
import ProxySettings from '../../components/proxy-settings/proxy-settings.vue'
|
||||
import SponsorBlockSettings from '../../components/sponsor-block-settings/sponsor-block-settings.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'Settings',
|
||||
|
@ -22,6 +23,7 @@ export default Vue.extend({
|
|||
'privacy-settings': PrivacySettings,
|
||||
'data-settings': DataSettings,
|
||||
'distraction-settings': DistractionSettings,
|
||||
'proxy-settings': ProxySettings
|
||||
'proxy-settings': ProxySettings,
|
||||
'sponsor-block-settings': SponsorBlockSettings
|
||||
}
|
||||
})
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
<privacy-settings />
|
||||
<data-settings />
|
||||
<proxy-settings />
|
||||
<sponsor-block-settings />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
:storyboard-src="videoStoryboardSrc"
|
||||
:format="activeFormat"
|
||||
:thumbnail="thumbnail"
|
||||
:video-id="videoId"
|
||||
class="videoPlayer"
|
||||
:class="{ theatrePlayer: useTheatreMode }"
|
||||
@ready="checkIfWatched"
|
||||
|
|
|
@ -280,6 +280,11 @@ Settings:
|
|||
Region: Region
|
||||
City: City
|
||||
Error getting network information. Is your proxy configured properly?: Error getting network information. Is your proxy configured properly?
|
||||
SponsorBlock Settings:
|
||||
SponsorBlock Settings: SponsorBlock Settings
|
||||
Enable SponsorBlock: Enable SponsorBlock
|
||||
'SponsorBlock API Url (Default is https://sponsor.ajay.app)': SponsorBlock API Url (Default is https://sponsor.ajay.app)
|
||||
Notify when sponsor segment is skipped: Notify when sponsor segment is skipped
|
||||
About:
|
||||
#On About page
|
||||
About: About
|
||||
|
@ -472,6 +477,14 @@ Video:
|
|||
translated from English: translated from English
|
||||
# $ is replaced with the number and % with the unit (days, hours, minutes...)
|
||||
Publicationtemplate: $ % ago
|
||||
Skipped segment: Skipped segment
|
||||
Sponsor Block category:
|
||||
sponsor: sponsor
|
||||
intro: intro
|
||||
outro: outro
|
||||
self-promotion: self-promotion
|
||||
interaction: interaction
|
||||
music offtopic: music offtopic
|
||||
#& Videos
|
||||
Videos:
|
||||
#& Sort By
|
||||
|
|
Loading…
Reference in New Issue