i have a dialog that changes the subtitles language. i want to be able to do that during the video . i tried to recreate the whole media source of the video but it restarts the video instantly.
Here's my subtitles SingleSampleMediaSource : textMediaSource = new SingleSampleMediaSource.Factory(dataSourceFactory)
.createMediaSource(Uri.parse(getSelectedSub(getSubLanguage())), textFormat, C.TIME_UNSET);
Here's my MergingMediaSource : MediaSource mediaSource = new MergingMediaSource(videoSource, textMediaSource);
player.prepare(mediaSource);
Here's my SubtitlesDialog :
public void showLanguageDialog(){
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
final LayoutInflater inflater = this.getLayoutInflater();
View dialogView = inflater.inflate(R.layout.sub_language_dialog, null);
dialogBuilder.setView(dialogView);
final AlertDialog alertDialog = dialogBuilder.create();
alertDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
alertDialog.show();
ImageButton cancel = dialogView.findViewById(R.id.sub_language_cancel);
cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
alertDialog.dismiss();
}
});
RelativeLayout mArabic = dialogView.findViewById(R.id.sub_language_arabic);
mArabic.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setSubLanguage("العربية");
textMediaSource = new SingleSampleMediaSource.Factory(dataSourceFactory)
.createMediaSource(Uri.parse(getSelectedSub("العربية")), textFormat, C.TIME_UNSET);
MediaSource mediaSource = new MergingMediaSource(videoSource, textMediaSource);
player.prepare(mediaSource);
alertDialog.dismiss();
}
});
RelativeLayout mEnglish = dialogView.findViewById(R.id.sub_language_english);
mEnglish.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setSubLanguage("English");
textMediaSource = new SingleSampleMediaSource.Factory(dataSourceFactory)
.createMediaSource(Uri.parse(getSelectedSub("English")), textFormat, C.TIME_UNSET);
MediaSource mediaSource = new MergingMediaSource(videoSource, textMediaSource);
player.prepare(mediaSource);
alertDialog.dismiss();
}
});
RelativeLayout mFrench = dialogView.findViewById(R.id.sub_language_french);
mFrench.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setSubLanguage("Français");
textMediaSource = new SingleSampleMediaSource.Factory(dataSourceFactory)
.createMediaSource(Uri.parse(getSelectedSub("Français")), textFormat, C.TIME_UNSET);
MediaSource mediaSource = new MergingMediaSource(videoSource, textMediaSource);
player.prepare(mediaSource);
alertDialog.dismiss();
}
});
is there anyway to change subtitles during the video without losing timing and positon, without losing video progress and buffering?.
source = new MergingMediaSource(videoSource, subtitleSource1, subtitleSource2);
i have found this line of code in solution in #3765 , i went to look for how to implement a switch dialog in the library/demo files but i seem not to reach it.
Thank you.
The solution is to merge all of the subtitle sources with a single MergingMediaSource, like the final line of code shown above. You should then be able to switch between them during playback.
The demo app's TrackSelectionHelper class demonstrates how to build a UI for switching between the tracks during playback. Programmatically you can switch during playback by calling
DefaultTrackSelector.setSelectionOverride.
As an aside, we're aware track selection is a bit tricky currently, and have a plan to move some of the code in the demo app to the core library (probably the UI module) to make it easier.
@ojw28 When using MergingMediaSource is there any access to the subtitles before selecting them?
@ojw28 Thank you , I'm going to take a look on the demo app's files. let's assume that we have an .mkv video with an embedded subtitles file. is it possible to switch to other subtitle files from the MergingMediaSource? or just hide the embedded one and show up the custom ones from your MediaSource?
@iToxicDeveloper - If you have an MKV with embedded subtitles, and use MergingMediaSource to merge with external subtitles, then it will be possible to switch between all of the embedded and non-embedded subtitles. The only thing that's not easy to do is enable multiple tracks simultaneously.
@Tolriq - What do you mean by "access"? The track selector will tell you the track is available, but there is no way to get the actual subtitles without selecting the track.
@ojw28 Thank you so much for your time, I've been looking at TrackSelectionHelper class in the demo app, i also checked this question #2343.. but i haven't really seen something similar to DefaultTrackSelector.setSelectionOverride. could you please point me to a question or a simplified example on how can i use the TrackSelector to switch between my subtitles using my custom dialog. i know you're really busy with questions and issues but i really couldn't make it.
I'm gonna show you some of my code so pointing me will be easier and will make things clearer.
Player Initialization :
//Exo Player Initialization
bandwidthMeter = new DefaultBandwidthMeter();
videoTrackSelectionFactory =
new AdaptiveTrackSelection.Factory(bandwidthMeter);
trackSelector =
new DefaultTrackSelector(videoTrackSelectionFactory);
player =
ExoPlayerFactory.newSimpleInstance(this, trackSelector);
bandwidthMeter2 = new DefaultBandwidthMeter();
dataSourceFactory = new DefaultDataSourceFactory(this,
Util.getUserAgent(this, "iWatch"), bandwidthMeter2);
//Text Format Initialization
textFormat = Format.createTextSampleFormat(null, MimeTypes.APPLICATION_SUBRIP,
null, Format.NO_VALUE, Format.NO_VALUE, "ar", null, Format.OFFSET_SAMPLE_RELATIVE);
//Video Source
videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
.createMediaSource(Uri.parse(getVideoUri()));
//Arabic Subtitles
textMediaSourceAr = new SingleSampleMediaSource.Factory(dataSourceFactory)
.createMediaSource(Uri.parse(getSelectedSub("العربية")), textFormat, C.TIME_UNSET);
//English Subtitles
textMediaSourceEn = new SingleSampleMediaSource.Factory(dataSourceFactory).createMediaSource(Uri.parse(getSelectedSub("English")),textFormat,C.TIME_UNSET);
//French Subtitles
textMediaSourceFr = new SingleSampleMediaSource.Factory(dataSourceFactory).createMediaSource(Uri.parse(getSelectedSub("Français")),textFormat,C.TIME_UNSET);
//Final MediaSource
MediaSource mediaSource = new MergingMediaSource(videoSource, textMediaSourceAr,textMediaSourceEn,textMediaSourceFr);
//Preparing The Player
player.prepare(mediaSource);
Custom Controller Initialization :
//Views Initialization
mVideoView = findViewById(R.id.video_player_videoView);
//Controller Initialization
PlayerControlView controlView = mVideoView.findViewById(R.id.exo_controller);
//Player Title
mTitle = controlView.findViewById(R.id.exo_controller_title);
mTitle.setText(getSelectedMovie());
//Player Back ImageButton
mBack = controlView.findViewById(R.id.exo_controller_back);
//Player Subtitles ImageButton
mSubtitles = controlView.findViewById(R.id.exo_controller_subtitles);
mSubtitles.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showSubDialog();
}
});
//Player More Options ImageButton
mMore = controlView.findViewById(R.id.exo_controller_settings);
mBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MoviePlayer.super.onBackPressed();
finish();
}
});
showSubDialog Method :
public void showSubDialog(){
//Some Stuff About AlertDialog
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
final LayoutInflater inflater = this.getLayoutInflater();
View dialogView = inflater.inflate(R.layout.movie_sub_dialog, null);
dialogBuilder.setView(dialogView);
final AlertDialog alertDialog = dialogBuilder.create();
alertDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
alertDialog.show();
//Some Listeners
alertDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
hideStatuBar();
}
});
Button cancel = dialogView.findViewById(R.id.movie_sub_dialog_cancel);
cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
alertDialog.dismiss();
}
});
final Switch mSwitch = dialogView.findViewById(R.id.movie_sub_dialog_switch);
if (getSubActivated()){
mSwitch.setChecked(true);
}
else {
mSwitch.setChecked(false);
}
Button done = dialogView.findViewById(R.id.movie_sub_dialog_done);
done.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mSwitch.isChecked())
{
//Show Subtitles View
mVideoView.getSubtitleView().setVisibility(View.VISIBLE);
setSubActivated(true);
}
else {
//Hide Subtitles View
mVideoView.getSubtitleView().setVisibility(View.GONE);
setSubActivated(false);
}
alertDialog.dismiss();
}
});
TextView currentLang = dialogView.findViewById(R.id.movie_sub_dialog_currentLang);
currentLang.setText(getSubLanguage());
//Subtitles Switch Button
RelativeLayout language = dialogView.findViewById(R.id.movie_sub_dialog_language);
language.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//Subtitles Language Switching
showLanguageDialog();
alertDialog.dismiss();
}
});
}
showLanguageDialog() Method :
public void showLanguageDialog(){
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
final LayoutInflater inflater = this.getLayoutInflater();
View dialogView = inflater.inflate(R.layout.sub_language_dialog, null);
dialogBuilder.setView(dialogView);
final AlertDialog alertDialog = dialogBuilder.create();
alertDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
alertDialog.show();
ImageButton cancel = dialogView.findViewById(R.id.sub_language_cancel);
cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
alertDialog.dismiss();
}
});
RelativeLayout mArabic = dialogView.findViewById(R.id.sub_language_arabic);
alertDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
hideStatuBar();
}
});
alertDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
hideStatuBar();
}
});
mArabic.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setSubLanguage("العربية");
alertDialog.dismiss();
}
});
RelativeLayout mEnglish = dialogView.findViewById(R.id.sub_language_english);
mEnglish.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setSubLanguage("English");
alertDialog.dismiss();
}
});
RelativeLayout mFrench = dialogView.findViewById(R.id.sub_language_french);
mFrench.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setSubLanguage("Français");
alertDialog.dismiss();
}
});
}
so i think that's everything , i want to be able to change the language on the click events of the language dialog, the current method applies changes after the video restarts.. is there any possible way ? :/ thank you again ^^
@ojw28 Well I'm not there yet but some subtitles will be transcoded from a source, so if by adding them to the MergingMediaSource it triggers an access when the video is started even if that track is not yet selected it will generate unwanted transcoding. Just want to be sure before going that road when adding video support.
@Tolriq - MergingMediaSource wont request the subtitle streams being merged unless they're actually selected, so you should be fine.
@iToxicDeveloper - The demo app's TrackSelectionHelper calls setSelectionOverride here, and it's this line of code executing that actually causes different tracks to be selected. If you run the demo app and attach a debugger to various points in that class, you should be able to figure out what it's doing.
@ojw28 i implemented the layouts and class of TrackSelectionHelper, i called it on my class based on the demo app player initialization like this : trackSelectionHelper = new TrackSelectionHelper(trackSelector, adaptiveTrackSelectionFactory);, i think what's left is to call the .showSelectionDialog?, i passed the arguments to the constructor , but there's something i can't figure out which is the rendererIndex.
i tried passing it like that but it returns an error :
mSubtitles = controlView.findViewById(R.id.exo_controller_subtitles);
mSubtitles.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mTrackInfo = trackSelector.getCurrentMappedTrackInfo();
if (mTrackInfo != null) {
trackSelectionHelper.showSelectionDialog(
MoviePlayer.this, "Subtitles", mTrackInfo, (int)v.getTag());
}
}
});
I'm sure that I'm missing something big :/
Check out updateButtonVisibilities method in PlayerActivity in demo app. It shows how to find the rendererIndex.
for (int i = 0; i < mappedTrackInfo.length; i++) {
TrackGroupArray trackGroups = mappedTrackInfo.getTrackGroups(i);
if (trackGroups.length != 0) {
Button button = new Button(this);
int label;
switch (player.getRendererType(i)) {
case C.TRACK_TYPE_AUDIO:
label = R.string.audio;
break;
case C.TRACK_TYPE_VIDEO:
label = R.string.video;
break;
case C.TRACK_TYPE_TEXT:
label = R.string.text;
break;
default:
continue;
}
button.setText(label);
button.setTag(i);
button.setOnClickListener(this);
debugRootView.addView(button, debugRootView.getChildCount() - 1);
}
Thank You @nerd-sj , It Worked Perfectly!