Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions src/renderer/helpers/colors.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,27 @@ export function getRandomColor() {
}

export function calculateColorLuminance(colorValue) {
const cutHex = colorValue.substring(1, 7)
const colorValueR = parseInt(cutHex.substring(0, 2), 16)
const colorValueG = parseInt(cutHex.substring(2, 4), 16)
const colorValueB = parseInt(cutHex.substring(4, 6), 16)
let colorValues

if (colorValue.startsWith('#')) {
const cutHex = colorValue.length === 4
? colorValue.slice(1).split('').map(value => value + value).join('')
: colorValue.substring(1, 7)

colorValues = [
parseInt(cutHex.substring(0, 2), 16),
parseInt(cutHex.substring(2, 4), 16),
parseInt(cutHex.substring(4, 6), 16)
]
} else {
colorValues = colorValue.match(/\d+(\.\d+)?/g)?.slice(0, 3).map(Number)
}

if (!colorValues || colorValues.some(value => isNaN(value))) {
return '#FFFFFF'
}

const [colorValueR, colorValueG, colorValueB] = colorValues

const luminance = (0.299 * colorValueR + 0.587 * colorValueG + 0.114 * colorValueB) / 255

Expand Down
99 changes: 81 additions & 18 deletions src/renderer/views/Watch/Watch.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import WatchVideoLiveChat from '../../components/WatchVideoLiveChat/WatchVideoLi
import WatchVideoPlaylist from '../../components/WatchVideoPlaylist/WatchVideoPlaylist.vue'
import WatchVideoRecommendations from '../../components/WatchVideoRecommendations/WatchVideoRecommendations.vue'
import FtAgeRestricted from '../../components/FtAgeRestricted/FtAgeRestricted.vue'
import { calculateColorLuminance } from '../../helpers/colors'
import {
buildVTTFileLocally,
copyToClipboard,
Expand Down Expand Up @@ -55,6 +56,10 @@ import { MANIFEST_TYPE_SABR } from '../../helpers/player/SabrManifestParser'

const MANIFEST_TYPE_DASH = 'application/dash+xml'
const MANIFEST_TYPE_HLS = 'application/x-mpegurl'
const UNAVAILABLE_VIDEO_THUMBNAILS = {
light: 'https://www.youtube.com/img/desktop/unavailable/unavailable_video.png',
dark: 'https://www.youtube.com/img/desktop/unavailable/unavailable_video_dark_theme.png'
}

export default defineComponent({
name: 'Watch',
Expand Down Expand Up @@ -345,18 +350,11 @@ export default defineComponent({

// react to route changes...
this.videoId = this.$route.params.id
this.resetVideoState()

this.firstLoad = true
this.videoPlayerLoaded = false
this.errorMessage = null
this.customErrorIcon = null
this.activeFormat = this.defaultVideoFormat
this.sabrData = null
this.videoStoryboardSrc = ''
this.captions = []
this.vrProjection = null
this.videoCurrentChapterIndex = 0
this.videoGenreIsMusic = false

this.checkIfTimestamp()
this.checkIfPlaylist()
Expand All @@ -370,6 +368,53 @@ export default defineComponent({
break
}
},

resetVideoState: function () {
this.isLoading = true
this.isFamilyFriendly = false
this.isLive = false
this.liveChat = null
this.isLiveContent = false
this.isUpcoming = false
this.isPostLiveDvr = false
this.isUnlisted = false
this.upcomingTimestamp = null
this.upcomingTimeLeft = null
this.thumbnail = ''
this.videoTitle = ''
this.videoDescription = ''
this.videoDescriptionHtml = ''
this.license = ''
this.videoViewCount = 0
this.videoLikeCount = 0
this.videoDislikeCount = 0
this.videoLengthSeconds = 0
this.videoChapters = []
this.videoCurrentChapterIndex = 0
this.videoChaptersKind = 'chapters'
this.channelName = ''
this.channelThumbnail = ''
this.channelId = ''
this.channelSubscriptionCountText = ''
this.videoPublished = 0
this.premiereDate = undefined
this.videoStoryboardSrc = ''
this.manifestSrc = null
this.manifestMimeType = MANIFEST_TYPE_DASH
this.sabrData = null
this.legacyFormats = []
this.captions = []
this.vrProjection = null
this.recommendedVideos = []
this.playabilityStatus = ''
this.adEndTimeUnixMs = 0
this.errorMessage = null
this.customErrorIcon = null
this.videoGenreIsMusic = false
this.streamingDataExpiryDate = null
this.updateTitle()
},

onMountedDependOnLocalStateLoading() {
// Prevent running twice
if (this.onMountedRun) { return }
Expand Down Expand Up @@ -860,16 +905,21 @@ export default defineComponent({
this.isLoading = false
this.updateTitle()
} catch (err) {
const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => {
copyToClipboard(err)
})
console.error(err)
if (this.backendPreference === 'local' && this.backendFallback && !err.toString().includes('private')) {
if (this.backendPreference === 'local' && this.backendFallback && !err.toString().includes('private') && !err.toString().includes('unavailable')) {
const errorMessage = this.$t('Local API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => {
copyToClipboard(err)
})
showToast(this.$t('Falling back to Invidious API'))
this.getVideoInformationInvidious()
} else {
this.isLoading = false

if (!this.thumbnail) {
this.thumbnail = this.getUnavailableVideoThumbnail()
}
this.errorMessage = err.message || err.toString()
}
}
},
Expand Down Expand Up @@ -1045,17 +1095,21 @@ export default defineComponent({
this.isLoading = false
})
.catch(err => {
console.error(err)
const errorMessage = this.$t('Invidious API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => {
copyToClipboard(err)
})
console.error(err)
if (process.env.SUPPORTS_LOCAL_API && this.backendPreference === 'invidious' && this.backendFallback) {
const errorMessage = this.$t('Invidious API Error (Click to copy)')
showToast(`${errorMessage}: ${err}`, 10000, () => {
copyToClipboard(err)
})
showToast(this.$t('Falling back to Local API'))
this.getVideoInformationLocal()
} else {
this.isLoading = false

if (!this.thumbnail) {
this.thumbnail = this.getUnavailableVideoThumbnail()
}
this.errorMessage = err.message || err.toString()
}
})
},
Expand All @@ -1066,6 +1120,15 @@ export default defineComponent({
return new Date(parseInt(expireString) * 1000)
},

getUnavailableVideoThumbnail: function () {
const backgroundColor = window.getComputedStyle(document.body).backgroundColor
const isLightTheme = calculateColorLuminance(backgroundColor) === '#000000'

return isLightTheme
? UNAVAILABLE_VIDEO_THUMBNAILS.light
: UNAVAILABLE_VIDEO_THUMBNAILS.dark
},

/**
* @param {string?} description
*/
Expand Down
Loading