From 358da49cebb6fda2f9ad5e1d3f0d7dcf20eecf2c Mon Sep 17 00:00:00 2001 From: blattersturm Date: Mon, 12 Jun 2023 00:40:52 +0200 Subject: [PATCH] tweak(ext/cfx-ui): youtube embeds for community tweets Some RTs (like https://twitter.com/xinerki/status/1666055508548018178) have a YouTube link rather than a native Twitter embed. --- .../AcitivityItemMediaViewer.module.scss | 7 ++- .../AcitivityItemMediaViewer.tsx | 4 +- .../cfx/common/services/activity/twitter.ts | 55 ++++++++++++++++++- .../src/cfx/common/services/activity/types.ts | 2 +- .../src/cfx/ui/ActivityItem/ActivityItem.tsx | 3 +- 5 files changed, 63 insertions(+), 8 deletions(-) diff --git a/ext/cfx-ui/src/cfx/apps/mpMenu/components/AcitivityItemMediaViewer/AcitivityItemMediaViewer.module.scss b/ext/cfx-ui/src/cfx/apps/mpMenu/components/AcitivityItemMediaViewer/AcitivityItemMediaViewer.module.scss index e93c0102ed..c0eabd8e94 100644 --- a/ext/cfx-ui/src/cfx/apps/mpMenu/components/AcitivityItemMediaViewer/AcitivityItemMediaViewer.module.scss +++ b/ext/cfx-ui/src/cfx/apps/mpMenu/components/AcitivityItemMediaViewer/AcitivityItemMediaViewer.module.scss @@ -46,8 +46,13 @@ } // Video element does not naturally grow - stretch it - video.media { + video.media, iframe.media { height: 90%; aspect-ratio: ui.use('ar'); } + + // iframes have a border + iframe.media { + border: none; + } } diff --git a/ext/cfx-ui/src/cfx/apps/mpMenu/components/AcitivityItemMediaViewer/AcitivityItemMediaViewer.tsx b/ext/cfx-ui/src/cfx/apps/mpMenu/components/AcitivityItemMediaViewer/AcitivityItemMediaViewer.tsx index 65152ba074..fc2660d209 100644 --- a/ext/cfx-ui/src/cfx/apps/mpMenu/components/AcitivityItemMediaViewer/AcitivityItemMediaViewer.tsx +++ b/ext/cfx-ui/src/cfx/apps/mpMenu/components/AcitivityItemMediaViewer/AcitivityItemMediaViewer.tsx @@ -11,7 +11,7 @@ const Outlet = createOutlet(); export const AcitivityItemMediaViewer = observer(function AcitivityItemMediaViewer() { const state = AcitivityItemMediaViewerState; - const ref = React.useRef(null); + const ref = React.useRef(null); const [toRect, setToRect] = React.useState(null); React.useEffect(() => { @@ -68,7 +68,7 @@ export const AcitivityItemMediaViewer = observer(function AcitivityItemMediaView const TargetElement = state.media.type === 'video' ? 'video' - : 'img'; + : (state.media.type === 'youtube' ? 'iframe' : 'img'); return ( diff --git a/ext/cfx-ui/src/cfx/common/services/activity/twitter.ts b/ext/cfx-ui/src/cfx/common/services/activity/twitter.ts index 04eaf0a2b7..fb003ee8aa 100644 --- a/ext/cfx-ui/src/cfx/common/services/activity/twitter.ts +++ b/ext/cfx-ui/src/cfx/common/services/activity/twitter.ts @@ -1,5 +1,26 @@ import { splitByIndices } from "cfx/utils/string"; -import { IActivityItemData, IActivityItemMedia, IRawTweet } from "./types"; +import { IActivityItemData, IActivityItemMedia, IRawTweet, IRawTweetTypes } from "./types"; + +function parseYoutube(url: string): [ boolean, string ] { + const parsedUrl = new URL(url); + if (parsedUrl.hostname === 'youtu.be' && parsedUrl.pathname.length >= 5) { + return [ true, parsedUrl.pathname.substring(1) ]; + } else if (parsedUrl.hostname == 'youtube.com' && parsedUrl.pathname.startsWith('/watch')) { + const v = parsedUrl.searchParams.get('v'); + + if (v) { + return [ true, v ]; + } + } + + return [ false, '' ]; +} + +function isYoutube(url: string) { + const [ valid ] = parseYoutube(url); + + return valid; +} export function rawTweetToActivityDataItem(rawTweet: IRawTweet): IActivityItemData | null { let actualTweet = rawTweet; @@ -35,14 +56,42 @@ export function rawTweetToActivityDataItem(rawTweet: IRawTweet): IActivityItemDa if (mediaEntity.type === 'animated_gif' || mediaEntity.type === 'video') { mediaItem.fullAspectRatio = mediaEntity.video_info.aspect_ratio[0] / mediaEntity.video_info.aspect_ratio[1]; - mediaItem.fullUrl = mediaEntity.video_info.variants.filter(a => a.content_type?.startsWith('video/')).sort((a, b) => b.bitrate - a.bitrate)[0].url; + mediaItem.fullUrl = mediaEntity.video_info.variants + .filter(a => a.content_type?.startsWith('video/')) + .sort((a, b) => b.bitrate - a.bitrate)[0] + .url; } media.push(mediaItem); } } - const mediaIndicesFull = (actualTweet.entities.media || []).map((media) => media.indices); + const youtubeIndices: IRawTweetTypes.Indices[] = []; + + // some tweets (e.g. https://twitter.com/Lucas7yoshi_RS/status/1665445339946418178) may have both an embed and a + // YT link + if (media.length === 0) { + const youtubeUrls = actualTweet.entities?.urls?.filter(x => isYoutube(x.expanded_url)) || []; + + for (const youtube of youtubeUrls) { + const [ _, videoId ] = parseYoutube(youtube.expanded_url); + + media.push({ + id: videoId, + type: 'youtube', + + previewAspectRatio: 16 / 9, + previewUrl: `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`, + + fullAspectRatio: 16 / 9, + fullUrl: `https://www.youtube.com/embed/${videoId}?enablejsapi=1&autoplay=1`, + }); + + youtubeIndices.push(youtube.indices); + } + } + + const mediaIndicesFull = (actualTweet.entities.media || []).map((media) => media.indices).concat(youtubeIndices); const mediaIndices = mediaIndicesFull.map(([x]) => x); const urlIndicesFull = (actualTweet.entities.urls || []).map((url) => url.indices); diff --git a/ext/cfx-ui/src/cfx/common/services/activity/types.ts b/ext/cfx-ui/src/cfx/common/services/activity/types.ts index 2ab8084ce8..953ab5ee88 100644 --- a/ext/cfx-ui/src/cfx/common/services/activity/types.ts +++ b/ext/cfx-ui/src/cfx/common/services/activity/types.ts @@ -200,7 +200,7 @@ export interface IActivityItemData { export interface IActivityItemMedia { id: string, - type: 'photo' | 'animated_gif' | 'video', + type: 'photo' | 'animated_gif' | 'video' | 'youtube', blurhash?: string, previewUrl?: string, diff --git a/ext/cfx-ui/src/cfx/ui/ActivityItem/ActivityItem.tsx b/ext/cfx-ui/src/cfx/ui/ActivityItem/ActivityItem.tsx index b31033883a..74a2b8b855 100644 --- a/ext/cfx-ui/src/cfx/ui/ActivityItem/ActivityItem.tsx +++ b/ext/cfx-ui/src/cfx/ui/ActivityItem/ActivityItem.tsx @@ -68,7 +68,8 @@ function Media({ media }: { media: IActivityItemMedia }) { switch (media.type) { case 'animated_gif': - case 'photo': { + case 'photo': + case 'youtube': { view = ( );