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