In this tutorial, I’m going to explain how to use ExoPlayer. In this tutorial, we will demonstrate how to stream audio and video local URI and server as well. Means we are going to create an audio-video player, that player will capable to play audio and video file local or remote server as well. If you want to implement ExoPlayer in RecylerView read this article.
ExoPlayer
ExoPlayer is an open-source project. That is distributed separately from the Android SDK. It not a part of Android SDK. In Android, ExoPlayer’s standard video and audio components are built on Android’s MediaCodec API, which was released in AndroidAndroid 4.1 (JELLY_BEAN
).
ExoPlayer, Play audio video (Demo App)
Objective
- First, We will create a gallery view that fetches all video file and shows on RecyclerView in GirdView layout.
- Second, I will create Singleton instance of ExoPlayer, The manager names are PlayerManager. Now you are thinking about what needs to creating a singleton? Don’t worry I explain, Two reasons behind it, first are Abstraction, Activity code should be clean and second better player state management.
- Will explain how to user PlayerManager in Activity.
1. Create a GalleryView
1.1 Create a model class for holding data
Go to src and create a new java file with VideoModel.java name, declare simple getter setter for
package com.androidwave.videoplayer; /** * Created on : Jan 24, 2019 * Author : AndroidWave * Email : info@androidwave.com */ public class VideoModel { private String mFilePath, mVideoThumb; private boolean isSelected; public String getFilePath() { return mFilePath; } public void setFilePath(String mFilePath) { this.mFilePath = mFilePath; } public String getVideoThumb() { return mVideoThumb; } public void setVideoThumb(String mVideoThumb) { this.mVideoThumb = mVideoThumb; } public boolean isSelected() { return isSelected; } public void setSelected(boolean selected) { isSelected = selected; } }
1.2 Create a video row layout for RecyclerView
Go to res folder and create a layout named video_list_row.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="200dp"> <ImageView android:id="@+id/imageViewThumbnail" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:scaleType="centerCrop" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:srcCompat="@android:drawable/ic_menu_report_image" /> <ImageView android:id="@+id/imageView2" android:layout_width="50dp" android:layout_height="50dp" android:layout_margin="@dimen/_24" android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:srcCompat="@drawable/play_button" /> </android.support.constraint.ConstraintLayout>
1.3 Create RecyclerView Adapter
Just go to src and create a java file for VideoRecyclerAdapter and set setOnClickListener() on itemView.
package com.androidwave.videoplayer; import android.content.Intent; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import com.bumptech.glide.Glide; import java.util.List; /** * Created on : Jan 24, 2019 * Author : AndroidWave * Email : info@androidwave.com */ public class VideoRecyclerAdapter extends RecyclerView.Adapter<VideoRecyclerAdapter.VideoViewHolder> { private List<VideoModel> videoList; public VideoRecyclerAdapter(List<VideoModel> videoList) { this.videoList = videoList; } public class VideoViewHolder extends RecyclerView.ViewHolder { public ImageView imageViewThumbnail; VideoViewHolder(View view) { super(view); imageViewThumbnail = view.findViewById(R.id.imageViewThumbnail); } } @Override public VideoViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(parent.getContext()) .inflate(R.layout.video_list_row, parent, false); return new VideoViewHolder(itemView); } @Override public void onBindViewHolder(final VideoViewHolder holder, int position) { final VideoModel mVideo = videoList.get(position); Glide.with(holder.itemView.getContext()).load(mVideo.getVideoThumb()).into(holder.imageViewThumbnail); holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent mPlayerIntent = PlayerActivity.getStartIntent(holder.itemView.getContext(), mVideo.getFilePath()); holder.itemView.getContext().startActivity(mPlayerIntent); } }); } @Override public int getItemCount() { return videoList.size(); } }
2. Open Activity class and manage following task
- Check permission for above marshmallow.
- If storage permission is granted then fetch all video file from gallery
- All files add details in VideoModel and add in the list.
- Set layout manager as a GridLayoutManager over the RecyclerView.
- Finally
initialised adapter and set on the RecyclerView
MainActivity Code looks like below
package com.androidwave.videoplayer; import android.Manifest; import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.provider.MediaStore; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.widget.Toast; import java.util.ArrayList; public class MainActivity extends AppCompatActivity { private static final int REQUEST_PERMISSIONS = 101; ArrayList<VideoModel> mVideoList; VideoRecyclerAdapter mAdapter; RecyclerView mRecyclerView; GridLayoutManager recyclerViewLayoutManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mRecyclerView = findViewById(R.id.recyclerViewGallery); recyclerViewLayoutManager = new GridLayoutManager(getApplicationContext(), 2); mRecyclerView.setLayoutManager(recyclerViewLayoutManager); mVideoList = new ArrayList<>(); checkPermission(); } private void checkPermission() { if ((ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) && (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)) { if ((ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) && (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE))) { } else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_PERMISSIONS); } } } else { // all permission granted getAllVideoFromGallery(); } } public void getAllVideoFromGallery() { Uri uri; Cursor mCursor; int COLUMN_INDEX_DATA, COLUMN_INDEX_NAME, COLUMN_ID, COLUMN_THUMB; String absolutePathOfFile = null; uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; String[] projection = {MediaStore.MediaColumns.DATA, MediaStore.Video.Media.BUCKET_DISPLAY_NAME, MediaStore.Video.Media._ID, MediaStore.Video.Thumbnails.DATA}; final String orderBy = MediaStore.Images.Media.DATE_TAKEN; mCursor = getApplicationContext().getContentResolver().query(uri, projection, null, null, orderBy + " DESC"); COLUMN_INDEX_DATA = mCursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); COLUMN_INDEX_NAME = mCursor.getColumnIndexOrThrow(MediaStore.Video.Media.BUCKET_DISPLAY_NAME); COLUMN_ID = mCursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID); COLUMN_THUMB = mCursor.getColumnIndexOrThrow(MediaStore.Video.Thumbnails.DATA); while (mCursor.moveToNext()) { absolutePathOfFile = mCursor.getString(COLUMN_INDEX_DATA); Log.e("Column", absolutePathOfFile); Log.e("Folder", mCursor.getString(COLUMN_INDEX_NAME)); Log.e("column_id", mCursor.getString(COLUMN_ID)); Log.e("thum", mCursor.getString(COLUMN_THUMB)); VideoModel mVideo = new VideoModel(); mVideo.setSelected(false); mVideo.setFilePath(absolutePathOfFile); mVideo.setVideoThumb(mCursor.getString(COLUMN_THUMB)); mVideoList.add(mVideo); } mAdapter = new VideoRecyclerAdapter(mVideoList); mRecyclerView.setAdapter(mAdapter); } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode) { case REQUEST_PERMISSIONS: { for (int i = 0; i < grantResults.length; i++) { if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { getAllVideoFromGallery(); } else { Toast.makeText(MainActivity.this, "The app was not allowed to read or write to your storage. Hence, it cannot function properly. Please consider granting it this permission", Toast.LENGTH_LONG).show(); } } } } } }
3. Create PlayerManager Singleton
Open
- Prepare SharedInstance of this class
- Initialize ExoPlayer and set DataSourceFactory and MediaSource
- Add Player.EventListener
- Create some usable method like play, pause, resume and release
Final output looks like
package com.androidwave.videoplayer; import android.content.Context; import android.net.Uri; import android.util.Log; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.hls.HlsMediaSource; import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.ui.PlayerView; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.util.Util; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.URL; import java.util.ArrayList; /** * Created on : Jan 21, 2019 * Author : AndroidWave * Email : info@androidwave.com */ public class PlayerManager { /** * declare some usable variable */ private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter(); private static final String TAG = "ExoPlayerManager"; private static PlayerManager mInstance = null; PlayerView mPlayerView; DefaultDataSourceFactory dataSourceFactory; String uriString = ""; ArrayList<String> mPlayList = null; Integer playlistIndex = 0; CallBacks.playerCallBack listner; private SimpleExoPlayer mPlayer; /** * private constructor * * @param mContext */ private PlayerManager(Context mContext) { TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(BANDWIDTH_METER); TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); mPlayer = ExoPlayerFactory.newSimpleInstance(mContext, trackSelector); mPlayerView = new PlayerView(mContext); mPlayerView.setUseController(true); mPlayerView.requestFocus(); mPlayerView.setPlayer(mPlayer); Uri mp4VideoUri = Uri.parse(uriString); dataSourceFactory = new DefaultDataSourceFactory(mContext, Util.getUserAgent(mContext, "androidwave"), BANDWIDTH_METER); final MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory) .createMediaSource(mp4VideoUri); mPlayer.prepare(videoSource); mPlayer.addListener(new Player.EventListener() { @Override public void onTimelineChanged(Timeline timeline, Object manifest, int reason) { Log.i(TAG, "onTimelineChanged: "); } @Override public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { Log.i(TAG, "onTracksChanged: "); } @Override public void onLoadingChanged(boolean isLoading) { Log.i(TAG, "onLoadingChanged: "); } @Override public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { Log.i(TAG, "onPlayerStateChanged: "); if (playbackState == 4 && mPlayList != null && playlistIndex + 1 < mPlayList.size()) { Log.e(TAG, "Song Changed..."); playlistIndex++; listner.onItemClickOnItem(playlistIndex); playStream(mPlayList.get(playlistIndex)); } else if (playbackState == 4 && mPlayList != null && playlistIndex + 1 == mPlayList.size()) { mPlayer.setPlayWhenReady(false); } if (playbackState == 4 && listner != null) { listner.onPlayingEnd(); } } @Override public void onRepeatModeChanged(int repeatMode) { Log.i(TAG, "onRepeatModeChanged: "); } @Override public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { Log.i(TAG, "onShuffleModeEnabledChanged: "); } @Override public void onPlayerError(ExoPlaybackException error) { Log.i(TAG, "onPlayerError: "); } @Override public void onPositionDiscontinuity(int reason) { Log.i(TAG, "onPositionDiscontinuity: "); } @Override public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { Log.i(TAG, "onPlaybackParametersChanged: "); } @Override public void onSeekProcessed() { Log.i(TAG, "onSeekProcessed: "); } }); } /** * Return ExoPlayerManager instance * * @param mContext * @return */ public static PlayerManager getSharedInstance(Context mContext) { if (mInstance == null) { mInstance = new PlayerManager(mContext); } return mInstance; } public void setPlayerListener(CallBacks.playerCallBack mPlayerCallBack) { listner = mPlayerCallBack; } public PlayerView getPlayerView() { return mPlayerView; } public void playStream(String urlToPlay) { uriString = urlToPlay; Uri mp4VideoUri = Uri.parse(uriString); MediaSource videoSource; // String filenameArray[] = urlToPlay.split("\\."); if (uriString.toUpperCase().contains("M3U8")) { videoSource = new HlsMediaSource.Factory(dataSourceFactory) .setAllowChunklessPreparation(true) .createMediaSource(mp4VideoUri, null, null); } else { mp4VideoUri = Uri.parse(urlToPlay); videoSource = new ExtractorMediaSource.Factory(dataSourceFactory).setExtractorsFactory(new DefaultExtractorsFactory()).createMediaSource((mp4VideoUri)); } // Prepare the player with the source. if (mPlayer != null && videoSource != null) { mPlayer.prepare(videoSource); mPlayer.setPlayWhenReady(true); } } public void pausePlayer() { if (mPlayer != null) { mPlayer.setPlayWhenReady(false); mPlayer.getPlaybackState(); } } public void resumePlayer() { if (mPlayer != null) { mPlayer.setPlayWhenReady(true); mPlayer.getPlaybackState(); } } public Boolean isPlayerPlaying() { return mPlayer.getPlayWhenReady(); } public ArrayList<String> readURLs(String url) { if (url == null) return null; ArrayList<String> allURls = new ArrayList<String>(); try { URL urls = new URL(url); BufferedReader in = new BufferedReader(new InputStreamReader(urls .openStream())); String str; while ((str = in.readLine()) != null) { allURls.add(str); } in.close(); return allURls; } catch (Exception e) { e.printStackTrace(); return null; } } }
Create a PlayerActivity
In src folder and create a new activity, Go to File =>Activity =>choose EmptyActivity template with XML layout. Now open the activity_player.xml and below code
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".PlayerActivity"> <com.google.android.exoplayer2.ui.PlayerView android:id="@+id/mPlayerView" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#A6000000" app:controller_layout_id="@layout/playback_control_view" app:player_layout_id="@layout/exo_simple_player_view" app:repeat_toggle_modes="none" app:show_timeout="45000" app:surface_type="texture_view"/> </android.support.constraint.ConstraintLayout>
Open PlayerActivity.java and bind player view with ExoPlayer using below code
package com.androidwave.videoplayer; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import com.google.android.exoplayer2.ui.PlayerView; public class PlayerActivity extends AppCompatActivity implements CallBacks.playerCallBack { String mFilePath = null; private static final String FILE_PATH = "PlayerActivity"; public static Intent getStartIntent(Context context, String mFilePath) { Intent intent = new Intent(context, PlayerActivity.class); intent.putExtra(FILE_PATH, mFilePath); return intent; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_player); if (getIntent().hasExtra(FILE_PATH)) { mFilePath = getIntent().getStringExtra(FILE_PATH); } PlayerView mPlayerView = findViewById(R.id.mPlayerView); mPlayerView.setPlayer(PlayerManager.getSharedInstance(PlayerActivity.this).getPlayerView().getPlayer()); PlayerManager.getSharedInstance(PlayerActivity.this).playStream(mFilePath); PlayerManager.getSharedInstance(this).setPlayerListener(this); } @Override public void onItemClickOnItem(Integer albumId) { } @Override public void onPlayingEnd() { PlayerManager.getSharedInstance(PlayerActivity.this).pausePlayer(); finish(); } @Override protected void onPause() { super.onPause(); PlayerManager.getSharedInstance(PlayerActivity.this).pausePlayer(); } @Override protected void onResume() { super.onResume(); PlayerManager.getSharedInstance(PlayerActivity.this).resumePlayer(); } @Override protected void onDestroy() { super.onDestroy(); PlayerManager.getSharedInstance(PlayerActivity.this).pausePlayer(); } }
4 Comments
How to play in service with playlist?
sir how we can zoom video using exoplayer
i don’t find class Interface Callbacks in your tutorial. I will add this comment here. Thanks for your post
public interface CallBacks {
void callbackObserver(Object object);
interface playerCallBack{
void onItemClickOnItem(int albumId);
void onPlayingEnd();
}
}
I have a vidéo player and i have implement a search bar the search bar filter the vidéo list but not update the position when you to vidéo In filtered list it play another video .car you make a tutorial for this