mirror of
https://github.com/streamyfin/streamyfin.git
synced 2025-08-20 18:37:18 +02:00
fix: move from react-native-video -> VLC for transcoded streams (#529)
Co-authored-by: Alex Kim <alexkim5682@gmail.com>
This commit is contained in:
committed by
GitHub
parent
ecb9b90163
commit
4c14c08b35
@@ -1,134 +0,0 @@
|
||||
import { TranscodedSubtitle } from "@/components/video-player/controls/types";
|
||||
import { TrackInfo } from "@/modules/vlc-player";
|
||||
import { MediaStream } from "@jellyfin/sdk/lib/generated-client";
|
||||
import { Platform } from "react-native";
|
||||
|
||||
const disableSubtitle = {
|
||||
name: "Disable",
|
||||
index: -1,
|
||||
IsTextSubtitleStream: true,
|
||||
} as TranscodedSubtitle;
|
||||
|
||||
export class SubtitleHelper {
|
||||
private mediaStreams: MediaStream[];
|
||||
|
||||
constructor(mediaStreams: MediaStream[]) {
|
||||
this.mediaStreams = mediaStreams.filter((x) => x.Type === "Subtitle");
|
||||
}
|
||||
|
||||
getSubtitles(): MediaStream[] {
|
||||
return this.mediaStreams;
|
||||
}
|
||||
|
||||
getUniqueSubtitles(): MediaStream[] {
|
||||
const uniqueSubs: MediaStream[] = [];
|
||||
const seen = new Set<string>();
|
||||
|
||||
this.mediaStreams.forEach((x) => {
|
||||
if (!seen.has(x.DisplayTitle!)) {
|
||||
seen.add(x.DisplayTitle!);
|
||||
uniqueSubs.push(x);
|
||||
}
|
||||
});
|
||||
|
||||
return uniqueSubs;
|
||||
}
|
||||
|
||||
getCurrentSubtitle(subtitleIndex?: number): MediaStream | undefined {
|
||||
return this.mediaStreams.find((x) => x.Index === subtitleIndex);
|
||||
}
|
||||
|
||||
getMostCommonSubtitleByName(
|
||||
subtitleIndex: number | undefined
|
||||
): number | undefined {
|
||||
if (subtitleIndex === undefined) -1;
|
||||
const uniqueSubs = this.getUniqueSubtitles();
|
||||
const currentSub = this.getCurrentSubtitle(subtitleIndex);
|
||||
|
||||
return uniqueSubs.find((x) => x.DisplayTitle === currentSub?.DisplayTitle)
|
||||
?.Index;
|
||||
}
|
||||
|
||||
getTextSubtitles(): MediaStream[] {
|
||||
return this.mediaStreams.filter((x) => x.IsTextSubtitleStream);
|
||||
}
|
||||
|
||||
getImageSubtitles(): MediaStream[] {
|
||||
return this.mediaStreams.filter((x) => !x.IsTextSubtitleStream);
|
||||
}
|
||||
|
||||
getEmbeddedTrackIndex(sourceSubtitleIndex: number): number {
|
||||
if (Platform.OS === "android") {
|
||||
const textSubs = this.getTextSubtitles();
|
||||
const matchingSubtitle = textSubs.find(
|
||||
(sub) => sub.Index === sourceSubtitleIndex
|
||||
);
|
||||
|
||||
if (!matchingSubtitle) return -1;
|
||||
return textSubs.indexOf(matchingSubtitle);
|
||||
}
|
||||
|
||||
// Get unique text-based subtitles because react-native-video removes hls text tracks duplicates. (iOS)
|
||||
const uniqueTextSubs = this.getUniqueTextBasedSubtitles();
|
||||
const matchingSubtitle = uniqueTextSubs.find(
|
||||
(sub) => sub.Index === sourceSubtitleIndex
|
||||
);
|
||||
|
||||
if (!matchingSubtitle) return -1;
|
||||
return uniqueTextSubs.indexOf(matchingSubtitle);
|
||||
}
|
||||
|
||||
sortSubtitles(
|
||||
textSubs: TranscodedSubtitle[],
|
||||
allSubs: MediaStream[]
|
||||
): TranscodedSubtitle[] {
|
||||
let textIndex = 0; // To track position in textSubtitles
|
||||
// Merge text and image subtitles in the order of allSubs
|
||||
const sortedSubtitles = allSubs.map((sub) => {
|
||||
if (sub.IsTextSubtitleStream) {
|
||||
if (textSubs.length === 0) return disableSubtitle;
|
||||
const textSubtitle = textSubs[textIndex];
|
||||
if (!textSubtitle) return disableSubtitle;
|
||||
textIndex++;
|
||||
return textSubtitle;
|
||||
} else {
|
||||
return {
|
||||
name: sub.DisplayTitle!,
|
||||
index: sub.Index!,
|
||||
IsTextSubtitleStream: sub.IsTextSubtitleStream,
|
||||
} as TranscodedSubtitle;
|
||||
}
|
||||
});
|
||||
|
||||
return sortedSubtitles;
|
||||
}
|
||||
|
||||
getSortedSubtitles(subtitleTracks: TrackInfo[]): TranscodedSubtitle[] {
|
||||
const textSubtitles =
|
||||
subtitleTracks.map((s) => ({
|
||||
name: s.name,
|
||||
index: s.index,
|
||||
IsTextSubtitleStream: true,
|
||||
})) || [];
|
||||
|
||||
const sortedSubs =
|
||||
Platform.OS === "android"
|
||||
? this.sortSubtitles(textSubtitles, this.mediaStreams)
|
||||
: this.sortSubtitles(textSubtitles, this.getUniqueSubtitles());
|
||||
|
||||
return sortedSubs;
|
||||
}
|
||||
|
||||
getUniqueTextBasedSubtitles(): MediaStream[] {
|
||||
return this.getUniqueSubtitles().filter((x) => x.IsTextSubtitleStream);
|
||||
}
|
||||
|
||||
// HLS stream indexes are not the same as the actual source indexes.
|
||||
// This function aims to get the source subtitle index from the embedded track index.
|
||||
getSourceSubtitleIndex = (embeddedTrackIndex: number): number => {
|
||||
if (Platform.OS === "android") {
|
||||
return this.getTextSubtitles()[embeddedTrackIndex]?.Index ?? -1;
|
||||
}
|
||||
return this.getUniqueTextBasedSubtitles()[embeddedTrackIndex]?.Index ?? -1;
|
||||
};
|
||||
}
|
||||
@@ -111,15 +111,6 @@ export const getStreamUrl = async ({
|
||||
if (mediaSource?.TranscodingUrl) {
|
||||
const urlObj = new URL(api.basePath + mediaSource?.TranscodingUrl); // Create a URL object
|
||||
|
||||
// If there is no subtitle stream index, add it to the URL.
|
||||
if (subtitleStreamIndex == -1) {
|
||||
urlObj.searchParams.set("SubtitleMethod", "Hls");
|
||||
}
|
||||
|
||||
// Add 'SubtitleMethod=Hls' if it doesn't exist
|
||||
if (!urlObj.searchParams.has("SubtitleMethod")) {
|
||||
urlObj.searchParams.append("SubtitleMethod", "Hls");
|
||||
}
|
||||
// Get the updated URL
|
||||
const transcodeUrl = urlObj.toString();
|
||||
|
||||
|
||||
@@ -42,11 +42,9 @@ export default {
|
||||
Type: MediaTypes.Video,
|
||||
Context: "Streaming",
|
||||
Protocol: "hls",
|
||||
Container: "ts",
|
||||
Container: "fmp4",
|
||||
VideoCodec: "h264, hevc",
|
||||
AudioCodec: "aac,mp3,ac3",
|
||||
CopyTimestamps: false,
|
||||
EnableSubtitlesInManifest: true,
|
||||
AudioCodec: "aac,mp3,ac3,dts",
|
||||
},
|
||||
{
|
||||
Type: MediaTypes.Audio,
|
||||
@@ -58,131 +56,81 @@ export default {
|
||||
},
|
||||
],
|
||||
SubtitleProfiles: [
|
||||
// Official foramts
|
||||
// Official formats
|
||||
{ Format: "vtt", Method: "Embed" },
|
||||
{ Format: "vtt", Method: "Hls" },
|
||||
{ Format: "vtt", Method: "External" },
|
||||
{ Format: "vtt", Method: "Encode" },
|
||||
|
||||
{ Format: "webvtt", Method: "Embed" },
|
||||
{ Format: "webvtt", Method: "Hls" },
|
||||
{ Format: "webvtt", Method: "External" },
|
||||
{ Format: "webvtt", Method: "Encode" },
|
||||
|
||||
{ Format: "srt", Method: "Embed" },
|
||||
{ Format: "srt", Method: "Hls" },
|
||||
{ Format: "srt", Method: "External" },
|
||||
{ Format: "srt", Method: "Encode" },
|
||||
|
||||
{ Format: "subrip", Method: "Embed" },
|
||||
{ Format: "subrip", Method: "Hls" },
|
||||
{ Format: "subrip", Method: "External" },
|
||||
{ Format: "subrip", Method: "Encode" },
|
||||
|
||||
{ Format: "ttml", Method: "Embed" },
|
||||
{ Format: "ttml", Method: "Hls" },
|
||||
{ Format: "ttml", Method: "External" },
|
||||
{ Format: "ttml", Method: "Encode" },
|
||||
|
||||
{ Format: "dvbsub", Method: "Embed" },
|
||||
{ Format: "dvbsub", Method: "Hls" },
|
||||
{ Format: "dvbsub", Method: "External" },
|
||||
{ Format: "dvdsub", Method: "Encode" },
|
||||
|
||||
{ Format: "ass", Method: "Embed" },
|
||||
{ Format: "ass", Method: "Hls" },
|
||||
{ Format: "ass", Method: "External" },
|
||||
{ Format: "ass", Method: "Encode" },
|
||||
|
||||
{ Format: "idx", Method: "Embed" },
|
||||
{ Format: "idx", Method: "Hls" },
|
||||
{ Format: "idx", Method: "External" },
|
||||
{ Format: "idx", Method: "Encode" },
|
||||
|
||||
{ Format: "pgs", Method: "Embed" },
|
||||
{ Format: "pgs", Method: "Hls" },
|
||||
{ Format: "pgs", Method: "External" },
|
||||
{ Format: "pgs", Method: "Encode" },
|
||||
|
||||
{ Format: "pgssub", Method: "Embed" },
|
||||
{ Format: "pgssub", Method: "Hls" },
|
||||
{ Format: "pgssub", Method: "External" },
|
||||
{ Format: "pgssub", Method: "Encode" },
|
||||
|
||||
{ Format: "ssa", Method: "Embed" },
|
||||
{ Format: "ssa", Method: "Hls" },
|
||||
{ Format: "ssa", Method: "External" },
|
||||
{ Format: "ssa", Method: "Encode" },
|
||||
|
||||
// Other formats
|
||||
{ Format: "microdvd", Method: "Embed" },
|
||||
{ Format: "microdvd", Method: "Hls" },
|
||||
{ Format: "microdvd", Method: "External" },
|
||||
{ Format: "microdvd", Method: "Encode" },
|
||||
|
||||
{ Format: "mov_text", Method: "Embed" },
|
||||
{ Format: "mov_text", Method: "Hls" },
|
||||
{ Format: "mov_text", Method: "External" },
|
||||
{ Format: "mov_text", Method: "Encode" },
|
||||
|
||||
{ Format: "mpl2", Method: "Embed" },
|
||||
{ Format: "mpl2", Method: "Hls" },
|
||||
{ Format: "mpl2", Method: "External" },
|
||||
{ Format: "mpl2", Method: "Encode" },
|
||||
|
||||
{ Format: "pjs", Method: "Embed" },
|
||||
{ Format: "pjs", Method: "Hls" },
|
||||
{ Format: "pjs", Method: "External" },
|
||||
{ Format: "pjs", Method: "Encode" },
|
||||
|
||||
{ Format: "realtext", Method: "Embed" },
|
||||
{ Format: "realtext", Method: "Hls" },
|
||||
{ Format: "realtext", Method: "External" },
|
||||
{ Format: "realtext", Method: "Encode" },
|
||||
|
||||
{ Format: "scc", Method: "Embed" },
|
||||
{ Format: "scc", Method: "Hls" },
|
||||
{ Format: "scc", Method: "External" },
|
||||
{ Format: "scc", Method: "Encode" },
|
||||
|
||||
{ Format: "smi", Method: "Embed" },
|
||||
{ Format: "smi", Method: "Hls" },
|
||||
{ Format: "smi", Method: "External" },
|
||||
{ Format: "smi", Method: "Encode" },
|
||||
|
||||
{ Format: "stl", Method: "Embed" },
|
||||
{ Format: "stl", Method: "Hls" },
|
||||
{ Format: "stl", Method: "External" },
|
||||
{ Format: "stl", Method: "Encode" },
|
||||
|
||||
{ Format: "sub", Method: "Embed" },
|
||||
{ Format: "sub", Method: "Hls" },
|
||||
{ Format: "sub", Method: "External" },
|
||||
{ Format: "sub", Method: "Encode" },
|
||||
|
||||
{ Format: "subviewer", Method: "Embed" },
|
||||
{ Format: "subviewer", Method: "Hls" },
|
||||
{ Format: "subviewer", Method: "External" },
|
||||
{ Format: "subviewer", Method: "Encode" },
|
||||
|
||||
{ Format: "teletext", Method: "Embed" },
|
||||
{ Format: "teletext", Method: "Hls" },
|
||||
{ Format: "teletext", Method: "External" },
|
||||
{ Format: "teletext", Method: "Encode" },
|
||||
|
||||
{ Format: "text", Method: "Embed" },
|
||||
{ Format: "text", Method: "Hls" },
|
||||
{ Format: "text", Method: "External" },
|
||||
{ Format: "text", Method: "Encode" },
|
||||
|
||||
{ Format: "vplayer", Method: "Embed" },
|
||||
{ Format: "vplayer", Method: "Hls" },
|
||||
{ Format: "vplayer", Method: "External" },
|
||||
{ Format: "vplayer", Method: "Encode" },
|
||||
|
||||
{ Format: "xsub", Method: "Embed" },
|
||||
{ Format: "xsub", Method: "Hls" },
|
||||
{ Format: "xsub", Method: "External" },
|
||||
{ Format: "xsub", Method: "Encode" },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import MediaTypes from "../../constants/MediaTypes";
|
||||
|
||||
export default {
|
||||
Name: "Vlc Player for HLS streams.",
|
||||
MaxStaticBitrate: 20_000_000,
|
||||
MaxStreamingBitrate: 12_000_000,
|
||||
CodecProfiles: [
|
||||
{
|
||||
Type: MediaTypes.Video,
|
||||
Codec: "h264,h265,hevc,mpeg4,divx,xvid,wmv,vc1,vp8,vp9,av1",
|
||||
},
|
||||
{
|
||||
Type: MediaTypes.Audio,
|
||||
Codec: "aac,ac3,eac3,mp3,flac,alac,opus,vorbis,pcm,wma",
|
||||
},
|
||||
],
|
||||
DirectPlayProfiles: [
|
||||
{
|
||||
Type: MediaTypes.Video,
|
||||
Container: "mp4,mkv,avi,mov,flv,ts,m2ts,webm,ogv,3gp,hls",
|
||||
VideoCodec:
|
||||
"h264,hevc,mpeg4,divx,xvid,wmv,vc1,vp8,vp9,av1,avi,mpeg,mpeg2video",
|
||||
AudioCodec: "aac,ac3,eac3,mp3,flac,alac,opus,vorbis,wma",
|
||||
},
|
||||
{
|
||||
Type: MediaTypes.Audio,
|
||||
Container: "mp3,aac,flac,alac,wav,ogg,wma",
|
||||
AudioCodec:
|
||||
"mp3,aac,flac,alac,opus,vorbis,wma,pcm,mpa,wav,ogg,oga,webma,ape",
|
||||
},
|
||||
],
|
||||
TranscodingProfiles: [
|
||||
{
|
||||
Type: MediaTypes.Video,
|
||||
Context: "Streaming",
|
||||
Protocol: "hls",
|
||||
Container: "fmp4",
|
||||
VideoCodec: "h264, hevc",
|
||||
AudioCodec: "aac,mp3,ac3",
|
||||
CopyTimestamps: false,
|
||||
EnableSubtitlesInManifest: true,
|
||||
},
|
||||
{
|
||||
Type: MediaTypes.Audio,
|
||||
Context: "Streaming",
|
||||
Protocol: "http",
|
||||
Container: "mp3",
|
||||
AudioCodec: "mp3",
|
||||
MaxAudioChannels: "2",
|
||||
},
|
||||
],
|
||||
SubtitleProfiles: [
|
||||
// Text based subtitles must use HLS.
|
||||
{ Format: "ass", Method: "Hls" },
|
||||
{ Format: "microdvd", Method: "Hls" },
|
||||
{ Format: "mov_text", Method: "Hls" },
|
||||
{ Format: "mpl2", Method: "Hls" },
|
||||
{ Format: "pjs", Method: "Hls" },
|
||||
{ Format: "realtext", Method: "Hls" },
|
||||
{ Format: "scc", Method: "Hls" },
|
||||
{ Format: "smi", Method: "Hls" },
|
||||
{ Format: "srt", Method: "Hls" },
|
||||
{ Format: "ssa", Method: "Hls" },
|
||||
{ Format: "stl", Method: "Hls" },
|
||||
{ Format: "sub", Method: "Hls" },
|
||||
{ Format: "subrip", Method: "Hls" },
|
||||
{ Format: "subviewer", Method: "Hls" },
|
||||
{ Format: "teletext", Method: "Hls" },
|
||||
{ Format: "text", Method: "Hls" },
|
||||
{ Format: "ttml", Method: "Hls" },
|
||||
{ Format: "vplayer", Method: "Hls" },
|
||||
{ Format: "vtt", Method: "Hls" },
|
||||
{ Format: "webvtt", Method: "Hls" },
|
||||
|
||||
// Image based subs use encode.
|
||||
{ Format: "dvdsub", Method: "Encode" },
|
||||
{ Format: "pgs", Method: "Encode" },
|
||||
{ Format: "pgssub", Method: "Encode" },
|
||||
{ Format: "xsub", Method: "Encode" },
|
||||
],
|
||||
};
|
||||
Reference in New Issue
Block a user