Skip to content

Commit dc771d5

Browse files
committed
Handle multiple srt cues at the same timestamp
1 parent 64bde6b commit dc771d5

3 files changed

Lines changed: 188 additions & 11 deletions

File tree

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package org.jellyfin.androidtv.ui.playback;
2+
3+
import android.content.Context;
4+
import android.graphics.Color;
5+
import android.util.TypedValue;
6+
import android.view.Gravity;
7+
import android.view.ViewGroup;
8+
import android.widget.LinearLayout;
9+
import android.widget.TextView;
10+
11+
import androidx.annotation.NonNull;
12+
import androidx.media3.common.text.Cue;
13+
import androidx.media3.common.text.CueGroup;
14+
import androidx.media3.ui.CaptionStyleCompat;
15+
16+
import org.jellyfin.androidtv.preference.UserPreferences;
17+
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
21+
import timber.log.Timber;
22+
23+
/**
24+
* Helper class to manage custom subtitle rendering with proper stacking of multiple cues.
25+
*/
26+
public class CustomSubtitleHelper {
27+
private final Context context;
28+
private final LinearLayout subtitleContainer;
29+
private final List<TextView> subtitleViews = new ArrayList<>();
30+
private CaptionStyleCompat captionStyle;
31+
private float textSize = 18f;
32+
33+
public CustomSubtitleHelper(@NonNull Context context, @NonNull LinearLayout subtitleContainer, @NonNull UserPreferences userPreferences) {
34+
this.context = context;
35+
this.subtitleContainer = subtitleContainer;
36+
37+
// Configure the subtitle style based on user preferences
38+
int strokeColor = userPreferences.get(UserPreferences.Companion.getSubtitleTextStrokeColor()).intValue();
39+
captionStyle = new CaptionStyleCompat(
40+
userPreferences.get(UserPreferences.Companion.getSubtitlesTextColor()).intValue(),
41+
userPreferences.get(UserPreferences.Companion.getSubtitlesBackgroundColor()).intValue(),
42+
Color.TRANSPARENT,
43+
Color.alpha(strokeColor) == 0 ? CaptionStyleCompat.EDGE_TYPE_NONE : CaptionStyleCompat.EDGE_TYPE_OUTLINE,
44+
strokeColor,
45+
null
46+
);
47+
48+
// Set text size based on user preferences, with a multiplier to make it larger
49+
// The multiplier (1.8) makes the text significantly larger than the default
50+
float userSizePreference = userPreferences.get(UserPreferences.Companion.getSubtitlesTextSize());
51+
textSize = 0.0533f * userSizePreference * 500;
52+
53+
Timber.d("Setting subtitle text size to %f (user preference: %f)", textSize, userSizePreference);
54+
}
55+
56+
/**
57+
* Process and display subtitle cues.
58+
* @param cueGroup The cue group containing subtitle cues
59+
*/
60+
public void onCues(CueGroup cueGroup) {
61+
List<Cue> cues = cueGroup != null ? cueGroup.cues : null;
62+
63+
if (cues == null || cues.isEmpty()) {
64+
subtitleContainer.setVisibility(ViewGroup.GONE);
65+
return;
66+
}
67+
68+
// Make sure the container is visible
69+
subtitleContainer.setVisibility(ViewGroup.VISIBLE);
70+
71+
// Clear previous subtitles
72+
subtitleContainer.removeAllViews();
73+
74+
Timber.d("Displaying %d subtitle cues", cues.size());
75+
76+
// Ensure we have enough TextView instances
77+
while (subtitleViews.size() < cues.size()) {
78+
TextView textView = new TextView(context);
79+
textView.setLayoutParams(new LinearLayout.LayoutParams(
80+
LinearLayout.LayoutParams.WRAP_CONTENT,
81+
LinearLayout.LayoutParams.WRAP_CONTENT));
82+
subtitleViews.add(textView);
83+
}
84+
85+
// Add and configure each cue
86+
for (int i = 0; i < cues.size(); i++) {
87+
Cue cue = cues.get(i);
88+
TextView cueView = subtitleViews.get(i);
89+
90+
// Set the text from the cue
91+
if (cue.text != null) {
92+
cueView.setText(cue.text);
93+
94+
// Apply styling
95+
applyTextStyle(cueView);
96+
97+
// Add to the container
98+
subtitleContainer.addView(cueView);
99+
}
100+
}
101+
}
102+
103+
/**
104+
* Apply text styling to a subtitle TextView.
105+
* @param textView The TextView to style
106+
*/
107+
private void applyTextStyle(TextView textView) {
108+
// Use a different unit for better TV display
109+
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize);
110+
textView.setTextColor(captionStyle.foregroundColor);
111+
112+
// Make text bold for better visibility on TV
113+
textView.setTypeface(textView.getTypeface(), android.graphics.Typeface.BOLD);
114+
115+
// Apply edge type (outline, drop shadow, etc.)
116+
switch (captionStyle.edgeType) {
117+
case CaptionStyleCompat.EDGE_TYPE_OUTLINE:
118+
// Increase outline thickness for better visibility
119+
textView.setShadowLayer(4, 0, 0, captionStyle.edgeColor);
120+
break;
121+
case CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW:
122+
// Increase shadow size for better visibility
123+
textView.setShadowLayer(4, 2, 2, captionStyle.edgeColor);
124+
break;
125+
case CaptionStyleCompat.EDGE_TYPE_NONE:
126+
default:
127+
textView.setShadowLayer(0, 0, 0, 0);
128+
break;
129+
}
130+
131+
// Center the text
132+
textView.setTextAlignment(TextView.TEXT_ALIGNMENT_CENTER);
133+
textView.setGravity(Gravity.CENTER);
134+
135+
// Set the background color
136+
textView.setBackgroundColor(captionStyle.backgroundColor);
137+
138+
// Add some padding
139+
int padding = (int) TypedValue.applyDimension(
140+
TypedValue.COMPLEX_UNIT_DIP,
141+
4,
142+
context.getResources().getDisplayMetrics());
143+
textView.setPadding(padding, padding, padding, padding);
144+
145+
// Add some margin between subtitle lines
146+
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) textView.getLayoutParams();
147+
params.setMargins(0, 0, 0, padding / 2);
148+
textView.setLayoutParams(params);
149+
}
150+
}

