Skip to content

Commit d30ff5c

Browse files
Rewrite all subtitle behavior
1 parent 7f480ea commit d30ff5c

File tree

11 files changed

+209
-189
lines changed

11 files changed

+209
-189
lines changed

app/src/main/java/org/jellyfin/androidtv/data/compat/StreamInfo.java

+9-51
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
package org.jellyfin.androidtv.data.compat;
22

3-
import org.jellyfin.androidtv.util.Utils;
43
import org.jellyfin.apiclient.model.dlna.DeviceProfile;
54
import org.jellyfin.apiclient.model.dlna.EncodingContext;
65
import org.jellyfin.apiclient.model.dlna.SubtitleDeliveryMethod;
76
import org.jellyfin.apiclient.model.dlna.SubtitleProfile;
87
import org.jellyfin.apiclient.model.dlna.TranscodeSeekInfo;
98
import org.jellyfin.apiclient.model.session.PlayMethod;
10-
import org.jellyfin.sdk.model.api.MediaProtocol;
119
import org.jellyfin.sdk.model.api.MediaSourceInfo;
1210
import org.jellyfin.sdk.model.api.MediaStream;
1311
import org.jellyfin.sdk.model.api.MediaStreamType;
@@ -66,16 +64,6 @@ public final void setContainer(String value) {
6664
Container = value;
6765
}
6866

69-
private long StartPositionTicks;
70-
71-
public final long getStartPositionTicks() {
72-
return StartPositionTicks;
73-
}
74-
75-
public final void setStartPositionTicks(long value) {
76-
StartPositionTicks = value;
77-
}
78-
7967
private DeviceProfile DeviceProfile;
8068

