diff options
author | Hailey <153161762+haileyok@users.noreply.github.com> | 2023-12-21 14:33:46 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-12-21 14:33:46 -0800 |
commit | fedb94dd70903ba5b653bd7fc76800ddb1f2bc4d (patch) | |
tree | 5d52c8b5cc4eddf023f56d7b74695c48e7cbe8be /src/lib/strings | |
parent | 7ab188dc1f316599ad6f5ecf5d15231c03547fa8 (diff) | |
download | voidsky-fedb94dd70903ba5b653bd7fc76800ddb1f2bc4d.tar.zst |
3rd party embed player (#2217)
* Implement embed player for YT, spotify, and twitch * fix: handle blur event * fix: use video dimensions for twitch * fix: remove hack (?) * fix: remove origin whitelist (?) * fix: prevent ads from opening in browser * fix: handle embeds that don't have a thumb * feat: handle dark/light mode * fix: ts warning * fix: adjust height of no-thumb label * fix: adjust height of no-thumb label * fix: remove debug log, set collapsable to false for player view * fix: fix dimensions "flash" * chore: remove old youtube link test * tests: add tests * fix: thumbless embed position when loading * fix: remove background from webview * cleanup embeds (almost) * more refactoring - Use separate layers for player and overlay to prevent weird sizing issues - Be sure the image is not visible under the player - Clean up some * cleanup styles * parse youtube shorts urls * remove debug * add soundcloud tracks and sets (playlists) * move logic into `ExternalLinkEmbed` * border radius for yt player on native * fix styling on web * allow scrolling in webview on android * remove unnecessary check * autoplay yt on web * fix tests after adding autoplay * move `useNavigation` to top of component --------- Co-authored-by: Paul Frazee <pfrazee@gmail.com>
Diffstat (limited to 'src/lib/strings')
-rw-r--r-- | src/lib/strings/embed-player.ts | 147 | ||||
-rw-r--r-- | src/lib/strings/url-helpers.ts | 29 |
2 files changed, 147 insertions, 29 deletions
diff --git a/src/lib/strings/embed-player.ts b/src/lib/strings/embed-player.ts new file mode 100644 index 000000000..fdd39280e --- /dev/null +++ b/src/lib/strings/embed-player.ts @@ -0,0 +1,147 @@ +export type EmbedPlayerParams = + | {type: 'youtube_video'; videoId: string; playerUri: string} + | {type: 'twitch_live'; channelId: string; playerUri: string} + | {type: 'spotify_album'; albumId: string; playerUri: string} + | { + type: 'spotify_playlist' + playlistId: string + playerUri: string + } + | {type: 'spotify_song'; songId: string; playerUri: string} + | {type: 'soundcloud_track'; user: string; track: string; playerUri: string} + | {type: 'soundcloud_set'; user: string; set: string; playerUri: string} + +export function parseEmbedPlayerFromUrl( + url: string, +): EmbedPlayerParams | undefined { + let urlp + try { + urlp = new URL(url) + } catch (e) { + return undefined + } + + // youtube + if (urlp.hostname === 'youtu.be') { + const videoId = urlp.pathname.split('/')[1] + if (videoId) { + return { + type: 'youtube_video', + videoId, + playerUri: `https://www.youtube.com/embed/${videoId}?autoplay=1`, + } + } + } + if (urlp.hostname === 'www.youtube.com' || urlp.hostname === 'youtube.com') { + const [_, page, shortVideoId] = urlp.pathname.split('/') + const videoId = + page === 'shorts' ? shortVideoId : (urlp.searchParams.get('v') as string) + + if (videoId) { + return { + type: 'youtube_video', + videoId, + playerUri: `https://www.youtube.com/embed/${videoId}?autoplay=1`, + } + } + } + + // twitch + if (urlp.hostname === 'twitch.tv' || urlp.hostname === 'www.twitch.tv') { + const parts = urlp.pathname.split('/') + if (parts.length === 2 && parts[1]) { + return { + type: 'twitch_live', + channelId: parts[1], + playerUri: `https://player.twitch.tv/?volume=0.5&!muted&autoplay&channel=${parts[1]}&parent=localhost`, + } + } + } + + // spotify + if (urlp.hostname === 'open.spotify.com') { + const [_, type, id] = urlp.pathname.split('/') + if (type && id) { + if (type === 'playlist') { + return { + type: 'spotify_playlist', + playlistId: id, + playerUri: `https://open.spotify.com/embed/playlist/${id}`, + } + } + if (type === 'album') { + return { + type: 'spotify_album', + albumId: id, + playerUri: `https://open.spotify.com/embed/album/${id}`, + } + } + if (type === 'track') { + return { + type: 'spotify_song', + songId: id, + playerUri: `https://open.spotify.com/embed/track/${id}`, + } + } + } + } + + // soundcloud + if ( + urlp.hostname === 'soundcloud.com' || + urlp.hostname === 'www.soundcloud.com' + ) { + const [_, user, trackOrSets, set] = urlp.pathname.split('/') + + if (user && trackOrSets) { + if (trackOrSets === 'sets' && set) { + return { + type: 'soundcloud_set', + user, + set: set, + playerUri: `https://w.soundcloud.com/player/?url=${url}&auto_play=true&visual=false&hide_related=true`, + } + } + + return { + type: 'soundcloud_track', + user, + track: trackOrSets, + playerUri: `https://w.soundcloud.com/player/?url=${url}&auto_play=true&visual=false&hide_related=true`, + } + } + } +} + +export function getPlayerHeight({ + type, + width, + hasThumb, +}: { + type: EmbedPlayerParams['type'] + width: number + hasThumb: boolean +}) { + if (!hasThumb) return (width / 16) * 9 + + switch (type) { + case 'youtube_video': + case 'twitch_live': + return (width / 16) * 9 + case 'spotify_album': + return 380 + case 'spotify_playlist': + return 360 + case 'spotify_song': + if (width <= 300) { + return 180 + } + return 232 + case 'soundcloud_track': + return 165 + case 'soundcloud_set': + return 360 + default: + return width + } +} diff --git a/src/lib/strings/url-helpers.ts b/src/lib/strings/url-helpers.ts index e9bf4111d..8a71718c8 100644 --- a/src/lib/strings/url-helpers.ts +++ b/src/lib/strings/url-helpers.ts @@ -139,35 +139,6 @@ export function feedUriToHref(url: string): string { } } -export function getYoutubeVideoId(link: string): string | undefined { - let url - try { - url = new URL(link) - } catch (e) { - return undefined - } - - if ( - url.hostname !== 'www.youtube.com' && - url.hostname !== 'youtube.com' && - url.hostname !== 'youtu.be' - ) { - return undefined - } - if (url.hostname === 'youtu.be') { - const videoId = url.pathname.split('/')[1] - if (!videoId) { - return undefined - } - return videoId - } - const videoId = url.searchParams.get('v') as string - if (!videoId) { - return undefined - } - return videoId -} - /** * Checks if the label in the post text matches the host of the link facet. * |