Page 1 of 1

Android LibVlc - how to show subtitles and select audio tracks in m3u8 stream.

Posted: 27 Nov 2015 01:30
by ssaguiar
Hi to all.

I hope you can help me because I just can't find an answer to this.
I searched all around and it's very hard to find an example code about how to select audio channels and show subtitles in a internet stream (m3u8), using Android.
At this moment, I can enumerate the number and names of the channels (audio and subtitle).

This is the log of my test app, made from a test source code:

Code: Select all

11-26 17:11:27.945 19870-19870/com.wass08.vlcsimpleplayer D/LIBVLC_PLAYER: SurfaceHolder.Callback -> surfaceCreated 11-26 17:11:27.955 19870-19870/com.wass08.vlcsimpleplayer D/LIBVLC_PLAYER: SurfaceHolder.Callback: width: 1280 height: 670 11-26 17:11:27.955 19870-19870/com.wass08.vlcsimpleplayer D/LIBVLC_PLAYER: Number of Subtitles Tracks = 2 11-26 17:11:27.955 19870-19870/com.wass08.vlcsimpleplayer D/LIBVLC_PLAYER: Number of Audio Tracks = 2 11-26 17:11:27.960 19870-19870/com.wass08.vlcsimpleplayer D/LIBVLC_PLAYER: Subtitle Track 0 = DVB subtitles - [leg] 11-26 17:11:27.960 19870-19870/com.wass08.vlcsimpleplayer D/LIBVLC_PLAYER: Subtitle Track 1 = Disable 11-26 17:11:27.960 19870-19870/com.wass08.vlcsimpleplayer D/LIBVLC_PLAYER: Audio Track 0 = Track 1 - [Portuguese] 11-26 17:11:30.015 19870-19870/com.wass08.vlcsimpleplayer D/LIBVLC_PLAYER: SurfaceHolder.Callback: width: 1280 height: 720 11-26 17:19:43.040 19870-19870/com.wass08.vlcsimpleplayer D/LIBVLC_PLAYER: SurfaceHolder.Callback -> surfaceDestroyed
This is the main code:

Code: Select all

