about summary refs log tree commit diff
path: root/src/lib/strings/embed-player.ts
diff options
context:
space:
mode:
authorHailey <153161762+haileyok@users.noreply.github.com>2023-12-21 14:33:46 -0800
committerGitHub <noreply@github.com>2023-12-21 14:33:46 -0800
commitfedb94dd70903ba5b653bd7fc76800ddb1f2bc4d (patch)
tree5d52c8b5cc4eddf023f56d7b74695c48e7cbe8be /src/lib/strings/embed-player.ts
parent7ab188dc1f316599ad6f5ecf5d15231c03547fa8 (diff)
downloadvoidsky-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/embed-player.ts')
-rw-r--r--src/lib/strings/embed-player.ts147
1 files changed, 147 insertions, 0 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
+  }
+}