8169
public final DeviceProfile getDeviceProfile() {
@@ -139,59 +127,29 @@ public final ArrayList<SubtitleStreamInfo> getSubtitleProfiles(boolean includeSe
139127
public final ArrayList<SubtitleStreamInfo> getSubtitleProfiles(boolean includeSelectedTrackOnly, boolean enableAllProfiles, String baseUrl, String accessToken) {
140128
ArrayList<SubtitleStreamInfo> list = new ArrayList<SubtitleStreamInfo>();
141129

142-
// HLS will preserve timestamps so we can just grab the full subtitle stream
143-
long startPositionTicks = getPlayMethod() == PlayMethod.Transcode ? getStartPositionTicks() : 0;
144-
145130
if (!includeSelectedTrackOnly) {
146131
if (getMediaSource() == null) return list;
147132

148133
for (org.jellyfin.sdk.model.api.MediaStream stream : getMediaSource().getMediaStreams()) {
149134
if (stream.getType() == org.jellyfin.sdk.model.api.MediaStreamType.SUBTITLE) {
150-
addSubtitleProfiles(list, stream, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
135+
SubtitleStreamInfo info = getSubtitleStreamInfo(stream, getDeviceProfile().getSubtitleProfiles());
136+
list.add(info);
151137
}
152138
}
153139
}
154140

155141
return list;
156142
}
157143

158-
private void addSubtitleProfiles(ArrayList<SubtitleStreamInfo> list, org.jellyfin.sdk.model.api.MediaStream stream, boolean enableAllProfiles, String baseUrl, String accessToken, long startPositionTicks) {
159-
if (enableAllProfiles) {
160-
for (SubtitleProfile profile : getDeviceProfile().getSubtitleProfiles()) {
161-
SubtitleStreamInfo info = getSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, new SubtitleProfile[]{profile});
162-
163-
list.add(info);
164-
}
165-
} else {
166-
SubtitleStreamInfo info = getSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, getDeviceProfile().getSubtitleProfiles());
167-
168-
list.add(info);
169-
}
170-
}
171-
172-
private SubtitleStreamInfo getSubtitleStreamInfo(org.jellyfin.sdk.model.api.MediaStream stream, String baseUrl, String accessToken, long startPositionTicks, SubtitleProfile[] subtitleProfiles) {
144+
private SubtitleStreamInfo getSubtitleStreamInfo(org.jellyfin.sdk.model.api.MediaStream stream, SubtitleProfile[] subtitleProfiles) {
173145
SubtitleProfile subtitleProfile = StreamBuilder.getSubtitleProfile(stream, subtitleProfiles, getPlayMethod());
174-
SubtitleStreamInfo tempVar = new SubtitleStreamInfo();
146+
SubtitleStreamInfo info = new SubtitleStreamInfo();
175147
String tempVar2 = stream.getLanguage();
176-
tempVar.setName((tempVar2 != null) ? tempVar2 : "Unknown");
177-
tempVar.setFormat(subtitleProfile.getFormat());
178-
tempVar.setIndex(stream.getIndex());
179-
tempVar.setDeliveryMethod(subtitleProfile.getMethod());
180-
tempVar.setDisplayTitle(stream.getDisplayTitle());
181-
SubtitleStreamInfo info = tempVar;
182-
183-
if (info.getDeliveryMethod() == SubtitleDeliveryMethod.External) {
184-
if (getMediaSource().getProtocol() == MediaProtocol.FILE || !stream.getCodec().equalsIgnoreCase(subtitleProfile.getFormat())) {
185-
info.setUrl(String.format("%1$s/Videos/%2$s/%3$s/Subtitles/%4$s/%5$s/Stream.%6$s", baseUrl, getItemId(), getMediaSourceId(), String.valueOf(stream.getIndex()), String.valueOf(startPositionTicks), subtitleProfile.getFormat()));
186-
187-
if (!Utils.isEmpty(accessToken)) {
188-
info.setUrl(info.getUrl() + "?api_key=" + accessToken);
189-
}
190-
} else {
191-
info.setUrl(stream.getPath());
192-
}
193-
}
194-
148+
info.setName((tempVar2 != null) ? tempVar2 : "Unknown");
149+
info.setFormat(subtitleProfile.getFormat());
150+
info.setIndex(stream.getIndex());
151+
info.setDeliveryMethod(subtitleProfile.getMethod());
152+
info.setDisplayTitle(stream.getDisplayTitle());
195153
return info;
196154
}
197155

app/src/main/java/org/jellyfin/androidtv/data/eventhandling/SocketHandler.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import org.jellyfin.androidtv.ui.navigation.Destinations
1616
import org.jellyfin.androidtv.ui.navigation.NavigationRepository
1717
import org.jellyfin.androidtv.ui.playback.MediaManager
1818
import org.jellyfin.androidtv.ui.playback.PlaybackControllerContainer
19+
import org.jellyfin.androidtv.ui.playback.setSubtitleIndex
1920
import org.jellyfin.androidtv.util.PlaybackHelper
2021
import org.jellyfin.sdk.api.client.ApiClient
2122
import org.jellyfin.sdk.api.client.exception.ApiClientException
@@ -104,7 +105,7 @@ class SocketHandler(
104105
val index = message["index"]?.toIntOrNull() ?: return@onEach
105106

106107
withContext(Dispatchers.Main) {
107-
playbackControllerContainer.playbackController?.switchSubtitleStream(index)
108+
playbackControllerContainer.playbackController?.setSubtitleIndex(index)
108109
}
109110
}
110111
.launchIn(coroutineScope)

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

+1-4
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,15 @@ public class GetPlaybackInfoResponse extends Response<PlaybackInfoResponse> {
2626
private AudioOptions options;
2727
private Response<StreamInfo> response;
2828
private boolean isVideo;
29-
private Long startPositionTicks;
3029

31-
public GetPlaybackInfoResponse(PlaybackManager playbackManager, DeviceInfo deviceInfo, ApiClient apiClient, AudioOptions options, Response<StreamInfo> response, boolean isVideo, Long startPositionTicks) {
30+
public GetPlaybackInfoResponse(PlaybackManager playbackManager, DeviceInfo deviceInfo, ApiClient apiClient, AudioOptions options, Response<StreamInfo> response, boolean isVideo) {
3231
super(response);
3332
this.playbackManager = playbackManager;
3433
this.deviceInfo = deviceInfo;
3534
this.apiClient = apiClient;
3635
this.options = options;
3736
this.response = response;
3837
this.isVideo = isVideo;
39-
this.startPositionTicks = startPositionTicks;
4038
}
4139

4240
@Override
@@ -96,7 +94,6 @@ private void onResponseInternal(PlaybackInfoResponse playbackInfo, MediaSourceIn
9694
streamInfo.setItemId(options.getItemId());
9795
streamInfo.setDeviceProfile(options.getProfile());
9896
streamInfo.setPlaySessionId(playbackInfo.getPlaySessionId());
99-
streamInfo.setStartPositionTicks(startPositionTicks);
10097

10198
if (options.getEnableDirectPlay() && mediaSourceInfo.getSupportsDirectPlay()){
10299
if (canDirectPlay(mediaSourceInfo)) {

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

+15-109
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import org.jellyfin.androidtv.R;
1616
import org.jellyfin.androidtv.data.compat.PlaybackException;
1717
import org.jellyfin.androidtv.data.compat.StreamInfo;
18-
import org.jellyfin.androidtv.data.compat.SubtitleStreamInfo;
1918
import org.jellyfin.androidtv.data.compat.VideoOptions;
2019
import org.jellyfin.androidtv.data.model.DataRefreshService;
2120
import org.jellyfin.androidtv.preference.UserPreferences;
@@ -28,13 +27,11 @@
2827
import org.jellyfin.androidtv.util.TimeUtils;
2928
import org.jellyfin.androidtv.util.Utils;
3029
import org.jellyfin.androidtv.util.apiclient.ReportingHelper;
31-
import org.jellyfin.androidtv.util.apiclient.StreamHelper;
3230
import org.jellyfin.androidtv.util.profile.ExoPlayerProfile;
3331
import org.jellyfin.androidtv.util.sdk.compat.JavaCompat;
3432
import org.jellyfin.apiclient.interaction.ApiClient;
3533
import org.jellyfin.apiclient.interaction.Response;
3634
import org.jellyfin.apiclient.model.dlna.DeviceProfile;
37-
import org.jellyfin.apiclient.model.dlna.SubtitleDeliveryMethod;
3835
import org.jellyfin.apiclient.model.session.PlayMethod;
3936
import org.jellyfin.sdk.model.api.BaseItemDto;
4037
import org.jellyfin.sdk.model.api.BaseItemKind;
@@ -70,20 +67,18 @@ public class PlaybackController implements PlaybackControllerNotifiable {
7067
List<BaseItemDto> mItems;
7168
VideoManager mVideoManager;
7269
int mCurrentIndex;
73-
private long mCurrentPosition = 0;
70+
protected long mCurrentPosition = 0;
7471
private PlaybackState mPlaybackState = PlaybackState.IDLE;
7572

7673
private StreamInfo mCurrentStreamInfo;
77-
private List<SubtitleStreamInfo> mSubtitleStreams;
7874

7975
@Nullable
8076
private CustomPlaybackOverlayFragment mFragment;
8177
private Boolean spinnerOff = false;
8278

83-
private VideoOptions mCurrentOptions;
84-
private int mDefaultSubIndex = -1;
79+
protected VideoOptions mCurrentOptions;
8580
private int mDefaultAudioIndex = -1;
86-
private boolean burningSubs = false;
81+
protected boolean burningSubs = false;
8782
private float mRequestedPlaybackSpeed = -1.0f;
8883

8984
private Runnable mReportLoop;
@@ -211,18 +206,6 @@ public int getSubtitleStreamIndex() {
211206
return (mCurrentOptions != null && mCurrentOptions.getSubtitleStreamIndex() != null) ? mCurrentOptions.getSubtitleStreamIndex() : -1;
212207
}
213208

214-
public List<SubtitleStreamInfo> getSubtitleStreams() {
215-
return mSubtitleStreams;
216-
}
217-
218-
public SubtitleStreamInfo getSubtitleStreamInfo(int index) {
219-
for (SubtitleStreamInfo info : mSubtitleStreams) {
220-
if (info.getIndex() == index) return info;
221-
}
222-
223-
return null;
224-
}
225-
226209
public boolean isTranscoding() {
227210
// use or here so that true is the default since
228211
// this method is used to exclude features that may break unless we are sure playback is direct
@@ -401,7 +384,7 @@ public void play(long position) {
401384
play(position, null);
402385
}
403386

404-
private void play(long position, @Nullable Integer forcedSubtitleIndex) {
387+
protected void play(long position, @Nullable Integer forcedSubtitleIndex) {
405388
Timber.i("Play called from state: %s with pos: %d and sub index: %d", mPlaybackState, position, forcedSubtitleIndex);
406389

407390
if (mFragment == null) {
@@ -607,22 +590,10 @@ private void startItem(BaseItemDto item, long position, StreamInfo response) {
607590
mCurrentOptions.setMediaSourceId(response.getMediaSource().getId());
608591

609592
// get subtitle info
610-
mSubtitleStreams = response.getSubtitleProfiles(false, apiClient.getValue().getApiUrl(), apiClient.getValue().getAccessToken());
611-
mDefaultSubIndex = response.getMediaSource().getDefaultSubtitleStreamIndex() != null ? response.getMediaSource().getDefaultSubtitleStreamIndex() : mDefaultSubIndex;
593+
mCurrentOptions.setSubtitleStreamIndex(response.getMediaSource().getDefaultSubtitleStreamIndex() != null ? response.getMediaSource().getDefaultSubtitleStreamIndex() : null);
612594
setDefaultAudioIndex(response);
613595
Timber.d("default audio index set to %s remote default %s", mDefaultAudioIndex, response.getMediaSource().getDefaultAudioStreamIndex());
614-
Timber.d("default sub index set to %s remote default %s", mDefaultSubIndex, response.getMediaSource().getDefaultSubtitleStreamIndex());
615-
616-
// if burning in, set the subtitle index and the burningSubs flag so that onPrepared and switchSubtitleStream will know that we already have subtitles enabled
617-
burningSubs = false;
618-
if (mCurrentStreamInfo.getPlayMethod() == PlayMethod.Transcode && getSubtitleStreamInfo(mDefaultSubIndex) != null &&
619-
getSubtitleStreamInfo(mDefaultSubIndex).getDeliveryMethod() == SubtitleDeliveryMethod.Encode) {
620-
mCurrentOptions.setSubtitleStreamIndex(mDefaultSubIndex);
621-
Timber.d("stream started with burnt in subs");
622-
burningSubs = true;
623-
} else {
624-
mCurrentOptions.setSubtitleStreamIndex(null);
625-
}
596+
Timber.d("default sub index set to %s remote default %s", mCurrentOptions.getSubtitleStreamIndex(), response.getMediaSource().getDefaultSubtitleStreamIndex());
626597

627598
Long mbPos = position * 10000;
628599

@@ -638,7 +609,7 @@ private void startItem(BaseItemDto item, long position, StreamInfo response) {
638609
if (mFragment != null) mFragment.updateDisplay();
639610

640611
if (mVideoManager != null) {
641-
mVideoManager.setVideoPath(response.getMediaUrl());
612+
mVideoManager.setMediaStreamInfo(api.getValue(), response);
642613
}
643614

644615
PlaybackControllerHelperKt.applyMediaSegments(this, item, () -> {
@@ -752,67 +723,6 @@ public void switchAudioStream(int index) {
752723
}
753724
}
754725

755-
756-
public void switchSubtitleStream(int index) {
757-
if (!hasInitializedVideoManager())
758-
return;
759-
// get current timestamp first
760-
refreshCurrentPosition();
761-
Timber.d("Setting subtitle index to: %d", index);
762-
763-
// clear the default in case there's an error loading the subtitles
764-
mDefaultSubIndex = -1;
765-
766-
// handle setting subtitles as disabled
767-
// restart playback if turning off burnt-in subtitles
768-
if (index < 0) {
769-
mCurrentOptions.setSubtitleStreamIndex(-1);
770-
if (burningSubs) {
771-
stop();
772-
play(mCurrentPosition, -1);
773-
}
774-
return;
775-
}
776-
777-
org.jellyfin.sdk.model.api.MediaStream stream = StreamHelper.getMediaStream(getCurrentMediaSource(), index);
778-
if (stream == null) {
779-
if (mFragment != null)
780-
Utils.showToast(mFragment.getContext(), mFragment.getString(R.string.subtitle_error));
781-
return;
782-
}
783-
SubtitleStreamInfo streamInfo = getSubtitleStreamInfo(index);
784-
if (streamInfo == null) {
785-
if (mFragment != null)
786-
Utils.showToast(mFragment.getContext(), mFragment.getString(R.string.msg_unable_load_subs));
787-
return;
788-
}
789-
790-
// handle switching on or between burnt-in subtitles, or switching to non-burnt subtitles
791-
// if switching from burnt-in subtitles to another type, playback still needs to be restarted
792-
if (burningSubs || streamInfo.getDeliveryMethod() == SubtitleDeliveryMethod.Encode) {
793-
stop();
794-
if (mFragment != null && streamInfo.getDeliveryMethod() == SubtitleDeliveryMethod.Encode)
795-
Utils.showToast(mFragment.getContext(), mFragment.getString(R.string.msg_burn_sub_warning));
796-
play(mCurrentPosition, index);
797-
return;
798-
}
799-
800-
// when burnt-in subtitles are selected, mCurrentOptions SubtitleStreamIndex is set in startItem() as soon as playback starts
801-
// otherwise mCurrentOptions SubtitleStreamIndex is kept null until now so we knew subtitles needed to be enabled but weren't already
802-
803-
if (streamInfo.getDeliveryMethod() == SubtitleDeliveryMethod.Embed) {
804-
if (!mVideoManager.setExoPlayerTrack(index, MediaStreamType.SUBTITLE, getCurrentlyPlayingItem().getMediaStreams())) {
805-
// error selecting internal subs
806-
if (mFragment != null)
807-
Utils.showToast(mFragment.getContext(), mFragment.getString(R.string.msg_unable_load_subs));
808-
} else {
809-
mCurrentOptions.setSubtitleStreamIndex(index);
810-
mDefaultSubIndex = index;
811-
812-
}
813-
}
814-
}
815-
816726
public void pause() {
817727
Timber.d("pause called at %s", mCurrentPosition);
818728
// if playback is paused and the seekbar is scrubbed, it will call pause even if already paused
@@ -887,7 +797,6 @@ private void resetPlayerErrors() {
887797
}
888798

889799
private void clearPlaybackSessionOptions() {
890-
mDefaultSubIndex = -1;
891800
mDefaultAudioIndex = -1;
892801
mSeekPosition = -1;
893802
finishedInitialSeek = false;
@@ -980,7 +889,7 @@ public void seek(long pos, boolean skipToNext) {
980889
public void onResponse(StreamInfo response) {
981890
mCurrentStreamInfo = response;
982891
if (mVideoManager != null) {
983-
mVideoManager.setVideoPath(response.getMediaUrl());
892+
mVideoManager.setMediaStreamInfo(api.getValue(), response);
984893
mVideoManager.start();
985894
}
986895
}
@@ -1186,16 +1095,13 @@ public void onPrepared() {
11861095
if (mPlaybackState == PlaybackState.PAUSED) {
11871096
mPlaybackState = PlaybackState.PLAYING;
11881097
} else {
1189-
// select or disable subtitles
1190-
Integer currentSubtitleIndex = mCurrentOptions.getSubtitleStreamIndex();
1191-
if (mDefaultSubIndex >= 0 && currentSubtitleIndex != null && currentSubtitleIndex == mDefaultSubIndex) {
1192-
Timber.i("subtitle stream %s is already selected", mDefaultSubIndex);
1193-
} else {
1194-
if (mDefaultSubIndex < 0)
1195-
Timber.i("Turning off subs");
1196-
else
1197-
Timber.i("Enabling default sub stream: %d", mDefaultSubIndex);
1198-
switchSubtitleStream(mDefaultSubIndex);
1098+
if (!burningSubs) {
1099+
// Make sure the requested subtitles are enabled when external/embedded
1100+
Integer currentSubtitleIndex = mCurrentOptions.getSubtitleStreamIndex();
1101+
1102+
if (currentSubtitleIndex != null && currentSubtitleIndex != -1) {
1103+
PlaybackControllerHelperKt.setSubtitleIndex(this, currentSubtitleIndex, true);
1104+
}
11991105
}
12001106

12011107
// select an audio track

0 commit comments

Comments
 (0)