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)),