Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support external subtitles in external player #2061

Merged
merged 3 commits into from
Sep 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/src/main/java/org/jellyfin/androidtv/constant/Codec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,8 @@ object Codec {
const val SUB = "sub"
const val SUBRIP = "subrip"
const val VTT = "vtt"
const val SMIL = "smil"
const val TTML = "ttml"
const val WEBVTT = "webvtt"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.jellyfin.androidtv.auth.repository.UserRepository;
import org.jellyfin.androidtv.data.compat.PlaybackException;
import org.jellyfin.androidtv.data.compat.StreamInfo;
import org.jellyfin.androidtv.data.compat.SubtitleStreamInfo;
import org.jellyfin.androidtv.data.compat.VideoOptions;
import org.jellyfin.androidtv.data.service.BackgroundService;
import org.jellyfin.androidtv.preference.UserPreferences;
Expand All @@ -31,6 +32,7 @@
import org.jellyfin.androidtv.util.sdk.compat.JavaCompat;
import org.jellyfin.apiclient.interaction.ApiClient;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.dlna.SubtitleDeliveryMethod;
import org.jellyfin.apiclient.model.dto.UserItemDataDto;
import org.jellyfin.apiclient.model.session.PlayMethod;
import org.jellyfin.sdk.model.api.BaseItemKind;
Expand All @@ -39,6 +41,7 @@
import java.io.File;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import kotlin.Lazy;
import timber.log.Timber;
Expand Down Expand Up @@ -70,19 +73,25 @@ public class ExternalPlayerActivity extends FragmentActivity {
static final String API_MX_TITLE = "title";
static final String API_MX_SEEK_POSITION = "position";
static final String API_MX_FILENAME = "filename";
static final String API_MX_SECURE_URI = "secure_uri";
static final String API_MX_RETURN_RESULT = "return_result";
static final String API_MX_RESULT_ID = "com.mxtech.intent.result.VIEW";
static final String API_MX_RESULT_POSITION = "position";
static final String API_MX_RESULT_END_BY = "end_by";
static final String API_MX_RESULT_END_BY_USER = "user";
static final String API_MX_RESULT_END_BY_PLAYBACK_COMPLETION = "playback_completion";
static final String API_MX_SUBS = "subs";
static final String API_MX_SUBS_NAME = "subs.name";
static final String API_MX_SUBS_FILENAME = "subs.filename";
static final String API_MX_SUBS_ENABLE = "subs.enable";

// https://wiki.videolan.org/Android_Player_Intents/
static final String API_VLC_TITLE = "title";
static final String API_VLC_SEEK_POSITION = "position";
static final String API_VLC_FROM_START = "from_start";
static final String API_VLC_RESULT_ID = "org.videolan.vlc.player.result";
static final String API_VLC_RESULT_POSITION = "extra_position";
static final String API_VLC_SUBS_ENABLE = "subtitles_location";

// https://www.vimu.tv/player-api
static final String API_VIMU_TITLE = "forcename";
Expand Down Expand Up @@ -401,6 +410,10 @@ protected void startExternalActivity(String path, String container) {
external.putExtra(API_MX_FILENAME, file.getName());
}
}

external.putExtra(API_MX_SECURE_URI, true);
this.adaptExternalSubtitles(mCurrentStreamInfo, external);

//End player API params

Timber.i("Starting external playback of path: %s and mime: video/%s at position/ms: %s",path,container,mPosition);
Expand All @@ -416,4 +429,50 @@ protected void startExternalActivity(String path, String container) {
handlePlayerError();
}
}