app/src/main/java/org/jellyfin/androidtv/ui/playback/VideoManager.java

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@
1111
import android.os.Handler;
1212
import android.view.View;
1313
import android.widget.FrameLayout;
14+
import android.widget.LinearLayout;
1415

1516
import androidx.annotation.NonNull;
1617
import androidx.annotation.Nullable;
1718
import androidx.annotation.OptIn;
1819
import androidx.media3.common.C;
1920
import androidx.media3.common.Format;
21+
import androidx.media3.common.text.Cue;
22+
import androidx.media3.common.text.CueGroup;
2023
import androidx.media3.common.MediaItem;
2124
import androidx.media3.common.PlaybackException;
2225
import androidx.media3.common.PlaybackParameters;
@@ -69,6 +72,8 @@ public class VideoManager {
6972
public ExoPlayer mExoPlayer;
7073
private PlayerView mExoPlayerView;
7174
private Handler mHandler = new Handler();
75+
private CustomSubtitleHelper mCustomSubtitleHelper;
76+
private LinearLayout mCustomSubtitleContainer;
7277

7378
private long mMetaDuration = -1;
7479
private long lastExoPlayerPosition = -1;
@@ -94,17 +99,25 @@ public VideoManager(@NonNull Activity activity, @NonNull View view, @NonNull Pla
9499

95100
mExoPlayerView = view.findViewById(R.id.exoPlayerView);
96101
mExoPlayerView.setPlayer(mExoPlayer);
97-
int strokeColor = userPreferences.get(UserPreferences.Companion.getSubtitleTextStrokeColor()).intValue();
98-
CaptionStyleCompat subtitleStyle = new CaptionStyleCompat(
99-
userPreferences.get(UserPreferences.Companion.getSubtitlesTextColor()).intValue(),
100-
userPreferences.get(UserPreferences.Companion.getSubtitlesBackgroundColor()).intValue(),
101-
Color.TRANSPARENT,
102-
Color.alpha(strokeColor) == 0 ? CaptionStyleCompat.EDGE_TYPE_NONE : CaptionStyleCompat.EDGE_TYPE_OUTLINE,
103-
strokeColor,
104-
null
105-
);
106-
mExoPlayerView.getSubtitleView().setFractionalTextSize(0.0533f * userPreferences.get(UserPreferences.Companion.getSubtitlesTextSize()));
107-
mExoPlayerView.getSubtitleView().setStyle(subtitleStyle);
102+
103+
// Get the custom subtitle container
104+
mCustomSubtitleContainer = view.findViewById(R.id.custom_subtitle_container);
105+
106+
// Initialize the custom subtitle helper
107+
mCustomSubtitleHelper = new CustomSubtitleHelper(mActivity, mCustomSubtitleContainer, userPreferences);
108+
109+
// Hide the default subtitle view
110+
mExoPlayerView.getSubtitleView().setVisibility(View.GONE);
111+
112+
// Add a listener for subtitle cues
113+
mExoPlayer.addListener(new Player.Listener() {
114+
@Override
115+
public void onCues(@NonNull List<Cue> cues) {
116+
// Process subtitle cues in our custom helper
117+
mCustomSubtitleHelper.onCues(new CueGroup(cues, 0));
118+
}
119+
});
120+
108121
mExoPlayer.addListener(new Player.Listener() {
109122
@Override
110123
public void onPlayerError(@NonNull PlaybackException error) {
@@ -550,6 +563,7 @@ public void destroy() {
550563
mPlaybackControllerNotifiable = null;
551564
stopPlayback();
552565
releasePlayer();
566+
mCustomSubtitleHelper = null;
553567
}
554568

555569
private void releasePlayer() {

app/src/main/res/layout/vlc_player_interface.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,19 @@
3030
android:layout_width="match_parent"
3131
android:layout_height="match_parent"
3232
app:use_controller="false" />
33+
34+
<!-- Custom subtitle container for stacked subtitles -->
35+
<LinearLayout
36+
android:id="@+id/custom_subtitle_container"
37+
android:layout_width="match_parent"
38+
android:layout_height="wrap_content"
39+
android:layout_gravity="bottom|center_horizontal"
40+
android:layout_marginBottom="50dp"
41+
android:layout_marginLeft="50dp"
42+
android:layout_marginRight="50dp"
43+
android:gravity="center"
44+
android:orientation="vertical"
45+
android:visibility="gone" />
3346

3447
<androidx.fragment.app.FragmentContainerView
3548
android:id="@+id/leanback_fragment"

0 commit comments

Comments
 (0)