public class FullscreenVlcPlayer extends Activity implements SurfaceHolder.Callback, IVideoPlayer { private String urlToStream; // Display Surface private LinearLayout vlcContainer; private SurfaceView mSurface; private SurfaceView mSubtitlesSurface; private SurfaceHolder holder; private SurfaceHolder mSubtitlesSurfaceHolder; // Overlay / Controls private FrameLayout vlcOverlay; private ImageView vlcButtonPlayPause; private Handler handlerOverlay; private Runnable runnableOverlay; private Handler handlerSeekbar; private Runnable runnableSeekbar; private SeekBar vlcSeekbar; private TextView vlcDuration; private TextView overlayTitle; // media player private LibVLC libvlc; private int mVideoWidth; private int mVideoHeight; private final static int VideoSizeChanged = -1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Retrieve our url Bundle b = getIntent().getExtras(); urlToStream = b.getString("url", null); // HIDE THE ACTION BAR getActionBar().hide(); // SETUP THE UI setContentView(R.layout.activity_fullscreen_vlc_player); // VLC vlcContainer = (LinearLayout) findViewById(R.id.vlc_container); mSurface = (SurfaceView) findViewById(R.id.vlc_surface); mSubtitlesSurface = (SurfaceView) findViewById(R.id.subtitles_surface); // OVERLAY / CONTROLS vlcOverlay = (FrameLayout) findViewById(R.id.vlc_overlay); vlcButtonPlayPause = (ImageView) findViewById(R.id.vlc_button_play_pause); vlcSeekbar = (SeekBar) findViewById(R.id.vlc_seekbar); vlcDuration = (TextView) findViewById(R.id.vlc_duration); overlayTitle = (TextView) findViewById(R.id.vlc_overlay_title); overlayTitle.setText(urlToStream); // AUTOSTART playMovie(); } private void setSubtitles() { /* Only show the subtitles surface when using "Full Acceleration" mode */ //if (libvlc.getHardwareAcceleration() == 2) //{ mSubtitlesSurface.setVisibility(View.VISIBLE); //} Map<Integer, String> mSubtitleTracksList; Map<Integer, String> mAudioTracksList; int numSubtitleTracks = libvlc.getSpuTracksCount(); int numAudioTracks = libvlc.getAudioTracksCount(); mSubtitleTracksList = libvlc.getSpuTrackDescription(); mAudioTracksList = libvlc.getAudioTrackDescription(); if (mSubtitleTracksList != null && mSubtitleTracksList.size() > 0) { } if (mAudioTracksList != null && mAudioTracksList.size() > 0) { } Log.d("LIBVLC_PLAYER", "Number of Subtitles Tracks = " + numSubtitleTracks); Log.d("LIBVLC_PLAYER", "Number of Audio Tracks = " + numAudioTracks); int i = 0; for (Map.Entry<Integer, String> entry : mSubtitleTracksList.entrySet()) { Log.d("LIBVLC_PLAYER", "Subtitle Track " + i + " = " + entry.getValue().toString()); i++; } libvlc.setSpuTrack(0); i = 0; for (Map.Entry<Integer, String> entry2 : mAudioTracksList.entrySet()) { Log.d("LIBVLC_PLAYER", "Audio Track " + i + " = " + entry2.getValue().toString()); i++; } libvlc.setAudioTrack(1); } private final SurfaceHolder.Callback mSubtitlesSurfaceCallback = new SurfaceHolder.Callback() { @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { if (libvlc != null) { libvlc.attachSubtitlesSurface(holder.getSurface()); Log.d("LIBVLC_PLAYER", "SurfaceHolder.Callback: width: " + width + " height: " + height); } } @Override public void surfaceCreated(SurfaceHolder holder) { Log.d("LIBVLC_PLAYER", "SurfaceHolder.Callback -> surfaceCreated"); } @Override public void surfaceDestroyed(SurfaceHolder holder) { libvlc.detachSubtitlesSurface(); Log.d("LIBVLC_PLAYER", "SurfaceHolder.Callback -> surfaceDestroyed"); } }; private void showOverlay() { vlcOverlay.setVisibility(View.VISIBLE); } private void hideOverlay() { vlcOverlay.setVisibility(View.GONE); } private void setupControls() { getActionBar().hide(); // PLAY PAUSE vlcButtonPlayPause.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View view) { if (libvlc.isPlaying()) { libvlc.pause(); vlcButtonPlayPause.setImageDrawable(getResources().getDrawable(R.drawable.ic_action_play_over_video)); } else { libvlc.play(); vlcButtonPlayPause.setImageDrawable(getResources().getDrawable(R.drawable.ic_action_pause_over_video)); } } } ); // SEEKBAR handlerSeekbar = new Handler(); runnableSeekbar = new Runnable() { @Override public void run() { if (libvlc != null) { long curTime = libvlc.getTime(); long totalTime = (long) (curTime / libvlc.getPosition()); int minutes = (int) (curTime / (60 * 1000)); int seconds = (int) ((curTime / 1000) % 60); int endMinutes = (int) (totalTime / (60 * 1000)); int endSeconds = (int) ((totalTime / 1000) % 60); String duration = String.format("%02d:%02d / %02d:%02d", minutes, seconds, endMinutes, endSeconds); vlcSeekbar.setProgress((int) (libvlc.getPosition() * 100)); vlcDuration.setText(duration); } handlerSeekbar.postDelayed(runnableSeekbar, 1000); } }; runnableSeekbar.run(); vlcSeekbar.setOnSeekBarChangeListener( new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int i, boolean b) { Log.v("NEW POS", "pos is : " + i); //if (i != 0) // libvlc.setPosition(((float) i / 100.0f)); } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } } ); // OVERLAY handlerOverlay = new Handler(); runnableOverlay = new Runnable() { @Override public void run() { vlcOverlay.setVisibility(View.GONE); toggleFullscreen(true); } }; final long timeToDisappear = 3000; handlerOverlay.postDelayed(runnableOverlay, timeToDisappear); vlcContainer.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View view) { vlcOverlay.setVisibility(View.VISIBLE); handlerOverlay.removeCallbacks(runnableOverlay); handlerOverlay.postDelayed(runnableOverlay, timeToDisappear); } } ); } public void playMovie() { if (libvlc != null && libvlc.isPlaying()) { return; } vlcContainer.setVisibility(View.VISIBLE); holder = mSurface.getHolder(); holder.addCallback(this); createPlayer(urlToStream); } private void toggleFullscreen(boolean fullscreen) { WindowManager.LayoutParams attrs = getWindow().getAttributes(); if (fullscreen) { attrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; vlcContainer.setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY ); } else { attrs.flags &= ~WindowManager.LayoutParams.FLAG_FULLSCREEN; } getWindow().setAttributes(attrs); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); setSize(mVideoWidth, mVideoHeight); } @Override protected void onResume() { super.onResume(); } @Override protected void onPause() { super.onPause(); //releasePlayer(); } @Override protected void onDestroy() { super.onDestroy(); releasePlayer(); } /** * ********** * Surface * *********** */ public void surfaceCreated(SurfaceHolder holder) { } public void surfaceChanged(SurfaceHolder surfaceholder, int format, int width, int height) { if (libvlc != null) { libvlc.attachSurface(surfaceholder.getSurface(), this); } } public void surfaceDestroyed(SurfaceHolder surfaceholder) { } private void setSize(int width, int height) { mVideoWidth = width; mVideoHeight = height; if (mVideoWidth * mVideoHeight <= 1) { return; } // get screen size int w = getWindow().getDecorView().getWidth(); int h = getWindow().getDecorView().getHeight(); // getWindow().getDecorView() doesn't always take orientation into // account, we have to correct the values boolean isPortrait = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; if (w > h && isPortrait || w < h && !isPortrait) { int i = w; w = h; h = i; } float videoAR = (float) mVideoWidth / (float) mVideoHeight; float screenAR = (float) w / (float) h; if (screenAR < videoAR) { h = (int) (w / videoAR); } else { w = (int) (h * videoAR); } // force surface buffer size if (holder != null) { holder.setFixedSize(mVideoWidth, mVideoHeight); } // set display size ViewGroup.LayoutParams lp = mSurface.getLayoutParams(); lp.width = w; lp.height = h; mSurface.setLayoutParams(lp); mSurface.invalidate(); } @Override public void setSurfaceSize(int width, int height, int visible_width, int visible_height, int sar_num, int sar_den) { Message msg = Message.obtain(mHandler, VideoSizeChanged, width, height); msg.sendToTarget(); } /** * ********** * Player * *********** */ private void createPlayer(String media) { releasePlayer(); setupControls(); try { if (media.length() > 0) { Toast toast = Toast.makeText(this, media, Toast.LENGTH_LONG); toast.setGravity( Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, 0 ); toast.show(); } // Create a new media player libvlc = LibVLC.getInstance(); libvlc.setHardwareAcceleration(LibVLC.HW_ACCELERATION_FULL); libvlc.eventVideoPlayerActivityCreated(true); libvlc.setSubtitlesEncoding(""); libvlc.setAout(LibVLC.AOUT_OPENSLES); libvlc.setTimeStretching(true); libvlc.setChroma("RV32"); libvlc.setVerboseMode(true); LibVLC.restart(this); EventHandler.getInstance().addHandler(mHandler); holder.setFormat(PixelFormat.RGBX_8888); holder.setKeepScreenOn(true); mSubtitlesSurface.setZOrderMediaOverlay(true); //mSubtitlesSurface.setZOrderOnTop(true); mSubtitlesSurfaceHolder = mSubtitlesSurface.getHolder(); //mSubtitlesSurfaceHolder.setFormat(PixelFormat.RGBA_8888); mSubtitlesSurfaceHolder.setFormat(PixelFormat.TRANSLUCENT); mSubtitlesSurfaceHolder.addCallback(mSubtitlesSurfaceCallback); MediaList list = libvlc.getMediaList(); list.clear(); list.add(new Media(libvlc, LibVLC.PathToURI(media)), false); libvlc.setVolume(100); libvlc.playIndex(0); } catch (Exception e) { Toast.makeText(this, "Could not create Vlc Player", Toast.LENGTH_LONG).show(); } } private void releasePlayer() { if (handlerSeekbar != null && runnableSeekbar != null) { handlerSeekbar.removeCallbacks(runnableSeekbar); } EventHandler.getInstance().removeHandler(mHandler); if (libvlc == null) { return; } libvlc.stop(); libvlc.detachSurface(); holder = null; libvlc.closeAout(); mVideoWidth = 0; mVideoHeight = 0; } /** * ********** * Events * *********** */ private Handler mHandler = new MyHandler(this); private static class MyHandler extends Handler { private WeakReference<FullscreenVlcPlayer> mOwner; public MyHandler(FullscreenVlcPlayer owner) { mOwner = new WeakReference<FullscreenVlcPlayer>(owner); } @Override public void handleMessage(Message msg) { FullscreenVlcPlayer player = mOwner.get(); // Player events if (msg.what == VideoSizeChanged) { player.setSize(msg.arg1, msg.arg2); return; } // Libvlc events Bundle b = msg.getData(); switch (b.getInt("event")) { case EventHandler.MediaPlayerEndReached: player.releasePlayer(); break; case EventHandler.MediaPlayerPlaying: player.setSubtitles(); break; case EventHandler.MediaPlayerPaused: case EventHandler.MediaPlayerStopped: default: break; } } } }
And main xml is:

Code: Select all

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/black" tools:context="com.wass08.vlcsimpleplayer.FullscreenVlcPlayer"> <LinearLayout android:id="@+id/vlc_container" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <FrameLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:visibility="visible" android:background="@android:color/black" > <SurfaceView android:id="@+id/vlc_surface" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center" /> <SurfaceView android:id="@+id/subtitles_surface" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center" android:visibility="invisible" /> <FrameLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#69000000" android:id="@+id/vlc_overlay" android:padding="10dp"> <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal|bottom" android:gravity="center_vertical"> <ImageView android:layout_width="50dp" android:layout_height="50dp" android:id="@+id/vlc_button_play_pause" android:src="@drawable/ic_action_pause_over_video"/> <LinearLayout android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1"> <SeekBar android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/vlc_seekbar" android:indeterminate="false" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" android:text="00:00 / 00:00" android:textColor="@android:color/white" android:id="@+id/vlc_duration" /> </LinearLayout> </LinearLayout> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:id="@+id/vlc_overlay_title" android:textColor="@android:color/white" android:textStyle="italic" android:singleLine="true" android:ellipsize="marquee" android:layout_gravity="center" android:gravity="center_horizontal" /> </FrameLayout> </FrameLayout> </LinearLayout> </FrameLayout>

Re: Android LibVlc - how to show subtitles and select audio tracks in m3u8 stream.

Posted: 29 Nov 2015 15:27
by ssaguiar
Nobody?

Nor even the developper can answer this question?

The VLC lib project is a very good one but it lacks of good documentation and examples of implementation.

It's almost impossible to use it.

Re: Android LibVlc - how to show subtitles and select audio tracks in m3u8 stream.

Posted: 30 Nov 2015 21:40
by sherington
I don't know the answer in your specific case, but in general when dealing with selecting tracks...

1. make sure that you are using proper track identifiers as obtained from the track information API;
2. do not assume the track identifiers start at zero;
3. do not assume the track identifiers go in a contiguous sequence.