mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
at rn-stack-repro 408 lines 12 kB view raw
1import {Dimensions} from 'react-native' 2import {isWeb} from 'platform/detection' 3const {height: SCREEN_HEIGHT} = Dimensions.get('window') 4 5export const embedPlayerSources = [ 6 'youtube', 7 'youtubeShorts', 8 'twitch', 9 'spotify', 10 'soundcloud', 11 'appleMusic', 12 'vimeo', 13 'giphy', 14 'tenor', 15] as const 16 17export type EmbedPlayerSource = (typeof embedPlayerSources)[number] 18 19export type EmbedPlayerType = 20 | 'youtube_video' 21 | 'youtube_short' 22 | 'twitch_video' 23 | 'spotify_album' 24 | 'spotify_playlist' 25 | 'spotify_song' 26 | 'soundcloud_track' 27 | 'soundcloud_set' 28 | 'apple_music_playlist' 29 | 'apple_music_album' 30 | 'apple_music_song' 31 | 'vimeo_video' 32 | 'giphy_gif' 33 | 'tenor_gif' 34 35export const externalEmbedLabels: Record<EmbedPlayerSource, string> = { 36 youtube: 'YouTube', 37 youtubeShorts: 'YouTube Shorts', 38 vimeo: 'Vimeo', 39 twitch: 'Twitch', 40 giphy: 'GIPHY', 41 tenor: 'Tenor', 42 spotify: 'Spotify', 43 appleMusic: 'Apple Music', 44 soundcloud: 'SoundCloud', 45} 46 47export interface EmbedPlayerParams { 48 type: EmbedPlayerType 49 playerUri: string 50 isGif?: boolean 51 source: EmbedPlayerSource 52 metaUri?: string 53 hideDetails?: boolean 54} 55 56const giphyRegex = /media(?:[0-4]\.giphy\.com|\.giphy\.com)/i 57const gifFilenameRegex = /^(\S+)\.(webp|gif|mp4)$/i 58 59export function parseEmbedPlayerFromUrl( 60 url: string, 61): EmbedPlayerParams | undefined { 62 let urlp 63 try { 64 urlp = new URL(url) 65 } catch (e) { 66 return undefined 67 } 68 69 // youtube 70 if (urlp.hostname === 'youtu.be') { 71 const videoId = urlp.pathname.split('/')[1] 72 const seek = encodeURIComponent(urlp.searchParams.get('t') ?? 0) 73 if (videoId) { 74 return { 75 type: 'youtube_video', 76 source: 'youtube', 77 playerUri: `https://bsky.app/iframe/youtube.html?videoId=${videoId}&start=${seek}`, 78 } 79 } 80 } 81 if ( 82 urlp.hostname === 'www.youtube.com' || 83 urlp.hostname === 'youtube.com' || 84 urlp.hostname === 'm.youtube.com' 85 ) { 86 const [_, page, shortVideoId] = urlp.pathname.split('/') 87 const videoId = 88 page === 'shorts' ? shortVideoId : (urlp.searchParams.get('v') as string) 89 const seek = encodeURIComponent(urlp.searchParams.get('t') ?? 0) 90 91 if (videoId) { 92 return { 93 type: page === 'shorts' ? 'youtube_short' : 'youtube_video', 94 source: page === 'shorts' ? 'youtubeShorts' : 'youtube', 95 hideDetails: page === 'shorts' ? true : undefined, 96 playerUri: `https://bsky.app/iframe/youtube.html?videoId=${videoId}&start=${seek}`, 97 } 98 } 99 } 100 101 // twitch 102 if ( 103 urlp.hostname === 'twitch.tv' || 104 urlp.hostname === 'www.twitch.tv' || 105 urlp.hostname === 'm.twitch.tv' 106 ) { 107 const parent = isWeb 108 ? // @ts-ignore only for web 109 window.location.hostname 110 : 'localhost' 111 112 const [_, channelOrVideo, clipOrId, id] = urlp.pathname.split('/') 113 114 if (channelOrVideo === 'videos') { 115 return { 116 type: 'twitch_video', 117 source: 'twitch', 118 playerUri: `https://player.twitch.tv/?volume=0.5&!muted&autoplay&video=${clipOrId}&parent=${parent}`, 119 } 120 } else if (clipOrId === 'clip') { 121 return { 122 type: 'twitch_video', 123 source: 'twitch', 124 playerUri: `https://clips.twitch.tv/embed?volume=0.5&autoplay=true&clip=${id}&parent=${parent}`, 125 } 126 } else if (channelOrVideo) { 127 return { 128 type: 'twitch_video', 129 source: 'twitch', 130 playerUri: `https://player.twitch.tv/?volume=0.5&!muted&autoplay&channel=${channelOrVideo}&parent=${parent}`, 131 } 132 } 133 } 134 135 // spotify 136 if (urlp.hostname === 'open.spotify.com') { 137 const [_, typeOrLocale, idOrType, id] = urlp.pathname.split('/') 138 139 if (idOrType) { 140 if (typeOrLocale === 'playlist' || idOrType === 'playlist') { 141 return { 142 type: 'spotify_playlist', 143 source: 'spotify', 144 playerUri: `https://open.spotify.com/embed/playlist/${ 145 id ?? idOrType 146 }`, 147 } 148 } 149 if (typeOrLocale === 'album' || idOrType === 'album') { 150 return { 151 type: 'spotify_album', 152 source: 'spotify', 153 playerUri: `https://open.spotify.com/embed/album/${id ?? idOrType}`, 154 } 155 } 156 if (typeOrLocale === 'track' || idOrType === 'track') { 157 return { 158 type: 'spotify_song', 159 source: 'spotify', 160 playerUri: `https://open.spotify.com/embed/track/${id ?? idOrType}`, 161 } 162 } 163 } 164 } 165 166 // soundcloud 167 if ( 168 urlp.hostname === 'soundcloud.com' || 169 urlp.hostname === 'www.soundcloud.com' 170 ) { 171 const [_, user, trackOrSets, set] = urlp.pathname.split('/') 172 173 if (user && trackOrSets) { 174 if (trackOrSets === 'sets' && set) { 175 return { 176 type: 'soundcloud_set', 177 source: 'soundcloud', 178 playerUri: `https://w.soundcloud.com/player/?url=${url}&auto_play=true&visual=false&hide_related=true`, 179 } 180 } 181 182 return { 183 type: 'soundcloud_track', 184 source: 'soundcloud', 185 playerUri: `https://w.soundcloud.com/player/?url=${url}&auto_play=true&visual=false&hide_related=true`, 186 } 187 } 188 } 189 190 if ( 191 urlp.hostname === 'music.apple.com' || 192 urlp.hostname === 'music.apple.com' 193 ) { 194 // This should always have: locale, type (playlist or album), name, and id. We won't use spread since we want 195 // to check if the length is correct 196 const pathParams = urlp.pathname.split('/') 197 const type = pathParams[2] 198 const songId = urlp.searchParams.get('i') 199 200 if (pathParams.length === 5 && (type === 'playlist' || type === 'album')) { 201 // We want to append the songId to the end of the url if it exists 202 const embedUri = `https://embed.music.apple.com${urlp.pathname}${ 203 urlp.search ? '?i=' + songId : '' 204 }` 205 206 if (type === 'playlist') { 207 return { 208 type: 'apple_music_playlist', 209 source: 'appleMusic', 210 playerUri: embedUri, 211 } 212 } else if (type === 'album') { 213 if (songId) { 214 return { 215 type: 'apple_music_song', 216 source: 'appleMusic', 217 playerUri: embedUri, 218 } 219 } else { 220 return { 221 type: 'apple_music_album', 222 source: 'appleMusic', 223 playerUri: embedUri, 224 } 225 } 226 } 227 } 228 } 229 230 if (urlp.hostname === 'vimeo.com' || urlp.hostname === 'www.vimeo.com') { 231 const [_, videoId] = urlp.pathname.split('/') 232 if (videoId) { 233 return { 234 type: 'vimeo_video', 235 source: 'vimeo', 236 playerUri: `https://player.vimeo.com/video/${videoId}?autoplay=1`, 237 } 238 } 239 } 240 241 if (urlp.hostname === 'giphy.com' || urlp.hostname === 'www.giphy.com') { 242 const [_, gifs, nameAndId] = urlp.pathname.split('/') 243 244 /* 245 * nameAndId is a string that consists of the name (dash separated) and the id of the gif (the last part of the name) 246 * We want to get the id of the gif, then direct to media.giphy.com/media/{id}/giphy.webp so we can 247 * use it in an <Image> component 248 */ 249 250 if (gifs === 'gifs' && nameAndId) { 251 const gifId = nameAndId.split('-').pop() 252 253 if (gifId) { 254 return { 255 type: 'giphy_gif', 256 source: 'giphy', 257 isGif: true, 258 hideDetails: true, 259 metaUri: `https://giphy.com/gifs/${gifId}`, 260 playerUri: `https://i.giphy.com/media/${gifId}/giphy.webp`, 261 } 262 } 263 } 264 } 265 266 // There are five possible hostnames that also can be giphy urls: media.giphy.com and media0-4.giphy.com 267 // These can include (presumably) a tracking id in the path name, so we have to check for that as well 268 if (giphyRegex.test(urlp.hostname)) { 269 // We can link directly to the gif, if its a proper link 270 const [_, media, trackingOrId, idOrFilename, filename] = 271 urlp.pathname.split('/') 272 273 if (media === 'media') { 274 if (idOrFilename && gifFilenameRegex.test(idOrFilename)) { 275 return { 276 type: 'giphy_gif', 277 source: 'giphy', 278 isGif: true, 279 hideDetails: true, 280 metaUri: `https://giphy.com/gifs/${trackingOrId}`, 281 playerUri: `https://i.giphy.com/media/${trackingOrId}/giphy.webp`, 282 } 283 } else if (filename && gifFilenameRegex.test(filename)) { 284 return { 285 type: 'giphy_gif', 286 source: 'giphy', 287 isGif: true, 288 hideDetails: true, 289 metaUri: `https://giphy.com/gifs/${idOrFilename}`, 290 playerUri: `https://i.giphy.com/media/${idOrFilename}/giphy.webp`, 291 } 292 } 293 } 294 } 295 296 // Finally, we should see if it is a link to i.giphy.com. These links don't necessarily end in .gif but can also 297 // be .webp 298 if (urlp.hostname === 'i.giphy.com' || urlp.hostname === 'www.i.giphy.com') { 299 const [_, mediaOrFilename, filename] = urlp.pathname.split('/') 300 301 if (mediaOrFilename === 'media' && filename) { 302 const gifId = filename.split('.')[0] 303 return { 304 type: 'giphy_gif', 305 source: 'giphy', 306 isGif: true, 307 hideDetails: true, 308 metaUri: `https://giphy.com/gifs/${gifId}`, 309 playerUri: `https://i.giphy.com/media/${gifId}/giphy.webp`, 310 } 311 } else if (mediaOrFilename) { 312 const gifId = mediaOrFilename.split('.')[0] 313 return { 314 type: 'giphy_gif', 315 source: 'giphy', 316 isGif: true, 317 hideDetails: true, 318 metaUri: `https://giphy.com/gifs/${gifId}`, 319 playerUri: `https://i.giphy.com/media/${ 320 mediaOrFilename.split('.')[0] 321 }/giphy.webp`, 322 } 323 } 324 } 325 326 if (urlp.hostname === 'tenor.com' || urlp.hostname === 'www.tenor.com') { 327 const [_, pathOrIntl, pathOrFilename, intlFilename] = 328 urlp.pathname.split('/') 329 const isIntl = pathOrFilename === 'view' 330 const filename = isIntl ? intlFilename : pathOrFilename 331 332 if ((pathOrIntl === 'view' || pathOrFilename === 'view') && filename) { 333 const includesExt = filename.split('.').pop() === 'gif' 334 335 return { 336 type: 'tenor_gif', 337 source: 'tenor', 338 isGif: true, 339 hideDetails: true, 340 playerUri: `${url}${!includesExt ? '.gif' : ''}`, 341 } 342 } 343 } 344} 345 346export function getPlayerAspect({ 347 type, 348 hasThumb, 349 width, 350}: { 351 type: EmbedPlayerParams['type'] 352 hasThumb: boolean 353 width: number 354}): {aspectRatio?: number; height?: number} { 355 if (!hasThumb) return {aspectRatio: 16 / 9} 356 357 switch (type) { 358 case 'youtube_video': 359 case 'twitch_video': 360 case 'vimeo_video': 361 return {aspectRatio: 16 / 9} 362 case 'youtube_short': 363 if (SCREEN_HEIGHT < 600) { 364 return {aspectRatio: (9 / 16) * 1.75} 365 } else { 366 return {aspectRatio: (9 / 16) * 1.5} 367 } 368 case 'spotify_album': 369 case 'apple_music_album': 370 case 'apple_music_playlist': 371 case 'spotify_playlist': 372 case 'soundcloud_set': 373 return {height: 380} 374 case 'spotify_song': 375 if (width <= 300) { 376 return {height: 155} 377 } 378 return {height: 232} 379 case 'soundcloud_track': 380 return {height: 165} 381 case 'apple_music_song': 382 return {height: 150} 383 default: 384 return {aspectRatio: 16 / 9} 385 } 386} 387 388export function getGifDims( 389 originalHeight: number, 390 originalWidth: number, 391 viewWidth: number, 392) { 393 const scaledHeight = (originalHeight / originalWidth) * viewWidth 394 395 return { 396 height: scaledHeight > 250 ? 250 : scaledHeight, 397 width: (250 / scaledHeight) * viewWidth, 398 } 399} 400 401export function getGiphyMetaUri(url: URL) { 402 if (giphyRegex.test(url.hostname) || url.hostname === 'i.giphy.com') { 403 const params = parseEmbedPlayerFromUrl(url.toString()) 404 if (params && params.type === 'giphy_gif') { 405 return params.metaUri 406 } 407 } 408}