diff --git a/front/bun.lock b/front/bun.lock
index db4d0a99..34b7d835 100644
--- a/front/bun.lock
+++ b/front/bun.lock
@@ -1265,7 +1265,7 @@
 
     "react-native-svg-transformer": ["react-native-svg-transformer@1.5.1", "", { "dependencies": { "@svgr/core": "^8.1.0", "@svgr/plugin-jsx": "^8.1.0", "@svgr/plugin-svgo": "^8.1.0", "path-dirname": "^1.0.2" }, "peerDependencies": { "react-native": ">=0.59.0", "react-native-svg": ">=12.0.0" } }, "sha512-dFvBNR8A9VPum9KCfh+LE49YiJEF8zUSnEFciKQroR/bEOhlPoZA0SuQ0qNk7m2iZl2w59FYjdRe0pMHWMDl0Q=="],
 
-    "react-native-video": ["react-native-video@github:zoriya/react-native-video#8287a84", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": ">=0.27.2" } }, "zoriya-react-native-video-8287a84"],
+    "react-native-video": ["react-native-video@github:zoriya/react-native-video#ad69842", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": ">=0.27.2" } }, "zoriya-react-native-video-ad69842"],
 
     "react-native-web": ["react-native-web@0.21.2", "", { "dependencies": { "@babel/runtime": "^7.18.6", "@react-native/normalize-colors": "^0.74.1", "fbjs": "^3.0.4", "inline-style-prefixer": "^7.0.1", "memoize-one": "^6.0.0", "nullthrows": "^1.1.1", "postcss-value-parser": "^4.2.0", "styleq": "^0.1.3" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg=="],
 
diff --git a/front/src/ui/player/controls/bottom-controls.tsx b/front/src/ui/player/controls/bottom-controls.tsx
index f53b21a4..ebf5d141 100644
--- a/front/src/ui/player/controls/bottom-controls.tsx
+++ b/front/src/ui/player/controls/bottom-controls.tsx
@@ -163,7 +163,7 @@ const ControlButtons = ({
 				
 			
 			
-				
+				
 				
 				
 				
diff --git a/front/src/ui/player/controls/tracks-menu.tsx b/front/src/ui/player/controls/tracks-menu.tsx
index aa1e4224..4a5c865a 100644
--- a/front/src/ui/player/controls/tracks-menu.tsx
+++ b/front/src/ui/player/controls/tracks-menu.tsx
@@ -3,19 +3,50 @@ import MusicNote from "@material-symbols/svg-400/rounded/music_note-fill.svg";
 import SettingsIcon from "@material-symbols/svg-400/rounded/settings-fill.svg";
 import VideoSettings from "@material-symbols/svg-400/rounded/video_settings-fill.svg";
 import { type ComponentProps, createContext, useContext } from "react";
-import { useEvent, type VideoPlayer } from "react-native-video";
 import { useTranslation } from "react-i18next";
-import { IconButton, Menu, tooltip } from "~/primitives";
-import { useDisplayName } from "~/track-utils";
+import { useEvent, type VideoPlayer } from "react-native-video";
 import { useForceRerender } from "yoshiki";
+import type { Subtitle } from "~/models";
+import { IconButton, Menu, tooltip } from "~/primitives";
+import { useFetch } from "~/query";
+import { useDisplayName, useSubtitleName } from "~/track-utils";
+import { useQueryState } from "~/utils";
+import { Player } from "..";
 
 type MenuProps = ComponentProps>>;
 
-export const SubtitleMenu = (props: Partial) => {
+export const SubtitleMenu = ({
+	player,
+	...props
+}: {
+	player: VideoPlayer;
+} & Partial) => {
 	const { t } = useTranslation();
+	const getDisplayName = useSubtitleName();
+
+	const rerender = useForceRerender();
+	useEvent(player, "onTrackChange", rerender);
+
+	const [slug] = useQueryState("slug", undefined!);
+	const { data } = useFetch(Player.infoQuery(slug));
+
+	if (data?.subtitles.length === 0) return null;
+
+	const selectedIdx = player
+		.getAvailableTextTracks()
+		.findIndex((x) => x.selected);
+
+	const select = (track: Subtitle | null, idx: number) => {
+		if (!track) {
+			player.selectTextTrack(null);
+			return;
+		}
+
+		// TODO: filter by codec here
+		const sub = player.getAvailableTextTracks()[idx];
+		player.selectTextTrack(sub);
+	};
 
-	// {subtitles && subtitles.length > 0 && (
-	// )}
 	return (
 		
 	);
 };
diff --git a/front/src/ui/player/index.tsx b/front/src/ui/player/index.tsx
index b1c11b9d..0146ee86 100644
--- a/front/src/ui/player/index.tsx
+++ b/front/src/ui/player/index.tsx
@@ -62,10 +62,13 @@ export const Player = () => {
 				imageUri: data?.show?.thumbnail?.high ?? undefined,
 			},
 			externalSubtitles: info?.subtitles
-				.filter((x) => x.link)
+				.filter(
+					(x) => Platform.OS === "web" || playMode === "hls" || x.isExternal,
+				)
 				.map((x) => ({
+					// we also add those without link to prevent the order from getting out of sync with `info.subtitles`.
+					// since we never actually play those this is fine
 					uri: x.link!,
-					// TODO: translate this `Unknown`
 					label: x.title ?? "Unknown",
 					language: x.language ?? "und",
 					type: x.codec,
diff --git a/transcoder/src/info.go b/transcoder/src/info.go
index 1d0a882d..5a3f2610 100644
--- a/transcoder/src/info.go
+++ b/transcoder/src/info.go
@@ -287,7 +287,7 @@ func RetriveMediaInfo(path string, sha string) (*MediaInfo, error) {
 			extension := OrNull(SubtitleExtensions[stream.CodecName])
 			var link string
 			if extension != nil {
-				link = fmt.Sprintf("video/%s/subtitle/%d.%s", base64.RawURLEncoding.EncodeToString([]byte(path)), i, *extension)
+				link = fmt.Sprintf("/video/%s/subtitle/%d.%s", base64.RawURLEncoding.EncodeToString([]byte(path)), i, *extension)
 			}
 			lang, _ := language.Parse(stream.Tags.Language)
 			idx := uint32(i)
@@ -315,7 +315,7 @@ func RetriveMediaInfo(path string, sha string) (*MediaInfo, error) {
 		}),
 		Fonts: MapStream(mi.Streams, ffprobe.StreamAttachment, func(stream *ffprobe.Stream, i uint32) string {
 			font, _ := stream.TagList.GetString("filename")
-			return fmt.Sprintf("video/%s/attachment/%s", base64.RawURLEncoding.EncodeToString([]byte(path)), font)
+			return fmt.Sprintf("/video/%s/attachment/%s", base64.RawURLEncoding.EncodeToString([]byte(path)), font)
 		}),
 	}
 	var codecs []string
diff --git a/transcoder/src/metadata.go b/transcoder/src/metadata.go
index 8230bbde..5d33875a 100644
--- a/transcoder/src/metadata.go
+++ b/transcoder/src/metadata.go
@@ -174,7 +174,7 @@ func (s *MetadataService) GetMetadata(ctx context.Context, path string, sha stri
 		tx.Exec(`update info set ver_keyframes = 0 where sha = $1`, sha)
 		err = tx.Commit()
 		if err != nil {
-			fmt.Printf("error deleteing old keyframes from database: %v", err)
+			fmt.Printf("error deleting old keyframes from database: %v", err)
 		}
 	}
 
@@ -256,7 +256,7 @@ func (s *MetadataService) getMetadata(path string, sha string) (*MediaInfo, erro
 		}
 		if s.Extension != nil {
 			link := fmt.Sprintf(
-				"video/%s/subtitle/%d.%s",
+				"/video/%s/subtitle/%d.%s",
 				base64.RawURLEncoding.EncodeToString([]byte(ret.Path)),
 				*s.Index,
 				*s.Extension,
@@ -391,5 +391,10 @@ func (s *MetadataService) storeFreshMetadata(path string, sha string) (*MediaInf
 		return set(ret, err)
 	}
 
+	err = ret.SearchExternalSubtitles()
+	if err != nil {
+		return set(ret, err)
+	}
+
 	return set(ret, nil)
 }
diff --git a/transcoder/src/subtitles.go b/transcoder/src/subtitles.go
index 3a52d58a..69fc4b63 100644
--- a/transcoder/src/subtitles.go
+++ b/transcoder/src/subtitles.go
@@ -31,7 +31,7 @@ outer:
 		for codec, ext := range SubtitleExtensions {
 			if strings.HasSuffix(match, ext) {
 				link := fmt.Sprintf(
-					"video/%s/direct/%s",
+					"/video/%s/direct/%s",
 					base64.RawURLEncoding.EncodeToString([]byte(match)),
 					filepath.Base(match),
 				)
diff --git a/transcoder/src/thumbnails.go b/transcoder/src/thumbnails.go
index 467d98a7..9e8e17eb 100644
--- a/transcoder/src/thumbnails.go
+++ b/transcoder/src/thumbnails.go
@@ -145,7 +145,7 @@ func (s *MetadataService) extractThumbnail(ctx context.Context, path string, sha
 		timestamps := ts
 		ts += interval
 		vtt += fmt.Sprintf(
-			"%s --> %s\nvideo/%s/thumbnails.png#xywh=%d,%d,%d,%d\n\n",
+			"%s --> %s\n/video/%s/thumbnails.png#xywh=%d,%d,%d,%d\n\n",
 			tsToVttTime(timestamps),
 			tsToVttTime(ts),
 			base64.RawURLEncoding.EncodeToString([]byte(path)),