/**
* Adapt external subtitles for external players. (e.g., MX Player, MPV, VLC, nPlayer)
* External subtitles have higher priority than embedded subtitles.
*
* @param mediaStreamInfo Current media stream info used to get subtitle profiles.
* @param playerIntent Put player API params of sub urls.
*/
private void adaptExternalSubtitles(StreamInfo mediaStreamInfo, Intent playerIntent) {

List<SubtitleStreamInfo> externalSubs = mediaStreamInfo.GetSubtitleProfiles(false,
apiClient.getValue().getApiUrl(), apiClient.getValue().getAccessToken()).stream()
.filter(stream -> stream.getDeliveryMethod() == SubtitleDeliveryMethod.External && stream.getUrl() != null)
.collect(Collectors.toList());

Uri[] subUrls = externalSubs.stream().map(stream -> Uri.parse(stream.getUrl())).toArray(Uri[]::new);
String[] subNames = externalSubs.stream().map(SubtitleStreamInfo::getDisplayTitle).toArray(String[]::new);
String[] subLanguages = externalSubs.stream().map(SubtitleStreamInfo::getName).toArray(String[]::new);

// select subtitle
Integer selectedSubStreamIndex = mediaStreamInfo.getMediaSource().getDefaultSubtitleStreamIndex();
Uri selectedSubUrl = null;
if (selectedSubStreamIndex != null) {
selectedSubUrl = externalSubs.stream()
.filter(stream -> stream.getIndex() == selectedSubStreamIndex)
.map(stream -> Uri.parse(stream.getUrl()))
.findFirst()
.orElse(null);
}
if (selectedSubUrl == null && subUrls.length > 0) {
selectedSubUrl = subUrls[0];
}

// MX Player API / MPV
playerIntent.putExtra(API_MX_SUBS, subUrls);
playerIntent.putExtra(API_MX_SUBS_NAME, subNames);
playerIntent.putExtra(API_MX_SUBS_FILENAME, subLanguages);
if (selectedSubUrl != null) {
playerIntent.putExtra(API_MX_SUBS_ENABLE, new Uri[] {selectedSubUrl});
}

// VLC
if (selectedSubUrl != null) {
playerIntent.putExtra(API_VLC_SUBS_ENABLE, selectedSubUrl);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,51 +18,9 @@ class ExternalPlayerProfile : DeviceProfile() {
directPlayProfiles = arrayOf(
DirectPlayProfile().apply {
type = DlnaProfileType.Video
container = arrayOf(
Codec.Container.M4V,
Codec.Container.`3GP`,
Codec.Container.TS,
Codec.Container.MPEGTS,
Codec.Container.MOV,
Codec.Container.XVID,
Codec.Container.VOB,
Codec.Container.MKV,
Codec.Container.WMV,
Codec.Container.ASF,
Codec.Container.OGM,
Codec.Container.OGV,
Codec.Container.M2V,
Codec.Container.AVI,
Codec.Container.MPG,
Codec.Container.MPEG,
Codec.Container.MP4,
Codec.Container.WEBM,
Codec.Container.DVR_MS,
Codec.Container.WTV
).joinToString(",")
}
)

transcodingProfiles = arrayOf(
// MKV video profile
TranscodingProfile().apply {
type = DlnaProfileType.Video
context = EncodingContext.Streaming
container = Codec.Container.MKV
videoCodec = Codec.Video.H264
audioCodec = arrayOf(
Codec.Audio.AAC,
Codec.Audio.MP3,
Codec.Audio.AC3
).joinToString(",")
copyTimestamps = true
},
// MP3 audio profile
TranscodingProfile().apply {
DirectPlayProfile().apply {
type = DlnaProfileType.Audio
context = EncodingContext.Streaming
container = Codec.Container.MP3
audioCodec = Codec.Audio.MP3
}
)

Expand All @@ -77,7 +35,25 @@ class ExternalPlayerProfile : DeviceProfile() {
subtitleProfile(Codec.Subtitle.VTT, SubtitleDeliveryMethod.Embed),
subtitleProfile(Codec.Subtitle.SUB, SubtitleDeliveryMethod.Embed),
subtitleProfile(Codec.Subtitle.IDX, SubtitleDeliveryMethod.Embed),
subtitleProfile(Codec.Subtitle.SMI, SubtitleDeliveryMethod.Embed)
subtitleProfile(Codec.Subtitle.SMI, SubtitleDeliveryMethod.Embed),
subtitleProfile(Codec.Subtitle.SMIL, SubtitleDeliveryMethod.Embed),
subtitleProfile(Codec.Subtitle.TTML, SubtitleDeliveryMethod.Embed),
subtitleProfile(Codec.Subtitle.WEBVTT, SubtitleDeliveryMethod.Embed),

subtitleProfile(Codec.Subtitle.SRT, SubtitleDeliveryMethod.External),
subtitleProfile(Codec.Subtitle.SUBRIP, SubtitleDeliveryMethod.External),
subtitleProfile(Codec.Subtitle.ASS, SubtitleDeliveryMethod.External),
subtitleProfile(Codec.Subtitle.SSA, SubtitleDeliveryMethod.External),
subtitleProfile(Codec.Subtitle.PGS, SubtitleDeliveryMethod.External),
subtitleProfile(Codec.Subtitle.PGSSUB, SubtitleDeliveryMethod.External),
subtitleProfile(Codec.Subtitle.DVDSUB, SubtitleDeliveryMethod.External),
subtitleProfile(Codec.Subtitle.VTT, SubtitleDeliveryMethod.External),
subtitleProfile(Codec.Subtitle.SUB, SubtitleDeliveryMethod.External),
subtitleProfile(Codec.Subtitle.IDX, SubtitleDeliveryMethod.External),
subtitleProfile(Codec.Subtitle.SMI, SubtitleDeliveryMethod.External),
subtitleProfile(Codec.Subtitle.SMIL, SubtitleDeliveryMethod.External),
subtitleProfile(Codec.Subtitle.TTML, SubtitleDeliveryMethod.External),
subtitleProfile(Codec.Subtitle.WEBVTT, SubtitleDeliveryMethod.External)
)
}
}