diff --git a/src/renderer/components/ft-list-video/ft-list-video.vue b/src/renderer/components/ft-list-video/ft-list-video.vue
index 495b3464bffcf..d49e86b3d4430 100644
--- a/src/renderer/components/ft-list-video/ft-list-video.vue
+++ b/src/renderer/components/ft-list-video/ft-list-video.vue
@@ -112,7 +112,10 @@
>
{{ channelName }}
-
+
+ {{ channelName }}
+
+
•
{{ $tc('Global.Counts.View Count', viewCount, {count: parsedViewCount}) }}
diff --git a/src/renderer/helpers/api/local.js b/src/renderer/helpers/api/local.js
index 72095878449bf..9a7ec7acd98a0 100644
--- a/src/renderer/helpers/api/local.js
+++ b/src/renderer/helpers/api/local.js
@@ -457,7 +457,7 @@ function handleSearchResponse(response) {
const results = response.results
.filter((item) => {
- return item.type === 'Video' || item.type === 'Channel' || item.type === 'Playlist' || item.type === 'HashtagTile'
+ return item.type === 'Video' || item.type === 'Channel' || item.type === 'Playlist' || item.type === 'HashtagTile' || item.type === 'Movie'
})
.map((item) => parseListItem(item))
@@ -537,22 +537,41 @@ export function parseLocalPlaylistVideo(video) {
}
/**
- * @param {import('youtubei.js').YTNodes.Video} video
+ * @param {import('youtubei.js').YTNodes.Video | import('youtubei.js').YTNodes.Movie} item
*/
-export function parseLocalListVideo(video) {
- return {
- type: 'video',
- videoId: video.id,
- title: video.title.text,
- author: video.author.name,
- authorId: video.author.id,
- description: video.description,
- viewCount: extractNumberFromString(video.view_count.text),
- publishedText: video.published.isEmpty() ? null : video.published.text,
- lengthSeconds: isNaN(video.duration.seconds) ? '' : video.duration.seconds,
- liveNow: video.is_live,
- isUpcoming: video.is_upcoming || video.is_premiere,
- premiereDate: video.upcoming
+export function parseLocalListVideo(item) {
+ if (item.type === 'Movie') {
+ /** @type {import('youtubei.js').YTNodes.Movie} */
+ const movie = item
+
+ return {
+ type: 'video',
+ videoId: movie.id,
+ title: movie.title.text,
+ author: movie.author.name,
+ authorId: movie.author.id !== 'N/A' ? movie.author.id : null,
+ description: movie.description_snippet?.text,
+ lengthSeconds: isNaN(movie.duration.seconds) ? '' : movie.duration.seconds,
+ liveNow: false,
+ isUpcoming: false,
+ }
+ } else {
+ /** @type {import('youtubei.js').YTNodes.Video} */
+ const video = item
+ return {
+ type: 'video',
+ videoId: video.id,
+ title: video.title.text,
+ author: video.author.name,
+ authorId: video.author.id,
+ description: video.description,
+ viewCount: video.view_count == null ? null : extractNumberFromString(video.view_count.text),
+ publishedText: (video.published == null || video.published.isEmpty()) ? null : video.published.text,
+ lengthSeconds: isNaN(video.duration.seconds) ? '' : video.duration.seconds,
+ liveNow: video.is_live,
+ isUpcoming: video.is_upcoming || video.is_premiere,
+ premiereDate: video.upcoming
+ }
}
}
@@ -561,6 +580,7 @@ export function parseLocalListVideo(video) {
*/
function parseListItem(item) {
switch (item.type) {
+ case 'Movie':
case 'Video':
return parseLocalListVideo(item)
case 'Channel': {
@@ -627,8 +647,8 @@ export function parseLocalWatchNextVideo(video) {
title: video.title.text,
author: video.author.name,
authorId: video.author.id,
- viewCount: extractNumberFromString(video.view_count.text),
- publishedText: video.published.isEmpty() ? null : video.published.text,
+ viewCount: video.view_count == null ? null : extractNumberFromString(video.view_count.text),
+ publishedText: (video.published == null || video.published.isEmpty()) ? null : video.published.text,
lengthSeconds: isNaN(video.duration.seconds) ? '' : video.duration.seconds,
liveNow: video.is_live,
isUpcoming: video.is_premiere
diff --git a/src/renderer/views/Watch/Watch.js b/src/renderer/views/Watch/Watch.js
index d449847f0f7cf..6bd4fafae68d6 100644
--- a/src/renderer/views/Watch/Watch.js
+++ b/src/renderer/views/Watch/Watch.js
@@ -307,7 +307,7 @@ export default defineComponent({
this.isFamilyFriendly = result.basic_info.is_family_safe
const recommendedVideos = result.watch_next_feed
- ?.filter((item) => item.type === 'CompactVideo')
+ ?.filter((item) => item.type === 'CompactVideo' || item.type === 'CompactMovie')
.map(parseLocalWatchNextVideo) ?? []
// place watched recommended videos last
@@ -324,10 +324,28 @@ export default defineComponent({
let playabilityStatus = result.playability_status
let bypassedResult = null
- if (playabilityStatus.status === 'LOGIN_REQUIRED') {
+ let streamingVideoId = this.videoId
+ let trailerIsNull = false
+
+ // if widevine support is added then we should check if playabilityStatus.status is UNPLAYABLE too
+ if (result.has_trailer) {
+ bypassedResult = result.getTrailerInfo()
+ /**
+ * @type {import ('youtubei.js').YTNodes.PlayerLegacyDesktopYpcTrailer}
+ */
+ const trailerScreen = result.playability_status.error_screen
+ streamingVideoId = trailerScreen.video_id
+ // if the trailer is null then it is likely age restricted.
+ trailerIsNull = bypassedResult == null
+ if (!trailerIsNull) {
+ playabilityStatus = bypassedResult.playability_status
+ }
+ }
+
+ if (playabilityStatus.status === 'LOGIN_REQUIRED' || trailerIsNull) {
// try to bypass the age restriction
- bypassedResult = await getLocalVideoInfo(this.videoId, true)
- playabilityStatus = result.playability_status
+ bypassedResult = await getLocalVideoInfo(streamingVideoId, true)
+ playabilityStatus = bypassedResult.playability_status
}
if (playabilityStatus.status === 'UNPLAYABLE') {